@warlock.js/core 5.26.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 (98) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/esm/cli/commands/openapi-generator.action.mjs +43 -0
  3. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
  4. package/esm/cli/commands/openapi-generator.command.mjs +44 -0
  5. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
  6. package/esm/cli/framework-cli-commands.mjs +2 -0
  7. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  8. package/esm/dev-server/build-development-openapi-document.mjs +36 -0
  9. package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
  10. package/esm/dev-server/collect-development-api-routes.mjs +27 -0
  11. package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
  12. package/esm/dev-server/dev-logger.mjs +9 -1
  13. package/esm/dev-server/dev-logger.mjs.map +1 -1
  14. package/esm/dev-server/development-server.mjs +6 -3
  15. package/esm/dev-server/development-server.mjs.map +1 -1
  16. package/esm/dev-server/files-orchestrator.mjs +17 -1
  17. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  18. package/esm/dev-server/model-resource-typings.mjs +246 -0
  19. package/esm/dev-server/model-resource-typings.mjs.map +1 -0
  20. package/esm/dev-server/type-generator.mjs +46 -2
  21. package/esm/dev-server/type-generator.mjs.map +1 -1
  22. package/esm/http/index.d.mts +2 -1
  23. package/esm/http/request.d.mts +2 -2
  24. package/esm/http/request.d.mts.map +1 -1
  25. package/esm/http/serialized.d.mts +114 -0
  26. package/esm/http/serialized.d.mts.map +1 -0
  27. package/esm/index.d.mts +8 -3
  28. package/esm/index.mjs +4 -1
  29. package/esm/openapi/build-openapi-document.d.mts +17 -0
  30. package/esm/openapi/build-openapi-document.d.mts.map +1 -0
  31. package/esm/openapi/build-openapi-document.mjs +124 -0
  32. package/esm/openapi/build-openapi-document.mjs.map +1 -0
  33. package/esm/openapi/cast-schema.mjs +89 -0
  34. package/esm/openapi/cast-schema.mjs.map +1 -0
  35. package/esm/openapi/collect-openapi-document.mjs +40 -0
  36. package/esm/openapi/collect-openapi-document.mjs.map +1 -0
  37. package/esm/openapi/development-openapi-provider.d.mts +17 -0
  38. package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
  39. package/esm/openapi/development-openapi-provider.mjs +21 -0
  40. package/esm/openapi/development-openapi-provider.mjs.map +1 -0
  41. package/esm/openapi/error-responses.mjs +67 -0
  42. package/esm/openapi/error-responses.mjs.map +1 -0
  43. package/esm/openapi/index.d.mts +3 -0
  44. package/esm/openapi/index.mjs +4 -0
  45. package/esm/openapi/openapi-child-output.mjs +57 -0
  46. package/esm/openapi/openapi-child-output.mjs.map +1 -0
  47. package/esm/openapi/openapi-protocol.mjs +30 -0
  48. package/esm/openapi/openapi-protocol.mjs.map +1 -0
  49. package/esm/openapi/paths.mjs +63 -0
  50. package/esm/openapi/paths.mjs.map +1 -0
  51. package/esm/openapi/request-schemas.mjs +138 -0
  52. package/esm/openapi/request-schemas.mjs.map +1 -0
  53. package/esm/openapi/response-schemas.mjs +85 -0
  54. package/esm/openapi/response-schemas.mjs.map +1 -0
  55. package/esm/openapi/schema-registry.mjs +143 -0
  56. package/esm/openapi/schema-registry.mjs.map +1 -0
  57. package/esm/openapi/security.mjs +98 -0
  58. package/esm/openapi/security.mjs.map +1 -0
  59. package/esm/openapi/types.d.mts +119 -0
  60. package/esm/openapi/types.d.mts.map +1 -0
  61. package/esm/production/registration-child-runner.mjs +139 -0
  62. package/esm/production/registration-child-runner.mjs.map +1 -0
  63. package/esm/production/route-registration-child.mjs +30 -2
  64. package/esm/production/route-registration-child.mjs.map +1 -1
  65. package/esm/production/route-registration-snapshot.mjs +22 -119
  66. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  67. package/esm/resource/define-resource.d.mts +44 -14
  68. package/esm/resource/define-resource.d.mts.map +1 -1
  69. package/esm/resource/define-resource.mjs +0 -40
  70. package/esm/resource/define-resource.mjs.map +1 -1
  71. package/esm/resource/index.d.mts +1 -1
  72. package/esm/resource/resource.d.mts +4 -4
  73. package/esm/resource/resource.d.mts.map +1 -1
  74. package/esm/resource/resource.mjs.map +1 -1
  75. package/esm/resource/types.d.mts +89 -4
  76. package/esm/resource/types.d.mts.map +1 -1
  77. package/esm/restful/restful.d.mts +5 -5
  78. package/esm/restful/restful.d.mts.map +1 -1
  79. package/esm/router/index.d.mts +1 -1
  80. package/esm/router/named-api-routes-with-responses.mjs +59 -0
  81. package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
  82. package/esm/router/response-schema-types.mjs +100 -0
  83. package/esm/router/response-schema-types.mjs.map +1 -0
  84. package/esm/router/router.d.mts +2 -2
  85. package/esm/router/router.d.mts.map +1 -1
  86. package/esm/router/router.mjs +38 -11
  87. package/esm/router/router.mjs.map +1 -1
  88. package/esm/router/types.d.mts +16 -1
  89. package/esm/router/types.d.mts.map +1 -1
  90. package/llms-full.txt +265 -5
  91. package/llms.txt +3 -2
  92. package/package.json +13 -13
  93. package/skills/README.md +3 -2
  94. package/skills/create-controller/SKILL.md +39 -2
  95. package/skills/define-resource/SKILL.md +100 -1
  96. package/skills/generate-openapi/SKILL.md +120 -0
  97. package/skills/index.md +3 -3
  98. package/skills/validate-input/SKILL.md +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"route-registration-child.mjs","names":[],"sources":["../../../../../../../core/src/production/route-registration-child.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\nimport { Application } from \"../application\";\nimport { bootstrap } from \"../bootstrap\";\nimport { loadConfigFiles } from \"../config/load-config-files\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { router } from \"../router/router\";\nimport { loadEnvironmentFiles } from \"../utils/load-environment\";\nimport { appPath } from \"../utils/paths\";\nimport type { Environment, RuntimeStrategy } from \"../utils/environment\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\n\nconst PROTOCOL_VERSION = 1;\n\ntype RequestMessage = Readonly<{\n version: typeof PROTOCOL_VERSION;\n cwd: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n}>;\n\nprocess.once(\"message\", async (message: unknown) => {\n try {\n const request = validateRequest(message);\n process.chdir(request.cwd);\n\n if (request.environment) Application.setEnvironment(request.environment);\n if (request.runtimeStrategy) Application.setRuntimeStrategy(request.runtimeStrategy);\n\n await loadEnvironmentFiles();\n await warlockConfigManager.load();\n await filesOrchestrator.init();\n await bootstrap();\n\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\n await filesOrchestrator.load(\"src/app/bootstrap.ts\");\n }\n\n await filesOrchestrator.initializeAll();\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());\n await loadConfigFiles(true);\n\n for (const file of filesOrchestrator.files.values()) {\n if (file.type === \"model\") {\n await sendProgress(file.relativePath);\n await filesOrchestrator.moduleLoader.loadModule(file, \"model\");\n }\n }\n\n await filesOrchestrator.moduleLoader.loadAll({ onBeforeLoad: (file) => sendProgress(file.relativePath) });\n\n await sendAndExit({\n type: \"route-registration:snapshot\",\n snapshot: { version: PROTOCOL_VERSION, routes: router.getNamedApiRoutes() },\n });\n } catch (error) {\n const message = error instanceof Error ? error.stack || error.message : String(error);\n await sendAndExit({ type: \"route-registration:error\", message }, 1);\n }\n});\n\nfunction validateRequest(value: unknown): RequestMessage {\n if (\n typeof value !== \"object\" ||\n value === null ||\n (value as { version?: unknown }).version !== PROTOCOL_VERSION ||\n typeof (value as { cwd?: unknown }).cwd !== \"string\" ||\n !isOptionalEnvironment((value as { environment?: unknown }).environment) ||\n !isOptionalRuntimeStrategy((value as { runtimeStrategy?: unknown }).runtimeStrategy)\n ) {\n throw new Error(\"Route registration child received an invalid protocol request.\");\n }\n\n return value as RequestMessage;\n}\n\nfunction isOptionalEnvironment(value: unknown): value is Environment | undefined {\n return (\n value === undefined || value === \"development\" || value === \"production\" || value === \"test\"\n );\n}\n\nfunction isOptionalRuntimeStrategy(value: unknown): value is RuntimeStrategy | undefined {\n return value === undefined || value === \"development\" || value === \"production\";\n}\n\nasync function sendAndExit(message: unknown, exitCode = 0): Promise<never> {\n if (typeof process.send !== \"function\") {\n throw new Error(\"Route registration child requires an IPC channel.\");\n }\n\n await new Promise<void>((resolve, reject) => {\n process.send!(message, (error) => (error ? reject(error) : resolve()));\n });\n process.disconnect?.();\n process.exit(exitCode);\n}\n\nasync function sendProgress(module: string): Promise<void> {\n if (typeof process.send !== \"function\") return;\n await new Promise<void>((resolve, reject) => {\n process.send!({ type: \"route-registration:progress\", module }, (error) =>\n error ? reject(error) : resolve(),\n );\n });\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,mBAAmB;AASzB,QAAQ,KAAK,WAAW,OAAO,YAAqB;CAClD,IAAI;EACF,MAAM,UAAU,gBAAgB,OAAO;EACvC,QAAQ,MAAM,QAAQ,GAAG;EAEzB,IAAI,QAAQ,aAAa,YAAY,eAAe,QAAQ,WAAW;EACvE,IAAI,QAAQ,iBAAiB,YAAY,mBAAmB,QAAQ,eAAe;EAEnF,MAAM,qBAAqB;EAC3B,MAAM,qBAAqB,KAAK;EAChC,MAAM,kBAAkB,KAAK;EAC7B,MAAM,UAAU;EAEhB,IAAI,MAAM,gBAAgB,QAAQ,cAAc,CAAC,GAC/C,MAAM,kBAAkB,KAAK,sBAAsB;EAGrD,MAAM,kBAAkB,cAAc;EACtC,kBAAkB,sBAAsB,QAAQ,kBAAkB,SAAS,CAAC;EAC5E,MAAM,gBAAgB,IAAI;EAE1B,KAAK,MAAM,QAAQ,kBAAkB,MAAM,OAAO,GAChD,IAAI,KAAK,SAAS,SAAS;GACzB,MAAM,aAAa,KAAK,YAAY;GACpC,MAAM,kBAAkB,aAAa,WAAW,MAAM,OAAO;EAC/D;EAGF,MAAM,kBAAkB,aAAa,QAAQ,EAAE,eAAe,SAAS,aAAa,KAAK,YAAY,EAAE,CAAC;EAExG,MAAM,YAAY;GAChB,MAAM;GACN,UAAU;IAAE,SAAS;IAAkB,QAAQ,OAAO,kBAAkB;GAAE;EAC5E,CAAC;CACH,SAAS,OAAO;EAEd,MAAM,YAAY;GAAE,MAAM;GAA4B,SADtC,iBAAiB,QAAQ,MAAM,SAAS,MAAM,UAAU,OAAO,KAAK;EACtB,GAAG,CAAC;CACpE;AACF,CAAC;AAED,SAAS,gBAAgB,OAAgC;CACvD,IACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAgC,YAAY,oBAC7C,OAAQ,MAA4B,QAAQ,YAC5C,CAAC,sBAAuB,MAAoC,WAAW,KACvE,CAAC,0BAA2B,MAAwC,eAAe,GAEnF,MAAM,IAAI,MAAM,gEAAgE;CAGlF,OAAO;AACT;AAEA,SAAS,sBAAsB,OAAkD;CAC/E,OACE,UAAU,UAAa,UAAU,iBAAiB,UAAU,gBAAgB,UAAU;AAE1F;AAEA,SAAS,0BAA0B,OAAsD;CACvF,OAAO,UAAU,UAAa,UAAU,iBAAiB,UAAU;AACrE;AAEA,eAAe,YAAY,SAAkB,WAAW,GAAmB;CACzE,IAAI,OAAO,QAAQ,SAAS,YAC1B,MAAM,IAAI,MAAM,mDAAmD;CAGrE,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,QAAQ,KAAM,UAAU,UAAW,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAE;CACvE,CAAC;CACD,QAAQ,aAAa;CACrB,QAAQ,KAAK,QAAQ;AACvB;AAEA,eAAe,aAAa,QAA+B;CACzD,IAAI,OAAO,QAAQ,SAAS,YAAY;CACxC,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,QAAQ,KAAM;GAAE,MAAM;GAA+B;EAAO,IAAI,UAC9D,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAClC;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"route-registration-child.mjs","names":[],"sources":["../../../../../../../core/src/production/route-registration-child.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\nimport { Application } from \"../application\";\nimport { bootstrap } from \"../bootstrap\";\nimport { loadConfigFiles } from \"../config/load-config-files\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { buildOpenApiForChild } from \"../openapi/openapi-child-output\";\nimport {\n OPENAPI_PROTOCOL_VERSION,\n OPENAPI_RESULT_MESSAGE,\n parseOpenApiChildOptions,\n type OpenApiChildRequestOptions,\n} from \"../openapi/openapi-protocol\";\nimport { collectNamedApiRoutesWithResponses } from \"../router/named-api-routes-with-responses\";\nimport { loadEnvironmentFiles } from \"../utils/load-environment\";\nimport { appPath } from \"../utils/paths\";\nimport type { Environment, RuntimeStrategy } from \"../utils/environment\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\n\nconst PROTOCOL_VERSION = 1;\n\ntype RequestMessage = Readonly<{\n version: typeof PROTOCOL_VERSION;\n cwd: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n /**\n * Present for `warlock generate.openapi`: build the OpenAPI document in this process and\n * send it instead of the route snapshot. The loading above is identical for both.\n */\n openapi?: OpenApiChildRequestOptions;\n}>;\n\nprocess.once(\"message\", async (message: unknown) => {\n try {\n const request = validateRequest(message);\n process.chdir(request.cwd);\n\n if (request.environment) Application.setEnvironment(request.environment);\n if (request.runtimeStrategy) Application.setRuntimeStrategy(request.runtimeStrategy);\n\n await loadEnvironmentFiles();\n await warlockConfigManager.load();\n await filesOrchestrator.init();\n await bootstrap();\n\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\n await filesOrchestrator.load(\"src/app/bootstrap.ts\");\n }\n\n await filesOrchestrator.initializeAll();\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());\n await loadConfigFiles(true);\n\n for (const file of filesOrchestrator.files.values()) {\n if (file.type === \"model\") {\n await sendProgress(file.relativePath);\n await filesOrchestrator.moduleLoader.loadModule(file, \"model\");\n }\n }\n\n await filesOrchestrator.moduleLoader.loadAll({ onBeforeLoad: (file) => sendProgress(file.relativePath) });\n\n if (request.openapi) {\n const { document, warnings } = await buildOpenApiForChild({\n cwd: request.cwd,\n files: filesOrchestrator.files.values(),\n request: request.openapi,\n onWarn: (warning) => {\n process.stderr.write(`[warlock] ${warning}\\n`);\n },\n });\n\n await sendAndExit({\n type: OPENAPI_RESULT_MESSAGE,\n result: { version: OPENAPI_PROTOCOL_VERSION, document, warnings },\n });\n }\n\n const routes = await collectNamedApiRoutesWithResponses({\n appRoot: request.cwd,\n files: filesOrchestrator.files.values(),\n onWarn: (warning) => {\n process.stderr.write(`[warlock] ${warning}\\n`);\n },\n });\n\n await sendAndExit({\n type: \"route-registration:snapshot\",\n snapshot: { version: PROTOCOL_VERSION, routes },\n });\n } catch (error) {\n const message = error instanceof Error ? error.stack || error.message : String(error);\n await sendAndExit({ type: \"route-registration:error\", message }, 1);\n }\n});\n\nfunction validateRequest(value: unknown): RequestMessage {\n if (\n typeof value !== \"object\" ||\n value === null ||\n (value as { version?: unknown }).version !== PROTOCOL_VERSION ||\n typeof (value as { cwd?: unknown }).cwd !== \"string\" ||\n !isOptionalEnvironment((value as { environment?: unknown }).environment) ||\n !isOptionalRuntimeStrategy((value as { runtimeStrategy?: unknown }).runtimeStrategy)\n ) {\n throw new Error(\"Route registration child received an invalid protocol request.\");\n }\n\n const openapi = (value as { openapi?: unknown }).openapi;\n\n if (openapi !== undefined) {\n parseOpenApiChildOptions(openapi);\n }\n\n return value as RequestMessage;\n}\n\nfunction isOptionalEnvironment(value: unknown): value is Environment | undefined {\n return (\n value === undefined || value === \"development\" || value === \"production\" || value === \"test\"\n );\n}\n\nfunction isOptionalRuntimeStrategy(value: unknown): value is RuntimeStrategy | undefined {\n return value === undefined || value === \"development\" || value === \"production\";\n}\n\nasync function sendAndExit(message: unknown, exitCode = 0): Promise<never> {\n if (typeof process.send !== \"function\") {\n throw new Error(\"Route registration child requires an IPC channel.\");\n }\n\n await new Promise<void>((resolve, reject) => {\n process.send!(message, (error) => (error ? reject(error) : resolve()));\n });\n process.disconnect?.();\n process.exit(exitCode);\n}\n\nasync function sendProgress(module: string): Promise<void> {\n if (typeof process.send !== \"function\") return;\n await new Promise<void>((resolve, reject) => {\n process.send!({ type: \"route-registration:progress\", module }, (error) =>\n error ? reject(error) : resolve(),\n );\n });\n}\n"],"mappings":"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"}
@@ -1,12 +1,7 @@
1
- import path from "node:path";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
4
- import { fork } from "node:child_process";
1
+ import { runRegistrationChild } from "./registration-child-runner.mjs";
5
2
 
6
3
  //#region ../core/src/production/route-registration-snapshot.ts
7
4
  const PROTOCOL_VERSION = 1;
8
- const DEFAULT_TIMEOUT_MS = 3e4;
9
- const MAX_DIAGNOSTIC_BYTES = 16384;
10
5
  /**
11
6
  * Collect named application API routes in a fresh process.
12
7
  *
@@ -16,128 +11,37 @@ const MAX_DIAGNOSTIC_BYTES = 16384;
16
11
  */
17
12
  function collectRouteRegistrationSnapshot(options = {}) {
18
13
  const cwd = options.cwd ?? process.cwd();
19
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20
- const startedAt = Date.now();
21
- const forkProcess = options.forkProcess ?? defaultForkProcess;
22
- const entry = options.childEntry ?? (options.forkProcess ? "<injected-route-registration-child>" : resolveCompiledChildEntry());
23
- return new Promise((resolve, reject) => {
24
- const child = forkProcess(entry, { cwd });
25
- let settled = false;
26
- let receivedSnapshot;
27
- let stdout = "";
28
- let stderr = "";
29
- let pendingModule;
30
- const append = (current, chunk) => {
31
- const remaining = MAX_DIAGNOSTIC_BYTES - Buffer.byteLength(current);
32
- return remaining <= 0 ? current : current + chunk.toString("utf8", 0, remaining);
33
- };
34
- const finish = (error, snapshot) => {
35
- if (settled) return;
36
- settled = true;
37
- clearTimeout(timeout);
38
- if (child.connected) child.disconnect?.();
39
- if (error) {
40
- child.kill?.();
41
- reject(error);
42
- } else resolve(snapshot);
43
- };
44
- child.stdout?.on("data", (chunk) => {
45
- stdout = append(stdout, chunk);
46
- });
47
- child.stderr?.on("data", (chunk) => {
48
- stderr = append(stderr, chunk);
49
- });
50
- const timeout = setTimeout(() => {
51
- const elapsedMs = Date.now() - startedAt;
52
- finish(/* @__PURE__ */ new Error(`Route registration child timed out after ${elapsedMs}ms${pendingModule ? ` while registering ${pendingModule}` : ""}.${formatDiagnostics(stdout, stderr)}`));
53
- }, timeoutMs);
54
- timeout.unref?.();
55
- child.on("error", (error) => {
56
- finish(/* @__PURE__ */ new Error(`Could not start route registration child: ${error.message}${formatDiagnostics(stdout, stderr)}`));
57
- });
58
- child.on("message", (message) => {
59
- if (!isChildMessage(message)) return;
60
- if (message.type === "route-registration:error") {
61
- finish(/* @__PURE__ */ new Error(`Route registration child failed: ${message.message}${formatDiagnostics(stdout, stderr)}`));
62
- return;
63
- }
64
- if (message.type === "route-registration:progress") {
65
- pendingModule = message.module;
66
- return;
67
- }
68
- try {
69
- const snapshot = validateSnapshot(message.snapshot);
70
- if (receivedSnapshot) {
71
- finish(/* @__PURE__ */ new Error("Route registration child sent more than one snapshot."));
72
- return;
73
- }
74
- receivedSnapshot = snapshot;
75
- } catch (error) {
76
- finish(error);
77
- }
78
- });
79
- child.on("close", (code) => {
80
- if (settled) return;
81
- if (code === 0 && receivedSnapshot) {
82
- finish(void 0, receivedSnapshot);
83
- return;
84
- }
85
- finish(/* @__PURE__ */ new Error(`Route registration child exited ${code ?? "by signal"} without a valid snapshot.${formatDiagnostics(stdout, stderr)}`));
86
- });
87
- if (!child.send) {
88
- finish(/* @__PURE__ */ new Error("Route registration child did not provide an IPC send channel."));
89
- return;
90
- }
91
- child.send({
14
+ return runRegistrationChild({
15
+ cwd,
16
+ timeoutMs: options.timeoutMs,
17
+ childEntry: options.childEntry,
18
+ forkProcess: options.forkProcess,
19
+ request: {
92
20
  version: PROTOCOL_VERSION,
93
21
  cwd,
94
22
  environment: options.environment,
95
23
  runtimeStrategy: options.runtimeStrategy
96
- });
24
+ },
25
+ resultMessageType: "route-registration:snapshot",
26
+ resultNoun: "snapshot",
27
+ readResult: (message) => validateSnapshot(message.snapshot)
97
28
  });
98
29
  }
99
- function defaultForkProcess(entry, options) {
100
- return fork(entry, [], {
101
- cwd: options.cwd,
102
- stdio: [
103
- "ignore",
104
- "pipe",
105
- "pipe",
106
- "ipc"
107
- ],
108
- windowsHide: true
109
- });
110
- }
111
- /** Resolve from Core's package root, not an assumed bundler-preserved dirname. */
112
- function resolveCompiledChildEntry() {
113
- let directory = path.dirname(fileURLToPath(import.meta.url));
114
- while (directory !== path.dirname(directory)) {
115
- const packageJson = path.join(directory, "package.json");
116
- if (existsSync(packageJson)) try {
117
- if (JSON.parse(readFileSync(packageJson, "utf8")).name === "@warlock.js/core") {
118
- const child = path.join(directory, "esm", "production", "route-registration-child.mjs");
119
- if (existsSync(child)) return child;
120
- throw new Error(`@warlock.js/core is missing its compiled route-registration child at ${child}. Reinstall a complete package.`);
121
- }
122
- } catch (error) {
123
- if (error instanceof Error && error.message.includes("route-registration child")) throw error;
124
- }
125
- directory = path.dirname(directory);
126
- }
127
- throw new Error("Could not locate @warlock.js/core package root for the route-registration child.");
128
- }
129
- function isChildMessage(value) {
130
- return typeof value === "object" && value !== null && "type" in value && (value.type === "route-registration:snapshot" || value.type === "route-registration:error" || value.type === "route-registration:progress");
131
- }
132
30
  function validateSnapshot(value) {
133
31
  if (!isRecord(value) || value.version !== PROTOCOL_VERSION || !Array.isArray(value.routes)) throw new Error("Route registration child sent an invalid snapshot protocol payload.");
134
32
  const routes = value.routes.map((route) => {
135
33
  if (!isRecord(route) || typeof route.name !== "string" || typeof route.path !== "string" || !isRequestMethod(route.method)) throw new Error("Route registration child sent an invalid route record.");
136
- if (Object.keys(route).length !== 3) throw new Error("Route registration child sent a route record with non-browser-safe fields.");
137
- return Object.freeze({
34
+ const response = route.response;
35
+ const allowedKeys = response === void 0 ? 3 : 4;
36
+ if (Object.keys(route).length !== allowedKeys || response !== void 0 && !isResponseTypeRecord(response)) throw new Error("Route registration child sent a route record with non-browser-safe fields.");
37
+ const base = {
138
38
  name: route.name,
139
39
  path: route.path,
140
40
  method: normalizeRequestMethod(route.method)
41
+ };
42
+ return Object.freeze(response === void 0 ? base : {
43
+ ...base,
44
+ response: Object.freeze({ ...response })
141
45
  });
142
46
  });
143
47
  return Object.freeze({
@@ -145,6 +49,9 @@ function validateSnapshot(value) {
145
49
  routes: Object.freeze(routes)
146
50
  });
147
51
  }
52
+ function isResponseTypeRecord(value) {
53
+ return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
54
+ }
148
55
  function isRecord(value) {
149
56
  return typeof value === "object" && value !== null && !Array.isArray(value);
150
57
  }
@@ -163,10 +70,6 @@ function isRequestMethod(value) {
163
70
  function normalizeRequestMethod(value) {
164
71
  return value.toLowerCase() === "all" ? "all" : value.toUpperCase();
165
72
  }
166
- function formatDiagnostics(stdout, stderr) {
167
- if (!stdout && !stderr) return "";
168
- return `\nChild stdout:\n${stdout}\nChild stderr:\n${stderr}`;
169
- }
170
73
 
171
74
  //#endregion
172
75
  export { collectRouteRegistrationSnapshot };
@@ -1 +1 @@
1
- {"version":3,"file":"route-registration-snapshot.mjs","names":[],"sources":["../../../../../../../core/src/production/route-registration-snapshot.ts"],"sourcesContent":["import { fork, type ChildProcess } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { NamedApiRoute, RequestMethod } from \"../router/types\";\nimport type { Environment, RuntimeStrategy } from \"../utils/environment\";\n\nconst PROTOCOL_VERSION = 1;\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MAX_DIAGNOSTIC_BYTES = 16_384;\n\nexport type RouteRegistrationSnapshot = Readonly<{\n version: typeof PROTOCOL_VERSION;\n routes: readonly NamedApiRoute[];\n}>;\n\nexport type RouteRegistrationChildRequest = Readonly<{\n version: typeof PROTOCOL_VERSION;\n cwd: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n}>;\n\ntype RouteRegistrationChildMessage =\n | Readonly<{ type: \"route-registration:snapshot\"; snapshot: unknown }>\n | Readonly<{ type: \"route-registration:error\"; message: string }>\n | Readonly<{ type: \"route-registration:progress\"; module: string }>;\n\nexport type RouteRegistrationChildProcess = Pick<\n ChildProcess,\n \"send\" | \"kill\" | \"disconnect\" | \"on\" | \"stdout\" | \"stderr\" | \"connected\"\n>;\n\nexport type RouteRegistrationSnapshotOptions = Readonly<{\n cwd?: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n timeoutMs?: number;\n childEntry?: string;\n forkProcess?: (entry: string, options: { cwd: string }) => RouteRegistrationChildProcess;\n}>;\n\n/**\n * Collect named application API routes in a fresh process.\n *\n * The child imports application registration modules but deliberately does not\n * register, boot, or start connectors. Application module top-level code can\n * still have arbitrary user-owned side effects; this boundary is framework-only.\n */\nexport function collectRouteRegistrationSnapshot(\n options: RouteRegistrationSnapshotOptions = {},\n): Promise<RouteRegistrationSnapshot> {\n const cwd = options.cwd ?? process.cwd();\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const startedAt = Date.now();\n const forkProcess = options.forkProcess ?? defaultForkProcess;\n // An injected fork is a protocol seam, not an installation check. Unit\n // fixtures do not have a packaged ESM child and do not need to resolve one.\n const entry =\n options.childEntry ??\n (options.forkProcess ? \"<injected-route-registration-child>\" : resolveCompiledChildEntry());\n\n return new Promise<RouteRegistrationSnapshot>((resolve, reject) => {\n const child = forkProcess(entry, { cwd });\n let settled = false;\n let receivedSnapshot: RouteRegistrationSnapshot | undefined;\n let stdout = \"\";\n let stderr = \"\";\n let pendingModule: string | undefined;\n\n const append = (current: string, chunk: Buffer) => {\n const remaining = MAX_DIAGNOSTIC_BYTES - Buffer.byteLength(current);\n return remaining <= 0 ? current : current + chunk.toString(\"utf8\", 0, remaining);\n };\n\n const finish = (error?: Error, snapshot?: RouteRegistrationSnapshot) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (child.connected) child.disconnect?.();\n if (error) {\n child.kill?.();\n reject(error);\n } else {\n resolve(snapshot!);\n }\n };\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n stdout = append(stdout, chunk);\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr = append(stderr, chunk);\n });\n\n const timeout = setTimeout(() => {\n const elapsedMs = Date.now() - startedAt;\n finish(\n new Error(\n `Route registration child timed out after ${elapsedMs}ms${pendingModule ? ` while registering ${pendingModule}` : \"\"}.${formatDiagnostics(stdout, stderr)}`,\n ),\n );\n }, timeoutMs);\n timeout.unref?.();\n\n child.on(\"error\", (error) => {\n finish(\n new Error(\n `Could not start route registration child: ${error.message}${formatDiagnostics(stdout, stderr)}`,\n ),\n );\n });\n\n child.on(\"message\", (message: unknown) => {\n if (!isChildMessage(message)) return;\n\n if (message.type === \"route-registration:error\") {\n finish(\n new Error(\n `Route registration child failed: ${message.message}${formatDiagnostics(stdout, stderr)}`,\n ),\n );\n return;\n }\n\n if (message.type === \"route-registration:progress\") {\n pendingModule = message.module;\n return;\n }\n\n try {\n const snapshot = validateSnapshot(message.snapshot);\n if (receivedSnapshot) {\n finish(new Error(\"Route registration child sent more than one snapshot.\"));\n return;\n }\n\n receivedSnapshot = snapshot;\n } catch (error) {\n finish(error as Error);\n }\n });\n\n child.on(\"close\", (code) => {\n if (settled) return;\n\n if (code === 0 && receivedSnapshot) {\n finish(undefined, receivedSnapshot);\n return;\n }\n\n finish(\n new Error(\n `Route registration child exited ${code ?? \"by signal\"} without a valid snapshot.${formatDiagnostics(stdout, stderr)}`,\n ),\n );\n });\n\n if (!child.send) {\n finish(new Error(\"Route registration child did not provide an IPC send channel.\"));\n return;\n }\n\n child.send({\n version: PROTOCOL_VERSION,\n cwd,\n environment: options.environment,\n runtimeStrategy: options.runtimeStrategy,\n } satisfies RouteRegistrationChildRequest);\n });\n}\n\nfunction defaultForkProcess(\n entry: string,\n options: { cwd: string },\n): RouteRegistrationChildProcess {\n const forkOptions: NonNullable<Parameters<typeof fork>[2]> & { windowsHide?: boolean } = {\n cwd: options.cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\", \"ipc\"],\n windowsHide: true,\n };\n return fork(entry, [], forkOptions);\n}\n\n/** Resolve from Core's package root, not an assumed bundler-preserved dirname. */\nfunction resolveCompiledChildEntry(): string {\n let directory = path.dirname(fileURLToPath(import.meta.url));\n\n while (directory !== path.dirname(directory)) {\n const packageJson = path.join(directory, \"package.json\");\n if (existsSync(packageJson)) {\n try {\n if (JSON.parse(readFileSync(packageJson, \"utf8\")).name === \"@warlock.js/core\") {\n const child = path.join(directory, \"esm\", \"production\", \"route-registration-child.mjs\");\n if (existsSync(child)) return child;\n throw new Error(\n `@warlock.js/core is missing its compiled route-registration child at ${child}. Reinstall a complete package.`,\n );\n }\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"route-registration child\"))\n throw error;\n }\n }\n directory = path.dirname(directory);\n }\n\n throw new Error(\n \"Could not locate @warlock.js/core package root for the route-registration child.\",\n );\n}\n\nfunction isChildMessage(value: unknown): value is RouteRegistrationChildMessage {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"type\" in value &&\n ((value as { type?: unknown }).type === \"route-registration:snapshot\" ||\n (value as { type?: unknown }).type === \"route-registration:error\" ||\n (value as { type?: unknown }).type === \"route-registration:progress\")\n );\n}\n\nfunction validateSnapshot(value: unknown): RouteRegistrationSnapshot {\n if (!isRecord(value) || value.version !== PROTOCOL_VERSION || !Array.isArray(value.routes)) {\n throw new Error(\"Route registration child sent an invalid snapshot protocol payload.\");\n }\n\n const routes = value.routes.map((route) => {\n if (\n !isRecord(route) ||\n typeof route.name !== \"string\" ||\n typeof route.path !== \"string\" ||\n !isRequestMethod(route.method)\n ) {\n throw new Error(\"Route registration child sent an invalid route record.\");\n }\n\n if (Object.keys(route).length !== 3) {\n throw new Error(\"Route registration child sent a route record with non-browser-safe fields.\");\n }\n\n return Object.freeze({\n name: route.name,\n path: route.path,\n method: normalizeRequestMethod(route.method),\n });\n });\n\n return Object.freeze({ version: PROTOCOL_VERSION, routes: Object.freeze(routes) });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isRequestMethod(value: unknown): value is RequestMethod {\n return (\n typeof value === \"string\" &&\n [\"get\", \"post\", \"put\", \"delete\", \"patch\", \"options\", \"head\", \"all\"].includes(\n value.toLowerCase(),\n )\n );\n}\n\nfunction normalizeRequestMethod(value: RequestMethod | string): RequestMethod {\n return value.toLowerCase() === \"all\" ? \"all\" : (value.toUpperCase() as RequestMethod);\n}\n\nfunction formatDiagnostics(stdout: string, stderr: string): string {\n if (!stdout && !stderr) return \"\";\n return `\\nChild stdout:\\n${stdout}\\nChild stderr:\\n${stderr}`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"route-registration-snapshot.mjs","names":[],"sources":["../../../../../../../core/src/production/route-registration-snapshot.ts"],"sourcesContent":["import type { NamedApiRoute, RequestMethod } from \"../router/types\";\nimport type { Environment, RuntimeStrategy } from \"../utils/environment\";\nimport {\n runRegistrationChild,\n type RegistrationChildForker,\n type RegistrationChildProcess,\n} from \"./registration-child-runner\";\n\nconst PROTOCOL_VERSION = 1;\n\nexport type RouteRegistrationSnapshot = Readonly<{\n version: typeof PROTOCOL_VERSION;\n routes: readonly NamedApiRoute[];\n}>;\n\nexport type RouteRegistrationChildRequest = Readonly<{\n version: typeof PROTOCOL_VERSION;\n cwd: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n}>;\n\nexport type RouteRegistrationChildProcess = RegistrationChildProcess;\n\nexport type RouteRegistrationSnapshotOptions = Readonly<{\n cwd?: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n timeoutMs?: number;\n childEntry?: string;\n forkProcess?: RegistrationChildForker;\n}>;\n\n/**\n * Collect named application API routes in a fresh process.\n *\n * The child imports application registration modules but deliberately does not\n * register, boot, or start connectors. Application module top-level code can\n * still have arbitrary user-owned side effects; this boundary is framework-only.\n */\nexport function collectRouteRegistrationSnapshot(\n options: RouteRegistrationSnapshotOptions = {},\n): Promise<RouteRegistrationSnapshot> {\n const cwd = options.cwd ?? process.cwd();\n\n return runRegistrationChild<RouteRegistrationSnapshot>({\n cwd,\n timeoutMs: options.timeoutMs,\n childEntry: options.childEntry,\n forkProcess: options.forkProcess,\n request: {\n version: PROTOCOL_VERSION,\n cwd,\n environment: options.environment,\n runtimeStrategy: options.runtimeStrategy,\n } satisfies RouteRegistrationChildRequest,\n resultMessageType: \"route-registration:snapshot\",\n resultNoun: \"snapshot\",\n readResult: (message) => validateSnapshot(message.snapshot),\n });\n}\n\nfunction validateSnapshot(value: unknown): RouteRegistrationSnapshot {\n if (!isRecord(value) || value.version !== PROTOCOL_VERSION || !Array.isArray(value.routes)) {\n throw new Error(\"Route registration child sent an invalid snapshot protocol payload.\");\n }\n\n const routes = value.routes.map((route) => {\n if (\n !isRecord(route) ||\n typeof route.name !== \"string\" ||\n typeof route.path !== \"string\" ||\n !isRequestMethod(route.method)\n ) {\n throw new Error(\"Route registration child sent an invalid route record.\");\n }\n\n const response = route.response;\n const allowedKeys = response === undefined ? 3 : 4;\n\n if (\n Object.keys(route).length !== allowedKeys ||\n (response !== undefined && !isResponseTypeRecord(response))\n ) {\n throw new Error(\"Route registration child sent a route record with non-browser-safe fields.\");\n }\n\n const base = {\n name: route.name,\n path: route.path,\n method: normalizeRequestMethod(route.method),\n };\n\n return Object.freeze(\n response === undefined ? base : { ...base, response: Object.freeze({ ...response }) },\n );\n });\n\n return Object.freeze({ version: PROTOCOL_VERSION, routes: Object.freeze(routes) });\n}\n\nfunction isResponseTypeRecord(value: unknown): value is Record<string, string> {\n return isRecord(value) && Object.values(value).every((entry) => typeof entry === \"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 isRequestMethod(value: unknown): value is RequestMethod {\n return (\n typeof value === \"string\" &&\n [\"get\", \"post\", \"put\", \"delete\", \"patch\", \"options\", \"head\", \"all\"].includes(\n value.toLowerCase(),\n )\n );\n}\n\nfunction normalizeRequestMethod(value: RequestMethod | string): RequestMethod {\n return value.toLowerCase() === \"all\" ? \"all\" : (value.toUpperCase() as RequestMethod);\n}\n\n"],"mappings":";;;AAQA,MAAM,mBAAmB;;;;;;;;AAgCzB,SAAgB,iCACd,UAA4C,CAAC,GACT;CACpC,MAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;CAEvC,OAAO,qBAAgD;EACrD;EACA,WAAW,QAAQ;EACnB,YAAY,QAAQ;EACpB,aAAa,QAAQ;EACrB,SAAS;GACP,SAAS;GACT;GACA,aAAa,QAAQ;GACrB,iBAAiB,QAAQ;EAC3B;EACA,mBAAmB;EACnB,YAAY;EACZ,aAAa,YAAY,iBAAiB,QAAQ,QAAQ;CAC5D,CAAC;AACH;AAEA,SAAS,iBAAiB,OAA2C;CACnE,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,YAAY,oBAAoB,CAAC,MAAM,QAAQ,MAAM,MAAM,GACvF,MAAM,IAAI,MAAM,qEAAqE;CAGvF,MAAM,SAAS,MAAM,OAAO,KAAK,UAAU;EACzC,IACE,CAAC,SAAS,KAAK,KACf,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,SAAS,YACtB,CAAC,gBAAgB,MAAM,MAAM,GAE7B,MAAM,IAAI,MAAM,wDAAwD;EAG1E,MAAM,WAAW,MAAM;EACvB,MAAM,cAAc,aAAa,SAAY,IAAI;EAEjD,IACE,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,eAC7B,aAAa,UAAa,CAAC,qBAAqB,QAAQ,GAEzD,MAAM,IAAI,MAAM,4EAA4E;EAG9F,MAAM,OAAO;GACX,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,QAAQ,uBAAuB,MAAM,MAAM;EAC7C;EAEA,OAAO,OAAO,OACZ,aAAa,SAAY,OAAO;GAAE,GAAG;GAAM,UAAU,OAAO,OAAO,EAAE,GAAG,SAAS,CAAC;EAAE,CACtF;CACF,CAAC;CAED,OAAO,OAAO,OAAO;EAAE,SAAS;EAAkB,QAAQ,OAAO,OAAO,MAAM;CAAE,CAAC;AACnF;AAEA,SAAS,qBAAqB,OAAiD;CAC7E,OAAO,SAAS,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,OAAO,UAAU,OAAO,UAAU,QAAQ;AAC3F;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,gBAAgB,OAAwC;CAC/D,OACE,OAAO,UAAU,YACjB;EAAC;EAAO;EAAQ;EAAO;EAAU;EAAS;EAAW;EAAQ;CAAK,CAAC,CAAC,SAClE,MAAM,YAAY,CACpB;AAEJ;AAEA,SAAS,uBAAuB,OAA8C;CAC5E,OAAO,MAAM,YAAY,MAAM,QAAQ,QAAS,MAAM,YAAY;AACpE"}
@@ -1,15 +1,17 @@
1
1
  import { ResourceConstructor, ResourceContract } from "./resource.mjs";
2
- import { ResourceSchema } from "./types.mjs";
2
+ import { ResourceOutputOf, ResourceSchema } from "./types.mjs";
3
3
 
4
4
  //#region ../core/src/resource/define-resource.d.ts
5
5
  /**
6
6
  * Options for defining a resource
7
+ *
8
+ * `S` is the literal schema type; it is what `defineResource` reads the output type from.
7
9
  */
8
- type DefineResourceOptions = {
10
+ type DefineResourceOptions<S extends ResourceSchema = ResourceSchema> = {
9
11
  /**
10
12
  * Resource schema - field mapping configuration
11
13
  */
12
- schema: ResourceSchema;
14
+ schema: S;
13
15
  /**
14
16
  * Optional: Boot hook - called before transformation
15
17
  */
@@ -29,6 +31,20 @@ type DefineResourceOptions = {
29
31
  * This utility creates a Resource class without the boilerplate,
30
32
  * perfect for simple use cases.
31
33
  *
34
+ * The JSON output type is inferred from the literal cast strings in `schema`,
35
+ * so `toJSON()` is typed instead of a generic object:
36
+ * - `"string"`, `"localized"`, `"url"`, `"uploadsUrl"`, `"storageUrl"` -> `string`
37
+ * - `"number"`, `"float"`, `"int"` -> `number`; `"boolean"` -> `boolean`
38
+ * - `"date"` -> `{ iso; format; timestamp; humanTime }` (the default date output)
39
+ * - `"object"` -> `Record<string, unknown>`; `"array"` -> `unknown[]`
40
+ * - `[]` -> array of the cast, `?` -> `T | null` (always present, never an optional key)
41
+ * - nested resources, lazy resources, `["inputKey", cast]` tuples, resolver functions,
42
+ * `arrayOf` schemas and `"self"` / `"self[]"` resolve to their own output type
43
+ * - builder fields (`this.date().format(...)`) are `unknown`
44
+ *
45
+ * The type reflects the schema only. When `transform`, `boot`, `extend` or a builder
46
+ * changes the real shape, declare the output yourself with an explicit type argument.
47
+ *
32
48
  * @param options - Resource configuration
33
49
  * @returns A Resource class
34
50
  *
@@ -38,32 +54,46 @@ type DefineResourceOptions = {
38
54
  * export const UserResource = defineResource({
39
55
  * schema: {
40
56
  * id: "number",
41
- * name: "string",
42
- * email: "string",
57
+ * name: "string?",
58
+ * tags: "string[]",
43
59
  * },
44
60
  * });
45
61
  *
46
- * // With hooks
62
+ * // Usage — json is { id: number; name: string | null; tags: string[] }
63
+ * const json = new UserResource(user).toJSON();
64
+ *
65
+ * // Read the output type elsewhere
66
+ * type UserJson = ResourceOutput<typeof UserResource>;
67
+ *
68
+ * // Self reference
47
69
  * export const CategoryResource = defineResource({
48
70
  * schema: {
49
71
  * id: "number",
50
72
  * name: "localized",
51
- * children: CategoryResource,
73
+ * children: "self[]",
52
74
  * },
75
+ * });
76
+ *
77
+ * // Explicit output: the hooks change the shape, so you state it.
78
+ * // `defineResource<Out>(...)` replaces the inferred type with `Out`.
79
+ * type ProductJson = { id: number; title: string; priceLabel: string };
80
+ *
81
+ * export const ProductResource = defineResource<ProductJson>({
82
+ * schema: { id: "number", title: "string", price: "number" },
53
83
  * transform: (data) => {
54
- * // Filter inactive children
55
- * if (data.children) {
56
- * data.children = data.children.filter(c => c.isActive);
57
- * }
84
+ * data.priceLabel = "$" + data.price;
85
+ * delete data.price;
86
+ *
58
87
  * return data;
59
88
  * },
60
89
  * });
61
90
  *
62
- * // Usage
63
- * const json = new UserResource(user).toJSON();
91
+ * // A cast also works when the declared type overlaps the inferred one:
92
+ * // defineResource({ schema, transform }) as ResourceConstructor<ProductJson>
64
93
  * ```
65
94
  */
66
- declare function defineResource(options: DefineResourceOptions): ResourceConstructor;
95
+ declare function defineResource<const S extends ResourceSchema>(options: DefineResourceOptions<S>): ResourceConstructor<ResourceOutputOf<S>>;
96
+ declare function defineResource<Out extends object, const S extends ResourceSchema = ResourceSchema>(options: DefineResourceOptions<S>): ResourceConstructor<Out>;
67
97
  //#endregion
68
98
  export { DefineResourceOptions, defineResource };
69
99
  //# sourceMappingURL=define-resource.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-resource.d.mts","names":[],"sources":["../../../../../../../core/src/resource/define-resource.ts"],"mappings":";;;;;;AAMA;KAAY,qBAAA;;;;EAIV,MAAA,EAAQ,cAAA;EAeW;;;EAVnB,IAAA,IAAQ,QAAA,GAAW,gBAAA;EAU0D;;;EAL7E,MAAA,IAAU,QAAA,GAAW,gBAAA;EALF;;;EAUnB,SAAA,IAAa,IAAA,EAAM,MAAA,eAAqB,QAAA,EAAU,gBAAA,KAAqB,MAAA;AAAA;;;;;;;;AAAM;AA2C/E;;;;;;;;AAAmF;;;;;;;;;;;;;;;;;;;;;;;;iBAAnE,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,mBAAmB"}
1
+ {"version":3,"file":"define-resource.d.mts","names":[],"sources":["../../../../../../../core/src/resource/define-resource.ts"],"mappings":";;;;;;AAQA;;;KAAY,qBAAA,WAAgC,cAAA,GAAiB,cAAA;EAAA;;;EAI3D,MAAA,EAAQ,CAAA;EAeW;;;EAVnB,IAAA,IAAQ,QAAA,GAAW,gBAAA;EAU0D;;;EAL7E,MAAA,IAAU,QAAA,GAAW,gBAAA;EAVrB;;;EAeA,SAAA,IAAa,IAAA,EAAM,MAAA,eAAqB,QAAA,EAAU,gBAAA,KAAqB,MAAA;AAAA;;;;;;;;;;;AAAM;AAsE/E;;;;;;;;;;;;;;;;;;;;AAEyC;AACzC;;;;;;;;;;;;;;;;;;;;;AAE0B;;;;;;;;;;;;;;iBALV,cAAA,iBAA+B,cAAA,EAC7C,OAAA,EAAS,qBAAA,CAAsB,CAAA,IAC9B,mBAAA,CAAoB,gBAAA,CAAiB,CAAA;AAAA,iBACxB,cAAA,qCAAmD,cAAA,GAAiB,cAAA,EAClF,OAAA,EAAS,qBAAA,CAAsB,CAAA,IAC9B,mBAAA,CAAoB,GAAA"}
@@ -1,46 +1,6 @@
1
1
  import { Resource } from "./resource.mjs";
2
2
 
3
3
  //#region ../core/src/resource/define-resource.ts
4
- /**
5
- * Define a resource with a clean, shorthand API.
6
- *
7
- * This utility creates a Resource class without the boilerplate,
8
- * perfect for simple use cases.
9
- *
10
- * @param options - Resource configuration
11
- * @returns A Resource class
12
- *
13
- * @example
14
- * ```typescript
15
- * // Simple resource
16
- * export const UserResource = defineResource({
17
- * schema: {
18
- * id: "number",
19
- * name: "string",
20
- * email: "string",
21
- * },
22
- * });
23
- *
24
- * // With hooks
25
- * export const CategoryResource = defineResource({
26
- * schema: {
27
- * id: "number",
28
- * name: "localized",
29
- * children: CategoryResource,
30
- * },
31
- * transform: (data) => {
32
- * // Filter inactive children
33
- * if (data.children) {
34
- * data.children = data.children.filter(c => c.isActive);
35
- * }
36
- * return data;
37
- * },
38
- * });
39
- *
40
- * // Usage
41
- * const json = new UserResource(user).toJSON();
42
- * ```
43
- */
44
4
  function defineResource(options) {
45
5
  const resource = class AnonymousResource extends Resource {
46
6
  static {
@@ -1 +1 @@
1
- {"version":3,"file":"define-resource.mjs","names":[],"sources":["../../../../../../../core/src/resource/define-resource.ts"],"sourcesContent":["import { Resource, type ResourceConstructor, type ResourceContract } from \"./resource\";\nimport type { ResourceSchema } from \"./types\";\n\n/**\n * Options for defining a resource\n */\nexport type DefineResourceOptions = {\n /**\n * Resource schema - field mapping configuration\n */\n schema: ResourceSchema;\n\n /**\n * Optional: Boot hook - called before transformation\n */\n boot?: (resource?: ResourceContract) => void;\n\n /**\n * Optional: Extend hook - called after transformation\n */\n extend?: (resource?: ResourceContract) => void;\n\n /**\n * Optional: Transform hook - modify final output\n */\n transform?: (data: Record<string, any>, resource: ResourceContract) => Record<string, any>;\n};\n\n/**\n * Define a resource with a clean, shorthand API.\n *\n * This utility creates a Resource class without the boilerplate,\n * perfect for simple use cases.\n *\n * @param options - Resource configuration\n * @returns A Resource class\n *\n * @example\n * ```typescript\n * // Simple resource\n * export const UserResource = defineResource({\n * schema: {\n * id: \"number\",\n * name: \"string\",\n * email: \"string\",\n * },\n * });\n *\n * // With hooks\n * export const CategoryResource = defineResource({\n * schema: {\n * id: \"number\",\n * name: \"localized\",\n * children: CategoryResource,\n * },\n * transform: (data) => {\n * // Filter inactive children\n * if (data.children) {\n * data.children = data.children.filter(c => c.isActive);\n * }\n * return data;\n * },\n * });\n *\n * // Usage\n * const json = new UserResource(user).toJSON();\n * ```\n */\nexport function defineResource(options: DefineResourceOptions): ResourceConstructor {\n const resource = class AnonymousResource extends Resource {\n static schema = options.schema;\n\n protected boot() {\n if (options.boot) {\n options.boot.call(this, this as unknown as ResourceContract);\n }\n }\n\n protected extend() {\n if (options.extend) {\n options.extend.call(this, this as unknown as ResourceContract);\n }\n\n if (options.transform) {\n options.transform.call(this, this.data, this);\n }\n }\n };\n\n // Normalize schema once at definition time — converts string cast types\n // (including [] and ? suffixes) into pre-built ResourceFieldBuilder instances\n resource.parsedSchema = Resource.normalizeSchema(resource.schema);\n\n return resource as any as ResourceConstructor;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,SAAgB,eAAe,SAAqD;CAClF,MAAM,WAAW,MAAM,0BAA0B,SAAS;;iBACxC,QAAQ;;EAExB,AAAU,OAAO;GACf,IAAI,QAAQ,MACV,QAAQ,KAAK,KAAK,MAAM,IAAmC;EAE/D;EAEA,AAAU,SAAS;GACjB,IAAI,QAAQ,QACV,QAAQ,OAAO,KAAK,MAAM,IAAmC;GAG/D,IAAI,QAAQ,WACV,QAAQ,UAAU,KAAK,MAAM,KAAK,MAAM,IAAI;EAEhD;CACF;CAIA,SAAS,eAAe,SAAS,gBAAgB,SAAS,MAAM;CAEhE,OAAO;AACT"}
1
+ {"version":3,"file":"define-resource.mjs","names":[],"sources":["../../../../../../../core/src/resource/define-resource.ts"],"sourcesContent":["import { Resource, type ResourceConstructor, type ResourceContract } from \"./resource\";\nimport type { ResourceOutputOf, ResourceSchema } from \"./types\";\n\n/**\n * Options for defining a resource\n *\n * `S` is the literal schema type; it is what `defineResource` reads the output type from.\n */\nexport type DefineResourceOptions<S extends ResourceSchema = ResourceSchema> = {\n /**\n * Resource schema - field mapping configuration\n */\n schema: S;\n\n /**\n * Optional: Boot hook - called before transformation\n */\n boot?: (resource?: ResourceContract) => void;\n\n /**\n * Optional: Extend hook - called after transformation\n */\n extend?: (resource?: ResourceContract) => void;\n\n /**\n * Optional: Transform hook - modify final output\n */\n transform?: (data: Record<string, any>, resource: ResourceContract) => Record<string, any>;\n};\n\n/**\n * Define a resource with a clean, shorthand API.\n *\n * This utility creates a Resource class without the boilerplate,\n * perfect for simple use cases.\n *\n * The JSON output type is inferred from the literal cast strings in `schema`,\n * so `toJSON()` is typed instead of a generic object:\n * - `\"string\"`, `\"localized\"`, `\"url\"`, `\"uploadsUrl\"`, `\"storageUrl\"` -> `string`\n * - `\"number\"`, `\"float\"`, `\"int\"` -> `number`; `\"boolean\"` -> `boolean`\n * - `\"date\"` -> `{ iso; format; timestamp; humanTime }` (the default date output)\n * - `\"object\"` -> `Record<string, unknown>`; `\"array\"` -> `unknown[]`\n * - `[]` -> array of the cast, `?` -> `T | null` (always present, never an optional key)\n * - nested resources, lazy resources, `[\"inputKey\", cast]` tuples, resolver functions,\n * `arrayOf` schemas and `\"self\"` / `\"self[]\"` resolve to their own output type\n * - builder fields (`this.date().format(...)`) are `unknown`\n *\n * The type reflects the schema only. When `transform`, `boot`, `extend` or a builder\n * changes the real shape, declare the output yourself with an explicit type argument.\n *\n * @param options - Resource configuration\n * @returns A Resource class\n *\n * @example\n * ```typescript\n * // Simple resource\n * export const UserResource = defineResource({\n * schema: {\n * id: \"number\",\n * name: \"string?\",\n * tags: \"string[]\",\n * },\n * });\n *\n * // Usage — json is { id: number; name: string | null; tags: string[] }\n * const json = new UserResource(user).toJSON();\n *\n * // Read the output type elsewhere\n * type UserJson = ResourceOutput<typeof UserResource>;\n *\n * // Self reference\n * export const CategoryResource = defineResource({\n * schema: {\n * id: \"number\",\n * name: \"localized\",\n * children: \"self[]\",\n * },\n * });\n *\n * // Explicit output: the hooks change the shape, so you state it.\n * // `defineResource<Out>(...)` replaces the inferred type with `Out`.\n * type ProductJson = { id: number; title: string; priceLabel: string };\n *\n * export const ProductResource = defineResource<ProductJson>({\n * schema: { id: \"number\", title: \"string\", price: \"number\" },\n * transform: (data) => {\n * data.priceLabel = \"$\" + data.price;\n * delete data.price;\n *\n * return data;\n * },\n * });\n *\n * // A cast also works when the declared type overlaps the inferred one:\n * // defineResource({ schema, transform }) as ResourceConstructor<ProductJson>\n * ```\n */\nexport function defineResource<const S extends ResourceSchema>(\n options: DefineResourceOptions<S>,\n): ResourceConstructor<ResourceOutputOf<S>>;\nexport function defineResource<Out extends object, const S extends ResourceSchema = ResourceSchema>(\n options: DefineResourceOptions<S>,\n): ResourceConstructor<Out>;\nexport function defineResource(options: DefineResourceOptions): ResourceConstructor<unknown> {\n const resource = class AnonymousResource extends Resource {\n static schema = options.schema;\n\n protected boot() {\n if (options.boot) {\n options.boot.call(this, this as unknown as ResourceContract);\n }\n }\n\n protected extend() {\n if (options.extend) {\n options.extend.call(this, this as unknown as ResourceContract);\n }\n\n if (options.transform) {\n options.transform.call(this, this.data, this);\n }\n }\n };\n\n // Normalize schema once at definition time — converts string cast types\n // (including [] and ? suffixes) into pre-built ResourceFieldBuilder instances\n resource.parsedSchema = Resource.normalizeSchema(resource.schema);\n\n // The runtime class is untyped; the overloads above carry the output type.\n return resource as unknown as ResourceConstructor<unknown>;\n}\n"],"mappings":";;;AAuGA,SAAgB,eAAe,SAA8D;CAC3F,MAAM,WAAW,MAAM,0BAA0B,SAAS;;iBACxC,QAAQ;;EAExB,AAAU,OAAO;GACf,IAAI,QAAQ,MACV,QAAQ,KAAK,KAAK,MAAM,IAAmC;EAE/D;EAEA,AAAU,SAAS;GACjB,IAAI,QAAQ,QACV,QAAQ,OAAO,KAAK,MAAM,IAAmC;GAG/D,IAAI,QAAQ,WACV,QAAQ,UAAU,KAAK,MAAM,KAAK,MAAM,IAAI;EAEhD;CACF;CAIA,SAAS,eAAe,SAAS,gBAAgB,SAAS,MAAM;CAGhE,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  import { ResourceFieldBuilder } from "./resource-field-builder.mjs";
2
2
  import { Resource, ResourceConstructor, ResourceContract } from "./resource.mjs";
3
- import { ResourceArraySchema, ResourceCastType, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, ResponseBodyValue, ResponseSchema } from "./types.mjs";
3
+ import { BaseCastOutput, CastOutput, FieldOutput, ResourceArraySchema, ResourceCastType, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceOutput, ResourceOutputOf, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, ResponseBodyOutput, ResponseBodyValue, ResponseSchema, ResponseSchemaOutput } from "./types.mjs";
4
4
  import { DefineResourceOptions, defineResource } from "./define-resource.mjs";
5
5
  import { RegisterResource } from "./register-resource.mjs";
@@ -7,7 +7,7 @@ import { GenericObject } from "@mongez/reinforcements";
7
7
  /**
8
8
  * Resource contract
9
9
  */
10
- interface ResourceContract {
10
+ interface ResourceContract<Out = GenericObject> {
11
11
  /**
12
12
  * Resource data
13
13
  */
@@ -23,7 +23,7 @@ interface ResourceContract {
23
23
  /**
24
24
  * Convert resource to JSON
25
25
  */
26
- toJSON(): GenericObject;
26
+ toJSON(): Out;
27
27
  /**
28
28
  * Transform the given value with given type
29
29
  */
@@ -80,8 +80,8 @@ interface ResourceContract {
80
80
  /**
81
81
  * Resource constructor
82
82
  */
83
- interface ResourceConstructor {
84
- new (originalData: GenericObject | Resource | Model): ResourceContract;
83
+ interface ResourceConstructor<Out = GenericObject> {
84
+ new (originalData: GenericObject | Resource | Model): ResourceContract<Out>;
85
85
  }
86
86
  declare class Resource implements ResourceContract {
87
87
  originalData: GenericObject | Resource | Model;
@@ -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":";;;;;;;;AAoBA;UAAiB,gBAAA,OAAuB,aAAA;EAAP;;;EAI/B,QAAA,EAAU,aAAA;EAUI;;;EALd,IAAA,EAAM,aAAA;EA8ByB;;;EAzB/B,YAAA,EAAc,aAAA;EAmCW;;;EA9BzB,MAAA,IAAU,GAAA;EAkDiB;;;EA7C3B,SAAA,CAAU,KAAA,OAAY,IAAA,EAAM,2BAAA,EAA6B,MAAA;EA4Db;;;EAvD5C,GAAA,CAAI,GAAA,UAAa,YAAA;EAzBjB;;;EA8BA,GAAA,CAAI,GAAA,UAAa,KAAA,QAAa,gBAAA;EApB9B;;;EAyBA,OAAA,CAAQ,MAAA,EAAQ,MAAA,SAAe,mBAAA,IAAuB,mBAAA;EAftD;;;EAoBA,MAAA,CAAO,QAAA,YAAoB,oBAAA;EApB8B;;;EAyBzD,IAAA,CAAK,QAAA,YAAoB,oBAAA;EAfzB;;;EAoBA,SAAA,CAAU,QAAA,YAAoB,oBAAA;EAf9B;;;EAoBA,GAAA,CAAI,QAAA,YAAoB,oBAAA;EApB8B;;;EAyBtD,UAAA,CAAW,QAAA,YAAoB,oBAAA;EAf/B;;;EAoBA,MAAA,CAAO,QAAA,YAAoB,oBAAA;EAfjB;;;EAoBV,OAAA,CAAQ,QAAA,YAAoB,oBAAA;EAfJ;;;EAoBxB,KAAA,CAAM,QAAA,YAAoB,oBAAA;EAV1B;;;EAeA,GAAA,CAAI,QAAA,YAAoB,oBAAA;AAAA;;;;UAMT,mBAAA,OAA0B,aAAA;EAAA,KACpC,YAAA,EAAc,aAAA,GAAgB,QAAA,GAAW,KAAA,GAAQ,gBAAA,CAAiB,GAAA;AAAA;AAAA,cAG5D,QAAA,YAAoB,gBAAA;EA+DL,YAAA,EAAc,aAAA,GAAgB,QAAA,GAAW,KAAA;EAzEvB;AAM9C;;EAQS,QAAA,EAAU,aAAA;EARwB;;;EAalC,IAAA,EAAM,aAAA;EAZ0D;;;;EAAA,UAkB7D,SAAA,GAAY,GAAA;EAnBmB;;;;EAAA,OAyB3B,MAAA,EAAQ,MAAA,SAAe,mBAAA;EAxBiB;;;AAAoB;EAApB,OA8BxC,YAAA,EAAc,MAAA,SAAe,mBAAA;EA3BvB;;;;;EAAA,OAkCN,eAAA,CAAgB,MAAA,EAAQ,MAAA,SAAe,mBAAA,aAAgC,MAAA,SAAe,mBAAA;EAzBvF;;;cAsDa,YAAA,EAAc,aAAA,GAAgB,QAAA,GAAW,KAAA;EApCxB;;;EAiDpC,MAAA,IAAM,aAAA;EA1CuF;;;;EAAA,UAsD1F,IAAA;EAZG;;;;;EAAA,UAqBH,eAAA;EAsKA;;;;;;EAAA,UA5IA,mBAAA,IAAuB,MAAA,SAAe,mBAAA;EAuMzB;;;EA5KhB,SAAA,CAAU,KAAA,OAAY,IAAA,EAAM,2BAAA,EAA6B,MAAA;EAoM9B;;;;EAAA,UA5LxB,cAAA,CAAe,KAAA,OAAY,cAAA,EAAgB,mBAAA,EAAqB,MAAA;EA+N5C;;;EAAA,UAxKpB,MAAA;EArNqB;;;;;;;EAAA,UAgOrB,sBAAA,CAAuB,KAAA,OAAY,OAAA;EA5NtC;;;;EAAA,UA0OG,WAAA,CAAY,KAAA;EA/NA;;;EAAA,UAsPZ,kBAAA,CACR,IAAA,OACA,MAAA,EAAQ,MAAA,SAAe,mBAAA,GACvB,MAAA,YAAe,aAAA;EA7OH;;;;EAAA,UA4QJ,kBAAA,CAAmB,MAAA,EAAQ,aAAA,GAAgB,QAAA,GAAW,KAAA,EAAO,SAAA,UAAmB,QAAA;EArQrC;;;EAgR9C,GAAA,CAAI,GAAA,UAAa,YAAA;;;;EAOjB,GAAA,CAAI,GAAA,UAAa,KAAA,QAAa,gBAAA;EA1PX;;;EAmQnB,OAAA,CAAQ,MAAA,EAAQ,MAAA,SAAe,mBAAA,IAAuB,mBAAA;EAjOnD;;;EA2OH,MAAA,CAAO,QAAA,YAAiB,oBAAA;EAtLxB;;;EA6LA,IAAA,CAAK,QAAA,YAAiB,oBAAA;EA7LmC;;;EAoMzD,SAAA,CAAU,QAAA,YAAiB,oBAAA;EA5LG;;;EAmM9B,GAAA,CAAI,QAAA,YAAiB,oBAAA;EAjIK;;;EAwI1B,UAAA,CAAW,QAAA,YAAiB,oBAAA;EAnGzB;;;EA0GH,MAAA,CAAO,QAAA,YAAiB,oBAAA;EAxG7B;;;EA+GK,OAAA,CAAQ,QAAA,YAAiB,oBAAA;EA/EK;;;EAsF9B,KAAA,CAAM,QAAA,YAAiB,oBAAA;EAtFyC;;;EA6FhE,GAAA,CAAI,QAAA,YAAiB,oBAAA;EAlFJ;;;EAAA,UAyFd,YAAA,CAAa,IAAA,EAAM,2BAAA,EAA6B,QAAA,YAAiB,oBAAA;AAAA"}
@@ -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 {\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(): GenericObject;\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 {\n new (originalData: GenericObject | Resource | Model): ResourceContract;\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 { 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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"}