@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/esm/cli/commands/openapi-generator.action.mjs +43 -0
  3. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
  4. package/esm/cli/commands/openapi-generator.command.mjs +44 -0
  5. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
  6. package/esm/cli/commands/postman-generator.action.mjs +47 -0
  7. package/esm/cli/commands/postman-generator.action.mjs.map +1 -0
  8. package/esm/cli/commands/postman-generator.command.mjs +43 -0
  9. package/esm/cli/commands/postman-generator.command.mjs.map +1 -0
  10. package/esm/cli/framework-cli-commands.mjs +4 -0
  11. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  12. package/esm/dev-server/build-development-openapi-document.mjs +36 -0
  13. package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
  14. package/esm/dev-server/collect-development-api-routes.mjs +27 -0
  15. package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
  16. package/esm/dev-server/dev-logger.mjs +9 -1
  17. package/esm/dev-server/dev-logger.mjs.map +1 -1
  18. package/esm/dev-server/development-server.mjs +6 -3
  19. package/esm/dev-server/development-server.mjs.map +1 -1
  20. package/esm/dev-server/files-orchestrator.mjs +17 -1
  21. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  22. package/esm/dev-server/model-resource-typings.mjs +246 -0
  23. package/esm/dev-server/model-resource-typings.mjs.map +1 -0
  24. package/esm/dev-server/type-generator.mjs +46 -2
  25. package/esm/dev-server/type-generator.mjs.map +1 -1
  26. package/esm/http/index.d.mts +2 -1
  27. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  28. package/esm/http/middleware/inject-request-context.mjs +29 -3
  29. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  30. package/esm/http/serialized.d.mts +114 -0
  31. package/esm/http/serialized.d.mts.map +1 -0
  32. package/esm/index.d.mts +11 -3
  33. package/esm/index.mjs +6 -1
  34. package/esm/openapi/build-openapi-document.d.mts +17 -0
  35. package/esm/openapi/build-openapi-document.d.mts.map +1 -0
  36. package/esm/openapi/build-openapi-document.mjs +124 -0
  37. package/esm/openapi/build-openapi-document.mjs.map +1 -0
  38. package/esm/openapi/cast-schema.mjs +89 -0
  39. package/esm/openapi/cast-schema.mjs.map +1 -0
  40. package/esm/openapi/collect-openapi-document.mjs +40 -0
  41. package/esm/openapi/collect-openapi-document.mjs.map +1 -0
  42. package/esm/openapi/development-openapi-provider.d.mts +28 -0
  43. package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
  44. package/esm/openapi/development-openapi-provider.mjs +35 -0
  45. package/esm/openapi/development-openapi-provider.mjs.map +1 -0
  46. package/esm/openapi/error-responses.mjs +67 -0
  47. package/esm/openapi/error-responses.mjs.map +1 -0
  48. package/esm/openapi/index.d.mts +6 -0
  49. package/esm/openapi/index.mjs +6 -0
  50. package/esm/openapi/openapi-child-output.mjs +57 -0
  51. package/esm/openapi/openapi-child-output.mjs.map +1 -0
  52. package/esm/openapi/openapi-protocol.mjs +30 -0
  53. package/esm/openapi/openapi-protocol.mjs.map +1 -0
  54. package/esm/openapi/paths.mjs +63 -0
  55. package/esm/openapi/paths.mjs.map +1 -0
  56. package/esm/openapi/postman/build-request-body.mjs +86 -0
  57. package/esm/openapi/postman/build-request-body.mjs.map +1 -0
  58. package/esm/openapi/postman/build-request-url.mjs +85 -0
  59. package/esm/openapi/postman/build-request-url.mjs.map +1 -0
  60. package/esm/openapi/postman/build-saved-responses.mjs +60 -0
  61. package/esm/openapi/postman/build-saved-responses.mjs.map +1 -0
  62. package/esm/openapi/postman/example-from-schema.d.mts +17 -0
  63. package/esm/openapi/postman/example-from-schema.d.mts.map +1 -0
  64. package/esm/openapi/postman/example-from-schema.mjs +92 -0
  65. package/esm/openapi/postman/example-from-schema.mjs.map +1 -0
  66. package/esm/openapi/postman/http-status-text.mjs +31 -0
  67. package/esm/openapi/postman/http-status-text.mjs.map +1 -0
  68. package/esm/openapi/postman/index.d.mts +3 -0
  69. package/esm/openapi/postman/openapi-to-postman-collection.d.mts +22 -0
  70. package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -0
  71. package/esm/openapi/postman/openapi-to-postman-collection.mjs +109 -0
  72. package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -0
  73. package/esm/openapi/postman/postman-types.d.mts +119 -0
  74. package/esm/openapi/postman/postman-types.d.mts.map +1 -0
  75. package/esm/openapi/postman/resolve-operation-auth.mjs +51 -0
  76. package/esm/openapi/postman/resolve-operation-auth.mjs.map +1 -0
  77. package/esm/openapi/postman/schema-refs.d.mts +7 -0
  78. package/esm/openapi/postman/schema-refs.d.mts.map +1 -0
  79. package/esm/openapi/postman/schema-refs.mjs +41 -0
  80. package/esm/openapi/postman/schema-refs.mjs.map +1 -0
  81. package/esm/openapi/postman/uuid-v5.mjs +29 -0
  82. package/esm/openapi/postman/uuid-v5.mjs.map +1 -0
  83. package/esm/openapi/request-schemas.mjs +138 -0
  84. package/esm/openapi/request-schemas.mjs.map +1 -0
  85. package/esm/openapi/response-schemas.mjs +85 -0
  86. package/esm/openapi/response-schemas.mjs.map +1 -0
  87. package/esm/openapi/schema-registry.mjs +143 -0
  88. package/esm/openapi/schema-registry.mjs.map +1 -0
  89. package/esm/openapi/security.mjs +98 -0
  90. package/esm/openapi/security.mjs.map +1 -0
  91. package/esm/openapi/types.d.mts +119 -0
  92. package/esm/openapi/types.d.mts.map +1 -0
  93. package/esm/production/registration-child-runner.mjs +139 -0
  94. package/esm/production/registration-child-runner.mjs.map +1 -0
  95. package/esm/production/route-registration-child.mjs +30 -2
  96. package/esm/production/route-registration-child.mjs.map +1 -1
  97. package/esm/production/route-registration-snapshot.mjs +22 -119
  98. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  99. package/esm/resource/define-resource.d.mts +44 -14
  100. package/esm/resource/define-resource.d.mts.map +1 -1
  101. package/esm/resource/define-resource.mjs +0 -40
  102. package/esm/resource/define-resource.mjs.map +1 -1
  103. package/esm/resource/index.d.mts +1 -1
  104. package/esm/resource/resource.d.mts +4 -4
  105. package/esm/resource/resource.d.mts.map +1 -1
  106. package/esm/resource/resource.mjs +2 -4
  107. package/esm/resource/resource.mjs.map +1 -1
  108. package/esm/resource/types.d.mts +89 -4
  109. package/esm/resource/types.d.mts.map +1 -1
  110. package/esm/router/index.d.mts +1 -1
  111. package/esm/router/named-api-routes-with-responses.mjs +59 -0
  112. package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
  113. package/esm/router/response-schema-types.mjs +100 -0
  114. package/esm/router/response-schema-types.mjs.map +1 -0
  115. package/esm/router/router.d.mts +2 -2
  116. package/esm/router/router.d.mts.map +1 -1
  117. package/esm/router/router.mjs +38 -11
  118. package/esm/router/router.mjs.map +1 -1
  119. package/esm/router/types.d.mts +16 -1
  120. package/esm/router/types.d.mts.map +1 -1
  121. package/llms-full.txt +293 -5
  122. package/llms.txt +3 -2
  123. package/package.json +13 -13
  124. package/skills/README.md +3 -2
  125. package/skills/create-controller/SKILL.md +39 -2
  126. package/skills/define-resource/SKILL.md +100 -1
  127. package/skills/generate-openapi/SKILL.md +148 -0
  128. package/skills/index.md +3 -3
  129. package/skills/validate-input/SKILL.md +2 -2
@@ -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 (e.g. "string", "number")
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 = ResourceOutputValueCastType | ResourceConstructor | [ResourceConstructor];
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: Record<string, ResponseBodyValue>;
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;;;;;;;KAcM,iBAAA,GACV,2BAAA,GAA8B,mBAAA,IAAuB,mBAAA;;;;AAhDX;KAsDhC,cAAA,oBACK,cAAA;EACb,IAAA,EAAM,MAAA,SAAe,iBAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/resource/types.ts"],"mappings":"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"}
@@ -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"}
@@ -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":"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"}
1
+ {"version":3,"file":"router.d.mts","names":[],"sources":["../../../../../../../core/src/router/router.ts"],"mappings":"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"}
@@ -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.list.bind(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.get.bind(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.delete.bind(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.bulkDelete.bind(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[method]?.bind(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) => Object.freeze({
501
- name: route.name,
502
- path: route.path,
503
- method: route.method
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.