@nooh-ts/compiler 0.1.1 → 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 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +980 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -86,18 +86,29 @@ const isPathInside = (file, root) => {
|
|
|
86
86
|
const generateAppModule = (plan, model) => {
|
|
87
87
|
const moduleId = `${plan.outputRoot}/app.ts`;
|
|
88
88
|
const typesModuleId = `${plan.outputRoot}/types.ts`;
|
|
89
|
+
const errorModuleId = `${plan.outputRoot}/error.ts`;
|
|
89
90
|
const root = model.groups.find((group) => group.id === "root");
|
|
90
91
|
if (!root) throw new Error("Nooh compilation requires a root route group.");
|
|
91
92
|
return {
|
|
92
93
|
code: [
|
|
93
94
|
...[
|
|
94
95
|
`import { Hono } from "hono";`,
|
|
96
|
+
`import config from ${JSON.stringify(relativeModuleSpecifier(moduleId, model.config.source))};`,
|
|
95
97
|
`import type { App } from ${JSON.stringify(relativeModuleSpecifier(moduleId, typesModuleId))};`,
|
|
98
|
+
`import { defaultErrorHandler } from ${JSON.stringify(relativeModuleSpecifier(moduleId, errorModuleId))};`,
|
|
96
99
|
`import root from ${JSON.stringify(relativeModuleSpecifier(moduleId, groupModuleId(plan, root.id)))};`
|
|
97
100
|
],
|
|
98
101
|
"",
|
|
99
102
|
"const app = new Hono<App>();",
|
|
100
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
|
+
"",
|
|
101
112
|
"app.route(\"/\", root);",
|
|
102
113
|
"",
|
|
103
114
|
"export type AppType = typeof app;",
|
|
@@ -110,6 +121,175 @@ const generateAppModule = (plan, model) => {
|
|
|
110
121
|
};
|
|
111
122
|
};
|
|
112
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
|
|
113
293
|
//#region src/generate/group.ts
|
|
114
294
|
const getGroupRoutes = (model, group) => {
|
|
115
295
|
const ids = new Set(group.routes);
|
|
@@ -162,10 +342,32 @@ const generateGroupModule = (plan, model, group) => {
|
|
|
162
342
|
"",
|
|
163
343
|
"use(\"*\", ...(groupConfig.middleware ?? []));"
|
|
164
344
|
] : [];
|
|
165
|
-
const
|
|
166
|
-
|
|
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");
|
|
167
363
|
});
|
|
168
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
|
+
] : [];
|
|
169
371
|
return {
|
|
170
372
|
code: [
|
|
171
373
|
...imports,
|
|
@@ -176,7 +378,8 @@ const generateGroupModule = (plan, model, group) => {
|
|
|
176
378
|
"",
|
|
177
379
|
...registerDeclarations,
|
|
178
380
|
...useDeclaration.length > 0 ? ["", ...useDeclaration] : [],
|
|
179
|
-
...
|
|
381
|
+
...groupErrorHandler,
|
|
382
|
+
...routeRegistrations.length > 0 ? ["", ...routeRegistrations] : [],
|
|
180
383
|
...childRegistrations.length > 0 ? ["", ...childRegistrations] : [],
|
|
181
384
|
"",
|
|
182
385
|
"export default route;",
|
|
@@ -204,14 +407,6 @@ const generateMiddlewareModule = (plan) => {
|
|
|
204
407
|
};
|
|
205
408
|
//#endregion
|
|
206
409
|
//#region src/generate/router.ts
|
|
207
|
-
const VALIDATION_TARGETS = [
|
|
208
|
-
"json",
|
|
209
|
-
"form",
|
|
210
|
-
"query",
|
|
211
|
-
"param",
|
|
212
|
-
"header",
|
|
213
|
-
"cookie"
|
|
214
|
-
];
|
|
215
410
|
const METHOD_FUNCTION_NAMES = {
|
|
216
411
|
all: "all",
|
|
217
412
|
delete: "del",
|
|
@@ -223,96 +418,316 @@ const METHOD_FUNCTION_NAMES = {
|
|
|
223
418
|
put: "put"
|
|
224
419
|
};
|
|
225
420
|
const getRoutesForRouter = (model, routerPath) => model.routes.filter((route) => route.routerPath === routerPath).sort((a, b) => a.method.localeCompare(b.method));
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
return [
|
|
230
|
-
|
|
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};`,
|
|
231
444
|
"",
|
|
232
|
-
|
|
445
|
+
`type ${prefix}RouteMiddleware = MiddlewareHandler<App, ${prefix}Path>;`,
|
|
233
446
|
"",
|
|
234
|
-
|
|
235
|
-
"type StandardSchema = Parameters<typeof sValidator>[1];",
|
|
447
|
+
`type ${prefix}RouteHandler = Handler<App, ${prefix}Path, any, any>;`,
|
|
236
448
|
"",
|
|
237
|
-
|
|
238
|
-
"
|
|
449
|
+
`type ${prefix}ValidationSchemaMap = Record<`,
|
|
450
|
+
" NoohRequestValidationTarget,",
|
|
451
|
+
" NoohStandardSchema",
|
|
239
452
|
">;",
|
|
240
453
|
"",
|
|
241
|
-
|
|
242
|
-
"
|
|
243
|
-
"
|
|
244
|
-
" infer I,",
|
|
245
|
-
" any",
|
|
246
|
-
"> ? I : never;",
|
|
454
|
+
`type ${prefix}ValidationOptions = Partial<${prefix}ValidationSchemaMap> & {`,
|
|
455
|
+
" readonly response?: NoohStandardSchema;",
|
|
456
|
+
"};",
|
|
247
457
|
"",
|
|
248
|
-
|
|
249
|
-
" Target extends
|
|
250
|
-
" Schema extends
|
|
251
|
-
"> =
|
|
252
|
-
"
|
|
253
|
-
"
|
|
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
|
+
" };",
|
|
254
478
|
"",
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"
|
|
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]>;",
|
|
258
490
|
"",
|
|
259
|
-
|
|
491
|
+
`type ${prefix}ResponseSchema<V extends ${prefix}ValidationOptions> =`,
|
|
492
|
+
" V extends {",
|
|
493
|
+
" readonly response: infer Schema extends NoohStandardSchema;",
|
|
494
|
+
" }",
|
|
495
|
+
" ? Schema",
|
|
496
|
+
" : never;",
|
|
497
|
+
"",
|
|
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>;`,
|
|
260
517
|
"",
|
|
261
|
-
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
|
|
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 = {}",
|
|
265
559
|
"> = {",
|
|
266
560
|
" readonly middleware?: M;",
|
|
267
561
|
" readonly validation?: V;",
|
|
268
|
-
" 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>;`,
|
|
269
566
|
"};",
|
|
270
567
|
"",
|
|
271
|
-
`export function ${functionName}
|
|
272
|
-
|
|
273
|
-
|
|
568
|
+
`export function ${functionName}(`,
|
|
569
|
+
` handler: ${prefix}RouteHandler`,
|
|
570
|
+
`): ${prefix}RouteHandlers;`,
|
|
274
571
|
"",
|
|
275
572
|
`export function ${functionName}<`,
|
|
276
|
-
"
|
|
277
|
-
|
|
278
|
-
|
|
573
|
+
" const D extends readonly RouteDependency[] = [],",
|
|
574
|
+
` const V extends ${prefix}ValidationOptions = {},`,
|
|
575
|
+
` const M extends readonly ${prefix}RouteMiddleware[] = [],`,
|
|
576
|
+
" const E extends ErrorDefinitions = {}",
|
|
279
577
|
">(",
|
|
280
|
-
|
|
281
|
-
|
|
578
|
+
` options: ${prefix}EndpointOptions<D, V, M, E>`,
|
|
579
|
+
`): ${prefix}RouteHandlers;`,
|
|
282
580
|
"",
|
|
283
|
-
`export function ${functionName}
|
|
284
|
-
" M extends readonly RouteMiddleware[],",
|
|
285
|
-
" V extends ValidationOptions,",
|
|
286
|
-
" H extends Handler<App, Path, ValidationInput<V>>,",
|
|
287
|
-
">(",
|
|
581
|
+
`export function ${functionName}(`,
|
|
288
582
|
" input:",
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
583
|
+
` | ${prefix}RouteHandler`,
|
|
584
|
+
` | ${prefix}EndpointOptions`,
|
|
585
|
+
`): ${prefix}RouteHandlers {`
|
|
586
|
+
];
|
|
587
|
+
if (mode === "introspection") return [
|
|
588
|
+
...common,
|
|
589
|
+
"",
|
|
590
|
+
" if (typeof input === \"function\") {",
|
|
591
|
+
" return defineRouteMetadata(",
|
|
592
|
+
" [input],",
|
|
593
|
+
" [],",
|
|
594
|
+
` ) as unknown as ${prefix}RouteHandlers;`,
|
|
595
|
+
" }",
|
|
596
|
+
"",
|
|
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
|
+
"",
|
|
292
609
|
" if (typeof input === \"function\") {",
|
|
293
|
-
|
|
610
|
+
` return defineRouteHandlers<${prefix}Path>([input]);`,
|
|
294
611
|
" }",
|
|
295
612
|
"",
|
|
296
|
-
"
|
|
297
|
-
"
|
|
298
|
-
|
|
299
|
-
" input.
|
|
300
|
-
"
|
|
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);`,
|
|
301
661
|
"}",
|
|
302
662
|
""
|
|
303
663
|
].join("\n");
|
|
304
664
|
};
|
|
305
|
-
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") => {
|
|
306
688
|
const routes = getRoutesForRouter(model, routerPath);
|
|
307
689
|
const moduleId = `${plan.outputRoot}/${routerPath}.ts`;
|
|
308
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;", "};");
|
|
309
725
|
return {
|
|
310
726
|
code: [
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
`import type { App } from ${JSON.stringify(relativeModuleSpecifier(moduleId, typesModuleId))};`,
|
|
727
|
+
...imports,
|
|
728
|
+
...prelude,
|
|
314
729
|
"",
|
|
315
|
-
...routes.map(renderMethod)
|
|
730
|
+
...routes.map((route) => renderMethod(model, route, mode))
|
|
316
731
|
].join("\n"),
|
|
317
732
|
id: moduleId,
|
|
318
733
|
kind: "router"
|
|
@@ -341,19 +756,405 @@ const generateTypesModule = (plan, config) => {
|
|
|
341
756
|
};
|
|
342
757
|
//#endregion
|
|
343
758
|
//#region src/generate/index.ts
|
|
759
|
+
const getRouterPaths = (model) => [...new Set(model.routes.map((route) => route.routerPath))].sort();
|
|
344
760
|
const generate = (plan, model) => {
|
|
345
761
|
const modules = [];
|
|
346
762
|
modules.push(generateTypesModule(plan, model.config));
|
|
763
|
+
modules.push(generateDependencyModule(plan));
|
|
764
|
+
modules.push(generateErrorModule(plan));
|
|
347
765
|
modules.push(generateMiddlewareModule(plan));
|
|
348
|
-
const
|
|
349
|
-
for (const routerPath of routerPaths) modules.push(generateRouterModule(plan, model, routerPath));
|
|
766
|
+
for (const routerPath of getRouterPaths(model)) modules.push(generateRouterModule(plan, model, routerPath));
|
|
350
767
|
for (const group of model.groups) modules.push(generateGroupModule(plan, model, group));
|
|
351
768
|
modules.push(generateAppModule(plan, model));
|
|
352
769
|
modules.sort((a, b) => a.id.localeCompare(b.id));
|
|
353
770
|
return { modules };
|
|
354
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
|
+
};
|
|
355
1151
|
//#endregion
|
|
356
1152
|
//#region src/pipeline/analyze.ts
|
|
1153
|
+
const emptyDependencyGraph$1 = () => ({
|
|
1154
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
1155
|
+
order: [],
|
|
1156
|
+
references: /* @__PURE__ */ new Map()
|
|
1157
|
+
});
|
|
357
1158
|
const segmentToHono = (segment) => {
|
|
358
1159
|
switch (segment.kind) {
|
|
359
1160
|
case "static": return segment.value;
|
|
@@ -453,7 +1254,7 @@ const buildGroups = (parsed, routeModels, diagnostics) => {
|
|
|
453
1254
|
};
|
|
454
1255
|
});
|
|
455
1256
|
};
|
|
456
|
-
const analyze = (parsed, config) => {
|
|
1257
|
+
const analyze = (parsed, config, dependencies = emptyDependencyGraph$1()) => {
|
|
457
1258
|
const diagnostics = [...parsed.diagnostics];
|
|
458
1259
|
const routeModels = [];
|
|
459
1260
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -493,16 +1294,20 @@ const analyze = (parsed, config) => {
|
|
|
493
1294
|
diagnostics,
|
|
494
1295
|
model: {
|
|
495
1296
|
config,
|
|
1297
|
+
dependencies,
|
|
496
1298
|
groups: buildGroups(parsed, routeModels, diagnostics),
|
|
1299
|
+
routeDependencies: [],
|
|
497
1300
|
routes: routeModels
|
|
498
1301
|
}
|
|
499
1302
|
};
|
|
500
1303
|
};
|
|
501
1304
|
//#endregion
|
|
502
1305
|
//#region src/pipeline/config.ts
|
|
1306
|
+
const DEFAULT_DEPENDENCIES_ROOT = "src/deps";
|
|
503
1307
|
const DEFAULT_ROUTES_ROOT = "src/routes";
|
|
504
1308
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
505
1309
|
const isString = (value) => typeof value === "string";
|
|
1310
|
+
const isFunction = (value) => typeof value === "function";
|
|
506
1311
|
const loadConfig = async (input) => {
|
|
507
1312
|
let defaultExport;
|
|
508
1313
|
try {
|
|
@@ -528,14 +1333,41 @@ const loadConfig = async (input) => {
|
|
|
528
1333
|
message: "The Nooh config \"routes\" option must be a string.",
|
|
529
1334
|
severity: "error"
|
|
530
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
|
+
}] };
|
|
531
1356
|
const root = normalizePath(input.root ?? "");
|
|
532
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);
|
|
533
1361
|
return {
|
|
534
1362
|
config: {
|
|
1363
|
+
dependenciesRoot: toProjectPath(dependencies, root),
|
|
535
1364
|
root,
|
|
536
|
-
routesRoot
|
|
1365
|
+
routesRoot,
|
|
537
1366
|
source,
|
|
538
|
-
value: {
|
|
1367
|
+
value: {
|
|
1368
|
+
dependencies: dependenciesValue,
|
|
1369
|
+
routes: routesValue
|
|
1370
|
+
}
|
|
539
1371
|
},
|
|
540
1372
|
diagnostics: []
|
|
541
1373
|
};
|
|
@@ -549,6 +1381,14 @@ const isGroupFile = (filePath) => {
|
|
|
549
1381
|
const filename = basename(filePath);
|
|
550
1382
|
return filename === "$.ts" || filename === "$.tsx";
|
|
551
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
|
+
};
|
|
552
1392
|
const discoverGroup = (config, file) => {
|
|
553
1393
|
const source = toProjectPath(file.path, config.root);
|
|
554
1394
|
if (!isPathInside(source, config.routesRoot)) return null;
|
|
@@ -574,8 +1414,14 @@ const discoverEndpoint = (config, file) => {
|
|
|
574
1414
|
const discover = (snapshot, config) => {
|
|
575
1415
|
const endpoints = [];
|
|
576
1416
|
const groups = [];
|
|
1417
|
+
const dependencies = [];
|
|
577
1418
|
for (const file of snapshot.files) {
|
|
578
1419
|
if (!isTypeScriptFile(file)) continue;
|
|
1420
|
+
const dependency = discoverDependency(config, file);
|
|
1421
|
+
if (dependency) {
|
|
1422
|
+
dependencies.push(dependency);
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
579
1425
|
const group = discoverGroup(config, file);
|
|
580
1426
|
if (group) {
|
|
581
1427
|
groups.push(group);
|
|
@@ -584,10 +1430,12 @@ const discover = (snapshot, config) => {
|
|
|
584
1430
|
const endpoint = discoverEndpoint(config, file);
|
|
585
1431
|
if (endpoint) endpoints.push(endpoint);
|
|
586
1432
|
}
|
|
1433
|
+
dependencies.sort((a, b) => a.path.localeCompare(b.path));
|
|
587
1434
|
endpoints.sort((a, b) => a.source.localeCompare(b.source));
|
|
588
1435
|
groups.sort((a, b) => a.source.localeCompare(b.source));
|
|
589
1436
|
return {
|
|
590
1437
|
config,
|
|
1438
|
+
dependencies,
|
|
591
1439
|
endpoints,
|
|
592
1440
|
groups
|
|
593
1441
|
};
|
|
@@ -716,13 +1564,24 @@ const DEFAULT_OUTPUT_ROOT = ".nooh";
|
|
|
716
1564
|
const modulePath = (outputRoot, value) => normalizePath(`${outputRoot}/${value}.ts`);
|
|
717
1565
|
const plan = (model, outputRoot = DEFAULT_OUTPUT_ROOT) => {
|
|
718
1566
|
const normalizedOutputRoot = normalizePath(outputRoot);
|
|
719
|
-
const modules = [
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
+
];
|
|
726
1585
|
const routerPaths = [...new Set(model.routes.map((route) => route.routerPath))].sort();
|
|
727
1586
|
for (const routerPath of routerPaths) {
|
|
728
1587
|
const route = model.routes.find((candidate) => candidate.routerPath === routerPath);
|
|
@@ -747,12 +1606,18 @@ const plan = (model, outputRoot = DEFAULT_OUTPUT_ROOT) => {
|
|
|
747
1606
|
});
|
|
748
1607
|
modules.sort((a, b) => a.id.localeCompare(b.id));
|
|
749
1608
|
return {
|
|
1609
|
+
dependencies: model.dependencies,
|
|
750
1610
|
modules,
|
|
751
1611
|
outputRoot: normalizedOutputRoot
|
|
752
1612
|
};
|
|
753
1613
|
};
|
|
754
1614
|
//#endregion
|
|
755
1615
|
//#region src/compiler.ts
|
|
1616
|
+
const emptyDependencyGraph = {
|
|
1617
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
1618
|
+
order: [],
|
|
1619
|
+
references: /* @__PURE__ */ new Map()
|
|
1620
|
+
};
|
|
756
1621
|
const createCompiler = () => ({
|
|
757
1622
|
analyze,
|
|
758
1623
|
compile: async (input) => {
|
|
@@ -761,12 +1626,15 @@ const createCompiler = () => ({
|
|
|
761
1626
|
diagnostics: configResult.diagnostics,
|
|
762
1627
|
model: {
|
|
763
1628
|
config: {
|
|
1629
|
+
dependenciesRoot: "",
|
|
764
1630
|
root: input.root ? input.root.replaceAll("\\", "/") : "",
|
|
765
1631
|
routesRoot: "",
|
|
766
1632
|
source: input.config,
|
|
767
1633
|
value: {}
|
|
768
1634
|
},
|
|
1635
|
+
dependencies: emptyDependencyGraph,
|
|
769
1636
|
groups: [],
|
|
1637
|
+
routeDependencies: [],
|
|
770
1638
|
routes: []
|
|
771
1639
|
},
|
|
772
1640
|
output: null,
|
|
@@ -774,8 +1642,13 @@ const createCompiler = () => ({
|
|
|
774
1642
|
};
|
|
775
1643
|
const discovered = discover(input.sources, configResult.config);
|
|
776
1644
|
const parsed = parse(discovered);
|
|
777
|
-
const
|
|
778
|
-
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
|
+
];
|
|
779
1652
|
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
|
|
780
1653
|
diagnostics,
|
|
781
1654
|
model: analyzed.model,
|
|
@@ -783,7 +1656,7 @@ const createCompiler = () => ({
|
|
|
783
1656
|
plan: null
|
|
784
1657
|
};
|
|
785
1658
|
const compilationPlan = plan(analyzed.model, input.options?.outputRoot);
|
|
786
|
-
const output =
|
|
1659
|
+
const output = generateRouteIntrospection(compilationPlan, analyzed.model);
|
|
787
1660
|
return {
|
|
788
1661
|
diagnostics,
|
|
789
1662
|
model: analyzed.model,
|
|
@@ -793,10 +1666,12 @@ const createCompiler = () => ({
|
|
|
793
1666
|
},
|
|
794
1667
|
discover,
|
|
795
1668
|
generate,
|
|
1669
|
+
generateRouteIntrospection,
|
|
796
1670
|
loadConfig,
|
|
797
1671
|
parse,
|
|
798
1672
|
plan
|
|
799
1673
|
});
|
|
1674
|
+
const introspect = (input) => introspectCompilation(input);
|
|
800
1675
|
//#endregion
|
|
801
1676
|
//#region src/compile.ts
|
|
802
1677
|
const compile = (input) => createCompiler().compile(input);
|
|
@@ -830,6 +1705,26 @@ const diff = (previous, next) => {
|
|
|
830
1705
|
};
|
|
831
1706
|
};
|
|
832
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
|
|
833
1728
|
//#region src/recompile.ts
|
|
834
1729
|
const recompile = (input) => {
|
|
835
1730
|
const compiler = createCompiler();
|
|
@@ -843,6 +1738,6 @@ const recompile = (input) => {
|
|
|
843
1738
|
});
|
|
844
1739
|
};
|
|
845
1740
|
//#endregion
|
|
846
|
-
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 };
|
|
847
1742
|
|
|
848
1743
|
//# sourceMappingURL=index.mjs.map
|