@warlock.js/core 5.26.0 → 5.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/esm/cli/commands/openapi-generator.action.mjs +43 -0
- package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
- package/esm/cli/commands/openapi-generator.command.mjs +44 -0
- package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
- package/esm/cli/commands/postman-generator.action.mjs +47 -0
- package/esm/cli/commands/postman-generator.action.mjs.map +1 -0
- package/esm/cli/commands/postman-generator.command.mjs +43 -0
- package/esm/cli/commands/postman-generator.command.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +4 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/dev-server/build-development-openapi-document.mjs +36 -0
- package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
- package/esm/dev-server/collect-development-api-routes.mjs +27 -0
- package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
- package/esm/dev-server/dev-logger.mjs +9 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +6 -3
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +17 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/model-resource-typings.mjs +246 -0
- package/esm/dev-server/model-resource-typings.mjs.map +1 -0
- package/esm/dev-server/type-generator.mjs +46 -2
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/http/index.d.mts +2 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +29 -3
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/serialized.d.mts +114 -0
- package/esm/http/serialized.d.mts.map +1 -0
- package/esm/index.d.mts +11 -3
- package/esm/index.mjs +6 -1
- package/esm/openapi/build-openapi-document.d.mts +17 -0
- package/esm/openapi/build-openapi-document.d.mts.map +1 -0
- package/esm/openapi/build-openapi-document.mjs +124 -0
- package/esm/openapi/build-openapi-document.mjs.map +1 -0
- package/esm/openapi/cast-schema.mjs +89 -0
- package/esm/openapi/cast-schema.mjs.map +1 -0
- package/esm/openapi/collect-openapi-document.mjs +40 -0
- package/esm/openapi/collect-openapi-document.mjs.map +1 -0
- package/esm/openapi/development-openapi-provider.d.mts +28 -0
- package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
- package/esm/openapi/development-openapi-provider.mjs +35 -0
- package/esm/openapi/development-openapi-provider.mjs.map +1 -0
- package/esm/openapi/error-responses.mjs +67 -0
- package/esm/openapi/error-responses.mjs.map +1 -0
- package/esm/openapi/index.d.mts +6 -0
- package/esm/openapi/index.mjs +6 -0
- package/esm/openapi/openapi-child-output.mjs +57 -0
- package/esm/openapi/openapi-child-output.mjs.map +1 -0
- package/esm/openapi/openapi-protocol.mjs +30 -0
- package/esm/openapi/openapi-protocol.mjs.map +1 -0
- package/esm/openapi/paths.mjs +63 -0
- package/esm/openapi/paths.mjs.map +1 -0
- package/esm/openapi/postman/build-request-body.mjs +86 -0
- package/esm/openapi/postman/build-request-body.mjs.map +1 -0
- package/esm/openapi/postman/build-request-url.mjs +85 -0
- package/esm/openapi/postman/build-request-url.mjs.map +1 -0
- package/esm/openapi/postman/build-saved-responses.mjs +60 -0
- package/esm/openapi/postman/build-saved-responses.mjs.map +1 -0
- package/esm/openapi/postman/example-from-schema.d.mts +17 -0
- package/esm/openapi/postman/example-from-schema.d.mts.map +1 -0
- package/esm/openapi/postman/example-from-schema.mjs +92 -0
- package/esm/openapi/postman/example-from-schema.mjs.map +1 -0
- package/esm/openapi/postman/http-status-text.mjs +31 -0
- package/esm/openapi/postman/http-status-text.mjs.map +1 -0
- package/esm/openapi/postman/index.d.mts +3 -0
- package/esm/openapi/postman/openapi-to-postman-collection.d.mts +22 -0
- package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -0
- package/esm/openapi/postman/openapi-to-postman-collection.mjs +109 -0
- package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -0
- package/esm/openapi/postman/postman-types.d.mts +119 -0
- package/esm/openapi/postman/postman-types.d.mts.map +1 -0
- package/esm/openapi/postman/resolve-operation-auth.mjs +51 -0
- package/esm/openapi/postman/resolve-operation-auth.mjs.map +1 -0
- package/esm/openapi/postman/schema-refs.d.mts +7 -0
- package/esm/openapi/postman/schema-refs.d.mts.map +1 -0
- package/esm/openapi/postman/schema-refs.mjs +41 -0
- package/esm/openapi/postman/schema-refs.mjs.map +1 -0
- package/esm/openapi/postman/uuid-v5.mjs +29 -0
- package/esm/openapi/postman/uuid-v5.mjs.map +1 -0
- package/esm/openapi/request-schemas.mjs +138 -0
- package/esm/openapi/request-schemas.mjs.map +1 -0
- package/esm/openapi/response-schemas.mjs +85 -0
- package/esm/openapi/response-schemas.mjs.map +1 -0
- package/esm/openapi/schema-registry.mjs +143 -0
- package/esm/openapi/schema-registry.mjs.map +1 -0
- package/esm/openapi/security.mjs +98 -0
- package/esm/openapi/security.mjs.map +1 -0
- package/esm/openapi/types.d.mts +119 -0
- package/esm/openapi/types.d.mts.map +1 -0
- package/esm/production/registration-child-runner.mjs +139 -0
- package/esm/production/registration-child-runner.mjs.map +1 -0
- package/esm/production/route-registration-child.mjs +30 -2
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs +22 -119
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/resource/define-resource.d.mts +44 -14
- package/esm/resource/define-resource.d.mts.map +1 -1
- package/esm/resource/define-resource.mjs +0 -40
- package/esm/resource/define-resource.mjs.map +1 -1
- package/esm/resource/index.d.mts +1 -1
- package/esm/resource/resource.d.mts +4 -4
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs +2 -4
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/resource/types.d.mts +89 -4
- package/esm/resource/types.d.mts.map +1 -1
- package/esm/router/index.d.mts +1 -1
- package/esm/router/named-api-routes-with-responses.mjs +59 -0
- package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
- package/esm/router/response-schema-types.mjs +100 -0
- package/esm/router/response-schema-types.mjs.map +1 -0
- package/esm/router/router.d.mts +2 -2
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +38 -11
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +16 -1
- package/esm/router/types.d.mts.map +1 -1
- package/llms-full.txt +293 -5
- package/llms.txt +3 -2
- package/package.json +13 -13
- package/skills/README.md +3 -2
- package/skills/create-controller/SKILL.md +39 -2
- package/skills/define-resource/SKILL.md +100 -1
- package/skills/generate-openapi/SKILL.md +148 -0
- package/skills/index.md +3 -3
- package/skills/validate-input/SKILL.md +2 -2
package/esm/resource/types.d.mts
CHANGED
|
@@ -60,18 +60,103 @@ type ResourceFieldBuilderDateOutputOptions = {
|
|
|
60
60
|
} | "format" | "timestamp" | "humanTime" | "locale" | "iso";
|
|
61
61
|
/**
|
|
62
62
|
* Allowed value types in a response schema body.
|
|
63
|
-
* - Cast type string for primitive fields
|
|
63
|
+
* - Cast type string for primitive fields, with the same suffixes as resource schemas
|
|
64
|
+
* (e.g. "string", "number?", "string[]", "date[]?")
|
|
64
65
|
* - ResourceConstructor for a single nested resource object
|
|
65
66
|
* - [ResourceConstructor] (tuple) for an array of a nested resource
|
|
67
|
+
* - A nested plain object of any of the above
|
|
66
68
|
*/
|
|
67
|
-
type ResponseBodyValue =
|
|
69
|
+
type ResponseBodyValue = ResourceCastType | ResourceConstructor | [ResourceConstructor] | {
|
|
70
|
+
[key: string]: ResponseBodyValue;
|
|
71
|
+
};
|
|
68
72
|
/**
|
|
69
73
|
* Response schema for a controller — used for documentation / OpenAPI generation.
|
|
70
74
|
* Keyed by HTTP status code, each entry declares the expected response body shape.
|
|
71
75
|
*/
|
|
72
76
|
type ResponseSchema = { [statusCode in ResponseStatus]?: {
|
|
73
|
-
body:
|
|
77
|
+
body: {
|
|
78
|
+
[key: string]: ResponseBodyValue;
|
|
79
|
+
};
|
|
74
80
|
} };
|
|
81
|
+
/**
|
|
82
|
+
* Collapse `any` to `unknown` so an untyped resolver never leaks `any` into a resource output.
|
|
83
|
+
*/
|
|
84
|
+
type AnyToUnknown<T> = 0 extends 1 & T ? unknown : T;
|
|
85
|
+
/**
|
|
86
|
+
* Output type of a single (suffix-free) cast.
|
|
87
|
+
*
|
|
88
|
+
* `date` is the DEFAULT date output of `ResourceFieldBuilder` (iso, format, timestamp and
|
|
89
|
+
* humanTime are on; timezone, locale and offset are off). A date field customised through a
|
|
90
|
+
* builder (`dateOptions()`) is a builder field and resolves to `unknown`.
|
|
91
|
+
*/
|
|
92
|
+
type BaseCastOutput<C extends string> = C extends "string" | "localized" | "url" | "uploadsUrl" | "storageUrl" ? string : C extends "number" | "float" | "int" ? number : C extends "boolean" ? boolean : C extends "object" ? Record<string, unknown> : C extends "array" ? unknown[] : C extends "date" ? {
|
|
93
|
+
iso: string;
|
|
94
|
+
format: string;
|
|
95
|
+
timestamp: number;
|
|
96
|
+
humanTime: string;
|
|
97
|
+
} : unknown;
|
|
98
|
+
/**
|
|
99
|
+
* Output type of a cast string, including its `[]`, `?` and `[]?` suffixes.
|
|
100
|
+
* `?` means "always present, value or null" (not an optional key).
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* CastOutput<"number"> // number
|
|
104
|
+
* CastOutput<"string?"> // string | null
|
|
105
|
+
* CastOutput<"string[]"> // string[]
|
|
106
|
+
* CastOutput<"url[]?"> // string[] | null
|
|
107
|
+
*/
|
|
108
|
+
type CastOutput<C extends string> = C extends `${infer B}[]?` ? BaseCastOutput<B>[] | null : C extends `${infer B}[]` ? BaseCastOutput<B>[] : C extends `${infer B}?` ? BaseCastOutput<B> | null : BaseCastOutput<C>;
|
|
109
|
+
/**
|
|
110
|
+
* Output type of one schema field.
|
|
111
|
+
*
|
|
112
|
+
* `Root` is the schema of the resource being defined, used to resolve `"self"` and `"self[]"`.
|
|
113
|
+
* Anything that cannot be inferred statically (builders) is `unknown`, never `any`.
|
|
114
|
+
*/
|
|
115
|
+
type FieldOutput<F, Root extends ResourceSchema = ResourceSchema> = F extends "self" ? ResourceOutputOf<Root, Root> : F extends "self[]" ? ResourceOutputOf<Root, Root>[] : F extends string ? CastOutput<F> : F extends readonly [string, infer C extends string] ? CastOutput<C> : F extends ResourceFieldBuilder ? unknown : F extends Lazy<infer R> ? FieldOutput<R, Root> : F extends {
|
|
116
|
+
readonly __type: "arrayOf";
|
|
117
|
+
readonly schema: infer Item extends ResourceSchema;
|
|
118
|
+
} ? ResourceOutputOf<Item, Root>[] : F extends (new (...args: any[]) => {
|
|
119
|
+
toJSON(): infer O;
|
|
120
|
+
}) ? AnyToUnknown<O> : F extends ((...args: any[]) => infer R) ? AnyToUnknown<R> : unknown;
|
|
121
|
+
/**
|
|
122
|
+
* JSON output type inferred from a resource schema.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* type UserJson = ResourceOutputOf<{ id: "number"; name: "string?" }>;
|
|
126
|
+
* // { id: number; name: string | null }
|
|
127
|
+
*/
|
|
128
|
+
type ResourceOutputOf<S extends ResourceSchema, Root extends ResourceSchema = S> = { -readonly [K in keyof S]: FieldOutput<S[K], Root> };
|
|
129
|
+
/**
|
|
130
|
+
* JSON output type of a resource class or of a resource instance.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* type UserJson = ResourceOutput<typeof UserResource>;
|
|
134
|
+
* type SameJson = ResourceOutput<InstanceType<typeof UserResource>>;
|
|
135
|
+
*/
|
|
136
|
+
type ResourceOutput<R> = R extends (new (...args: any[]) => {
|
|
137
|
+
toJSON(): infer O;
|
|
138
|
+
}) ? O : R extends {
|
|
139
|
+
toJSON(): infer O;
|
|
140
|
+
} ? O : never;
|
|
141
|
+
/**
|
|
142
|
+
* Output type of one response schema body value, with the same rules as a resource schema:
|
|
143
|
+
* a cast string resolves through `CastOutput`, a resource through `ResourceOutput`,
|
|
144
|
+
* `[Resource]` to an array of it, and a nested object recurses.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* type Out = ResponseBodyOutput<{ user: typeof UserResource; token: "string"; tags: "string[]?" }>;
|
|
148
|
+
*/
|
|
149
|
+
type ResponseBodyOutput<B> = B extends string ? CastOutput<B> : B extends readonly [infer R] ? ResourceOutput<R>[] : B extends (new (...args: any[]) => unknown) ? ResourceOutput<B> : B extends object ? { -readonly [K in keyof B]: ResponseBodyOutput<B[K]> } : never;
|
|
150
|
+
/**
|
|
151
|
+
* Output type of a whole response schema: the body output per declared status code.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* type LoginResponses = ResponseSchemaOutput<typeof loginSchema>;
|
|
155
|
+
* // { 200: { user: UserJson; token: string }; 400: { error: string } }
|
|
156
|
+
*/
|
|
157
|
+
type ResponseSchemaOutput<S extends ResponseSchema> = { -readonly [K in keyof S]-?: S[K] extends {
|
|
158
|
+
body: infer B;
|
|
159
|
+
} ? ResponseBodyOutput<B> : never };
|
|
75
160
|
//#endregion
|
|
76
|
-
export { ResourceArraySchema, ResourceCastType, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, ResponseBodyValue, ResponseSchema };
|
|
161
|
+
export { BaseCastOutput, CastOutput, FieldOutput, ResourceArraySchema, ResourceCastType, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceOutput, ResourceOutputOf, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, ResponseBodyOutput, ResponseBodyValue, ResponseSchema, ResponseSchemaOutput };
|
|
77
162
|
//# sourceMappingURL=types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/resource/types.ts"],"mappings":";;;;;;KAKY,2BAAA;;;AAAZ;;;;AAAuC;AAsBvC;;KAAY,qBAAA;;AAAqB;AAQjC;;;;KAAY,gBAAA,GACR,2BAAA,MACG,2BAAA,UACA,2BAAA,SACA,2BAAA;AAAA,KAEK,mBAAA;EACV,MAAA;EACA,MAAA,EAAQ,MAAM,SAAS,mBAAA;AAAA;AAAA,KAGb,mBAAA,GACR,gBAAA,GACA,mBAAA,GACA,qBAAA,GACA,IAAA,CAAK,mBAAA,aACI,gBAAA,IACT,oBAAA,GACA,mBAAA,KACE,KAAA,OAAY,QAAA,EAAU,gBAAA;AAAA,KAEhB,cAAA,GAAiB,MAAM,SAAS,mBAAA;AAAA,KAEhC,qCAAA;EAnBL;;AAA2B;EAwB5B,MAAA;EAtByB;;;EA0BzB,SAAA;EAxBJ;;;EA4BI,SAAA;EA5BsC;AAG5C;;EA6BM,QAAA;EA5BF;;;EAgCE,MAAA;EA7BF;;;EAiCE,MAAA;EA7BsB;;;EAiCtB,GAAA;AAAA
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/resource/types.ts"],"mappings":"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"}
|
package/esm/router/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeRoutePath } from "./normalize-route-path.mjs";
|
|
2
2
|
import { normalizeRequestPath } from "./normalize-request-path.mjs";
|
|
3
|
-
import { GroupedRoutesOptions, HttpContext, Middleware, MiddlewareResponse, NamedApiRoute, PartialPick, RequestControllerContract, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestMethod, ResourceMethod, RestfulMiddleware, Route, RouteOptions, RouteResource, RouterGroupCallback, RouterStacks } from "./types.mjs";
|
|
3
|
+
import { GroupedRoutesOptions, HttpContext, Middleware, MiddlewareResponse, NamedApiRoute, NamedApiRoutesOptions, PartialPick, RequestControllerContract, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestMethod, ResourceMethod, RestfulMiddleware, Route, RouteOptions, RouteResource, RouterGroupCallback, RouterStacks } from "./types.mjs";
|
|
4
4
|
import { PositionalHandlerSuspect, clearPositionalHandlerSuspects, describePositionalHandlerSuspect, forgetPositionalHandlerSuspects, inspectHandlerSignature, listPositionalHandlerSuspects, looksLikePositionalHandler, reportPositionalHandlerSuspects } from "./positional-handler-diagnostics.mjs";
|
|
5
5
|
import { matchesDerivedRouteName, routeNameMethodSuffix } from "./route-name-method-suffix.mjs";
|
|
6
6
|
import { RouteRegistry } from "./route-registry.mjs";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { router } from "./router.mjs";
|
|
2
|
+
import { importAppModule } from "../loader/app-module-importer.mjs";
|
|
3
|
+
import { buildResourceMap, createResourceTypeResolver, describeResponseSchemaTypes } from "./response-schema-types.mjs";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
//#region ../core/src/router/named-api-routes-with-responses.ts
|
|
7
|
+
const RESOURCE_FILE_PATTERN = /(^|\/)src\/.*\.resource\.tsx?$/;
|
|
8
|
+
function isResourceFile(file) {
|
|
9
|
+
return RESOURCE_FILE_PATTERN.test(file.relativePath.replace(/\\/g, "/"));
|
|
10
|
+
}
|
|
11
|
+
async function importResourceNamespaces(files, onWarn) {
|
|
12
|
+
const namespaces = [];
|
|
13
|
+
for (const file of files) try {
|
|
14
|
+
const namespace = await importAppModule(pathToFileURL(file.absolutePath).href);
|
|
15
|
+
namespaces.push({
|
|
16
|
+
file: file.absolutePath,
|
|
17
|
+
namespace
|
|
18
|
+
});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
onWarn?.(`Could not import ${file.relativePath} to type API responses: ${error instanceof Error ? error.message : String(error)}`);
|
|
21
|
+
}
|
|
22
|
+
return namespaces;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Identity map from every resource class exported by a `*.resource.ts(x)` file under
|
|
26
|
+
* `src/` to the module export it came from.
|
|
27
|
+
*
|
|
28
|
+
* The modules are imported through the same importer the routes used, so a resource class
|
|
29
|
+
* found in a `responseSchema` is the very instance exported by its module. A module that
|
|
30
|
+
* cannot be imported is reported once through `onWarn` and left out of the map.
|
|
31
|
+
*/
|
|
32
|
+
async function collectResourceExportMap(files, onWarn) {
|
|
33
|
+
return buildResourceMap(await importResourceNamespaces([...files].filter(isResourceFile), onWarn));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Named API route snapshots, with a `response` type map on every route whose handler
|
|
37
|
+
* declares `responseSchema`.
|
|
38
|
+
*
|
|
39
|
+
* Resource modules are imported only when at least one route declares a schema, through
|
|
40
|
+
* the same importer the routes used, so a resource class seen in a schema is the very
|
|
41
|
+
* instance exported by its module. Never throws: if anything fails the routes come back
|
|
42
|
+
* without `response` (and the failure is reported once through `onWarn`).
|
|
43
|
+
*/
|
|
44
|
+
async function collectNamedApiRoutesWithResponses(options) {
|
|
45
|
+
try {
|
|
46
|
+
if (!router.list().some((route) => route.handler?.responseSchema)) return router.getNamedApiRoutes();
|
|
47
|
+
const resolveResource = createResourceTypeResolver(await collectResourceExportMap(options.files, options.onWarn), options.appRoot);
|
|
48
|
+
return router.getNamedApiRoutes({ resolveResponse: (schema, route) => describeResponseSchemaTypes(schema, resolveResource, (fieldPath) => {
|
|
49
|
+
options.onWarn?.(`Route "${route.name}" (${route.method} ${route.path}): response ${fieldPath} is a resource that is not exported from a *.resource.ts file, typed as unknown.`);
|
|
50
|
+
}) });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
options.onWarn?.(`Could not type API responses: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
return router.getNamedApiRoutes();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { collectNamedApiRoutesWithResponses, collectResourceExportMap };
|
|
59
|
+
//# sourceMappingURL=named-api-routes-with-responses.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"named-api-routes-with-responses.mjs","names":[],"sources":["../../../../../../../core/src/router/named-api-routes-with-responses.ts"],"sourcesContent":["import { pathToFileURL } from \"node:url\";\nimport { importAppModule } from \"../loader/app-module-importer\";\nimport {\n buildResourceMap,\n createResourceTypeResolver,\n describeResponseSchemaTypes,\n type ResourceModuleExport,\n type ResourceModuleNamespace,\n type ResourceTypeResolver,\n} from \"./response-schema-types\";\nimport { router } from \"./router\";\nimport type { NamedApiRoute } from \"./types\";\n\nconst RESOURCE_FILE_PATTERN = /(^|\\/)src\\/.*\\.resource\\.tsx?$/;\n\nexport type ResourceSourceFile = Readonly<{\n absolutePath: string;\n relativePath: string;\n}>;\n\nexport type NamedApiRoutesWithResponsesOptions = Readonly<{\n /**\n * Application root, the folder that contains `.warlock/typings/`.\n */\n appRoot: string;\n /**\n * Every file the orchestrator knows about. `*.resource.ts(x)` files under `src/` are\n * imported to map resource classes back to their exports.\n */\n files: Iterable<ResourceSourceFile>;\n /**\n * Receives a human readable warning (unmapped resource, unreadable resource module).\n */\n onWarn?: (message: string) => void;\n}>;\n\nfunction isResourceFile(file: ResourceSourceFile): boolean {\n return RESOURCE_FILE_PATTERN.test(file.relativePath.replace(/\\\\/g, \"/\"));\n}\n\nasync function importResourceNamespaces(\n files: readonly ResourceSourceFile[],\n onWarn: ((message: string) => void) | undefined,\n): Promise<ResourceModuleNamespace[]> {\n const namespaces: ResourceModuleNamespace[] = [];\n\n for (const file of files) {\n try {\n const namespace = (await importAppModule(pathToFileURL(file.absolutePath).href)) as Record<\n string,\n unknown\n >;\n\n namespaces.push({ file: file.absolutePath, namespace });\n } catch (error) {\n onWarn?.(\n `Could not import ${file.relativePath} to type API responses: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n return namespaces;\n}\n\n/**\n * Identity map from every resource class exported by a `*.resource.ts(x)` file under\n * `src/` to the module export it came from.\n *\n * The modules are imported through the same importer the routes used, so a resource class\n * found in a `responseSchema` is the very instance exported by its module. A module that\n * cannot be imported is reported once through `onWarn` and left out of the map.\n */\nexport async function collectResourceExportMap(\n files: Iterable<ResourceSourceFile>,\n onWarn?: (message: string) => void,\n): Promise<Map<unknown, ResourceModuleExport>> {\n const namespaces = await importResourceNamespaces([...files].filter(isResourceFile), onWarn);\n\n return buildResourceMap(namespaces);\n}\n\n/**\n * Named API route snapshots, with a `response` type map on every route whose handler\n * declares `responseSchema`.\n *\n * Resource modules are imported only when at least one route declares a schema, through\n * the same importer the routes used, so a resource class seen in a schema is the very\n * instance exported by its module. Never throws: if anything fails the routes come back\n * without `response` (and the failure is reported once through `onWarn`).\n */\nexport async function collectNamedApiRoutesWithResponses(\n options: NamedApiRoutesWithResponsesOptions,\n): Promise<readonly NamedApiRoute[]> {\n try {\n const declaresSchema = router.list().some((route) => route.handler?.responseSchema);\n\n if (!declaresSchema) return router.getNamedApiRoutes();\n\n const resolveResource: ResourceTypeResolver = createResourceTypeResolver(\n await collectResourceExportMap(options.files, options.onWarn),\n options.appRoot,\n );\n\n return router.getNamedApiRoutes({\n resolveResponse: (schema, route) =>\n describeResponseSchemaTypes(schema, resolveResource, (fieldPath) => {\n options.onWarn?.(\n `Route \"${route.name}\" (${route.method} ${route.path}): response ${fieldPath} is a resource that is not exported from a *.resource.ts file, typed as unknown.`,\n );\n }),\n });\n } catch (error) {\n options.onWarn?.(\n `Could not type API responses: ${error instanceof Error ? error.message : String(error)}`,\n );\n\n return router.getNamedApiRoutes();\n }\n}\n"],"mappings":";;;;;;AAaA,MAAM,wBAAwB;AAuB9B,SAAS,eAAe,MAAmC;CACzD,OAAO,sBAAsB,KAAK,KAAK,aAAa,QAAQ,OAAO,GAAG,CAAC;AACzE;AAEA,eAAe,yBACb,OACA,QACoC;CACpC,MAAM,aAAwC,CAAC;CAE/C,KAAK,MAAM,QAAQ,OACjB,IAAI;EACF,MAAM,YAAa,MAAM,gBAAgB,cAAc,KAAK,YAAY,CAAC,CAAC,IAAI;EAK9E,WAAW,KAAK;GAAE,MAAM,KAAK;GAAc;EAAU,CAAC;CACxD,SAAS,OAAO;EACd,SACE,oBAAoB,KAAK,aAAa,0BACpC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEzD;CACF;CAGF,OAAO;AACT;;;;;;;;;AAUA,eAAsB,yBACpB,OACA,QAC6C;CAG7C,OAAO,iBAAiB,MAFC,yBAAyB,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,cAAc,GAAG,MAAM,CAEzD;AACpC;;;;;;;;;;AAWA,eAAsB,mCACpB,SACmC;CACnC,IAAI;EAGF,IAAI,CAFmB,OAAO,KAAK,CAAC,CAAC,MAAM,UAAU,MAAM,SAAS,cAElD,GAAG,OAAO,OAAO,kBAAkB;EAErD,MAAM,kBAAwC,2BAC5C,MAAM,yBAAyB,QAAQ,OAAO,QAAQ,MAAM,GAC5D,QAAQ,OACV;EAEA,OAAO,OAAO,kBAAkB,EAC9B,kBAAkB,QAAQ,UACxB,4BAA4B,QAAQ,kBAAkB,cAAc;GAClE,QAAQ,SACN,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,GAAG,MAAM,KAAK,cAAc,UAAU,iFAC/E;EACF,CAAC,EACL,CAAC;CACH,SAAS,OAAO;EACd,QAAQ,SACN,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACxF;EAEA,OAAO,OAAO,kBAAkB;CAClC;AACF"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { posix, relative, sep } from "node:path";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/router/response-schema-types.ts
|
|
4
|
+
const CORE_PACKAGE = "@warlock.js/core";
|
|
5
|
+
function isIndexFile(file) {
|
|
6
|
+
return /(^|[\\/])index\.[cm]?[jt]sx?$/.test(file);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Build an identity map from every function export (resource classes) to the module
|
|
10
|
+
* export it came from.
|
|
11
|
+
*
|
|
12
|
+
* When the same value is exported more than once (a barrel re-export, or a default next
|
|
13
|
+
* to a named export) the most specific location wins: a non-`index.*` file beats an
|
|
14
|
+
* `index.*` file, then a named export beats `default`, then the first one seen.
|
|
15
|
+
*/
|
|
16
|
+
function buildResourceMap(namespaces) {
|
|
17
|
+
const map = /* @__PURE__ */ new Map();
|
|
18
|
+
const rank = (location) => (isIndexFile(location.file) ? 2 : 0) + (location.exportName === "default" ? 1 : 0);
|
|
19
|
+
for (const { file, namespace } of namespaces) for (const [exportName, value] of Object.entries(namespace)) {
|
|
20
|
+
if (typeof value !== "function") continue;
|
|
21
|
+
const candidate = {
|
|
22
|
+
file,
|
|
23
|
+
exportName
|
|
24
|
+
};
|
|
25
|
+
const existing = map.get(value);
|
|
26
|
+
if (!existing || rank(candidate) < rank(existing)) map.set(value, candidate);
|
|
27
|
+
}
|
|
28
|
+
return map;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Turn a resource map into a resolver whose import paths are relative to
|
|
32
|
+
* `<appRoot>/.warlock/typings/`, the folder the generated declarations live in.
|
|
33
|
+
*/
|
|
34
|
+
function createResourceTypeResolver(map, appRoot) {
|
|
35
|
+
const typingsDirectory = `${appRoot}${sep}.warlock${sep}typings`;
|
|
36
|
+
return (value) => {
|
|
37
|
+
const location = map.get(value);
|
|
38
|
+
if (!location) return void 0;
|
|
39
|
+
let importPath = relative(typingsDirectory, location.file).split(sep).join(posix.sep).replace(/\.[cm]?[jt]sx?$/, "");
|
|
40
|
+
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
41
|
+
return {
|
|
42
|
+
importPath,
|
|
43
|
+
exportName: location.exportName
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function describeCast(cast) {
|
|
48
|
+
return `import(${JSON.stringify(CORE_PACKAGE)}).CastOutput<${JSON.stringify(cast)}>`;
|
|
49
|
+
}
|
|
50
|
+
function describeResource(reference) {
|
|
51
|
+
return `import(${JSON.stringify(CORE_PACKAGE)}).ResourceOutput<typeof import(${JSON.stringify(reference.importPath)})[${JSON.stringify(reference.exportName)}]>`;
|
|
52
|
+
}
|
|
53
|
+
function describeBodyValue(value, path, resolveResource, onUnmapped) {
|
|
54
|
+
if (typeof value === "string") return describeCast(value);
|
|
55
|
+
if (typeof value === "function") {
|
|
56
|
+
const reference = resolveResource(value);
|
|
57
|
+
if (!reference) {
|
|
58
|
+
onUnmapped?.(path);
|
|
59
|
+
return "unknown";
|
|
60
|
+
}
|
|
61
|
+
return describeResource(reference);
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) return `(${describeBodyValue(value[0], path, resolveResource, onUnmapped)})[]`;
|
|
64
|
+
if (typeof value === "object" && value !== null) {
|
|
65
|
+
const members = Object.entries(value).map(([key, member]) => `${JSON.stringify(key)}: ${describeBodyValue(member, `${path}.${key}`, resolveResource, onUnmapped)}`);
|
|
66
|
+
return members.length === 0 ? "{}" : `{ ${members.join("; ")} }`;
|
|
67
|
+
}
|
|
68
|
+
return "unknown";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Serialize a route's `responseSchema` into TypeScript type expressions, one per status code.
|
|
72
|
+
*
|
|
73
|
+
* The expressions are self-contained: they reference only `CastOutput` and `ResourceOutput`
|
|
74
|
+
* from `@warlock.js/core`, `typeof import(...)` of resource modules, object literal types
|
|
75
|
+
* and arrays. A resource that `resolveResource` cannot map becomes `unknown` and is
|
|
76
|
+
* reported through `onUnmapped`.
|
|
77
|
+
*
|
|
78
|
+
* Both the typings child process and the dev server call this, so their output matches.
|
|
79
|
+
*
|
|
80
|
+
* @returns `undefined` when the schema declares no status code.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* describeResponseSchemaTypes({ 400: { body: { error: "string" } } }, resolve);
|
|
84
|
+
* // { "400": '{ "error": import("@warlock.js/core").CastOutput<"string"> }' }
|
|
85
|
+
*/
|
|
86
|
+
function describeResponseSchemaTypes(schema, resolveResource, onUnmapped) {
|
|
87
|
+
if (typeof schema !== "object" || schema === null) return void 0;
|
|
88
|
+
const described = {};
|
|
89
|
+
for (const [status, entry] of Object.entries(schema)) {
|
|
90
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
91
|
+
const body = entry.body;
|
|
92
|
+
if (typeof body !== "object" || body === null) continue;
|
|
93
|
+
described[status] = describeBodyValue(body, `${status}.body`, resolveResource, onUnmapped);
|
|
94
|
+
}
|
|
95
|
+
return Object.keys(described).length === 0 ? void 0 : described;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
export { buildResourceMap, createResourceTypeResolver, describeResponseSchemaTypes };
|
|
100
|
+
//# sourceMappingURL=response-schema-types.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-schema-types.mjs","names":[],"sources":["../../../../../../../core/src/router/response-schema-types.ts"],"sourcesContent":["import { posix, relative, sep } from \"node:path\";\n\n/**\n * Where a resource class lives: the module it is exported from and the export name.\n */\nexport type ResourceModuleExport = Readonly<{\n file: string;\n exportName: string;\n}>;\n\n/**\n * Import location of a resource as it is written in a generated declaration file.\n * `importPath` has no extension and starts with `./` or `../`.\n */\nexport type ResourceTypeReference = Readonly<{\n importPath: string;\n exportName: string;\n}>;\n\n/**\n * Maps a response-schema resource value back to where it is exported from.\n * Returns `undefined` when the value is not a known resource export.\n */\nexport type ResourceTypeResolver = (value: unknown) => ResourceTypeReference | undefined;\n\n/**\n * A loaded module namespace together with the file it was imported from.\n */\nexport type ResourceModuleNamespace = Readonly<{\n file: string;\n namespace: Record<string, unknown>;\n}>;\n\n/**\n * Called with the dotted path of every field that could not be mapped\n * (for example `200.body.user`).\n */\nexport type UnmappedResourceHandler = (fieldPath: string) => void;\n\nconst CORE_PACKAGE = \"@warlock.js/core\";\n\nfunction isIndexFile(file: string): boolean {\n return /(^|[\\\\/])index\\.[cm]?[jt]sx?$/.test(file);\n}\n\n/**\n * Build an identity map from every function export (resource classes) to the module\n * export it came from.\n *\n * When the same value is exported more than once (a barrel re-export, or a default next\n * to a named export) the most specific location wins: a non-`index.*` file beats an\n * `index.*` file, then a named export beats `default`, then the first one seen.\n */\nexport function buildResourceMap(\n namespaces: readonly ResourceModuleNamespace[],\n): Map<unknown, ResourceModuleExport> {\n const map = new Map<unknown, ResourceModuleExport>();\n\n const rank = (location: ResourceModuleExport): number =>\n (isIndexFile(location.file) ? 2 : 0) + (location.exportName === \"default\" ? 1 : 0);\n\n for (const { file, namespace } of namespaces) {\n for (const [exportName, value] of Object.entries(namespace)) {\n if (typeof value !== \"function\") continue;\n\n const candidate: ResourceModuleExport = { file, exportName };\n const existing = map.get(value);\n\n if (!existing || rank(candidate) < rank(existing)) {\n map.set(value, candidate);\n }\n }\n }\n\n return map;\n}\n\n/**\n * Turn a resource map into a resolver whose import paths are relative to\n * `<appRoot>/.warlock/typings/`, the folder the generated declarations live in.\n */\nexport function createResourceTypeResolver(\n map: ReadonlyMap<unknown, ResourceModuleExport>,\n appRoot: string,\n): ResourceTypeResolver {\n const typingsDirectory = `${appRoot}${sep}.warlock${sep}typings`;\n\n return (value) => {\n const location = map.get(value);\n\n if (!location) return undefined;\n\n let importPath = relative(typingsDirectory, location.file)\n .split(sep)\n .join(posix.sep)\n .replace(/\\.[cm]?[jt]sx?$/, \"\");\n\n if (!importPath.startsWith(\".\")) {\n importPath = `./${importPath}`;\n }\n\n return { importPath, exportName: location.exportName };\n };\n}\n\nfunction describeCast(cast: string): string {\n return `import(${JSON.stringify(CORE_PACKAGE)}).CastOutput<${JSON.stringify(cast)}>`;\n}\n\nfunction describeResource(reference: ResourceTypeReference): string {\n return (\n `import(${JSON.stringify(CORE_PACKAGE)}).ResourceOutput<` +\n `typeof import(${JSON.stringify(reference.importPath)})[${JSON.stringify(reference.exportName)}]>`\n );\n}\n\nfunction describeBodyValue(\n value: unknown,\n path: string,\n resolveResource: ResourceTypeResolver,\n onUnmapped: UnmappedResourceHandler | undefined,\n): string {\n if (typeof value === \"string\") {\n return describeCast(value);\n }\n\n if (typeof value === \"function\") {\n const reference = resolveResource(value);\n\n if (!reference) {\n onUnmapped?.(path);\n return \"unknown\";\n }\n\n return describeResource(reference);\n }\n\n if (Array.isArray(value)) {\n return `(${describeBodyValue(value[0], path, resolveResource, onUnmapped)})[]`;\n }\n\n if (typeof value === \"object\" && value !== null) {\n const members = Object.entries(value).map(\n ([key, member]) =>\n `${JSON.stringify(key)}: ${describeBodyValue(member, `${path}.${key}`, resolveResource, onUnmapped)}`,\n );\n\n return members.length === 0 ? \"{}\" : `{ ${members.join(\"; \")} }`;\n }\n\n return \"unknown\";\n}\n\n/**\n * Serialize a route's `responseSchema` into TypeScript type expressions, one per status code.\n *\n * The expressions are self-contained: they reference only `CastOutput` and `ResourceOutput`\n * from `@warlock.js/core`, `typeof import(...)` of resource modules, object literal types\n * and arrays. A resource that `resolveResource` cannot map becomes `unknown` and is\n * reported through `onUnmapped`.\n *\n * Both the typings child process and the dev server call this, so their output matches.\n *\n * @returns `undefined` when the schema declares no status code.\n *\n * @example\n * describeResponseSchemaTypes({ 400: { body: { error: \"string\" } } }, resolve);\n * // { \"400\": '{ \"error\": import(\"@warlock.js/core\").CastOutput<\"string\"> }' }\n */\nexport function describeResponseSchemaTypes(\n schema: unknown,\n resolveResource: ResourceTypeResolver,\n onUnmapped?: UnmappedResourceHandler,\n): Record<string, string> | undefined {\n if (typeof schema !== \"object\" || schema === null) return undefined;\n\n const described: Record<string, string> = {};\n\n for (const [status, entry] of Object.entries(schema)) {\n if (typeof entry !== \"object\" || entry === null) continue;\n\n const body = (entry as { body?: unknown }).body;\n\n if (typeof body !== \"object\" || body === null) continue;\n\n described[status] = describeBodyValue(body, `${status}.body`, resolveResource, onUnmapped);\n }\n\n return Object.keys(described).length === 0 ? undefined : described;\n}\n"],"mappings":";;;AAuCA,MAAM,eAAe;AAErB,SAAS,YAAY,MAAuB;CAC1C,OAAO,gCAAgC,KAAK,IAAI;AAClD;;;;;;;;;AAUA,SAAgB,iBACd,YACoC;CACpC,MAAM,sBAAM,IAAI,IAAmC;CAEnD,MAAM,QAAQ,cACX,YAAY,SAAS,IAAI,IAAI,IAAI,MAAM,SAAS,eAAe,YAAY,IAAI;CAElF,KAAK,MAAM,EAAE,MAAM,eAAe,YAChC,KAAK,MAAM,CAAC,YAAY,UAAU,OAAO,QAAQ,SAAS,GAAG;EAC3D,IAAI,OAAO,UAAU,YAAY;EAEjC,MAAM,YAAkC;GAAE;GAAM;EAAW;EAC3D,MAAM,WAAW,IAAI,IAAI,KAAK;EAE9B,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,KAAK,QAAQ,GAC9C,IAAI,IAAI,OAAO,SAAS;CAE5B;CAGF,OAAO;AACT;;;;;AAMA,SAAgB,2BACd,KACA,SACsB;CACtB,MAAM,mBAAmB,GAAG,UAAU,IAAI,UAAU,IAAI;CAExD,QAAQ,UAAU;EAChB,MAAM,WAAW,IAAI,IAAI,KAAK;EAE9B,IAAI,CAAC,UAAU,OAAO;EAEtB,IAAI,aAAa,SAAS,kBAAkB,SAAS,IAAI,CAAC,CACvD,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,GAAG,CAAC,CACf,QAAQ,mBAAmB,EAAE;EAEhC,IAAI,CAAC,WAAW,WAAW,GAAG,GAC5B,aAAa,KAAK;EAGpB,OAAO;GAAE;GAAY,YAAY,SAAS;EAAW;CACvD;AACF;AAEA,SAAS,aAAa,MAAsB;CAC1C,OAAO,UAAU,KAAK,UAAU,YAAY,EAAE,eAAe,KAAK,UAAU,IAAI,EAAE;AACpF;AAEA,SAAS,iBAAiB,WAA0C;CAClE,OACE,UAAU,KAAK,UAAU,YAAY,EAAE,iCACtB,KAAK,UAAU,UAAU,UAAU,EAAE,IAAI,KAAK,UAAU,UAAU,UAAU,EAAE;AAEnG;AAEA,SAAS,kBACP,OACA,MACA,iBACA,YACQ;CACR,IAAI,OAAO,UAAU,UACnB,OAAO,aAAa,KAAK;CAG3B,IAAI,OAAO,UAAU,YAAY;EAC/B,MAAM,YAAY,gBAAgB,KAAK;EAEvC,IAAI,CAAC,WAAW;GACd,aAAa,IAAI;GACjB,OAAO;EACT;EAEA,OAAO,iBAAiB,SAAS;CACnC;CAEA,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,IAAI,kBAAkB,MAAM,IAAI,MAAM,iBAAiB,UAAU,EAAE;CAG5E,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAC/C,MAAM,UAAU,OAAO,QAAQ,KAAK,CAAC,CAAC,KACnC,CAAC,KAAK,YACL,GAAG,KAAK,UAAU,GAAG,EAAE,IAAI,kBAAkB,QAAQ,GAAG,KAAK,GAAG,OAAO,iBAAiB,UAAU,GACtG;EAEA,OAAO,QAAQ,WAAW,IAAI,OAAO,KAAK,QAAQ,KAAK,IAAI,EAAE;CAC/D;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,4BACd,QACA,iBACA,YACoC;CACpC,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM,OAAO;CAE1D,MAAM,YAAoC,CAAC;CAE3C,KAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,MAAM,GAAG;EACpD,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAEjD,MAAM,OAAQ,MAA6B;EAE3C,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM;EAE/C,UAAU,UAAU,kBAAkB,MAAM,GAAG,OAAO,QAAQ,iBAAiB,UAAU;CAC3F;CAEA,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,WAAW,IAAI,SAAY;AAC3D"}
|
package/esm/router/router.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FastifyInstance } from "../http/server.mjs";
|
|
2
|
-
import { GroupedRoutesOptions, NamedApiRoute, RequestHandler, RequestHandlerType, ResourceMethod, Route, RouteOptions, RouteResource, RouterGroupCallback, RouterStacks } from "./types.mjs";
|
|
2
|
+
import { GroupedRoutesOptions, NamedApiRoute, NamedApiRoutesOptions, RequestHandler, RequestHandlerType, ResourceMethod, Route, RouteOptions, RouteResource, RouterGroupCallback, RouterStacks } from "./types.mjs";
|
|
3
3
|
import { RouteBuilder } from "./route-builder.mjs";
|
|
4
4
|
import { FastifyHttpProxyOptions } from "@fastify/http-proxy";
|
|
5
5
|
import { FastifyStaticOptions } from "@fastify/static";
|
|
@@ -197,7 +197,7 @@ declare class Router {
|
|
|
197
197
|
* Names are the registered names, including method suffixes. `all` stays
|
|
198
198
|
* intact as route metadata; consumers decide whether their use supports it.
|
|
199
199
|
*/
|
|
200
|
-
getNamedApiRoutes(): readonly NamedApiRoute[];
|
|
200
|
+
getNamedApiRoutes(options?: NamedApiRoutesOptions): readonly NamedApiRoute[];
|
|
201
201
|
/**
|
|
202
202
|
* Number of routes currently registered on the singleton.
|
|
203
203
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.mts","names":[],"sources":["../../../../../../../core/src/router/router.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"router.d.mts","names":[],"sources":["../../../../../../../core/src/router/router.ts"],"mappings":"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"}
|
package/esm/router/router.mjs
CHANGED
|
@@ -50,6 +50,23 @@ async function runRouteHooks(route, phase, request, reply) {
|
|
|
50
50
|
for (const hook of hooks) if (await hook(request, reply) === reply) return reply;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
+
* Bound functions do not inherit static properties, so the handler metadata that
|
|
54
|
+
* documentation and typings read (`responseSchema`) is copied onto the bound copy.
|
|
55
|
+
*/
|
|
56
|
+
function copyResponseSchema(source, target) {
|
|
57
|
+
const responseSchema = source?.responseSchema;
|
|
58
|
+
if (responseSchema) target.responseSchema = responseSchema;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Bind a restful resource method to its resource, keeping its `responseSchema`.
|
|
62
|
+
*/
|
|
63
|
+
function bindResourceHandler(resource, method) {
|
|
64
|
+
const source = resource[method];
|
|
65
|
+
const handler = source?.bind(resource);
|
|
66
|
+
copyResponseSchema(source, handler);
|
|
67
|
+
return handler;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
53
70
|
* Describe one claimant of a route name, for the duplicate-name error.
|
|
54
71
|
*
|
|
55
72
|
* `warlock dev` registers SSR page routes on the same router as API routes, so
|
|
@@ -180,6 +197,7 @@ var Router = class Router {
|
|
|
180
197
|
const [controller, action] = handler;
|
|
181
198
|
if (typeof controller[action] !== "function") throw new Error(`Invalid controller action "${action}" for controller "${controller.constructor.name}"`);
|
|
182
199
|
handler = controller[action].bind(controller);
|
|
200
|
+
copyResponseSchema(controller[action], handler);
|
|
183
201
|
if (!handler.validation) {
|
|
184
202
|
handler.validation = {};
|
|
185
203
|
if (controller[`${action}ValidationSchema`]) handler.validation.schema = controller[`${action}ValidationSchema`]();
|
|
@@ -284,12 +302,12 @@ var Router = class Router {
|
|
|
284
302
|
const isAcceptableResource = (type) => {
|
|
285
303
|
return Boolean((!options.except || !options.except.includes(type)) && (!options.only || options.only.includes(type)));
|
|
286
304
|
};
|
|
287
|
-
if (routeResource.list && isAcceptableResource("list")) this.get(path, options.replace?.list || routeResource
|
|
305
|
+
if (routeResource.list && isAcceptableResource("list")) this.get(path, options.replace?.list || bindResourceHandler(routeResource, "list"), {
|
|
288
306
|
...options,
|
|
289
307
|
name: "list",
|
|
290
308
|
restful: true
|
|
291
309
|
});
|
|
292
|
-
if (routeResource.get && isAcceptableResource("get")) this.get("/:id", options.replace?.get || routeResource
|
|
310
|
+
if (routeResource.get && isAcceptableResource("get")) this.get("/:id", options.replace?.get || bindResourceHandler(routeResource, "get"), {
|
|
293
311
|
...options,
|
|
294
312
|
name: "single",
|
|
295
313
|
restful: true
|
|
@@ -318,12 +336,12 @@ var Router = class Router {
|
|
|
318
336
|
restful: true
|
|
319
337
|
});
|
|
320
338
|
}
|
|
321
|
-
if (routeResource.delete && isAcceptableResource("delete")) this.delete("/:id", options.replace?.delete || routeResource
|
|
339
|
+
if (routeResource.delete && isAcceptableResource("delete")) this.delete("/:id", options.replace?.delete || bindResourceHandler(routeResource, "delete"), {
|
|
322
340
|
...options,
|
|
323
341
|
name: "delete",
|
|
324
342
|
restful: true
|
|
325
343
|
});
|
|
326
|
-
if (routeResource.bulkDelete && isAcceptableResource("delete")) this.delete(path, options.replace?.bulkDelete || routeResource
|
|
344
|
+
if (routeResource.bulkDelete && isAcceptableResource("delete")) this.delete(path, options.replace?.bulkDelete || bindResourceHandler(routeResource, "bulkDelete"), {
|
|
327
345
|
...options,
|
|
328
346
|
name: "bulkDelete",
|
|
329
347
|
restful: true
|
|
@@ -444,7 +462,7 @@ var Router = class Router {
|
|
|
444
462
|
* Manage validation system for the given resource
|
|
445
463
|
*/
|
|
446
464
|
manageValidation(resource, method) {
|
|
447
|
-
const handler = resource
|
|
465
|
+
const handler = bindResourceHandler(resource, method);
|
|
448
466
|
const methodValidation = resource?.validation?.[method];
|
|
449
467
|
if (method === "patch") {
|
|
450
468
|
handler.validation = methodValidation;
|
|
@@ -496,12 +514,21 @@ var Router = class Router {
|
|
|
496
514
|
* Names are the registered names, including method suffixes. `all` stays
|
|
497
515
|
* intact as route metadata; consumers decide whether their use supports it.
|
|
498
516
|
*/
|
|
499
|
-
getNamedApiRoutes() {
|
|
500
|
-
return Object.freeze(this.routes.filter((route) => !route.isPage && typeof route.name === "string" && route.name.length > 0).map((route) =>
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
517
|
+
getNamedApiRoutes(options = {}) {
|
|
518
|
+
return Object.freeze(this.routes.filter((route) => !route.isPage && typeof route.name === "string" && route.name.length > 0).map((route) => {
|
|
519
|
+
const snapshot = {
|
|
520
|
+
name: route.name,
|
|
521
|
+
path: route.path,
|
|
522
|
+
method: route.method
|
|
523
|
+
};
|
|
524
|
+
const schema = route.handler?.responseSchema;
|
|
525
|
+
const response = schema ? options.resolveResponse?.(schema, route) : void 0;
|
|
526
|
+
if (!response || Object.keys(response).length === 0) return Object.freeze(snapshot);
|
|
527
|
+
return Object.freeze({
|
|
528
|
+
...snapshot,
|
|
529
|
+
response: Object.freeze({ ...response })
|
|
530
|
+
});
|
|
531
|
+
}));
|
|
505
532
|
}
|
|
506
533
|
/**
|
|
507
534
|
* Number of routes currently registered on the singleton.
|