pastoria 1.0.8 → 1.0.9

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/src/generate.ts CHANGED
@@ -20,6 +20,7 @@
20
20
  * - Add @resource <resource-name> to exports for lazy loading
21
21
  * - Add @appRoot to a component to designate it as the application root wrapper
22
22
  * - Add @gqlContext to a class extending PastoriaRootContext to provide a custom GraphQL context
23
+ * - Add @serverRoute to functions to add an express handler
23
24
  *
24
25
  * The generator automatically creates Zod schemas for route parameters based on
25
26
  * TypeScript types, enabling runtime validation and type safety.
@@ -27,25 +28,20 @@
27
28
 
28
29
  import {readFile} from 'node:fs/promises';
29
30
  import * as path from 'node:path';
30
- import {default as pc} from 'picocolors';
31
- import {Project, SourceFile, Symbol, SyntaxKind, ts, TypeFlags} from 'ts-morph';
32
-
33
- const JS_RESOURCE_FILENAME = '__generated__/router/js_resource.ts';
34
- const JS_RESOURCE_TEMPLATE = path.join(
35
- import.meta.dirname,
36
- '../templates/js_resource.ts',
37
- );
38
-
39
- const ROUTER_FILENAME = '__generated__/router/router.tsx';
40
- const ROUTER_TEMPLATE = path.join(
41
- import.meta.dirname,
42
- '../templates/router.tsx',
43
- );
44
-
45
- const APP_ROOT_FILENAME = '__generated__/router/app_root.ts';
46
- const CONTEXT_FILENAME = '__generated__/router/context.ts';
47
-
48
- async function loadRouterFiles(project: Project) {
31
+ import pc from 'picocolors';
32
+ import {
33
+ CodeBlockWriter,
34
+ IndentationText,
35
+ Project,
36
+ SourceFile,
37
+ Symbol,
38
+ SyntaxKind,
39
+ ts,
40
+ TypeFlags,
41
+ WriterFunction,
42
+ } from 'ts-morph';
43
+
44
+ async function loadRouterTemplates(project: Project, filename: string) {
49
45
  async function loadSourceFile(fileName: string, templateFileName: string) {
50
46
  const template = await readFile(templateFileName, 'utf-8');
51
47
  const warningComment = `/*
@@ -59,50 +55,51 @@ async function loadRouterFiles(project: Project) {
59
55
  });
60
56
  }
61
57
 
62
- const [jsResource, router] = await Promise.all([
63
- loadSourceFile(JS_RESOURCE_FILENAME, JS_RESOURCE_TEMPLATE),
64
- loadSourceFile(ROUTER_FILENAME, ROUTER_TEMPLATE),
65
- ]);
66
-
67
- return {jsResource, router} as const;
58
+ const template = path.join(import.meta.dirname, '../templates', filename);
59
+ const output = path.join('__generated__/router', filename);
60
+ return loadSourceFile(output, template);
68
61
  }
69
62
 
70
63
  type RouterResource = {
71
- resourceName: string;
72
64
  sourceFile: SourceFile;
73
65
  symbol: Symbol;
66
+ queries: Map<string, string>;
67
+ entryPoints: Map<string, string>;
74
68
  };
75
69
 
76
70
  type RouterRoute = {
77
- routeName: string;
78
71
  sourceFile: SourceFile;
79
72
  symbol: Symbol;
80
73
  params: Map<string, ts.Type>;
81
74
  };
82
75
 
83
- type AppRoot = {
76
+ type ExportedSymbol = {
84
77
  sourceFile: SourceFile;
85
78
  symbol: Symbol;
86
79
  };
87
80
 
88
- type GqlContext = {
89
- sourceFile: SourceFile;
90
- symbol: Symbol;
91
- };
81
+ interface PastoriaMetadata {
82
+ resources: Map<string, RouterResource>;
83
+ routes: Map<string, RouterRoute>;
84
+ serverHandlers: Map<string, ExportedSymbol>;
85
+ appRoot: ExportedSymbol | null;
86
+ gqlContext: ExportedSymbol | null;
87
+ }
92
88
 
93
89
  // Regex to quickly check if a file contains any Pastoria JSDoc tags
94
- const PASTORIA_TAG_REGEX = /@(route|resource|appRoot|param|gqlContext)\b/;
95
-
96
- function collectRouterNodes(project: Project): {
97
- resources: RouterResource[];
98
- routes: RouterRoute[];
99
- appRoot: AppRoot | null;
100
- gqlContext: GqlContext | null;
101
- } {
102
- const resources: RouterResource[] = [];
103
- const routes: RouterRoute[] = [];
104
- let appRoot: AppRoot | null = null;
105
- let gqlContext: GqlContext | null = null;
90
+ const PASTORIA_TAG_REGEX =
91
+ /@(route|resource|appRoot|param|gqlContext|serverRoute)\b/;
92
+
93
+ /**
94
+ *
95
+ * @query {abc} 123
96
+ */
97
+ function collectPastoriaMetadata(project: Project): PastoriaMetadata {
98
+ const resources = new Map<string, RouterResource>();
99
+ const routes = new Map<string, RouterRoute>();
100
+ const serverHandlers = new Map<string, ExportedSymbol>();
101
+ let appRoot: ExportedSymbol | null = null;
102
+ let gqlContext: ExportedSymbol | null = null;
106
103
 
107
104
  function visitRouterNodes(sourceFile: SourceFile) {
108
105
  // Skip generated files
@@ -117,8 +114,10 @@ function collectRouterNodes(project: Project): {
117
114
  }
118
115
 
119
116
  sourceFile.getExportSymbols().forEach((symbol) => {
120
- let routerResource = null as RouterResource | null;
121
- let routerRoute = null as RouterRoute | null;
117
+ let routerResource = null as [string, RouterResource] | null;
118
+ const resourceQueries = new Map<string, string>();
119
+ const resourceEntryPoints = new Map<string, string>();
120
+ let routerRoute = null as [string, RouterRoute] | null;
122
121
  const routeParams = new Map<string, ts.Type>();
123
122
 
124
123
  function visitJSDocTags(tag: ts.JSDoc | ts.JSDocTag) {
@@ -137,25 +136,51 @@ function collectRouterNodes(project: Project): {
137
136
  } else if (typeof tag.comment === 'string') {
138
137
  switch (tag.tagName.getText()) {
139
138
  case 'route': {
140
- routerRoute = {
141
- routeName: tag.comment,
142
- sourceFile,
143
- symbol,
144
- params: routeParams,
145
- };
139
+ routerRoute = [
140
+ tag.comment,
141
+ {sourceFile, symbol, params: routeParams},
142
+ ];
146
143
  break;
147
144
  }
148
145
  case 'resource': {
149
- routerResource = {
150
- resourceName: tag.comment,
151
- sourceFile,
152
- symbol,
153
- };
146
+ routerResource = [
147
+ tag.comment,
148
+ {
149
+ sourceFile,
150
+ symbol,
151
+ queries: resourceQueries,
152
+ entryPoints: resourceEntryPoints,
153
+ },
154
+ ];
155
+ break;
156
+ }
157
+ case 'serverRoute': {
158
+ serverHandlers.set(tag.comment, {sourceFile, symbol});
159
+ break;
160
+ }
161
+ case 'query': {
162
+ const match = tag.comment.match(
163
+ /^\s*\{\s*(?<query>\w+)\s*\}\s+(?<name>\w+)\s*$/,
164
+ )?.groups;
165
+
166
+ if (match && match.query && match.name) {
167
+ resourceQueries.set(match.name, match.query);
168
+ }
169
+ break;
170
+ }
171
+ case 'entrypoint': {
172
+ const match = tag.comment.match(
173
+ /^\s*\{\s*(?<resource>[\w#]+)\s*\}\s+(?<name>\w+)\s*$/,
174
+ )?.groups;
175
+
176
+ if (match && match.resource && match.name) {
177
+ resourceEntryPoints.set(match.name, match.resource);
178
+ }
154
179
  break;
155
180
  }
156
181
  }
157
182
  } else {
158
- // Handle tags without comments (like @appRoot, @gqlContext)
183
+ // Handle tags without comments (like @ExportedSymbol, @gqlContext)
159
184
  switch (tag.tagName.getText()) {
160
185
  case 'appRoot': {
161
186
  if (appRoot != null) {
@@ -218,13 +243,18 @@ function collectRouterNodes(project: Project): {
218
243
  .flatMap((decl) => ts.getJSDocCommentsAndTags(decl.compilerNode))
219
244
  .forEach(visitJSDocTags);
220
245
 
221
- if (routerRoute != null) routes.push(routerRoute);
222
- if (routerResource != null) resources.push(routerResource);
246
+ if (routerRoute != null) {
247
+ routes.set(routerRoute[0], routerRoute[1]);
248
+ }
249
+
250
+ if (routerResource != null) {
251
+ resources.set(routerResource[0], routerResource[1]);
252
+ }
223
253
  });
224
254
  }
225
255
 
226
256
  project.getSourceFiles().forEach(visitRouterNodes);
227
- return {resources, routes, appRoot, gqlContext};
257
+ return {resources, routes, appRoot, gqlContext, serverHandlers};
228
258
  }
229
259
 
230
260
  function zodSchemaOfType(tc: ts.TypeChecker, t: ts.Type): string {
@@ -260,53 +290,287 @@ function zodSchemaOfType(tc: ts.TypeChecker, t: ts.Type): string {
260
290
  }
261
291
  }
262
292
 
263
- export async function generatePastoriaArtifacts() {
264
- const targetDir = process.cwd();
265
- const project = new Project({
266
- tsConfigFilePath: path.join(targetDir, 'tsconfig.json'),
267
- });
293
+ function writeEntryPoint(
294
+ writer: CodeBlockWriter,
295
+ metadata: PastoriaMetadata,
296
+ consumedQueries: Set<string>,
297
+ resourceName: string,
298
+ resource: RouterResource,
299
+ parseVars = true,
300
+ ) {
301
+ writer.writeLine(`root: JSResource.fromModuleId('${resourceName}'),`);
302
+ writer
303
+ .write(`getPreloadProps(${parseVars ? '{params, schema}' : ''})`)
304
+ .block(() => {
305
+ if (parseVars) {
306
+ writer.writeLine('const variables = schema.parse(params);');
307
+ }
308
+
309
+ writer.write('return').block(() => {
310
+ writer
311
+ .write('queries:')
312
+ .block(() => {
313
+ for (const [queryRef, query] of resource.queries.entries()) {
314
+ consumedQueries.add(query);
315
+
316
+ writer
317
+ .write(`${queryRef}:`)
318
+ .block(() => {
319
+ writer.writeLine(`parameters: ${query}Parameters,`);
320
+ writer.writeLine(`variables`);
321
+ })
322
+ .write(',');
323
+ }
324
+ })
325
+ .writeLine(',');
326
+
327
+ writer.write('entryPoints:').block(() => {
328
+ for (const [
329
+ epRef,
330
+ subresourceName,
331
+ ] of resource.entryPoints.entries()) {
332
+ const subresource = metadata.resources.get(subresourceName);
333
+ if (subresource) {
334
+ writer.write(`${epRef}:`).block(() => {
335
+ writer.writeLine(`entryPointParams: {},`);
336
+ writer.write('entryPoint:').block(() => {
337
+ writeEntryPoint(
338
+ writer,
339
+ metadata,
340
+ consumedQueries,
341
+ subresourceName,
342
+ subresource,
343
+ false,
344
+ );
345
+ });
346
+ });
347
+ }
348
+ }
349
+ });
350
+ });
351
+ });
352
+ }
268
353
 
354
+ async function generateRouter(project: Project, metadata: PastoriaMetadata) {
355
+ const routerTemplate = await loadRouterTemplates(project, 'router.tsx');
269
356
  const tc = project.getTypeChecker().compilerObject;
270
- const routerFiles = await loadRouterFiles(project);
271
- const routerNodes = collectRouterNodes(project);
272
-
273
- // Generate app_root.ts if @appRoot tag is found
274
- const appRoot: AppRoot | null = routerNodes.appRoot;
275
- if (appRoot != null) {
276
- const appRootSourceFile: SourceFile = appRoot.sourceFile;
277
- const appRootSymbol: Symbol = appRoot.symbol;
278
- const filePath = path.relative(targetDir, appRootSourceFile.getFilePath());
279
- const appRootFile = project.createSourceFile(APP_ROOT_FILENAME, '', {
280
- overwrite: true,
357
+
358
+ const routerConf = routerTemplate
359
+ .getVariableDeclarationOrThrow('ROUTER_CONF')
360
+ .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
361
+ .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
362
+
363
+ routerConf.getPropertyOrThrow('noop').remove();
364
+
365
+ let entryPointImportIndex = 0;
366
+ for (const [
367
+ routeName,
368
+ {sourceFile, symbol, params},
369
+ ] of metadata.routes.entries()) {
370
+ const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
371
+ let entryPointExpression: string;
372
+
373
+ // Resource-routes are combined declarations of a resource and a route
374
+ // where we should generate the entrypoint for the route.
375
+ const isResourceRoute = Array.from(metadata.resources.entries()).find(
376
+ ([, {symbol: resourceSymbol}]) => symbol === resourceSymbol,
377
+ );
378
+
379
+ if (isResourceRoute) {
380
+ const [resourceName, resource] = isResourceRoute;
381
+ const entryPointFunctionName = `entrypoint_${resourceName.replace(/\W/g, '__')}`;
382
+
383
+ routerTemplate.addImportDeclaration({
384
+ moduleSpecifier: './js_resource',
385
+ namedImports: ['JSResource', 'ModuleType'],
386
+ });
387
+
388
+ const consumedQueries = new Set<string>();
389
+ routerTemplate.addFunction({
390
+ name: entryPointFunctionName,
391
+ returnType: `EntryPoint<ModuleType<'${resourceName}'>, EntryPointParams<'${routeName}'>>`,
392
+ statements: (writer) => {
393
+ writer.write('return ').block(() => {
394
+ writeEntryPoint(
395
+ writer,
396
+ metadata,
397
+ consumedQueries,
398
+ resourceName,
399
+ resource,
400
+ );
401
+ });
402
+ },
403
+ });
404
+
405
+ for (const query of consumedQueries) {
406
+ routerTemplate.addImportDeclaration({
407
+ moduleSpecifier: `#genfiles/queries/${query}$parameters`,
408
+ defaultImport: `${query}Parameters`,
409
+ });
410
+ }
411
+
412
+ entryPointExpression = entryPointFunctionName + '()';
413
+ } else {
414
+ const importAlias = `e${entryPointImportIndex++}`;
415
+ const moduleSpecifier = routerTemplate.getRelativePathAsModuleSpecifierTo(
416
+ sourceFile.getFilePath(),
417
+ );
418
+
419
+ routerTemplate.addImportDeclaration({
420
+ moduleSpecifier,
421
+ namedImports: [
422
+ {
423
+ name: symbol.getName(),
424
+ alias: importAlias,
425
+ },
426
+ ],
427
+ });
428
+
429
+ entryPointExpression = importAlias;
430
+ }
431
+
432
+ routerConf.addPropertyAssignment({
433
+ name: `"${routeName}"`,
434
+ initializer: (writer) => {
435
+ writer
436
+ .write('{')
437
+ .indent(() => {
438
+ writer.writeLine(`entrypoint: ${entryPointExpression},`);
439
+ if (params.size === 0) {
440
+ writer.writeLine(`schema: z.object({})`);
441
+ } else {
442
+ writer.writeLine(`schema: z.object({`);
443
+ for (const [paramName, paramType] of Array.from(params)) {
444
+ writer.writeLine(
445
+ ` ${paramName}: ${zodSchemaOfType(tc, paramType)},`,
446
+ );
447
+ }
448
+
449
+ writer.writeLine('})');
450
+ }
451
+ })
452
+ .write('} as const');
453
+ },
281
454
  });
282
455
 
283
- const moduleSpecifier = appRootFile.getRelativePathAsModuleSpecifierTo(
284
- appRootSourceFile.getFilePath(),
456
+ console.log(
457
+ 'Created route',
458
+ pc.cyan(routeName),
459
+ 'for',
460
+ pc.green(symbol.getName()),
461
+ 'exported from',
462
+ pc.yellow(filePath),
285
463
  );
464
+ }
286
465
 
287
- appRootFile.addStatements(`/*
288
- * This file was generated by \`pastoria\`.
289
- * Do not modify this file directly.
290
- */
466
+ await routerTemplate.save();
467
+ }
291
468
 
292
- export {${appRootSymbol.getName()} as App} from '${moduleSpecifier}';
293
- `);
469
+ async function generateJsResource(
470
+ project: Project,
471
+ metadata: PastoriaMetadata,
472
+ ) {
473
+ const jsResourceTemplate = await loadRouterTemplates(
474
+ project,
475
+ 'js_resource.ts',
476
+ );
294
477
 
295
- await appRootFile.save();
478
+ const resourceConf = jsResourceTemplate
479
+ .getVariableDeclarationOrThrow('RESOURCE_CONF')
480
+ .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
481
+ .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
482
+
483
+ resourceConf.getPropertyOrThrow('noop').remove();
484
+ for (const [
485
+ resourceName,
486
+ {sourceFile, symbol},
487
+ ] of metadata.resources.entries()) {
488
+ const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
489
+ const moduleSpecifier =
490
+ jsResourceTemplate.getRelativePathAsModuleSpecifierTo(
491
+ sourceFile.getFilePath(),
492
+ );
493
+
494
+ resourceConf.addPropertyAssignment({
495
+ name: `"${resourceName}"`,
496
+ initializer: (writer) => {
497
+ writer.block(() => {
498
+ writer
499
+ .writeLine(`src: "${filePath}",`)
500
+ .writeLine(
501
+ `loader: () => import("${moduleSpecifier}").then(m => m.${symbol.getName()})`,
502
+ );
503
+ });
504
+ },
505
+ });
296
506
 
297
507
  console.log(
298
- 'Created app root for',
299
- pc.green(appRootSymbol.getName()),
508
+ 'Created resource',
509
+ pc.cyan(resourceName),
510
+ 'for',
511
+ pc.green(symbol.getName()),
300
512
  'exported from',
301
513
  pc.yellow(filePath),
302
514
  );
303
515
  }
304
516
 
305
- // Generate context.ts
306
- const gqlContext: GqlContext | null = routerNodes.gqlContext;
307
- const contextFile = project.createSourceFile(CONTEXT_FILENAME, '', {
308
- overwrite: true,
309
- });
517
+ await jsResourceTemplate.save();
518
+ }
519
+
520
+ async function generateAppRoot(project: Project, metadata: PastoriaMetadata) {
521
+ const targetDir = process.cwd();
522
+ const appRoot: ExportedSymbol | null = metadata.appRoot;
523
+
524
+ if (appRoot == null) {
525
+ await project
526
+ .getSourceFile('__generated__/router/app_root.ts')
527
+ ?.deleteImmediately();
528
+
529
+ return;
530
+ }
531
+
532
+ const appRootSourceFile: SourceFile = appRoot.sourceFile;
533
+ const appRootSymbol: Symbol = appRoot.symbol;
534
+ const filePath = path.relative(targetDir, appRootSourceFile.getFilePath());
535
+ const appRootFile = project.createSourceFile(
536
+ '__generated__/router/app_root.ts',
537
+ '',
538
+ {overwrite: true},
539
+ );
540
+
541
+ const moduleSpecifier = appRootFile.getRelativePathAsModuleSpecifierTo(
542
+ appRootSourceFile.getFilePath(),
543
+ );
544
+
545
+ appRootFile.addStatements(`/*
546
+ * This file was generated by \`pastoria\`.
547
+ * Do not modify this file directly.
548
+ */
549
+
550
+ export {${appRootSymbol.getName()} as App} from '${moduleSpecifier}';
551
+ `);
552
+
553
+ await appRootFile.save();
554
+
555
+ console.log(
556
+ 'Created app root for',
557
+ pc.green(appRootSymbol.getName()),
558
+ 'exported from',
559
+ pc.yellow(filePath),
560
+ );
561
+ }
562
+
563
+ async function generateGraphqlContext(
564
+ project: Project,
565
+ metadata: PastoriaMetadata,
566
+ ) {
567
+ const targetDir = process.cwd();
568
+ const gqlContext: ExportedSymbol | null = metadata.gqlContext;
569
+ const contextFile = project.createSourceFile(
570
+ '__generated__/router/context.ts',
571
+ '',
572
+ {overwrite: true},
573
+ );
310
574
 
311
575
  if (gqlContext != null) {
312
576
  const contextSourceFile: SourceFile = gqlContext.sourceFile;
@@ -351,95 +615,53 @@ export class Context extends PastoriaRootContext {}
351
615
  }
352
616
 
353
617
  await contextFile.save();
618
+ }
354
619
 
355
- const resourceConf = routerFiles.jsResource
356
- .getVariableDeclarationOrThrow('RESOURCE_CONF')
357
- .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
358
- .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
359
-
360
- resourceConf.getPropertyOrThrow('noop').remove();
361
- for (const {resourceName, sourceFile, symbol} of routerNodes.resources) {
362
- const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
363
- const moduleSpecifier =
364
- routerFiles.jsResource.getRelativePathAsModuleSpecifierTo(
365
- sourceFile.getFilePath(),
366
- );
620
+ async function generateServerHandler(
621
+ project: Project,
622
+ metadata: PastoriaMetadata,
623
+ ) {
624
+ if (metadata.serverHandlers.size === 0) {
625
+ await project
626
+ .getSourceFile('__generated__/router/server_handler.ts')
627
+ ?.deleteImmediately();
367
628
 
368
- resourceConf.addPropertyAssignment({
369
- name: `"${resourceName}"`,
370
- initializer: (writer) => {
371
- writer.block(() => {
372
- writer
373
- .writeLine(`src: "${filePath}",`)
374
- .writeLine(
375
- `loader: () => import("${moduleSpecifier}").then(m => m.${symbol.getName()})`,
376
- );
377
- });
378
- },
379
- });
380
-
381
- console.log(
382
- 'Created resource',
383
- pc.cyan(resourceName),
384
- 'for',
385
- pc.green(symbol.getName()),
386
- 'exported from',
387
- pc.yellow(filePath),
388
- );
629
+ return;
389
630
  }
390
631
 
391
- const routerConf = routerFiles.router
392
- .getVariableDeclarationOrThrow('ROUTER_CONF')
393
- .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
394
- .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
395
-
396
- routerConf.getPropertyOrThrow('noop').remove();
632
+ const sourceText = `import express from 'express';
633
+ export const router = express.Router();
634
+ `;
397
635
 
398
- let entryPointImportIndex = 0;
399
- for (const {routeName, sourceFile, symbol, params} of routerNodes.routes) {
400
- const importAlias = `e${entryPointImportIndex++}`;
636
+ const serverHandlerTemplate = project.createSourceFile(
637
+ '__generated__/router/server_handler.ts',
638
+ sourceText,
639
+ {overwrite: true},
640
+ );
641
+
642
+ let serverHandlerImportIndex = 0;
643
+ for (const [
644
+ routeName,
645
+ {symbol, sourceFile},
646
+ ] of metadata.serverHandlers.entries()) {
647
+ const importAlias = `e${serverHandlerImportIndex++}`;
401
648
  const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
402
649
  const moduleSpecifier =
403
- routerFiles.router.getRelativePathAsModuleSpecifierTo(
650
+ serverHandlerTemplate.getRelativePathAsModuleSpecifierTo(
404
651
  sourceFile.getFilePath(),
405
652
  );
406
653
 
407
- routerFiles.router.addImportDeclaration({
654
+ serverHandlerTemplate.addImportDeclaration({
408
655
  moduleSpecifier,
409
- namedImports: [
410
- {
411
- name: symbol.getName(),
412
- alias: importAlias,
413
- },
414
- ],
656
+ namedImports: [{name: symbol.getName(), alias: importAlias}],
415
657
  });
416
658
 
417
- routerConf.addPropertyAssignment({
418
- name: `"${routeName}"`,
419
- initializer: (writer) => {
420
- writer
421
- .write('{')
422
- .indent(() => {
423
- writer.writeLine(`entrypoint: ${importAlias},`);
424
- if (params.size === 0) {
425
- writer.writeLine(`schema: z.object({})`);
426
- } else {
427
- writer.writeLine(`schema: z.object({`);
428
- for (const [paramName, paramType] of Array.from(params)) {
429
- writer.writeLine(
430
- ` ${paramName}: ${zodSchemaOfType(tc, paramType)},`,
431
- );
432
- }
433
-
434
- writer.writeLine('})');
435
- }
436
- })
437
- .write('} as const');
438
- },
439
- });
659
+ serverHandlerTemplate.addStatements(
660
+ `router.use('${routeName}', ${importAlias})`,
661
+ );
440
662
 
441
663
  console.log(
442
- 'Created route',
664
+ 'Created server handler',
443
665
  pc.cyan(routeName),
444
666
  'for',
445
667
  pc.green(symbol.getName()),
@@ -448,5 +670,23 @@ export class Context extends PastoriaRootContext {}
448
670
  );
449
671
  }
450
672
 
451
- await Promise.all([routerFiles.jsResource.save(), routerFiles.router.save()]);
673
+ await serverHandlerTemplate.save();
674
+ }
675
+
676
+ export async function generatePastoriaArtifacts() {
677
+ const targetDir = process.cwd();
678
+ const project = new Project({
679
+ tsConfigFilePath: path.join(targetDir, 'tsconfig.json'),
680
+ manipulationSettings: {
681
+ indentationText: IndentationText.TwoSpaces,
682
+ },
683
+ });
684
+
685
+ const metadata = collectPastoriaMetadata(project);
686
+
687
+ await generateAppRoot(project, metadata);
688
+ await generateGraphqlContext(project, metadata);
689
+ await generateRouter(project, metadata);
690
+ await generateJsResource(project, metadata);
691
+ await generateServerHandler(project, metadata);
452
692
  }