Skip to content

Commit

Permalink
Generate mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
Alduino committed Jul 16, 2022
1 parent b7d84c8 commit eb7ec26
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 16 deletions.
1 change: 1 addition & 0 deletions project/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 1,2 @@
export {nappiPlugin} from "./plugin";
export type {InferResponse} from "./inferResponse";
59 changes: 54 additions & 5 deletions project/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
import {existsSync, readdirSync, statSync} from "fs";
import {existsSync, mkdirSync, readdirSync, statSync, writeFileSync} from "fs";
import {posix} from "path";
import {NextConfig} from "next";
import {name} from "../package.json";
Expand Down Expand Up @@ -26,22 26,71 @@ function safifyIdentifier(from: string) {

export interface NappiPluginConfig {
baseDir?: string;
tsOut?: string;
}

function getApiUrl(path: string) {
const metadata = {
hasDynamicRoutes: false,
fullPathWithDynamic: ""
};

if (path.endsWith("/index")) path = path.substring(0, path.length - "/index".length);

path = "/api/" path;

metadata.fullPathWithDynamic = path;

path = path.replace(/\[[^\]] ]/g, () => {
metadata.hasDynamicRoutes = true;
return "${string}";
});

return {
fullPath: path,
metadata
};
}

function getApiMap(path: string) {
const {fullPath, metadata} = getApiUrl(path);

const type = `InferResponse<typeof ${safifyIdentifier(path)}>`;

if (metadata.hasDynamicRoutes) {
return [
`[_: \`${fullPath}\`]: ${type}`,
`["${metadata.fullPathWithDynamic}"]: never`
];
} else {
return `[\`${fullPath}\`]: ${type}`;
}
}

export function nappiPlugin(baseConfig: NextConfig, config: NappiPluginConfig = {}): NextConfig {
const {baseDir = getBaseDir()} = config;
const {baseDir = getBaseDir(), tsOut = "types/nappi.d.ts"} = config;

const tsRelativeToApi = posix.relative(posix.dirname(tsOut), baseDir);

const files = recursiveReaddir(baseDir)
.filter(file => file.endsWith(".ts") || file.endsWith(".tsx"))
.map(file => posix.relative(baseDir "/", file))
.map(file => file.replace(/\.. $/, ""));

const sourceFile = [
`import {InferResponse} from "${name}"`,
...files.map(path => `import ${safifyIdentifier(path)} from "${path}";`),
`declare module "${name}" {`,
"import { NextApiHandler } from \"next\";",
...files.map(path => `import type {default as ${safifyIdentifier(path)}} from "${posix.join(tsRelativeToApi, path)}";`),
"export type InferResponse<Handler> = Handler extends NextApiHandler<infer Response> ? Response : never;",
"type SafeNappiMapping = {",
...files.map(path => ` ["${path}"]: InferResponse<${safifyIdentifier(path)}>`),
...files.flatMap(path => getApiMap(path)).map(line => line ";"),
"}",
"export function jsonFetch<Path extends keyof SafeNappiMapping>(path: Path): Promise<SafeNappiMapping[Path]>;",
"}"
].join("\n");

mkdirSync(posix.dirname(tsOut), {recursive: true});
writeFileSync(tsOut, sourceFile);

return baseConfig;
}
16 changes: 6 additions & 10 deletions test/pages/unsafeUser.tsx
Original file line number Diff line number Diff line change
@@ -1,18 1,14 @@
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { UnsafeUserRes } from "./api/unsafeUser";
import {jsonFetch} from "safe-nappi";

async function fetch() {
const res = await jsonFetch("");
}

const UnsafeUser = () => {
const [userRes, setUserRes] = useState<UnsafeUserRes | null>(null);

useEffect(() => {
fetch("/api/unsafeUser")
.then((res) => res.json())
.then(async (data) => {
await new Promise((res) => setTimeout(res, 2000));
setUserRes(data);
});
});

return (
<div>{userRes ? <pre>{JSON.stringify(userRes)}</pre> : "Loading..."}</div>
);
Expand Down
3 changes: 2 additions & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 6,8 @@
"baseUrl": ".",
"paths": {
"~/*": ["./*"]
}
},
"types": ["./types/nappi.d.ts"]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
Expand Down
14 changes: 14 additions & 0 deletions test/types/nappi.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 1,14 @@
declare module "safe-nappi" {
import { NextApiHandler } from "next";
import type {default as unsafeUser} from "../pages/api/unsafeUser";
import type {default as users_index} from "../pages/api/users/index";
import type {default as users_id_index} from "../pages/api/users/[id]/index";
export type InferResponse<Handler> = Handler extends NextApiHandler<infer Response> ? Response : never;
type SafeNappiMapping = {
[`/api/unsafeUser`]: InferResponse<typeof unsafeUser>;
[`/api/users`]: InferResponse<typeof users_index>;
[_: `/api/users/${string}`]: InferResponse<typeof users_id_index>;
["/api/users/[id]"]: never;
}
export function jsonFetch<Path extends keyof SafeNappiMapping>(path: Path): Promise<SafeNappiMapping[Path]>;
}

0 comments on commit eb7ec26

Please sign in to comment.