rpc4next 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -11
- package/dist/cli/cli-handler.d.ts +2 -2
- package/dist/cli/cli-handler.js +5 -4
- package/dist/cli/constants.d.ts +7 -0
- package/dist/cli/constants.js +9 -0
- package/dist/cli/core/cache.d.ts +1 -1
- package/dist/cli/core/constants.d.ts +0 -1
- package/dist/cli/core/constants.js +1 -2
- package/dist/cli/core/generate-path-structure.js +1 -8
- package/dist/cli/core/route-scanner.d.ts +11 -8
- package/dist/cli/core/route-scanner.js +98 -81
- package/dist/cli/core/scan-utils.js +2 -2
- package/dist/cli/generator.d.ts +1 -1
- package/dist/cli/generator.js +9 -4
- package/dist/cli/logger.d.ts +2 -1
- package/dist/cli/logger.js +24 -4
- package/dist/cli/types.d.ts +17 -5
- package/dist/cli/watcher.d.ts +1 -1
- package/dist/cli/watcher.js +11 -6
- package/package.json +1 -1
- package/dist/cli/core/types.d.ts +0 -2
- package/dist/cli/core/types.js +0 -2
package/README.md
CHANGED
|
@@ -8,15 +8,12 @@ Inspired by Hono RPC and Pathpida, **rpc4next** automatically generates a type-s
|
|
|
8
8
|
|
|
9
9
|
## ✨ Features
|
|
10
10
|
|
|
11
|
-
- ✅ 既存の `app/**/route.ts` および `app/**/page.tsx` を活用するため、新たなハンドラファイルの作成は不要
|
|
12
11
|
- ✅ ルート、パラメータ、クエリパラメータ、 リクエストボディ、レスポンスの型安全なクライアント生成
|
|
12
|
+
- ✅ 既存の `app/**/route.ts` および `app/**/page.tsx` を活用するため、新たなハンドラファイルの作成は不要
|
|
13
13
|
- ✅ 最小限のセットアップで、カスタムサーバー不要
|
|
14
14
|
- ✅ 動的ルート(`[id]`、`[...slug]` など)に対応
|
|
15
15
|
- ✅ CLI による自動クライアント用型定義生成
|
|
16
16
|
|
|
17
|
-
> **注意**
|
|
18
|
-
> RPCとしてresponseの戻り値の推論が機能するのは、対象となる `route.ts` の HTTPメソッドハンドラ内で`NextResponse.json()` をしている物のみになります。
|
|
19
|
-
|
|
20
17
|
---
|
|
21
18
|
|
|
22
19
|
## 🚀 Getting Started
|
|
@@ -54,7 +51,7 @@ export async function GET(
|
|
|
54
51
|
|
|
55
52
|
🚩 Query or OptionalQuery 型を export することで、searchParams の型も自動的にクライアントに反映されます。
|
|
56
53
|
|
|
57
|
-
- **RPCとしてresponseの戻り値の推論が機能するのは、対象となる `route.ts` の HTTPメソッドハンドラ内で`NextResponse.json()`
|
|
54
|
+
- **RPCとしてresponseの戻り値の推論が機能するのは、対象となる `route.ts` の HTTPメソッドハンドラ内で`NextResponse.json()` をしている関数のみになります**
|
|
58
55
|
|
|
59
56
|
---
|
|
60
57
|
|
|
@@ -85,8 +82,6 @@ npx rpc4next <baseDir> <outputPath>
|
|
|
85
82
|
npx rpc4next <baseDir> <outputPath> --generate-params-types <paramsFileName>
|
|
86
83
|
```
|
|
87
84
|
|
|
88
|
-
※ このオプションを指定する際は、必ずファイル名をセットしてください。ファイル名が指定されない場合、エラーが発生します。
|
|
89
|
-
|
|
90
85
|
---
|
|
91
86
|
|
|
92
87
|
### 4. Create Your RPC Client
|
|
@@ -152,10 +147,7 @@ const createRouteHandler = routeHandlerFactory((err, rc) =>
|
|
|
152
147
|
rc.text("error", { status: 400 })
|
|
153
148
|
);
|
|
154
149
|
|
|
155
|
-
const { POST } = createRouteHandler().post(
|
|
156
|
-
async (rc) => rc.json("json response"),
|
|
157
|
-
async (rc) => rc.text("plain text")
|
|
158
|
-
);
|
|
150
|
+
const { POST } = createRouteHandler().post(async (rc) => rc.text("plain text"));
|
|
159
151
|
```
|
|
160
152
|
|
|
161
153
|
これだけで、POST リクエストの返り値が、responseの内容(json,textなど)、status,contenttypeが型付けされるようになります。
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { CliOptions, Logger } from "./types";
|
|
2
|
-
export declare const handleCli: (baseDir: string, outputPath: string, options: CliOptions, logger: Logger) =>
|
|
1
|
+
import type { CliOptions, ExitCode, Logger } from "./types";
|
|
2
|
+
export declare const handleCli: (baseDir: string, outputPath: string, options: CliOptions, logger: Logger) => ExitCode;
|
package/dist/cli/cli-handler.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.handleCli = void 0;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const constants_1 = require("./constants");
|
|
8
9
|
const generator_1 = require("./generator");
|
|
9
10
|
const watcher_1 = require("./watcher");
|
|
10
11
|
const handleCli = (baseDir, outputPath, options, logger) => {
|
|
@@ -13,14 +14,14 @@ const handleCli = (baseDir, outputPath, options, logger) => {
|
|
|
13
14
|
const paramsFileName = typeof options.paramsFile === "string" ? options.paramsFile : null;
|
|
14
15
|
if (options.paramsFile !== undefined && !paramsFileName) {
|
|
15
16
|
logger.error("Error: --params-file requires a filename.");
|
|
16
|
-
return
|
|
17
|
+
return constants_1.EXIT_FAILURE;
|
|
17
18
|
}
|
|
18
19
|
if (options.watch) {
|
|
19
20
|
(0, watcher_1.setupWatcher)(resolvedBaseDir, () => {
|
|
20
21
|
(0, generator_1.generate)({
|
|
21
22
|
baseDir: resolvedBaseDir,
|
|
22
23
|
outputPath: resolvedOutputPath,
|
|
23
|
-
paramsFileName
|
|
24
|
+
paramsFileName,
|
|
24
25
|
logger,
|
|
25
26
|
});
|
|
26
27
|
}, logger);
|
|
@@ -29,10 +30,10 @@ const handleCli = (baseDir, outputPath, options, logger) => {
|
|
|
29
30
|
(0, generator_1.generate)({
|
|
30
31
|
baseDir: resolvedBaseDir,
|
|
31
32
|
outputPath: resolvedOutputPath,
|
|
32
|
-
paramsFileName
|
|
33
|
+
paramsFileName,
|
|
33
34
|
logger,
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
|
-
return
|
|
37
|
+
return constants_1.EXIT_SUCCESS;
|
|
37
38
|
};
|
|
38
39
|
exports.handleCli = handleCli;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ErrorExitCode, SuccessExitCode } from "./types";
|
|
2
|
+
export declare const END_POINT_FILE_NAMES: readonly ["page.tsx", "route.ts"];
|
|
3
|
+
export declare const EXIT_SUCCESS: SuccessExitCode;
|
|
4
|
+
export declare const EXIT_FAILURE: ErrorExitCode;
|
|
5
|
+
export declare const SUCCESS_INDENT_LEVEL = 1;
|
|
6
|
+
export declare const SUCCESS_PAD_LENGTH = 20;
|
|
7
|
+
export declare const SUCCESS_SEPARATOR = "\u2192";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUCCESS_SEPARATOR = exports.SUCCESS_PAD_LENGTH = exports.SUCCESS_INDENT_LEVEL = exports.EXIT_FAILURE = exports.EXIT_SUCCESS = exports.END_POINT_FILE_NAMES = void 0;
|
|
4
|
+
exports.END_POINT_FILE_NAMES = ["page.tsx", "route.ts"];
|
|
5
|
+
exports.EXIT_SUCCESS = 0;
|
|
6
|
+
exports.EXIT_FAILURE = 1;
|
|
7
|
+
exports.SUCCESS_INDENT_LEVEL = 1;
|
|
8
|
+
exports.SUCCESS_PAD_LENGTH = 20;
|
|
9
|
+
exports.SUCCESS_SEPARATOR = "→";
|
package/dist/cli/core/cache.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RPC4NEXT_CLIENT_IMPORT_PATH = exports.TYPE_KEYS = exports.TYPE_KEY_PARAMS = exports.TYPE_KEY_OPTIONAL_QUERY = exports.TYPE_KEY_QUERY = exports.TYPE_END_POINT = exports.TYPE_SEPARATOR = exports.STATEMENT_TERMINATOR = exports.NEWLINE = exports.INDENT = exports.QUERY_TYPES =
|
|
4
|
-
exports.END_POINT_FILE_NAMES = ["page.tsx", "route.ts"];
|
|
3
|
+
exports.RPC4NEXT_CLIENT_IMPORT_PATH = exports.TYPE_KEYS = exports.TYPE_KEY_PARAMS = exports.TYPE_KEY_OPTIONAL_QUERY = exports.TYPE_KEY_QUERY = exports.TYPE_END_POINT = exports.TYPE_SEPARATOR = exports.STATEMENT_TERMINATOR = exports.NEWLINE = exports.INDENT = exports.QUERY_TYPES = void 0;
|
|
5
4
|
exports.QUERY_TYPES = ["Query", "OptionalQuery"];
|
|
6
5
|
exports.INDENT = " ";
|
|
7
6
|
exports.NEWLINE = "\n";
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.generatePages = void 0;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
4
|
const constants_1 = require("./constants");
|
|
10
5
|
const route_scanner_1 = require("./route-scanner");
|
|
11
6
|
const type_utils_1 = require("./type-utils");
|
|
@@ -20,9 +15,7 @@ const generatePages = (outputPath, baseDir) => {
|
|
|
20
15
|
: "";
|
|
21
16
|
const keyTypes = constants_1.TYPE_KEYS.filter((type) => pathStructure.includes(type));
|
|
22
17
|
const keyTypesImportStr = (0, type_utils_1.createImport)(keyTypes.join(" ,"), constants_1.RPC4NEXT_CLIENT_IMPORT_PATH);
|
|
23
|
-
const dirParamsTypes = paramsTypes.map(({ paramsType,
|
|
24
|
-
const stats = fs_1.default.statSync(filePath);
|
|
25
|
-
const dirPath = stats.isFile() ? path_1.default.dirname(filePath) : filePath;
|
|
18
|
+
const dirParamsTypes = paramsTypes.map(({ paramsType, dirPath }) => {
|
|
26
19
|
const params = `export type Params = ${paramsType}${constants_1.STATEMENT_TERMINATOR}`;
|
|
27
20
|
return {
|
|
28
21
|
paramsType: params,
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
* Inspired by pathpida (https://github.com/aspida/pathpida),
|
|
3
3
|
* especially the design and UX of its CLI.
|
|
4
4
|
*/
|
|
5
|
+
type ImportObj = {
|
|
6
|
+
statement: string;
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
type ParamsType = {
|
|
10
|
+
paramsType: string;
|
|
11
|
+
dirPath: string;
|
|
12
|
+
};
|
|
5
13
|
export declare const hasTargetFiles: (dirPath: string) => boolean;
|
|
6
14
|
export declare const scanAppDir: (output: string, input: string, indent?: string, parentParams?: {
|
|
7
15
|
paramName: string;
|
|
@@ -14,12 +22,7 @@ export declare const scanAppDir: (output: string, input: string, indent?: string
|
|
|
14
22
|
};
|
|
15
23
|
}[]) => {
|
|
16
24
|
pathStructure: string;
|
|
17
|
-
imports:
|
|
18
|
-
|
|
19
|
-
path: string;
|
|
20
|
-
}[];
|
|
21
|
-
paramsTypes: {
|
|
22
|
-
paramsType: string;
|
|
23
|
-
path: string;
|
|
24
|
-
}[];
|
|
25
|
+
imports: ImportObj[];
|
|
26
|
+
paramsTypes: ParamsType[];
|
|
25
27
|
};
|
|
28
|
+
export {};
|
|
@@ -15,9 +15,9 @@ const constants_1 = require("./constants");
|
|
|
15
15
|
const scan_utils_1 = require("./scan-utils");
|
|
16
16
|
const type_utils_1 = require("./type-utils");
|
|
17
17
|
const constants_2 = require("../../lib/constants");
|
|
18
|
-
const
|
|
18
|
+
const constants_3 = require("../constants");
|
|
19
|
+
const endPointFileNames = new Set(constants_3.END_POINT_FILE_NAMES);
|
|
19
20
|
const hasTargetFiles = (dirPath) => {
|
|
20
|
-
// Return cached result if available
|
|
21
21
|
if (cache_1.visitedDirsCache.has(dirPath))
|
|
22
22
|
return cache_1.visitedDirsCache.get(dirPath);
|
|
23
23
|
const entries = fs_1.default.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -25,7 +25,7 @@ const hasTargetFiles = (dirPath) => {
|
|
|
25
25
|
const { name } = entry;
|
|
26
26
|
const entryPath = path_1.default.join(dirPath, name);
|
|
27
27
|
if (name === "node_modules" ||
|
|
28
|
-
//
|
|
28
|
+
// private
|
|
29
29
|
name.startsWith("_") ||
|
|
30
30
|
// intercepts
|
|
31
31
|
name.startsWith("(.)") ||
|
|
@@ -38,137 +38,154 @@ const hasTargetFiles = (dirPath) => {
|
|
|
38
38
|
cache_1.visitedDirsCache.set(dirPath, true);
|
|
39
39
|
return true;
|
|
40
40
|
}
|
|
41
|
-
if (entry.isDirectory()) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
41
|
+
if (entry.isDirectory() && (0, exports.hasTargetFiles)(entryPath)) {
|
|
42
|
+
cache_1.visitedDirsCache.set(dirPath, true);
|
|
43
|
+
return true;
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
cache_1.visitedDirsCache.set(dirPath, false);
|
|
49
47
|
return false;
|
|
50
48
|
};
|
|
51
49
|
exports.hasTargetFiles = hasTargetFiles;
|
|
50
|
+
const extractParamInfo = (entryName, { isDynamic, isCatchAll, isOptionalCatchAll, }) => {
|
|
51
|
+
let param = entryName;
|
|
52
|
+
// Remove brackets if it's a dynamic segment
|
|
53
|
+
if (isDynamic) {
|
|
54
|
+
param = param.replace(/^\[+|\]+$/g, "");
|
|
55
|
+
}
|
|
56
|
+
// Remove leading "..." if it's a catch-all segment
|
|
57
|
+
if (isCatchAll || isOptionalCatchAll) {
|
|
58
|
+
param = param.replace(/^\.{3}/, "");
|
|
59
|
+
}
|
|
60
|
+
const prefix = isOptionalCatchAll
|
|
61
|
+
? constants_2.OPTIONAL_CATCH_ALL_PREFIX
|
|
62
|
+
: isCatchAll
|
|
63
|
+
? constants_2.CATCH_ALL_PREFIX
|
|
64
|
+
: isDynamic
|
|
65
|
+
? constants_2.DYNAMIC_PREFIX
|
|
66
|
+
: "";
|
|
67
|
+
return { paramName: param, keyName: `${prefix}${param}` };
|
|
68
|
+
};
|
|
52
69
|
const scanAppDir = (output, input, indent = "", parentParams = []) => {
|
|
53
|
-
if (cache_1.scanAppDirCache.has(input))
|
|
70
|
+
if (cache_1.scanAppDirCache.has(input))
|
|
54
71
|
return cache_1.scanAppDirCache.get(input);
|
|
55
|
-
|
|
56
|
-
indent
|
|
72
|
+
const previousIndent = indent;
|
|
73
|
+
const currentIndent = indent + constants_1.INDENT;
|
|
57
74
|
const pathStructures = [];
|
|
58
75
|
const imports = [];
|
|
59
|
-
const
|
|
60
|
-
const params = [...parentParams];
|
|
76
|
+
const typeFragments = [];
|
|
61
77
|
const paramsTypes = [];
|
|
78
|
+
const params = [...parentParams];
|
|
79
|
+
// Get entries under the directory (only target files or directories) and sort them
|
|
62
80
|
const entries = fs_1.default
|
|
63
81
|
.readdirSync(input, { withFileTypes: true })
|
|
64
82
|
.filter((entry) => {
|
|
65
|
-
const { name } = entry;
|
|
66
83
|
if (entry.isDirectory()) {
|
|
67
|
-
const dirPath = path_1.default.join(input, name);
|
|
84
|
+
const dirPath = path_1.default.join(input, entry.name);
|
|
68
85
|
return (0, exports.hasTargetFiles)(dirPath);
|
|
69
86
|
}
|
|
70
87
|
return endPointFileNames.has(entry.name);
|
|
71
88
|
})
|
|
72
89
|
.sort();
|
|
73
|
-
|
|
74
|
-
const fullPath = path_1.default.join(input, entry.name);
|
|
75
|
-
const nameWithoutExt = entry.isFile()
|
|
76
|
-
? entry.name.replace(/\.[^/.]+$/, "")
|
|
77
|
-
: entry.name;
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const fullPath = path_1.default.join(input, entry.name).replace(/\\/g, "/");
|
|
78
92
|
if (entry.isDirectory()) {
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
isGroup,
|
|
107
|
-
isParallel,
|
|
108
|
-
isOptionalCatchAll,
|
|
109
|
-
isCatchAll,
|
|
110
|
-
isDynamic,
|
|
111
|
-
};
|
|
112
|
-
nextParams = [...params, { paramName, routeType }];
|
|
113
|
-
}
|
|
93
|
+
const entryName = entry.name;
|
|
94
|
+
const isGroup = entryName.startsWith("(") && entryName.endsWith(")");
|
|
95
|
+
const isParallel = entryName.startsWith("@");
|
|
96
|
+
const isOptionalCatchAll = entryName.startsWith("[[...") && entryName.endsWith("]]");
|
|
97
|
+
const isCatchAll = entryName.startsWith("[...") && entryName.endsWith("]");
|
|
98
|
+
const isDynamic = entryName.startsWith("[") && entryName.endsWith("]");
|
|
99
|
+
const { paramName, keyName } = extractParamInfo(entryName, {
|
|
100
|
+
isDynamic,
|
|
101
|
+
isCatchAll,
|
|
102
|
+
isOptionalCatchAll,
|
|
103
|
+
});
|
|
104
|
+
// If it's a dynamic segment, inherit the parameters
|
|
105
|
+
const nextParams = isDynamic || isCatchAll || isOptionalCatchAll
|
|
106
|
+
? [
|
|
107
|
+
...params,
|
|
108
|
+
{
|
|
109
|
+
paramName,
|
|
110
|
+
routeType: {
|
|
111
|
+
isDynamic,
|
|
112
|
+
isCatchAll,
|
|
113
|
+
isOptionalCatchAll,
|
|
114
|
+
isGroup,
|
|
115
|
+
isParallel,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
]
|
|
119
|
+
: params;
|
|
114
120
|
const isSkipDir = isGroup || isParallel;
|
|
115
|
-
const { pathStructure: childPathStructure, imports: childImports, paramsTypes: childParamsTypes, } = (0, exports.scanAppDir)(output, fullPath, isSkipDir ?
|
|
121
|
+
const { pathStructure: childPathStructure, imports: childImports, paramsTypes: childParamsTypes, } = (0, exports.scanAppDir)(output, fullPath, isSkipDir ? previousIndent : currentIndent, nextParams);
|
|
116
122
|
imports.push(...childImports);
|
|
117
123
|
paramsTypes.push(...childParamsTypes);
|
|
118
124
|
if (isSkipDir) {
|
|
119
|
-
// Extract the
|
|
125
|
+
// Extract only the inner part inside `{}` from the child output
|
|
120
126
|
const match = childPathStructure.match(/^\s*\{([\s\S]*)\}\s*$/);
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
pathStructures.push(`${
|
|
127
|
+
const childContent = match ? match[1].trim() : "";
|
|
128
|
+
if (childContent) {
|
|
129
|
+
pathStructures.push(`${currentIndent}${childContent}`);
|
|
124
130
|
}
|
|
125
131
|
}
|
|
126
132
|
else {
|
|
127
|
-
pathStructures.push(`${
|
|
133
|
+
pathStructures.push(`${currentIndent}"${keyName}": ${childPathStructure}`);
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
else {
|
|
137
|
+
// Process endpoint file
|
|
131
138
|
const queryDef = (0, scan_utils_1.scanQuery)(output, fullPath);
|
|
132
139
|
if (queryDef) {
|
|
133
|
-
const { importStatement
|
|
134
|
-
imports.push({ statement, path });
|
|
135
|
-
|
|
140
|
+
const { importStatement, importPath, type } = queryDef;
|
|
141
|
+
imports.push({ statement: importStatement, path: importPath });
|
|
142
|
+
typeFragments.push(type);
|
|
136
143
|
}
|
|
144
|
+
// Process routes for each HTTP method (excluding OPTIONS)
|
|
137
145
|
constants_2.HTTP_METHODS_EXCLUDE_OPTIONS.forEach((method) => {
|
|
138
146
|
const routeDef = (0, scan_utils_1.scanRoute)(output, fullPath, method);
|
|
139
147
|
if (routeDef) {
|
|
140
|
-
const { importStatement
|
|
141
|
-
imports.push({ statement, path });
|
|
142
|
-
|
|
148
|
+
const { importStatement, importPath, type } = routeDef;
|
|
149
|
+
imports.push({ statement: importStatement, path: importPath });
|
|
150
|
+
typeFragments.push(type);
|
|
143
151
|
}
|
|
144
152
|
});
|
|
145
|
-
|
|
153
|
+
// Add endpoint type
|
|
154
|
+
typeFragments.push(constants_1.TYPE_END_POINT);
|
|
155
|
+
// If parameters exist, generate their type definition
|
|
146
156
|
if (params.length > 0) {
|
|
147
|
-
const fields = params.map((
|
|
148
|
-
const
|
|
149
|
-
const { isCatchAll, isOptionalCatchAll } = routeType;
|
|
150
|
-
const paramType = isCatchAll
|
|
157
|
+
const fields = params.map(({ paramName, routeType }) => {
|
|
158
|
+
const paramType = routeType.isCatchAll
|
|
151
159
|
? "string[]"
|
|
152
|
-
: isOptionalCatchAll
|
|
160
|
+
: routeType.isOptionalCatchAll
|
|
153
161
|
? "string[] | undefined"
|
|
154
162
|
: "string";
|
|
155
163
|
return { name: paramName, type: paramType };
|
|
156
164
|
});
|
|
157
|
-
const
|
|
158
|
-
paramsTypes.push({
|
|
159
|
-
|
|
165
|
+
const paramsTypeStr = (0, type_utils_1.createObjectType)(fields);
|
|
166
|
+
paramsTypes.push({
|
|
167
|
+
paramsType: paramsTypeStr,
|
|
168
|
+
dirPath: path_1.default.dirname(fullPath),
|
|
169
|
+
});
|
|
170
|
+
typeFragments.push((0, type_utils_1.createRecodeType)(constants_1.TYPE_KEY_PARAMS, paramsTypeStr));
|
|
160
171
|
}
|
|
161
172
|
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
}
|
|
174
|
+
// Combine all type definitions
|
|
175
|
+
const typeString = typeFragments.join(" & ");
|
|
176
|
+
// Construct the nested path structure
|
|
177
|
+
const pathStructureBody = pathStructures.length > 0
|
|
178
|
+
? `{${constants_1.NEWLINE}${pathStructures.join(`,${constants_1.NEWLINE}`)}${constants_1.NEWLINE}${previousIndent}}`
|
|
179
|
+
: "";
|
|
180
|
+
const pathStructure = typeString && pathStructureBody
|
|
181
|
+
? `${typeString} & ${pathStructureBody}`
|
|
182
|
+
: typeString || pathStructureBody;
|
|
167
183
|
const result = {
|
|
168
184
|
pathStructure,
|
|
169
185
|
imports,
|
|
170
186
|
paramsTypes,
|
|
171
187
|
};
|
|
188
|
+
// Cache the result for reuse
|
|
172
189
|
cache_1.scanAppDirCache.set(input, result);
|
|
173
190
|
return result;
|
|
174
191
|
};
|
|
@@ -24,7 +24,7 @@ const scanFile = (outputFile, inputFile, findCallBack, typeCallBack) => {
|
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
26
|
exports.scanFile = scanFile;
|
|
27
|
-
// query
|
|
27
|
+
// Create query definitions
|
|
28
28
|
const scanQuery = (outputFile, inputFile) => {
|
|
29
29
|
return (0, exports.scanFile)(outputFile, inputFile, (fileContents) => {
|
|
30
30
|
return constants_1.QUERY_TYPES.find((type) => new RegExp(`export (interface ${type} ?{|type ${type} ?=)`).test(fileContents));
|
|
@@ -33,7 +33,7 @@ const scanQuery = (outputFile, inputFile) => {
|
|
|
33
33
|
: (0, type_utils_1.createRecodeType)(constants_1.TYPE_KEY_OPTIONAL_QUERY, importAlias));
|
|
34
34
|
};
|
|
35
35
|
exports.scanQuery = scanQuery;
|
|
36
|
-
// route
|
|
36
|
+
// Create route definitions
|
|
37
37
|
const scanRoute = (outputFile, inputFile, httpMethod) => {
|
|
38
38
|
return (0, exports.scanFile)(outputFile, inputFile, (fileContents) => {
|
|
39
39
|
return [httpMethod].find((method) => new RegExp(`export (async )?(function ${method} ?\\(|const ${method} ?=|\\{[^}]*\\b${method}\\b[^}]*\\} ?=|const \\{[^}]*\\b${method}\\b[^}]*\\} ?=|\\{[^}]*\\b${method}\\b[^}]*\\} from)`).test(fileContents));
|
package/dist/cli/generator.d.ts
CHANGED
package/dist/cli/generator.js
CHANGED
|
@@ -5,18 +5,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.generate = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const constants_1 = require("./constants");
|
|
8
10
|
const generate_path_structure_1 = require("./core/generate-path-structure");
|
|
11
|
+
const logger_1 = require("./logger");
|
|
9
12
|
const generate = ({ baseDir, outputPath, paramsFileName, logger, }) => {
|
|
10
|
-
logger.info("
|
|
13
|
+
logger.info("Types regenerated due to file change", { event: "generate" });
|
|
11
14
|
const { pathStructure, paramsTypes } = (0, generate_path_structure_1.generatePages)(outputPath, baseDir);
|
|
12
15
|
fs_1.default.writeFileSync(outputPath, pathStructure);
|
|
13
|
-
logger.success(
|
|
16
|
+
logger.success((0, logger_1.padMessage)("Path structure type", path_1.default.relative(process.cwd(), outputPath), constants_1.SUCCESS_SEPARATOR, constants_1.SUCCESS_PAD_LENGTH), { indentLevel: constants_1.SUCCESS_INDENT_LEVEL });
|
|
14
17
|
if (paramsFileName) {
|
|
15
18
|
paramsTypes.forEach(({ paramsType, dirPath }) => {
|
|
16
|
-
const filePath =
|
|
19
|
+
const filePath = path_1.default.join(dirPath, paramsFileName);
|
|
17
20
|
fs_1.default.writeFileSync(filePath, paramsType);
|
|
18
21
|
});
|
|
19
|
-
logger.success(
|
|
22
|
+
logger.success((0, logger_1.padMessage)("Params types", paramsFileName, constants_1.SUCCESS_SEPARATOR, constants_1.SUCCESS_PAD_LENGTH), {
|
|
23
|
+
indentLevel: constants_1.SUCCESS_INDENT_LEVEL,
|
|
24
|
+
});
|
|
20
25
|
}
|
|
21
26
|
};
|
|
22
27
|
exports.generate = generate;
|
package/dist/cli/logger.d.ts
CHANGED
package/dist/cli/logger.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createLogger = void 0;
|
|
6
|
+
exports.createLogger = exports.padMessage = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const constants_1 = require("./core/constants");
|
|
9
|
+
const padMessage = (label, value, separator = "→", targetLength = 24) => {
|
|
10
|
+
return label.padEnd(targetLength) + ` ${separator} ${value}`;
|
|
11
|
+
};
|
|
12
|
+
exports.padMessage = padMessage;
|
|
13
|
+
const createIndent = (level = 0) => constants_1.INDENT.repeat(level);
|
|
4
14
|
const createLogger = () => {
|
|
5
15
|
return {
|
|
6
|
-
info: (msg) =>
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
info: (msg, options = {}) => {
|
|
17
|
+
const { indentLevel = 0, event } = options;
|
|
18
|
+
const prefix = event ? `${chalk_1.default.cyan(`[${event}]`)} ` : "";
|
|
19
|
+
console.log(`${createIndent(indentLevel)}${prefix}${msg}`);
|
|
20
|
+
},
|
|
21
|
+
success: (msg, options = {}) => {
|
|
22
|
+
const { indentLevel = 0 } = options;
|
|
23
|
+
console.log(`${createIndent(indentLevel)}${chalk_1.default.green("✓")} ${msg}`);
|
|
24
|
+
},
|
|
25
|
+
error: (msg, options = {}) => {
|
|
26
|
+
const { indentLevel = 0 } = options;
|
|
27
|
+
console.error(`${createIndent(indentLevel)}${chalk_1.default.red("✗")} ${chalk_1.default.red(msg)}`);
|
|
28
|
+
},
|
|
9
29
|
};
|
|
10
30
|
};
|
|
11
31
|
exports.createLogger = createLogger;
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
success: (msg: string) => void;
|
|
4
|
-
error: (msg: string) => void;
|
|
5
|
-
}
|
|
1
|
+
import type { END_POINT_FILE_NAMES } from "./constants";
|
|
2
|
+
export type EndPointFileNames = (typeof END_POINT_FILE_NAMES)[number];
|
|
6
3
|
export interface CliOptions {
|
|
7
4
|
watch?: boolean;
|
|
8
5
|
paramsFile?: string;
|
|
9
6
|
}
|
|
7
|
+
type LogOptions = {
|
|
8
|
+
indentLevel?: number;
|
|
9
|
+
event?: string;
|
|
10
|
+
};
|
|
11
|
+
export interface Logger {
|
|
12
|
+
info: (msg: string, options?: LogOptions) => void;
|
|
13
|
+
success: (msg: string, options?: Pick<LogOptions, "indentLevel">) => void;
|
|
14
|
+
error: (msg: string, options?: Pick<LogOptions, "indentLevel">) => void;
|
|
15
|
+
}
|
|
16
|
+
type BuildRange<N extends number, Result extends number[] = []> = Result["length"] extends N ? Result : BuildRange<N, [...Result, Result["length"]]>;
|
|
17
|
+
type NumericRange<F extends number, T extends number> = Exclude<BuildRange<T>, BuildRange<F>> | F;
|
|
18
|
+
export type SuccessExitCode = 0;
|
|
19
|
+
export type ErrorExitCode = NumericRange<1, 256>;
|
|
20
|
+
export type ExitCode = SuccessExitCode | ErrorExitCode;
|
|
21
|
+
export {};
|
package/dist/cli/watcher.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Logger } from "./types";
|
|
1
|
+
import type { Logger } from "./types";
|
|
2
2
|
export declare const setupWatcher: (baseDir: string, onGenerate: () => void, logger: Logger) => void;
|
package/dist/cli/watcher.js
CHANGED
|
@@ -4,12 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.setupWatcher = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
7
8
|
const chokidar_1 = __importDefault(require("chokidar"));
|
|
9
|
+
const constants_1 = require("./constants");
|
|
8
10
|
const cache_1 = require("./core/cache");
|
|
9
11
|
const debounce_1 = require("./debounce");
|
|
10
12
|
const setupWatcher = (baseDir, onGenerate, logger) => {
|
|
11
|
-
logger.info(
|
|
12
|
-
|
|
13
|
+
logger.info(`${path_1.default.relative(process.cwd(), baseDir)}`, {
|
|
14
|
+
event: "watch",
|
|
15
|
+
});
|
|
16
|
+
const isTargetFiles = (path) => constants_1.END_POINT_FILE_NAMES.some((fileName) => path.endsWith(fileName));
|
|
13
17
|
const changedPaths = new Set();
|
|
14
18
|
// Once the debounced function starts executing, no new watcher events will be processed until it completes.
|
|
15
19
|
// This is due to JavaScript's single-threaded event loop: the current debounced function runs to completion,
|
|
@@ -30,10 +34,11 @@ const setupWatcher = (baseDir, onGenerate, logger) => {
|
|
|
30
34
|
watcher.on("ready", () => {
|
|
31
35
|
// First execution
|
|
32
36
|
debouncedGenerate();
|
|
33
|
-
watcher.on("all", (event,
|
|
34
|
-
if (isTargetFiles(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
watcher.on("all", (event, filePath) => {
|
|
38
|
+
if (isTargetFiles(filePath)) {
|
|
39
|
+
const relativePath = path_1.default.relative(process.cwd(), filePath);
|
|
40
|
+
logger.info(relativePath, { event });
|
|
41
|
+
changedPaths.add(filePath);
|
|
37
42
|
debouncedGenerate();
|
|
38
43
|
}
|
|
39
44
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rpc4next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Inspired by Hono RPC and Pathpida, rpc4next brings a lightweight and intuitive RPC solution to Next.js, making server-client communication seamless",
|
|
5
5
|
"author": "watanabe-1",
|
|
6
6
|
"license": "MIT",
|
package/dist/cli/core/types.d.ts
DELETED
package/dist/cli/core/types.js
DELETED