@warlock.js/core 5.27.0 → 5.29.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 (127) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/esm/cli/commands/openapi-generator.action.mjs +1 -1
  3. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -1
  4. package/esm/cli/commands/postman-generator.action.mjs +47 -0
  5. package/esm/cli/commands/postman-generator.action.mjs.map +1 -0
  6. package/esm/cli/commands/postman-generator.command.mjs +43 -0
  7. package/esm/cli/commands/postman-generator.command.mjs.map +1 -0
  8. package/esm/cli/framework-cli-commands.mjs +2 -0
  9. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  10. package/esm/container/development-app-modules.d.mts +31 -0
  11. package/esm/container/development-app-modules.d.mts.map +1 -0
  12. package/esm/container/index.d.mts +2 -0
  13. package/esm/container/index.d.mts.map +1 -1
  14. package/esm/container/index.mjs.map +1 -1
  15. package/esm/dev-server/app-module-registry.mjs +52 -0
  16. package/esm/dev-server/app-module-registry.mjs.map +1 -0
  17. package/esm/dev-server/files-orchestrator.mjs +13 -2
  18. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  19. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  20. package/esm/http/middleware/inject-request-context.mjs +29 -3
  21. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  22. package/esm/http/request.d.mts +2 -2
  23. package/esm/http/request.d.mts.map +1 -1
  24. package/esm/index.d.mts +7 -2
  25. package/esm/index.mjs +5 -2
  26. package/esm/localization/parse-locale-dictionary.d.mts +6 -1
  27. package/esm/localization/parse-locale-dictionary.d.mts.map +1 -1
  28. package/esm/localization/parse-locale-dictionary.mjs +5 -1
  29. package/esm/localization/parse-locale-dictionary.mjs.map +1 -1
  30. package/esm/openapi/development-openapi-provider.d.mts +12 -1
  31. package/esm/openapi/development-openapi-provider.d.mts.map +1 -1
  32. package/esm/openapi/development-openapi-provider.mjs +15 -1
  33. package/esm/openapi/development-openapi-provider.mjs.map +1 -1
  34. package/esm/openapi/index.d.mts +5 -2
  35. package/esm/openapi/index.mjs +3 -1
  36. package/esm/openapi/postman/build-request-body.mjs +86 -0
  37. package/esm/openapi/postman/build-request-body.mjs.map +1 -0
  38. package/esm/openapi/postman/build-request-url.mjs +85 -0
  39. package/esm/openapi/postman/build-request-url.mjs.map +1 -0
  40. package/esm/openapi/postman/build-saved-responses.mjs +60 -0
  41. package/esm/openapi/postman/build-saved-responses.mjs.map +1 -0
  42. package/esm/openapi/postman/example-from-schema.d.mts +17 -0
  43. package/esm/openapi/postman/example-from-schema.d.mts.map +1 -0
  44. package/esm/openapi/postman/example-from-schema.mjs +92 -0
  45. package/esm/openapi/postman/example-from-schema.mjs.map +1 -0
  46. package/esm/openapi/postman/http-status-text.mjs +31 -0
  47. package/esm/openapi/postman/http-status-text.mjs.map +1 -0
  48. package/esm/openapi/postman/index.d.mts +3 -0
  49. package/esm/openapi/postman/openapi-to-postman-collection.d.mts +22 -0
  50. package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -0
  51. package/esm/openapi/postman/openapi-to-postman-collection.mjs +109 -0
  52. package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -0
  53. package/esm/openapi/postman/postman-types.d.mts +119 -0
  54. package/esm/openapi/postman/postman-types.d.mts.map +1 -0
  55. package/esm/openapi/postman/resolve-operation-auth.mjs +51 -0
  56. package/esm/openapi/postman/resolve-operation-auth.mjs.map +1 -0
  57. package/esm/openapi/postman/schema-refs.d.mts +7 -0
  58. package/esm/openapi/postman/schema-refs.d.mts.map +1 -0
  59. package/esm/openapi/postman/schema-refs.mjs +41 -0
  60. package/esm/openapi/postman/schema-refs.mjs.map +1 -0
  61. package/esm/openapi/postman/uuid-v5.mjs +29 -0
  62. package/esm/openapi/postman/uuid-v5.mjs.map +1 -0
  63. package/esm/openapi/response-schemas.mjs +4 -1
  64. package/esm/openapi/response-schemas.mjs.map +1 -1
  65. package/esm/openapi/schema-registry.mjs +6 -0
  66. package/esm/openapi/schema-registry.mjs.map +1 -1
  67. package/esm/resource/index.d.mts +1 -0
  68. package/esm/resource/index.mjs +1 -0
  69. package/esm/resource/nullable-resource.d.mts +44 -0
  70. package/esm/resource/nullable-resource.d.mts.map +1 -0
  71. package/esm/resource/nullable-resource.mjs +38 -0
  72. package/esm/resource/nullable-resource.mjs.map +1 -0
  73. package/esm/resource/resource.d.mts.map +1 -1
  74. package/esm/resource/resource.mjs +24 -7
  75. package/esm/resource/resource.mjs.map +1 -1
  76. package/esm/resource/types.d.mts +10 -5
  77. package/esm/resource/types.d.mts.map +1 -1
  78. package/esm/restful/restful.d.mts +5 -5
  79. package/esm/restful/restful.d.mts.map +1 -1
  80. package/esm/router/response-schema-types.mjs +2 -0
  81. package/esm/router/response-schema-types.mjs.map +1 -1
  82. package/llms-full.txt +269 -241
  83. package/llms.txt +42 -42
  84. package/package.json +12 -12
  85. package/skills/README.md +39 -39
  86. package/skills/add-connector/SKILL.md +4 -4
  87. package/skills/benchmark-code/SKILL.md +3 -3
  88. package/skills/build-restful/SKILL.md +5 -5
  89. package/skills/build-url/SKILL.md +7 -7
  90. package/skills/configure-app/SKILL.md +7 -7
  91. package/skills/create-controller/SKILL.md +8 -8
  92. package/skills/create-module/SKILL.md +9 -9
  93. package/skills/define-resource/SKILL.md +4 -4
  94. package/skills/encrypt-data/SKILL.md +7 -7
  95. package/skills/generate-openapi/SKILL.md +33 -5
  96. package/skills/hash-password/SKILL.md +8 -8
  97. package/skills/health-checks/SKILL.md +6 -6
  98. package/skills/lower-stage3-decorators/SKILL.md +4 -4
  99. package/skills/process-image/SKILL.md +5 -5
  100. package/skills/register-route/SKILL.md +6 -6
  101. package/skills/request-memo/SKILL.md +4 -4
  102. package/skills/request-tracing/SKILL.md +6 -6
  103. package/skills/resolve-path/SKILL.md +6 -6
  104. package/skills/retry-operation/SKILL.md +4 -4
  105. package/skills/run-app/SKILL.md +11 -11
  106. package/skills/send-mail/SKILL.md +3 -3
  107. package/skills/send-response/SKILL.md +6 -6
  108. package/skills/store-file/SKILL.md +4 -4
  109. package/skills/test-http/SKILL.md +5 -5
  110. package/skills/test-service/SKILL.md +5 -5
  111. package/skills/update-packages/SKILL.md +6 -6
  112. package/skills/upload-file/SKILL.md +7 -7
  113. package/skills/use-app-context/SKILL.md +7 -7
  114. package/skills/use-localization/SKILL.md +6 -6
  115. package/skills/use-middleware/SKILL.md +6 -6
  116. package/skills/use-model-transformers/SKILL.md +6 -6
  117. package/skills/use-repository/SKILL.md +7 -7
  118. package/skills/use-request-locals/SKILL.md +4 -4
  119. package/skills/validate-input/SKILL.md +7 -7
  120. package/skills/warlock-conventions/SKILL.md +6 -6
  121. package/skills/warlock-doctor/SKILL.md +5 -5
  122. package/skills/warlock-routes/SKILL.md +6 -6
  123. package/skills/wire-socket/SKILL.md +4 -4
  124. package/skills/write-cli-command/SKILL.md +6 -6
  125. package/skills/write-middleware/SKILL.md +8 -8
  126. package/skills/write-seeder/SKILL.md +5 -5
  127. package/skills/write-use-case/SKILL.md +3 -3
@@ -0,0 +1,29 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ //#region ../core/src/openapi/postman/uuid-v5.ts
4
+ /**
5
+ * Namespace of every id the Postman converter derives. Fixed forever: changing it would
6
+ * change the `_postman_id` of every collection already imported.
7
+ */
8
+ const POSTMAN_ID_NAMESPACE = "8f1c2b64-3d7a-4c59-9a0e-5b6d1e47c2a3";
9
+ /**
10
+ * RFC 4122 version 5 (SHA-1, name based) UUID, so the same name always yields the same id.
11
+ */
12
+ function uuidV5(name, namespace = POSTMAN_ID_NAMESPACE) {
13
+ const digest = createHash("sha1").update(Buffer.from(namespace.replace(/-/g, ""), "hex")).update(name, "utf8").digest();
14
+ const bytes = Buffer.from(digest.subarray(0, 16));
15
+ bytes.writeUInt8(bytes.readUInt8(6) & 15 | 80, 6);
16
+ bytes.writeUInt8(bytes.readUInt8(8) & 63 | 128, 8);
17
+ const hex = bytes.toString("hex");
18
+ return [
19
+ hex.slice(0, 8),
20
+ hex.slice(8, 12),
21
+ hex.slice(12, 16),
22
+ hex.slice(16, 20),
23
+ hex.slice(20, 32)
24
+ ].join("-");
25
+ }
26
+
27
+ //#endregion
28
+ export { uuidV5 };
29
+ //# sourceMappingURL=uuid-v5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid-v5.mjs","names":[],"sources":["../../../../../../../../core/src/openapi/postman/uuid-v5.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\n/**\n * Namespace of every id the Postman converter derives. Fixed forever: changing it would\n * change the `_postman_id` of every collection already imported.\n */\nexport const POSTMAN_ID_NAMESPACE = \"8f1c2b64-3d7a-4c59-9a0e-5b6d1e47c2a3\";\n\n/**\n * RFC 4122 version 5 (SHA-1, name based) UUID, so the same name always yields the same id.\n */\nexport function uuidV5(name: string, namespace: string = POSTMAN_ID_NAMESPACE): string {\n const digest = createHash(\"sha1\")\n .update(Buffer.from(namespace.replace(/-/g, \"\"), \"hex\"))\n .update(name, \"utf8\")\n .digest();\n const bytes = Buffer.from(digest.subarray(0, 16));\n\n bytes.writeUInt8((bytes.readUInt8(6) & 0x0f) | 0x50, 6);\n bytes.writeUInt8((bytes.readUInt8(8) & 0x3f) | 0x80, 8);\n\n const hex = bytes.toString(\"hex\");\n\n return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20, 32)].join(\"-\");\n}\n"],"mappings":";;;;;;;AAMA,MAAa,uBAAuB;;;;AAKpC,SAAgB,OAAO,MAAc,YAAoB,sBAA8B;CACrF,MAAM,SAAS,WAAW,MAAM,CAAC,CAC9B,OAAO,OAAO,KAAK,UAAU,QAAQ,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CACvD,OAAO,MAAM,MAAM,CAAC,CACpB,OAAO;CACV,MAAM,QAAQ,OAAO,KAAK,OAAO,SAAS,GAAG,EAAE,CAAC;CAEhD,MAAM,WAAY,MAAM,UAAU,CAAC,IAAI,KAAQ,IAAM,CAAC;CACtD,MAAM,WAAY,MAAM,UAAU,CAAC,IAAI,KAAQ,KAAM,CAAC;CAEtD,MAAM,MAAM,MAAM,SAAS,KAAK;CAEhC,OAAO;EAAC,IAAI,MAAM,GAAG,CAAC;EAAG,IAAI,MAAM,GAAG,EAAE;EAAG,IAAI,MAAM,IAAI,EAAE;EAAG,IAAI,MAAM,IAAI,EAAE;EAAG,IAAI,MAAM,IAAI,EAAE;CAAC,CAAC,CAAC,KAAK,GAAG;AAC9G"}
@@ -1,3 +1,5 @@
1
+ import { isNullableResource } from "../resource/nullable-resource.mjs";
2
+
1
3
  //#region ../core/src/openapi/response-schemas.ts
2
4
  const STATUS_TEXT = {
3
5
  "200": "Successful response",
@@ -30,11 +32,12 @@ function describeStatus(status) {
30
32
  }
31
33
  /**
32
34
  * 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
35
+ * (array of it), `nullable(Resource)` (the resource or null) or a nested plain object of those. Anything else (a resolver function, a
34
36
  * builder) is `{}` plus a warning.
35
37
  */
36
38
  function bodyValueSchema(value, path, registry, warn) {
37
39
  if (typeof value === "string") return registry.cast(value, path, warn);
40
+ if (isNullableResource(value)) return { oneOf: [bodyValueSchema(value.resource, path, registry, warn), { type: "null" }] };
38
41
  if (typeof value === "function") {
39
42
  if (registry.isResourceClass(value)) return registry.resourceRef(value);
40
43
  warn(`${path}: a function that is not a resource class has no static shape and is documented as {}.`);
@@ -1 +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"}
1
+ {"version":3,"file":"response-schemas.mjs","names":[],"sources":["../../../../../../../core/src/openapi/response-schemas.ts"],"sourcesContent":["import { isNullableResource } from \"../resource/nullable-resource\";\nimport 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), `nullable(Resource)` (the resource or null) 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 (isNullableResource(value)) {\n return { oneOf: [bodyValueSchema(value.resource, path, registry, warn), { type: \"null\" }] };\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":";;;AAIA,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,mBAAmB,KAAK,GAC1B,OAAO,EAAE,OAAO,CAAC,gBAAgB,MAAM,UAAU,MAAM,UAAU,IAAI,GAAG,EAAE,MAAM,OAAO,CAAC,EAAE;CAG5F,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"}
@@ -1,3 +1,4 @@
1
+ import { isNullableResource } from "../resource/nullable-resource.mjs";
1
2
  import { castToJsonSchema } from "./cast-schema.mjs";
2
3
  import { isLazy } from "@mongez/reinforcements";
3
4
 
@@ -101,7 +102,12 @@ var SchemaRegistry = class {
101
102
  items: context.ownerRef
102
103
  };
103
104
  if (typeof config === "string") return this.cast(config, context.path);
105
+ if (isNullableResource(config)) return { oneOf: [this.fieldSchema(config.resource, context), { type: "null" }] };
104
106
  if (isLazy(config)) return this.lazyFieldSchema(config, context);
107
+ if (Array.isArray(config) && config.length === 1) return {
108
+ type: "array",
109
+ items: this.fieldSchema(config[0], context)
110
+ };
105
111
  if (Array.isArray(config) && config.length === 2 && typeof config[1] === "string") return this.cast(config[1], context.path);
106
112
  if (typeof config === "function") {
107
113
  if (this.isResourceClass(config)) return this.resourceRef(config);
@@ -1 +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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"}
1
+ {"version":3,"file":"schema-registry.mjs","names":[],"sources":["../../../../../../../core/src/openapi/schema-registry.ts"],"sourcesContent":["import { isLazy } from \"@mongez/reinforcements\";\nimport { isNullableResource } from \"../resource/nullable-resource\";\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 (isNullableResource(config)) {\n return { oneOf: [this.fieldSchema(config.resource, context), { type: \"null\" }] };\n }\n\n if (isLazy(config)) {\n return this.lazyFieldSchema(config, context);\n }\n\n if (Array.isArray(config) && config.length === 1) {\n return { type: \"array\", items: this.fieldSchema(config[0], 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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"}
@@ -1,5 +1,6 @@
1
1
  import { ResourceFieldBuilder } from "./resource-field-builder.mjs";
2
2
  import { Resource, ResourceConstructor, ResourceContract } from "./resource.mjs";
3
+ import { NULLABLE_RESOURCE, NullableResource, NullableResourceTarget, isNullableResource, nullable } from "./nullable-resource.mjs";
3
4
  import { BaseCastOutput, CastOutput, FieldOutput, ResourceArraySchema, ResourceCastType, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceOutput, ResourceOutputOf, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, ResponseBodyOutput, ResponseBodyValue, ResponseSchema, ResponseSchemaOutput } from "./types.mjs";
4
5
  import { DefineResourceOptions, defineResource } from "./define-resource.mjs";
5
6
  import { RegisterResource } from "./register-resource.mjs";
@@ -1,3 +1,4 @@
1
+ import { NULLABLE_RESOURCE, isNullableResource, nullable } from "./nullable-resource.mjs";
1
2
  import { ResourceFieldBuilder } from "./resource-field-builder.mjs";
2
3
  import { Resource } from "./resource.mjs";
3
4
  import { defineResource } from "./define-resource.mjs";
@@ -0,0 +1,44 @@
1
+ import { ResourceConstructor } from "./resource.mjs";
2
+ import { Lazy } from "@mongez/reinforcements";
3
+
4
+ //#region ../core/src/resource/nullable-resource.d.ts
5
+ /**
6
+ * Brand of a nullable resource marker. `Symbol.for` keeps it identical across duplicated
7
+ * copies of this module (the dev server, the typings child process and the app all load one).
8
+ */
9
+ declare const NULLABLE_RESOURCE: unique symbol;
10
+ /**
11
+ * What `nullable()` accepts: a resource class, or a lazy reference to one (for cyclic resources).
12
+ */
13
+ type NullableResourceTarget = ResourceConstructor<any> | Lazy<ResourceConstructor<any>>;
14
+ /**
15
+ * Marker returned by `nullable(Resource)`.
16
+ */
17
+ interface NullableResource<R extends NullableResourceTarget = NullableResourceTarget> {
18
+ readonly [NULLABLE_RESOURCE]: true;
19
+ readonly resource: R;
20
+ }
21
+ /**
22
+ * Declare a nested resource that is always present in the output: the resource's output when
23
+ * there is a value, `null` when the input is `null` or `undefined` (the field is not omitted).
24
+ *
25
+ * Accepted wherever a resource is accepted: as a `defineResource` schema field and as a value in
26
+ * a `responseSchema` body (including nested objects).
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const PostResource = defineResource({
31
+ * schema: { id: "number", author: nullable(UserResource) },
32
+ * });
33
+ *
34
+ * // ResourceOutput<typeof PostResource> -> { id: number; author: UserJson | null }
35
+ * ```
36
+ */
37
+ declare function nullable<R extends NullableResourceTarget>(resource: R): NullableResource<R>;
38
+ /**
39
+ * True when the value is a marker created by `nullable()`.
40
+ */
41
+ declare function isNullableResource(value: unknown): value is NullableResource;
42
+ //#endregion
43
+ export { NULLABLE_RESOURCE, NullableResource, NullableResourceTarget, isNullableResource, nullable };
44
+ //# sourceMappingURL=nullable-resource.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nullable-resource.d.mts","names":[],"sources":["../../../../../../../core/src/resource/nullable-resource.ts"],"mappings":";;;;;;AAOA;;cAAa,iBAAA;;AAA2D;AAKxE;KAAY,sBAAA,GAAyB,mBAAA,QAA2B,IAAA,CAAK,mBAAA;;;;UAKpD,gBAAA,WAA2B,sBAAA,GAAyB,sBAAA;EAAA,UACzD,iBAAA;EAAA,SACD,QAAA,EAAU,CAAA;AAAA;;;;AAPmE;AAKxF;;;;;;;;;;;;iBAqBgB,QAAA,WAAmB,sBAAA,EAAwB,QAAA,EAAU,CAAA,GAAI,gBAAA,CAAiB,CAAA;;;;iBAO1E,kBAAA,CAAmB,KAAA,YAAiB,KAAA,IAAS,gBAAgB"}
@@ -0,0 +1,38 @@
1
+ //#region ../core/src/resource/nullable-resource.ts
2
+ /**
3
+ * Brand of a nullable resource marker. `Symbol.for` keeps it identical across duplicated
4
+ * copies of this module (the dev server, the typings child process and the app all load one).
5
+ */
6
+ const NULLABLE_RESOURCE = Symbol.for("warlock.resource.nullable");
7
+ /**
8
+ * Declare a nested resource that is always present in the output: the resource's output when
9
+ * there is a value, `null` when the input is `null` or `undefined` (the field is not omitted).
10
+ *
11
+ * Accepted wherever a resource is accepted: as a `defineResource` schema field and as a value in
12
+ * a `responseSchema` body (including nested objects).
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const PostResource = defineResource({
17
+ * schema: { id: "number", author: nullable(UserResource) },
18
+ * });
19
+ *
20
+ * // ResourceOutput<typeof PostResource> -> { id: number; author: UserJson | null }
21
+ * ```
22
+ */
23
+ function nullable(resource) {
24
+ return {
25
+ [NULLABLE_RESOURCE]: true,
26
+ resource
27
+ };
28
+ }
29
+ /**
30
+ * True when the value is a marker created by `nullable()`.
31
+ */
32
+ function isNullableResource(value) {
33
+ return typeof value === "object" && value !== null && value[NULLABLE_RESOURCE] === true;
34
+ }
35
+
36
+ //#endregion
37
+ export { NULLABLE_RESOURCE, isNullableResource, nullable };
38
+ //# sourceMappingURL=nullable-resource.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nullable-resource.mjs","names":[],"sources":["../../../../../../../core/src/resource/nullable-resource.ts"],"sourcesContent":["import { type Lazy } from \"@mongez/reinforcements\";\nimport { type ResourceConstructor } from \"./resource\";\n\n/**\n * Brand of a nullable resource marker. `Symbol.for` keeps it identical across duplicated\n * copies of this module (the dev server, the typings child process and the app all load one).\n */\nexport const NULLABLE_RESOURCE = Symbol.for(\"warlock.resource.nullable\");\n\n/**\n * What `nullable()` accepts: a resource class, or a lazy reference to one (for cyclic resources).\n */\nexport type NullableResourceTarget = ResourceConstructor<any> | Lazy<ResourceConstructor<any>>;\n\n/**\n * Marker returned by `nullable(Resource)`.\n */\nexport interface NullableResource<R extends NullableResourceTarget = NullableResourceTarget> {\n readonly [NULLABLE_RESOURCE]: true;\n readonly resource: R;\n}\n\n/**\n * Declare a nested resource that is always present in the output: the resource's output when\n * there is a value, `null` when the input is `null` or `undefined` (the field is not omitted).\n *\n * Accepted wherever a resource is accepted: as a `defineResource` schema field and as a value in\n * a `responseSchema` body (including nested objects).\n *\n * @example\n * ```typescript\n * const PostResource = defineResource({\n * schema: { id: \"number\", author: nullable(UserResource) },\n * });\n *\n * // ResourceOutput<typeof PostResource> -> { id: number; author: UserJson | null }\n * ```\n */\nexport function nullable<R extends NullableResourceTarget>(resource: R): NullableResource<R> {\n return { [NULLABLE_RESOURCE]: true, resource };\n}\n\n/**\n * True when the value is a marker created by `nullable()`.\n */\nexport function isNullableResource(value: unknown): value is NullableResource {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Record<symbol, unknown>)[NULLABLE_RESOURCE] === true\n );\n}\n"],"mappings":";;;;;AAOA,MAAa,oBAAoB,OAAO,IAAI,2BAA2B;;;;;;;;;;;;;;;;;AA+BvE,SAAgB,SAA2C,UAAkC;CAC3F,OAAO;GAAG,oBAAoB;EAAM;CAAS;AAC/C;;;;AAKA,SAAgB,mBAAmB,OAA2C;CAC5E,OACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAkC,uBAAuB;AAE9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.mts","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"mappings":"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"}
1
+ {"version":3,"file":"resource.d.mts","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { useRequestStore } from "../http/context/request-context.mjs";
2
+ import { isNullableResource } from "./nullable-resource.mjs";
2
3
  import { ResourceFieldBuilder } from "./resource-field-builder.mjs";
3
4
  import { Model } from "@warlock.js/cascade";
4
5
  import { get, isLazy, set } from "@mongez/reinforcements";
@@ -9,6 +10,12 @@ import { get, isLazy, set } from "@mongez/reinforcements";
9
10
  * Prevents runaway serialization on deep trees or circular data.
10
11
  */
11
12
  const MAX_SELF_DEPTH = 10;
13
+ /**
14
+ * Whether the given schema entry is an `arrayOf` item schema
15
+ */
16
+ function isArrayOfSchema(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value) && value.__type === "arrayOf" && typeof value.schema === "object" && value.schema !== null;
18
+ }
12
19
  var Resource = class Resource {
13
20
  static {
14
21
  this.schema = {};
@@ -26,11 +33,15 @@ var Resource = class Resource {
26
33
  for (const [key, value] of Object.entries(schema)) if (value === "self" || value === "self[]") parsed[key] = value;
27
34
  else if (isLazy(value)) parsed[key] = value;
28
35
  else if (typeof value === "string") parsed[key] = ResourceFieldBuilder.fromCastType(value);
29
- else if (Array.isArray(value) && value.length === 2 && typeof value[0] === "string") {
36
+ else if (Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "string") {
30
37
  const builder = ResourceFieldBuilder.fromCastType(value[1]);
31
38
  builder.setInputKey(value[0]);
32
39
  parsed[key] = builder;
33
- } else parsed[key] = value;
40
+ } else if (isArrayOfSchema(value)) parsed[key] = {
41
+ ...value,
42
+ schema: Resource.normalizeSchema(value.schema)
43
+ };
44
+ else parsed[key] = value;
34
45
  return parsed;
35
46
  }
36
47
  /**
@@ -102,13 +113,19 @@ var Resource = class Resource {
102
113
  transformValue(value, outputSettings, locale) {
103
114
  let outputValue;
104
115
  if (isLazy(outputSettings)) return this.transformValue(value, outputSettings.resolve(), locale);
116
+ if (isNullableResource(outputSettings)) {
117
+ if (value === null || value === void 0) return null;
118
+ return this.transformValue(value, outputSettings.resource, locale) ?? null;
119
+ }
120
+ if (Array.isArray(outputSettings) && outputSettings.length === 1) {
121
+ if (!Array.isArray(value)) return;
122
+ return value.map((item) => this.transformValue(item, outputSettings[0], locale)).filter((v) => v !== void 0);
123
+ }
105
124
  if (outputSettings === "self" || outputSettings === "self[]") outputValue = this.transformSelfReference(value, outputSettings === "self[]");
106
125
  else if (typeof outputSettings === "function" && outputSettings.prototype instanceof Resource) {
107
126
  if (!value) return;
108
- if (Array.isArray(value)) {
109
- if (value.length === 0) return;
110
- outputValue = value.map((item) => new outputSettings(item).toJSON()).filter((v) => v !== void 0);
111
- } else outputValue = new outputSettings(value).toJSON();
127
+ if (Array.isArray(value)) outputValue = value.map((item) => new outputSettings(item).toJSON()).filter((v) => v !== void 0);
128
+ else outputValue = new outputSettings(value).toJSON();
112
129
  } else if (typeof outputSettings === "function") outputValue = outputSettings.call(this, value, this);
113
130
  else if (outputSettings instanceof ResourceFieldBuilder) outputValue = outputSettings.transform(value, locale);
114
131
  else if (typeof outputSettings === "object" && outputSettings !== null && "__type" in outputSettings && outputSettings.__type === "arrayOf") if (Array.isArray(value)) outputValue = value.map((item) => this.transformArrayItem(item, outputSettings.schema, locale)).filter((v) => v !== void 0);
@@ -152,7 +169,7 @@ var Resource = class Resource {
152
169
  for (const [outputKey, outputSettings] of Object.entries(schema)) {
153
170
  let fieldKey = outputKey;
154
171
  let valueTransformType = outputSettings;
155
- if (Array.isArray(outputSettings)) {
172
+ if (Array.isArray(outputSettings) && outputSettings.length === 2) {
156
173
  fieldKey = outputSettings[0];
157
174
  valueTransformType = outputSettings[1];
158
175
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resource.mjs","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"sourcesContent":["import { get, isLazy, set, type GenericObject } from \"@mongez/reinforcements\";\nimport { Model } from \"@warlock.js/cascade\";\nimport { useRequestStore } from \"../http/context/request-context\";\nimport { ResourceFieldBuilder } from \"./resource-field-builder\";\nimport {\n type ResourceArraySchema,\n type ResourceFieldConfig,\n type ResourceOutputValueCastType,\n type ResourceSchema,\n} from \"./types\";\n\n/**\n * Maximum recursion depth for self-referencing fields.\n * Prevents runaway serialization on deep trees or circular data.\n */\nconst MAX_SELF_DEPTH = 10;\n\n/**\n * Resource contract\n */\nexport interface ResourceContract<Out = GenericObject> {\n /**\n * Resource data\n */\n resource: GenericObject;\n\n /**\n * Resource final output\n */\n data: GenericObject;\n\n /**\n * Original data\n */\n originalData: GenericObject;\n\n /**\n * Convert resource to JSON\n */\n toJSON(): Out;\n\n /**\n * Transform the given value with given type\n */\n transform(value: any, type: ResourceOutputValueCastType, locale?: string): any;\n\n /**\n * Get a input value for the given key\n */\n get(key: string, defaultValue?: any): any;\n\n /**\n * Set the given value for the given field\n */\n set(key: string, value: any): ResourceContract;\n\n /**\n * Create an array schema for transforming array items\n */\n arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema;\n\n /**\n * Get a string field builder\n */\n string(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a date field builder\n */\n date(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a localized field builder\n */\n localized(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a url field builder\n */\n url(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a uploadsUrl field builder\n */\n uploadsUrl(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a number field builder\n */\n number(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a boolean field builder\n */\n boolean(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a float field builder\n */\n float(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a int field builder\n */\n int(inputKey?: string): ResourceFieldBuilder;\n}\n\n/**\n * Resource constructor\n */\nexport interface ResourceConstructor<Out = GenericObject> {\n new (originalData: GenericObject | Resource | Model): ResourceContract<Out>;\n}\n\nexport class Resource implements ResourceContract {\n /**\n * Resource data\n */\n public resource: GenericObject = {};\n\n /**\n * Resource final output\n */\n public data: GenericObject = {};\n\n /**\n * Tracks visited object identities during self-reference recursion.\n * Prevents infinite loops on circular data (e.g. A.parent → B, B.parent → A).\n */\n protected _selfSeen?: Set<unknown>;\n\n /**\n * Raw resource schema — field declarations as written by the developer.\n * Used by doc generators for introspection.\n */\n public static schema: Record<string, ResourceFieldConfig | string> = {};\n\n /**\n * Normalized schema — all string cast types and tuples converted to\n * ResourceFieldBuilder instances at definition time. Used by transformOutput at runtime.\n */\n public static parsedSchema: Record<string, ResourceFieldConfig> = {};\n\n /**\n * Normalize a raw schema into parsedSchema.\n * Converts string cast types (including suffixes) and tuples into pre-built builders.\n * Other entry types (ResourceConstructor, resolver functions, ResourceArraySchema) are kept as-is.\n */\n public static normalizeSchema(schema: Record<string, ResourceFieldConfig | string>): Record<string, ResourceFieldConfig> {\n const parsed: Record<string, ResourceFieldConfig> = {};\n\n for (const [key, value] of Object.entries(schema)) {\n if (value === \"self\" || value === \"self[]\") {\n // Keep self-references as-is — resolved lazily in transformValue\n parsed[key] = value;\n } else if (isLazy<ResourceConstructor>(value)) {\n // Keep Lazy<ResourceConstructor> as-is — resolved at transform\n // time so cyclic resource refs (A embeds B embeds A) compose\n // without import-graph cycles.\n parsed[key] = value;\n } else if (typeof value === \"string\") {\n parsed[key] = ResourceFieldBuilder.fromCastType(value);\n } else if (Array.isArray(value) && value.length === 2 && typeof value[0] === \"string\") {\n const builder = ResourceFieldBuilder.fromCastType(value[1] as string);\n builder.setInputKey(value[0]);\n parsed[key] = builder;\n } else {\n parsed[key] = value;\n }\n }\n\n return parsed;\n }\n\n /**\n * Constructor\n */\n public constructor(public originalData: GenericObject | Resource | Model) {\n if (this.originalData instanceof Model) {\n this.resource = this.originalData.data;\n } else if (this.originalData instanceof Resource) {\n this.resource = this.originalData.data;\n } else {\n this.resource = this.originalData;\n }\n }\n\n /**\n * Convert resource to JSON\n */\n public toJSON() {\n this.boot();\n this.transformOutput();\n this.extend();\n\n return this.data;\n }\n\n /**\n * Boot method\n * Called before transforming the resource\n */\n protected boot() {\n //\n }\n\n /**\n * Transform resource to output using the pre-normalized parsedSchema.\n * Builders handle their own array/nullable logic internally.\n * ResourceConstructor and ResourceArraySchema handle arrays in transformValue.\n */\n protected transformOutput() {\n const localeCode = useRequestStore()?.request?.locale;\n const parsedSchema = this.resolveParsedSchema();\n // A model is read through its accessors; anything else through the\n // constructor's normalized input (a wrapped resource's serialized data).\n const source = this.originalData instanceof Model ? this.originalData : this.resource;\n\n for (const [outputKey, outputSettings] of Object.entries(parsedSchema)) {\n const inputKey = outputSettings instanceof ResourceFieldBuilder\n ? outputSettings.getInputKey()\n : undefined;\n const inputValue = this.resolveSourceValue(source, outputKey, inputKey);\n const outputValue = this.transformValue(inputValue, outputSettings, localeCode);\n\n if (outputValue !== undefined) {\n this.set(outputKey, outputValue);\n }\n }\n }\n\n /**\n * Resolve the normalized schema of THIS class.\n * `@RegisterResource()` is optional: when the class has no own `parsedSchema`\n * (a static field is inherited from the base as `{}`), normalize its own\n * static `schema` (or an instance `schema` field) once and cache it on the class.\n */\n protected resolveParsedSchema(): Record<string, ResourceFieldConfig> {\n const ctor = this.constructor as typeof Resource;\n\n if (Object.hasOwn(ctor, \"parsedSchema\")) {\n return ctor.parsedSchema;\n }\n\n const instanceSchema = (this as unknown as { schema?: Record<string, ResourceFieldConfig | string> }).schema;\n const schema = instanceSchema ?? ctor.schema;\n\n if (!schema || Object.keys(schema).length === 0) {\n throw new Error(\n `Resource \"${ctor.name}\" has no schema. Declare \\`static schema = { ... }\\` on the class.`,\n );\n }\n\n const parsed = Resource.normalizeSchema(schema);\n\n // Instance schemas are per-class by convention, so caching on the class is safe\n ctor.parsedSchema = parsed;\n\n return parsed;\n }\n\n /**\n * Transform the given value with given type\n */\n public transform(value: any, type: ResourceOutputValueCastType, locale?: string) {\n return new ResourceFieldBuilder(type).transform(value, locale);\n }\n\n /**\n * Transform the given value for the given output setting.\n * After normalization, string cast types no longer reach here — they are pre-converted to builders.\n */\n protected transformValue(value: any, outputSettings: ResourceFieldConfig, locale?: string): any {\n let outputValue: any;\n\n if (isLazy<ResourceConstructor>(outputSettings)) {\n // Resolve the lazy ref now (cyclic resource imports are fully loaded\n // by serialization time) and recurse — the resolved constructor is\n // handled by the ResourceConstructor branch below.\n return this.transformValue(value, outputSettings.resolve(), locale);\n }\n\n if (outputSettings === \"self\" || outputSettings === \"self[]\") {\n // Self-reference — resolve using the same resource class with cycle detection\n outputValue = this.transformSelfReference(value, outputSettings === \"self[]\");\n } else if (\n typeof outputSettings === \"function\" &&\n outputSettings.prototype instanceof Resource\n ) {\n if (!value) return;\n // Nested resource — handle both single and array values\n if (Array.isArray(value)) {\n if (value.length === 0) return;\n outputValue = value\n .map((item) => new (outputSettings as typeof Resource)(item).toJSON())\n .filter((v) => v !== undefined);\n } else {\n outputValue = new (outputSettings as typeof Resource)(value).toJSON();\n }\n } else if (typeof outputSettings === \"function\") {\n // Resolver function - bind to Resource instance for access to this.get(), etc.\n outputValue = (outputSettings as Function).call(this, value, this);\n } else if (outputSettings instanceof ResourceFieldBuilder) {\n // Builder — handles array mapping and nullable internally via isArrayField\n outputValue = outputSettings.transform(value, locale);\n } else if (\n typeof outputSettings === \"object\" &&\n outputSettings !== null &&\n \"__type\" in outputSettings &&\n outputSettings.__type === \"arrayOf\"\n ) {\n // ResourceArraySchema — structured array items with their own sub-schema\n if (Array.isArray(value)) {\n outputValue = value\n .map((item) => this.transformArrayItem(item, outputSettings.schema, locale))\n .filter((v) => v !== undefined);\n } else {\n outputValue = this.transformArrayItem(value, outputSettings.schema, locale);\n }\n }\n\n return outputValue;\n }\n\n /**\n * Extend the resource output\n */\n protected extend() {\n //\n }\n\n /**\n * Transform a self-referencing field value.\n *\n * @example\n * // Single: parent: \"self\"\n * // Array: children: \"self[]\"\n */\n protected transformSelfReference(value: any, isArray: boolean): any {\n if (isArray) {\n return Array.isArray(value)\n ? value.map((item) => this.resolveSelf(item)).filter((v) => v !== undefined)\n : undefined;\n }\n\n return this.resolveSelf(value);\n }\n\n /**\n * Resolve a single self-reference value.\n * Uses identity-based cycle detection (id/_id) and a max depth guard.\n */\n protected resolveSelf(value: any): any {\n if (!value) return undefined;\n\n const identity = value.id ?? value._id ?? value;\n // Ancestors of the current path only: siblings never see each other,\n // so the set size is the depth.\n const ancestors = this._selfSeen ?? new Set();\n\n // Circular reference or depth limit reached — stop recursion\n if (ancestors.has(identity) || ancestors.size >= MAX_SELF_DEPTH) {\n return undefined;\n }\n\n const SelfConstructor = this.constructor as typeof Resource;\n const child = new SelfConstructor(value);\n child._selfSeen = new Set(ancestors).add(identity);\n\n return child.toJSON();\n }\n\n /**\n * Transform a single array item according to the given schema\n */\n protected transformArrayItem(\n item: any,\n schema: Record<string, ResourceFieldConfig>,\n locale?: string,\n ) {\n const transformedItem: GenericObject = {};\n\n for (const [outputKey, outputSettings] of Object.entries(schema)) {\n let fieldKey = outputKey;\n let valueTransformType = outputSettings as ResourceFieldConfig;\n\n if (Array.isArray(outputSettings)) {\n fieldKey = outputSettings[0];\n valueTransformType = outputSettings[1];\n }\n\n const inputKey = valueTransformType instanceof ResourceFieldBuilder\n ? valueTransformType.getInputKey()\n : undefined;\n const inputValue = this.resolveSourceValue(item, outputKey, inputKey ?? fieldKey);\n const outputValue = this.transformValue(inputValue, valueTransformType, locale);\n\n if (outputValue !== undefined) {\n set(transformedItem, outputKey, outputValue);\n }\n }\n\n return transformedItem;\n }\n\n /**\n * Resolve one schema field from a model or plain object. Mongo records use `_id`\n * internally, while resources expose `id` by convention.\n */\n protected resolveSourceValue(source: GenericObject | Resource | Model, outputKey: string, inputKey?: string) {\n const key = inputKey ?? outputKey;\n const read = (field: string) => source instanceof Model ? source.get(field) : get(source, field);\n const value = read(key);\n\n return outputKey === \"id\" && key === \"id\" && value === undefined ? read(\"_id\") : value;\n }\n\n /**\n * Get a input value for the given key\n */\n public get(key: string, defaultValue?: any) {\n return get(this.resource, key, defaultValue);\n }\n\n /**\n * Set the given value for the given field\n */\n public set(key: string, value: any): ResourceContract {\n set(this.data, key, value);\n\n return this;\n }\n\n /**\n * Create an array schema for transforming array items\n */\n public arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema {\n return {\n __type: \"arrayOf\",\n schema,\n };\n }\n\n /**\n * Get a string field builder\n */\n public string(inputKey?: string) {\n return this.fieldBuilder(\"string\", inputKey);\n }\n\n /**\n * Get a date field builder\n */\n public date(inputKey?: string) {\n return this.fieldBuilder(\"date\", inputKey);\n }\n\n /**\n * Get a localized field builder\n */\n public localized(inputKey?: string) {\n return this.fieldBuilder(\"localized\", inputKey);\n }\n\n /**\n * Get a url field builder\n */\n public url(inputKey?: string) {\n return this.fieldBuilder(\"url\", inputKey);\n }\n\n /**\n * Get a uploadsUrl field builder\n */\n public uploadsUrl(inputKey?: string) {\n return this.fieldBuilder(\"uploadsUrl\", inputKey);\n }\n\n /**\n * Get a number field builder\n */\n public number(inputKey?: string) {\n return this.fieldBuilder(\"number\", inputKey);\n }\n\n /**\n * Get a boolean field builder\n */\n public boolean(inputKey?: string) {\n return this.fieldBuilder(\"boolean\", inputKey);\n }\n\n /**\n * Get a float field builder\n */\n public float(inputKey?: string) {\n return this.fieldBuilder(\"float\", inputKey);\n }\n\n /**\n * Get a int field builder\n */\n public int(inputKey?: string) {\n return this.fieldBuilder(\"int\", inputKey);\n }\n\n /**\n * New field builder\n */\n protected fieldBuilder(type: ResourceOutputValueCastType, inputKey?: string) {\n const builder = new ResourceFieldBuilder(type);\n\n if (inputKey) {\n builder.setInputKey(inputKey);\n }\n\n return builder;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resource.mjs","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"sourcesContent":["import { get, isLazy, set, type GenericObject } from \"@mongez/reinforcements\";\nimport { Model } from \"@warlock.js/cascade\";\nimport { useRequestStore } from \"../http/context/request-context\";\nimport { isNullableResource } from \"./nullable-resource\";\nimport { ResourceFieldBuilder } from \"./resource-field-builder\";\nimport {\n type ResourceArraySchema,\n type ResourceFieldConfig,\n type ResourceOutputValueCastType,\n type ResourceSchema,\n} from \"./types\";\n\n/**\n * Maximum recursion depth for self-referencing fields.\n * Prevents runaway serialization on deep trees or circular data.\n */\nconst MAX_SELF_DEPTH = 10;\n\n/**\n * Whether the given schema entry is an `arrayOf` item schema\n */\nfunction isArrayOfSchema(value: unknown): value is ResourceArraySchema {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n (value as { __type?: unknown }).__type === \"arrayOf\" &&\n typeof (value as { schema?: unknown }).schema === \"object\" &&\n (value as { schema?: unknown }).schema !== null\n );\n}\n\n/**\n * Resource contract\n */\nexport interface ResourceContract<Out = GenericObject> {\n /**\n * Resource data\n */\n resource: GenericObject;\n\n /**\n * Resource final output\n */\n data: GenericObject;\n\n /**\n * Original data\n */\n originalData: GenericObject;\n\n /**\n * Convert resource to JSON\n */\n toJSON(): Out;\n\n /**\n * Transform the given value with given type\n */\n transform(value: any, type: ResourceOutputValueCastType, locale?: string): any;\n\n /**\n * Get a input value for the given key\n */\n get(key: string, defaultValue?: any): any;\n\n /**\n * Set the given value for the given field\n */\n set(key: string, value: any): ResourceContract;\n\n /**\n * Create an array schema for transforming array items\n */\n arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema;\n\n /**\n * Get a string field builder\n */\n string(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a date field builder\n */\n date(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a localized field builder\n */\n localized(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a url field builder\n */\n url(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a uploadsUrl field builder\n */\n uploadsUrl(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a number field builder\n */\n number(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a boolean field builder\n */\n boolean(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a float field builder\n */\n float(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a int field builder\n */\n int(inputKey?: string): ResourceFieldBuilder;\n}\n\n/**\n * Resource constructor\n */\nexport interface ResourceConstructor<Out = GenericObject> {\n new (originalData: GenericObject | Resource | Model): ResourceContract<Out>;\n}\n\nexport class Resource implements ResourceContract {\n /**\n * Resource data\n */\n public resource: GenericObject = {};\n\n /**\n * Resource final output\n */\n public data: GenericObject = {};\n\n /**\n * Tracks visited object identities during self-reference recursion.\n * Prevents infinite loops on circular data (e.g. A.parent → B, B.parent → A).\n */\n protected _selfSeen?: Set<unknown>;\n\n /**\n * Raw resource schema — field declarations as written by the developer.\n * Used by doc generators for introspection.\n */\n public static schema: Record<string, ResourceFieldConfig | string> = {};\n\n /**\n * Normalized schema — all string cast types and tuples converted to\n * ResourceFieldBuilder instances at definition time. Used by transformOutput at runtime.\n */\n public static parsedSchema: Record<string, ResourceFieldConfig> = {};\n\n /**\n * Normalize a raw schema into parsedSchema.\n * Converts string cast types (including suffixes) and tuples into pre-built builders.\n * Other entry types (ResourceConstructor, resolver functions, ResourceArraySchema) are kept as-is.\n */\n public static normalizeSchema(schema: Record<string, ResourceFieldConfig | string>): Record<string, ResourceFieldConfig> {\n const parsed: Record<string, ResourceFieldConfig> = {};\n\n for (const [key, value] of Object.entries(schema)) {\n if (value === \"self\" || value === \"self[]\") {\n // Keep self-references as-is — resolved lazily in transformValue\n parsed[key] = value;\n } else if (isLazy<ResourceConstructor>(value)) {\n // Keep Lazy<ResourceConstructor> as-is — resolved at transform\n // time so cyclic resource refs (A embeds B embeds A) compose\n // without import-graph cycles.\n parsed[key] = value;\n } else if (typeof value === \"string\") {\n parsed[key] = ResourceFieldBuilder.fromCastType(value);\n } else if (\n Array.isArray(value) &&\n value.length === 2 &&\n typeof value[0] === \"string\" &&\n typeof value[1] === \"string\"\n ) {\n const builder = ResourceFieldBuilder.fromCastType(value[1]);\n builder.setInputKey(value[0]);\n parsed[key] = builder;\n } else if (isArrayOfSchema(value)) {\n // arrayOf item schemas follow the same rules as the top level: cast\n // strings and tuples become builders (nested arrayOf recurses).\n parsed[key] = { ...value, schema: Resource.normalizeSchema(value.schema) };\n } else {\n parsed[key] = value;\n }\n }\n\n return parsed;\n }\n\n /**\n * Constructor\n */\n public constructor(public originalData: GenericObject | Resource | Model) {\n if (this.originalData instanceof Model) {\n this.resource = this.originalData.data;\n } else if (this.originalData instanceof Resource) {\n this.resource = this.originalData.data;\n } else {\n this.resource = this.originalData;\n }\n }\n\n /**\n * Convert resource to JSON\n */\n public toJSON() {\n this.boot();\n this.transformOutput();\n this.extend();\n\n return this.data;\n }\n\n /**\n * Boot method\n * Called before transforming the resource\n */\n protected boot() {\n //\n }\n\n /**\n * Transform resource to output using the pre-normalized parsedSchema.\n * Builders handle their own array/nullable logic internally.\n * ResourceConstructor and ResourceArraySchema handle arrays in transformValue.\n */\n protected transformOutput() {\n const localeCode = useRequestStore()?.request?.locale;\n const parsedSchema = this.resolveParsedSchema();\n // A model is read through its accessors; anything else through the\n // constructor's normalized input (a wrapped resource's serialized data).\n const source = this.originalData instanceof Model ? this.originalData : this.resource;\n\n for (const [outputKey, outputSettings] of Object.entries(parsedSchema)) {\n const inputKey = outputSettings instanceof ResourceFieldBuilder\n ? outputSettings.getInputKey()\n : undefined;\n const inputValue = this.resolveSourceValue(source, outputKey, inputKey);\n const outputValue = this.transformValue(inputValue, outputSettings, localeCode);\n\n if (outputValue !== undefined) {\n this.set(outputKey, outputValue);\n }\n }\n }\n\n /**\n * Resolve the normalized schema of THIS class.\n * `@RegisterResource()` is optional: when the class has no own `parsedSchema`\n * (a static field is inherited from the base as `{}`), normalize its own\n * static `schema` (or an instance `schema` field) once and cache it on the class.\n */\n protected resolveParsedSchema(): Record<string, ResourceFieldConfig> {\n const ctor = this.constructor as typeof Resource;\n\n if (Object.hasOwn(ctor, \"parsedSchema\")) {\n return ctor.parsedSchema;\n }\n\n const instanceSchema = (this as unknown as { schema?: Record<string, ResourceFieldConfig | string> }).schema;\n const schema = instanceSchema ?? ctor.schema;\n\n if (!schema || Object.keys(schema).length === 0) {\n throw new Error(\n `Resource \"${ctor.name}\" has no schema. Declare \\`static schema = { ... }\\` on the class.`,\n );\n }\n\n const parsed = Resource.normalizeSchema(schema);\n\n // Instance schemas are per-class by convention, so caching on the class is safe\n ctor.parsedSchema = parsed;\n\n return parsed;\n }\n\n /**\n * Transform the given value with given type\n */\n public transform(value: any, type: ResourceOutputValueCastType, locale?: string) {\n return new ResourceFieldBuilder(type).transform(value, locale);\n }\n\n /**\n * Transform the given value for the given output setting.\n * After normalization, string cast types no longer reach here — they are pre-converted to builders.\n */\n protected transformValue(value: any, outputSettings: ResourceFieldConfig, locale?: string): any {\n let outputValue: any;\n\n if (isLazy<ResourceConstructor>(outputSettings)) {\n // Resolve the lazy ref now (cyclic resource imports are fully loaded\n // by serialization time) and recurse — the resolved constructor is\n // handled by the ResourceConstructor branch below.\n return this.transformValue(value, outputSettings.resolve(), locale);\n }\n\n if (isNullableResource(outputSettings)) {\n // nullable(Resource) — always present: null for a missing input, the resource output otherwise\n if (value === null || value === undefined) return null;\n\n return this.transformValue(value, outputSettings.resource, locale) ?? null;\n }\n\n if (Array.isArray(outputSettings) && outputSettings.length === 1) {\n // [Resource] — a list field. [] stays [], a missing/null input omits the field.\n if (!Array.isArray(value)) return;\n\n return value\n .map((item) => this.transformValue(item, outputSettings[0], locale))\n .filter((v) => v !== undefined);\n }\n\n if (outputSettings === \"self\" || outputSettings === \"self[]\") {\n // Self-reference — resolve using the same resource class with cycle detection\n outputValue = this.transformSelfReference(value, outputSettings === \"self[]\");\n } else if (\n typeof outputSettings === \"function\" &&\n outputSettings.prototype instanceof Resource\n ) {\n if (!value) return;\n // Nested resource — handle both single and array values\n if (Array.isArray(value)) {\n // An empty list stays an empty list ([] is the honest output); only a\n // missing/null input omits the field.\n outputValue = value\n .map((item) => new (outputSettings as typeof Resource)(item).toJSON())\n .filter((v) => v !== undefined);\n } else {\n outputValue = new (outputSettings as typeof Resource)(value).toJSON();\n }\n } else if (typeof outputSettings === \"function\") {\n // Resolver function - bind to Resource instance for access to this.get(), etc.\n outputValue = (outputSettings as Function).call(this, value, this);\n } else if (outputSettings instanceof ResourceFieldBuilder) {\n // Builder — handles array mapping and nullable internally via isArrayField\n outputValue = outputSettings.transform(value, locale);\n } else if (\n typeof outputSettings === \"object\" &&\n outputSettings !== null &&\n \"__type\" in outputSettings &&\n outputSettings.__type === \"arrayOf\"\n ) {\n // ResourceArraySchema — structured array items with their own sub-schema\n if (Array.isArray(value)) {\n outputValue = value\n .map((item) => this.transformArrayItem(item, outputSettings.schema, locale))\n .filter((v) => v !== undefined);\n } else {\n outputValue = this.transformArrayItem(value, outputSettings.schema, locale);\n }\n }\n\n return outputValue;\n }\n\n /**\n * Extend the resource output\n */\n protected extend() {\n //\n }\n\n /**\n * Transform a self-referencing field value.\n *\n * @example\n * // Single: parent: \"self\"\n * // Array: children: \"self[]\"\n */\n protected transformSelfReference(value: any, isArray: boolean): any {\n if (isArray) {\n return Array.isArray(value)\n ? value.map((item) => this.resolveSelf(item)).filter((v) => v !== undefined)\n : undefined;\n }\n\n return this.resolveSelf(value);\n }\n\n /**\n * Resolve a single self-reference value.\n * Uses identity-based cycle detection (id/_id) and a max depth guard.\n */\n protected resolveSelf(value: any): any {\n if (!value) return undefined;\n\n const identity = value.id ?? value._id ?? value;\n // Ancestors of the current path only: siblings never see each other,\n // so the set size is the depth.\n const ancestors = this._selfSeen ?? new Set();\n\n // Circular reference or depth limit reached — stop recursion\n if (ancestors.has(identity) || ancestors.size >= MAX_SELF_DEPTH) {\n return undefined;\n }\n\n const SelfConstructor = this.constructor as typeof Resource;\n const child = new SelfConstructor(value);\n child._selfSeen = new Set(ancestors).add(identity);\n\n return child.toJSON();\n }\n\n /**\n * Transform a single array item according to the given schema\n */\n protected transformArrayItem(\n item: any,\n schema: Record<string, ResourceFieldConfig>,\n locale?: string,\n ) {\n const transformedItem: GenericObject = {};\n\n for (const [outputKey, outputSettings] of Object.entries(schema)) {\n let fieldKey = outputKey;\n let valueTransformType = outputSettings as ResourceFieldConfig;\n\n if (Array.isArray(outputSettings) && outputSettings.length === 2) {\n fieldKey = outputSettings[0] as string;\n valueTransformType = outputSettings[1] as ResourceFieldConfig;\n }\n\n const inputKey = valueTransformType instanceof ResourceFieldBuilder\n ? valueTransformType.getInputKey()\n : undefined;\n const inputValue = this.resolveSourceValue(item, outputKey, inputKey ?? fieldKey);\n const outputValue = this.transformValue(inputValue, valueTransformType, locale);\n\n if (outputValue !== undefined) {\n set(transformedItem, outputKey, outputValue);\n }\n }\n\n return transformedItem;\n }\n\n /**\n * Resolve one schema field from a model or plain object. Mongo records use `_id`\n * internally, while resources expose `id` by convention.\n */\n protected resolveSourceValue(source: GenericObject | Resource | Model, outputKey: string, inputKey?: string) {\n const key = inputKey ?? outputKey;\n const read = (field: string) => source instanceof Model ? source.get(field) : get(source, field);\n const value = read(key);\n\n return outputKey === \"id\" && key === \"id\" && value === undefined ? read(\"_id\") : value;\n }\n\n /**\n * Get a input value for the given key\n */\n public get(key: string, defaultValue?: any) {\n return get(this.resource, key, defaultValue);\n }\n\n /**\n * Set the given value for the given field\n */\n public set(key: string, value: any): ResourceContract {\n set(this.data, key, value);\n\n return this;\n }\n\n /**\n * Create an array schema for transforming array items\n */\n public arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema {\n return {\n __type: \"arrayOf\",\n schema,\n };\n }\n\n /**\n * Get a string field builder\n */\n public string(inputKey?: string) {\n return this.fieldBuilder(\"string\", inputKey);\n }\n\n /**\n * Get a date field builder\n */\n public date(inputKey?: string) {\n return this.fieldBuilder(\"date\", inputKey);\n }\n\n /**\n * Get a localized field builder\n */\n public localized(inputKey?: string) {\n return this.fieldBuilder(\"localized\", inputKey);\n }\n\n /**\n * Get a url field builder\n */\n public url(inputKey?: string) {\n return this.fieldBuilder(\"url\", inputKey);\n }\n\n /**\n * Get a uploadsUrl field builder\n */\n public uploadsUrl(inputKey?: string) {\n return this.fieldBuilder(\"uploadsUrl\", inputKey);\n }\n\n /**\n * Get a number field builder\n */\n public number(inputKey?: string) {\n return this.fieldBuilder(\"number\", inputKey);\n }\n\n /**\n * Get a boolean field builder\n */\n public boolean(inputKey?: string) {\n return this.fieldBuilder(\"boolean\", inputKey);\n }\n\n /**\n * Get a float field builder\n */\n public float(inputKey?: string) {\n return this.fieldBuilder(\"float\", inputKey);\n }\n\n /**\n * Get a int field builder\n */\n public int(inputKey?: string) {\n return this.fieldBuilder(\"int\", inputKey);\n }\n\n /**\n * New field builder\n */\n protected fieldBuilder(type: ResourceOutputValueCastType, inputKey?: string) {\n const builder = new ResourceFieldBuilder(type);\n\n if (inputKey) {\n builder.setInputKey(inputKey);\n }\n\n return builder;\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { ResponseStatus } from "../http/response.mjs";
2
2
  import { ResourceFieldBuilder } from "./resource-field-builder.mjs";
3
3
  import { ResourceConstructor, ResourceContract } from "./resource.mjs";
4
+ import { NullableResource } from "./nullable-resource.mjs";
4
5
  import { Lazy } from "@mongez/reinforcements";
5
6
 
6
7
  //#region ../core/src/resource/types.d.ts
@@ -26,7 +27,7 @@ type ResourceArraySchema = {
26
27
  __type: "arrayOf";
27
28
  schema: Record<string, ResourceFieldConfig>;
28
29
  };
29
- type ResourceFieldConfig = ResourceCastType | ResourceConstructor | ResourceSelfReference | Lazy<ResourceConstructor> | [string, ResourceCastType] | ResourceFieldBuilder | ResourceArraySchema | ((value: any, resource: ResourceContract) => any);
30
+ type ResourceFieldConfig = ResourceCastType | ResourceConstructor | ResourceSelfReference | Lazy<ResourceConstructor> | NullableResource | [ResourceConstructor] | [string, ResourceCastType] | ResourceFieldBuilder | ResourceArraySchema | ((value: any, resource: ResourceContract) => any);
30
31
  type ResourceSchema = Record<string, ResourceFieldConfig>;
31
32
  type ResourceFieldBuilderDateOutputOptions = {
32
33
  /**
@@ -64,9 +65,10 @@ type ResourceFieldBuilderDateOutputOptions = {
64
65
  * (e.g. "string", "number?", "string[]", "date[]?")
65
66
  * - ResourceConstructor for a single nested resource object
66
67
  * - [ResourceConstructor] (tuple) for an array of a nested resource
68
+ * - nullable(ResourceConstructor) for a nested resource that is `null` when the value is missing
67
69
  * - A nested plain object of any of the above
68
70
  */
69
- type ResponseBodyValue = ResourceCastType | ResourceConstructor | [ResourceConstructor] | {
71
+ type ResponseBodyValue = ResourceCastType | ResourceConstructor | [ResourceConstructor] | NullableResource<ResourceConstructor<any>> | {
70
72
  [key: string]: ResponseBodyValue;
71
73
  };
72
74
  /**
@@ -112,7 +114,9 @@ type CastOutput<C extends string> = C extends `${infer B}[]?` ? BaseCastOutput<B
112
114
  * `Root` is the schema of the resource being defined, used to resolve `"self"` and `"self[]"`.
113
115
  * Anything that cannot be inferred statically (builders) is `unknown`, never `any`.
114
116
  */
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 {
117
+ 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 NullableResource<infer R> ? FieldOutput<R, Root> | null : F extends readonly [new (...args: any[]) => {
118
+ toJSON(): infer O;
119
+ }] ? AnyToUnknown<O>[] : 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
120
  readonly __type: "arrayOf";
117
121
  readonly schema: infer Item extends ResourceSchema;
118
122
  } ? ResourceOutputOf<Item, Root>[] : F extends (new (...args: any[]) => {
@@ -141,12 +145,13 @@ type ResourceOutput<R> = R extends (new (...args: any[]) => {
141
145
  /**
142
146
  * Output type of one response schema body value, with the same rules as a resource schema:
143
147
  * a cast string resolves through `CastOutput`, a resource through `ResourceOutput`,
144
- * `[Resource]` to an array of it, and a nested object recurses.
148
+ * `[Resource]` to an array of it, `nullable(Resource)` to its output or `null`, and a nested
149
+ * object recurses.
145
150
  *
146
151
  * @example
147
152
  * type Out = ResponseBodyOutput<{ user: typeof UserResource; token: "string"; tags: "string[]?" }>;
148
153
  */
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;
154
+ 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 NullableResource<infer R> ? ResourceOutput<R> | null : B extends object ? { -readonly [K in keyof B]: ResponseBodyOutput<B[K]> } : never;
150
155
  /**
151
156
  * Output type of a whole response schema: the body output per declared status code.
152
157
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/resource/types.ts"],"mappings":"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"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/resource/types.ts"],"mappings":"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"}
@@ -43,14 +43,14 @@ declare abstract class Restful<T extends Model> implements RouteResource {
43
43
  list({
44
44
  request,
45
45
  response
46
- }: HttpContext): Promise<any[] | Response | Record<string, any>>;
46
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
47
47
  /**
48
48
  * Get single record
49
49
  */
50
50
  get({
51
51
  request,
52
52
  response
53
- }: HttpContext): Promise<any[] | Response | Record<string, any>>;
53
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
54
54
  /**
55
55
  * Create a new record
56
56
  */
@@ -78,14 +78,14 @@ declare abstract class Restful<T extends Model> implements RouteResource {
78
78
  delete({
79
79
  request,
80
80
  response
81
- }: HttpContext): Promise<any[] | Response | Record<string, any>>;
81
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
82
82
  /**
83
83
  * Bulk delete records
84
84
  */
85
85
  bulkDelete({
86
86
  request,
87
87
  response
88
- }: HttpContext): Promise<any[] | Response | Record<string, any>>;
88
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
89
89
  /**
90
90
  * Before create
91
91
  */
@@ -136,7 +136,7 @@ declare abstract class Restful<T extends Model> implements RouteResource {
136
136
  * Call middleware for the given method
137
137
  *
138
138
  */
139
- protected callMiddleware(method: string, request: Request, response: Response, _record?: any): Promise<any[] | Response | Record<string, any> | undefined>;
139
+ protected callMiddleware(method: string, request: Request, response: Response, _record?: any): Promise<any[] | Record<string, any> | Response | undefined>;
140
140
  }
141
141
  //#endregion
142
142
  export { Restful };
@@ -1 +1 @@
1
- {"version":3,"file":"restful.d.mts","names":[],"sources":["../../../../../../../core/src/restful/restful.ts"],"mappings":"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"}
1
+ {"version":3,"file":"restful.d.mts","names":[],"sources":["../../../../../../../core/src/restful/restful.ts"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { isNullableResource } from "../resource/nullable-resource.mjs";
1
2
  import { posix, relative, sep } from "node:path";
2
3
 
3
4
  //#region ../core/src/router/response-schema-types.ts
@@ -52,6 +53,7 @@ function describeResource(reference) {
52
53
  }
53
54
  function describeBodyValue(value, path, resolveResource, onUnmapped) {
54
55
  if (typeof value === "string") return describeCast(value);
56
+ if (isNullableResource(value)) return `(${describeBodyValue(value.resource, path, resolveResource, onUnmapped)}) | null`;
55
57
  if (typeof value === "function") {
56
58
  const reference = resolveResource(value);
57
59
  if (!reference) {