pastoria 1.0.7 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # pastoria
2
2
 
3
+ ## 1.0.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 60ab948: Add support for routing shorthands
8
+ - 37afd6c: Add support for @serverRoute
9
+
10
+ ## 1.0.8
11
+
12
+ ### Patch Changes
13
+
14
+ - 265903c: Add babel-plugin-relay dependency
15
+
3
16
  ## 1.0.7
4
17
 
5
18
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,YAAY,EACZ,KAAK,uBAAuB,EAE7B,MAAM,MAAM,CAAC;AA0Gd,eAAO,MAAM,YAAY,EAAE,uBAK1B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,uBAM1B,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,uBAAuB,GAChC,YAAY,CAyBd;AAED,wBAAsB,WAAW,kBAUhC"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,YAAY,EACZ,KAAK,uBAAuB,EAE7B,MAAM,MAAM,CAAC;AA8Id,eAAO,MAAM,YAAY,EAAE,uBAK1B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,uBAM1B,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,uBAAuB,GAChC,YAAY,CAyBd;AAED,wBAAsB,WAAW,kBAUhC"}
package/dist/build.js CHANGED
@@ -1,10 +1,9 @@
1
- import react from '@vitejs/plugin-react';
2
1
  import tailwindcss from '@tailwindcss/vite';
2
+ import react from '@vitejs/plugin-react';
3
3
  import { access } from 'node:fs/promises';
4
- import * as path from 'node:path';
5
- import { cjsInterop } from 'vite-plugin-cjs-interop';
6
4
  import { build, } from 'vite';
7
- function generateClientEntry(hasAppRoot) {
5
+ import { cjsInterop } from 'vite-plugin-cjs-interop';
6
+ function generateClientEntry({ hasAppRoot }) {
8
7
  const appImport = hasAppRoot
9
8
  ? `import {App} from '#genfiles/router/app_root';`
10
9
  : '';
@@ -22,11 +21,17 @@ async function main() {
22
21
  main();
23
22
  `;
24
23
  }
25
- function generateServerEntry(hasAppRoot) {
24
+ function generateServerEntry({ hasAppRoot, hasServerHandler, }) {
26
25
  const appImport = hasAppRoot
27
26
  ? `import {App} from '#genfiles/router/app_root';`
28
27
  : '';
29
28
  const appValue = hasAppRoot ? 'App' : 'null';
29
+ const serverHandlerImport = hasServerHandler
30
+ ? `import {router as serverHandler} from '#genfiles/router/server_handler';`
31
+ : '';
32
+ const serverHandlerUse = hasServerHandler
33
+ ? ' router.use(serverHandler)'
34
+ : '';
30
35
  return `// Generated by Pastoria.
31
36
  import {JSResource} from '#genfiles/router/js_resource';
32
37
  import {
@@ -37,6 +42,8 @@ import {
37
42
  import {getSchema} from '#genfiles/schema/schema';
38
43
  import {Context} from '#genfiles/router/context';
39
44
  ${appImport}
45
+ ${serverHandlerImport}
46
+ import express from 'express';
40
47
  import {GraphQLSchema, specifiedDirectives} from 'graphql';
41
48
  import {PastoriaConfig} from 'pastoria-config';
42
49
  import {createRouterHandler} from 'pastoria-runtime/server';
@@ -53,7 +60,7 @@ export function createHandler(
53
60
  config: Required<PastoriaConfig>,
54
61
  manifest?: Manifest,
55
62
  ) {
56
- return createRouterHandler(
63
+ const routeHandler = createRouterHandler(
57
64
  listRoutes(),
58
65
  JSResource.srcOfModuleId,
59
66
  router__loadEntryPoint,
@@ -65,9 +72,37 @@ export function createHandler(
65
72
  config,
66
73
  manifest,
67
74
  );
75
+
76
+ const router = express.Router();
77
+ router.use(routeHandler);
78
+ ${serverHandlerUse}
79
+
80
+ return router;
68
81
  }
69
82
  `;
70
83
  }
84
+ async function determineCapabilities() {
85
+ const capabilities = {
86
+ hasAppRoot: false,
87
+ hasServerHandler: false,
88
+ };
89
+ async function hasAppRoot() {
90
+ try {
91
+ await access('__generated__/router/app_root.ts');
92
+ capabilities.hasAppRoot = true;
93
+ }
94
+ catch { }
95
+ }
96
+ async function hasServerHandler() {
97
+ try {
98
+ await access('__generated__/router/server_handler.ts');
99
+ capabilities.hasServerHandler = true;
100
+ }
101
+ catch { }
102
+ }
103
+ await Promise.all([hasAppRoot(), hasServerHandler()]);
104
+ return capabilities;
105
+ }
71
106
  function pastoriaEntryPlugin() {
72
107
  const clientEntryModuleId = 'virtual:pastoria-entry-client.tsx';
73
108
  const serverEntryModuleId = 'virtual:pastoria-entry-server.tsx';
@@ -82,20 +117,12 @@ function pastoriaEntryPlugin() {
82
117
  }
83
118
  },
84
119
  async load(id) {
85
- const appRootPath = path.join(process.cwd(), '__generated__/router/app_root.ts');
86
- let hasAppRoot = false;
87
- try {
88
- await access(appRootPath);
89
- hasAppRoot = true;
90
- }
91
- catch {
92
- hasAppRoot = false;
93
- }
120
+ const capabilities = await determineCapabilities();
94
121
  if (id === clientEntryModuleId) {
95
- return generateClientEntry(hasAppRoot);
122
+ return generateClientEntry(capabilities);
96
123
  }
97
124
  else if (id === serverEntryModuleId) {
98
- return generateServerEntry(hasAppRoot);
125
+ return generateServerEntry(capabilities);
99
126
  }
100
127
  },
101
128
  };
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,MAAM,sBAAsB,CAAC;AACzC,OAAO,EAAC,MAAM,EAAC,MAAM,kBAAkB,CAAC;AAExC,OAAO,EACL,KAAK,GAIN,MAAM,MAAM,CAAC;AACd,OAAO,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAOnD,SAAS,mBAAmB,CAAC,EAAC,UAAU,EAAuB;IAC7D,MAAM,SAAS,GAAG,UAAU;QAC1B,CAAC,CAAC,gDAAgD;QAClD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAE7C,OAAO;;EAEP,SAAS;;;;;0CAK+B,QAAQ;;;;CAIjD,CAAC;AACF,CAAC;AAED,SAAS,mBAAmB,CAAC,EAC3B,UAAU,EACV,gBAAgB,GACK;IACrB,MAAM,SAAS,GAAG,UAAU;QAC1B,CAAC,CAAC,gDAAgD;QAClD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAE7C,MAAM,mBAAmB,GAAG,gBAAgB;QAC1C,CAAC,CAAC,0EAA0E;QAC5E,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,gBAAgB,GAAG,gBAAgB;QACvC,CAAC,CAAC,6BAA6B;QAC/B,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;;;;;;;EASP,SAAS;EACT,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;MAuBf,QAAQ;;;;;;;;;;IAUV,gBAAgB;;;;CAInB,CAAC;AACF,CAAC;AAED,KAAK,UAAU,qBAAqB;IAClC,MAAM,YAAY,GAAyB;QACzC,UAAU,EAAE,KAAK;QACjB,gBAAgB,EAAE,KAAK;KACxB,CAAC;IAEF,KAAK,UAAU,UAAU;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,kCAAkC,CAAC,CAAC;YACjD,YAAY,CAAC,UAAU,GAAG,IAAI,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,KAAK,UAAU,gBAAgB;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,wCAAwC,CAAC,CAAC;YACvD,YAAY,CAAC,gBAAgB,GAAG,IAAI,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;IACtD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,mBAAmB;IAC1B,MAAM,mBAAmB,GAAG,mCAAmC,CAAC;IAChE,MAAM,mBAAmB,GAAG,mCAAmC,CAAC;IAEhE,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,SAAS,CAAC,EAAE;YACV,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBAC/B,OAAO,mBAAmB,CAAC,CAAC,kEAAkE;YAChG,CAAC;iBAAM,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBACtC,OAAO,mBAAmB,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE;YACX,MAAM,YAAY,GAAG,MAAM,qBAAqB,EAAE,CAAC;YACnD,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBAC/B,OAAO,mBAAmB,CAAC,YAAY,CAAC,CAAC;YAC3C,CAAC;iBAAM,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBACtC,OAAO,mBAAmB,CAAC,YAAY,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAA4B;IACnD,MAAM,EAAE,aAAa;IACrB,aAAa,EAAE;QACb,KAAK,EAAE,mCAAmC;KAC3C;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAA4B;IACnD,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,IAAI;IACT,aAAa,EAAE;QACb,KAAK,EAAE,mCAAmC;KAC3C;CACF,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAC/B,QAAiC;IAEjC,OAAO;QACL,OAAO,EAAE,QAAiB;QAC1B,KAAK,EAAE;YACL,GAAG,QAAQ;YACX,iBAAiB,EAAE,CAAC;YACpB,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,IAAI;SAClB;QACD,OAAO,EAAE;YACP,mBAAmB,EAAE;YACrB,WAAW,EAAE;YACb,KAAK,CAAC;gBACJ,KAAK,EAAE;oBACL,OAAO,EAAE,CAAC,CAAC,6BAA6B,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC;iBACxD;aACF,CAAC;YACF,UAAU,CAAC;gBACT,YAAY,EAAE,CAAC,aAAa,EAAE,mBAAmB,EAAE,eAAe,CAAC;aACpE,CAAC;SACH;QACD,GAAG,EAAE;YACH,UAAU,EAAE,CAAC,kBAAkB,CAAC;SACjC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC;QAC9B,GAAG,iBAAiB,CAAC,YAAY,CAAC;QAClC,UAAU,EAAE,KAAK;KAClB,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC;QAC9B,GAAG,iBAAiB,CAAC,YAAY,CAAC;QAClC,UAAU,EAAE,KAAK;KAClB,CAAC,CAAC;AACL,CAAC"}
@@ -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.
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA6OH,wBAAsB,yBAAyB,kBA6L9C"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAyoBH,wBAAsB,yBAAyB,kBAgB9C"}
package/dist/generate.js CHANGED
@@ -20,21 +20,16 @@
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.
26
27
  */
27
28
  import { readFile } from 'node:fs/promises';
28
29
  import * as path from 'node:path';
29
- import { default as pc } from 'picocolors';
30
- import { Project, SyntaxKind, ts, TypeFlags } from 'ts-morph';
31
- const JS_RESOURCE_FILENAME = '__generated__/router/js_resource.ts';
32
- const JS_RESOURCE_TEMPLATE = path.join(import.meta.dirname, '../templates/js_resource.ts');
33
- const ROUTER_FILENAME = '__generated__/router/router.tsx';
34
- const ROUTER_TEMPLATE = path.join(import.meta.dirname, '../templates/router.tsx');
35
- const APP_ROOT_FILENAME = '__generated__/router/app_root.ts';
36
- const CONTEXT_FILENAME = '__generated__/router/context.ts';
37
- async function loadRouterFiles(project) {
30
+ import pc from 'picocolors';
31
+ import { IndentationText, Project, SyntaxKind, ts, TypeFlags, } from 'ts-morph';
32
+ async function loadRouterTemplates(project, filename) {
38
33
  async function loadSourceFile(fileName, templateFileName) {
39
34
  const template = await readFile(templateFileName, 'utf-8');
40
35
  const warningComment = `/*
@@ -47,17 +42,20 @@ async function loadRouterFiles(project) {
47
42
  overwrite: true,
48
43
  });
49
44
  }
50
- const [jsResource, router] = await Promise.all([
51
- loadSourceFile(JS_RESOURCE_FILENAME, JS_RESOURCE_TEMPLATE),
52
- loadSourceFile(ROUTER_FILENAME, ROUTER_TEMPLATE),
53
- ]);
54
- return { jsResource, router };
45
+ const template = path.join(import.meta.dirname, '../templates', filename);
46
+ const output = path.join('__generated__/router', filename);
47
+ return loadSourceFile(output, template);
55
48
  }
56
49
  // Regex to quickly check if a file contains any Pastoria JSDoc tags
57
- const PASTORIA_TAG_REGEX = /@(route|resource|appRoot|param|gqlContext)\b/;
58
- function collectRouterNodes(project) {
59
- const resources = [];
60
- const routes = [];
50
+ const PASTORIA_TAG_REGEX = /@(route|resource|appRoot|param|gqlContext|serverRoute)\b/;
51
+ /**
52
+ *
53
+ * @query {abc} 123
54
+ */
55
+ function collectPastoriaMetadata(project) {
56
+ const resources = new Map();
57
+ const routes = new Map();
58
+ const serverHandlers = new Map();
61
59
  let appRoot = null;
62
60
  let gqlContext = null;
63
61
  function visitRouterNodes(sourceFile) {
@@ -72,6 +70,8 @@ function collectRouterNodes(project) {
72
70
  }
73
71
  sourceFile.getExportSymbols().forEach((symbol) => {
74
72
  let routerResource = null;
73
+ const resourceQueries = new Map();
74
+ const resourceEntryPoints = new Map();
75
75
  let routerRoute = null;
76
76
  const routeParams = new Map();
77
77
  function visitJSDocTags(tag) {
@@ -89,26 +89,46 @@ function collectRouterNodes(project) {
89
89
  else if (typeof tag.comment === 'string') {
90
90
  switch (tag.tagName.getText()) {
91
91
  case 'route': {
92
- routerRoute = {
93
- routeName: tag.comment,
94
- sourceFile,
95
- symbol,
96
- params: routeParams,
97
- };
92
+ routerRoute = [
93
+ tag.comment,
94
+ { sourceFile, symbol, params: routeParams },
95
+ ];
98
96
  break;
99
97
  }
100
98
  case 'resource': {
101
- routerResource = {
102
- resourceName: tag.comment,
103
- sourceFile,
104
- symbol,
105
- };
99
+ routerResource = [
100
+ tag.comment,
101
+ {
102
+ sourceFile,
103
+ symbol,
104
+ queries: resourceQueries,
105
+ entryPoints: resourceEntryPoints,
106
+ },
107
+ ];
108
+ break;
109
+ }
110
+ case 'serverRoute': {
111
+ serverHandlers.set(tag.comment, { sourceFile, symbol });
112
+ break;
113
+ }
114
+ case 'query': {
115
+ const match = tag.comment.match(/^\s*\{\s*(?<query>\w+)\s*\}\s+(?<name>\w+)\s*$/)?.groups;
116
+ if (match && match.query && match.name) {
117
+ resourceQueries.set(match.name, match.query);
118
+ }
119
+ break;
120
+ }
121
+ case 'entrypoint': {
122
+ const match = tag.comment.match(/^\s*\{\s*(?<resource>[\w#]+)\s*\}\s+(?<name>\w+)\s*$/)?.groups;
123
+ if (match && match.resource && match.name) {
124
+ resourceEntryPoints.set(match.name, match.resource);
125
+ }
106
126
  break;
107
127
  }
108
128
  }
109
129
  }
110
130
  else {
111
- // Handle tags without comments (like @appRoot, @gqlContext)
131
+ // Handle tags without comments (like @ExportedSymbol, @gqlContext)
112
132
  switch (tag.tagName.getText()) {
113
133
  case 'appRoot': {
114
134
  if (appRoot != null) {
@@ -161,14 +181,16 @@ function collectRouterNodes(project) {
161
181
  .getDeclarations()
162
182
  .flatMap((decl) => ts.getJSDocCommentsAndTags(decl.compilerNode))
163
183
  .forEach(visitJSDocTags);
164
- if (routerRoute != null)
165
- routes.push(routerRoute);
166
- if (routerResource != null)
167
- resources.push(routerResource);
184
+ if (routerRoute != null) {
185
+ routes.set(routerRoute[0], routerRoute[1]);
186
+ }
187
+ if (routerResource != null) {
188
+ resources.set(routerResource[0], routerResource[1]);
189
+ }
168
190
  });
169
191
  }
170
192
  project.getSourceFiles().forEach(visitRouterNodes);
171
- return { resources, routes, appRoot, gqlContext };
193
+ return { resources, routes, appRoot, gqlContext, serverHandlers };
172
194
  }
173
195
  function zodSchemaOfType(tc, t) {
174
196
  if (t.getFlags() & TypeFlags.String) {
@@ -201,39 +223,177 @@ function zodSchemaOfType(tc, t) {
201
223
  return `z.any()`;
202
224
  }
203
225
  }
204
- export async function generatePastoriaArtifacts() {
205
- const targetDir = process.cwd();
206
- const project = new Project({
207
- tsConfigFilePath: path.join(targetDir, 'tsconfig.json'),
226
+ function writeEntryPoint(writer, metadata, consumedQueries, resourceName, resource, parseVars = true) {
227
+ writer.writeLine(`root: JSResource.fromModuleId('${resourceName}'),`);
228
+ writer
229
+ .write(`getPreloadProps(${parseVars ? '{params, schema}' : ''})`)
230
+ .block(() => {
231
+ if (parseVars) {
232
+ writer.writeLine('const variables = schema.parse(params);');
233
+ }
234
+ writer.write('return').block(() => {
235
+ writer
236
+ .write('queries:')
237
+ .block(() => {
238
+ for (const [queryRef, query] of resource.queries.entries()) {
239
+ consumedQueries.add(query);
240
+ writer
241
+ .write(`${queryRef}:`)
242
+ .block(() => {
243
+ writer.writeLine(`parameters: ${query}Parameters,`);
244
+ writer.writeLine(`variables`);
245
+ })
246
+ .write(',');
247
+ }
248
+ })
249
+ .writeLine(',');
250
+ writer.write('entryPoints:').block(() => {
251
+ for (const [epRef, subresourceName,] of resource.entryPoints.entries()) {
252
+ const subresource = metadata.resources.get(subresourceName);
253
+ if (subresource) {
254
+ writer.write(`${epRef}:`).block(() => {
255
+ writer.writeLine(`entryPointParams: {},`);
256
+ writer.write('entryPoint:').block(() => {
257
+ writeEntryPoint(writer, metadata, consumedQueries, subresourceName, subresource, false);
258
+ });
259
+ });
260
+ }
261
+ }
262
+ });
263
+ });
208
264
  });
265
+ }
266
+ async function generateRouter(project, metadata) {
267
+ const routerTemplate = await loadRouterTemplates(project, 'router.tsx');
209
268
  const tc = project.getTypeChecker().compilerObject;
210
- const routerFiles = await loadRouterFiles(project);
211
- const routerNodes = collectRouterNodes(project);
212
- // Generate app_root.ts if @appRoot tag is found
213
- const appRoot = routerNodes.appRoot;
214
- if (appRoot != null) {
215
- const appRootSourceFile = appRoot.sourceFile;
216
- const appRootSymbol = appRoot.symbol;
217
- const filePath = path.relative(targetDir, appRootSourceFile.getFilePath());
218
- const appRootFile = project.createSourceFile(APP_ROOT_FILENAME, '', {
219
- overwrite: true,
269
+ const routerConf = routerTemplate
270
+ .getVariableDeclarationOrThrow('ROUTER_CONF')
271
+ .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
272
+ .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
273
+ routerConf.getPropertyOrThrow('noop').remove();
274
+ let entryPointImportIndex = 0;
275
+ for (const [routeName, { sourceFile, symbol, params },] of metadata.routes.entries()) {
276
+ const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
277
+ let entryPointExpression;
278
+ // Resource-routes are combined declarations of a resource and a route
279
+ // where we should generate the entrypoint for the route.
280
+ const isResourceRoute = Array.from(metadata.resources.entries()).find(([, { symbol: resourceSymbol }]) => symbol === resourceSymbol);
281
+ if (isResourceRoute) {
282
+ const [resourceName, resource] = isResourceRoute;
283
+ const entryPointFunctionName = `entrypoint_${resourceName.replace(/\W/g, '__')}`;
284
+ routerTemplate.addImportDeclaration({
285
+ moduleSpecifier: './js_resource',
286
+ namedImports: ['JSResource', 'ModuleType'],
287
+ });
288
+ const consumedQueries = new Set();
289
+ routerTemplate.addFunction({
290
+ name: entryPointFunctionName,
291
+ returnType: `EntryPoint<ModuleType<'${resourceName}'>, EntryPointParams<'${routeName}'>>`,
292
+ statements: (writer) => {
293
+ writer.write('return ').block(() => {
294
+ writeEntryPoint(writer, metadata, consumedQueries, resourceName, resource);
295
+ });
296
+ },
297
+ });
298
+ for (const query of consumedQueries) {
299
+ routerTemplate.addImportDeclaration({
300
+ moduleSpecifier: `#genfiles/queries/${query}$parameters`,
301
+ defaultImport: `${query}Parameters`,
302
+ });
303
+ }
304
+ entryPointExpression = entryPointFunctionName + '()';
305
+ }
306
+ else {
307
+ const importAlias = `e${entryPointImportIndex++}`;
308
+ const moduleSpecifier = routerTemplate.getRelativePathAsModuleSpecifierTo(sourceFile.getFilePath());
309
+ routerTemplate.addImportDeclaration({
310
+ moduleSpecifier,
311
+ namedImports: [
312
+ {
313
+ name: symbol.getName(),
314
+ alias: importAlias,
315
+ },
316
+ ],
317
+ });
318
+ entryPointExpression = importAlias;
319
+ }
320
+ routerConf.addPropertyAssignment({
321
+ name: `"${routeName}"`,
322
+ initializer: (writer) => {
323
+ writer
324
+ .write('{')
325
+ .indent(() => {
326
+ writer.writeLine(`entrypoint: ${entryPointExpression},`);
327
+ if (params.size === 0) {
328
+ writer.writeLine(`schema: z.object({})`);
329
+ }
330
+ else {
331
+ writer.writeLine(`schema: z.object({`);
332
+ for (const [paramName, paramType] of Array.from(params)) {
333
+ writer.writeLine(` ${paramName}: ${zodSchemaOfType(tc, paramType)},`);
334
+ }
335
+ writer.writeLine('})');
336
+ }
337
+ })
338
+ .write('} as const');
339
+ },
340
+ });
341
+ console.log('Created route', pc.cyan(routeName), 'for', pc.green(symbol.getName()), 'exported from', pc.yellow(filePath));
342
+ }
343
+ await routerTemplate.save();
344
+ }
345
+ async function generateJsResource(project, metadata) {
346
+ const jsResourceTemplate = await loadRouterTemplates(project, 'js_resource.ts');
347
+ const resourceConf = jsResourceTemplate
348
+ .getVariableDeclarationOrThrow('RESOURCE_CONF')
349
+ .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
350
+ .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
351
+ resourceConf.getPropertyOrThrow('noop').remove();
352
+ for (const [resourceName, { sourceFile, symbol },] of metadata.resources.entries()) {
353
+ const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
354
+ const moduleSpecifier = jsResourceTemplate.getRelativePathAsModuleSpecifierTo(sourceFile.getFilePath());
355
+ resourceConf.addPropertyAssignment({
356
+ name: `"${resourceName}"`,
357
+ initializer: (writer) => {
358
+ writer.block(() => {
359
+ writer
360
+ .writeLine(`src: "${filePath}",`)
361
+ .writeLine(`loader: () => import("${moduleSpecifier}").then(m => m.${symbol.getName()})`);
362
+ });
363
+ },
220
364
  });
221
- const moduleSpecifier = appRootFile.getRelativePathAsModuleSpecifierTo(appRootSourceFile.getFilePath());
222
- appRootFile.addStatements(`/*
365
+ console.log('Created resource', pc.cyan(resourceName), 'for', pc.green(symbol.getName()), 'exported from', pc.yellow(filePath));
366
+ }
367
+ await jsResourceTemplate.save();
368
+ }
369
+ async function generateAppRoot(project, metadata) {
370
+ const targetDir = process.cwd();
371
+ const appRoot = metadata.appRoot;
372
+ if (appRoot == null) {
373
+ await project
374
+ .getSourceFile('__generated__/router/app_root.ts')
375
+ ?.deleteImmediately();
376
+ return;
377
+ }
378
+ const appRootSourceFile = appRoot.sourceFile;
379
+ const appRootSymbol = appRoot.symbol;
380
+ const filePath = path.relative(targetDir, appRootSourceFile.getFilePath());
381
+ const appRootFile = project.createSourceFile('__generated__/router/app_root.ts', '', { overwrite: true });
382
+ const moduleSpecifier = appRootFile.getRelativePathAsModuleSpecifierTo(appRootSourceFile.getFilePath());
383
+ appRootFile.addStatements(`/*
223
384
  * This file was generated by \`pastoria\`.
224
385
  * Do not modify this file directly.
225
386
  */
226
387
 
227
388
  export {${appRootSymbol.getName()} as App} from '${moduleSpecifier}';
228
389
  `);
229
- await appRootFile.save();
230
- console.log('Created app root for', pc.green(appRootSymbol.getName()), 'exported from', pc.yellow(filePath));
231
- }
232
- // Generate context.ts
233
- const gqlContext = routerNodes.gqlContext;
234
- const contextFile = project.createSourceFile(CONTEXT_FILENAME, '', {
235
- overwrite: true,
236
- });
390
+ await appRootFile.save();
391
+ console.log('Created app root for', pc.green(appRootSymbol.getName()), 'exported from', pc.yellow(filePath));
392
+ }
393
+ async function generateGraphqlContext(project, metadata) {
394
+ const targetDir = process.cwd();
395
+ const gqlContext = metadata.gqlContext;
396
+ const contextFile = project.createSourceFile('__generated__/router/context.ts', '', { overwrite: true });
237
397
  if (gqlContext != null) {
238
398
  const contextSourceFile = gqlContext.sourceFile;
239
399
  const contextSymbol = gqlContext.symbol;
@@ -264,68 +424,45 @@ export class Context extends PastoriaRootContext {}
264
424
  console.log('No @gqlContext found, generating default', pc.green('Context'));
265
425
  }
266
426
  await contextFile.save();
267
- const resourceConf = routerFiles.jsResource
268
- .getVariableDeclarationOrThrow('RESOURCE_CONF')
269
- .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
270
- .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
271
- resourceConf.getPropertyOrThrow('noop').remove();
272
- for (const { resourceName, sourceFile, symbol } of routerNodes.resources) {
273
- const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
274
- const moduleSpecifier = routerFiles.jsResource.getRelativePathAsModuleSpecifierTo(sourceFile.getFilePath());
275
- resourceConf.addPropertyAssignment({
276
- name: `"${resourceName}"`,
277
- initializer: (writer) => {
278
- writer.block(() => {
279
- writer
280
- .writeLine(`src: "${filePath}",`)
281
- .writeLine(`loader: () => import("${moduleSpecifier}").then(m => m.${symbol.getName()})`);
282
- });
283
- },
284
- });
285
- console.log('Created resource', pc.cyan(resourceName), 'for', pc.green(symbol.getName()), 'exported from', pc.yellow(filePath));
427
+ }
428
+ async function generateServerHandler(project, metadata) {
429
+ if (metadata.serverHandlers.size === 0) {
430
+ await project
431
+ .getSourceFile('__generated__/router/server_handler.ts')
432
+ ?.deleteImmediately();
433
+ return;
286
434
  }
287
- const routerConf = routerFiles.router
288
- .getVariableDeclarationOrThrow('ROUTER_CONF')
289
- .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
290
- .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
291
- routerConf.getPropertyOrThrow('noop').remove();
292
- let entryPointImportIndex = 0;
293
- for (const { routeName, sourceFile, symbol, params } of routerNodes.routes) {
294
- const importAlias = `e${entryPointImportIndex++}`;
435
+ const sourceText = `import express from 'express';
436
+ export const router = express.Router();
437
+ `;
438
+ const serverHandlerTemplate = project.createSourceFile('__generated__/router/server_handler.ts', sourceText, { overwrite: true });
439
+ let serverHandlerImportIndex = 0;
440
+ for (const [routeName, { symbol, sourceFile },] of metadata.serverHandlers.entries()) {
441
+ const importAlias = `e${serverHandlerImportIndex++}`;
295
442
  const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
296
- const moduleSpecifier = routerFiles.router.getRelativePathAsModuleSpecifierTo(sourceFile.getFilePath());
297
- routerFiles.router.addImportDeclaration({
443
+ const moduleSpecifier = serverHandlerTemplate.getRelativePathAsModuleSpecifierTo(sourceFile.getFilePath());
444
+ serverHandlerTemplate.addImportDeclaration({
298
445
  moduleSpecifier,
299
- namedImports: [
300
- {
301
- name: symbol.getName(),
302
- alias: importAlias,
303
- },
304
- ],
305
- });
306
- routerConf.addPropertyAssignment({
307
- name: `"${routeName}"`,
308
- initializer: (writer) => {
309
- writer
310
- .write('{')
311
- .indent(() => {
312
- writer.writeLine(`entrypoint: ${importAlias},`);
313
- if (params.size === 0) {
314
- writer.writeLine(`schema: z.object({})`);
315
- }
316
- else {
317
- writer.writeLine(`schema: z.object({`);
318
- for (const [paramName, paramType] of Array.from(params)) {
319
- writer.writeLine(` ${paramName}: ${zodSchemaOfType(tc, paramType)},`);
320
- }
321
- writer.writeLine('})');
322
- }
323
- })
324
- .write('} as const');
325
- },
446
+ namedImports: [{ name: symbol.getName(), alias: importAlias }],
326
447
  });
327
- console.log('Created route', pc.cyan(routeName), 'for', pc.green(symbol.getName()), 'exported from', pc.yellow(filePath));
448
+ serverHandlerTemplate.addStatements(`router.use('${routeName}', ${importAlias})`);
449
+ console.log('Created server handler', pc.cyan(routeName), 'for', pc.green(symbol.getName()), 'exported from', pc.yellow(filePath));
328
450
  }
329
- await Promise.all([routerFiles.jsResource.save(), routerFiles.router.save()]);
451
+ await serverHandlerTemplate.save();
452
+ }
453
+ export async function generatePastoriaArtifacts() {
454
+ const targetDir = process.cwd();
455
+ const project = new Project({
456
+ tsConfigFilePath: path.join(targetDir, 'tsconfig.json'),
457
+ manipulationSettings: {
458
+ indentationText: IndentationText.TwoSpaces,
459
+ },
460
+ });
461
+ const metadata = collectPastoriaMetadata(project);
462
+ await generateAppRoot(project, metadata);
463
+ await generateGraphqlContext(project, metadata);
464
+ await generateRouter(project, metadata);
465
+ await generateJsResource(project, metadata);
466
+ await generateServerHandler(project, metadata);
330
467
  }
331
468
  //# sourceMappingURL=generate.js.map