@kosmojs/lib 0.0.30 → 0.1.1

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/pkg/index.js CHANGED
@@ -1,1453 +1,1183 @@
1
- // src/ast.ts
2
- import { join, resolve } from "node:path";
1
+ import { builtinModules } from "node:module";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
3
  import { styleText } from "node:util";
4
4
  import crc from "crc/crc32";
5
5
  import { flattener } from "tfusion";
6
- import {
7
- Project,
8
- SyntaxKind
9
- } from "ts-morph";
10
- import {
11
- defaults,
12
- RequestValidationTargets
13
- } from "@kosmojs/core";
14
- import { HTTPMethods } from "@kosmojs/core/api";
6
+ import { Project, SyntaxKind } from "ts-morph";
7
+ import picomatch from "picomatch";
8
+ import { access, constants, mkdir, readFile, writeFile } from "node:fs/promises";
9
+ import handlebars from "handlebars";
10
+ import { glob } from "tinyglobby";
11
+ import { parse } from "path-to-regexp";
12
+ import mimeTypes from "mime-types";
13
+ import { Spinner } from "@topcli/spinner";
14
+ //#region ../core/pkg/index.js
15
+ var defaults = {
16
+ appPrefix: "@",
17
+ srcPrefix: "~",
18
+ libPrefix: "_",
19
+ coreDir: "core",
20
+ srcDir: "src",
21
+ libDir: "lib",
22
+ configDir: "config",
23
+ apiDir: "api",
24
+ pagesDir: "pages",
25
+ entryDir: "entry",
26
+ fetchDir: "fetch",
27
+ refineTypeName: "VRefine"
28
+ };
29
+ /**
30
+ * Request metadata validation targets.
31
+ * */
32
+ var RequestMetadataTargets = {
33
+ query: "URL query parameters",
34
+ headers: "HTTP request headers",
35
+ cookies: "HTTP cookies"
36
+ };
37
+ /**
38
+ * Request body validation targets.
39
+ *
40
+ * Body formats are mutually exclusive - only one should be specified per handler.
41
+ *
42
+ * **Development behavior:**
43
+ * - If multiple formats are defined, the builder displays a warning and
44
+ * disables validation schemas for the affected handler.
45
+ * - If an unsuitable target is defined (e.g., `json`, `form`)
46
+ * for a method without a body like GET, HEAD), a warning is displayed and
47
+ * validation schemas are disabled for that handler.
48
+ *
49
+ * This ensures misconfigurations are detected during development
50
+ * for runtime to execute without false positive validation failures.
51
+ *
52
+ * Always define exactly one target that is suitable for current handler.
53
+ * */
54
+ var RequestBodyTargets = {
55
+ json: "JSON request body",
56
+ form: "URL-encoded or Multipart form",
57
+ raw: "Raw body format (string/Buffer/ArrayBuffer/Blob)"
58
+ };
59
+ var RequestValidationTargets = {
60
+ ...RequestMetadataTargets,
61
+ ...RequestBodyTargets
62
+ };
63
+ //#endregion
64
+ //#region ../core/pkg/api/index.js
65
+ var HTTPMethods = /* @__PURE__ */ function(HTTPMethods) {
66
+ HTTPMethods["HEAD"] = "HEAD";
67
+ HTTPMethods["OPTIONS"] = "OPTIONS";
68
+ HTTPMethods["GET"] = "GET";
69
+ HTTPMethods["PUT"] = "PUT";
70
+ HTTPMethods["PATCH"] = "PATCH";
71
+ HTTPMethods["POST"] = "POST";
72
+ HTTPMethods["DELETE"] = "DELETE";
73
+ return HTTPMethods;
74
+ }({});
75
+ //#endregion
76
+ //#region src/ast.ts
15
77
  var astFactory = () => {
16
- const createProject = (opts) => new Project(opts);
17
- const resolveRouteSignature = async (route, opts) => {
18
- const {
19
- sourceFile = createProject().addSourceFileAtPath(route.fileFullpath)
20
- } = { ...opts };
21
- const [typeDeclarations, referencedFiles] = extractTypeDeclarations(
22
- sourceFile,
23
- opts
24
- );
25
- const defaultExport = extractDefaultExport(sourceFile);
26
- const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
27
- const methods = defaultExport ? extractRouteMethods(route, defaultExport) : [];
28
- return {
29
- typeDeclarations,
30
- paramsRefinements,
31
- methods: methods.map((e) => e.method),
32
- validationDefinitions: methods.flatMap((e) => e.validationDefinitions),
33
- referencedFiles
34
- };
35
- };
36
- const extractDefaultExport = (sourceFile) => {
37
- const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
38
- if (exportAssignment.isExportEquals()) {
39
- return [];
40
- }
41
- const callExpression = exportAssignment.getExpression();
42
- return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
43
- });
44
- return defaultExport;
45
- };
46
- const extractParamsRefinements = (callExpression) => {
47
- const [
48
- _routeName,
49
- // first generic - the route name
50
- paramsGeneric
51
- // second generic - params refinements
52
- ] = extractGenerics(callExpression);
53
- if (!paramsGeneric?.isKind(SyntaxKind.TupleType)) {
54
- return;
55
- }
56
- return paramsGeneric.getElements().map((node, index) => {
57
- return {
58
- index,
59
- text: node.getText()
60
- };
61
- });
62
- };
63
- const extractRouteMethods = (route, callExpression) => {
64
- const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
65
- if (!funcDeclaration) {
66
- return [];
67
- }
68
- const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(
69
- SyntaxKind.ArrayLiteralExpression
70
- );
71
- if (!arrayLiteralExpression) {
72
- return [];
73
- }
74
- const callExpressions = [];
75
- for (const e of arrayLiteralExpression.getChildrenOfKind(
76
- SyntaxKind.CallExpression
77
- )) {
78
- const name = e.getExpression().getText();
79
- if (HTTPMethods[name]) {
80
- callExpressions.push([e, name]);
81
- }
82
- }
83
- const methods = [];
84
- for (const [callExpression2, method] of callExpressions) {
85
- const [vDefs, vOpts] = extractGenerics(callExpression2);
86
- methods.push({
87
- method,
88
- validationDefinitions: extractValidationDefinitions(
89
- route,
90
- method,
91
- vDefs,
92
- vOpts
93
- )
94
- });
95
- }
96
- return methods;
97
- };
98
- const parseRuntimeValidation = (typeNode) => {
99
- if (typeNode.isKind(SyntaxKind.LiteralType)) {
100
- const literal = typeNode.getFirstChild();
101
- if (literal?.isKind(SyntaxKind.TrueKeyword)) {
102
- return true;
103
- } else if (literal?.isKind(SyntaxKind.FalseKeyword)) {
104
- return false;
105
- }
106
- }
107
- return void 0;
108
- };
109
- const extractResponseVariant = (typeNode) => {
110
- if (!typeNode.isKind(SyntaxKind.TupleType)) {
111
- return;
112
- }
113
- let status = 200;
114
- let contentType;
115
- let body;
116
- const [statusNode, contentTypeNode, bodyNode] = typeNode.getElements();
117
- if (statusNode?.isKind(SyntaxKind.LiteralType)) {
118
- const literal = statusNode.getFirstChildByKind(SyntaxKind.NumericLiteral);
119
- if (literal) {
120
- status = Number(literal.getText());
121
- }
122
- }
123
- if (contentTypeNode) {
124
- contentType = extractStringLiteral(contentTypeNode);
125
- }
126
- if (bodyNode) {
127
- body = bodyNode.getText();
128
- if (["object"].includes(body)) {
129
- body = "{}";
130
- }
131
- }
132
- return { status, contentType, body };
133
- };
134
- const parseValidationOptions = (typeNode) => {
135
- const opts = {};
136
- if (!typeNode?.isKind(SyntaxKind.TypeLiteral)) {
137
- return opts;
138
- }
139
- for (const prop of typeNode.getMembers()) {
140
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
141
- continue;
142
- }
143
- const target = prop.getName();
144
- const typeNode2 = prop.getTypeNodeOrThrow();
145
- if (!typeNode2.isKind(SyntaxKind.TypeLiteral)) {
146
- continue;
147
- }
148
- let contentType;
149
- let runtimeValidation;
150
- const customErrors = {};
151
- for (const member of typeNode2.getMembers()) {
152
- if (!member.isKind(SyntaxKind.PropertySignature)) {
153
- continue;
154
- }
155
- const nameNode = member.getNameNode();
156
- const valueNode = member.getTypeNodeOrThrow();
157
- const name = nameNode.isKind(SyntaxKind.StringLiteral) ? nameNode.getLiteralText() : nameNode.getText();
158
- if (name === "contentType") {
159
- contentType = extractStringLiteral(valueNode);
160
- } else if (name === "runtimeValidation") {
161
- runtimeValidation = parseRuntimeValidation(valueNode);
162
- } else if (name.startsWith("error")) {
163
- const literal = extractStringLiteral(valueNode);
164
- if (literal) {
165
- customErrors[name] = literal;
166
- }
167
- }
168
- }
169
- opts[target] = {
170
- contentType,
171
- runtimeValidation,
172
- customErrors
173
- };
174
- }
175
- return opts;
176
- };
177
- const extractStringLiteral = (typeNode) => {
178
- const literal = typeNode.isKind(SyntaxKind.LiteralType) ? typeNode.getFirstChildByKind(SyntaxKind.StringLiteral) : void 0;
179
- return literal ? literal.getLiteralText() : void 0;
180
- };
181
- const extractValidationDefinitions = (route, method, defsNode, optsNode) => {
182
- const definitions = [];
183
- if (!defsNode?.isKind(SyntaxKind.TypeLiteral)) {
184
- return definitions;
185
- }
186
- const optsMap = parseValidationOptions(optsNode);
187
- const createId = (target, hash) => {
188
- return [
189
- target.replace(/^./, (c) => c.toUpperCase()),
190
- "T",
191
- method,
192
- crc(route.id + hash)
193
- ].join("");
194
- };
195
- for (const prop of defsNode.getMembers()) {
196
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
197
- continue;
198
- }
199
- const target = prop.getName();
200
- const typeNode = prop.getTypeNodeOrThrow();
201
- if (target === "response") {
202
- const variants = typeNode.isKind(SyntaxKind.UnionType) ? typeNode.getChildrenOfKind(SyntaxKind.TupleType) : [typeNode];
203
- definitions.push({
204
- ...optsMap[target],
205
- method,
206
- target,
207
- variants: variants.flatMap((e, i) => {
208
- const { status, contentType, body } = extractResponseVariant(e) || {};
209
- if (!status) {
210
- return [];
211
- }
212
- if (contentType && typeof contentType !== "string") {
213
- console.warn(
214
- styleText(
215
- ["bold", "red"],
216
- `\u2717 The second element of a response variant should specify the Response Content Type`
217
- )
218
- );
219
- console.warn(
220
- styleText(["blue"], ` Example: [200, "json", Schema]`)
221
- );
222
- console.warn(
223
- ` Route: ${route.name}; Method: ${method}; Response Variant: #${i}`
224
- );
225
- console.warn();
226
- }
227
- return [
228
- {
229
- id: createId(
230
- target,
231
- JSON.stringify([status, contentType, body])
232
- ),
233
- status,
234
- contentType,
235
- body
236
- }
237
- ];
238
- })
239
- });
240
- } else if (Object.keys(RequestValidationTargets).includes(target)) {
241
- definitions.push({
242
- ...optsMap[target],
243
- method,
244
- target,
245
- schema: {
246
- id: createId(target),
247
- text: typeNode.getText()
248
- }
249
- });
250
- }
251
- }
252
- return definitions;
253
- };
254
- const extractTypeDeclarations = (sourceFile, opts) => {
255
- const declarations = [];
256
- const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
257
- for (const declaration of sourceFile.getImportDeclarations()) {
258
- const modulePath = declaration.getModuleSpecifierValue();
259
- const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
260
- const typeOnlyDeclaration = declaration.isTypeOnly();
261
- const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
262
- if (defaultImport) {
263
- const name = defaultImport.getText();
264
- const text = `import type ${name} from "${path}";`;
265
- declarations.push({
266
- importDeclaration: {
267
- name,
268
- path
269
- },
270
- text
271
- });
272
- if (referencedFiles) {
273
- referencedFiles.push(...getReferencedFiles(defaultImport));
274
- }
275
- }
276
- const namespaceImport = typeOnlyDeclaration ? declaration.getNamespaceImport() : void 0;
277
- if (namespaceImport) {
278
- const name = namespaceImport.getText();
279
- const text = `import type * as ${name} from "${path}";`;
280
- declarations.push({
281
- importDeclaration: {
282
- name,
283
- path
284
- },
285
- text
286
- });
287
- if (referencedFiles) {
288
- referencedFiles.push(...getReferencedFiles(namespaceImport));
289
- }
290
- }
291
- for (const namedImport of declaration.getNamedImports()) {
292
- if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
293
- const nameNode = namedImport.getNameNode();
294
- const name = nameNode.getText();
295
- const alias = namedImport.getAliasNode()?.getText();
296
- const nameText = alias ? `${name} as ${alias}` : name;
297
- declarations.push({
298
- importDeclaration: {
299
- name,
300
- alias,
301
- path
302
- },
303
- text: `import type { ${nameText} } from "${path}";`
304
- });
305
- if (referencedFiles) {
306
- if (nameNode.isKind(SyntaxKind.Identifier)) {
307
- referencedFiles.push(...getReferencedFiles(nameNode));
308
- }
309
- }
310
- }
311
- }
312
- }
313
- for (const declaration of sourceFile.getTypeAliases()) {
314
- const name = declaration.getName();
315
- const text = declaration.getFullText().trim();
316
- declarations.push({
317
- typeAliasDeclaration: { name },
318
- text
319
- });
320
- }
321
- for (const declaration of sourceFile.getInterfaces()) {
322
- const name = declaration.getName();
323
- const text = declaration.getFullText().trim();
324
- declarations.push({
325
- interfaceDeclaration: { name },
326
- text
327
- });
328
- }
329
- for (const declaration of sourceFile.getEnums()) {
330
- const name = declaration.getName();
331
- const text = declaration.getFullText().trim();
332
- declarations.push({
333
- enumDeclaration: { name },
334
- text
335
- });
336
- }
337
- for (const declaration of sourceFile.getExportDeclarations()) {
338
- const typeOnlyDeclaration = declaration.isTypeOnly();
339
- const modulePath = declaration.getModuleSpecifierValue();
340
- const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
341
- for (const namedExport of declaration.getNamedExports()) {
342
- if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
343
- const nameNode = namedExport.getNameNode();
344
- const name = nameNode.getText();
345
- const alias = namedExport.getAliasNode()?.getText();
346
- const nameText = alias ? `${name} as ${alias}` : name;
347
- declarations.push({
348
- exportDeclaration: {
349
- name,
350
- alias: alias ?? name,
351
- path
352
- },
353
- text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
354
- });
355
- if (referencedFiles) {
356
- if (nameNode.isKind(SyntaxKind.Identifier)) {
357
- referencedFiles.push(...getReferencedFiles(nameNode));
358
- }
359
- }
360
- }
361
- }
362
- }
363
- return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
364
- };
365
- const getReferencedFiles = (importIdentifier) => {
366
- const declarations = importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || [];
367
- return declarations.flatMap((e) => {
368
- const sourceFile = e.getSourceFile();
369
- return sourceFile ? [sourceFile.getFilePath()] : [];
370
- });
371
- };
372
- const extractGenerics = (callExpression) => {
373
- return callExpression.getTypeArguments();
374
- };
375
- const typeResolverFactory = ({ root, name }) => {
376
- const project = createProject({
377
- tsConfigFilePath: resolve(
378
- root,
379
- join(defaults.srcDir, name, "tsconfig.json")
380
- ),
381
- skipAddingFilesFromTsConfig: true
382
- });
383
- const literalTypesResolver = (literalTypes, options) => {
384
- const sourceFile = project.createSourceFile(
385
- `${crc(literalTypes)}-${Date.now()}.ts`,
386
- literalTypes,
387
- { overwrite: true }
388
- );
389
- const resolvedTypes = flattener(project, sourceFile, {
390
- ...options,
391
- stripComments: true
392
- });
393
- project.removeSourceFile(sourceFile);
394
- return resolvedTypes;
395
- };
396
- return {
397
- getSourceFile: (fileFullpath) => {
398
- return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
399
- },
400
- refreshSourceFile: async (fileFullpath) => {
401
- const sourceFile = project.getSourceFile(fileFullpath);
402
- if (sourceFile) {
403
- await sourceFile.refreshFromFileSystem();
404
- }
405
- },
406
- literalTypesResolver
407
- };
408
- };
409
- return {
410
- createProject,
411
- extractDefaultExport,
412
- extractParamsRefinements,
413
- extractRouteMethods,
414
- extractTypeDeclarations,
415
- resolveRouteSignature,
416
- typeResolverFactory
417
- };
78
+ const createProject = (opts) => new Project(opts);
79
+ const resolveRouteSignature = async (route, opts) => {
80
+ const { sourceFile = createProject().addSourceFileAtPath(route.fileFullpath) } = { ...opts };
81
+ const [typeDeclarations, referencedFiles] = extractTypeDeclarations(sourceFile, opts);
82
+ const defaultExport = extractDefaultExport(sourceFile);
83
+ const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
84
+ const methods = defaultExport ? extractRouteMethods(route, defaultExport) : [];
85
+ return {
86
+ typeDeclarations,
87
+ paramsRefinements,
88
+ methods: methods.map((e) => e.method),
89
+ validationDefinitions: methods.flatMap((e) => e.validationDefinitions),
90
+ referencedFiles
91
+ };
92
+ };
93
+ const extractDefaultExport = (sourceFile) => {
94
+ const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
95
+ if (exportAssignment.isExportEquals()) return [];
96
+ const callExpression = exportAssignment.getExpression();
97
+ return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
98
+ });
99
+ return defaultExport;
100
+ };
101
+ const extractParamsRefinements = (callExpression) => {
102
+ const [_routeName, paramsGeneric] = extractGenerics(callExpression);
103
+ if (!paramsGeneric?.isKind(SyntaxKind.TupleType)) return;
104
+ return paramsGeneric.getElements().map((node, index) => {
105
+ return {
106
+ index,
107
+ text: node.getText()
108
+ };
109
+ });
110
+ };
111
+ const extractRouteMethods = (route, callExpression) => {
112
+ const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
113
+ if (!funcDeclaration) return [];
114
+ const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression);
115
+ if (!arrayLiteralExpression) return [];
116
+ const callExpressions = [];
117
+ for (const e of arrayLiteralExpression.getChildrenOfKind(SyntaxKind.CallExpression)) {
118
+ const name = e.getExpression().getText();
119
+ if (HTTPMethods[name]) callExpressions.push([e, name]);
120
+ }
121
+ const methods = [];
122
+ for (const [callExpression, method] of callExpressions) {
123
+ const [vDefs, vOpts] = extractGenerics(callExpression);
124
+ methods.push({
125
+ method,
126
+ validationDefinitions: extractValidationDefinitions(route, method, vDefs, vOpts)
127
+ });
128
+ }
129
+ return methods;
130
+ };
131
+ /** Parse a boolean literal type node */
132
+ const parseRuntimeValidation = (typeNode) => {
133
+ if (typeNode.isKind(SyntaxKind.LiteralType)) {
134
+ const literal = typeNode.getFirstChild();
135
+ if (literal?.isKind(SyntaxKind.TrueKeyword)) return true;
136
+ else if (literal?.isKind(SyntaxKind.FalseKeyword)) return false;
137
+ }
138
+ };
139
+ const extractResponseVariant = (typeNode) => {
140
+ if (!typeNode.isKind(SyntaxKind.TupleType)) return;
141
+ let status = 200;
142
+ let contentType;
143
+ let body;
144
+ const [statusNode, contentTypeNode, bodyNode] = typeNode.getElements();
145
+ if (statusNode?.isKind(SyntaxKind.LiteralType)) {
146
+ const literal = statusNode.getFirstChildByKind(SyntaxKind.NumericLiteral);
147
+ if (literal) status = Number(literal.getText());
148
+ }
149
+ if (contentTypeNode) contentType = extractStringLiteral(contentTypeNode);
150
+ if (bodyNode) {
151
+ body = bodyNode.getText();
152
+ if (["object"].includes(body)) body = "{}";
153
+ }
154
+ return {
155
+ status,
156
+ contentType,
157
+ body
158
+ };
159
+ };
160
+ /** Parse opts TypeLiteral into a map keyed by target */
161
+ const parseValidationOptions = (typeNode) => {
162
+ const opts = {};
163
+ if (!typeNode?.isKind(SyntaxKind.TypeLiteral)) return opts;
164
+ for (const prop of typeNode.getMembers()) {
165
+ if (!prop.isKind(SyntaxKind.PropertySignature)) continue;
166
+ const target = prop.getName();
167
+ const typeNode = prop.getTypeNodeOrThrow();
168
+ if (!typeNode.isKind(SyntaxKind.TypeLiteral)) continue;
169
+ let contentType;
170
+ let runtimeValidation;
171
+ const customErrors = {};
172
+ for (const member of typeNode.getMembers()) {
173
+ if (!member.isKind(SyntaxKind.PropertySignature)) continue;
174
+ const nameNode = member.getNameNode();
175
+ const valueNode = member.getTypeNodeOrThrow();
176
+ const name = nameNode.isKind(SyntaxKind.StringLiteral) ? nameNode.getLiteralText() : nameNode.getText();
177
+ if (name === "contentType") contentType = extractStringLiteral(valueNode);
178
+ else if (name === "runtimeValidation") runtimeValidation = parseRuntimeValidation(valueNode);
179
+ else if (name.startsWith("error")) {
180
+ const literal = extractStringLiteral(valueNode);
181
+ if (literal) customErrors[name] = literal;
182
+ }
183
+ }
184
+ opts[target] = {
185
+ contentType,
186
+ runtimeValidation,
187
+ customErrors
188
+ };
189
+ }
190
+ return opts;
191
+ };
192
+ const extractStringLiteral = (typeNode) => {
193
+ const literal = typeNode.isKind(SyntaxKind.LiteralType) ? typeNode.getFirstChildByKind(SyntaxKind.StringLiteral) : void 0;
194
+ return literal ? literal.getLiteralText() : void 0;
195
+ };
196
+ /**
197
+ * Extract validation definitions from route handler generics.
198
+ * Merges defs (schemas) and opts (validation options) into a flat array.
199
+ * */
200
+ const extractValidationDefinitions = (route, method, defsNode, optsNode) => {
201
+ const definitions = [];
202
+ if (!defsNode?.isKind(SyntaxKind.TypeLiteral)) return definitions;
203
+ const optsMap = parseValidationOptions(optsNode);
204
+ const createId = (target, hash) => {
205
+ return [
206
+ target.replace(/^./, (c) => c.toUpperCase()),
207
+ "T",
208
+ method,
209
+ crc(route.id + hash)
210
+ ].join("");
211
+ };
212
+ for (const prop of defsNode.getMembers()) {
213
+ if (!prop.isKind(SyntaxKind.PropertySignature)) continue;
214
+ const target = prop.getName();
215
+ const typeNode = prop.getTypeNodeOrThrow();
216
+ if (target === "response") {
217
+ const variants = typeNode.isKind(SyntaxKind.UnionType) ? typeNode.getChildrenOfKind(SyntaxKind.TupleType) : [typeNode];
218
+ definitions.push({
219
+ ...optsMap[target],
220
+ method,
221
+ target,
222
+ variants: variants.flatMap((e, i) => {
223
+ const { status, contentType, body } = extractResponseVariant(e) || {};
224
+ if (!status) return [];
225
+ if (contentType && typeof contentType !== "string") {
226
+ console.warn(styleText(["bold", "red"], `✗ The second element of a response variant should specify the Response Content Type`));
227
+ console.warn(styleText(["blue"], ` Example: [200, "json", Schema]`));
228
+ console.warn(` Route: ${route.name}; Method: ${method}; Response Variant: #${i}`);
229
+ console.warn();
230
+ }
231
+ return [{
232
+ id: createId(target, JSON.stringify([
233
+ status,
234
+ contentType,
235
+ body
236
+ ])),
237
+ status,
238
+ contentType,
239
+ body
240
+ }];
241
+ })
242
+ });
243
+ } else if (Object.keys(RequestValidationTargets).includes(target)) definitions.push({
244
+ ...optsMap[target],
245
+ method,
246
+ target,
247
+ schema: {
248
+ id: createId(target),
249
+ text: typeNode.getText()
250
+ }
251
+ });
252
+ }
253
+ return definitions;
254
+ };
255
+ const extractTypeDeclarations = (sourceFile, opts) => {
256
+ const declarations = [];
257
+ const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
258
+ for (const declaration of sourceFile.getImportDeclarations()) {
259
+ const modulePath = declaration.getModuleSpecifierValue();
260
+ const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
261
+ const typeOnlyDeclaration = declaration.isTypeOnly();
262
+ const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
263
+ if (defaultImport) {
264
+ const name = defaultImport.getText();
265
+ const text = `import type ${name} from "${path}";`;
266
+ declarations.push({
267
+ importDeclaration: {
268
+ name,
269
+ path
270
+ },
271
+ text
272
+ });
273
+ if (referencedFiles) referencedFiles.push(...getReferencedFiles(defaultImport));
274
+ }
275
+ const namespaceImport = typeOnlyDeclaration ? declaration.getNamespaceImport() : void 0;
276
+ if (namespaceImport) {
277
+ const name = namespaceImport.getText();
278
+ const text = `import type * as ${name} from "${path}";`;
279
+ declarations.push({
280
+ importDeclaration: {
281
+ name,
282
+ path
283
+ },
284
+ text
285
+ });
286
+ if (referencedFiles) referencedFiles.push(...getReferencedFiles(namespaceImport));
287
+ }
288
+ for (const namedImport of declaration.getNamedImports()) if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
289
+ const nameNode = namedImport.getNameNode();
290
+ const name = nameNode.getText();
291
+ const alias = namedImport.getAliasNode()?.getText();
292
+ const nameText = alias ? `${name} as ${alias}` : name;
293
+ declarations.push({
294
+ importDeclaration: {
295
+ name,
296
+ alias,
297
+ path
298
+ },
299
+ text: `import type { ${nameText} } from "${path}";`
300
+ });
301
+ if (referencedFiles) {
302
+ if (nameNode.isKind(SyntaxKind.Identifier)) referencedFiles.push(...getReferencedFiles(nameNode));
303
+ }
304
+ }
305
+ }
306
+ for (const declaration of sourceFile.getTypeAliases()) {
307
+ const name = declaration.getName();
308
+ const text = declaration.getFullText().trim();
309
+ declarations.push({
310
+ typeAliasDeclaration: { name },
311
+ text
312
+ });
313
+ }
314
+ for (const declaration of sourceFile.getInterfaces()) {
315
+ const name = declaration.getName();
316
+ const text = declaration.getFullText().trim();
317
+ declarations.push({
318
+ interfaceDeclaration: { name },
319
+ text
320
+ });
321
+ }
322
+ for (const declaration of sourceFile.getEnums()) {
323
+ const name = declaration.getName();
324
+ const text = declaration.getFullText().trim();
325
+ declarations.push({
326
+ enumDeclaration: { name },
327
+ text
328
+ });
329
+ }
330
+ for (const declaration of sourceFile.getExportDeclarations()) {
331
+ const typeOnlyDeclaration = declaration.isTypeOnly();
332
+ const modulePath = declaration.getModuleSpecifierValue();
333
+ const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
334
+ for (const namedExport of declaration.getNamedExports()) if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
335
+ const nameNode = namedExport.getNameNode();
336
+ const name = nameNode.getText();
337
+ const alias = namedExport.getAliasNode()?.getText();
338
+ const nameText = alias ? `${name} as ${alias}` : name;
339
+ declarations.push({
340
+ exportDeclaration: {
341
+ name,
342
+ alias: alias ?? name,
343
+ path
344
+ },
345
+ text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
346
+ });
347
+ if (referencedFiles) {
348
+ if (nameNode.isKind(SyntaxKind.Identifier)) referencedFiles.push(...getReferencedFiles(nameNode));
349
+ }
350
+ }
351
+ }
352
+ return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
353
+ };
354
+ const getReferencedFiles = (importIdentifier) => {
355
+ return (importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || []).flatMap((e) => {
356
+ const sourceFile = e.getSourceFile();
357
+ return sourceFile ? [sourceFile.getFilePath()] : [];
358
+ });
359
+ };
360
+ const extractGenerics = (callExpression) => {
361
+ return callExpression.getTypeArguments();
362
+ };
363
+ const typeResolverFactory = ({ root, name }) => {
364
+ const project = createProject({
365
+ tsConfigFilePath: resolve(root, join(defaults.srcDir, name, "tsconfig.json")),
366
+ skipAddingFilesFromTsConfig: true
367
+ });
368
+ const literalTypesResolver = (literalTypes, options) => {
369
+ const sourceFile = project.createSourceFile(`${crc(literalTypes)}-${Date.now()}.ts`, literalTypes, { overwrite: true });
370
+ const resolvedTypes = flattener(project, sourceFile, {
371
+ ...options,
372
+ stripComments: true
373
+ });
374
+ project.removeSourceFile(sourceFile);
375
+ return resolvedTypes;
376
+ };
377
+ return {
378
+ getSourceFile: (fileFullpath) => {
379
+ return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
380
+ },
381
+ refreshSourceFile: async (fileFullpath) => {
382
+ const sourceFile = project.getSourceFile(fileFullpath);
383
+ if (sourceFile) await sourceFile.refreshFromFileSystem();
384
+ },
385
+ literalTypesResolver
386
+ };
387
+ };
388
+ return {
389
+ createProject,
390
+ extractDefaultExport,
391
+ extractParamsRefinements,
392
+ extractRouteMethods,
393
+ extractTypeDeclarations,
394
+ resolveRouteSignature,
395
+ typeResolverFactory
396
+ };
418
397
  };
419
-
420
- // src/generators.ts
421
- import picomatch from "picomatch";
398
+ //#endregion
399
+ //#region src/generators.ts
422
400
  var defineGenerator = (f) => f;
423
401
  var defineGeneratorFactory = (f) => f;
424
402
  var createTemplateResolver = (customTemplates, defaultTemplate) => {
425
- const templates = Object.entries({
426
- ...customTemplates
427
- }).map(([pattern, template]) => [picomatch(pattern), template]);
428
- return (pattern, entry) => {
429
- const match = templates.find(([isMatch]) => isMatch(pattern));
430
- if (!match) {
431
- return defaultTemplate;
432
- }
433
- return typeof match[1] === "function" ? match[1](entry) : match[1];
434
- };
403
+ const templates = Object.entries({ ...customTemplates }).map(([pattern, template]) => [picomatch(pattern), template]);
404
+ return (pattern, entry) => {
405
+ const match = templates.find(([isMatch]) => isMatch(pattern));
406
+ if (!match) return defaultTemplate;
407
+ return typeof match[1] === "function" ? match[1](entry) : match[1];
408
+ };
435
409
  };
436
-
437
- // src/paths.ts
438
- import { access, constants } from "node:fs/promises";
439
- import { join as join2, resolve as resolve2 } from "node:path";
440
- import { defaults as defaults2 } from "@kosmojs/core";
410
+ //#endregion
411
+ //#region src/paths.ts
441
412
  var pathResolver = (sourceFolder) => {
442
- const createPath = (...a) => {
443
- return sourceFolder.root ? resolve2(sourceFolder.root, join2(...a)) : join2(...a);
444
- };
445
- const createImport = {
446
- src(a, { origin }) {
447
- return origin === "src" ? join2(defaults2.srcPrefix, ...a) : join2(defaults2.appPrefix, defaults2.srcDir, sourceFolder.name, ...a);
448
- },
449
- config(a, o) {
450
- return this.src([defaults2.configDir, ...a], o);
451
- },
452
- api(a, o) {
453
- return this.src([defaults2.apiDir, ...a], o);
454
- },
455
- pages(a, o) {
456
- return this.src([defaults2.pagesDir, ...a], o);
457
- },
458
- lib(a, { origin }) {
459
- return origin === "src" ? join2(defaults2.libPrefix, ...a) : join2(defaults2.appPrefix, defaults2.libDir, sourceFolder.name, ...a);
460
- },
461
- libCore(a, o) {
462
- return this.lib(["core", ...a], o);
463
- },
464
- libApi(a, o) {
465
- return this.lib([defaults2.apiDir, ...a], o);
466
- },
467
- libEntry(a, o) {
468
- return this.lib([defaults2.entryDir, ...a], o);
469
- }
470
- };
471
- return {
472
- createPath: {
473
- src(...a) {
474
- return createPath(defaults2.srcDir, sourceFolder.name, ...a);
475
- },
476
- api(...a) {
477
- return this.src(defaults2.apiDir, ...a);
478
- },
479
- pages(...a) {
480
- return this.src(defaults2.pagesDir, ...a);
481
- },
482
- config(...a) {
483
- return this.src(defaults2.configDir, ...a);
484
- },
485
- entry(...a) {
486
- return this.src(defaults2.entryDir, ...a);
487
- },
488
- lib(...a) {
489
- return createPath(defaults2.libDir, sourceFolder.name, ...a);
490
- },
491
- libCore(...a) {
492
- return this.lib("core", ...a);
493
- },
494
- libApi(...a) {
495
- return this.lib(defaults2.apiDir, ...a);
496
- },
497
- libEntry(...a) {
498
- return this.lib(defaults2.entryDir, ...a);
499
- },
500
- libPages(...a) {
501
- return this.lib(defaults2.pagesDir, ...a);
502
- },
503
- distDir(...a) {
504
- return createPath(sourceFolder.distDir, sourceFolder.name, ...a);
505
- }
506
- },
507
- createImport,
508
- createImportHelpers(o) {
509
- if (!["src", "lib"].includes(o?.origin)) {
510
- throw new Error(
511
- `createImportHelpers: required exactly one argument of shape { origin: "src|lib" }`
512
- );
513
- }
514
- return {
515
- createImport(key, ...a) {
516
- return createImport[key](a.slice(0, -1), o);
517
- }
518
- };
519
- }
520
- };
413
+ const createPath = (...a) => {
414
+ return sourceFolder.root ? resolve(sourceFolder.root, join(...a)) : join(...a);
415
+ };
416
+ const createImport = {
417
+ src(a, { origin }) {
418
+ return origin === "src" ? join(defaults.srcPrefix, ...a) : join(defaults.appPrefix, defaults.srcDir, sourceFolder.name, ...a);
419
+ },
420
+ api(a, o) {
421
+ return this.src([defaults.apiDir, ...a], o);
422
+ },
423
+ pages(a, o) {
424
+ return this.src([defaults.pagesDir, ...a], o);
425
+ },
426
+ lib(a, { origin }) {
427
+ return origin === "src" ? join(defaults.libPrefix, ...a) : join(defaults.appPrefix, defaults.libDir, sourceFolder.name, ...a);
428
+ },
429
+ libCore(a, o) {
430
+ return this.lib(["core", ...a], o);
431
+ },
432
+ libApi(a, o) {
433
+ return this.lib([defaults.apiDir, ...a], o);
434
+ },
435
+ libEntry(a, o) {
436
+ return this.lib([defaults.entryDir, ...a], o);
437
+ }
438
+ };
439
+ return {
440
+ createPath: {
441
+ src(...a) {
442
+ return createPath(defaults.srcDir, sourceFolder.name, ...a);
443
+ },
444
+ api(...a) {
445
+ return this.src(defaults.apiDir, ...a);
446
+ },
447
+ pages(...a) {
448
+ return this.src(defaults.pagesDir, ...a);
449
+ },
450
+ entry(...a) {
451
+ return this.src(defaults.entryDir, ...a);
452
+ },
453
+ lib(...a) {
454
+ return createPath(defaults.libDir, sourceFolder.name, ...a);
455
+ },
456
+ libCore(...a) {
457
+ return this.lib("core", ...a);
458
+ },
459
+ libApi(...a) {
460
+ return this.lib(defaults.apiDir, ...a);
461
+ },
462
+ libEntry(...a) {
463
+ return this.lib(defaults.entryDir, ...a);
464
+ },
465
+ libPages(...a) {
466
+ return this.lib(defaults.pagesDir, ...a);
467
+ },
468
+ distDir(...a) {
469
+ return createPath(sourceFolder.distDir, sourceFolder.name, ...a);
470
+ }
471
+ },
472
+ createImport,
473
+ createImportHelpers(o) {
474
+ if (!["src", "lib"].includes(o?.origin)) throw new Error(`createImportHelpers: required exactly one argument of shape { origin: "src|lib" }`);
475
+ return { createImport(key, ...a) {
476
+ return createImport[key](a.slice(0, -1), o);
477
+ } };
478
+ }
479
+ };
521
480
  };
522
481
  var pathExists = async (path) => {
523
- try {
524
- await access(path, constants.F_OK);
525
- return true;
526
- } catch {
527
- return false;
528
- }
482
+ try {
483
+ await access(path, constants.F_OK);
484
+ return true;
485
+ } catch {
486
+ return false;
487
+ }
529
488
  };
530
-
531
- // src/render.ts
532
- import { mkdir, readFile, writeFile } from "node:fs/promises";
533
- import { dirname, join as join3 } from "node:path";
534
- import crc2 from "crc/crc32";
535
- import handlebars from "handlebars";
489
+ //#endregion
490
+ //#region src/render.ts
536
491
  var render = (template, context, options) => {
537
- const { noEscape = true, renderer = handlebars } = { ...options };
538
- return renderer.compile(template, { noEscape })(context);
492
+ const { noEscape = true, renderer = handlebars } = { ...options };
493
+ return renderer.compile(template, { noEscape })(context);
539
494
  };
540
495
  var renderToFile = async (file, template, context, options) => {
541
- const content = render(template, context, options);
542
- if (await pathExists(file)) {
543
- const { overwrite = true } = { ...options };
544
- if (overwrite === false) {
545
- return;
546
- }
547
- const fileContent = await readFile(file, "utf8");
548
- if (typeof overwrite === "function" && !overwrite(fileContent)) {
549
- return;
550
- }
551
- if (crc2(content) === crc2(fileContent)) {
552
- return;
553
- }
554
- }
555
- await mkdir(dirname(file), { recursive: true });
556
- await writeFile(file, content, "utf8");
496
+ const content = render(template, context, options);
497
+ /**
498
+ * Two fs calls (exists + read) are worth it to avoid touching the file
499
+ * and triggering watchers unnecessarily.
500
+ * */
501
+ if (await pathExists(file)) {
502
+ const { overwrite = true } = { ...options };
503
+ if (overwrite === false) return;
504
+ const fileContent = await readFile(file, "utf8");
505
+ if (typeof overwrite === "function" && !overwrite(fileContent)) return;
506
+ if (crc(content) === crc(fileContent)) return;
507
+ }
508
+ await mkdir(dirname(file), { recursive: true });
509
+ await writeFile(file, content, "utf8");
557
510
  };
558
511
  var renderFactory = (options) => {
559
- const createRenderer = (selfOoptions) => {
560
- const renderer = handlebars.create();
561
- renderer.registerPartial({
562
- ...options?.partials,
563
- ...selfOoptions?.partials
564
- });
565
- renderer.registerHelper({
566
- ...options?.helpers,
567
- ...selfOoptions?.helpers
568
- });
569
- return renderer;
570
- };
571
- return {
572
- render(template, context, selfOoptions) {
573
- return render(template, context, {
574
- ...options,
575
- ...selfOoptions,
576
- renderer: createRenderer(selfOoptions)
577
- });
578
- },
579
- async renderToFile(file, template, context, selfOoptions) {
580
- return renderToFile(
581
- options?.outdir ? join3(options.outdir, file) : file,
582
- template,
583
- context,
584
- {
585
- ...options,
586
- ...selfOoptions,
587
- renderer: createRenderer(selfOoptions)
588
- }
589
- );
590
- }
591
- };
512
+ const createRenderer = (selfOoptions) => {
513
+ const renderer = handlebars.create();
514
+ renderer.registerPartial({
515
+ ...options?.partials,
516
+ ...selfOoptions?.partials
517
+ });
518
+ renderer.registerHelper({
519
+ ...options?.helpers,
520
+ ...selfOoptions?.helpers
521
+ });
522
+ return renderer;
523
+ };
524
+ return {
525
+ render(template, context, selfOoptions) {
526
+ return render(template, context, {
527
+ ...options,
528
+ ...selfOoptions,
529
+ renderer: createRenderer(selfOoptions)
530
+ });
531
+ },
532
+ async renderToFile(file, template, context, selfOoptions) {
533
+ return renderToFile(options?.outdir ? join(options.outdir, file) : file, template, context, {
534
+ ...options,
535
+ ...selfOoptions,
536
+ renderer: createRenderer(selfOoptions)
537
+ });
538
+ }
539
+ };
592
540
  };
593
-
594
- // src/routes/index.ts
595
- import {
596
- defaults as defaults5
597
- } from "@kosmojs/core";
598
-
599
- // src/routes/base.ts
600
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
601
- import { styleText as styleText2 } from "node:util";
602
- import crc4 from "crc/crc32";
603
- import picomatch2 from "picomatch";
604
- import { glob } from "tinyglobby";
605
- import {
606
- defaults as defaults3
607
- } from "@kosmojs/core";
608
-
609
- // src/routes/paths.ts
610
- import crc3 from "crc/crc32";
611
- import { parse } from "path-to-regexp";
612
- var pathTokensFactory = (path, {
613
- transformStaticValue = normalizeStaticValue
614
- } = {}) => {
615
- const extractParts = (tokens2, createConst, insideGroup = false) => {
616
- const parts = [];
617
- for (const token of tokens2) {
618
- switch (token.type) {
619
- case "text":
620
- if (token.value !== "/") {
621
- parts.push({
622
- type: "static",
623
- value: transformStaticValue(token.value)
624
- });
625
- }
626
- break;
627
- case "param":
628
- parts.push({
629
- type: "param",
630
- kind: insideGroup ? "optional" : "required",
631
- name: token.name,
632
- const: createConst(token.name)
633
- });
634
- break;
635
- case "wildcard":
636
- parts.push({
637
- type: "param",
638
- kind: "splat",
639
- name: token.name,
640
- const: createConst(token.name)
641
- });
642
- break;
643
- case "group":
644
- parts.push(...extractParts(token.tokens, createConst, true));
645
- break;
646
- }
647
- }
648
- return parts;
649
- };
650
- const patternTransforms = [
651
- // Transform required params: [id] => :id
652
- // Only pure \w param names,
653
- // [some-id] used as is, not treated as param,
654
- // use [some_id] instead.
655
- (s) => s.replace(/\[(\w+)\]/g, ":$1"),
656
- // Transform optional params: {id} => {:id}
657
- // Only pure \w param names,
658
- // anything else treated as a path-to-regexp pattern and used as is.
659
- // {some-id} treated as an optional static segment.
660
- // use {some_id} for simple param syntax
661
- // or {:some-id} pattern where :some is the param name and -id is a static segment.
662
- (s) => s.replace(/\{(\w+)\}/g, "{:$1}"),
663
- // Transform splat params: {...param} => {*param}
664
- (s) => s.replace(/\{\.\.\./g, "{*"),
665
- // Insert leading slash inside optional/splat groups.
666
- // {:name} => {/:name}
667
- // {*name} => {/*name}
668
- (s) => {
669
- return s.startsWith("{") ? s.replace(/^\{/, "{/") : s;
670
- }
671
- ];
672
- const detectBareParams = (s) => {
673
- let depth = 0;
674
- for (const [i, ch] of [...s].entries()) {
675
- if (ch === "{") {
676
- depth += 1;
677
- } else if (ch === "}") {
678
- depth -= 1;
679
- } else if (ch === ":" && depth === 0) {
680
- const match = s.slice(i + 1).match(/^\w+/);
681
- return match?.[0] || ":";
682
- }
683
- }
684
- return;
685
- };
686
- const tokens = path.replace(/^index\/?/, "").split("/").flatMap((orig) => {
687
- if (!orig.length) {
688
- return [];
689
- }
690
- const bareParam = detectBareParams(orig);
691
- if (bareParam === ":") {
692
- throw new Error(
693
- `${path} contains colons outside braces, use : only within {}`
694
- );
695
- } else if (bareParam) {
696
- throw new Error(
697
- `${path} contains bare params, use [${bareParam}] instead of :${bareParam}`
698
- );
699
- }
700
- const pattern = patternTransforms.reduce((src, fn) => fn(src), orig);
701
- const { tokens: tokens2 } = parse(
702
- pattern.replace(/\{[^}]*\}|(\+)/g, (m, p1) => p1 ? "\\+" : m)
703
- );
704
- const parts = extractParts(tokens2, (val) => {
705
- return /\W/.test(val) || /^\d/.test(val) ? [val.replace(/^\d+|\W/g, "_"), crc3(orig)].join("_") : val;
706
- });
707
- const isStatic = parts.length === 1 ? parts[0].type === "static" : false;
708
- const isParam = parts.length === 1 ? parts[0].type === "param" : false;
709
- const kind = isStatic ? "static" : isParam ? "param" : "mixed";
710
- return [
711
- {
712
- kind,
713
- orig,
714
- pattern,
715
- parts
716
- }
717
- ];
718
- });
719
- return tokens;
541
+ //#endregion
542
+ //#region src/routes/paths.ts
543
+ /**
544
+ * Parse a filesystem route path into structured PathToken array.
545
+ *
546
+ * Uses path-to-regexp v8 AST for parsing, with directory-friendly syntax:
547
+ * - [param] => required param - :param
548
+ * - {param} => optional param - {:param}
549
+ * - {...param} => splat - {*param}
550
+ *
551
+ * Direct :param syntax is prohibited outside {} to avoid ambiguity.
552
+ * Inside {} it is treated as path-to-regexp power syntax and used as-is.
553
+ *
554
+ * Examples:
555
+ * Required: [id], [name]
556
+ * Optional: {name}, {format}
557
+ * Splat: {...path}
558
+ * Mixed segments: shop/[id]-{name}
559
+ * Power syntax: {-v:version{-:pre}}, :name{@:version{.:min}}.js
560
+ * */
561
+ var pathTokensFactory = (path, { transformStaticValue = normalizeStaticValue } = {}) => {
562
+ /**
563
+ * Recursively extract parts from path-to-regexp AST tokens.
564
+ * A param inside a group is optional; top-level params are required.
565
+ * Wildcard tokens are always splat.
566
+ * Slash-only text nodes (restored for parsing) are skipped.
567
+ * */
568
+ const extractParts = (tokens, createConst, insideGroup = false) => {
569
+ const parts = [];
570
+ for (const token of tokens) switch (token.type) {
571
+ case "text":
572
+ if (token.value !== "/") parts.push({
573
+ type: "static",
574
+ value: transformStaticValue(token.value)
575
+ });
576
+ break;
577
+ case "param":
578
+ parts.push({
579
+ type: "param",
580
+ kind: insideGroup ? "optional" : "required",
581
+ name: token.name,
582
+ const: createConst(token.name)
583
+ });
584
+ break;
585
+ case "wildcard":
586
+ parts.push({
587
+ type: "param",
588
+ kind: "splat",
589
+ name: token.name,
590
+ const: createConst(token.name)
591
+ });
592
+ break;
593
+ case "group":
594
+ parts.push(...extractParts(token.tokens, createConst, true));
595
+ break;
596
+ }
597
+ return parts;
598
+ };
599
+ const patternTransforms = [
600
+ (s) => s.replace(/\[(\w+)\]/g, ":$1"),
601
+ (s) => s.replace(/\{(\w+)\}/g, "{:$1}"),
602
+ (s) => s.replace(/\{\.\.\./g, "{*"),
603
+ (s) => {
604
+ return s.startsWith("{") ? s.replace(/^\{/, "{/") : s;
605
+ }
606
+ ];
607
+ const detectBareParams = (s) => {
608
+ let depth = 0;
609
+ for (const [i, ch] of [...s].entries()) if (ch === "{") depth += 1;
610
+ else if (ch === "}") depth -= 1;
611
+ else if (ch === ":" && depth === 0) return s.slice(i + 1).match(/^\w+/)?.[0] || ":";
612
+ };
613
+ return path.replace(/^index\/?/, "").split("/").flatMap((orig) => {
614
+ if (!orig.length) return [];
615
+ const bareParam = detectBareParams(orig);
616
+ if (bareParam === ":") throw new Error(`${path} contains colons outside braces, use : only within {}`);
617
+ else if (bareParam) throw new Error(`${path} contains bare params, use [${bareParam}] instead of :${bareParam}`);
618
+ const pattern = patternTransforms.reduce((src, fn) => fn(src), orig);
619
+ const { tokens } = parse(pattern.replace(/\{[^}]*\}|(\+)/g, (m, p1) => p1 ? "\\+" : m));
620
+ const parts = extractParts(tokens, (val) => {
621
+ return /\W/.test(val) || /^\d/.test(val) ? [val.replace(/^\d+|\W/g, "_"), crc(orig)].join("_") : val;
622
+ });
623
+ const isStatic = parts.length === 1 ? parts[0].type === "static" : false;
624
+ const isParam = parts.length === 1 ? parts[0].type === "param" : false;
625
+ return [{
626
+ kind: isStatic ? "static" : isParam ? "param" : "mixed",
627
+ orig,
628
+ pattern,
629
+ parts
630
+ }];
631
+ });
720
632
  };
721
633
  var createPathPattern = (tokens) => {
722
- return tokens.map(({ pattern }, i) => {
723
- const next = tokens[i + 1];
724
- if (!next || next.pattern.includes("/")) {
725
- return pattern;
726
- }
727
- const slashRequired = tokens.slice(i + 1).some((e) => {
728
- return e.parts.some((e2) => {
729
- return e2.type === "static" || e2.kind === "required";
730
- });
731
- });
732
- return slashRequired ? `${pattern}/` : pattern;
733
- }).join("");
634
+ return tokens.map(({ pattern }, i) => {
635
+ const next = tokens[i + 1];
636
+ if (!next || next.pattern.includes("/")) return pattern;
637
+ return tokens.slice(i + 1).some((e) => {
638
+ return e.parts.some((e) => {
639
+ return e.type === "static" || e.kind === "required";
640
+ });
641
+ }) ? `${pattern}/` : pattern;
642
+ }).join("");
734
643
  };
735
644
  var createHonoPattern = (tokens) => {
736
- const staticValue = ({ value }) => {
737
- return normalizeStaticValue(value);
738
- };
739
- const paramValue = (p) => {
740
- if (p.kind === "splat") {
741
- return `*`;
742
- }
743
- if (p.kind === "optional") {
744
- return `:${p.name}?`;
745
- }
746
- return `:${p.name}`;
747
- };
748
- const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
749
- const tokensToRegex = (tokens2) => {
750
- return tokens2.flatMap((t) => {
751
- if (t.type === "text") {
752
- return t.value === "/" ? [] : [escapeRegex(t.value)];
753
- }
754
- if (t.type === "wildcard") {
755
- return [".+"];
756
- }
757
- if (t.type === "param") {
758
- return ["[^/]+"];
759
- }
760
- if (t.type === "group") {
761
- return [`(?:${tokensToRegex(t.tokens)})?`];
762
- }
763
- return [];
764
- }).join("");
765
- };
766
- return tokens.flatMap((token, i) => {
767
- if (token.kind === "static") {
768
- return [staticValue(token.parts[0])];
769
- }
770
- if (token.kind === "param") {
771
- return [paramValue(token.parts[0])];
772
- }
773
- const { tokens: tokens2 } = parse(token.pattern.replace(/\//g, ""));
774
- const regex = tokensToRegex(tokens2);
775
- return [`:_${i}{${regex}}`];
776
- }).join("/");
645
+ const staticValue = ({ value }) => {
646
+ return normalizeStaticValue(value);
647
+ };
648
+ const paramValue = (p) => {
649
+ if (p.kind === "splat") return `*`;
650
+ if (p.kind === "optional") return `:${p.name}?`;
651
+ return `:${p.name}`;
652
+ };
653
+ const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
654
+ const tokensToRegex = (tokens) => {
655
+ return tokens.flatMap((t) => {
656
+ if (t.type === "text") return t.value === "/" ? [] : [escapeRegex(t.value)];
657
+ if (t.type === "wildcard") return [".+"];
658
+ if (t.type === "param") return ["[^/]+"];
659
+ if (t.type === "group") return [`(?:${tokensToRegex(t.tokens)})?`];
660
+ return [];
661
+ }).join("");
662
+ };
663
+ return tokens.flatMap((token, i) => {
664
+ if (token.kind === "static") return [staticValue(token.parts[0])];
665
+ if (token.kind === "param") return [paramValue(token.parts[0])];
666
+ const { tokens } = parse(token.pattern.replace(/\//g, ""));
667
+ return [`:_${i}{${tokensToRegex(tokens)}}`];
668
+ }).join("/");
777
669
  };
778
670
  var normalizeStaticValue = (value) => {
779
- return value.replace(/\+/g, "\\\\+");
671
+ return value.replace(/\+/g, "\\\\+");
780
672
  };
781
-
782
- // src/routes/base.ts
673
+ //#endregion
674
+ //#region src/routes/base.ts
783
675
  var API_INDEX_BASENAME = "index";
784
676
  var API_INDEX_PATTERN = `${API_INDEX_BASENAME}.ts`;
785
677
  var API_USE_BASENAME = "use";
786
- var API_USE_PATTERN = `${API_USE_BASENAME}.ts`;
678
+ var API_USE_PATTERN = `use.ts`;
787
679
  var PAGE_INDEX_BASENAME = "index";
788
680
  var PAGE_INDEX_PATTERN = `${PAGE_INDEX_BASENAME}.{tsx,vue,mdx,md}`;
789
681
  var PAGE_LAYOUT_BASENAME = "layout";
790
682
  var PAGE_LAYOUT_PATTERN = `${PAGE_LAYOUT_BASENAME}.{tsx,vue,mdx}`;
791
683
  var ROUTE_FILE_PATTERNS = [
792
- // match index files in api dir
793
- `${defaults3.apiDir}/**/${API_INDEX_PATTERN}`,
794
- // match use files in api dir
795
- `${defaults3.apiDir}/**/${API_USE_PATTERN}`,
796
- // match index files in pages dir
797
- `${defaults3.pagesDir}/**/${PAGE_INDEX_PATTERN}`,
798
- // match layout files in pages dir
799
- `${defaults3.pagesDir}/**/${PAGE_LAYOUT_PATTERN}`
684
+ `${defaults.apiDir}/**/${API_INDEX_PATTERN}`,
685
+ `${defaults.apiDir}/**/${API_USE_PATTERN}`,
686
+ `${defaults.pagesDir}/**/${PAGE_INDEX_PATTERN}`,
687
+ `${defaults.pagesDir}/**/${PAGE_LAYOUT_PATTERN}`
800
688
  ];
801
689
  var scanRoutes = async (sourceFolder) => {
802
- const { createPath } = pathResolver(sourceFolder);
803
- return glob(ROUTE_FILE_PATTERNS, {
804
- cwd: createPath.src(),
805
- absolute: true,
806
- onlyFiles: true,
807
- followSymbolicLinks: false,
808
- ignore: [
809
- // ignore top-level matches, routes resides in folders, even index route
810
- `${defaults3.apiDir}/${API_INDEX_PATTERN}`,
811
- `${defaults3.apiDir}/${API_USE_PATTERN}`,
812
- `${defaults3.pagesDir}/${PAGE_INDEX_PATTERN}`,
813
- `${defaults3.pagesDir}/${PAGE_LAYOUT_PATTERN}`
814
- ]
815
- });
690
+ const { createPath } = pathResolver(sourceFolder);
691
+ return glob(ROUTE_FILE_PATTERNS, {
692
+ cwd: createPath.src(),
693
+ absolute: true,
694
+ onlyFiles: true,
695
+ followSymbolicLinks: false,
696
+ ignore: [
697
+ `${defaults.apiDir}/${API_INDEX_PATTERN}`,
698
+ `${defaults.apiDir}/${API_USE_PATTERN}`,
699
+ `${defaults.pagesDir}/${PAGE_INDEX_PATTERN}`,
700
+ `${defaults.pagesDir}/${PAGE_LAYOUT_PATTERN}`
701
+ ]
702
+ });
816
703
  };
817
704
  var isRouteFile = (file, sourceFolder) => {
818
- const [
819
- // source folder name
820
- _sourceFolder,
821
- // route folder, api or pages
822
- _folder,
823
- ...rest
824
- ] = resolve3(sourceFolder.root, file).replace(`${sourceFolder.root}/${defaults3.srcDir}/`, "").split("/");
825
- if (!_folder || _sourceFolder !== sourceFolder.name || rest.length < 2) {
826
- return false;
827
- }
828
- return picomatch2.isMatch(join4(_folder, ...rest), ROUTE_FILE_PATTERNS) ? [_folder, rest.join("/")] : false;
705
+ const [_sourceFolder, _folder, ...rest] = resolve(sourceFolder.root, file).replace(`${sourceFolder.root}/${defaults.srcDir}/`, "").split("/");
706
+ /**
707
+ * Ensure the file:
708
+ * - is under the correct source root
709
+ * - belongs to a known route folder (`api/` or `pages/`)
710
+ * - is nested at least one level deep (not a direct child of the folder)
711
+ * */
712
+ if (!_folder || _sourceFolder !== sourceFolder.name || rest.length < 2) return false;
713
+ return picomatch.isMatch(join(_folder, ...rest), ROUTE_FILE_PATTERNS) ? [_folder, rest.join("/")] : false;
829
714
  };
830
715
  var isApiRoute = (file) => {
831
- return picomatch2.matchBase(file, `**/${API_INDEX_PATTERN}`);
716
+ return picomatch.matchBase(file, `**/${API_INDEX_PATTERN}`);
832
717
  };
833
718
  var isApiUse = (file) => {
834
- return picomatch2.matchBase(file, `**/${API_USE_PATTERN}`);
719
+ return picomatch.matchBase(file, `**/${API_USE_PATTERN}`);
835
720
  };
836
721
  var isPageRoute = (file) => {
837
- return picomatch2.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
722
+ return picomatch.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
838
723
  };
839
724
  var isPageLayout = (file) => {
840
- return picomatch2.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
725
+ return picomatch.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
841
726
  };
842
727
  var createRouteEntry = (fileFullpath, sourceFolder) => {
843
- const resolvedPaths = isRouteFile(fileFullpath, sourceFolder);
844
- if (!resolvedPaths) {
845
- return;
846
- }
847
- const [folder, file] = resolvedPaths;
848
- const id = `${file.replace(/\W+/g, "_")}_${crc4(file)}`;
849
- const name = dirname2(file);
850
- try {
851
- const pathTokens = pathTokensFactory(dirname2(file));
852
- return {
853
- id,
854
- name,
855
- folder,
856
- file,
857
- fileFullpath,
858
- pathTokens,
859
- pathPattern: createPathPattern(pathTokens),
860
- honoPattern: createHonoPattern(pathTokens)
861
- };
862
- } catch (error) {
863
- console.error(
864
- `\u2757${styleText2("red", "ERROR")}: Failed parsing path for "${styleText2("cyan", file)}"`
865
- );
866
- console.error(error);
867
- return;
868
- }
728
+ const resolvedPaths = isRouteFile(fileFullpath, sourceFolder);
729
+ if (!resolvedPaths) return;
730
+ const [folder, file] = resolvedPaths;
731
+ const id = `${file.replace(/\W+/g, "_")}_${crc(file)}`;
732
+ const name = dirname(file);
733
+ try {
734
+ const pathTokens = pathTokensFactory(dirname(file));
735
+ return {
736
+ id,
737
+ name,
738
+ folder,
739
+ file,
740
+ fileFullpath,
741
+ pathTokens,
742
+ pathPattern: createPathPattern(pathTokens),
743
+ honoPattern: createHonoPattern(pathTokens)
744
+ };
745
+ } catch (error) {
746
+ console.error(`❗${styleText("red", "ERROR")}: Failed parsing path for "${styleText("cyan", file)}"`);
747
+ console.error(error);
748
+ return;
749
+ }
869
750
  };
751
+ /**
752
+ * Weight of a single path segment.
753
+ *
754
+ * Fixed values per kind prevent complex mixed segments
755
+ * from outscoring multiple statics.
756
+ *
757
+ * static: 4 (exact match, most specific)
758
+ * mixed: 3+ (static + param parts; more statics = slightly higher)
759
+ * required: 2 (matches any single segment)
760
+ * optional: 1 (may or may not match)
761
+ * splat: 0 (matches anything, least specific)
762
+ * */
870
763
  var segmentWeight = (token) => {
871
- if (token.kind === "static") {
872
- return 4;
873
- }
874
- if (token.kind === "mixed") {
875
- let statics = 0;
876
- let optionals = 0;
877
- for (const part2 of token.parts) {
878
- if (part2.type === "static") {
879
- statics += 1;
880
- } else if (part2.kind === "optional" || part2.kind === "splat") {
881
- optionals += 1;
882
- }
883
- }
884
- return 3 + statics * 0.01 - optionals * 5e-3;
885
- }
886
- const part = token.parts[0];
887
- switch (part.kind) {
888
- case "required":
889
- return 2;
890
- case "optional":
891
- return 1;
892
- case "splat":
893
- return 0;
894
- }
764
+ if (token.kind === "static") return 4;
765
+ if (token.kind === "mixed") {
766
+ let statics = 0;
767
+ let optionals = 0;
768
+ for (const part of token.parts) if (part.type === "static") statics += 1;
769
+ else if (part.kind === "optional" || part.kind === "splat") optionals += 1;
770
+ return 3 + statics * .01 - optionals * .005;
771
+ }
772
+ switch (token.parts[0].kind) {
773
+ case "required": return 2;
774
+ case "optional": return 1;
775
+ case "splat": return 0;
776
+ }
895
777
  };
778
+ /**
779
+ * Compare two route entries for registration order.
780
+ *
781
+ * More specific routes sort first (higher priority).
782
+ * Segments are compared left-to-right; first difference wins.
783
+ *
784
+ * When one route is a prefix of the other, the trailing segment
785
+ * determines order: a trailing splat loses (shorter sibling first),
786
+ * anything else wins (longer route is more specific).
787
+ *
788
+ * At equal weight and length, lexicographic tiebreak ensures
789
+ * deterministic ordering across JS engines.
790
+ * */
896
791
  var sortRoutes = (a, b) => {
897
- const pairs = Array.from(
898
- { length: Math.max(a.pathTokens.length, b.pathTokens.length) },
899
- (_, i) => [a.pathTokens[i], b.pathTokens[i]]
900
- );
901
- for (const [at, bt] of pairs) {
902
- if (!at || !bt) {
903
- const extra = at ?? bt;
904
- const isSplat = extra?.kind === "param" ? extra.parts[0].kind === "splat" : false;
905
- if (isSplat) {
906
- return at ? 1 : -1;
907
- }
908
- return at ? -1 : 1;
909
- }
910
- const aw = segmentWeight(at);
911
- const bw = segmentWeight(bt);
912
- if (aw !== bw) {
913
- return bw - aw;
914
- }
915
- }
916
- return a.pathPattern.localeCompare(b.pathPattern);
792
+ const pairs = Array.from({ length: Math.max(a.pathTokens.length, b.pathTokens.length) }, (_, i) => [a.pathTokens[i], b.pathTokens[i]]);
793
+ for (const [at, bt] of pairs) {
794
+ if (!at || !bt) {
795
+ const extra = at ?? bt;
796
+ if (extra?.kind === "param" ? extra.parts[0].kind === "splat" : false) return at ? 1 : -1;
797
+ return at ? -1 : 1;
798
+ }
799
+ const aw = segmentWeight(at);
800
+ const bw = segmentWeight(bt);
801
+ if (aw !== bw) return bw - aw;
802
+ }
803
+ return a.pathPattern.localeCompare(b.pathPattern);
917
804
  };
918
-
919
- // src/routes/resolve.ts
920
- import { dirname as dirname3, join as join5, resolve as resolve4 } from "node:path";
921
- import { styleText as styleText3 } from "node:util";
922
- import crc5 from "crc/crc32";
923
- import mimeTypes from "mime-types";
924
- import {
925
- defaults as defaults4
926
- } from "@kosmojs/core";
927
-
928
- // src/templates/resolved-types.hbs
805
+ //#endregion
806
+ //#region src/templates/resolved-types.hbs?raw
929
807
  var resolved_types_default = "{{#each resolvedTypes}}\nexport type {{name}} = {{text}};\n{{/each}}\n";
930
-
931
- // src/templates/types.hbs
932
- var types_default = '{{#each typeDeclarations}}{{text}}\n{{/each}}\n\nexport type {{params.id}} = {\n {{#each paramsSchema}}\n "{{name}}"{{#unless isRequired}}?{{/unless}}: {{#if refinement}}\n {{refinement.text}},\n {{else}}\n {{#if isSplat}}Array<string>{{else}}string{{/if}},\n {{/if}}\n {{/each}}\n};\n\n{{#each validationTypes}}export type {{id}} = {{text}};\n{{/each}}\n';
933
-
934
- // src/routes/resolve.ts
808
+ //#endregion
809
+ //#region src/templates/types.hbs?raw
810
+ var types_default = "{{#each typeDeclarations}}{{text}}\n{{/each}}\n\nexport type {{params.id}} = {\n {{#each paramsSchema}}\n \"{{name}}\"{{#unless isRequired}}?{{/unless}}: {{#if refinement}}\n {{refinement.text}},\n {{else}}\n {{#if isSplat}}Array<string>{{else}}string{{/if}},\n {{/if}}\n {{/each}}\n};\n\n{{#each validationTypes}}export type {{id}} = {{text}};\n{{/each}}\n";
811
+ //#endregion
812
+ //#region src/routes/resolve.ts
935
813
  var resolverFactory = (sourceFolder, cacheFactory) => {
936
- const {
937
- //
938
- generators = [],
939
- refineTypeName = defaults4.refineTypeName
940
- } = sourceFolder.config;
941
- const { typeResolverFactory, resolveRouteSignature } = astFactory();
942
- const resolveTypes = generators.some(({ meta }) => meta.resolveTypes);
943
- const {
944
- //
945
- literalTypesResolver,
946
- getSourceFile,
947
- refreshSourceFile
948
- } = typeResolverFactory(sourceFolder);
949
- return {
950
- pageLayoutResolver(entry) {
951
- const { name } = entry;
952
- const handler = async () => {
953
- return {
954
- kind: "pageLayout",
955
- entry
956
- };
957
- };
958
- return { name, handler };
959
- },
960
- pageRouteResolver(entry) {
961
- const {
962
- id,
963
- name,
964
- folder,
965
- file,
966
- fileFullpath,
967
- pathTokens,
968
- pathPattern,
969
- honoPattern
970
- } = entry;
971
- const handler = async () => {
972
- const entry2 = {
973
- id,
974
- name,
975
- pathTokens,
976
- pathPattern,
977
- honoPattern,
978
- params: {
979
- schema: pathTokens.flatMap((e) => {
980
- return e.parts.filter((p) => p.type === "param");
981
- })
982
- },
983
- folder,
984
- file,
985
- fileFullpath
986
- };
987
- return {
988
- kind: "pageRoute",
989
- entry: entry2
990
- };
991
- };
992
- return { name, handler };
993
- },
994
- apiUseResolver(entry) {
995
- const { name } = entry;
996
- const handler = async () => {
997
- return {
998
- kind: "apiUse",
999
- entry
1000
- };
1001
- };
1002
- return { name, handler };
1003
- },
1004
- apiRouteResolver(entry) {
1005
- const {
1006
- id,
1007
- name,
1008
- file,
1009
- folder,
1010
- fileFullpath,
1011
- pathTokens,
1012
- pathPattern,
1013
- honoPattern
1014
- } = entry;
1015
- const handler = async (updatedFile) => {
1016
- const paramsSchema = pathTokens.flatMap(
1017
- (e) => {
1018
- return e.parts.flatMap((p) => {
1019
- return p.type === "param" ? [p] : [];
1020
- });
1021
- }
1022
- );
1023
- const optionalParams = paramsSchema.length ? paramsSchema.filter((e) => e.kind === "required").length === 0 : true;
1024
- const cacheStore = cacheFactory ? cacheFactory({ id, file, fileFullpath }, sourceFolder, {
1025
- resolveTypes
1026
- }) : void 0;
1027
- let cache = cacheStore ? await cacheStore.get({ validate: true }) : void 0;
1028
- if (!cache) {
1029
- if (updatedFile === fileFullpath) {
1030
- await refreshSourceFile(fileFullpath);
1031
- }
1032
- const {
1033
- typeDeclarations,
1034
- paramsRefinements,
1035
- methods,
1036
- validationDefinitions: validationDefinitions2,
1037
- referencedFiles = []
1038
- } = await resolveRouteSignature(
1039
- { id, name, fileFullpath, optionalParams },
1040
- {
1041
- withReferencedFiles: true,
1042
- sourceFile: getSourceFile(fileFullpath),
1043
- relpathResolver(path) {
1044
- return join5(
1045
- sourceFolder.name,
1046
- defaults4.apiDir,
1047
- dirname3(file),
1048
- path
1049
- );
1050
- }
1051
- }
1052
- );
1053
- const validationTypes = validationDefinitions2.flatMap((def) => {
1054
- return def.target === "response" ? def.variants.flatMap(({ id: id2, body }) => {
1055
- return body ? [{ id: id2, text: body }] : [];
1056
- }) : [def.schema];
1057
- });
1058
- const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
1059
- if (text === "number") {
1060
- const param = paramsSchema.at(index);
1061
- return param ? [param.name] : [];
1062
- }
1063
- return [];
1064
- }) : [];
1065
- const typesFile = pathResolver(sourceFolder).createPath.libApi(
1066
- dirname3(file),
1067
- "types.ts"
1068
- );
1069
- const params = {
1070
- id: ["ParamsT", crc5(name)].join(""),
1071
- schema: paramsSchema,
1072
- resolvedType: void 0
1073
- };
1074
- const typesFileContent = render(types_default, {
1075
- params,
1076
- paramsSchema: paramsSchema.map((param, index) => {
1077
- return {
1078
- ...param,
1079
- isRequired: param.kind === "required",
1080
- isSplat: param.kind === "splat",
1081
- refinement: paramsRefinements?.at(index)
1082
- };
1083
- }),
1084
- typeDeclarations,
1085
- validationTypes
1086
- });
1087
- const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
1088
- stripComments: true,
1089
- overrides: { [refineTypeName]: refineTypeName },
1090
- withProperties: [
1091
- params.id,
1092
- ...validationTypes.flatMap(({ id: id2 }) => id2)
1093
- ]
1094
- }) : void 0;
1095
- await renderToFile(
1096
- typesFile,
1097
- resolvedTypes ? resolved_types_default : typesFileContent,
1098
- { resolvedTypes }
1099
- );
1100
- params.resolvedType = resolvedTypes?.find(
1101
- (e) => e.name === params.id
1102
- );
1103
- cache = {
1104
- // placeholder values, if cacheFactory given,
1105
- // they will be replaced by persistCache
1106
- hash: 0,
1107
- referencedFiles: {},
1108
- // real values
1109
- params,
1110
- methods,
1111
- typeDeclarations,
1112
- numericParams,
1113
- validationDefinitions: validationDefinitions2.map((def) => {
1114
- return {
1115
- ...def,
1116
- ...def.target === "response" ? {
1117
- variants: def.variants.map((variant) => {
1118
- return {
1119
- ...variant,
1120
- resolvedType: resolvedTypes?.find(
1121
- (e) => e.name === variant.id
1122
- )
1123
- };
1124
- })
1125
- } : {
1126
- schema: {
1127
- ...def.schema,
1128
- resolvedType: resolvedTypes?.find(
1129
- (e) => e.name === def.schema.id
1130
- )
1131
- }
1132
- }
1133
- };
1134
- })
1135
- };
1136
- if (cacheStore) {
1137
- const { referencedFiles: _void, ...rest } = cache;
1138
- cache = await cacheStore.set({ ...rest, referencedFiles });
1139
- }
1140
- }
1141
- const validationDefinitions = cache.validationDefinitions.flatMap(
1142
- (def) => {
1143
- let augmentedDef = def;
1144
- if (def.target === "response") {
1145
- augmentedDef = {
1146
- ...def,
1147
- variants: def.variants.flatMap((variant, i) => {
1148
- if (typeof variant.contentType !== "string") {
1149
- return [variant];
1150
- }
1151
- if (variant.contentType.includes("/")) {
1152
- return [variant];
1153
- }
1154
- const contentType = mimeTypes.lookup(variant.contentType);
1155
- if (contentType === false) {
1156
- console.warn(
1157
- styleText3(
1158
- ["bold", "red"],
1159
- "\u2717 Failed resolving Response Content Type"
1160
- )
1161
- );
1162
- console.warn(
1163
- ` Invalid value provided for mime-types lookup - ${variant.contentType}`
1164
- );
1165
- console.warn(
1166
- styleText3(
1167
- ["cyan"],
1168
- ` Response variant #${i} excluded from route schemas`
1169
- )
1170
- );
1171
- console.warn(` Route: ${name}; Method: ${def.method}`);
1172
- console.warn();
1173
- return [];
1174
- }
1175
- return [{ ...variant, contentType }];
1176
- })
1177
- };
1178
- } else if (def.contentType && !def.contentType.includes("/")) {
1179
- const contentType = mimeTypes.lookup(def.contentType);
1180
- if (contentType === false) {
1181
- console.warn(
1182
- styleText3(
1183
- ["bold", "red"],
1184
- "\u2717 Failed resolving Response Content Type"
1185
- )
1186
- );
1187
- console.warn(
1188
- ` Invalid value provided for mime-types lookup - ${def.contentType}`
1189
- );
1190
- console.warn(` Route: ${name}; Method: ${def.method}`);
1191
- console.warn();
1192
- } else {
1193
- augmentedDef = { ...def, contentType };
1194
- }
1195
- }
1196
- return augmentedDef ? [augmentedDef] : [];
1197
- }
1198
- );
1199
- const entry2 = {
1200
- id,
1201
- name,
1202
- pathTokens,
1203
- pathPattern,
1204
- honoPattern,
1205
- params: cache.params,
1206
- numericParams: cache.numericParams,
1207
- optionalParams,
1208
- folder,
1209
- file,
1210
- fileFullpath,
1211
- methods: cache.methods,
1212
- typeDeclarations: cache.typeDeclarations,
1213
- validationDefinitions,
1214
- referencedFiles: Object.keys(cache.referencedFiles).map(
1215
- // expand referenced files path,
1216
- // they are stored as relative in cache
1217
- (e) => resolve4(sourceFolder.root, e)
1218
- )
1219
- };
1220
- return {
1221
- kind: "apiRoute",
1222
- entry: entry2
1223
- };
1224
- };
1225
- return { name, handler };
1226
- }
1227
- };
814
+ const { generators = [], refineTypeName = defaults.refineTypeName } = sourceFolder.config;
815
+ const { typeResolverFactory, resolveRouteSignature } = astFactory();
816
+ const resolveTypes = generators.some(({ meta }) => meta.resolveTypes);
817
+ const { literalTypesResolver, getSourceFile, refreshSourceFile } = typeResolverFactory(sourceFolder);
818
+ return {
819
+ pageLayoutResolver(entry) {
820
+ const { name } = entry;
821
+ const handler = async () => {
822
+ return {
823
+ kind: "pageLayout",
824
+ entry
825
+ };
826
+ };
827
+ return {
828
+ name,
829
+ handler
830
+ };
831
+ },
832
+ pageRouteResolver(entry) {
833
+ const { id, name, folder, file, fileFullpath, pathTokens, pathPattern, honoPattern } = entry;
834
+ const handler = async () => {
835
+ return {
836
+ kind: "pageRoute",
837
+ entry: {
838
+ id,
839
+ name,
840
+ pathTokens,
841
+ pathPattern,
842
+ honoPattern,
843
+ params: { schema: pathTokens.flatMap((e) => {
844
+ return e.parts.filter((p) => p.type === "param");
845
+ }) },
846
+ folder,
847
+ file,
848
+ fileFullpath
849
+ }
850
+ };
851
+ };
852
+ return {
853
+ name,
854
+ handler
855
+ };
856
+ },
857
+ apiUseResolver(entry) {
858
+ const { name } = entry;
859
+ const handler = async () => {
860
+ return {
861
+ kind: "apiUse",
862
+ entry
863
+ };
864
+ };
865
+ return {
866
+ name,
867
+ handler
868
+ };
869
+ },
870
+ apiRouteResolver(entry) {
871
+ const { id, name, file, folder, fileFullpath, pathTokens, pathPattern, honoPattern } = entry;
872
+ const handler = async (updatedFile) => {
873
+ const paramsSchema = pathTokens.flatMap((e) => {
874
+ return e.parts.flatMap((p) => {
875
+ return p.type === "param" ? [p] : [];
876
+ });
877
+ });
878
+ const optionalParams = paramsSchema.length ? paramsSchema.filter((e) => e.kind === "required").length === 0 : true;
879
+ const cacheStore = cacheFactory ? cacheFactory({
880
+ id,
881
+ file,
882
+ fileFullpath
883
+ }, sourceFolder, { resolveTypes }) : void 0;
884
+ let cache = cacheStore ? await cacheStore.get({ validate: true }) : void 0;
885
+ if (!cache) {
886
+ if (updatedFile === fileFullpath) await refreshSourceFile(fileFullpath);
887
+ const { typeDeclarations, paramsRefinements, methods, validationDefinitions, referencedFiles = [] } = await resolveRouteSignature({
888
+ id,
889
+ name,
890
+ fileFullpath,
891
+ optionalParams
892
+ }, {
893
+ withReferencedFiles: true,
894
+ sourceFile: getSourceFile(fileFullpath),
895
+ relpathResolver(path) {
896
+ return join(sourceFolder.name, defaults.apiDir, dirname(file), path);
897
+ }
898
+ });
899
+ const validationTypes = validationDefinitions.flatMap((def) => {
900
+ return def.target === "response" ? def.variants.flatMap(({ id, body }) => {
901
+ return body ? [{
902
+ id,
903
+ text: body
904
+ }] : [];
905
+ }) : [def.schema];
906
+ });
907
+ const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
908
+ if (text === "number") {
909
+ const param = paramsSchema.at(index);
910
+ return param ? [param.name] : [];
911
+ }
912
+ return [];
913
+ }) : [];
914
+ const typesFile = pathResolver(sourceFolder).createPath.libApi(dirname(file), "types.ts");
915
+ const params = {
916
+ id: ["ParamsT", crc(name)].join(""),
917
+ schema: paramsSchema,
918
+ resolvedType: void 0
919
+ };
920
+ const typesFileContent = render(types_default, {
921
+ params,
922
+ paramsSchema: paramsSchema.map((param, index) => {
923
+ return {
924
+ ...param,
925
+ isRequired: param.kind === "required",
926
+ isSplat: param.kind === "splat",
927
+ refinement: paramsRefinements?.at(index)
928
+ };
929
+ }),
930
+ typeDeclarations,
931
+ validationTypes
932
+ });
933
+ const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
934
+ stripComments: true,
935
+ overrides: { [refineTypeName]: refineTypeName },
936
+ withProperties: [params.id, ...validationTypes.flatMap(({ id }) => id)]
937
+ }) : void 0;
938
+ /**
939
+ * Deploy types.ts file; required by core generators (like fetch).
940
+ * If types resolved, write resolved types;
941
+ * otherwise write original types extracted from API route.
942
+ * */
943
+ await renderToFile(typesFile, resolvedTypes ? resolved_types_default : typesFileContent, { resolvedTypes });
944
+ params.resolvedType = resolvedTypes?.find((e) => e.name === params.id);
945
+ cache = {
946
+ hash: 0,
947
+ referencedFiles: {},
948
+ params,
949
+ methods,
950
+ typeDeclarations,
951
+ numericParams,
952
+ validationDefinitions: validationDefinitions.map((def) => {
953
+ return {
954
+ ...def,
955
+ ...def.target === "response" ? { variants: def.variants.map((variant) => {
956
+ return {
957
+ ...variant,
958
+ resolvedType: resolvedTypes?.find((e) => e.name === variant.id)
959
+ };
960
+ }) } : { schema: {
961
+ ...def.schema,
962
+ resolvedType: resolvedTypes?.find((e) => e.name === def.schema.id)
963
+ } }
964
+ };
965
+ })
966
+ };
967
+ if (cacheStore) {
968
+ const { referencedFiles: _void, ...rest } = cache;
969
+ cache = await cacheStore.set({
970
+ ...rest,
971
+ referencedFiles
972
+ });
973
+ }
974
+ }
975
+ const validationDefinitions = cache.validationDefinitions.flatMap((def) => {
976
+ let augmentedDef = def;
977
+ if (def.target === "response") augmentedDef = {
978
+ ...def,
979
+ variants: def.variants.flatMap((variant, i) => {
980
+ if (typeof variant.contentType !== "string") return [variant];
981
+ if (variant.contentType.includes("/")) return [variant];
982
+ const contentType = mimeTypes.lookup(variant.contentType);
983
+ if (contentType === false) {
984
+ console.warn(styleText(["bold", "red"], "✗ Failed resolving Response Content Type"));
985
+ console.warn(` Invalid value provided for mime-types lookup - ${variant.contentType}`);
986
+ console.warn(styleText(["cyan"], ` Response variant #${i} excluded from route schemas`));
987
+ console.warn(` Route: ${name}; Method: ${def.method}`);
988
+ console.warn();
989
+ return [];
990
+ }
991
+ return [{
992
+ ...variant,
993
+ contentType
994
+ }];
995
+ })
996
+ };
997
+ else if (def.contentType && !def.contentType.includes("/")) {
998
+ const contentType = mimeTypes.lookup(def.contentType);
999
+ if (contentType === false) {
1000
+ console.warn(styleText(["bold", "red"], "✗ Failed resolving Response Content Type"));
1001
+ console.warn(` Invalid value provided for mime-types lookup - ${def.contentType}`);
1002
+ console.warn(` Route: ${name}; Method: ${def.method}`);
1003
+ console.warn();
1004
+ } else augmentedDef = {
1005
+ ...def,
1006
+ contentType
1007
+ };
1008
+ }
1009
+ return augmentedDef ? [augmentedDef] : [];
1010
+ });
1011
+ return {
1012
+ kind: "apiRoute",
1013
+ entry: {
1014
+ id,
1015
+ name,
1016
+ pathTokens,
1017
+ pathPattern,
1018
+ honoPattern,
1019
+ params: cache.params,
1020
+ numericParams: cache.numericParams,
1021
+ optionalParams,
1022
+ folder,
1023
+ file,
1024
+ fileFullpath,
1025
+ methods: cache.methods,
1026
+ typeDeclarations: cache.typeDeclarations,
1027
+ validationDefinitions,
1028
+ referencedFiles: Object.keys(cache.referencedFiles).map((e) => resolve(sourceFolder.root, e))
1029
+ }
1030
+ };
1031
+ };
1032
+ return {
1033
+ name,
1034
+ handler
1035
+ };
1036
+ }
1037
+ };
1228
1038
  };
1229
-
1230
- // src/routes/nesting.ts
1231
- import { basename } from "node:path";
1039
+ //#endregion
1040
+ //#region src/routes/nesting.ts
1232
1041
  var nestedRoutesFactory = (routeEntries) => {
1233
- const entryStack = structuredClone(routeEntries).sort(sortRoutes);
1234
- const transformEntries = (entries, parent) => {
1235
- return [...new Set(entries.map((e) => e.name))].flatMap((name) => {
1236
- const nameEntries = entryStack.flatMap(({ fileFullpath, ...entry }) => {
1237
- return entry.name === name ? [entry] : [];
1238
- });
1239
- const index = nameEntries.find(
1240
- (e) => basename(e.file).startsWith(PAGE_INDEX_BASENAME)
1241
- );
1242
- const layout = nameEntries.find(
1243
- (e) => basename(e.file).startsWith(PAGE_LAYOUT_BASENAME)
1244
- );
1245
- if (index || layout) {
1246
- return [
1247
- {
1248
- index: index ? {
1249
- ...index,
1250
- pathTokens: index.pathTokens.slice(
1251
- parent?.pathTokens.length || 0
1252
- )
1253
- } : void 0,
1254
- layout: layout ? {
1255
- ...layout,
1256
- pathTokens: layout.pathTokens.slice(
1257
- parent?.pathTokens.length || 0
1258
- )
1259
- } : void 0,
1260
- parent: parent?.name,
1261
- children: transformEntries(
1262
- findDescendantEntries(index || layout),
1263
- index || layout
1264
- )
1265
- }
1266
- ];
1267
- }
1268
- return [];
1269
- });
1270
- };
1271
- const findDescendantEntries = ({
1272
- name,
1273
- pathTokens
1274
- }) => {
1275
- const potentialChildren = entryStack.filter((entry) => {
1276
- if (entry.pathTokens.length <= pathTokens.length) {
1277
- return false;
1278
- }
1279
- if (!entry.name.startsWith(`${name}/`)) {
1280
- return false;
1281
- }
1282
- return true;
1283
- });
1284
- return potentialChildren.filter((child) => {
1285
- const hasIntermediateRoute = potentialChildren.some((intermediate) => {
1286
- if (intermediate === child) {
1287
- return false;
1288
- }
1289
- if (intermediate.pathTokens.length <= pathTokens.length) {
1290
- return false;
1291
- }
1292
- if (intermediate.pathTokens.length >= child.pathTokens.length) {
1293
- return false;
1294
- }
1295
- return child.name.startsWith(`${intermediate.name}/`);
1296
- });
1297
- return !hasIntermediateRoute;
1298
- });
1299
- };
1300
- const rootEntries = entryStack.filter((entry) => {
1301
- const hasParent = entryStack.some((potential) => {
1302
- if (potential === entry) {
1303
- return false;
1304
- }
1305
- if (potential.pathTokens.length >= entry.pathTokens.length) {
1306
- return false;
1307
- }
1308
- return entry.name.startsWith(`${potential.name}/`);
1309
- });
1310
- return !hasParent;
1311
- });
1312
- return transformEntries(rootEntries);
1042
+ const entryStack = structuredClone(routeEntries).sort(sortRoutes);
1043
+ /**
1044
+ * Group entries by name (at the same level, both layout and index share same name).
1045
+ *
1046
+ * Handle 3 cases:
1047
+ * - both index and layout exists
1048
+ * - only index exists
1049
+ * - only layout exists
1050
+ *
1051
+ * */
1052
+ const transformEntries = (entries, parent) => {
1053
+ return [...new Set(entries.map((e) => e.name))].flatMap((name) => {
1054
+ const nameEntries = entryStack.flatMap(({ fileFullpath, ...entry }) => {
1055
+ return entry.name === name ? [entry] : [];
1056
+ });
1057
+ const index = nameEntries.find((e) => basename(e.file).startsWith(PAGE_INDEX_BASENAME));
1058
+ const layout = nameEntries.find((e) => basename(e.file).startsWith(PAGE_LAYOUT_BASENAME));
1059
+ if (index || layout) return [{
1060
+ index: index ? {
1061
+ ...index,
1062
+ pathTokens: index.pathTokens.slice(parent?.pathTokens.length || 0)
1063
+ } : void 0,
1064
+ layout: layout ? {
1065
+ ...layout,
1066
+ pathTokens: layout.pathTokens.slice(parent?.pathTokens.length || 0)
1067
+ } : void 0,
1068
+ parent: parent?.name,
1069
+ children: transformEntries(findDescendantEntries(index || layout), index || layout)
1070
+ }];
1071
+ return [];
1072
+ });
1073
+ };
1074
+ /**
1075
+ * Recursively finds direct descendant routes.
1076
+ *
1077
+ * "Direct" means the next existing route in the path hierarchy,
1078
+ * skipping intermediate path segments that don't have index/layout files.
1079
+ *
1080
+ * Example: blog/post/[slug]/index.tsx is a direct child of blog/index.tsx
1081
+ * even though "post" segment exists (it has no index.tsx).
1082
+ * */
1083
+ const findDescendantEntries = ({ name, pathTokens }) => {
1084
+ const potentialChildren = entryStack.filter((entry) => {
1085
+ if (entry.pathTokens.length <= pathTokens.length) return false;
1086
+ if (!entry.name.startsWith(`${name}/`)) return false;
1087
+ return true;
1088
+ });
1089
+ return potentialChildren.filter((child) => {
1090
+ return !potentialChildren.some((intermediate) => {
1091
+ if (intermediate === child) return false;
1092
+ if (intermediate.pathTokens.length <= pathTokens.length) return false;
1093
+ if (intermediate.pathTokens.length >= child.pathTokens.length) return false;
1094
+ return child.name.startsWith(`${intermediate.name}/`);
1095
+ });
1096
+ });
1097
+ };
1098
+ return transformEntries(entryStack.filter((entry) => {
1099
+ return !entryStack.some((potential) => {
1100
+ if (potential === entry) return false;
1101
+ if (potential.pathTokens.length >= entry.pathTokens.length) return false;
1102
+ return entry.name.startsWith(`${potential.name}/`);
1103
+ });
1104
+ }));
1313
1105
  };
1314
-
1315
- // src/routes/index.ts
1106
+ //#endregion
1107
+ //#region src/routes/index.ts
1316
1108
  var routesFactory = async (sourceFolder, cacheFactory) => {
1317
- const {
1318
- apiRouteResolver,
1319
- apiUseResolver,
1320
- pageRouteResolver,
1321
- pageLayoutResolver
1322
- } = resolverFactory(sourceFolder, cacheFactory);
1323
- const resolversFactory = (routeFiles2) => {
1324
- const resolvers = /* @__PURE__ */ new Map();
1325
- const entries = routeFiles2.flatMap((file) => {
1326
- const entry = createRouteEntry(file, sourceFolder);
1327
- return entry ? [entry] : [];
1328
- });
1329
- for (const entry of entries) {
1330
- if (entry.folder === defaults5.apiDir) {
1331
- if (isApiRoute(entry.file)) {
1332
- resolvers.set(entry.fileFullpath, apiRouteResolver(entry));
1333
- } else if (isApiUse(entry.file)) {
1334
- resolvers.set(entry.fileFullpath, apiUseResolver(entry));
1335
- }
1336
- } else if (entry.folder === defaults5.pagesDir) {
1337
- if (isPageRoute(entry.file)) {
1338
- resolvers.set(entry.fileFullpath, pageRouteResolver(entry));
1339
- } else if (isPageLayout(entry.file)) {
1340
- resolvers.set(entry.fileFullpath, pageLayoutResolver(entry));
1341
- }
1342
- }
1343
- }
1344
- return resolvers;
1345
- };
1346
- const routeFiles = await scanRoutes(sourceFolder);
1347
- return {
1348
- resolvers: resolversFactory(routeFiles),
1349
- resolversFactory
1350
- };
1109
+ const { apiRouteResolver, apiUseResolver, pageRouteResolver, pageLayoutResolver } = resolverFactory(sourceFolder, cacheFactory);
1110
+ const resolversFactory = (routeFiles) => {
1111
+ const resolvers = /* @__PURE__ */ new Map();
1112
+ const entries = routeFiles.flatMap((file) => {
1113
+ const entry = createRouteEntry(file, sourceFolder);
1114
+ return entry ? [entry] : [];
1115
+ });
1116
+ for (const entry of entries) if (entry.folder === defaults.apiDir) {
1117
+ if (isApiRoute(entry.file)) resolvers.set(entry.fileFullpath, apiRouteResolver(entry));
1118
+ else if (isApiUse(entry.file)) resolvers.set(entry.fileFullpath, apiUseResolver(entry));
1119
+ } else if (entry.folder === defaults.pagesDir) {
1120
+ if (isPageRoute(entry.file)) resolvers.set(entry.fileFullpath, pageRouteResolver(entry));
1121
+ else if (isPageLayout(entry.file)) resolvers.set(entry.fileFullpath, pageLayoutResolver(entry));
1122
+ }
1123
+ return resolvers;
1124
+ };
1125
+ return {
1126
+ resolvers: resolversFactory(await scanRoutes(sourceFolder)),
1127
+ resolversFactory
1128
+ };
1351
1129
  };
1352
-
1353
- // src/spinner.ts
1354
- import { Spinner } from "@topcli/spinner";
1130
+ //#endregion
1131
+ //#region src/spinner.ts
1355
1132
  var spinnerFactory = (startText) => {
1356
- const spinner = new Spinner().start(startText);
1357
- let _text = startText;
1358
- return {
1359
- text(text) {
1360
- _text = text;
1361
- spinner.text = text;
1362
- },
1363
- append(text) {
1364
- spinner.text = `${_text} \u203A ${text}`;
1365
- },
1366
- succeed(text) {
1367
- if (text) {
1368
- this.append(text);
1369
- } else {
1370
- this.text(_text);
1371
- }
1372
- spinner.succeed();
1373
- },
1374
- failed(text) {
1375
- if (text) {
1376
- this.text([_text, text].join("\n"));
1377
- }
1378
- spinner.failed();
1379
- }
1380
- };
1133
+ const spinner = new Spinner().start(startText);
1134
+ let _text = startText;
1135
+ return {
1136
+ text(text) {
1137
+ _text = text;
1138
+ spinner.text = text;
1139
+ },
1140
+ append(text) {
1141
+ spinner.text = `${_text} › ${text}`;
1142
+ },
1143
+ succeed(text) {
1144
+ if (text) this.append(text);
1145
+ else this.text(_text);
1146
+ spinner.succeed();
1147
+ },
1148
+ failed(text) {
1149
+ if (text) this.text([_text, text].join("\n"));
1150
+ spinner.failed();
1151
+ }
1152
+ };
1381
1153
  };
1382
1154
  var withSpinner = (text, pipe, spinner) => pipe(spinner || spinnerFactory(text));
1383
-
1384
- // src/typebox.ts
1385
- import { defaults as defaults6 } from "@kosmojs/core";
1155
+ //#endregion
1156
+ //#region src/typebox.ts
1386
1157
  var typeboxLiteralText = (text, sourceFolder) => {
1387
- const { refineTypeName = defaults6.refineTypeName } = sourceFolder.config;
1388
- return [
1389
- // Escape backticks for safe use in template literals
1390
- [/(?<!\\)`/g, "\\`"],
1391
- // Escape $ for safe use in template literals
1392
- [/(?<!\\)\$\{/g, "\\${"],
1393
- /**
1394
- * TypeBox's built-in `Options` type is not configurable.
1395
- * To allow a custom type name, exposing `refineTypeName` option,
1396
- * defaulted to VRefine, then renaming it to `Options`.
1397
- * */
1398
- [new RegExp(`\\b${refineTypeName}\\s*<`, "g"), "Options<"]
1399
- ].reduce((text2, [a, b]) => text2.replace(a, b), text);
1158
+ const { refineTypeName = defaults.refineTypeName } = sourceFolder.config;
1159
+ return [
1160
+ [/(?<!\\)`/g, "\\`"],
1161
+ [/(?<!\\)\$\{/g, "\\${"],
1162
+ [new RegExp(`\\b${refineTypeName}\\s*<`, "g"), "Options<"]
1163
+ ].reduce((text, [a, b]) => text.replace(a, b), text);
1400
1164
  };
1401
-
1402
- // src/vite.ts
1403
- import { builtinModules } from "node:module";
1165
+ //#endregion
1166
+ //#region src/vite.ts
1404
1167
  var nodePrefix = () => {
1405
- return {
1406
- name: "kosmojs:node-prefix",
1407
- enforce: "pre",
1408
- resolveId(source) {
1409
- return builtinModules.includes(source) ? { id: `node:${source}`, external: true } : void 0;
1410
- }
1411
- };
1412
- };
1413
- var vitePlugins = {
1414
- nodePrefix
1168
+ return {
1169
+ name: "kosmojs:node-prefix",
1170
+ enforce: "pre",
1171
+ resolveId(source) {
1172
+ return builtinModules.includes(source) ? {
1173
+ id: `node:${source}`,
1174
+ external: true
1175
+ } : void 0;
1176
+ }
1177
+ };
1415
1178
  };
1416
- export {
1417
- API_INDEX_BASENAME,
1418
- API_INDEX_PATTERN,
1419
- API_USE_BASENAME,
1420
- API_USE_PATTERN,
1421
- PAGE_INDEX_BASENAME,
1422
- PAGE_INDEX_PATTERN,
1423
- PAGE_LAYOUT_BASENAME,
1424
- PAGE_LAYOUT_PATTERN,
1425
- astFactory,
1426
- createHonoPattern,
1427
- createPathPattern,
1428
- createRouteEntry,
1429
- createTemplateResolver,
1430
- defineGenerator,
1431
- defineGeneratorFactory,
1432
- isApiRoute,
1433
- isApiUse,
1434
- isPageLayout,
1435
- isPageRoute,
1436
- isRouteFile,
1437
- nestedRoutesFactory,
1438
- normalizeStaticValue,
1439
- pathExists,
1440
- pathResolver,
1441
- pathTokensFactory,
1442
- render,
1443
- renderFactory,
1444
- renderToFile,
1445
- routesFactory,
1446
- scanRoutes,
1447
- sortRoutes,
1448
- spinnerFactory,
1449
- typeboxLiteralText,
1450
- vitePlugins,
1451
- withSpinner
1452
- };
1453
- //# sourceMappingURL=index.js.map
1179
+ var vitePlugins = { nodePrefix };
1180
+ //#endregion
1181
+ export { API_INDEX_BASENAME, API_INDEX_PATTERN, API_USE_BASENAME, API_USE_PATTERN, PAGE_INDEX_BASENAME, PAGE_INDEX_PATTERN, PAGE_LAYOUT_BASENAME, PAGE_LAYOUT_PATTERN, astFactory, createHonoPattern, createPathPattern, createRouteEntry, createTemplateResolver, defineGenerator, defineGeneratorFactory, isApiRoute, isApiUse, isPageLayout, isPageRoute, isRouteFile, nestedRoutesFactory, normalizeStaticValue, pathExists, pathResolver, pathTokensFactory, render, renderFactory, renderToFile, routesFactory, scanRoutes, sortRoutes, spinnerFactory, typeboxLiteralText, vitePlugins, withSpinner };
1182
+
1183
+ //# sourceMappingURL=index.js.map