@kosmojs/dev 0.0.25 → 0.0.27

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.
@@ -1,1321 +0,0 @@
1
- // src/base-plugin/worker.ts
2
- import { parentPort, workerData } from "node:worker_threads";
3
- import chokidar from "chokidar";
4
- import crc6 from "crc/crc32";
5
-
6
- // src/paths.ts
7
- import { join } from "node:path";
8
-
9
- // src/defaults.ts
10
- var defaults = {
11
- appPrefix: "~",
12
- srcPrefix: "@",
13
- libPrefix: "_",
14
- coreDir: "core",
15
- srcDir: "src",
16
- libDir: "lib",
17
- configDir: "config",
18
- apiDir: "api",
19
- pagesDir: "pages",
20
- entryDir: "entry",
21
- fetchDir: "fetch"
22
- };
23
-
24
- // src/paths.ts
25
- var pathResolver = ({
26
- appRoot: appRoot2,
27
- sourceFolder: sourceFolder2
28
- }) => {
29
- const createPath2 = (...a) => {
30
- return appRoot2 ? join(appRoot2, ...a) : join(...a);
31
- };
32
- const createImport = {
33
- coreApi(...a) {
34
- return join(defaults.appPrefix, defaults.coreDir, defaults.apiDir, ...a);
35
- },
36
- src(...a) {
37
- return join(defaults.srcPrefix, sourceFolder2, ...a);
38
- },
39
- config(...a) {
40
- return this.src(defaults.configDir, ...a);
41
- },
42
- api(...a) {
43
- return this.src(defaults.apiDir, ...a);
44
- },
45
- pages(...a) {
46
- return this.src(defaults.pagesDir, ...a);
47
- },
48
- lib(...a) {
49
- return join(defaults.libPrefix, sourceFolder2, ...a);
50
- },
51
- libApi(...a) {
52
- return this.lib(defaults.apiDir, ...a);
53
- },
54
- libEntry(...a) {
55
- return this.lib(defaults.entryDir, ...a);
56
- }
57
- };
58
- return {
59
- createPath: {
60
- coreApi(...a) {
61
- return createPath2(defaults.coreDir, defaults.apiDir, ...a);
62
- },
63
- src(...a) {
64
- return createPath2(defaults.srcDir, sourceFolder2, ...a);
65
- },
66
- api(...a) {
67
- return this.src(defaults.apiDir, ...a);
68
- },
69
- pages(...a) {
70
- return this.src(defaults.pagesDir, ...a);
71
- },
72
- config(...a) {
73
- return this.src(defaults.configDir, ...a);
74
- },
75
- entry(...a) {
76
- return this.src(defaults.entryDir, ...a);
77
- },
78
- lib(...a) {
79
- return createPath2(defaults.libDir, defaults.srcDir, sourceFolder2, ...a);
80
- },
81
- libApi(...a) {
82
- return this.lib(defaults.apiDir, ...a);
83
- },
84
- libEntry(...a) {
85
- return this.lib(defaults.entryDir, ...a);
86
- },
87
- libPages(...a) {
88
- return this.lib(defaults.pagesDir, ...a);
89
- }
90
- },
91
- createImport,
92
- createImportHelper: (key, ...a) => {
93
- return createImport[key](...a.slice(0, -1));
94
- }
95
- };
96
- };
97
-
98
- // src/routes-factory/resolve.ts
99
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
100
- import { styleText as styleText2 } from "node:util";
101
- import crc5 from "crc/crc32";
102
- import mimeTypes from "mime-types";
103
- import picomatch from "picomatch";
104
- import { glob } from "tinyglobby";
105
-
106
- // src/ast.ts
107
- import { resolve } from "node:path";
108
- import { styleText } from "node:util";
109
- import crc from "crc/crc32";
110
- import { flattener } from "tfusion";
111
- import {
112
- Project,
113
- SyntaxKind
114
- } from "ts-morph";
115
- import {
116
- HTTPMethods,
117
- RequestValidationTargets
118
- } from "@kosmojs/api";
119
- var createProject = (opts) => new Project(opts);
120
- var resolveRouteSignature = async (route, opts) => {
121
- const {
122
- sourceFile = createProject().addSourceFileAtPath(route.fileFullpath)
123
- } = { ...opts };
124
- const [typeDeclarations, referencedFiles] = extractTypeDeclarations(
125
- sourceFile,
126
- opts
127
- );
128
- const defaultExport = extractDefaultExport(sourceFile);
129
- const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
130
- const methods = defaultExport ? extractRouteMethods(route, defaultExport) : [];
131
- return {
132
- typeDeclarations,
133
- paramsRefinements,
134
- methods: methods.map((e) => e.method),
135
- validationDefinitions: methods.flatMap((e) => e.validationDefinitions),
136
- referencedFiles
137
- };
138
- };
139
- var extractDefaultExport = (sourceFile) => {
140
- const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
141
- if (exportAssignment.isExportEquals()) {
142
- return [];
143
- }
144
- const callExpression = exportAssignment.getExpression();
145
- return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
146
- });
147
- return defaultExport;
148
- };
149
- var extractParamsRefinements = (callExpression) => {
150
- const [
151
- _routeName,
152
- // first generic - the route name
153
- paramsGeneric
154
- // second generic - params refinements
155
- ] = extractGenerics(callExpression);
156
- if (!paramsGeneric?.isKind(SyntaxKind.TupleType)) {
157
- return;
158
- }
159
- return paramsGeneric.getElements().map((node, index) => {
160
- return {
161
- index,
162
- text: node.getText()
163
- };
164
- });
165
- };
166
- var extractRouteMethods = (route, callExpression) => {
167
- const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
168
- if (!funcDeclaration) {
169
- return [];
170
- }
171
- const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(
172
- SyntaxKind.ArrayLiteralExpression
173
- );
174
- if (!arrayLiteralExpression) {
175
- return [];
176
- }
177
- const callExpressions = [];
178
- for (const e of arrayLiteralExpression.getChildrenOfKind(
179
- SyntaxKind.CallExpression
180
- )) {
181
- const name = e.getExpression().getText();
182
- if (HTTPMethods[name]) {
183
- callExpressions.push([e, name]);
184
- }
185
- }
186
- const methods = [];
187
- for (const [callExpression2, method] of callExpressions) {
188
- const [vDefs, vOpts] = extractGenerics(callExpression2);
189
- methods.push({
190
- method,
191
- validationDefinitions: extractValidationDefinitions(
192
- route,
193
- method,
194
- vDefs,
195
- vOpts
196
- )
197
- });
198
- }
199
- return methods;
200
- };
201
- var parseRuntimeValidation = (typeNode) => {
202
- if (typeNode.isKind(SyntaxKind.LiteralType)) {
203
- const literal = typeNode.getFirstChild();
204
- if (literal?.isKind(SyntaxKind.TrueKeyword)) {
205
- return true;
206
- } else if (literal?.isKind(SyntaxKind.FalseKeyword)) {
207
- return false;
208
- }
209
- }
210
- return void 0;
211
- };
212
- var extractResponseVariant = (typeNode) => {
213
- if (!typeNode.isKind(SyntaxKind.TupleType)) {
214
- return;
215
- }
216
- let status = 200;
217
- let contentType;
218
- let body;
219
- const [statusNode, contentTypeNode, bodyNode] = typeNode.getElements();
220
- if (statusNode?.isKind(SyntaxKind.LiteralType)) {
221
- const literal = statusNode.getFirstChildByKind(SyntaxKind.NumericLiteral);
222
- if (literal) {
223
- status = Number(literal.getText());
224
- }
225
- }
226
- if (contentTypeNode) {
227
- contentType = extractStringLiteral(contentTypeNode);
228
- }
229
- if (bodyNode) {
230
- body = bodyNode.getText();
231
- if (["object"].includes(body)) {
232
- body = "{}";
233
- }
234
- }
235
- return { status, contentType, body };
236
- };
237
- var parseValidationOptions = (typeNode) => {
238
- const opts = {};
239
- if (!typeNode?.isKind(SyntaxKind.TypeLiteral)) {
240
- return opts;
241
- }
242
- for (const prop of typeNode.getMembers()) {
243
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
244
- continue;
245
- }
246
- const target = prop.getName();
247
- const typeNode2 = prop.getTypeNodeOrThrow();
248
- if (!typeNode2.isKind(SyntaxKind.TypeLiteral)) {
249
- continue;
250
- }
251
- let contentType;
252
- let runtimeValidation;
253
- const customErrors = {};
254
- for (const member of typeNode2.getMembers()) {
255
- if (!member.isKind(SyntaxKind.PropertySignature)) {
256
- continue;
257
- }
258
- const nameNode = member.getNameNode();
259
- const valueNode = member.getTypeNodeOrThrow();
260
- const name = nameNode.isKind(SyntaxKind.StringLiteral) ? nameNode.getLiteralText() : nameNode.getText();
261
- if (name === "contentType") {
262
- contentType = extractStringLiteral(valueNode);
263
- } else if (name === "runtimeValidation") {
264
- runtimeValidation = parseRuntimeValidation(valueNode);
265
- } else if (name.startsWith("error")) {
266
- const literal = extractStringLiteral(valueNode);
267
- if (literal) {
268
- customErrors[name] = literal;
269
- }
270
- }
271
- }
272
- opts[target] = {
273
- contentType,
274
- runtimeValidation,
275
- customErrors
276
- };
277
- }
278
- return opts;
279
- };
280
- var extractStringLiteral = (typeNode) => {
281
- const literal = typeNode.isKind(SyntaxKind.LiteralType) ? typeNode.getFirstChildByKind(SyntaxKind.StringLiteral) : void 0;
282
- return literal ? literal.getLiteralText() : void 0;
283
- };
284
- var extractValidationDefinitions = (route, method, defsNode, optsNode) => {
285
- const definitions = [];
286
- if (!defsNode?.isKind(SyntaxKind.TypeLiteral)) {
287
- return definitions;
288
- }
289
- const optsMap = parseValidationOptions(optsNode);
290
- const createId = (target, hash) => {
291
- return [
292
- target.replace(/^./, (c) => c.toUpperCase()),
293
- "T",
294
- method,
295
- crc(route.id + hash)
296
- ].join("");
297
- };
298
- for (const prop of defsNode.getMembers()) {
299
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
300
- continue;
301
- }
302
- const target = prop.getName();
303
- const typeNode = prop.getTypeNodeOrThrow();
304
- if (target === "response") {
305
- const variants = typeNode.isKind(SyntaxKind.UnionType) ? typeNode.getChildrenOfKind(SyntaxKind.TupleType) : [typeNode];
306
- definitions.push({
307
- ...optsMap[target],
308
- method,
309
- target,
310
- variants: variants.flatMap((e, i) => {
311
- const { status, contentType, body } = extractResponseVariant(e) || {};
312
- if (!status) {
313
- return [];
314
- }
315
- if (contentType && typeof contentType !== "string") {
316
- console.warn(
317
- styleText(
318
- ["bold", "red"],
319
- `\u2717 The second element of a response variant should specify the Response Content Type`
320
- )
321
- );
322
- console.warn(
323
- styleText(["blue"], ` Example: [200, "json", Schema]`)
324
- );
325
- console.warn(
326
- ` Route: ${route.name}; Method: ${method}; Response Variant: #${i}`
327
- );
328
- console.warn();
329
- }
330
- return [
331
- {
332
- id: createId(target, JSON.stringify([status, contentType, body])),
333
- status,
334
- contentType,
335
- body
336
- }
337
- ];
338
- })
339
- });
340
- } else if (Object.keys(RequestValidationTargets).includes(target)) {
341
- definitions.push({
342
- ...optsMap[target],
343
- method,
344
- target,
345
- schema: {
346
- id: createId(target),
347
- text: typeNode.getText()
348
- }
349
- });
350
- }
351
- }
352
- return definitions;
353
- };
354
- var extractTypeDeclarations = (sourceFile, opts) => {
355
- const declarations = [];
356
- const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
357
- for (const declaration of sourceFile.getImportDeclarations()) {
358
- const modulePath = declaration.getModuleSpecifierValue();
359
- const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
360
- const typeOnlyDeclaration = declaration.isTypeOnly();
361
- const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
362
- if (defaultImport) {
363
- const name = defaultImport.getText();
364
- const text = `import type ${name} from "${path}";`;
365
- declarations.push({
366
- importDeclaration: {
367
- name,
368
- path
369
- },
370
- text
371
- });
372
- if (referencedFiles) {
373
- referencedFiles.push(...getReferencedFiles(defaultImport));
374
- }
375
- }
376
- for (const namedImport of declaration.getNamedImports()) {
377
- if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
378
- const nameNode = namedImport.getNameNode();
379
- const name = nameNode.getText();
380
- const alias = namedImport.getAliasNode()?.getText();
381
- const nameText = alias ? `${name} as ${alias}` : name;
382
- declarations.push({
383
- importDeclaration: {
384
- name,
385
- alias,
386
- path
387
- },
388
- text: `import type { ${nameText} } from "${path}";`
389
- });
390
- if (referencedFiles) {
391
- if (nameNode.isKind(SyntaxKind.Identifier)) {
392
- referencedFiles.push(...getReferencedFiles(nameNode));
393
- }
394
- }
395
- }
396
- }
397
- }
398
- for (const declaration of sourceFile.getTypeAliases()) {
399
- const name = declaration.getName();
400
- const text = declaration.getFullText().trim();
401
- declarations.push({
402
- typeAliasDeclaration: { name },
403
- text
404
- });
405
- }
406
- for (const declaration of sourceFile.getInterfaces()) {
407
- const name = declaration.getName();
408
- const text = declaration.getFullText().trim();
409
- declarations.push({
410
- interfaceDeclaration: { name },
411
- text
412
- });
413
- }
414
- for (const declaration of sourceFile.getEnums()) {
415
- const name = declaration.getName();
416
- const text = declaration.getFullText().trim();
417
- declarations.push({
418
- enumDeclaration: { name },
419
- text
420
- });
421
- }
422
- for (const declaration of sourceFile.getExportDeclarations()) {
423
- const typeOnlyDeclaration = declaration.isTypeOnly();
424
- const modulePath = declaration.getModuleSpecifierValue();
425
- const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
426
- for (const namedExport of declaration.getNamedExports()) {
427
- if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
428
- const nameNode = namedExport.getNameNode();
429
- const name = nameNode.getText();
430
- const alias = namedExport.getAliasNode()?.getText();
431
- const nameText = alias ? `${name} as ${alias}` : name;
432
- declarations.push({
433
- exportDeclaration: {
434
- name,
435
- alias: alias ?? name,
436
- path
437
- },
438
- text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
439
- });
440
- if (referencedFiles) {
441
- if (nameNode.isKind(SyntaxKind.Identifier)) {
442
- referencedFiles.push(...getReferencedFiles(nameNode));
443
- }
444
- }
445
- }
446
- }
447
- }
448
- return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
449
- };
450
- var getReferencedFiles = (importIdentifier) => {
451
- const declarations = importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || [];
452
- return declarations.flatMap((e) => {
453
- const sourceFile = e.getSourceFile();
454
- return sourceFile ? [sourceFile.getFilePath()] : [];
455
- });
456
- };
457
- var extractGenerics = (callExpression) => {
458
- return callExpression.getTypeArguments();
459
- };
460
- var typeResolverFactory = ({ appRoot: appRoot2 }) => {
461
- const project = createProject({
462
- tsConfigFilePath: resolve(appRoot2, "tsconfig.json"),
463
- skipAddingFilesFromTsConfig: true
464
- });
465
- const literalTypesResolver = (literalTypes, options) => {
466
- const sourceFile = project.createSourceFile(
467
- `${crc(literalTypes)}-${Date.now()}.ts`,
468
- literalTypes,
469
- { overwrite: true }
470
- );
471
- const resolvedTypes = flattener(project, sourceFile, {
472
- ...options,
473
- stripComments: true
474
- });
475
- project.removeSourceFile(sourceFile);
476
- return resolvedTypes;
477
- };
478
- return {
479
- getSourceFile: (fileFullpath) => {
480
- return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
481
- },
482
- refreshSourceFile: async (fileFullpath) => {
483
- const sourceFile = project.getSourceFile(fileFullpath);
484
- if (sourceFile) {
485
- await sourceFile.refreshFromFileSystem();
486
- }
487
- },
488
- literalTypesResolver
489
- };
490
- };
491
-
492
- // src/cache.ts
493
- import { mkdir, readFile, writeFile } from "node:fs/promises";
494
- import { dirname, resolve as resolve2 } from "node:path";
495
- import crc2 from "crc/crc32";
496
- import self from "@kosmojs/dev/package.json" with { type: "json" };
497
-
498
- // src/fs.ts
499
- import { access, constants } from "node:fs/promises";
500
- var pathExists = async (path) => {
501
- try {
502
- await access(path, constants.F_OK);
503
- return true;
504
- } catch {
505
- return false;
506
- }
507
- };
508
-
509
- // src/cache.ts
510
- var cacheFactory = (route, {
511
- appRoot: appRoot2,
512
- sourceFolder: sourceFolder2,
513
- extraContext
514
- }) => {
515
- const cacheFile = pathResolver({
516
- appRoot: appRoot2,
517
- sourceFolder: sourceFolder2
518
- }).createPath.libApi(dirname(route.file), "cache.json");
519
- const getCache = async (opt) => {
520
- if (await pathExists(cacheFile)) {
521
- try {
522
- const cache = JSON.parse(await readFile(cacheFile, "utf8"));
523
- return opt?.validate ? validateCache(cache) : cache;
524
- } catch (_e) {
525
- }
526
- }
527
- return void 0;
528
- };
529
- const persistCache = async ({
530
- referencedFiles: _referencedFiles,
531
- ...rest
532
- }) => {
533
- const hash = await generateFileHash(route.fileFullpath, {
534
- ...extraContext
535
- });
536
- const referencedFiles = {};
537
- for (const file of _referencedFiles) {
538
- referencedFiles[
539
- // Strip project root to ensure cached paths are relative
540
- // and portable across environments (CI, local, etc.)
541
- file.replace(`${appRoot2}/`, "")
542
- ] = await generateFileHash(file);
543
- }
544
- const cache = { ...rest, hash, referencedFiles };
545
- await mkdir(dirname(cacheFile), { recursive: true });
546
- await writeFile(cacheFile, JSON.stringify(cache, null, 2), "utf8");
547
- return cache;
548
- };
549
- const validateCache = async (cache) => {
550
- if (!cache?.hash) {
551
- return;
552
- }
553
- if (!cache.typeDeclarations || !cache.referencedFiles) {
554
- return;
555
- }
556
- const hash = await generateFileHash(route.fileFullpath, {
557
- ...extraContext
558
- });
559
- if (!identicalHashSum(cache.hash, hash)) {
560
- return;
561
- }
562
- for (const [file, hash2] of Object.entries(cache.referencedFiles)) {
563
- if (!identicalHashSum(hash2, await generateFileHash(resolve2(appRoot2, file)))) {
564
- return;
565
- }
566
- }
567
- return cache;
568
- };
569
- return {
570
- getCache,
571
- validateCache,
572
- persistCache
573
- };
574
- };
575
- var generateFileHash = async (file, extraContext) => {
576
- let fileContent;
577
- try {
578
- fileContent = await readFile(file, "utf8");
579
- } catch (_e) {
580
- return 0;
581
- }
582
- return fileContent ? crc2(
583
- JSON.stringify({
584
- ...extraContext,
585
- [self.cacheVersion]: fileContent
586
- })
587
- ) : 0;
588
- };
589
- var identicalHashSum = (a, b) => {
590
- return a === b;
591
- };
592
-
593
- // src/render.ts
594
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
595
- import { dirname as dirname2, join as join2 } from "node:path";
596
- import crc3 from "crc/crc32";
597
- import handlebars from "handlebars";
598
- var render = (template, context, options) => {
599
- const { noEscape = true, renderer = handlebars } = { ...options };
600
- return renderer.compile(template, { noEscape })(context);
601
- };
602
- var renderToFile = async (file, template, context, options) => {
603
- const content = render(template, context, options);
604
- if (await pathExists(file)) {
605
- const { overwrite = true } = { ...options };
606
- if (overwrite === false) {
607
- return;
608
- }
609
- const fileContent = await readFile2(file, "utf8");
610
- if (typeof overwrite === "function" && !overwrite(fileContent)) {
611
- return;
612
- }
613
- if (crc3(content) === crc3(fileContent)) {
614
- return;
615
- }
616
- }
617
- await mkdir2(dirname2(file), { recursive: true });
618
- await writeFile2(file, content, "utf8");
619
- };
620
-
621
- // src/routes-factory/base.ts
622
- import crc4 from "crc/crc32";
623
- import { parse } from "path-to-regexp";
624
- var pathTokensFactory = (path, {
625
- transformStaticValue = normalizeStaticValue
626
- } = {}) => {
627
- const extractParts = (tokens2, createConst, insideGroup = false) => {
628
- const parts = [];
629
- for (const token of tokens2) {
630
- switch (token.type) {
631
- case "text":
632
- if (token.value !== "/") {
633
- parts.push({
634
- type: "static",
635
- value: transformStaticValue(token.value)
636
- });
637
- }
638
- break;
639
- case "param":
640
- parts.push({
641
- type: "param",
642
- kind: insideGroup ? "optional" : "required",
643
- name: token.name,
644
- const: createConst(token.name)
645
- });
646
- break;
647
- case "wildcard":
648
- parts.push({
649
- type: "param",
650
- kind: "splat",
651
- name: token.name,
652
- const: createConst(token.name)
653
- });
654
- break;
655
- case "group":
656
- parts.push(...extractParts(token.tokens, createConst, true));
657
- break;
658
- }
659
- }
660
- return parts;
661
- };
662
- const patternTransforms = [
663
- // Transform required params: [id] => :id
664
- // Only pure \w param names,
665
- // [some-id] used as is, not treated as param,
666
- // use [some_id] instead.
667
- (s) => s.replace(/\[(\w+)\]/g, ":$1"),
668
- // Transform optional params: {id} => {:id}
669
- // Only pure \w param names,
670
- // anything else treated as a path-to-regexp pattern and used as is.
671
- // {some-id} treated as an optional static segment.
672
- // use {some_id} for simple param syntax
673
- // or {:some-id} pattern where :some is the param name and -id is a static segment.
674
- (s) => s.replace(/\{(\w+)\}/g, "{:$1}"),
675
- // Transform splat params: {...param} => {*param}
676
- (s) => s.replace(/\{\.\.\./g, "{*"),
677
- // Insert leading slash inside optional/splat groups.
678
- // {:name} => {/:name}
679
- // {*name} => {/*name}
680
- (s) => {
681
- return s.startsWith("{") ? s.replace(/^\{/, "{/") : s;
682
- }
683
- ];
684
- const detectBareParams = (s) => {
685
- let depth = 0;
686
- for (const [i, ch] of [...s].entries()) {
687
- if (ch === "{") {
688
- depth += 1;
689
- } else if (ch === "}") {
690
- depth -= 1;
691
- } else if (ch === ":" && depth === 0) {
692
- const match = s.slice(i + 1).match(/^\w+/);
693
- return match?.[0] || ":";
694
- }
695
- }
696
- return;
697
- };
698
- const tokens = path.replace(/^index\/?/, "").split("/").flatMap((orig) => {
699
- if (!orig.length) {
700
- return [];
701
- }
702
- const bareParam = detectBareParams(orig);
703
- if (bareParam === ":") {
704
- throw new Error(
705
- `${path} contains colons outside braces, use : only within {}`
706
- );
707
- } else if (bareParam) {
708
- throw new Error(
709
- `${path} contains bare params, use [${bareParam}] instead of :${bareParam}`
710
- );
711
- }
712
- const pattern = patternTransforms.reduce((src, fn) => fn(src), orig);
713
- const { tokens: tokens2 } = parse(pattern);
714
- const parts = extractParts(tokens2, (val) => {
715
- return /\W/.test(val) || /^\d/.test(val) ? [val.replace(/^\d+|\W/g, "_"), crc4(orig)].join("_") : val;
716
- });
717
- const isStatic = parts.length === 1 ? parts[0].type === "static" : false;
718
- const isParam = parts.length === 1 ? parts[0].type === "param" : false;
719
- const kind = isStatic ? "static" : isParam ? "param" : "mixed";
720
- return [
721
- {
722
- kind,
723
- orig,
724
- pattern,
725
- parts
726
- }
727
- ];
728
- });
729
- return [
730
- tokens,
731
- tokens.map(({ pattern }, i) => {
732
- const next = tokens[i + 1];
733
- if (!next || next.pattern.includes("/")) {
734
- return pattern;
735
- }
736
- const slashRequired = tokens.slice(i + 1).some((e) => {
737
- return e.parts.some((e2) => {
738
- return e2.type === "static" || e2.kind === "required";
739
- });
740
- });
741
- return slashRequired ? `${pattern}/` : pattern;
742
- }).join("")
743
- ];
744
- };
745
- var normalizeStaticValue = (value) => {
746
- return value.replace(/\+/g, "\\\\+");
747
- };
748
-
749
- // src/routes-factory/templates/resolved-types.hbs
750
- var resolved_types_default = "{{#each resolvedTypes}}\nexport type {{name}} = {{text}};\n{{/each}}\n";
751
-
752
- // src/routes-factory/templates/types.hbs
753
- 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';
754
-
755
- // src/routes-factory/resolve.ts
756
- var API_INDEX_BASENAME = "index";
757
- var API_INDEX_PATTERN = `${API_INDEX_BASENAME}.ts`;
758
- var API_USE_BASENAME = "use";
759
- var API_USE_PATTERN = `${API_USE_BASENAME}.ts`;
760
- var PAGE_INDEX_BASENAME = "index";
761
- var PAGE_INDEX_PATTERN = `${PAGE_INDEX_BASENAME}.{tsx,vue}`;
762
- var PAGE_LAYOUT_BASENAME = "layout";
763
- var PAGE_LAYOUT_PATTERN = `${PAGE_LAYOUT_BASENAME}.{tsx,vue}`;
764
- var ROUTE_FILE_PATTERNS = [
765
- // match index files in api dir
766
- `${defaults.apiDir}/**/${API_INDEX_PATTERN}`,
767
- // match use files in api dir
768
- `${defaults.apiDir}/**/${API_USE_PATTERN}`,
769
- // match index files in pages dir
770
- `${defaults.pagesDir}/**/${PAGE_INDEX_PATTERN}`,
771
- // match layout files in pages dir
772
- `${defaults.pagesDir}/**/${PAGE_LAYOUT_PATTERN}`
773
- ];
774
- var scanRoutes = async ({
775
- appRoot: appRoot2,
776
- sourceFolder: sourceFolder2
777
- }) => {
778
- const { createPath: createPath2 } = pathResolver({ appRoot: appRoot2, sourceFolder: sourceFolder2 });
779
- return glob(ROUTE_FILE_PATTERNS, {
780
- cwd: createPath2.src(),
781
- absolute: true,
782
- onlyFiles: true,
783
- followSymbolicLinks: false,
784
- ignore: [
785
- // ignore top-level matches, routes resides in folders, even index route
786
- `${defaults.apiDir}/${API_INDEX_PATTERN}`,
787
- `${defaults.apiDir}/${API_USE_PATTERN}`,
788
- `${defaults.pagesDir}/${PAGE_INDEX_PATTERN}`,
789
- `${defaults.pagesDir}/${PAGE_LAYOUT_PATTERN}`
790
- ]
791
- });
792
- };
793
- var isRouteFile = (file, {
794
- appRoot: appRoot2,
795
- sourceFolder: sourceFolder2
796
- }) => {
797
- const [_sourceFolder, folder, ...rest] = resolve3(appRoot2, file).replace(`${appRoot2}/${defaults.srcDir}/`, "").split("/");
798
- if (!folder || _sourceFolder !== sourceFolder2 || rest.length < 2) {
799
- return false;
800
- }
801
- return picomatch.isMatch(join3(folder, ...rest), ROUTE_FILE_PATTERNS) ? [folder, rest.join("/")] : false;
802
- };
803
- var isApiRoute = (file) => {
804
- return picomatch.matchBase(file, `**/${API_INDEX_PATTERN}`);
805
- };
806
- var isApiUse = (file) => {
807
- return picomatch.matchBase(file, `**/${API_USE_PATTERN}`);
808
- };
809
- var isPageRoute = (file) => {
810
- return picomatch.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
811
- };
812
- var isPageLayout = (file) => {
813
- return picomatch.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
814
- };
815
- var createRouteEntry = (fileFullpath, {
816
- appRoot: appRoot2,
817
- sourceFolder: sourceFolder2
818
- }) => {
819
- const resolvedPaths = isRouteFile(fileFullpath, { appRoot: appRoot2, sourceFolder: sourceFolder2 });
820
- if (!resolvedPaths) {
821
- return;
822
- }
823
- const [folder, file] = resolvedPaths;
824
- const id = `${file.replace(/\W+/g, "_")}_${crc5(file)}`;
825
- const name = dirname3(file);
826
- try {
827
- const [pathTokens, pathPattern] = pathTokensFactory(dirname3(file));
828
- return { id, name, folder, file, fileFullpath, pathTokens, pathPattern };
829
- } catch (error) {
830
- console.error(
831
- `\u2757${styleText2("red", "ERROR")}: Failed parsing path for "${styleText2("cyan", file)}"`
832
- );
833
- console.error(error);
834
- return;
835
- }
836
- };
837
- var pageLayoutResolverFactory = () => {
838
- return (entry) => {
839
- const { name } = entry;
840
- const handler = async () => {
841
- return {
842
- kind: "pageLayout",
843
- entry
844
- };
845
- };
846
- return { name, handler };
847
- };
848
- };
849
- var pageRouteResolverFactory = () => {
850
- return (entry) => {
851
- const { id, name, folder, file, fileFullpath, pathTokens, pathPattern } = entry;
852
- const handler = async () => {
853
- const entry2 = {
854
- id,
855
- name,
856
- pathTokens,
857
- pathPattern,
858
- params: {
859
- schema: pathTokens.flatMap((e) => {
860
- return e.parts.filter((p) => p.type === "param");
861
- })
862
- },
863
- folder,
864
- file,
865
- fileFullpath
866
- };
867
- return {
868
- kind: "pageRoute",
869
- entry: entry2
870
- };
871
- };
872
- return { name, handler };
873
- };
874
- };
875
- var apiUseResolverFactory = () => {
876
- return (entry) => {
877
- const { name } = entry;
878
- const handler = async () => {
879
- return {
880
- kind: "apiUse",
881
- entry
882
- };
883
- };
884
- return { name, handler };
885
- };
886
- };
887
- var apiRouteResolverFactory = (pluginOptions) => {
888
- const {
889
- appRoot: appRoot2,
890
- sourceFolder: sourceFolder2,
891
- generators: generators2 = [],
892
- refineTypeName
893
- } = pluginOptions;
894
- const resolveTypes = generators2.some((e) => e.options?.resolveTypes);
895
- const {
896
- //
897
- literalTypesResolver,
898
- getSourceFile,
899
- refreshSourceFile
900
- } = typeResolverFactory(pluginOptions);
901
- return ({
902
- id,
903
- name,
904
- file,
905
- folder,
906
- fileFullpath,
907
- pathTokens,
908
- pathPattern
909
- }) => {
910
- const handler = async (updatedFile) => {
911
- const paramsSchema = pathTokens.flatMap(
912
- (e) => {
913
- return e.parts.flatMap((p) => {
914
- return p.type === "param" ? [p] : [];
915
- });
916
- }
917
- );
918
- const optionalParams = paramsSchema.length ? paramsSchema.filter((e) => e.kind === "required").length === 0 : true;
919
- const { getCache, persistCache } = cacheFactory(
920
- { id, file, fileFullpath },
921
- {
922
- appRoot: appRoot2,
923
- sourceFolder: sourceFolder2,
924
- extraContext: { resolveTypes }
925
- }
926
- );
927
- let cache = await getCache({ validate: true });
928
- if (!cache) {
929
- if (updatedFile === fileFullpath) {
930
- await refreshSourceFile(fileFullpath);
931
- }
932
- const {
933
- typeDeclarations,
934
- paramsRefinements,
935
- methods,
936
- validationDefinitions: validationDefinitions2,
937
- referencedFiles = []
938
- } = await resolveRouteSignature(
939
- { id, name, fileFullpath, optionalParams },
940
- {
941
- withReferencedFiles: true,
942
- sourceFile: getSourceFile(fileFullpath),
943
- relpathResolver(path) {
944
- return join3(sourceFolder2, defaults.apiDir, dirname3(file), path);
945
- }
946
- }
947
- );
948
- const validationTypes = validationDefinitions2.flatMap((def) => {
949
- return def.target === "response" ? def.variants.flatMap(({ id: id2, body }) => {
950
- return body ? [{ id: id2, text: body }] : [];
951
- }) : [def.schema];
952
- });
953
- const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
954
- if (text === "number") {
955
- const param = paramsSchema.at(index);
956
- return param ? [param.name] : [];
957
- }
958
- return [];
959
- }) : [];
960
- const typesFile = pathResolver({
961
- appRoot: appRoot2,
962
- sourceFolder: sourceFolder2
963
- }).createPath.libApi(dirname3(file), "types.ts");
964
- const params = {
965
- id: ["ParamsT", crc5(name)].join(""),
966
- schema: paramsSchema,
967
- resolvedType: void 0
968
- };
969
- const typesFileContent = render(types_default, {
970
- params,
971
- paramsSchema: paramsSchema.map((param, index) => {
972
- return {
973
- ...param,
974
- isRequired: param.kind === "required",
975
- isSplat: param.kind === "splat",
976
- refinement: paramsRefinements?.at(index)
977
- };
978
- }),
979
- typeDeclarations,
980
- validationTypes
981
- });
982
- const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
983
- stripComments: true,
984
- overrides: { [refineTypeName]: refineTypeName },
985
- withProperties: [
986
- params.id,
987
- ...validationTypes.flatMap(({ id: id2 }) => id2)
988
- ]
989
- }) : void 0;
990
- await renderToFile(
991
- typesFile,
992
- resolvedTypes ? resolved_types_default : typesFileContent,
993
- { resolvedTypes }
994
- );
995
- params.resolvedType = resolvedTypes?.find((e) => e.name === params.id);
996
- cache = await persistCache({
997
- params,
998
- methods,
999
- typeDeclarations,
1000
- numericParams,
1001
- referencedFiles,
1002
- validationDefinitions: validationDefinitions2.map((def) => {
1003
- return {
1004
- ...def,
1005
- ...def.target === "response" ? {
1006
- variants: def.variants.map((variant) => {
1007
- return {
1008
- ...variant,
1009
- resolvedType: resolvedTypes?.find(
1010
- (e) => e.name === variant.id
1011
- )
1012
- };
1013
- })
1014
- } : {
1015
- schema: {
1016
- ...def.schema,
1017
- resolvedType: resolvedTypes?.find(
1018
- (e) => e.name === def.schema.id
1019
- )
1020
- }
1021
- }
1022
- };
1023
- })
1024
- });
1025
- }
1026
- const validationDefinitions = cache.validationDefinitions.flatMap(
1027
- (def) => {
1028
- let augmentedDef = def;
1029
- if (def.target === "response") {
1030
- augmentedDef = {
1031
- ...def,
1032
- variants: def.variants.flatMap((variant, i) => {
1033
- if (typeof variant.contentType !== "string") {
1034
- return [variant];
1035
- }
1036
- if (variant.contentType.includes("/")) {
1037
- return [variant];
1038
- }
1039
- const contentType = mimeTypes.lookup(variant.contentType);
1040
- if (contentType === false) {
1041
- console.warn(
1042
- styleText2(
1043
- ["bold", "red"],
1044
- "\u2717 Failed resolving Response Content Type"
1045
- )
1046
- );
1047
- console.warn(
1048
- ` Invalid value provided for mime-types lookup - ${variant.contentType}`
1049
- );
1050
- console.warn(
1051
- styleText2(
1052
- ["cyan"],
1053
- ` Response variant #${i} excluded from route schemas`
1054
- )
1055
- );
1056
- console.warn(` Route: ${name}; Method: ${def.method}`);
1057
- console.warn();
1058
- return [];
1059
- }
1060
- return [{ ...variant, contentType }];
1061
- })
1062
- };
1063
- } else if (def.contentType && !def.contentType.includes("/")) {
1064
- const contentType = mimeTypes.lookup(def.contentType);
1065
- if (contentType === false) {
1066
- console.warn(
1067
- styleText2(
1068
- ["bold", "red"],
1069
- "\u2717 Failed resolving Response Content Type"
1070
- )
1071
- );
1072
- console.warn(
1073
- ` Invalid value provided for mime-types lookup - ${def.contentType}`
1074
- );
1075
- console.warn(` Route: ${name}; Method: ${def.method}`);
1076
- console.warn();
1077
- } else {
1078
- augmentedDef = { ...def, contentType };
1079
- }
1080
- }
1081
- return augmentedDef ? [augmentedDef] : [];
1082
- }
1083
- );
1084
- const entry = {
1085
- id,
1086
- name,
1087
- pathTokens,
1088
- pathPattern,
1089
- params: cache.params,
1090
- numericParams: cache.numericParams,
1091
- optionalParams,
1092
- folder,
1093
- file,
1094
- fileFullpath,
1095
- methods: cache.methods,
1096
- typeDeclarations: cache.typeDeclarations,
1097
- validationDefinitions,
1098
- referencedFiles: Object.keys(cache.referencedFiles).map(
1099
- // expand referenced files path,
1100
- // they are stored as relative in cache
1101
- (e) => resolve3(appRoot2, e)
1102
- )
1103
- };
1104
- return {
1105
- kind: "apiRoute",
1106
- entry
1107
- };
1108
- };
1109
- return { name, handler };
1110
- };
1111
- };
1112
-
1113
- // src/routes-factory/index.ts
1114
- var routesFactory = async (pluginOptions) => {
1115
- const { appRoot: appRoot2, sourceFolder: sourceFolder2 } = pluginOptions;
1116
- const apiRouteResolver = apiRouteResolverFactory(pluginOptions);
1117
- const apiUseResolver = apiUseResolverFactory(pluginOptions);
1118
- const pageRouteResolver = pageRouteResolverFactory(pluginOptions);
1119
- const pageLayoutResolver = pageLayoutResolverFactory(pluginOptions);
1120
- const resolversFactory2 = (routeFiles2) => {
1121
- const resolvers2 = /* @__PURE__ */ new Map();
1122
- const entries = routeFiles2.flatMap((file) => {
1123
- const entry = createRouteEntry(file, pluginOptions);
1124
- return entry ? [entry] : [];
1125
- });
1126
- for (const entry of entries) {
1127
- if (entry.folder === defaults.apiDir) {
1128
- if (isApiRoute(entry.file)) {
1129
- resolvers2.set(entry.fileFullpath, apiRouteResolver(entry));
1130
- } else if (isApiUse(entry.file)) {
1131
- resolvers2.set(entry.fileFullpath, apiUseResolver(entry));
1132
- }
1133
- } else if (entry.folder === defaults.pagesDir) {
1134
- if (isPageRoute(entry.file)) {
1135
- resolvers2.set(entry.fileFullpath, pageRouteResolver(entry));
1136
- } else if (isPageLayout(entry.file)) {
1137
- resolvers2.set(entry.fileFullpath, pageLayoutResolver(entry));
1138
- }
1139
- }
1140
- }
1141
- return resolvers2;
1142
- };
1143
- const routeFiles = await scanRoutes({ appRoot: appRoot2, sourceFolder: sourceFolder2 });
1144
- return {
1145
- resolvers: resolversFactory2(routeFiles),
1146
- resolversFactory: resolversFactory2
1147
- };
1148
- };
1149
-
1150
- // src/base-plugin/worker.ts
1151
- var { generatorModules, ...restOptions } = workerData;
1152
- var generators = [];
1153
- for (const [path, opts] of generatorModules) {
1154
- generators.push(await import(path).then((m) => m.default(opts)));
1155
- }
1156
- var resolvedOptions = {
1157
- ...restOptions,
1158
- generators
1159
- };
1160
- var { appRoot, sourceFolder } = resolvedOptions;
1161
- var watchHandlers = [];
1162
- var resolvedEntries = /* @__PURE__ */ new Map();
1163
- var { resolvers, resolversFactory } = await routesFactory(resolvedOptions);
1164
- var spinnerFactory = (startText) => {
1165
- const id = [startText, Date.now().toString()].map(crc6).join(":");
1166
- const postMessage = (method, text) => {
1167
- const spinner = { id, startText, method, text };
1168
- parentPort?.postMessage({ spinner });
1169
- };
1170
- const postError = (error) => {
1171
- parentPort?.postMessage({ error: structuredClone(error) });
1172
- };
1173
- return {
1174
- id,
1175
- startText,
1176
- text(text) {
1177
- postMessage("text", text);
1178
- },
1179
- append(text) {
1180
- postMessage("append", text);
1181
- },
1182
- succeed(text) {
1183
- postMessage("succeed", text);
1184
- },
1185
- failed(error) {
1186
- postError(error);
1187
- postMessage("failed", error?.stack || error?.message);
1188
- }
1189
- };
1190
- };
1191
- var createEventHandler = async (file) => {
1192
- const [resolver] = resolversFactory([file]).values();
1193
- if (resolver) {
1194
- const spinner = spinnerFactory(`Resolving ${resolver.name} Route`);
1195
- try {
1196
- const resolvedEntry = await resolver.handler();
1197
- resolvers.set(file, resolver);
1198
- resolvedEntries.set(resolvedEntry.entry.fileFullpath, resolvedEntry);
1199
- spinner.succeed();
1200
- } catch (error) {
1201
- spinner.failed(error);
1202
- }
1203
- }
1204
- };
1205
- var updateEventHandler = async (file) => {
1206
- const relatedResolvers = /* @__PURE__ */ new Map();
1207
- if (resolvedEntries.has(file)) {
1208
- const resolver = resolvers.get(file);
1209
- if (resolver) {
1210
- relatedResolvers.set(file, resolver);
1211
- }
1212
- } else {
1213
- const referencedRoutes = resolvedEntries.values().flatMap(({ kind, entry }) => {
1214
- return kind === "apiRoute" ? entry.referencedFiles.includes(file) ? [entry] : [] : [];
1215
- });
1216
- for (const route of referencedRoutes) {
1217
- const resolver = resolvers.get(route.fileFullpath);
1218
- if (resolver) {
1219
- relatedResolvers.set(route.fileFullpath, resolver);
1220
- }
1221
- }
1222
- }
1223
- let spinner = spinnerFactory(`Updating ${relatedResolvers.size} Routes`);
1224
- for (const resolver of relatedResolvers.values()) {
1225
- spinner.append(resolver.name);
1226
- try {
1227
- const resolvedEntry = await resolver.handler(file);
1228
- resolvedEntries.set(resolvedEntry.entry.fileFullpath, resolvedEntry);
1229
- } catch (error) {
1230
- spinner.failed(error);
1231
- spinner = spinnerFactory(`Updating ${relatedResolvers.size} Routes`);
1232
- }
1233
- }
1234
- spinner.succeed();
1235
- };
1236
- var deleteEventHandler = async () => {
1237
- };
1238
- var runWatchHandlers = async (event) => {
1239
- let spinner = spinnerFactory("Running Generators");
1240
- const entries = Array.from(resolvedEntries.values());
1241
- for (const { name, handler } of watchHandlers) {
1242
- spinner.append(name);
1243
- try {
1244
- await handler(structuredClone(entries), event);
1245
- } catch (error) {
1246
- spinner.failed(error);
1247
- spinner = spinnerFactory("Running Generators");
1248
- }
1249
- }
1250
- spinner.succeed();
1251
- };
1252
- var { createPath } = pathResolver({ appRoot, sourceFolder });
1253
- var watcher = chokidar.watch(
1254
- [
1255
- // watching for changes in sourceFolder's apiDir and pagesDir
1256
- createPath.api(),
1257
- createPath.pages()
1258
- ],
1259
- {
1260
- ...resolvedOptions.watcher.options,
1261
- awaitWriteFinish: typeof resolvedOptions.watcher.options?.awaitWriteFinish === "object" ? resolvedOptions.watcher.options.awaitWriteFinish : {
1262
- stabilityThreshold: resolvedOptions.watcher.delay,
1263
- pollInterval: Math.floor(resolvedOptions.watcher.delay / 4)
1264
- },
1265
- // Do Not emit "add" event for existing files
1266
- ignoreInitial: true
1267
- // Not using Chokidar's `ignored` option.
1268
- // Instead, allow all events through and filter them manually as needed.
1269
- }
1270
- );
1271
- watcher.on("all", async (event, file) => {
1272
- if (event.endsWith("Dir")) {
1273
- return;
1274
- }
1275
- if (!isRouteFile(file, { appRoot, sourceFolder })) {
1276
- return;
1277
- }
1278
- const match = {
1279
- add: { handler: createEventHandler, kind: "create" },
1280
- change: { handler: updateEventHandler, kind: "update" },
1281
- unlink: { handler: deleteEventHandler, kind: "delete" }
1282
- }[event];
1283
- if (match) {
1284
- const { handler, kind } = match;
1285
- await handler(file);
1286
- await runWatchHandlers({ kind, file });
1287
- }
1288
- });
1289
- {
1290
- let spinner = spinnerFactory("Resolving Routes");
1291
- for (const { name, handler } of resolvers.values()) {
1292
- spinner.append(
1293
- `[ ${resolvedEntries.size + 1} of ${resolvers.size} ] ${name}`
1294
- );
1295
- try {
1296
- const resolvedEntry = await handler();
1297
- resolvedEntries.set(resolvedEntry.entry.fileFullpath, resolvedEntry);
1298
- } catch (error) {
1299
- spinner.failed(error);
1300
- spinner = spinnerFactory("Resolving Routes");
1301
- }
1302
- }
1303
- spinner.succeed();
1304
- }
1305
- {
1306
- let spinner = spinnerFactory("Initializing Generators");
1307
- for (const { name, factory } of generators) {
1308
- spinner.append(name);
1309
- try {
1310
- const { watch } = await factory(resolvedOptions);
1311
- watchHandlers.push({ name, handler: watch });
1312
- } catch (error) {
1313
- spinner.failed(error);
1314
- spinner = spinnerFactory("Initializing Generators");
1315
- }
1316
- }
1317
- spinner.succeed();
1318
- }
1319
- await runWatchHandlers();
1320
- parentPort?.postMessage("ready");
1321
- //# sourceMappingURL=worker.js.map