@warlock.js/core 5.25.0 → 5.27.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 (163) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/esm/cli/commands/doctor/checks/index.mjs +2 -0
  3. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
  4. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
  5. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
  6. package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
  7. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
  8. package/esm/cli/commands/doctor.command.mjs +5 -0
  9. package/esm/cli/commands/doctor.command.mjs.map +1 -1
  10. package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
  11. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  12. package/esm/cli/commands/openapi-generator.action.mjs +43 -0
  13. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
  14. package/esm/cli/commands/openapi-generator.command.mjs +44 -0
  15. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
  16. package/esm/cli/commands/typings-generator.command.mjs +4 -1
  17. package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
  18. package/esm/cli/framework-cli-commands.mjs +2 -0
  19. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  20. package/esm/database/migrate-action.mjs +2 -2
  21. package/esm/database/migrate-action.mjs.map +1 -1
  22. package/esm/dev-server/build-development-openapi-document.mjs +36 -0
  23. package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
  24. package/esm/dev-server/collect-development-api-routes.mjs +27 -0
  25. package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
  26. package/esm/dev-server/dev-logger.mjs +9 -1
  27. package/esm/dev-server/dev-logger.mjs.map +1 -1
  28. package/esm/dev-server/development-server.mjs +6 -3
  29. package/esm/dev-server/development-server.mjs.map +1 -1
  30. package/esm/dev-server/files-orchestrator.mjs +17 -1
  31. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  32. package/esm/dev-server/legacy-locales-warning.mjs +16 -0
  33. package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
  34. package/esm/dev-server/model-resource-typings.mjs +246 -0
  35. package/esm/dev-server/model-resource-typings.mjs.map +1 -0
  36. package/esm/dev-server/module-loader.mjs +16 -0
  37. package/esm/dev-server/module-loader.mjs.map +1 -1
  38. package/esm/dev-server/special-file-patterns.mjs +2 -2
  39. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  40. package/esm/dev-server/translation-keys-sources.mjs +7 -3
  41. package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
  42. package/esm/dev-server/type-generator.mjs +73 -6
  43. package/esm/dev-server/type-generator.mjs.map +1 -1
  44. package/esm/generations/features/access.feature.mjs +38 -18
  45. package/esm/generations/features/access.feature.mjs.map +1 -1
  46. package/esm/generations/features/bull-board.feature.mjs +2 -2
  47. package/esm/generations/features/bull-board.feature.mjs.map +1 -1
  48. package/esm/http/build-rate-limit-options.d.mts +8 -2
  49. package/esm/http/build-rate-limit-options.d.mts.map +1 -1
  50. package/esm/http/build-rate-limit-options.mjs +1 -0
  51. package/esm/http/build-rate-limit-options.mjs.map +1 -1
  52. package/esm/http/errors/errors.d.mts +1 -17
  53. package/esm/http/errors/errors.d.mts.map +1 -1
  54. package/esm/http/errors/errors.mjs +1 -20
  55. package/esm/http/errors/errors.mjs.map +1 -1
  56. package/esm/http/index.d.mts +4 -3
  57. package/esm/http/index.mjs +1 -1
  58. package/esm/http/middleware/inject-request-context.d.mts +1 -1
  59. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  60. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  61. package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
  62. package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
  63. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  64. package/esm/http/request.d.mts +9 -26
  65. package/esm/http/request.d.mts.map +1 -1
  66. package/esm/http/request.mjs +1 -22
  67. package/esm/http/request.mjs.map +1 -1
  68. package/esm/http/response.d.mts.map +1 -1
  69. package/esm/http/response.mjs +4 -2
  70. package/esm/http/response.mjs.map +1 -1
  71. package/esm/http/serialized.d.mts +114 -0
  72. package/esm/http/serialized.d.mts.map +1 -0
  73. package/esm/index.d.mts +12 -5
  74. package/esm/index.mjs +8 -2
  75. package/esm/localization/index.mjs +4 -0
  76. package/esm/localization/parse-locale-dictionary.d.mts +23 -0
  77. package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
  78. package/esm/localization/parse-locale-dictionary.mjs +215 -0
  79. package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
  80. package/esm/localization/register-locale-dictionary.d.mts +12 -0
  81. package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
  82. package/esm/localization/register-locale-dictionary.mjs +58 -0
  83. package/esm/localization/register-locale-dictionary.mjs.map +1 -0
  84. package/esm/openapi/build-openapi-document.d.mts +17 -0
  85. package/esm/openapi/build-openapi-document.d.mts.map +1 -0
  86. package/esm/openapi/build-openapi-document.mjs +124 -0
  87. package/esm/openapi/build-openapi-document.mjs.map +1 -0
  88. package/esm/openapi/cast-schema.mjs +89 -0
  89. package/esm/openapi/cast-schema.mjs.map +1 -0
  90. package/esm/openapi/collect-openapi-document.mjs +40 -0
  91. package/esm/openapi/collect-openapi-document.mjs.map +1 -0
  92. package/esm/openapi/development-openapi-provider.d.mts +17 -0
  93. package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
  94. package/esm/openapi/development-openapi-provider.mjs +21 -0
  95. package/esm/openapi/development-openapi-provider.mjs.map +1 -0
  96. package/esm/openapi/error-responses.mjs +67 -0
  97. package/esm/openapi/error-responses.mjs.map +1 -0
  98. package/esm/openapi/index.d.mts +3 -0
  99. package/esm/openapi/index.mjs +4 -0
  100. package/esm/openapi/openapi-child-output.mjs +57 -0
  101. package/esm/openapi/openapi-child-output.mjs.map +1 -0
  102. package/esm/openapi/openapi-protocol.mjs +30 -0
  103. package/esm/openapi/openapi-protocol.mjs.map +1 -0
  104. package/esm/openapi/paths.mjs +63 -0
  105. package/esm/openapi/paths.mjs.map +1 -0
  106. package/esm/openapi/request-schemas.mjs +138 -0
  107. package/esm/openapi/request-schemas.mjs.map +1 -0
  108. package/esm/openapi/response-schemas.mjs +85 -0
  109. package/esm/openapi/response-schemas.mjs.map +1 -0
  110. package/esm/openapi/schema-registry.mjs +143 -0
  111. package/esm/openapi/schema-registry.mjs.map +1 -0
  112. package/esm/openapi/security.mjs +98 -0
  113. package/esm/openapi/security.mjs.map +1 -0
  114. package/esm/openapi/types.d.mts +119 -0
  115. package/esm/openapi/types.d.mts.map +1 -0
  116. package/esm/production/production-builder.mjs +11 -2
  117. package/esm/production/production-builder.mjs.map +1 -1
  118. package/esm/production/registration-child-runner.mjs +139 -0
  119. package/esm/production/registration-child-runner.mjs.map +1 -0
  120. package/esm/production/route-registration-child.mjs +30 -2
  121. package/esm/production/route-registration-child.mjs.map +1 -1
  122. package/esm/production/route-registration-snapshot.mjs +22 -119
  123. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  124. package/esm/resource/define-resource.d.mts +44 -14
  125. package/esm/resource/define-resource.d.mts.map +1 -1
  126. package/esm/resource/define-resource.mjs +0 -40
  127. package/esm/resource/define-resource.mjs.map +1 -1
  128. package/esm/resource/index.d.mts +1 -1
  129. package/esm/resource/resource.d.mts +4 -4
  130. package/esm/resource/resource.d.mts.map +1 -1
  131. package/esm/resource/resource.mjs.map +1 -1
  132. package/esm/resource/types.d.mts +89 -4
  133. package/esm/resource/types.d.mts.map +1 -1
  134. package/esm/restful/restful.d.mts +5 -5
  135. package/esm/restful/restful.d.mts.map +1 -1
  136. package/esm/router/index.d.mts +1 -1
  137. package/esm/router/named-api-routes-with-responses.mjs +59 -0
  138. package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
  139. package/esm/router/response-schema-types.mjs +100 -0
  140. package/esm/router/response-schema-types.mjs.map +1 -0
  141. package/esm/router/router.d.mts +2 -2
  142. package/esm/router/router.d.mts.map +1 -1
  143. package/esm/router/router.mjs +38 -11
  144. package/esm/router/router.mjs.map +1 -1
  145. package/esm/router/types.d.mts +24 -1
  146. package/esm/router/types.d.mts.map +1 -1
  147. package/esm/tests/start-http-development-server.d.mts +10 -0
  148. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  149. package/esm/tests/start-http-development-server.mjs +16 -2
  150. package/esm/tests/start-http-development-server.mjs.map +1 -1
  151. package/esm/validation/validateAll.mjs +48 -1
  152. package/esm/validation/validateAll.mjs.map +1 -1
  153. package/llms-full.txt +383 -80
  154. package/llms.txt +4 -3
  155. package/package.json +21 -21
  156. package/skills/README.md +4 -3
  157. package/skills/create-controller/SKILL.md +43 -2
  158. package/skills/define-resource/SKILL.md +100 -1
  159. package/skills/generate-openapi/SKILL.md +120 -0
  160. package/skills/index.md +3 -3
  161. package/skills/test-http/SKILL.md +9 -1
  162. package/skills/use-localization/SKILL.md +39 -26
  163. package/skills/validate-input/SKILL.md +2 -2
@@ -0,0 +1,85 @@
1
+ //#region ../core/src/openapi/response-schemas.ts
2
+ const STATUS_TEXT = {
3
+ "200": "Successful response",
4
+ "201": "Created",
5
+ "202": "Accepted",
6
+ "204": "No content",
7
+ "301": "Moved permanently",
8
+ "302": "Found",
9
+ "304": "Not modified",
10
+ "400": "Bad request",
11
+ "401": "Unauthorized",
12
+ "403": "Forbidden",
13
+ "404": "Not found",
14
+ "405": "Method not allowed",
15
+ "409": "Conflict",
16
+ "410": "Gone",
17
+ "422": "Validation failed",
18
+ "429": "Too many requests",
19
+ "500": "Internal server error",
20
+ "503": "Service unavailable"
21
+ };
22
+ function isRecord(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24
+ }
25
+ /**
26
+ * Human description of a status code for the required `description` of a response.
27
+ */
28
+ function describeStatus(status) {
29
+ return STATUS_TEXT[status] ?? `Status ${status}`;
30
+ }
31
+ /**
32
+ * JSON Schema of one response body value: a cast string, a resource class, `[Resource]`
33
+ * (array of it) or a nested plain object of those. Anything else (a resolver function, a
34
+ * builder) is `{}` plus a warning.
35
+ */
36
+ function bodyValueSchema(value, path, registry, warn) {
37
+ if (typeof value === "string") return registry.cast(value, path, warn);
38
+ if (typeof value === "function") {
39
+ if (registry.isResourceClass(value)) return registry.resourceRef(value);
40
+ warn(`${path}: a function that is not a resource class has no static shape and is documented as {}.`);
41
+ return {};
42
+ }
43
+ if (Array.isArray(value)) return {
44
+ type: "array",
45
+ items: bodyValueSchema(value[0], `${path}[]`, registry, warn)
46
+ };
47
+ if (isRecord(value)) return objectSchema(value, path, registry, warn);
48
+ warn(`${path}: unsupported response value is documented as {}.`);
49
+ return {};
50
+ }
51
+ function objectSchema(body, path, registry, warn) {
52
+ const properties = {};
53
+ for (const [key, member] of Object.entries(body)) properties[key] = bodyValueSchema(member, `${path}.${key}`, registry, warn);
54
+ return {
55
+ type: "object",
56
+ properties,
57
+ required: Object.keys(properties)
58
+ };
59
+ }
60
+ /**
61
+ * Map a handler's `responseSchema` to OpenAPI responses, one per declared status code.
62
+ *
63
+ * `routeLabel` prefixes warnings (for example `POST /login`). A status without a usable
64
+ * `body` becomes a description-only response.
65
+ */
66
+ function buildDeclaredResponses(responseSchema, registry, warn) {
67
+ const responses = {};
68
+ if (!isRecord(responseSchema)) return responses;
69
+ for (const [status, entry] of Object.entries(responseSchema)) {
70
+ const body = isRecord(entry) ? entry.body : void 0;
71
+ if (!isRecord(body)) {
72
+ responses[status] = { description: describeStatus(status) };
73
+ continue;
74
+ }
75
+ responses[status] = {
76
+ description: describeStatus(status),
77
+ content: { "application/json": { schema: objectSchema(body, `response ${status}`, registry, warn) } }
78
+ };
79
+ }
80
+ return responses;
81
+ }
82
+
83
+ //#endregion
84
+ export { buildDeclaredResponses, describeStatus };
85
+ //# sourceMappingURL=response-schemas.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-schemas.mjs","names":[],"sources":["../../../../../../../core/src/openapi/response-schemas.ts"],"sourcesContent":["import type { SchemaRegistry } from \"./schema-registry\";\nimport type { OpenApiResponse, OpenApiSchema, WarningSink } from \"./types\";\n\nconst STATUS_TEXT: Record<string, string> = {\n \"200\": \"Successful response\",\n \"201\": \"Created\",\n \"202\": \"Accepted\",\n \"204\": \"No content\",\n \"301\": \"Moved permanently\",\n \"302\": \"Found\",\n \"304\": \"Not modified\",\n \"400\": \"Bad request\",\n \"401\": \"Unauthorized\",\n \"403\": \"Forbidden\",\n \"404\": \"Not found\",\n \"405\": \"Method not allowed\",\n \"409\": \"Conflict\",\n \"410\": \"Gone\",\n \"422\": \"Validation failed\",\n \"429\": \"Too many requests\",\n \"500\": \"Internal server error\",\n \"503\": \"Service unavailable\",\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Human description of a status code for the required `description` of a response.\n */\nexport function describeStatus(status: string): string {\n return STATUS_TEXT[status] ?? `Status ${status}`;\n}\n\n/**\n * JSON Schema of one response body value: a cast string, a resource class, `[Resource]`\n * (array of it) or a nested plain object of those. Anything else (a resolver function, a\n * builder) is `{}` plus a warning.\n */\nfunction bodyValueSchema(value: unknown, path: string, registry: SchemaRegistry, warn: WarningSink): OpenApiSchema {\n if (typeof value === \"string\") {\n return registry.cast(value, path, warn);\n }\n\n if (typeof value === \"function\") {\n if (registry.isResourceClass(value)) {\n return registry.resourceRef(value);\n }\n\n warn(`${path}: a function that is not a resource class has no static shape and is documented as {}.`);\n\n return {};\n }\n\n if (Array.isArray(value)) {\n return { type: \"array\", items: bodyValueSchema(value[0], `${path}[]`, registry, warn) };\n }\n\n if (isRecord(value)) {\n return objectSchema(value, path, registry, warn);\n }\n\n warn(`${path}: unsupported response value is documented as {}.`);\n\n return {};\n}\n\nfunction objectSchema(\n body: Record<string, unknown>,\n path: string,\n registry: SchemaRegistry,\n warn: WarningSink,\n): OpenApiSchema {\n const properties: Record<string, OpenApiSchema> = {};\n\n for (const [key, member] of Object.entries(body)) {\n properties[key] = bodyValueSchema(member, `${path}.${key}`, registry, warn);\n }\n\n return { type: \"object\", properties, required: Object.keys(properties) };\n}\n\n/**\n * Map a handler's `responseSchema` to OpenAPI responses, one per declared status code.\n *\n * `routeLabel` prefixes warnings (for example `POST /login`). A status without a usable\n * `body` becomes a description-only response.\n */\nexport function buildDeclaredResponses(\n responseSchema: unknown,\n registry: SchemaRegistry,\n warn: WarningSink,\n): Record<string, OpenApiResponse> {\n const responses: Record<string, OpenApiResponse> = {};\n\n if (!isRecord(responseSchema)) {\n return responses;\n }\n\n for (const [status, entry] of Object.entries(responseSchema)) {\n const body = isRecord(entry) ? entry.body : undefined;\n\n if (!isRecord(body)) {\n responses[status] = { description: describeStatus(status) };\n\n continue;\n }\n\n responses[status] = {\n description: describeStatus(status),\n content: {\n \"application/json\": { schema: objectSchema(body, `response ${status}`, registry, warn) },\n },\n };\n }\n\n return responses;\n}\n"],"mappings":";AAGA,MAAM,cAAsC;CAC1C,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;AACT;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;;AAKA,SAAgB,eAAe,QAAwB;CACrD,OAAO,YAAY,WAAW,UAAU;AAC1C;;;;;;AAOA,SAAS,gBAAgB,OAAgB,MAAc,UAA0B,MAAkC;CACjH,IAAI,OAAO,UAAU,UACnB,OAAO,SAAS,KAAK,OAAO,MAAM,IAAI;CAGxC,IAAI,OAAO,UAAU,YAAY;EAC/B,IAAI,SAAS,gBAAgB,KAAK,GAChC,OAAO,SAAS,YAAY,KAAK;EAGnC,KAAK,GAAG,KAAK,uFAAuF;EAEpG,OAAO,CAAC;CACV;CAEA,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO;EAAE,MAAM;EAAS,OAAO,gBAAgB,MAAM,IAAI,GAAG,KAAK,KAAK,UAAU,IAAI;CAAE;CAGxF,IAAI,SAAS,KAAK,GAChB,OAAO,aAAa,OAAO,MAAM,UAAU,IAAI;CAGjD,KAAK,GAAG,KAAK,kDAAkD;CAE/D,OAAO,CAAC;AACV;AAEA,SAAS,aACP,MACA,MACA,UACA,MACe;CACf,MAAM,aAA4C,CAAC;CAEnD,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,IAAI,GAC7C,WAAW,OAAO,gBAAgB,QAAQ,GAAG,KAAK,GAAG,OAAO,UAAU,IAAI;CAG5E,OAAO;EAAE,MAAM;EAAU;EAAY,UAAU,OAAO,KAAK,UAAU;CAAE;AACzE;;;;;;;AAQA,SAAgB,uBACd,gBACA,UACA,MACiC;CACjC,MAAM,YAA6C,CAAC;CAEpD,IAAI,CAAC,SAAS,cAAc,GAC1B,OAAO;CAGT,KAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,cAAc,GAAG;EAC5D,MAAM,OAAO,SAAS,KAAK,IAAI,MAAM,OAAO;EAE5C,IAAI,CAAC,SAAS,IAAI,GAAG;GACnB,UAAU,UAAU,EAAE,aAAa,eAAe,MAAM,EAAE;GAE1D;EACF;EAEA,UAAU,UAAU;GAClB,aAAa,eAAe,MAAM;GAClC,SAAS,EACP,oBAAoB,EAAE,QAAQ,aAAa,MAAM,YAAY,UAAU,UAAU,IAAI,EAAE,EACzF;EACF;CACF;CAEA,OAAO;AACT"}
@@ -0,0 +1,143 @@
1
+ import { castToJsonSchema } from "./cast-schema.mjs";
2
+ import { isLazy } from "@mongez/reinforcements";
3
+
4
+ //#region ../core/src/openapi/schema-registry.ts
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function sanitizeName(name) {
9
+ return name.replace(/[^A-Za-z0-9._-]/g, "");
10
+ }
11
+ /**
12
+ * Owns `components.schemas`: names resource classes (by identity), builds their schemas once
13
+ * and hands out `$ref`s, so nested, lazy and `"self"` references recurse without unrolling.
14
+ */
15
+ var SchemaRegistry = class {
16
+ constructor(resolveName, warn) {
17
+ this.resolveName = resolveName;
18
+ this.warn = warn;
19
+ this.schemas = {};
20
+ this.resourceNames = /* @__PURE__ */ new Map();
21
+ this.sharedNames = /* @__PURE__ */ new Map();
22
+ this.fallbackCounter = 0;
23
+ }
24
+ /**
25
+ * Register a fixed schema (an error shape shared by many operations) under a stable key.
26
+ * Returns its `$ref`; the component name gets a numeric suffix only if a resource took it.
27
+ */
28
+ shared(key, schema) {
29
+ let name = this.sharedNames.get(key);
30
+ if (!name) {
31
+ name = this.claim(key);
32
+ this.sharedNames.set(key, name);
33
+ this.schemas[name] = schema;
34
+ }
35
+ return { $ref: `#/components/schemas/${name}` };
36
+ }
37
+ /**
38
+ * `$ref` to a resource class, building its component on first use.
39
+ */
40
+ resourceRef(resource) {
41
+ const existing = this.resourceNames.get(resource);
42
+ if (existing) return { $ref: `#/components/schemas/${existing}` };
43
+ const name = this.claim(this.nameFor(resource));
44
+ const ref = { $ref: `#/components/schemas/${name}` };
45
+ this.resourceNames.set(resource, name);
46
+ this.schemas[name] = {};
47
+ this.schemas[name] = this.buildResourceSchema(resource, name, ref);
48
+ return ref;
49
+ }
50
+ /**
51
+ * Schema of a cast string, or `{}` and a warning for an unknown cast.
52
+ */
53
+ cast(cast, path, warn = this.warn) {
54
+ const schema = castToJsonSchema(cast);
55
+ if (!schema) {
56
+ warn(`${path}: unknown cast "${cast}" is documented as {}.`);
57
+ return {};
58
+ }
59
+ return schema;
60
+ }
61
+ /**
62
+ * True when the function looks like a resource class: it carries a static `schema` object.
63
+ */
64
+ isResourceClass(value) {
65
+ return typeof value === "function" && isRecord(Reflect.get(value, "schema"));
66
+ }
67
+ nameFor(resource) {
68
+ const resolved = sanitizeName(this.resolveName?.(resource) ?? "");
69
+ if (resolved && resolved !== "default") return resolved;
70
+ const className = typeof resource === "function" ? sanitizeName(resource.name) : "";
71
+ if (className && className !== "default") return className;
72
+ return `Resource${++this.fallbackCounter}`;
73
+ }
74
+ claim(base) {
75
+ if (!(base in this.schemas)) return base;
76
+ let suffix = 2;
77
+ while (`${base}${suffix}` in this.schemas) suffix++;
78
+ return `${base}${suffix}`;
79
+ }
80
+ buildResourceSchema(resource, name, ref) {
81
+ const schema = Reflect.get(resource, "schema");
82
+ if (!isRecord(schema) || Object.keys(schema).length === 0) {
83
+ this.warn(`Resource "${name}" declares no static schema and is documented as {}.`);
84
+ return {};
85
+ }
86
+ const properties = {};
87
+ for (const [key, config] of Object.entries(schema)) properties[key] = this.fieldSchema(config, {
88
+ ownerRef: ref,
89
+ path: `${name}.${key}`
90
+ });
91
+ return {
92
+ type: "object",
93
+ properties,
94
+ required: Object.keys(properties)
95
+ };
96
+ }
97
+ fieldSchema(config, context) {
98
+ if (config === "self") return context.ownerRef;
99
+ if (config === "self[]") return {
100
+ type: "array",
101
+ items: context.ownerRef
102
+ };
103
+ if (typeof config === "string") return this.cast(config, context.path);
104
+ if (isLazy(config)) return this.lazyFieldSchema(config, context);
105
+ if (Array.isArray(config) && config.length === 2 && typeof config[1] === "string") return this.cast(config[1], context.path);
106
+ if (typeof config === "function") {
107
+ if (this.isResourceClass(config)) return this.resourceRef(config);
108
+ this.warn(`${context.path}: a resolver function has no static shape and is documented as {}.`);
109
+ return {};
110
+ }
111
+ if (isRecord(config) && config.__type === "arrayOf" && isRecord(config.schema)) {
112
+ const properties = {};
113
+ for (const [key, item] of Object.entries(config.schema)) properties[key] = this.fieldSchema(item, {
114
+ ...context,
115
+ path: `${context.path}.${key}`
116
+ });
117
+ return {
118
+ type: "array",
119
+ items: {
120
+ type: "object",
121
+ properties,
122
+ required: Object.keys(properties)
123
+ }
124
+ };
125
+ }
126
+ this.warn(`${context.path}: a field builder has no static shape and is documented as {}. Declare it with a cast string to document it.`);
127
+ return {};
128
+ }
129
+ lazyFieldSchema(lazy, context) {
130
+ let resolved;
131
+ try {
132
+ resolved = lazy.resolve();
133
+ } catch (error) {
134
+ this.warn(`${context.path}: a lazy resource reference could not be resolved (${error instanceof Error ? error.message : String(error)}) and is documented as {}.`);
135
+ return {};
136
+ }
137
+ return this.fieldSchema(resolved, context);
138
+ }
139
+ };
140
+
141
+ //#endregion
142
+ export { SchemaRegistry };
143
+ //# sourceMappingURL=schema-registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-registry.mjs","names":[],"sources":["../../../../../../../core/src/openapi/schema-registry.ts"],"sourcesContent":["import { isLazy } from \"@mongez/reinforcements\";\nimport { castToJsonSchema } from \"./cast-schema\";\nimport type { OpenApiSchema, WarningSink } from \"./types\";\n\ntype ResourceFieldContext = {\n /** `$ref` schema of the resource whose field is being mapped, the target of `\"self\"`. */\n ownerRef: OpenApiSchema;\n /** Display name used in warnings, for example `UserResource.avatar`. */\n path: string;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction sanitizeName(name: string): string {\n return name.replace(/[^A-Za-z0-9._-]/g, \"\");\n}\n\n/**\n * Owns `components.schemas`: names resource classes (by identity), builds their schemas once\n * and hands out `$ref`s, so nested, lazy and `\"self\"` references recurse without unrolling.\n */\nexport class SchemaRegistry {\n public readonly schemas: Record<string, OpenApiSchema> = {};\n\n private readonly resourceNames = new Map<unknown, string>();\n private readonly sharedNames = new Map<string, string>();\n private fallbackCounter = 0;\n\n public constructor(\n private readonly resolveName: ((resource: unknown) => string | undefined) | undefined,\n private readonly warn: WarningSink,\n ) {}\n\n /**\n * Register a fixed schema (an error shape shared by many operations) under a stable key.\n * Returns its `$ref`; the component name gets a numeric suffix only if a resource took it.\n */\n public shared(key: string, schema: OpenApiSchema): OpenApiSchema {\n let name = this.sharedNames.get(key);\n\n if (!name) {\n name = this.claim(key);\n this.sharedNames.set(key, name);\n this.schemas[name] = schema;\n }\n\n return { $ref: `#/components/schemas/${name}` };\n }\n\n /**\n * `$ref` to a resource class, building its component on first use.\n */\n public resourceRef(resource: unknown): OpenApiSchema {\n const existing = this.resourceNames.get(resource);\n\n if (existing) {\n return { $ref: `#/components/schemas/${existing}` };\n }\n\n const name = this.claim(this.nameFor(resource));\n const ref = { $ref: `#/components/schemas/${name}` };\n\n // Named before its fields are mapped, so a cycle resolves to this same ref.\n this.resourceNames.set(resource, name);\n this.schemas[name] = {};\n this.schemas[name] = this.buildResourceSchema(resource, name, ref);\n\n return ref;\n }\n\n /**\n * Schema of a cast string, or `{}` and a warning for an unknown cast.\n */\n public cast(cast: string, path: string, warn: WarningSink = this.warn): OpenApiSchema {\n const schema = castToJsonSchema(cast);\n\n if (!schema) {\n warn(`${path}: unknown cast \"${cast}\" is documented as {}.`);\n\n return {};\n }\n\n return schema;\n }\n\n /**\n * True when the function looks like a resource class: it carries a static `schema` object.\n */\n public isResourceClass(value: unknown): boolean {\n return typeof value === \"function\" && isRecord(Reflect.get(value, \"schema\"));\n }\n\n private nameFor(resource: unknown): string {\n const resolved = sanitizeName(this.resolveName?.(resource) ?? \"\");\n\n if (resolved && resolved !== \"default\") {\n return resolved;\n }\n\n const className = typeof resource === \"function\" ? sanitizeName(resource.name) : \"\";\n\n if (className && className !== \"default\") {\n return className;\n }\n\n return `Resource${++this.fallbackCounter}`;\n }\n\n private claim(base: string): string {\n if (!(base in this.schemas)) {\n return base;\n }\n\n let suffix = 2;\n\n while (`${base}${suffix}` in this.schemas) {\n suffix++;\n }\n\n return `${base}${suffix}`;\n }\n\n private buildResourceSchema(resource: unknown, name: string, ref: OpenApiSchema): OpenApiSchema {\n const schema = Reflect.get(resource as object, \"schema\");\n\n if (!isRecord(schema) || Object.keys(schema).length === 0) {\n this.warn(`Resource \"${name}\" declares no static schema and is documented as {}.`);\n\n return {};\n }\n\n const properties: Record<string, OpenApiSchema> = {};\n\n for (const [key, config] of Object.entries(schema)) {\n properties[key] = this.fieldSchema(config, { ownerRef: ref, path: `${name}.${key}` });\n }\n\n return { type: \"object\", properties, required: Object.keys(properties) };\n }\n\n private fieldSchema(config: unknown, context: ResourceFieldContext): OpenApiSchema {\n if (config === \"self\") {\n return context.ownerRef;\n }\n\n if (config === \"self[]\") {\n return { type: \"array\", items: context.ownerRef };\n }\n\n if (typeof config === \"string\") {\n return this.cast(config, context.path);\n }\n\n if (isLazy(config)) {\n return this.lazyFieldSchema(config, context);\n }\n\n if (Array.isArray(config) && config.length === 2 && typeof config[1] === \"string\") {\n return this.cast(config[1], context.path);\n }\n\n if (typeof config === \"function\") {\n if (this.isResourceClass(config)) {\n return this.resourceRef(config);\n }\n\n this.warn(`${context.path}: a resolver function has no static shape and is documented as {}.`);\n\n return {};\n }\n\n if (isRecord(config) && config.__type === \"arrayOf\" && isRecord(config.schema)) {\n const properties: Record<string, OpenApiSchema> = {};\n\n for (const [key, item] of Object.entries(config.schema)) {\n properties[key] = this.fieldSchema(item, { ...context, path: `${context.path}.${key}` });\n }\n\n return {\n type: \"array\",\n items: { type: \"object\", properties, required: Object.keys(properties) },\n };\n }\n\n this.warn(\n `${context.path}: a field builder has no static shape and is documented as {}. Declare it with a cast string to document it.`,\n );\n\n return {};\n }\n\n private lazyFieldSchema(lazy: { resolve(): unknown }, context: ResourceFieldContext): OpenApiSchema {\n let resolved: unknown;\n\n try {\n resolved = lazy.resolve();\n } catch (error) {\n this.warn(\n `${context.path}: a lazy resource reference could not be resolved (${\n error instanceof Error ? error.message : String(error)\n }) and is documented as {}.`,\n );\n\n return {};\n }\n\n return this.fieldSchema(resolved, context);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ //#region ../core/src/openapi/security.ts
2
+ /**
3
+ * The marker `authMiddleware()` attaches to the middleware it returns.
4
+ * Core reads it structurally; it never imports the auth package.
5
+ */
6
+ const AUTH_DESCRIPTOR_SYMBOL = Symbol.for("warlock.auth");
7
+ function isAuthSource(value) {
8
+ if (value === "header") return true;
9
+ return typeof value === "object" && value !== null && typeof value.cookie === "string" && value.cookie.length > 0;
10
+ }
11
+ /**
12
+ * Validate an auth descriptor. Returns `undefined` for anything that is not
13
+ * `{ sources: ("header" | { cookie: string })[], userTypes?: string[] }` with at least one source.
14
+ */
15
+ function parseAuthDescriptor(value) {
16
+ if (typeof value !== "object" || value === null) return;
17
+ const { sources, userTypes } = value;
18
+ if (!Array.isArray(sources) || sources.length === 0 || !sources.every(isAuthSource)) return;
19
+ if (userTypes !== void 0 && !(Array.isArray(userTypes) && userTypes.every((type) => typeof type === "string"))) return;
20
+ return {
21
+ sources: [...sources],
22
+ userTypes: Array.isArray(userTypes) ? [...userTypes] : []
23
+ };
24
+ }
25
+ /**
26
+ * Combine the descriptors of every guard on a route (group middleware is already merged into
27
+ * the route): unique sources, union of user types.
28
+ *
29
+ * Middleware without a descriptor is ordinary middleware and is ignored silently. A function
30
+ * that carries a descriptor of the wrong shape is reported through `warn`.
31
+ *
32
+ * @returns `undefined` when the route has no valid auth descriptor.
33
+ */
34
+ function readRouteAuth(middleware, warn) {
35
+ if (!Array.isArray(middleware)) return;
36
+ const sources = [];
37
+ const userTypes = [];
38
+ let found = false;
39
+ for (const fn of middleware) {
40
+ if (typeof fn !== "function") continue;
41
+ const raw = Reflect.get(fn, AUTH_DESCRIPTOR_SYMBOL);
42
+ if (raw === void 0) continue;
43
+ const descriptor = parseAuthDescriptor(raw);
44
+ if (!descriptor) {
45
+ warn("a middleware carries a malformed warlock.auth descriptor; the route is documented without security.");
46
+ continue;
47
+ }
48
+ found = true;
49
+ for (const source of descriptor.sources) if (!sources.some((existing) => typeof existing === "string" || typeof source === "string" ? existing === source : existing.cookie === source.cookie)) sources.push(source);
50
+ for (const type of descriptor.userTypes) if (!userTypes.includes(type)) userTypes.push(type);
51
+ }
52
+ return found ? {
53
+ sources,
54
+ userTypes
55
+ } : void 0;
56
+ }
57
+ /**
58
+ * Collects the security schemes the routes actually use and turns descriptors into
59
+ * per-operation requirements. A dual header + cookie guard is an OR: one requirement each.
60
+ */
61
+ var SecuritySchemes = class {
62
+ constructor() {
63
+ this.schemes = {};
64
+ this.cookieSchemeNames = /* @__PURE__ */ new Map();
65
+ }
66
+ requirementsFor(descriptor) {
67
+ return descriptor.sources.map((source) => {
68
+ if (source === "header") {
69
+ this.schemes.bearerAuth = {
70
+ type: "http",
71
+ scheme: "bearer",
72
+ bearerFormat: "JWT"
73
+ };
74
+ return { bearerAuth: [] };
75
+ }
76
+ return { [this.cookieScheme(source.cookie)]: [] };
77
+ });
78
+ }
79
+ cookieScheme(cookie) {
80
+ const existing = this.cookieSchemeNames.get(cookie);
81
+ if (existing) return existing;
82
+ const name = this.cookieSchemeNames.size === 0 ? "cookieAuth" : `cookieAuth_${cookie.replace(/[^A-Za-z0-9._-]/g, "")}`;
83
+ this.cookieSchemeNames.set(cookie, name);
84
+ this.schemes[name] = {
85
+ type: "apiKey",
86
+ in: "cookie",
87
+ name: cookie
88
+ };
89
+ return name;
90
+ }
91
+ hasSchemes() {
92
+ return Object.keys(this.schemes).length > 0;
93
+ }
94
+ };
95
+
96
+ //#endregion
97
+ export { SecuritySchemes, readRouteAuth };
98
+ //# sourceMappingURL=security.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.mjs","names":[],"sources":["../../../../../../../core/src/openapi/security.ts"],"sourcesContent":["import type { AuthDescriptor, OpenApiSecurityRequirement, OpenApiSecurityScheme, WarningSink } from \"./types\";\n\n/**\n * The marker `authMiddleware()` attaches to the middleware it returns.\n * Core reads it structurally; it never imports the auth package.\n */\nexport const AUTH_DESCRIPTOR_SYMBOL = Symbol.for(\"warlock.auth\");\n\nfunction isAuthSource(value: unknown): value is AuthDescriptor[\"sources\"][number] {\n if (value === \"header\") {\n return true;\n }\n\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { cookie?: unknown }).cookie === \"string\" &&\n (value as { cookie: string }).cookie.length > 0\n );\n}\n\n/**\n * Validate an auth descriptor. Returns `undefined` for anything that is not\n * `{ sources: (\"header\" | { cookie: string })[], userTypes?: string[] }` with at least one source.\n */\nexport function parseAuthDescriptor(value: unknown): AuthDescriptor | undefined {\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n const { sources, userTypes } = value as { sources?: unknown; userTypes?: unknown };\n\n if (!Array.isArray(sources) || sources.length === 0 || !sources.every(isAuthSource)) {\n return undefined;\n }\n\n if (userTypes !== undefined && !(Array.isArray(userTypes) && userTypes.every((type) => typeof type === \"string\"))) {\n return undefined;\n }\n\n return { sources: [...sources], userTypes: Array.isArray(userTypes) ? [...userTypes] : [] };\n}\n\n/**\n * Combine the descriptors of every guard on a route (group middleware is already merged into\n * the route): unique sources, union of user types.\n *\n * Middleware without a descriptor is ordinary middleware and is ignored silently. A function\n * that carries a descriptor of the wrong shape is reported through `warn`.\n *\n * @returns `undefined` when the route has no valid auth descriptor.\n */\nexport function readRouteAuth(middleware: unknown, warn: WarningSink): AuthDescriptor | undefined {\n if (!Array.isArray(middleware)) {\n return undefined;\n }\n\n const sources: AuthDescriptor[\"sources\"] = [];\n const userTypes: string[] = [];\n let found = false;\n\n for (const fn of middleware) {\n if (typeof fn !== \"function\") {\n continue;\n }\n\n const raw = Reflect.get(fn, AUTH_DESCRIPTOR_SYMBOL);\n\n if (raw === undefined) {\n continue;\n }\n\n const descriptor = parseAuthDescriptor(raw);\n\n if (!descriptor) {\n warn(\"a middleware carries a malformed warlock.auth descriptor; the route is documented without security.\");\n\n continue;\n }\n\n found = true;\n\n for (const source of descriptor.sources) {\n const duplicate = sources.some((existing) =>\n typeof existing === \"string\" || typeof source === \"string\"\n ? existing === source\n : existing.cookie === source.cookie,\n );\n\n if (!duplicate) {\n sources.push(source);\n }\n }\n\n for (const type of descriptor.userTypes) {\n if (!userTypes.includes(type)) {\n userTypes.push(type);\n }\n }\n }\n\n return found ? { sources, userTypes } : undefined;\n}\n\n/**\n * Collects the security schemes the routes actually use and turns descriptors into\n * per-operation requirements. A dual header + cookie guard is an OR: one requirement each.\n */\nexport class SecuritySchemes {\n public readonly schemes: Record<string, OpenApiSecurityScheme> = {};\n\n private readonly cookieSchemeNames = new Map<string, string>();\n\n public requirementsFor(descriptor: AuthDescriptor): OpenApiSecurityRequirement[] {\n return descriptor.sources.map((source) => {\n if (source === \"header\") {\n this.schemes.bearerAuth = { type: \"http\", scheme: \"bearer\", bearerFormat: \"JWT\" };\n\n return { bearerAuth: [] };\n }\n\n const name = this.cookieScheme(source.cookie);\n\n return { [name]: [] };\n });\n }\n\n private cookieScheme(cookie: string): string {\n const existing = this.cookieSchemeNames.get(cookie);\n\n if (existing) {\n return existing;\n }\n\n // The first cookie keeps the conventional name; a second, different cookie gets its own.\n const name =\n this.cookieSchemeNames.size === 0 ? \"cookieAuth\" : `cookieAuth_${cookie.replace(/[^A-Za-z0-9._-]/g, \"\")}`;\n\n this.cookieSchemeNames.set(cookie, name);\n this.schemes[name] = { type: \"apiKey\", in: \"cookie\", name: cookie };\n\n return name;\n }\n\n public hasSchemes(): boolean {\n return Object.keys(this.schemes).length > 0;\n }\n}\n"],"mappings":";;;;;AAMA,MAAa,yBAAyB,OAAO,IAAI,cAAc;AAE/D,SAAS,aAAa,OAA4D;CAChF,IAAI,UAAU,UACZ,OAAO;CAGT,OACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAA+B,WAAW,YACjD,MAA6B,OAAO,SAAS;AAElD;;;;;AAMA,SAAgB,oBAAoB,OAA4C;CAC9E,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC;CAGF,MAAM,EAAE,SAAS,cAAc;CAE/B,IAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,KAAK,CAAC,QAAQ,MAAM,YAAY,GAChF;CAGF,IAAI,cAAc,UAAa,EAAE,MAAM,QAAQ,SAAS,KAAK,UAAU,OAAO,SAAS,OAAO,SAAS,QAAQ,IAC7G;CAGF,OAAO;EAAE,SAAS,CAAC,GAAG,OAAO;EAAG,WAAW,MAAM,QAAQ,SAAS,IAAI,CAAC,GAAG,SAAS,IAAI,CAAC;CAAE;AAC5F;;;;;;;;;;AAWA,SAAgB,cAAc,YAAqB,MAA+C;CAChG,IAAI,CAAC,MAAM,QAAQ,UAAU,GAC3B;CAGF,MAAM,UAAqC,CAAC;CAC5C,MAAM,YAAsB,CAAC;CAC7B,IAAI,QAAQ;CAEZ,KAAK,MAAM,MAAM,YAAY;EAC3B,IAAI,OAAO,OAAO,YAChB;EAGF,MAAM,MAAM,QAAQ,IAAI,IAAI,sBAAsB;EAElD,IAAI,QAAQ,QACV;EAGF,MAAM,aAAa,oBAAoB,GAAG;EAE1C,IAAI,CAAC,YAAY;GACf,KAAK,qGAAqG;GAE1G;EACF;EAEA,QAAQ;EAER,KAAK,MAAM,UAAU,WAAW,SAO9B,IAAI,CANc,QAAQ,MAAM,aAC9B,OAAO,aAAa,YAAY,OAAO,WAAW,WAC9C,aAAa,SACb,SAAS,WAAW,OAAO,MAGpB,GACX,QAAQ,KAAK,MAAM;EAIvB,KAAK,MAAM,QAAQ,WAAW,WAC5B,IAAI,CAAC,UAAU,SAAS,IAAI,GAC1B,UAAU,KAAK,IAAI;CAGzB;CAEA,OAAO,QAAQ;EAAE;EAAS;CAAU,IAAI;AAC1C;;;;;AAMA,IAAa,kBAAb,MAA6B;;iBACsC,CAAC;2CAE7B,IAAI,IAAoB;;CAE7D,AAAO,gBAAgB,YAA0D;EAC/E,OAAO,WAAW,QAAQ,KAAK,WAAW;GACxC,IAAI,WAAW,UAAU;IACvB,KAAK,QAAQ,aAAa;KAAE,MAAM;KAAQ,QAAQ;KAAU,cAAc;IAAM;IAEhF,OAAO,EAAE,YAAY,CAAC,EAAE;GAC1B;GAIA,OAAO,GAFM,KAAK,aAAa,OAAO,MAEzB,IAAI,CAAC,EAAE;EACtB,CAAC;CACH;CAEA,AAAQ,aAAa,QAAwB;EAC3C,MAAM,WAAW,KAAK,kBAAkB,IAAI,MAAM;EAElD,IAAI,UACF,OAAO;EAIT,MAAM,OACJ,KAAK,kBAAkB,SAAS,IAAI,eAAe,cAAc,OAAO,QAAQ,oBAAoB,EAAE;EAExG,KAAK,kBAAkB,IAAI,QAAQ,IAAI;EACvC,KAAK,QAAQ,QAAQ;GAAE,MAAM;GAAU,IAAI;GAAU,MAAM;EAAO;EAElE,OAAO;CACT;CAEA,AAAO,aAAsB;EAC3B,OAAO,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,SAAS;CAC5C;AACF"}
@@ -0,0 +1,119 @@
1
+ import { Route } from "../router/types.mjs";
2
+
3
+ //#region ../core/src/openapi/types.d.ts
4
+ /**
5
+ * A JSON Schema fragment (OpenAPI 3.1 schemas are JSON Schema 2020-12).
6
+ */
7
+ type OpenApiSchema = Record<string, unknown>;
8
+ type OpenApiParameter = {
9
+ name: string;
10
+ in: "path" | "query" | "header" | "cookie";
11
+ required?: boolean;
12
+ description?: string;
13
+ style?: "deepObject";
14
+ explode?: boolean;
15
+ schema: OpenApiSchema;
16
+ };
17
+ type OpenApiRequestBody = {
18
+ required?: boolean;
19
+ content: Record<string, {
20
+ schema: OpenApiSchema;
21
+ }>;
22
+ };
23
+ type OpenApiResponse = {
24
+ description: string;
25
+ content?: Record<string, {
26
+ schema: OpenApiSchema;
27
+ }>;
28
+ };
29
+ type OpenApiSecurityRequirement = Record<string, string[]>;
30
+ type OpenApiOperation = {
31
+ operationId: string;
32
+ summary?: string;
33
+ description?: string;
34
+ tags?: string[];
35
+ parameters?: OpenApiParameter[];
36
+ requestBody?: OpenApiRequestBody;
37
+ responses: Record<string, OpenApiResponse>;
38
+ security?: OpenApiSecurityRequirement[];
39
+ };
40
+ type OpenApiSecurityScheme = {
41
+ type: "http";
42
+ scheme: "bearer";
43
+ bearerFormat?: string;
44
+ } | {
45
+ type: "apiKey";
46
+ in: "cookie";
47
+ name: string;
48
+ };
49
+ type OpenApiComponents = {
50
+ schemas?: Record<string, OpenApiSchema>;
51
+ securitySchemes?: Record<string, OpenApiSecurityScheme>;
52
+ };
53
+ type OpenApiDocument = {
54
+ openapi: "3.1.0";
55
+ jsonSchemaDialect: string;
56
+ info: {
57
+ title: string;
58
+ version: string;
59
+ description?: string;
60
+ };
61
+ servers?: {
62
+ url: string;
63
+ }[];
64
+ tags?: {
65
+ name: string;
66
+ }[];
67
+ paths: Record<string, Record<string, OpenApiOperation>>;
68
+ components?: OpenApiComponents;
69
+ };
70
+ /**
71
+ * The `validation.response` configuration keys that shape a failed-validation response.
72
+ * Every key is optional; the documented defaults apply to the missing ones.
73
+ */
74
+ type OpenApiValidationResponseConfig = {
75
+ errors?: string;
76
+ inputKey?: string;
77
+ inputError?: string;
78
+ status?: number;
79
+ };
80
+ /**
81
+ * Everything the builder needs besides the routes. Plain values only, so the same call
82
+ * serves the CLI child and the in-process dev path.
83
+ */
84
+ type OpenApiContext = {
85
+ info: {
86
+ title: string;
87
+ version: string;
88
+ description?: string;
89
+ }; /** Absolute server URLs, first is the default. */
90
+ servers?: string[]; /** Also document page (SSR) routes. Defaults to `false`. */
91
+ includePages?: boolean;
92
+ /**
93
+ * Name a resource class for `components.schemas`, usually its export name
94
+ * (`UserResource`). Return `undefined` for an unknown resource: it is named
95
+ * `Resource1`, `Resource2`, ... in encounter order.
96
+ */
97
+ resolveResourceName?: (resource: unknown) => string | undefined; /** The app's `validation.response` config value. */
98
+ validationResponse?: OpenApiValidationResponseConfig;
99
+ };
100
+ type OpenApiBuildResult = {
101
+ document: OpenApiDocument;
102
+ warnings: string[];
103
+ };
104
+ /** The slice of a route the builder reads. A full `Route` satisfies it. */
105
+ type OpenApiRouteInput = Pick<Route, "method" | "path" | "handler"> & Partial<Pick<Route, "name" | "label" | "description" | "middleware" | "isPage" | "sourceFile">>;
106
+ /**
107
+ * The descriptor `authMiddleware()` attaches under `Symbol.for("warlock.auth")`.
108
+ * Read structurally and validated; core never imports the auth package.
109
+ */
110
+ type AuthDescriptor = {
111
+ sources: ("header" | {
112
+ cookie: string;
113
+ })[];
114
+ userTypes: string[];
115
+ };
116
+ type WarningSink = (message: string) => void;
117
+ //#endregion
118
+ export { AuthDescriptor, OpenApiBuildResult, OpenApiComponents, OpenApiContext, OpenApiDocument, OpenApiOperation, OpenApiParameter, OpenApiRequestBody, OpenApiResponse, OpenApiRouteInput, OpenApiSchema, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiValidationResponseConfig, WarningSink };
119
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/openapi/types.ts"],"mappings":";;;;;AAKA;KAAY,aAAA,GAAgB,MAAM;AAAA,KAEtB,gBAAA;EACV,IAAA;EACA,EAAA;EACA,QAAA;EACA,WAAA;EACA,KAAA;EACA,OAAA;EACA,MAAA,EAAQ,aAAa;AAAA;AAAA,KAGX,kBAAA;EACV,QAAA;EACA,OAAA,EAAS,MAAM;IAAW,MAAA,EAAQ,aAAA;EAAA;AAAA;AAAA,KAGxB,eAAA;EACV,WAAA;EACA,OAAA,GAAU,MAAM;IAAW,MAAA,EAAQ,aAAA;EAAA;AAAA;AAAA,KAGzB,0BAAA,GAA6B,MAAM;AAAA,KAEnC,gBAAA;EACV,WAAA;EACA,OAAA;EACA,WAAA;EACA,IAAA;EACA,UAAA,GAAa,gBAAA;EACb,WAAA,GAAc,kBAAA;EACd,SAAA,EAAW,MAAA,SAAe,eAAA;EAC1B,QAAA,GAAW,0BAAA;AAAA;AAAA,KAGD,qBAAA;EACN,IAAA;EAAc,MAAA;EAAkB,YAAA;AAAA;EAChC,IAAA;EAAgB,EAAA;EAAc,IAAA;AAAA;AAAA,KAExB,iBAAA;EACV,OAAA,GAAU,MAAA,SAAe,aAAA;EACzB,eAAA,GAAkB,MAAA,SAAe,qBAAA;AAAA;AAAA,KAGvB,eAAA;EACV,OAAA;EACA,iBAAA;EACA,IAAA;IAAQ,KAAA;IAAe,OAAA;IAAiB,WAAA;EAAA;EACxC,OAAA;IAAY,GAAA;EAAA;EACZ,IAAA;IAAS,IAAA;EAAA;EACT,KAAA,EAAO,MAAA,SAAe,MAAA,SAAe,gBAAA;EACrC,UAAA,GAAa,iBAAA;AAAA;;;;;KAOH,+BAAA;EACV,MAAA;EACA,QAAA;EACA,UAAA;EACA,MAAA;AAAA;;;;;KAOU,cAAA;EACV,IAAA;IAAQ,KAAA;IAAe,OAAA;IAAiB,WAAA;EAAA,GA/Bb;EAiC3B,OAAA,aAhCyB;EAkCzB,YAAA;EAjCiC;;;;;EAuCjC,mBAAA,IAAuB,QAAA,kCAxCE;EA0CzB,kBAAA,GAAqB,+BAA+B;AAAA;AAAA,KAG1C,kBAAA;EACV,QAAA,EAAU,eAAe;EACzB,QAAA;AAAA;;KAIU,iBAAA,GAAoB,IAAA,CAAK,KAAA,mCACnC,OAAA,CACE,IAAA,CAAK,KAAA;;;;;KAOG,cAAA;EACV,OAAA;IAAuB,MAAA;EAAA;EACvB,SAAA;AAAA;AAAA,KAGU,WAAA,IAAe,OAAe"}
@@ -278,7 +278,7 @@ bootstrap();
278
278
  * Returns relative paths from .warlock/production/ to src/app/
279
279
  */
280
280
  async globModule(matches) {
281
- return (await glob("**/*.{ts,tsx}", {
281
+ return (await glob("**/*.{ts,tsx,json}", {
282
282
  cwd: appPath(),
283
283
  absolute: false
284
284
  })).filter((file) => matches("src/app/" + file)).sort().map((file) => "../../src/app/" + file.replace(/\.(ts|tsx)$/, ""));
@@ -327,7 +327,16 @@ bootstrap();
327
327
  async generateLocales() {
328
328
  const files = await this.globModule(isLocaleFile);
329
329
  if (files.length === 0) return false;
330
- await this.generateImportsFile(files, "locales.ts");
330
+ const content = files.map((file, index) => {
331
+ if (!file.endsWith(".json")) return `import "${file}";`;
332
+ const moduleFolder = file.split("/").at(-3);
333
+ return [
334
+ `import dictionary${index} from "${file}";`,
335
+ "import { registerLocaleDictionary } from \"@warlock.js/core\";",
336
+ `registerLocaleDictionary({ sourceFile: ${JSON.stringify(file)}, source: JSON.stringify(dictionary${index}), defaultNamespace: ${JSON.stringify(moduleFolder)} });`
337
+ ].join("\n");
338
+ }).join("\n");
339
+ await putFileAsync(path.join(this.productionDir, "locales.ts"), content + "\n");
331
340
  return true;
332
341
  }
333
342
  /**