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 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) => number;
1
+ import type { CliOptions, ExitCode, Logger } from "./types";
2
+ export declare const handleCli: (baseDir: string, outputPath: string, options: CliOptions, logger: Logger) => ExitCode;
@@ -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 1;
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: options.paramsFile || null,
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: options.paramsFile || null,
33
+ paramsFileName,
33
34
  logger,
34
35
  });
35
36
  }
36
- return 0;
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 = "→";
@@ -7,7 +7,7 @@ export declare const scanAppDirCache: Map<string, {
7
7
  }[];
8
8
  paramsTypes: {
9
9
  paramsType: string;
10
- path: string;
10
+ dirPath: string;
11
11
  }[];
12
12
  }>;
13
13
  export declare const clearVisitedDirsCacheAbove: (targetPath: string) => void;
@@ -1,4 +1,3 @@
1
- export declare const END_POINT_FILE_NAMES: readonly ["page.tsx", "route.ts"];
2
1
  export declare const QUERY_TYPES: readonly ["Query", "OptionalQuery"];
3
2
  export declare const INDENT = " ";
4
3
  export declare const NEWLINE = "\n";
@@ -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 = exports.END_POINT_FILE_NAMES = void 0;
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, path: filePath }) => {
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
- statement: string;
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 endPointFileNames = new Set(constants_1.END_POINT_FILE_NAMES);
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
- // privete
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
- if ((0, exports.hasTargetFiles)(entryPath)) {
43
- cache_1.visitedDirsCache.set(dirPath, true);
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 += constants_1.INDENT;
72
+ const previousIndent = indent;
73
+ const currentIndent = indent + constants_1.INDENT;
57
74
  const pathStructures = [];
58
75
  const imports = [];
59
- const types = [];
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
- entries.forEach((entry) => {
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 isGroup = nameWithoutExt.startsWith("(") && nameWithoutExt.endsWith(")");
80
- const isParallel = nameWithoutExt.startsWith("@");
81
- const isOptionalCatchAll = nameWithoutExt.startsWith("[[...") && nameWithoutExt.endsWith("]]");
82
- const isCatchAll = nameWithoutExt.startsWith("[...") && nameWithoutExt.endsWith("]");
83
- const isDynamic = nameWithoutExt.startsWith("[") && nameWithoutExt.endsWith("]");
84
- const { paramName, keyName } = (() => {
85
- let param = nameWithoutExt;
86
- // Remove []
87
- if (isDynamic) {
88
- param = param.replace(/^\[+|\]+$/g, "");
89
- }
90
- // Remove ...
91
- if (isCatchAll || isOptionalCatchAll) {
92
- param = param.replace(/^\.{3}/, "");
93
- }
94
- const prefix = isOptionalCatchAll
95
- ? constants_2.OPTIONAL_CATCH_ALL_PREFIX
96
- : isCatchAll
97
- ? constants_2.CATCH_ALL_PREFIX
98
- : isDynamic
99
- ? constants_2.DYNAMIC_PREFIX
100
- : "";
101
- return { paramName: param, keyName: `${prefix}${param}` };
102
- })();
103
- let nextParams = params;
104
- if (isDynamic || isCatchAll || isOptionalCatchAll) {
105
- const routeType = {
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 ? indent.replace(constants_1.INDENT, "") : indent, nextParams);
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 contents inside {}
125
+ // Extract only the inner part inside `{}` from the child output
120
126
  const match = childPathStructure.match(/^\s*\{([\s\S]*)\}\s*$/);
121
- const childStr = match ? match[1].trim() : null;
122
- if (childStr) {
123
- pathStructures.push(`${indent}${childStr}`);
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(`${indent}"${keyName}": ${childPathStructure}`);
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: statement, importPath: path, type } = queryDef;
134
- imports.push({ statement, path });
135
- types.push(type);
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: statement, importPath: path, type, } = routeDef;
141
- imports.push({ statement, path });
142
- types.push(type);
148
+ const { importStatement, importPath, type } = routeDef;
149
+ imports.push({ statement: importStatement, path: importPath });
150
+ typeFragments.push(type);
143
151
  }
144
152
  });
145
- types.push(constants_1.TYPE_END_POINT);
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((param) => {
148
- const { paramName, routeType } = param;
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 paramsType = (0, type_utils_1.createObjectType)(fields);
158
- paramsTypes.push({ paramsType, path: fullPath.replace(/\\/g, "/") });
159
- types.push((0, type_utils_1.createRecodeType)(constants_1.TYPE_KEY_PARAMS, paramsType));
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
- const typeString = types.join(" & ");
164
- const pathStructure = pathStructures.length > 0
165
- ? `${typeString}${typeString ? " & " : ""}{${constants_1.NEWLINE}${pathStructures.join(`,${constants_1.NEWLINE}`)}${constants_1.NEWLINE}${indent.replace(constants_1.INDENT, "")}}`
166
- : typeString;
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));
@@ -1,4 +1,4 @@
1
- import { Logger } from "./types";
1
+ import type { Logger } from "./types";
2
2
  export declare const generate: ({ baseDir, outputPath, paramsFileName, logger, }: {
3
3
  baseDir: string;
4
4
  outputPath: string;
@@ -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("Generating...");
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(`Generated types at ${outputPath}`);
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 = `${dirPath}/${paramsFileName}`;
19
+ const filePath = path_1.default.join(dirPath, paramsFileName);
17
20
  fs_1.default.writeFileSync(filePath, paramsType);
18
21
  });
19
- logger.success(`Generated params type at ${paramsFileName}`);
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;
@@ -1,2 +1,3 @@
1
- import { Logger } from "./types";
1
+ import type { Logger } from "./types";
2
+ export declare const padMessage: (label: string, value: string, separator?: string, targetLength?: number) => string;
2
3
  export declare const createLogger: () => Logger;
@@ -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) => console.log(msg),
7
- success: (msg) => console.log(msg),
8
- error: (msg) => console.error(msg),
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;
@@ -1,9 +1,21 @@
1
- export interface Logger {
2
- info: (msg: string) => void;
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 {};
@@ -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;
@@ -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(`Watching ${baseDir}...`);
12
- const isTargetFiles = (path) => path.endsWith("route.ts") || path.endsWith("page.tsx");
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, path) => {
34
- if (isTargetFiles(path)) {
35
- logger.info(`[${event}] ${path}`);
36
- changedPaths.add(path);
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.6",
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",
@@ -1,2 +0,0 @@
1
- import type { END_POINT_FILE_NAMES } from "./constants";
2
- export type EndPointFileNames = (typeof END_POINT_FILE_NAMES)[number];
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });