@nooh-ts/compiler 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +76 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +996 -106
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/readme.md +132 -0
package/dist/index.mjs
CHANGED
|
@@ -46,6 +46,12 @@ const dirname = (value) => {
|
|
|
46
46
|
if (index === 0) return "/";
|
|
47
47
|
return normalized.slice(0, index);
|
|
48
48
|
};
|
|
49
|
+
const basename = (value) => {
|
|
50
|
+
const normalized = normalizePath(value);
|
|
51
|
+
const index = normalized.lastIndexOf("/");
|
|
52
|
+
if (index === -1) return normalized;
|
|
53
|
+
return normalized.slice(index + 1);
|
|
54
|
+
};
|
|
49
55
|
const relativePath = (from, to) => {
|
|
50
56
|
const fromParts = normalizePath(from).split("/").filter(Boolean);
|
|
51
57
|
const toParts = normalizePath(to).split("/").filter(Boolean);
|
|
@@ -80,18 +86,29 @@ const isPathInside = (file, root) => {
|
|
|
80
86
|
const generateAppModule = (plan, model) => {
|
|
81
87
|
const moduleId = `${plan.outputRoot}/app.ts`;
|
|
82
88
|
const typesModuleId = `${plan.outputRoot}/types.ts`;
|
|
89
|
+
const errorModuleId = `${plan.outputRoot}/error.ts`;
|
|
83
90
|
const root = model.groups.find((group) => group.id === "root");
|
|
84
91
|
if (!root) throw new Error("Nooh compilation requires a root route group.");
|
|
85
92
|
return {
|
|
86
93
|
code: [
|
|
87
94
|
...[
|
|
88
95
|
`import { Hono } from "hono";`,
|
|
96
|
+
`import config from ${JSON.stringify(relativeModuleSpecifier(moduleId, model.config.source))};`,
|
|
89
97
|
`import type { App } from ${JSON.stringify(relativeModuleSpecifier(moduleId, typesModuleId))};`,
|
|
98
|
+
`import { defaultErrorHandler } from ${JSON.stringify(relativeModuleSpecifier(moduleId, errorModuleId))};`,
|
|
90
99
|
`import root from ${JSON.stringify(relativeModuleSpecifier(moduleId, groupModuleId(plan, root.id)))};`
|
|
91
100
|
],
|
|
92
101
|
"",
|
|
93
102
|
"const app = new Hono<App>();",
|
|
94
103
|
"",
|
|
104
|
+
"if (config.onError !== undefined) {",
|
|
105
|
+
" app.onError(",
|
|
106
|
+
" config.onError as Parameters<typeof app.onError>[0],",
|
|
107
|
+
" );",
|
|
108
|
+
"} else {",
|
|
109
|
+
" app.onError(defaultErrorHandler);",
|
|
110
|
+
"}",
|
|
111
|
+
"",
|
|
95
112
|
"app.route(\"/\", root);",
|
|
96
113
|
"",
|
|
97
114
|
"export type AppType = typeof app;",
|
|
@@ -104,6 +121,175 @@ const generateAppModule = (plan, model) => {
|
|
|
104
121
|
};
|
|
105
122
|
};
|
|
106
123
|
//#endregion
|
|
124
|
+
//#region src/generate/dependency.ts
|
|
125
|
+
const generateDependencyModule = (plan) => {
|
|
126
|
+
const moduleId = `${plan.outputRoot}/router/di.ts`;
|
|
127
|
+
return {
|
|
128
|
+
code: [
|
|
129
|
+
"import type {",
|
|
130
|
+
" DependencyResolutionContext,",
|
|
131
|
+
"} from \"@nooh-ts/nooh\";",
|
|
132
|
+
"",
|
|
133
|
+
"const requestContexts =",
|
|
134
|
+
" new WeakMap<object, DependencyResolutionContext>();",
|
|
135
|
+
"",
|
|
136
|
+
"export const createDependencyResolutionContext = (): DependencyResolutionContext => ({",
|
|
137
|
+
" cache: new Map<object, unknown>(),",
|
|
138
|
+
" resolving: new Set<object>(),",
|
|
139
|
+
" stack: [],",
|
|
140
|
+
"});",
|
|
141
|
+
"",
|
|
142
|
+
"export const getDependencyResolutionContext = (",
|
|
143
|
+
" request: object,",
|
|
144
|
+
"): DependencyResolutionContext => {",
|
|
145
|
+
" const existing = requestContexts.get(request);",
|
|
146
|
+
"",
|
|
147
|
+
" if (existing) {",
|
|
148
|
+
" return existing;",
|
|
149
|
+
" }",
|
|
150
|
+
"",
|
|
151
|
+
" const created = createDependencyResolutionContext();",
|
|
152
|
+
"",
|
|
153
|
+
" requestContexts.set(request, created);",
|
|
154
|
+
"",
|
|
155
|
+
" return created;",
|
|
156
|
+
"};",
|
|
157
|
+
""
|
|
158
|
+
].join("\n"),
|
|
159
|
+
id: moduleId,
|
|
160
|
+
kind: "di"
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/generate/error.ts
|
|
165
|
+
const generateErrorModule = (plan) => {
|
|
166
|
+
const moduleId = `${plan.outputRoot}/error.ts`;
|
|
167
|
+
const typesModuleId = `${plan.outputRoot}/types.ts`;
|
|
168
|
+
return {
|
|
169
|
+
code: [
|
|
170
|
+
`import { HTTPException } from "hono/http-exception";`,
|
|
171
|
+
`import type { ErrorHandler } from "hono";`,
|
|
172
|
+
`import type { App } from ${JSON.stringify(relativeModuleSpecifier(moduleId, typesModuleId))};`,
|
|
173
|
+
`import type { NoohStandardSchema } from "@nooh-ts/nooh";`,
|
|
174
|
+
"",
|
|
175
|
+
"const isRecord = (",
|
|
176
|
+
" value: unknown,",
|
|
177
|
+
"): value is Record<PropertyKey, unknown> =>",
|
|
178
|
+
" typeof value === \"object\" && value !== null;",
|
|
179
|
+
"",
|
|
180
|
+
"export class ResponseValidationError extends Error {",
|
|
181
|
+
" readonly route: string;",
|
|
182
|
+
" readonly issues: readonly unknown[];",
|
|
183
|
+
"",
|
|
184
|
+
" constructor(",
|
|
185
|
+
" route: string,",
|
|
186
|
+
" issues: readonly unknown[],",
|
|
187
|
+
" ) {",
|
|
188
|
+
" super(`Response validation failed for ${route}.`);",
|
|
189
|
+
" this.name = \"ResponseValidationError\";",
|
|
190
|
+
" this.route = route;",
|
|
191
|
+
" this.issues = issues;",
|
|
192
|
+
" }",
|
|
193
|
+
"}",
|
|
194
|
+
"",
|
|
195
|
+
"const extractResponseValue = async (",
|
|
196
|
+
" response: Response,",
|
|
197
|
+
" route: string,",
|
|
198
|
+
"): Promise<unknown> => {",
|
|
199
|
+
" if (",
|
|
200
|
+
" response.status === 204 ||",
|
|
201
|
+
" response.status === 205 ||",
|
|
202
|
+
" response.status === 304 ||",
|
|
203
|
+
" response.body === null",
|
|
204
|
+
" ) {",
|
|
205
|
+
" return undefined;",
|
|
206
|
+
" }",
|
|
207
|
+
"",
|
|
208
|
+
" const contentType =",
|
|
209
|
+
" response.headers.get(\"content-type\")?.split(\";\", 1)[0]?.trim().toLowerCase() ??",
|
|
210
|
+
" \"\";",
|
|
211
|
+
"",
|
|
212
|
+
" let clone: Response;",
|
|
213
|
+
"",
|
|
214
|
+
" try {",
|
|
215
|
+
" clone = response.clone();",
|
|
216
|
+
" } catch {",
|
|
217
|
+
" throw new ResponseValidationError(route, [",
|
|
218
|
+
" {",
|
|
219
|
+
" message: \"The response body could not be cloned for validation.\",",
|
|
220
|
+
" },",
|
|
221
|
+
" ]);",
|
|
222
|
+
" }",
|
|
223
|
+
"",
|
|
224
|
+
" if (",
|
|
225
|
+
" contentType === \"application/json\" ||",
|
|
226
|
+
" contentType.endsWith(\"+json\")",
|
|
227
|
+
" ) {",
|
|
228
|
+
" try {",
|
|
229
|
+
" return await clone.json();",
|
|
230
|
+
" } catch {",
|
|
231
|
+
" throw new ResponseValidationError(route, [",
|
|
232
|
+
" {",
|
|
233
|
+
" message: \"The response body is not valid JSON.\",",
|
|
234
|
+
" },",
|
|
235
|
+
" ]);",
|
|
236
|
+
" }",
|
|
237
|
+
" }",
|
|
238
|
+
"",
|
|
239
|
+
" if (contentType.startsWith(\"text/\")) {",
|
|
240
|
+
" return clone.text();",
|
|
241
|
+
" }",
|
|
242
|
+
"",
|
|
243
|
+
" throw new ResponseValidationError(route, [",
|
|
244
|
+
" {",
|
|
245
|
+
" message:",
|
|
246
|
+
" `Response validation only supports JSON and text responses. ` +",
|
|
247
|
+
" `Received \"${contentType || \"unknown\"}\".`,",
|
|
248
|
+
" },",
|
|
249
|
+
" ]);",
|
|
250
|
+
"};",
|
|
251
|
+
"",
|
|
252
|
+
"export const validateResponse = async (",
|
|
253
|
+
" response: Response,",
|
|
254
|
+
" schema: NoohStandardSchema,",
|
|
255
|
+
" route: string,",
|
|
256
|
+
"): Promise<Response> => {",
|
|
257
|
+
" const value = await extractResponseValue(response, route);",
|
|
258
|
+
"",
|
|
259
|
+
" const validate = schema[\"~standard\"]",
|
|
260
|
+
" .validate as (value: unknown) =>",
|
|
261
|
+
" | unknown",
|
|
262
|
+
" | Promise<unknown>;",
|
|
263
|
+
"",
|
|
264
|
+
" const result = await validate(value);",
|
|
265
|
+
"",
|
|
266
|
+
" if (",
|
|
267
|
+
" isRecord(result) &&",
|
|
268
|
+
" \"issues\" in result &&",
|
|
269
|
+
" Array.isArray(result.issues)",
|
|
270
|
+
" ) {",
|
|
271
|
+
" throw new ResponseValidationError(route, result.issues);",
|
|
272
|
+
" }",
|
|
273
|
+
"",
|
|
274
|
+
" return response;",
|
|
275
|
+
"};",
|
|
276
|
+
"",
|
|
277
|
+
"export const defaultErrorHandler: ErrorHandler<App> = (error, c) => {",
|
|
278
|
+
" if (error instanceof HTTPException) {",
|
|
279
|
+
" return error.getResponse();",
|
|
280
|
+
" }",
|
|
281
|
+
"",
|
|
282
|
+
" console.error(error);",
|
|
283
|
+
"",
|
|
284
|
+
" return c.json({ error: \"Internal Server Error\" }, 500);",
|
|
285
|
+
"};",
|
|
286
|
+
""
|
|
287
|
+
].join("\n"),
|
|
288
|
+
id: moduleId,
|
|
289
|
+
kind: "error"
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
//#endregion
|
|
107
293
|
//#region src/generate/group.ts
|
|
108
294
|
const getGroupRoutes = (model, group) => {
|
|
109
295
|
const ids = new Set(group.routes);
|
|
@@ -156,10 +342,32 @@ const generateGroupModule = (plan, model, group) => {
|
|
|
156
342
|
"",
|
|
157
343
|
"use(\"*\", ...(groupConfig.middleware ?? []));"
|
|
158
344
|
] : [];
|
|
159
|
-
const
|
|
160
|
-
|
|
345
|
+
const routeRegistrations = routes.map((route, index) => {
|
|
346
|
+
const register = `register${capitalize(route.method)}`;
|
|
347
|
+
const path = JSON.stringify(ensureLeadingSlash(route.localPath));
|
|
348
|
+
const endpointApp = `endpointApp${index}`;
|
|
349
|
+
const endpointRegister = `registerEndpoint${index}`;
|
|
350
|
+
return [
|
|
351
|
+
` if (endpoint${index}.onError === undefined) {`,
|
|
352
|
+
` ${register}(${path}, ...endpoint${index});`,
|
|
353
|
+
" } else {",
|
|
354
|
+
` const ${endpointApp} = new Hono<App>();`,
|
|
355
|
+
` ${endpointApp}.onError(`,
|
|
356
|
+
` endpoint${index}.onError as Parameters<typeof ${endpointApp}.onError>[0],`,
|
|
357
|
+
" );",
|
|
358
|
+
` const ${endpointRegister} = ${endpointApp}.${route.method} as unknown as RouteRegister;`,
|
|
359
|
+
` ${endpointRegister}(${path}, ...endpoint${index});`,
|
|
360
|
+
` route.route(${path}, ${endpointApp});`,
|
|
361
|
+
" }"
|
|
362
|
+
].join("\n");
|
|
161
363
|
});
|
|
162
364
|
const childRegistrations = children.map((child, index) => ` route.route(${JSON.stringify(getChildPath(group, child))}, child${index});`);
|
|
365
|
+
const groupErrorHandler = group.configSource ? [
|
|
366
|
+
"",
|
|
367
|
+
"if (groupConfig.onError !== undefined) {",
|
|
368
|
+
" route.onError(groupConfig.onError);",
|
|
369
|
+
"}"
|
|
370
|
+
] : [];
|
|
163
371
|
return {
|
|
164
372
|
code: [
|
|
165
373
|
...imports,
|
|
@@ -170,7 +378,8 @@ const generateGroupModule = (plan, model, group) => {
|
|
|
170
378
|
"",
|
|
171
379
|
...registerDeclarations,
|
|
172
380
|
...useDeclaration.length > 0 ? ["", ...useDeclaration] : [],
|
|
173
|
-
...
|
|
381
|
+
...groupErrorHandler,
|
|
382
|
+
...routeRegistrations.length > 0 ? ["", ...routeRegistrations] : [],
|
|
174
383
|
...childRegistrations.length > 0 ? ["", ...childRegistrations] : [],
|
|
175
384
|
"",
|
|
176
385
|
"export default route;",
|
|
@@ -198,14 +407,6 @@ const generateMiddlewareModule = (plan) => {
|
|
|
198
407
|
};
|
|
199
408
|
//#endregion
|
|
200
409
|
//#region src/generate/router.ts
|
|
201
|
-
const VALIDATION_TARGETS = [
|
|
202
|
-
"json",
|
|
203
|
-
"form",
|
|
204
|
-
"query",
|
|
205
|
-
"param",
|
|
206
|
-
"header",
|
|
207
|
-
"cookie"
|
|
208
|
-
];
|
|
209
410
|
const METHOD_FUNCTION_NAMES = {
|
|
210
411
|
all: "all",
|
|
211
412
|
delete: "del",
|
|
@@ -217,96 +418,316 @@ const METHOD_FUNCTION_NAMES = {
|
|
|
217
418
|
put: "put"
|
|
218
419
|
};
|
|
219
420
|
const getRoutesForRouter = (model, routerPath) => model.routes.filter((route) => route.routerPath === routerPath).sort((a, b) => a.method.localeCompare(b.method));
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
return [
|
|
224
|
-
|
|
421
|
+
const getRouteDependencyModel = (model, routeId) => model.routeDependencies.find((route) => route.routeId === routeId);
|
|
422
|
+
const getDependencyNames = (model, route) => {
|
|
423
|
+
const dependencyModel = getRouteDependencyModel(model, route.id);
|
|
424
|
+
if (!dependencyModel) return [];
|
|
425
|
+
return dependencyModel.roots.map((dependencyId) => {
|
|
426
|
+
const node = model.dependencies.nodes.get(dependencyId);
|
|
427
|
+
if (!node) throw new Error([
|
|
428
|
+
"Nooh internal error:",
|
|
429
|
+
`dependency "${dependencyId}"`,
|
|
430
|
+
`for route "${route.id}" is missing`,
|
|
431
|
+
"from the dependency graph."
|
|
432
|
+
].join(" "));
|
|
433
|
+
return node.declaration.name;
|
|
434
|
+
});
|
|
435
|
+
};
|
|
436
|
+
const renderMethod = (model, route, mode) => {
|
|
437
|
+
const functionName = METHOD_FUNCTION_NAMES[route.method];
|
|
438
|
+
const prefix = functionName.charAt(0).toUpperCase() + functionName.slice(1);
|
|
439
|
+
const path = JSON.stringify(ensureLeadingSlash(route.localPath));
|
|
440
|
+
const routeLabel = JSON.stringify(`${route.method.toUpperCase()} ${ensureLeadingSlash(route.localPath)}`);
|
|
441
|
+
const dependencyNames = getDependencyNames(model, route);
|
|
442
|
+
const common = [
|
|
443
|
+
`type ${prefix}Path = ${path};`,
|
|
225
444
|
"",
|
|
226
|
-
|
|
445
|
+
`type ${prefix}RouteMiddleware = MiddlewareHandler<App, ${prefix}Path>;`,
|
|
227
446
|
"",
|
|
228
|
-
|
|
229
|
-
"type StandardSchema = Parameters<typeof sValidator>[1];",
|
|
447
|
+
`type ${prefix}RouteHandler = Handler<App, ${prefix}Path, any, any>;`,
|
|
230
448
|
"",
|
|
231
|
-
|
|
232
|
-
"
|
|
449
|
+
`type ${prefix}ValidationSchemaMap = Record<`,
|
|
450
|
+
" NoohRequestValidationTarget,",
|
|
451
|
+
" NoohStandardSchema",
|
|
233
452
|
">;",
|
|
234
453
|
"",
|
|
235
|
-
|
|
236
|
-
"
|
|
237
|
-
"
|
|
238
|
-
" infer I,",
|
|
239
|
-
" any",
|
|
240
|
-
"> ? I : never;",
|
|
454
|
+
`type ${prefix}ValidationOptions = Partial<${prefix}ValidationSchemaMap> & {`,
|
|
455
|
+
" readonly response?: NoohStandardSchema;",
|
|
456
|
+
"};",
|
|
241
457
|
"",
|
|
242
|
-
|
|
243
|
-
" Target extends
|
|
244
|
-
" Schema extends
|
|
245
|
-
"> =
|
|
246
|
-
"
|
|
247
|
-
"
|
|
458
|
+
`type ${prefix}ValidationEntry<`,
|
|
459
|
+
" Target extends NoohRequestValidationTarget,",
|
|
460
|
+
" Schema extends NoohStandardSchema",
|
|
461
|
+
"> = undefined extends NoohStandardSchemaInput<Schema>",
|
|
462
|
+
" ? {",
|
|
463
|
+
" readonly in: {",
|
|
464
|
+
" readonly [Key in Target]?: NoohStandardSchemaInput<Schema>;",
|
|
465
|
+
" };",
|
|
466
|
+
" readonly out: {",
|
|
467
|
+
" readonly [Key in Target]: NoohStandardSchemaOutput<Schema>;",
|
|
468
|
+
" };",
|
|
469
|
+
" }",
|
|
470
|
+
" : {",
|
|
471
|
+
" readonly in: {",
|
|
472
|
+
" readonly [Key in Target]: NoohStandardSchemaInput<Schema>;",
|
|
473
|
+
" };",
|
|
474
|
+
" readonly out: {",
|
|
475
|
+
" readonly [Key in Target]: NoohStandardSchemaOutput<Schema>;",
|
|
476
|
+
" };",
|
|
477
|
+
" };",
|
|
248
478
|
"",
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
"
|
|
479
|
+
`type ${prefix}ValidationInput<V extends ${prefix}ValidationOptions> =`,
|
|
480
|
+
" keyof V extends never",
|
|
481
|
+
" ? {}",
|
|
482
|
+
" : UnionToIntersection<{",
|
|
483
|
+
" [Target in keyof V & NoohRequestValidationTarget]:",
|
|
484
|
+
" V[Target] extends NoohStandardSchema",
|
|
485
|
+
" ? ",
|
|
486
|
+
` ${prefix}ValidationEntry<Target, V[Target]>`,
|
|
487
|
+
" : never",
|
|
488
|
+
" }[keyof V &",
|
|
489
|
+
" NoohRequestValidationTarget]>;",
|
|
252
490
|
"",
|
|
253
|
-
|
|
491
|
+
`type ${prefix}ResponseSchema<V extends ${prefix}ValidationOptions> =`,
|
|
492
|
+
" V extends {",
|
|
493
|
+
" readonly response: infer Schema extends NoohStandardSchema;",
|
|
494
|
+
" }",
|
|
495
|
+
" ? Schema",
|
|
496
|
+
" : never;",
|
|
254
497
|
"",
|
|
255
|
-
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
"
|
|
498
|
+
`type ${prefix}NoohHandlerReturn<V extends ${prefix}ValidationOptions> =`,
|
|
499
|
+
" V extends {",
|
|
500
|
+
" readonly response: infer Schema extends NoohStandardSchema;",
|
|
501
|
+
" }",
|
|
502
|
+
" ? Response &",
|
|
503
|
+
" TypedResponse<",
|
|
504
|
+
" NoohStandardSchemaInput<Schema>,",
|
|
505
|
+
" any,",
|
|
506
|
+
" any",
|
|
507
|
+
" >",
|
|
508
|
+
" | Promise<",
|
|
509
|
+
" Response &",
|
|
510
|
+
" TypedResponse<",
|
|
511
|
+
" NoohStandardSchemaInput<Schema>,",
|
|
512
|
+
" any,",
|
|
513
|
+
" any",
|
|
514
|
+
" >",
|
|
515
|
+
" >",
|
|
516
|
+
` : ReturnType<${prefix}RouteHandler>;`,
|
|
517
|
+
"",
|
|
518
|
+
`type ${prefix}RouteContext<V extends ${prefix}ValidationOptions> =`,
|
|
519
|
+
" Context<",
|
|
520
|
+
" App,",
|
|
521
|
+
` ${prefix}Path,`,
|
|
522
|
+
` ${prefix}ValidationInput<V>`,
|
|
523
|
+
" >;",
|
|
524
|
+
"",
|
|
525
|
+
`type ${prefix}RouteNext = Parameters<${prefix}RouteHandler>[1];`,
|
|
526
|
+
"",
|
|
527
|
+
`type ${prefix}RouteErrorHandler = NoohErrorHandler<App, ${prefix}Path>;`,
|
|
528
|
+
"",
|
|
529
|
+
`type ${prefix}HandlerInput<`,
|
|
530
|
+
" D extends readonly RouteDependency[],",
|
|
531
|
+
` V extends ${prefix}ValidationOptions,`,
|
|
532
|
+
" E extends ErrorDefinitions",
|
|
533
|
+
"> = {",
|
|
534
|
+
` readonly c: ${prefix}RouteContext<V>;`,
|
|
535
|
+
` readonly next: ${prefix}RouteNext;`,
|
|
536
|
+
"} & DependencyContext<D> & (",
|
|
537
|
+
" keyof E extends never",
|
|
538
|
+
" ? {}",
|
|
539
|
+
" : { readonly errors: ErrorContext<E> }",
|
|
540
|
+
");",
|
|
541
|
+
"",
|
|
542
|
+
`type ${prefix}NoohHandler<`,
|
|
543
|
+
" D extends readonly RouteDependency[],",
|
|
544
|
+
` V extends ${prefix}ValidationOptions,`,
|
|
545
|
+
" E extends ErrorDefinitions",
|
|
546
|
+
"> = (",
|
|
547
|
+
` input: ${prefix}HandlerInput<D, V, E>`,
|
|
548
|
+
`) => ${prefix}NoohHandlerReturn<V>;`,
|
|
549
|
+
"",
|
|
550
|
+
`type ${prefix}RouteHandlers = readonly ${prefix}RouteHandler[] & {`,
|
|
551
|
+
` readonly onError?: ${prefix}RouteErrorHandler;`,
|
|
552
|
+
"};",
|
|
553
|
+
"",
|
|
554
|
+
`type ${prefix}EndpointOptions<`,
|
|
555
|
+
" D extends readonly RouteDependency[] = readonly RouteDependency[],",
|
|
556
|
+
` V extends ${prefix}ValidationOptions = ${prefix}ValidationOptions,`,
|
|
557
|
+
` M extends readonly ${prefix}RouteMiddleware[] = readonly ${prefix}RouteMiddleware[],`,
|
|
558
|
+
" E extends ErrorDefinitions = {}",
|
|
259
559
|
"> = {",
|
|
260
560
|
" readonly middleware?: M;",
|
|
261
561
|
" readonly validation?: V;",
|
|
262
|
-
" readonly
|
|
562
|
+
" readonly deps?: D & ValidateDependencies<D, ReservedDependencyName>;",
|
|
563
|
+
" readonly errors?: E;",
|
|
564
|
+
` readonly onError?: ${prefix}RouteErrorHandler;`,
|
|
565
|
+
` readonly handler: ${prefix}NoohHandler<D, V, E>;`,
|
|
263
566
|
"};",
|
|
264
567
|
"",
|
|
265
|
-
`export function ${functionName}
|
|
266
|
-
|
|
267
|
-
|
|
568
|
+
`export function ${functionName}(`,
|
|
569
|
+
` handler: ${prefix}RouteHandler`,
|
|
570
|
+
`): ${prefix}RouteHandlers;`,
|
|
268
571
|
"",
|
|
269
572
|
`export function ${functionName}<`,
|
|
270
|
-
"
|
|
271
|
-
|
|
272
|
-
|
|
573
|
+
" const D extends readonly RouteDependency[] = [],",
|
|
574
|
+
` const V extends ${prefix}ValidationOptions = {},`,
|
|
575
|
+
` const M extends readonly ${prefix}RouteMiddleware[] = [],`,
|
|
576
|
+
" const E extends ErrorDefinitions = {}",
|
|
273
577
|
">(",
|
|
274
|
-
|
|
275
|
-
|
|
578
|
+
` options: ${prefix}EndpointOptions<D, V, M, E>`,
|
|
579
|
+
`): ${prefix}RouteHandlers;`,
|
|
276
580
|
"",
|
|
277
|
-
`export function ${functionName}
|
|
278
|
-
" M extends readonly RouteMiddleware[],",
|
|
279
|
-
" V extends ValidationOptions,",
|
|
280
|
-
" H extends Handler<App, Path, ValidationInput<V>>,",
|
|
281
|
-
">(",
|
|
581
|
+
`export function ${functionName}(`,
|
|
282
582
|
" input:",
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
583
|
+
` | ${prefix}RouteHandler`,
|
|
584
|
+
` | ${prefix}EndpointOptions`,
|
|
585
|
+
`): ${prefix}RouteHandlers {`
|
|
586
|
+
];
|
|
587
|
+
if (mode === "introspection") return [
|
|
588
|
+
...common,
|
|
589
|
+
"",
|
|
286
590
|
" if (typeof input === \"function\") {",
|
|
287
|
-
" return
|
|
591
|
+
" return defineRouteMetadata(",
|
|
592
|
+
" [input],",
|
|
593
|
+
" [],",
|
|
594
|
+
` ) as unknown as ${prefix}RouteHandlers;`,
|
|
288
595
|
" }",
|
|
289
596
|
"",
|
|
290
|
-
" return
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
"
|
|
597
|
+
" return defineRouteMetadata(",
|
|
598
|
+
` [input.handler as unknown as ${prefix}RouteHandler],`,
|
|
599
|
+
" input.deps ?? [],",
|
|
600
|
+
` ) as unknown as ${prefix}RouteHandlers;`,
|
|
601
|
+
"}",
|
|
602
|
+
""
|
|
603
|
+
].join("\n");
|
|
604
|
+
const dependencyResolution = dependencyNames.flatMap((_, index) => [` const resolvedDependency${index} = dependencies[${index}]!.resolve(dependencyContext);`]);
|
|
605
|
+
const dependencyProperties = dependencyNames.map((name, index) => ` ${JSON.stringify(name)}: resolvedDependency${index},`);
|
|
606
|
+
return [
|
|
607
|
+
...common,
|
|
608
|
+
"",
|
|
609
|
+
" if (typeof input === \"function\") {",
|
|
610
|
+
` return defineRouteHandlers<${prefix}Path>([input]);`,
|
|
611
|
+
" }",
|
|
612
|
+
"",
|
|
613
|
+
" const dependencies = input.deps ?? [];",
|
|
614
|
+
"",
|
|
615
|
+
" const errors =",
|
|
616
|
+
" input.errors === undefined",
|
|
617
|
+
" ? undefined",
|
|
618
|
+
" : createErrorContext(input.errors);",
|
|
619
|
+
"",
|
|
620
|
+
` const handler: ${prefix}RouteHandler = async (c, next) => {`,
|
|
621
|
+
...dependencyNames.length > 0 ? [
|
|
622
|
+
" const dependencyContext =",
|
|
623
|
+
" getDependencyResolutionContext(c);",
|
|
624
|
+
"",
|
|
625
|
+
...dependencyResolution,
|
|
626
|
+
""
|
|
627
|
+
] : [],
|
|
628
|
+
" const response = await input.handler({",
|
|
629
|
+
" c: c as unknown as",
|
|
630
|
+
` ${prefix}RouteContext<${prefix}ValidationOptions>,`,
|
|
631
|
+
" next,",
|
|
632
|
+
...dependencyNames.length > 0 ? dependencyProperties : [],
|
|
633
|
+
" ...(errors !== undefined ? { errors } : {})",
|
|
634
|
+
" });",
|
|
635
|
+
"",
|
|
636
|
+
" if (input.validation?.response !== undefined) {",
|
|
637
|
+
" return validateResponse(",
|
|
638
|
+
" response as Response,",
|
|
639
|
+
" input.validation.response,",
|
|
640
|
+
` ${routeLabel}`,
|
|
641
|
+
" );",
|
|
642
|
+
" }",
|
|
643
|
+
"",
|
|
644
|
+
" return response;",
|
|
645
|
+
" };",
|
|
646
|
+
"",
|
|
647
|
+
" const handlers = [",
|
|
648
|
+
` ...((input.middleware ?? []) as readonly ${prefix}RouteHandler[]),`,
|
|
649
|
+
...[
|
|
650
|
+
"json",
|
|
651
|
+
"form",
|
|
652
|
+
"query",
|
|
653
|
+
"param",
|
|
654
|
+
"header",
|
|
655
|
+
"cookie"
|
|
656
|
+
].map((target) => ` ...(input.validation?.${target} !== undefined ? [validatorEngine(${JSON.stringify(target)}, input.validation.${target}) as ${prefix}RouteHandler] : []),`),
|
|
657
|
+
" handler,",
|
|
658
|
+
" ] as const;",
|
|
659
|
+
"",
|
|
660
|
+
` return defineRouteHandlers<${prefix}Path>(handlers, input.onError);`,
|
|
295
661
|
"}",
|
|
296
662
|
""
|
|
297
663
|
].join("\n");
|
|
298
664
|
};
|
|
299
|
-
const
|
|
665
|
+
const COMMON_TYPES = [
|
|
666
|
+
"type RouteDependency = AnyDependencyReference;",
|
|
667
|
+
"",
|
|
668
|
+
"type ErrorDefinitions = Record<string, ErrorConstructor>;",
|
|
669
|
+
"",
|
|
670
|
+
"type UnionToIntersection<Union> =",
|
|
671
|
+
" (Union extends unknown",
|
|
672
|
+
" ? (value: Union) => void",
|
|
673
|
+
" : never) extends",
|
|
674
|
+
" (value: infer Intersection) => void",
|
|
675
|
+
" ? Intersection",
|
|
676
|
+
" : never;",
|
|
677
|
+
"",
|
|
678
|
+
"type ReservedDependencyName =",
|
|
679
|
+
" | \"c\"",
|
|
680
|
+
" | \"next\"",
|
|
681
|
+
" | \"error\"",
|
|
682
|
+
" | \"errors\"",
|
|
683
|
+
" | \"onError\"",
|
|
684
|
+
" | \"response\"",
|
|
685
|
+
" | NoohValidationTarget;"
|
|
686
|
+
];
|
|
687
|
+
const generateRouterModule = (plan, model, routerPath, mode = "runtime") => {
|
|
300
688
|
const routes = getRoutesForRouter(model, routerPath);
|
|
301
689
|
const moduleId = `${plan.outputRoot}/${routerPath}.ts`;
|
|
302
690
|
const typesModuleId = `${plan.outputRoot}/types.ts`;
|
|
691
|
+
const dependencyModuleId = `${plan.outputRoot}/router/di.ts`;
|
|
692
|
+
const errorModuleId = `${plan.outputRoot}/error.ts`;
|
|
693
|
+
const configModuleId = model.config.source;
|
|
694
|
+
const usesDependencies = mode === "runtime" && routes.some((route) => {
|
|
695
|
+
return !!getRouteDependencyModel(model, route.id)?.roots.length;
|
|
696
|
+
});
|
|
697
|
+
const imports = [
|
|
698
|
+
"import type {",
|
|
699
|
+
" Context,",
|
|
700
|
+
" Handler,",
|
|
701
|
+
" MiddlewareHandler,",
|
|
702
|
+
" TypedResponse",
|
|
703
|
+
"} from \"hono\";",
|
|
704
|
+
"import type {",
|
|
705
|
+
" AnyDependencyReference,",
|
|
706
|
+
" DependencyContext,",
|
|
707
|
+
" ErrorContext,",
|
|
708
|
+
" ErrorConstructor,",
|
|
709
|
+
" NoohErrorHandler,",
|
|
710
|
+
" NoohRequestValidationTarget,",
|
|
711
|
+
" NoohStandardSchema,",
|
|
712
|
+
" NoohStandardSchemaInput,",
|
|
713
|
+
" NoohStandardSchemaOutput,",
|
|
714
|
+
" NoohValidationTarget,",
|
|
715
|
+
" ValidateDependencies",
|
|
716
|
+
"} from \"@nooh-ts/nooh\";",
|
|
717
|
+
`import type { App } from ${JSON.stringify(relativeModuleSpecifier(moduleId, typesModuleId))};`
|
|
718
|
+
];
|
|
719
|
+
if (mode === "runtime") imports.unshift(`import { sValidator } from "@hono/standard-validator";`, `import config from ${JSON.stringify(relativeModuleSpecifier(moduleId, configModuleId))};`, `import { validateResponse } from ${JSON.stringify(relativeModuleSpecifier(moduleId, errorModuleId))};`);
|
|
720
|
+
if (usesDependencies) imports.push("import {", " getDependencyResolutionContext", "} from " + JSON.stringify(relativeModuleSpecifier(moduleId, dependencyModuleId)) + ";");
|
|
721
|
+
const prelude = ["", ...COMMON_TYPES];
|
|
722
|
+
if (mode === "runtime") prelude.push("", "const validatorEngine = (", " config.validator?.engine ?? sValidator", ") as unknown as (", " target: NoohRequestValidationTarget,", " schema: NoohStandardSchema", ") => unknown;");
|
|
723
|
+
if (mode === "runtime") prelude.push("", "const createErrorContext = <", " const E extends ErrorDefinitions", ">(", " definitions: E | undefined", "): ErrorContext<E> => {", " const errors: Record<string, unknown> = {};", "", " if (definitions === undefined) {", " return errors as ErrorContext<E>;", " }", "", " for (const [name, Constructor] of Object.entries(definitions)) {", " errors[name] = (...args: unknown[]) =>", " Reflect.construct(Constructor, args);", " }", "", " return errors as ErrorContext<E>;", "};", "", "const defineRouteHandlers = <", " const P extends string,", " const T extends readonly Handler<App, P, any, any>[]", ">(", " handlers: T,", " onError?: NoohErrorHandler<App, P>", "): T & { readonly onError?: NoohErrorHandler<App, P> } => {", " if (onError !== undefined) {", " Object.defineProperty(handlers, \"onError\", {", " configurable: false,", " enumerable: false,", " value: onError,", " writable: false", " });", " }", "", " return handlers as T & {", " readonly onError?: NoohErrorHandler<App, P>", " };", "};");
|
|
724
|
+
if (mode === "introspection") prelude.push("", "const NOOH_ROUTE_METADATA = Symbol.for(\"nooh.route\");", "", "type NoohRouteMetadata = {", " readonly kind: \"route\";", " readonly dependencies: readonly RouteDependency[]", "};", "", "const defineRouteMetadata = <", " T extends readonly unknown[]", ">(", " handlers: T,", " dependencies: readonly RouteDependency[]", "): T => {", " const metadata: NoohRouteMetadata = {", " kind: \"route\",", " dependencies", "};", "", " Object.defineProperty(", " handlers,", " NOOH_ROUTE_METADATA,", " {", " value: metadata,", " enumerable: false", " }", " );", "", " return handlers;", "};");
|
|
303
725
|
return {
|
|
304
726
|
code: [
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
`import type { App } from ${JSON.stringify(relativeModuleSpecifier(moduleId, typesModuleId))};`,
|
|
727
|
+
...imports,
|
|
728
|
+
...prelude,
|
|
308
729
|
"",
|
|
309
|
-
...routes.map(renderMethod)
|
|
730
|
+
...routes.map((route) => renderMethod(model, route, mode))
|
|
310
731
|
].join("\n"),
|
|
311
732
|
id: moduleId,
|
|
312
733
|
kind: "router"
|
|
@@ -335,19 +756,405 @@ const generateTypesModule = (plan, config) => {
|
|
|
335
756
|
};
|
|
336
757
|
//#endregion
|
|
337
758
|
//#region src/generate/index.ts
|
|
759
|
+
const getRouterPaths = (model) => [...new Set(model.routes.map((route) => route.routerPath))].sort();
|
|
338
760
|
const generate = (plan, model) => {
|
|
339
761
|
const modules = [];
|
|
340
762
|
modules.push(generateTypesModule(plan, model.config));
|
|
763
|
+
modules.push(generateDependencyModule(plan));
|
|
764
|
+
modules.push(generateErrorModule(plan));
|
|
341
765
|
modules.push(generateMiddlewareModule(plan));
|
|
342
|
-
const
|
|
343
|
-
for (const routerPath of routerPaths) modules.push(generateRouterModule(plan, model, routerPath));
|
|
766
|
+
for (const routerPath of getRouterPaths(model)) modules.push(generateRouterModule(plan, model, routerPath));
|
|
344
767
|
for (const group of model.groups) modules.push(generateGroupModule(plan, model, group));
|
|
345
768
|
modules.push(generateAppModule(plan, model));
|
|
346
769
|
modules.sort((a, b) => a.id.localeCompare(b.id));
|
|
347
770
|
return { modules };
|
|
348
771
|
};
|
|
772
|
+
const generateRouteIntrospection = (plan, model) => {
|
|
773
|
+
const modules = [];
|
|
774
|
+
for (const routerPath of getRouterPaths(model)) modules.push(generateRouterModule(plan, model, routerPath, "introspection"));
|
|
775
|
+
modules.sort((a, b) => a.id.localeCompare(b.id));
|
|
776
|
+
return { modules };
|
|
777
|
+
};
|
|
778
|
+
//#endregion
|
|
779
|
+
//#region src/pipeline/dependencies.ts
|
|
780
|
+
const isRecord$2 = (value) => typeof value === "object" && value !== null;
|
|
781
|
+
const isDependencyReference = (value) => isRecord$2(value) && value.__nooh_dependency === true && typeof value.name === "string" && Array.isArray(value.dependencies) && (value.scope === "value" || value.scope === "singleton" || value.scope === "request" || value.scope === "transient");
|
|
782
|
+
const isDependencyContainer = (value) => {
|
|
783
|
+
if (!isRecord$2(value) || Array.isArray(value)) return false;
|
|
784
|
+
if (!Object.isFrozen(value)) return false;
|
|
785
|
+
const entries = Object.entries(value);
|
|
786
|
+
return entries.length > 0 && entries.every(([, entry]) => isDependencyReference(entry));
|
|
787
|
+
};
|
|
788
|
+
const emptyGraph = () => ({
|
|
789
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
790
|
+
order: [],
|
|
791
|
+
references: /* @__PURE__ */ new Map()
|
|
792
|
+
});
|
|
793
|
+
const canDependOn = (parent, child) => {
|
|
794
|
+
if (parent === "singleton") return child === "singleton" || child === "value";
|
|
795
|
+
if (parent === "request") return child === "singleton" || child === "request" || child === "transient" || child === "value";
|
|
796
|
+
if (parent === "transient") return true;
|
|
797
|
+
return child === "value";
|
|
798
|
+
};
|
|
799
|
+
const createScopeDiagnostic = (declaration, dependency) => {
|
|
800
|
+
if (canDependOn(declaration.scope, dependency.scope)) return null;
|
|
801
|
+
return {
|
|
802
|
+
code: "NOOH020",
|
|
803
|
+
file: declaration.source,
|
|
804
|
+
message: [`Dependency "${declaration.name}" with scope "${declaration.scope}"`, `cannot depend on "${dependency.name}" with scope "${dependency.scope}".`].join(" "),
|
|
805
|
+
severity: "error"
|
|
806
|
+
};
|
|
807
|
+
};
|
|
808
|
+
const createMissingDependencyDiagnostic = (declaration, dependencyId) => ({
|
|
809
|
+
code: "NOOH021",
|
|
810
|
+
file: declaration.source,
|
|
811
|
+
message: [`Dependency "${declaration.name}" references`, `unknown dependency "${dependencyId}".`].join(" "),
|
|
812
|
+
severity: "error"
|
|
813
|
+
});
|
|
814
|
+
const createDuplicateDependencyDiagnostic = (declaration) => ({
|
|
815
|
+
code: "NOOH022",
|
|
816
|
+
file: declaration.source,
|
|
817
|
+
message: `Duplicate dependency provider "${declaration.id}".`,
|
|
818
|
+
severity: "error"
|
|
819
|
+
});
|
|
820
|
+
const createCycleDiagnostic = (cycle) => {
|
|
821
|
+
const [first] = cycle;
|
|
822
|
+
return {
|
|
823
|
+
code: "NOOH023",
|
|
824
|
+
...first?.declaration.source !== void 0 && { file: first.declaration.source },
|
|
825
|
+
message: `Circular dependency detected: ${cycle.map((node) => node.declaration.name).join(" -> ")}`,
|
|
826
|
+
severity: "error"
|
|
827
|
+
};
|
|
828
|
+
};
|
|
829
|
+
const topologicalOrder = (nodes, diagnostics) => {
|
|
830
|
+
const state = /* @__PURE__ */ new Map();
|
|
831
|
+
const stack = [];
|
|
832
|
+
const order = [];
|
|
833
|
+
const visit = (id) => {
|
|
834
|
+
const current = state.get(id);
|
|
835
|
+
if (current === "visited") return;
|
|
836
|
+
if (current === "visiting") {
|
|
837
|
+
const cycleStart = stack.indexOf(id);
|
|
838
|
+
const cycle = (cycleStart === -1 ? [...stack, id] : [...stack.slice(cycleStart), id]).map((cycleId) => nodes.get(cycleId)).filter((n) => n !== void 0);
|
|
839
|
+
diagnostics.push(createCycleDiagnostic(cycle));
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const node = nodes.get(id);
|
|
843
|
+
if (!node) return;
|
|
844
|
+
state.set(id, "visiting");
|
|
845
|
+
stack.push(id);
|
|
846
|
+
for (const dependencyId of node.declaration.dependencies) visit(dependencyId);
|
|
847
|
+
stack.pop();
|
|
848
|
+
state.set(id, "visited");
|
|
849
|
+
order.push(id);
|
|
850
|
+
};
|
|
851
|
+
const ids = [...nodes.keys()].sort();
|
|
852
|
+
for (const id of ids) visit(id);
|
|
853
|
+
return order;
|
|
854
|
+
};
|
|
855
|
+
const createDependencyGraph = (declarations, references = /* @__PURE__ */ new Map()) => {
|
|
856
|
+
if (declarations.length === 0) return {
|
|
857
|
+
diagnostics: [],
|
|
858
|
+
graph: {
|
|
859
|
+
...emptyGraph(),
|
|
860
|
+
references
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
const diagnostics = [];
|
|
864
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
865
|
+
for (const declaration of declarations) {
|
|
866
|
+
if (nodes.has(declaration.id)) {
|
|
867
|
+
diagnostics.push(createDuplicateDependencyDiagnostic(declaration));
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
nodes.set(declaration.id, { declaration });
|
|
871
|
+
}
|
|
872
|
+
for (const declaration of declarations) {
|
|
873
|
+
if (!nodes.get(declaration.id)) continue;
|
|
874
|
+
for (const dependencyId of declaration.dependencies) {
|
|
875
|
+
const dependencyNode = nodes.get(dependencyId);
|
|
876
|
+
if (!dependencyNode) {
|
|
877
|
+
diagnostics.push(createMissingDependencyDiagnostic(declaration, dependencyId));
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const scopeDiagnostic = createScopeDiagnostic(declaration, dependencyNode.declaration);
|
|
881
|
+
if (scopeDiagnostic) diagnostics.push(scopeDiagnostic);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const order = topologicalOrder(nodes, diagnostics);
|
|
885
|
+
const unique = /* @__PURE__ */ new Map();
|
|
886
|
+
for (const diagnostic of diagnostics) {
|
|
887
|
+
const key = [
|
|
888
|
+
diagnostic.code,
|
|
889
|
+
diagnostic.file ?? "",
|
|
890
|
+
diagnostic.message
|
|
891
|
+
].join("\0");
|
|
892
|
+
unique.set(key, diagnostic);
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
diagnostics: [...unique.values()],
|
|
896
|
+
graph: {
|
|
897
|
+
nodes,
|
|
898
|
+
order,
|
|
899
|
+
references
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
};
|
|
903
|
+
const loadModule = async (loader, path) => {
|
|
904
|
+
if (loader.loadModule) return loader.loadModule(path);
|
|
905
|
+
return { default: await loader.loadDefault(path) };
|
|
906
|
+
};
|
|
907
|
+
const collectReferences = (source, module) => {
|
|
908
|
+
const diagnostics = [];
|
|
909
|
+
const references = [];
|
|
910
|
+
const seenObjects = /* @__PURE__ */ new Set();
|
|
911
|
+
const addReference = (id, value) => {
|
|
912
|
+
if (seenObjects.has(value)) {
|
|
913
|
+
diagnostics.push({
|
|
914
|
+
code: "NOOH027",
|
|
915
|
+
file: source,
|
|
916
|
+
message: [
|
|
917
|
+
`Dependency reference "${value.name}"`,
|
|
918
|
+
"is exported more than once.",
|
|
919
|
+
`Duplicate provider: "${id}".`
|
|
920
|
+
].join(" "),
|
|
921
|
+
severity: "error"
|
|
922
|
+
});
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
seenObjects.add(value);
|
|
926
|
+
references.push({
|
|
927
|
+
id,
|
|
928
|
+
name: value.name,
|
|
929
|
+
reference: value,
|
|
930
|
+
scope: value.scope,
|
|
931
|
+
source
|
|
932
|
+
});
|
|
933
|
+
};
|
|
934
|
+
for (const [exportName, exported] of Object.entries(module)) {
|
|
935
|
+
if (exportName === "__esModule") continue;
|
|
936
|
+
if (isDependencyReference(exported)) {
|
|
937
|
+
addReference(`${source}#${exportName}`, exported);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (isDependencyContainer(exported)) for (const [name, reference] of Object.entries(exported)) {
|
|
941
|
+
if (!isDependencyReference(reference)) continue;
|
|
942
|
+
addReference(`${source}#${exportName}.${name}`, reference);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return {
|
|
946
|
+
diagnostics,
|
|
947
|
+
references
|
|
948
|
+
};
|
|
949
|
+
};
|
|
950
|
+
const loadDependencyGraph = async (sources, loader) => {
|
|
951
|
+
if (sources.length === 0) return {
|
|
952
|
+
diagnostics: [],
|
|
953
|
+
graph: emptyGraph()
|
|
954
|
+
};
|
|
955
|
+
const diagnostics = [];
|
|
956
|
+
const loaded = [];
|
|
957
|
+
for (const source of sources) {
|
|
958
|
+
let module;
|
|
959
|
+
try {
|
|
960
|
+
module = await loadModule(loader, source.path);
|
|
961
|
+
} catch (error) {
|
|
962
|
+
diagnostics.push({
|
|
963
|
+
code: "NOOH024",
|
|
964
|
+
file: source.path,
|
|
965
|
+
message: error instanceof Error ? ["Failed to load dependency module:", error.message].join(" ") : "Failed to load dependency module.",
|
|
966
|
+
severity: "error"
|
|
967
|
+
});
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
const result = collectReferences(source.path, module);
|
|
971
|
+
diagnostics.push(...result.diagnostics);
|
|
972
|
+
loaded.push(...result.references);
|
|
973
|
+
}
|
|
974
|
+
const references = /* @__PURE__ */ new Map();
|
|
975
|
+
for (const entry of loaded) references.set(entry.reference, entry.id);
|
|
976
|
+
const declarations = [];
|
|
977
|
+
for (const entry of loaded) {
|
|
978
|
+
const dependencies = [];
|
|
979
|
+
for (const dependency of entry.reference.dependencies) {
|
|
980
|
+
if (!isDependencyReference(dependency)) {
|
|
981
|
+
diagnostics.push({
|
|
982
|
+
code: "NOOH026",
|
|
983
|
+
file: entry.source,
|
|
984
|
+
message: [`Dependency "${entry.name}" contains`, "an invalid dependency reference."].join(" "),
|
|
985
|
+
severity: "error"
|
|
986
|
+
});
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const dependencyId = references.get(dependency);
|
|
990
|
+
if (!dependencyId) {
|
|
991
|
+
diagnostics.push({
|
|
992
|
+
code: "NOOH021",
|
|
993
|
+
file: entry.source,
|
|
994
|
+
message: [`Dependency "${entry.name}" references`, `an unknown dependency "${dependency.name}".`].join(" "),
|
|
995
|
+
severity: "error"
|
|
996
|
+
});
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
dependencies.push(dependencyId);
|
|
1000
|
+
}
|
|
1001
|
+
declarations.push({
|
|
1002
|
+
dependencies,
|
|
1003
|
+
id: entry.id,
|
|
1004
|
+
name: entry.name,
|
|
1005
|
+
scope: entry.scope,
|
|
1006
|
+
source: entry.source
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
const result = createDependencyGraph(declarations, references);
|
|
1010
|
+
return {
|
|
1011
|
+
diagnostics: [...diagnostics, ...result.diagnostics],
|
|
1012
|
+
graph: result.graph
|
|
1013
|
+
};
|
|
1014
|
+
};
|
|
1015
|
+
const dependencyClosure = (graph, roots) => {
|
|
1016
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1017
|
+
const result = [];
|
|
1018
|
+
const visit = (id) => {
|
|
1019
|
+
if (visited.has(id)) return;
|
|
1020
|
+
visited.add(id);
|
|
1021
|
+
const node = graph.nodes.get(id);
|
|
1022
|
+
if (!node) return;
|
|
1023
|
+
for (const dependencyId of node.declaration.dependencies) visit(dependencyId);
|
|
1024
|
+
result.push(id);
|
|
1025
|
+
};
|
|
1026
|
+
for (const root of roots) visit(root);
|
|
1027
|
+
return result;
|
|
1028
|
+
};
|
|
1029
|
+
//#endregion
|
|
1030
|
+
//#region src/introspect.ts
|
|
1031
|
+
const NOOH_ROUTE_METADATA = Symbol.for("nooh.route");
|
|
1032
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null;
|
|
1033
|
+
const isRouteMetadata = (value) => {
|
|
1034
|
+
if (!isRecord$1(value)) return false;
|
|
1035
|
+
return value.kind === "route" && Array.isArray(value.dependencies);
|
|
1036
|
+
};
|
|
1037
|
+
const readRouteMetadata = (value) => {
|
|
1038
|
+
if (!isRecord$1(value)) return null;
|
|
1039
|
+
const metadata = value[NOOH_ROUTE_METADATA];
|
|
1040
|
+
return isRouteMetadata(metadata) ? metadata : null;
|
|
1041
|
+
};
|
|
1042
|
+
const introspectRoute = async (route, loader) => {
|
|
1043
|
+
let exported;
|
|
1044
|
+
try {
|
|
1045
|
+
exported = await loader.loadDefault(route.source);
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
return { diagnostics: [{
|
|
1048
|
+
code: "NOOH031",
|
|
1049
|
+
file: route.source,
|
|
1050
|
+
message: error instanceof Error ? `Failed to introspect route: ${error.message}` : "Failed to introspect route.",
|
|
1051
|
+
severity: "error"
|
|
1052
|
+
}] };
|
|
1053
|
+
}
|
|
1054
|
+
const metadata = readRouteMetadata(exported);
|
|
1055
|
+
if (!metadata) return { diagnostics: [{
|
|
1056
|
+
code: "NOOH032",
|
|
1057
|
+
file: route.source,
|
|
1058
|
+
message: [
|
|
1059
|
+
"The route did not expose Nooh route metadata.",
|
|
1060
|
+
"",
|
|
1061
|
+
"Make sure the route is evaluated against",
|
|
1062
|
+
"the generated Nooh introspection router."
|
|
1063
|
+
].join("\n"),
|
|
1064
|
+
severity: "error"
|
|
1065
|
+
}] };
|
|
1066
|
+
return {
|
|
1067
|
+
diagnostics: [],
|
|
1068
|
+
metadata
|
|
1069
|
+
};
|
|
1070
|
+
};
|
|
1071
|
+
const unknownDependencyDiagnostic = (route, dependency) => ({
|
|
1072
|
+
code: "NOOH034",
|
|
1073
|
+
file: route.source,
|
|
1074
|
+
message: isRecord$1(dependency) && typeof dependency.name === "string" ? [`Route "${route.id}" references unknown dependency`, `"${dependency.name}".`].join(" ") : `Route "${route.id}" contains an invalid dependency reference.`,
|
|
1075
|
+
severity: "error"
|
|
1076
|
+
});
|
|
1077
|
+
const duplicateRouteDependencyNameDiagnostic = (route, name) => ({
|
|
1078
|
+
code: "NOOH035",
|
|
1079
|
+
file: route.source,
|
|
1080
|
+
message: [`Route "${route.id}" injects multiple dependencies`, `with the same name "${name}".`].join(" "),
|
|
1081
|
+
severity: "error"
|
|
1082
|
+
});
|
|
1083
|
+
const introspectCompilation = async (input) => {
|
|
1084
|
+
if (!input.compilation.plan) return {
|
|
1085
|
+
diagnostics: [{
|
|
1086
|
+
code: "NOOH033",
|
|
1087
|
+
message: "Cannot introspect a compilation without a compilation plan.",
|
|
1088
|
+
severity: "error"
|
|
1089
|
+
}],
|
|
1090
|
+
introspection: null
|
|
1091
|
+
};
|
|
1092
|
+
const graph = input.compilation.model.dependencies;
|
|
1093
|
+
const diagnostics = [];
|
|
1094
|
+
const routeDependencies = /* @__PURE__ */ new Map();
|
|
1095
|
+
for (const route of input.compilation.model.routes) {
|
|
1096
|
+
const result = await introspectRoute(route, input.loader);
|
|
1097
|
+
diagnostics.push(...result.diagnostics);
|
|
1098
|
+
if (!result.metadata) continue;
|
|
1099
|
+
const roots = [];
|
|
1100
|
+
const names = /* @__PURE__ */ new Set();
|
|
1101
|
+
for (const dependency of result.metadata.dependencies) {
|
|
1102
|
+
if (typeof dependency !== "object" || dependency === null) {
|
|
1103
|
+
diagnostics.push(unknownDependencyDiagnostic(route, dependency));
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
const id = graph.references.get(dependency);
|
|
1107
|
+
if (!id) {
|
|
1108
|
+
diagnostics.push(unknownDependencyDiagnostic(route, dependency));
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
const node = graph.nodes.get(id);
|
|
1112
|
+
if (!node) {
|
|
1113
|
+
diagnostics.push({
|
|
1114
|
+
code: "NOOH034",
|
|
1115
|
+
file: route.source,
|
|
1116
|
+
message: [`Route "${route.id}" references dependency`, `"${id}" that is missing from the dependency graph.`].join(" "),
|
|
1117
|
+
severity: "error"
|
|
1118
|
+
});
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
const { name } = node.declaration;
|
|
1122
|
+
if (names.has(name)) {
|
|
1123
|
+
diagnostics.push(duplicateRouteDependencyNameDiagnostic(route, name));
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
names.add(name);
|
|
1127
|
+
roots.push(id);
|
|
1128
|
+
}
|
|
1129
|
+
const closure = dependencyClosure(graph, roots);
|
|
1130
|
+
routeDependencies.set(route.id, {
|
|
1131
|
+
closure,
|
|
1132
|
+
roots
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
|
|
1136
|
+
diagnostics,
|
|
1137
|
+
introspection: null
|
|
1138
|
+
};
|
|
1139
|
+
return {
|
|
1140
|
+
diagnostics,
|
|
1141
|
+
introspection: {
|
|
1142
|
+
dependencies: graph,
|
|
1143
|
+
routeDependencies: [...routeDependencies.entries()].map(([routeId, value]) => ({
|
|
1144
|
+
closure: value.closure,
|
|
1145
|
+
roots: value.roots,
|
|
1146
|
+
routeId
|
|
1147
|
+
})).sort((a, b) => a.routeId.localeCompare(b.routeId))
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
};
|
|
349
1151
|
//#endregion
|
|
350
1152
|
//#region src/pipeline/analyze.ts
|
|
1153
|
+
const emptyDependencyGraph$1 = () => ({
|
|
1154
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
1155
|
+
order: [],
|
|
1156
|
+
references: /* @__PURE__ */ new Map()
|
|
1157
|
+
});
|
|
351
1158
|
const segmentToHono = (segment) => {
|
|
352
1159
|
switch (segment.kind) {
|
|
353
1160
|
case "static": return segment.value;
|
|
@@ -447,7 +1254,7 @@ const buildGroups = (parsed, routeModels, diagnostics) => {
|
|
|
447
1254
|
};
|
|
448
1255
|
});
|
|
449
1256
|
};
|
|
450
|
-
const analyze = (parsed, config) => {
|
|
1257
|
+
const analyze = (parsed, config, dependencies = emptyDependencyGraph$1()) => {
|
|
451
1258
|
const diagnostics = [...parsed.diagnostics];
|
|
452
1259
|
const routeModels = [];
|
|
453
1260
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -487,16 +1294,20 @@ const analyze = (parsed, config) => {
|
|
|
487
1294
|
diagnostics,
|
|
488
1295
|
model: {
|
|
489
1296
|
config,
|
|
1297
|
+
dependencies,
|
|
490
1298
|
groups: buildGroups(parsed, routeModels, diagnostics),
|
|
1299
|
+
routeDependencies: [],
|
|
491
1300
|
routes: routeModels
|
|
492
1301
|
}
|
|
493
1302
|
};
|
|
494
1303
|
};
|
|
495
1304
|
//#endregion
|
|
496
1305
|
//#region src/pipeline/config.ts
|
|
1306
|
+
const DEFAULT_DEPENDENCIES_ROOT = "src/deps";
|
|
497
1307
|
const DEFAULT_ROUTES_ROOT = "src/routes";
|
|
498
1308
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
499
1309
|
const isString = (value) => typeof value === "string";
|
|
1310
|
+
const isFunction = (value) => typeof value === "function";
|
|
500
1311
|
const loadConfig = async (input) => {
|
|
501
1312
|
let defaultExport;
|
|
502
1313
|
try {
|
|
@@ -522,65 +1333,95 @@ const loadConfig = async (input) => {
|
|
|
522
1333
|
message: "The Nooh config \"routes\" option must be a string.",
|
|
523
1334
|
severity: "error"
|
|
524
1335
|
}] };
|
|
1336
|
+
const dependenciesValue = defaultExport.dependencies;
|
|
1337
|
+
if (dependenciesValue !== void 0 && !isString(dependenciesValue)) return { diagnostics: [{
|
|
1338
|
+
code: "NOOH013",
|
|
1339
|
+
file: input.config,
|
|
1340
|
+
message: "The Nooh config \"dependencies\" option must be a string.",
|
|
1341
|
+
severity: "error"
|
|
1342
|
+
}] };
|
|
1343
|
+
const validatorValue = defaultExport.validator;
|
|
1344
|
+
if (validatorValue !== void 0 && !isRecord(validatorValue)) return { diagnostics: [{
|
|
1345
|
+
code: "NOOH014",
|
|
1346
|
+
file: input.config,
|
|
1347
|
+
message: "The Nooh config \"validator\" option must be an object.",
|
|
1348
|
+
severity: "error"
|
|
1349
|
+
}] };
|
|
1350
|
+
if (isRecord(validatorValue) && validatorValue.engine !== void 0 && !isFunction(validatorValue.engine)) return { diagnostics: [{
|
|
1351
|
+
code: "NOOH015",
|
|
1352
|
+
file: input.config,
|
|
1353
|
+
message: "The Nooh config \"validator.engine\" option must be a function.",
|
|
1354
|
+
severity: "error"
|
|
1355
|
+
}] };
|
|
525
1356
|
const root = normalizePath(input.root ?? "");
|
|
526
1357
|
const source = toProjectPath(input.config, root);
|
|
1358
|
+
const routes = routesValue ?? DEFAULT_ROUTES_ROOT;
|
|
1359
|
+
const dependencies = dependenciesValue ?? DEFAULT_DEPENDENCIES_ROOT;
|
|
1360
|
+
const routesRoot = toProjectPath(routes, root);
|
|
527
1361
|
return {
|
|
528
1362
|
config: {
|
|
1363
|
+
dependenciesRoot: toProjectPath(dependencies, root),
|
|
529
1364
|
root,
|
|
530
|
-
routesRoot
|
|
1365
|
+
routesRoot,
|
|
531
1366
|
source,
|
|
532
|
-
value: {
|
|
1367
|
+
value: {
|
|
1368
|
+
dependencies: dependenciesValue,
|
|
1369
|
+
routes: routesValue
|
|
1370
|
+
}
|
|
533
1371
|
},
|
|
534
1372
|
diagnostics: []
|
|
535
1373
|
};
|
|
536
1374
|
};
|
|
537
1375
|
//#endregion
|
|
538
1376
|
//#region src/pipeline/discover.ts
|
|
1377
|
+
const ROUTE_FILE_PATTERN = /\.(get|post|put|patch|delete|options|head|all)\.(?:ts|tsx)$/;
|
|
539
1378
|
const isTypeScriptFile = (file) => file.path.endsWith(".ts") || file.path.endsWith(".tsx");
|
|
1379
|
+
const isRouteFile = (filePath) => ROUTE_FILE_PATTERN.test(filePath);
|
|
540
1380
|
const isGroupFile = (filePath) => {
|
|
541
|
-
const filename = filePath
|
|
1381
|
+
const filename = basename(filePath);
|
|
542
1382
|
return filename === "$.ts" || filename === "$.tsx";
|
|
543
1383
|
};
|
|
1384
|
+
const discoverDependency = (config, file) => {
|
|
1385
|
+
const source = toProjectPath(file.path, config.root);
|
|
1386
|
+
if (!isPathInside(source, config.dependenciesRoot)) return null;
|
|
1387
|
+
return {
|
|
1388
|
+
content: file.content,
|
|
1389
|
+
path: source
|
|
1390
|
+
};
|
|
1391
|
+
};
|
|
544
1392
|
const discoverGroup = (config, file) => {
|
|
545
1393
|
const source = toProjectPath(file.path, config.root);
|
|
546
1394
|
if (!isPathInside(source, config.routesRoot)) return null;
|
|
547
|
-
const parts = relativePath(config.routesRoot, source).split("/").filter(Boolean);
|
|
548
1395
|
if (!isGroupFile(source)) return null;
|
|
549
|
-
const
|
|
550
|
-
|
|
1396
|
+
const relative = relativePath(config.routesRoot, source);
|
|
1397
|
+
const directory = dirname(relative);
|
|
551
1398
|
return {
|
|
552
|
-
groupPath: normalizePath(
|
|
1399
|
+
groupPath: normalizePath(directory),
|
|
553
1400
|
source
|
|
554
1401
|
};
|
|
555
1402
|
};
|
|
556
1403
|
const discoverEndpoint = (config, file) => {
|
|
557
1404
|
const source = toProjectPath(file.path, config.root);
|
|
558
1405
|
if (!isPathInside(source, config.routesRoot)) return null;
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
if (endpointIndex === -1) return null;
|
|
562
|
-
const endpointsRoot = normalizePath([
|
|
563
|
-
config.routesRoot,
|
|
564
|
-
...parts.slice(0, endpointIndex),
|
|
565
|
-
"endpoints"
|
|
566
|
-
].join("/"));
|
|
567
|
-
const groupParts = parts.slice(0, endpointIndex);
|
|
568
|
-
const localParts = parts.slice(endpointIndex + 1);
|
|
569
|
-
if (localParts.length === 0) return null;
|
|
1406
|
+
if (!isRouteFile(source)) return null;
|
|
1407
|
+
const relative = relativePath(config.routesRoot, source);
|
|
570
1408
|
return {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
localPath: normalizePath(localParts.join("/")),
|
|
1409
|
+
groupPath: normalizePath(dirname(relative)),
|
|
1410
|
+
localPath: normalizePath(basename(relative)),
|
|
574
1411
|
source
|
|
575
1412
|
};
|
|
576
1413
|
};
|
|
577
1414
|
const discover = (snapshot, config) => {
|
|
578
1415
|
const endpoints = [];
|
|
579
1416
|
const groups = [];
|
|
1417
|
+
const dependencies = [];
|
|
580
1418
|
for (const file of snapshot.files) {
|
|
581
1419
|
if (!isTypeScriptFile(file)) continue;
|
|
582
|
-
const
|
|
583
|
-
if (
|
|
1420
|
+
const dependency = discoverDependency(config, file);
|
|
1421
|
+
if (dependency) {
|
|
1422
|
+
dependencies.push(dependency);
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
584
1425
|
const group = discoverGroup(config, file);
|
|
585
1426
|
if (group) {
|
|
586
1427
|
groups.push(group);
|
|
@@ -589,10 +1430,12 @@ const discover = (snapshot, config) => {
|
|
|
589
1430
|
const endpoint = discoverEndpoint(config, file);
|
|
590
1431
|
if (endpoint) endpoints.push(endpoint);
|
|
591
1432
|
}
|
|
1433
|
+
dependencies.sort((a, b) => a.path.localeCompare(b.path));
|
|
592
1434
|
endpoints.sort((a, b) => a.source.localeCompare(b.source));
|
|
593
1435
|
groups.sort((a, b) => a.source.localeCompare(b.source));
|
|
594
1436
|
return {
|
|
595
1437
|
config,
|
|
1438
|
+
dependencies,
|
|
596
1439
|
endpoints,
|
|
597
1440
|
groups
|
|
598
1441
|
};
|
|
@@ -721,13 +1564,24 @@ const DEFAULT_OUTPUT_ROOT = ".nooh";
|
|
|
721
1564
|
const modulePath = (outputRoot, value) => normalizePath(`${outputRoot}/${value}.ts`);
|
|
722
1565
|
const plan = (model, outputRoot = DEFAULT_OUTPUT_ROOT) => {
|
|
723
1566
|
const normalizedOutputRoot = normalizePath(outputRoot);
|
|
724
|
-
const modules = [
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1567
|
+
const modules = [
|
|
1568
|
+
{
|
|
1569
|
+
id: modulePath(normalizedOutputRoot, "types"),
|
|
1570
|
+
kind: "types"
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
id: modulePath(normalizedOutputRoot, "router/di"),
|
|
1574
|
+
kind: "di"
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
id: modulePath(normalizedOutputRoot, "router/middleware"),
|
|
1578
|
+
kind: "middleware"
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
id: modulePath(normalizedOutputRoot, "error"),
|
|
1582
|
+
kind: "error"
|
|
1583
|
+
}
|
|
1584
|
+
];
|
|
731
1585
|
const routerPaths = [...new Set(model.routes.map((route) => route.routerPath))].sort();
|
|
732
1586
|
for (const routerPath of routerPaths) {
|
|
733
1587
|
const route = model.routes.find((candidate) => candidate.routerPath === routerPath);
|
|
@@ -752,12 +1606,18 @@ const plan = (model, outputRoot = DEFAULT_OUTPUT_ROOT) => {
|
|
|
752
1606
|
});
|
|
753
1607
|
modules.sort((a, b) => a.id.localeCompare(b.id));
|
|
754
1608
|
return {
|
|
1609
|
+
dependencies: model.dependencies,
|
|
755
1610
|
modules,
|
|
756
1611
|
outputRoot: normalizedOutputRoot
|
|
757
1612
|
};
|
|
758
1613
|
};
|
|
759
1614
|
//#endregion
|
|
760
1615
|
//#region src/compiler.ts
|
|
1616
|
+
const emptyDependencyGraph = {
|
|
1617
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
1618
|
+
order: [],
|
|
1619
|
+
references: /* @__PURE__ */ new Map()
|
|
1620
|
+
};
|
|
761
1621
|
const createCompiler = () => ({
|
|
762
1622
|
analyze,
|
|
763
1623
|
compile: async (input) => {
|
|
@@ -766,12 +1626,15 @@ const createCompiler = () => ({
|
|
|
766
1626
|
diagnostics: configResult.diagnostics,
|
|
767
1627
|
model: {
|
|
768
1628
|
config: {
|
|
1629
|
+
dependenciesRoot: "",
|
|
769
1630
|
root: input.root ? input.root.replaceAll("\\", "/") : "",
|
|
770
1631
|
routesRoot: "",
|
|
771
1632
|
source: input.config,
|
|
772
1633
|
value: {}
|
|
773
1634
|
},
|
|
1635
|
+
dependencies: emptyDependencyGraph,
|
|
774
1636
|
groups: [],
|
|
1637
|
+
routeDependencies: [],
|
|
775
1638
|
routes: []
|
|
776
1639
|
},
|
|
777
1640
|
output: null,
|
|
@@ -779,8 +1642,13 @@ const createCompiler = () => ({
|
|
|
779
1642
|
};
|
|
780
1643
|
const discovered = discover(input.sources, configResult.config);
|
|
781
1644
|
const parsed = parse(discovered);
|
|
782
|
-
const
|
|
783
|
-
const
|
|
1645
|
+
const dependencyResult = await loadDependencyGraph(discovered.dependencies, input.loader);
|
|
1646
|
+
const analyzed = analyze(parsed, configResult.config, dependencyResult.graph);
|
|
1647
|
+
const diagnostics = [
|
|
1648
|
+
...configResult.diagnostics,
|
|
1649
|
+
...dependencyResult.diagnostics,
|
|
1650
|
+
...analyzed.diagnostics
|
|
1651
|
+
];
|
|
784
1652
|
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
|
|
785
1653
|
diagnostics,
|
|
786
1654
|
model: analyzed.model,
|
|
@@ -788,7 +1656,7 @@ const createCompiler = () => ({
|
|
|
788
1656
|
plan: null
|
|
789
1657
|
};
|
|
790
1658
|
const compilationPlan = plan(analyzed.model, input.options?.outputRoot);
|
|
791
|
-
const output =
|
|
1659
|
+
const output = generateRouteIntrospection(compilationPlan, analyzed.model);
|
|
792
1660
|
return {
|
|
793
1661
|
diagnostics,
|
|
794
1662
|
model: analyzed.model,
|
|
@@ -798,10 +1666,12 @@ const createCompiler = () => ({
|
|
|
798
1666
|
},
|
|
799
1667
|
discover,
|
|
800
1668
|
generate,
|
|
1669
|
+
generateRouteIntrospection,
|
|
801
1670
|
loadConfig,
|
|
802
1671
|
parse,
|
|
803
1672
|
plan
|
|
804
1673
|
});
|
|
1674
|
+
const introspect = (input) => introspectCompilation(input);
|
|
805
1675
|
//#endregion
|
|
806
1676
|
//#region src/compile.ts
|
|
807
1677
|
const compile = (input) => createCompiler().compile(input);
|
|
@@ -835,6 +1705,26 @@ const diff = (previous, next) => {
|
|
|
835
1705
|
};
|
|
836
1706
|
};
|
|
837
1707
|
//#endregion
|
|
1708
|
+
//#region src/finalize.ts
|
|
1709
|
+
const finalizeCompilation = (compilation, introspection) => {
|
|
1710
|
+
if (!(compilation.plan && compilation.output)) return compilation;
|
|
1711
|
+
const model = {
|
|
1712
|
+
...compilation.model,
|
|
1713
|
+
dependencies: introspection.dependencies,
|
|
1714
|
+
routeDependencies: introspection.routeDependencies
|
|
1715
|
+
};
|
|
1716
|
+
const plan = {
|
|
1717
|
+
...compilation.plan,
|
|
1718
|
+
dependencies: introspection.dependencies,
|
|
1719
|
+
routeDependencies: introspection.routeDependencies
|
|
1720
|
+
};
|
|
1721
|
+
return {
|
|
1722
|
+
...compilation,
|
|
1723
|
+
model,
|
|
1724
|
+
plan
|
|
1725
|
+
};
|
|
1726
|
+
};
|
|
1727
|
+
//#endregion
|
|
838
1728
|
//#region src/recompile.ts
|
|
839
1729
|
const recompile = (input) => {
|
|
840
1730
|
const compiler = createCompiler();
|
|
@@ -848,6 +1738,6 @@ const recompile = (input) => {
|
|
|
848
1738
|
});
|
|
849
1739
|
};
|
|
850
1740
|
//#endregion
|
|
851
|
-
export { analyze, compile, createCompiler, diff, discover, generate, loadConfig, parse, plan, recompile };
|
|
1741
|
+
export { NOOH_ROUTE_METADATA, analyze, compile, createCompiler, createDependencyGraph, dependencyClosure, diff, discover, finalizeCompilation, generate, generateRouteIntrospection, introspect, introspectCompilation, introspectRoute, loadConfig, loadDependencyGraph, parse, plan, readRouteMetadata, recompile };
|
|
852
1742
|
|
|
853
1743
|
//# sourceMappingURL=index.mjs.map
|