@warlock.js/core 5.24.0 → 5.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -1
- package/bin/warlock.js +0 -0
- package/esm/application/application.d.mts +21 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs +29 -1
- package/esm/application/application.mjs.map +1 -1
- package/esm/application/boot-signal.d.mts +2 -1
- package/esm/application/boot-signal.d.mts.map +1 -1
- package/esm/application/boot-signal.mjs.map +1 -1
- package/esm/application/index.d.mts +1 -0
- package/esm/application/index.mjs +1 -0
- package/esm/application/roles.d.mts +72 -0
- package/esm/application/roles.d.mts.map +1 -0
- package/esm/application/roles.mjs +124 -0
- package/esm/application/roles.mjs.map +1 -0
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/create-database.command.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/index.mjs +2 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +5 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/path-resolver.mjs.map +1 -1
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +54 -5
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.command.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs +20 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs +3 -1
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +7 -0
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/types.d.mts +8 -0
- package/esm/connectors/types.d.mts.map +1 -1
- package/esm/connectors/types.mjs.map +1 -1
- package/esm/database/create-database-action.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +4 -3
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs +2 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +2 -0
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/legacy-locales-warning.mjs +16 -0
- package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs +26 -5
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/register-installed-devtools.mjs +47 -0
- package/esm/dev-server/register-installed-devtools.mjs.map +1 -0
- package/esm/dev-server/run-connector-typings.mjs +28 -0
- package/esm/dev-server/run-connector-typings.mjs.map +1 -0
- package/esm/dev-server/special-file-patterns.mjs +5 -3
- package/esm/dev-server/special-file-patterns.mjs.map +1 -1
- package/esm/dev-server/special-files-collector.d.mts +2 -2
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs +7 -4
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/translation-keys-sources.mjs +7 -3
- package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +27 -4
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/encryption/password.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +3 -1
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +38 -18
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
- package/esm/generations/features/ai-google.feature.mjs.map +1 -1
- package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
- package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
- package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
- package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
- package/esm/generations/features/ai.feature.mjs.map +1 -1
- package/esm/generations/features/bull-board.feature.mjs +2 -2
- package/esm/generations/features/bull-board.feature.mjs.map +1 -1
- package/esm/generations/features/devtools.feature.mjs +23 -0
- package/esm/generations/features/devtools.feature.mjs.map +1 -0
- package/esm/generations/features/herald.feature.mjs.map +1 -1
- package/esm/generations/features/image.feature.mjs.map +1 -1
- package/esm/generations/features/index.mjs +2 -0
- package/esm/generations/features/index.mjs.map +1 -1
- package/esm/generations/features/mail.feature.mjs.map +1 -1
- package/esm/generations/features/mongodb.feature.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/postgres.feature.mjs.map +1 -1
- package/esm/generations/features/react-email.feature.mjs.map +1 -1
- package/esm/generations/features/react.feature.mjs.map +1 -1
- package/esm/generations/features/redis.feature.mjs.map +1 -1
- package/esm/generations/features/s3.feature.mjs.map +1 -1
- package/esm/generations/features/scheduler.feature.mjs.map +1 -1
- package/esm/generations/features/ses.feature.mjs.map +1 -1
- package/esm/generations/features/socket.feature.mjs.map +1 -1
- package/esm/generations/features/test.feature.mjs +6 -2
- package/esm/generations/features/test.feature.mjs.map +1 -1
- package/esm/generations/features/types.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/http/build-rate-limit-options.d.mts +8 -2
- package/esm/http/build-rate-limit-options.d.mts.map +1 -1
- package/esm/http/build-rate-limit-options.mjs +1 -0
- package/esm/http/build-rate-limit-options.mjs.map +1 -1
- package/esm/http/database/RequestLog.mjs.map +1 -1
- package/esm/http/errors/errors.d.mts +1 -17
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +1 -20
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/index.d.mts +4 -3
- package/esm/http/index.mjs +3 -2
- package/esm/http/middleware/inject-request-context.d.mts +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +9 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/request.d.mts +7 -24
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +1 -22
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +5 -5
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +4 -2
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server-rebuilt.d.mts +20 -0
- package/esm/http/server-rebuilt.d.mts.map +1 -0
- package/esm/http/server-rebuilt.mjs +21 -0
- package/esm/http/server-rebuilt.mjs.map +1 -0
- package/esm/http/tracing/index.mjs +1 -1
- package/esm/http/tracing/tracing-dispatcher.d.mts +13 -6
- package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -1
- package/esm/http/tracing/tracing-dispatcher.mjs +46 -14
- package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -1
- package/esm/http/tracing/tracing.type.d.mts +2 -1
- package/esm/http/tracing/tracing.type.d.mts.map +1 -1
- package/esm/http/types.d.mts +9 -0
- package/esm/http/types.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/index.d.mts +8 -4
- package/esm/index.mjs +8 -3
- package/esm/loader/app-module-importer.d.mts +17 -0
- package/esm/loader/app-module-importer.d.mts.map +1 -0
- package/esm/loader/app-module-importer.mjs +20 -0
- package/esm/loader/app-module-importer.mjs.map +1 -0
- package/esm/localization/index.mjs +4 -0
- package/esm/localization/parse-locale-dictionary.d.mts +23 -0
- package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/parse-locale-dictionary.mjs +215 -0
- package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
- package/esm/localization/register-locale-dictionary.d.mts +12 -0
- package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/register-locale-dictionary.mjs +58 -0
- package/esm/localization/register-locale-dictionary.mjs.map +1 -0
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/events.d.mts +7 -0
- package/esm/mail/events.d.mts.map +1 -1
- package/esm/mail/events.mjs +7 -0
- package/esm/mail/events.mjs.map +1 -1
- package/esm/mail/send-mail.d.mts.map +1 -1
- package/esm/mail/send-mail.mjs +23 -2
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/mail/types.d.mts +5 -1
- package/esm/mail/types.d.mts.map +1 -1
- package/esm/mail/types.mjs.map +1 -1
- package/esm/manifest/manifest-manager.mjs.map +1 -1
- package/esm/production/build-contributions.mjs +19 -3
- package/esm/production/build-contributions.mjs.map +1 -1
- package/esm/production/production-builder.mjs +39 -10
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs +6 -0
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/resolve-build-config.d.mts +1 -1
- package/esm/production/resolve-build-config.mjs +15 -0
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/production/route-registration-child.mjs +14 -2
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs +9 -2
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/resource/resource-field-builder.mjs +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts +5 -0
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs +17 -6
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +1 -0
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +10 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/index.d.mts +3 -2
- package/esm/tests/index.mjs +3 -2
- package/esm/tests/start-http-development-server.d.mts +10 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +16 -2
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts +16 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +20 -2
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/use-cases/use-case-events.mjs.map +1 -1
- package/esm/utils/is-test-file.mjs +15 -0
- package/esm/utils/is-test-file.mjs.map +1 -0
- package/esm/utils/normalized-path.d.mts +3 -1
- package/esm/utils/normalized-path.d.mts.map +1 -1
- package/esm/utils/normalized-path.mjs +4 -2
- package/esm/utils/normalized-path.mjs.map +1 -1
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/plugins/database-plugin.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +57 -4
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/warlock-config/default-configurations.mjs +1 -0
- package/esm/warlock-config/default-configurations.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +7 -0
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +382 -214
- package/llms.txt +1 -1
- package/package.json +23 -19
- package/skills/README.md +1 -1
- package/skills/configure-app/SKILL.md +2 -0
- package/skills/create-controller/SKILL.md +4 -0
- package/skills/define-resource/SKILL.md +4 -0
- package/skills/request-tracing/SKILL.md +38 -15
- package/skills/run-app/SKILL.md +71 -34
- package/skills/send-mail/SKILL.md +55 -25
- package/skills/test-http/SKILL.md +28 -9
- package/skills/test-service/SKILL.md +61 -40
- package/skills/use-localization/SKILL.md +39 -26
- package/skills/warlock-conventions/SKILL.md +26 -18
- package/skills/write-cli-command/SKILL.md +50 -45
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"production-supervisor.mjs","names":[],"sources":["../../../../../../../core/src/production/production-supervisor.ts"],"sourcesContent":["import { spawn } from \"child_process\";\nimport { BOOT_SIGNAL_ENV_KEY, isBootSignal } from \"../application/boot-signal\";\nimport {\n displayMissingReadinessNotice,\n displayProductionReadyBanner,\n displayProductionStartFailure,\n} from \"../cli/cli-commands.utils\";\n\n/**\n * How long a still-running child may stay silent before we say so.\n *\n * Only reached by a bundle built before readiness reporting existed; a current\n * bundle signals as soon as its late-phase connectors are up.\n */\nexport const DEFAULT_READINESS_NOTICE_DELAY_MS = 10_000;\n\nexport type ProductionSupervisorOptions = {\n /** Arguments handed to the node binary, entry file included. */\n nodeArgs: string[];\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n readinessNoticeDelayMs?: number;\n};\n\nexport type ProductionSupervisorResult = {\n /** What the CLI should exit with — never 0 for a boot that never completed. */\n exitCode: number;\n /** Whether the child ever reported a finished boot. */\n ready: boolean;\n};\n\n/**\n * Run the production bundle in a child process and report honestly on it.\n *\n * The supervisor exists because a parent that only forwards `stdio` cannot\n * see whether its child ever booted — it only sees the process exit. It\n * therefore opens an IPC channel and treats the child's `warlock:ready`\n * signal, emitted by `Application.markBooted`, as the single source of truth\n * for \"started\":\n *\n * 1. The success banner is printed only on that signal.\n * 2. A child that dies before signalling is reported as a failed start and\n * forced to a non-zero exit, even if it exited 0.\n * 3. A child still running without having signalled draws a note on stderr —\n * never stdout, which would be the false-green this whole channel prevents.\n * 4. Whether the child ever wrote to stdout/stderr before dying is tracked\n * (`sawChildOutput`) so the failure summary only claims \"the cause is\n * printed above\" when that is actually true — see the stdio comment\n * below.\n *\n * Resolves rather than exiting so the outcome stays assertable in a test; the\n * `start` command turns the result into the process exit code.\n */\nexport function superviseProductionProcess({\n nodeArgs,\n cwd = process.cwd(),\n env = process.env,\n readinessNoticeDelayMs = DEFAULT_READINESS_NOTICE_DELAY_MS,\n}: ProductionSupervisorOptions): Promise<ProductionSupervisorResult> {\n return new Promise<ProductionSupervisorResult>((resolve) => {\n // On Windows, we need to be careful with signals - the console sends Ctrl+C\n // to all processes in the group, so we just need to not interfere.\n // `process.execPath`, not \"node\": the bare name needs `node` on PATH, which\n // a systemd unit, a cron job, or a slim container may not provide — and a\n // PATH `node` that *is* present can be a different version than the one\n // running this CLI. execPath is the binary already executing us.\n //\n // The fourth stdio slot opens the IPC channel the child reports readiness\n // on. Without it `process.send` is undefined in the child and we would be\n // back to guessing whether the boot succeeded.\n //\n // stdout/stderr are piped rather than inherited so this supervisor can\n // tell whether the child ever wrote anything, not just forward it blind.\n // `displayProductionStartFailure` used to claim \"the cause is printed\n // above\" on every failed boot, unconditionally — true when the child's\n // logger got far enough to print, false (and misleading) when it died\n // before its log channels were configured. Piping costs nothing here:\n // both streams are re-forwarded to this process's own stdout/stderr as\n // each chunk arrives, so a human watching the terminal sees the exact\n // same live output \"inherit\" gave them.\n const child = spawn(process.execPath, nodeArgs, {\n stdio: [\"inherit\", \"pipe\", \"pipe\", \"ipc\"],\n cwd,\n // The flag identifies us as a parent that speaks the boot protocol. The\n // child stays silent without it, so an app running under pm2 or any other\n // supervisor never writes to a channel that belongs to someone else.\n env: { ...env, [BOOT_SIGNAL_ENV_KEY]: \"1\" },\n // Important: keep child in same process group for proper signal handling\n detached: false,\n });\n\n const startedAt = Date.now();\n let isReady = false;\n let isShuttingDown = false;\n let isSettled = false;\n\n // Whether the child wrote anything to either stream — the only honest\n // basis for telling a developer \"the cause is printed above\". Set from\n // the first chunk on either stream; a child that dies before its first\n // write (a crash mid-import, a logger with no console channel) leaves\n // this `false`, which is exactly the case the summary must admit rather\n // than paper over.\n let sawChildOutput = false;\n\n const forwardChildOutput = (target: NodeJS.WritableStream) => (chunk: Buffer) => {\n sawChildOutput = true;\n target.write(chunk);\n };\n\n child.stdout?.on(\"data\", forwardChildOutput(process.stdout));\n child.stderr?.on(\"data\", forwardChildOutput(process.stderr));\n\n const readinessNoticeTimer = setTimeout(() => {\n if (isReady) {\n return;\n }\n\n displayMissingReadinessNotice(Date.now() - startedAt);\n }, readinessNoticeDelayMs);\n\n // A diagnostic timer must never be the reason the CLI stays alive.\n readinessNoticeTimer.unref?.();\n\n const settle = (result: ProductionSupervisorResult) => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n clearTimeout(readinessNoticeTimer);\n process.off(\"SIGTERM\", onSigterm);\n process.off(\"SIGINT\", onSigint);\n process.off(\"SIGHUP\", onSighup);\n\n resolve(result);\n };\n\n child.on(\"message\", (message: unknown) => {\n if (isReady || !isBootSignal(message)) {\n return;\n }\n\n isReady = true;\n clearTimeout(readinessNoticeTimer);\n\n displayProductionReadyBanner({ bootDurationMs: message.bootDurationMs }).catch((error) => {\n // The server is up either way — a banner that fails to render must not\n // be reported as, or turn into, a boot failure.\n console.error(error);\n });\n });\n\n // A spawn that never starts (missing binary, EACCES) emits `error` and no\n // `exit`, so without this the command would hang silently on a failure.\n // The error is printed BEFORE the summary that references it — the\n // summary says the cause is \"above\", so it has to already be there.\n child.on(\"error\", (error) => {\n console.error(error);\n displayProductionStartFailure(1, true);\n\n settle({ exitCode: 1, ready: false });\n });\n\n // Forward every stop signal so a supervisor that only signals this\n // process (docker kill -s INT, systemd KillSignal) still stops the child.\n // A repeated signal means the graceful shutdown is stuck: force-kill.\n const forward = (signal: NodeJS.Signals) => () => {\n if (isShuttingDown) {\n child.kill(\"SIGKILL\");\n return;\n }\n\n isShuttingDown = true;\n child.kill(signal);\n };\n\n const onSigterm = forward(\"SIGTERM\");\n const onSighup = forward(\"SIGHUP\");\n\n // On Windows Ctrl+C reaches both processes, so the child already has it —\n // we only record that this exit was asked for (kill() there is a hard kill).\n const onSigint =\n process.platform === \"win32\"\n ? () => {\n isShuttingDown = true;\n }\n : forward(\"SIGINT\");\n\n process.on(\"SIGTERM\", onSigterm);\n process.on(\"SIGINT\", onSigint);\n process.on(\"SIGHUP\", onSighup);\n\n // `close`, not `exit`: `exit` fires as soon as the OS process ends, which\n // can race ahead of the last chunks still in flight on the piped stdout/\n // stderr streams. `close` waits for those streams to end too, so\n // `sawChildOutput` reflects everything the child actually wrote instead\n // of whatever had been delivered by the time the process happened to die.\n child.on(\"close\", (code) => {\n // A child that dies before reporting readiness never served a request.\n // Exiting 0 there — which a clean `process.exit(0)` in failing app code\n // would do — reports a boot failure as a successful run, so the exit code\n // is forced non-zero to match what actually happened.\n //\n // An operator who interrupts the boot is not a failed boot, so a signalled\n // shutdown keeps the child's own exit code.\n if (!isReady && !isShuttingDown) {\n const exitCode = code === 0 || code === null ? 1 : code;\n\n displayProductionStartFailure(exitCode, sawChildOutput);\n\n return settle({ exitCode, ready: false });\n }\n\n settle({ exitCode: code ?? 0, ready: isReady });\n });\n });\n}\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;AAuCjD,SAAgB,2BAA2B,EACzC,UACA,MAAM,QAAQ,IAAI,GAClB,MAAM,QAAQ,KACd,yBAAyB,qCAC0C;CACnE,OAAO,IAAI,SAAqC,YAAY;EAqB1D,MAAM,QAAQ,MAAM,QAAQ,UAAU,UAAU;GAC9C,OAAO;IAAC;IAAW;IAAQ;IAAQ;GAAK;GACxC;GAIA,KAAK;IAAE,GAAG;KAAM,sBAAsB;GAAI;GAE1C,UAAU;EACZ,CAAC;EAED,MAAM,YAAY,KAAK,IAAI;EAC3B,IAAI,UAAU;EACd,IAAI,iBAAiB;EACrB,IAAI,YAAY;EAQhB,IAAI,iBAAiB;EAErB,MAAM,sBAAsB,YAAmC,UAAkB;GAC/E,iBAAiB;GACjB,OAAO,MAAM,KAAK;EACpB;EAEA,MAAM,QAAQ,GAAG,QAAQ,mBAAmB,QAAQ,MAAM,CAAC;EAC3D,MAAM,QAAQ,GAAG,QAAQ,mBAAmB,QAAQ,MAAM,CAAC;EAE3D,MAAM,uBAAuB,iBAAiB;GAC5C,IAAI,SACF;GAGF,8BAA8B,KAAK,IAAI,IAAI,SAAS;EACtD,GAAG,sBAAsB;EAGzB,qBAAqB,QAAQ;EAE7B,MAAM,UAAU,WAAuC;GACrD,IAAI,WACF;GAGF,YAAY;GACZ,aAAa,oBAAoB;GACjC,QAAQ,IAAI,WAAW,SAAS;GAChC,QAAQ,IAAI,UAAU,QAAQ;GAC9B,QAAQ,IAAI,UAAU,QAAQ;GAE9B,QAAQ,MAAM;EAChB;EAEA,MAAM,GAAG,YAAY,YAAqB;GACxC,IAAI,WAAW,CAAC,aAAa,OAAO,GAClC;GAGF,UAAU;GACV,aAAa,oBAAoB;GAEjC,6BAA6B,EAAE,gBAAgB,QAAQ,eAAe,CAAC,CAAC,CAAC,OAAO,UAAU;IAGxF,QAAQ,MAAM,KAAK;GACrB,CAAC;EACH,CAAC;EAMD,MAAM,GAAG,UAAU,UAAU;GAC3B,QAAQ,MAAM,KAAK;GACnB,8BAA8B,GAAG,IAAI;GAErC,OAAO;IAAE,UAAU;IAAG,OAAO;GAAM,CAAC;EACtC,CAAC;EAKD,MAAM,WAAW,iBAAiC;GAChD,IAAI,gBAAgB;IAClB,MAAM,KAAK,SAAS;IACpB;GACF;GAEA,iBAAiB;GACjB,MAAM,KAAK,MAAM;EACnB;EAEA,MAAM,YAAY,QAAQ,SAAS;EACnC,MAAM,WAAW,QAAQ,QAAQ;EAIjC,MAAM,WACJ,QAAQ,aAAa,gBACX;GACJ,iBAAiB;EACnB,IACA,QAAQ,QAAQ;EAEtB,QAAQ,GAAG,WAAW,SAAS;EAC/B,QAAQ,GAAG,UAAU,QAAQ;EAC7B,QAAQ,GAAG,UAAU,QAAQ;EAO7B,MAAM,GAAG,UAAU,SAAS;GAQ1B,IAAI,CAAC,WAAW,CAAC,gBAAgB;IAC/B,MAAM,WAAW,SAAS,KAAK,SAAS,OAAO,IAAI;IAEnD,8BAA8B,UAAU,cAAc;IAEtD,OAAO,OAAO;KAAE;KAAU,OAAO;IAAM,CAAC;GAC1C;GAEA,OAAO;IAAE,UAAU,QAAQ;IAAG,OAAO;GAAQ,CAAC;EAChD,CAAC;CACH,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"production-supervisor.mjs","names":[],"sources":["../../../../../../../core/src/production/production-supervisor.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { spawn } from \"child_process\";\nimport { BOOT_SIGNAL_ENV_KEY, isBootSignal } from \"../application/boot-signal\";\nimport {\n displayMissingReadinessNotice,\n displayProductionReadyBanner,\n displayProductionStartFailure,\n} from \"../cli/cli-commands.utils\";\n\n/**\n * How long a still-running child may stay silent before we say so.\n *\n * Only reached by a bundle built before readiness reporting existed; a current\n * bundle signals as soon as its late-phase connectors are up.\n */\nexport const DEFAULT_READINESS_NOTICE_DELAY_MS = 10_000;\n\nexport type ProductionSupervisorOptions = {\n /** Arguments handed to the node binary, entry file included. */\n nodeArgs: string[];\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n readinessNoticeDelayMs?: number;\n};\n\nexport type ProductionSupervisorResult = {\n /** What the CLI should exit with — never 0 for a boot that never completed. */\n exitCode: number;\n /** Whether the child ever reported a finished boot. */\n ready: boolean;\n};\n\n/**\n * Run the production bundle in a child process and report honestly on it.\n *\n * The supervisor exists because a parent that only forwards `stdio` cannot\n * see whether its child ever booted — it only sees the process exit. It\n * therefore opens an IPC channel and treats the child's `warlock:ready`\n * signal, emitted by `Application.markBooted`, as the single source of truth\n * for \"started\":\n *\n * 1. The success banner is printed only on that signal.\n * 2. A child that dies before signalling is reported as a failed start and\n * forced to a non-zero exit, even if it exited 0.\n * 3. A child still running without having signalled draws a note on stderr —\n * never stdout, which would be the false-green this whole channel prevents.\n * 4. Whether the child ever wrote to stdout/stderr before dying is tracked\n * (`sawChildOutput`) so the failure summary only claims \"the cause is\n * printed above\" when that is actually true — see the stdio comment\n * below.\n *\n * Resolves rather than exiting so the outcome stays assertable in a test; the\n * `start` command turns the result into the process exit code.\n */\nexport function superviseProductionProcess({\n nodeArgs,\n cwd = process.cwd(),\n env = process.env,\n readinessNoticeDelayMs = DEFAULT_READINESS_NOTICE_DELAY_MS,\n}: ProductionSupervisorOptions): Promise<ProductionSupervisorResult> {\n return new Promise<ProductionSupervisorResult>((resolve) => {\n // On Windows, we need to be careful with signals - the console sends Ctrl+C\n // to all processes in the group, so we just need to not interfere.\n // `process.execPath`, not \"node\": the bare name needs `node` on PATH, which\n // a systemd unit, a cron job, or a slim container may not provide — and a\n // PATH `node` that *is* present can be a different version than the one\n // running this CLI. execPath is the binary already executing us.\n //\n // The fourth stdio slot opens the IPC channel the child reports readiness\n // on. Without it `process.send` is undefined in the child and we would be\n // back to guessing whether the boot succeeded.\n //\n // stdout/stderr are piped rather than inherited so this supervisor can\n // tell whether the child ever wrote anything, not just forward it blind.\n // `displayProductionStartFailure` used to claim \"the cause is printed\n // above\" on every failed boot, unconditionally — true when the child's\n // logger got far enough to print, false (and misleading) when it died\n // before its log channels were configured. Piping costs nothing here:\n // both streams are re-forwarded to this process's own stdout/stderr as\n // each chunk arrives, so a human watching the terminal sees the exact\n // same live output \"inherit\" gave them.\n const child = spawn(process.execPath, nodeArgs, {\n stdio: [\"inherit\", \"pipe\", \"pipe\", \"ipc\"],\n cwd,\n // The flag identifies us as a parent that speaks the boot protocol. The\n // child stays silent without it, so an app running under pm2 or any other\n // supervisor never writes to a channel that belongs to someone else.\n env: { ...env, [BOOT_SIGNAL_ENV_KEY]: \"1\" },\n // Important: keep child in same process group for proper signal handling\n detached: false,\n });\n\n const startedAt = Date.now();\n let isReady = false;\n let isShuttingDown = false;\n let isSettled = false;\n\n // Whether the child wrote anything to either stream — the only honest\n // basis for telling a developer \"the cause is printed above\". Set from\n // the first chunk on either stream; a child that dies before its first\n // write (a crash mid-import, a logger with no console channel) leaves\n // this `false`, which is exactly the case the summary must admit rather\n // than paper over.\n let sawChildOutput = false;\n\n const forwardChildOutput = (target: NodeJS.WritableStream) => (chunk: Buffer) => {\n sawChildOutput = true;\n target.write(chunk);\n };\n\n child.stdout?.on(\"data\", forwardChildOutput(process.stdout));\n child.stderr?.on(\"data\", forwardChildOutput(process.stderr));\n\n const readinessNoticeTimer = setTimeout(() => {\n if (isReady) {\n return;\n }\n\n displayMissingReadinessNotice(Date.now() - startedAt);\n }, readinessNoticeDelayMs);\n\n // A diagnostic timer must never be the reason the CLI stays alive.\n readinessNoticeTimer.unref?.();\n\n const settle = (result: ProductionSupervisorResult) => {\n if (isSettled) {\n return;\n }\n\n isSettled = true;\n clearTimeout(readinessNoticeTimer);\n process.off(\"SIGTERM\", onSigterm);\n process.off(\"SIGINT\", onSigint);\n process.off(\"SIGHUP\", onSighup);\n\n resolve(result);\n };\n\n child.on(\"message\", (message: unknown) => {\n if (isReady || !isBootSignal(message)) {\n return;\n }\n\n isReady = true;\n clearTimeout(readinessNoticeTimer);\n\n displayProductionReadyBanner({ bootDurationMs: message.bootDurationMs }).catch((error) => {\n // The server is up either way — a banner that fails to render must not\n // be reported as, or turn into, a boot failure.\n console.error(error);\n });\n\n // A worker-only process never binds a port, so `http-connector.ts`\n // never prints a URL for it — this is the one line that tells an\n // operator watching `warlock start` that the process is up on purpose,\n // not silently stuck.\n const roles = message.roles ?? [];\n\n if (roles.length > 0) {\n const servesHttp = roles.includes(\"api\") || roles.includes(\"web\");\n const summary = servesHttp\n ? `roles: ${roles.join(\", \")}`\n : `worker ready — roles: ${roles.join(\", \")}`;\n\n console.log(` ${colors.dim(summary)}`);\n }\n });\n\n // A spawn that never starts (missing binary, EACCES) emits `error` and no\n // `exit`, so without this the command would hang silently on a failure.\n // The error is printed BEFORE the summary that references it — the\n // summary says the cause is \"above\", so it has to already be there.\n child.on(\"error\", (error) => {\n console.error(error);\n displayProductionStartFailure(1, true);\n\n settle({ exitCode: 1, ready: false });\n });\n\n // Forward every stop signal so a supervisor that only signals this\n // process (docker kill -s INT, systemd KillSignal) still stops the child.\n // A repeated signal means the graceful shutdown is stuck: force-kill.\n const forward = (signal: NodeJS.Signals) => () => {\n if (isShuttingDown) {\n child.kill(\"SIGKILL\");\n return;\n }\n\n isShuttingDown = true;\n child.kill(signal);\n };\n\n const onSigterm = forward(\"SIGTERM\");\n const onSighup = forward(\"SIGHUP\");\n\n // On Windows Ctrl+C reaches both processes, so the child already has it —\n // we only record that this exit was asked for (kill() there is a hard kill).\n const onSigint =\n process.platform === \"win32\"\n ? () => {\n isShuttingDown = true;\n }\n : forward(\"SIGINT\");\n\n process.on(\"SIGTERM\", onSigterm);\n process.on(\"SIGINT\", onSigint);\n process.on(\"SIGHUP\", onSighup);\n\n // `close`, not `exit`: `exit` fires as soon as the OS process ends, which\n // can race ahead of the last chunks still in flight on the piped stdout/\n // stderr streams. `close` waits for those streams to end too, so\n // `sawChildOutput` reflects everything the child actually wrote instead\n // of whatever had been delivered by the time the process happened to die.\n child.on(\"close\", (code) => {\n // A child that dies before reporting readiness never served a request.\n // Exiting 0 there — which a clean `process.exit(0)` in failing app code\n // would do — reports a boot failure as a successful run, so the exit code\n // is forced non-zero to match what actually happened.\n //\n // An operator who interrupts the boot is not a failed boot, so a signalled\n // shutdown keeps the child's own exit code.\n if (!isReady && !isShuttingDown) {\n const exitCode = code === 0 || code === null ? 1 : code;\n\n displayProductionStartFailure(exitCode, sawChildOutput);\n\n return settle({ exitCode, ready: false });\n }\n\n settle({ exitCode: code ?? 0, ready: isReady });\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;AAuCjD,SAAgB,2BAA2B,EACzC,UACA,MAAM,QAAQ,IAAI,GAClB,MAAM,QAAQ,KACd,yBAAyB,qCAC0C;CACnE,OAAO,IAAI,SAAqC,YAAY;EAqB1D,MAAM,QAAQ,MAAM,QAAQ,UAAU,UAAU;GAC9C,OAAO;IAAC;IAAW;IAAQ;IAAQ;GAAK;GACxC;GAIA,KAAK;IAAE,GAAG;KAAM,sBAAsB;GAAI;GAE1C,UAAU;EACZ,CAAC;EAED,MAAM,YAAY,KAAK,IAAI;EAC3B,IAAI,UAAU;EACd,IAAI,iBAAiB;EACrB,IAAI,YAAY;EAQhB,IAAI,iBAAiB;EAErB,MAAM,sBAAsB,YAAmC,UAAkB;GAC/E,iBAAiB;GACjB,OAAO,MAAM,KAAK;EACpB;EAEA,MAAM,QAAQ,GAAG,QAAQ,mBAAmB,QAAQ,MAAM,CAAC;EAC3D,MAAM,QAAQ,GAAG,QAAQ,mBAAmB,QAAQ,MAAM,CAAC;EAE3D,MAAM,uBAAuB,iBAAiB;GAC5C,IAAI,SACF;GAGF,8BAA8B,KAAK,IAAI,IAAI,SAAS;EACtD,GAAG,sBAAsB;EAGzB,qBAAqB,QAAQ;EAE7B,MAAM,UAAU,WAAuC;GACrD,IAAI,WACF;GAGF,YAAY;GACZ,aAAa,oBAAoB;GACjC,QAAQ,IAAI,WAAW,SAAS;GAChC,QAAQ,IAAI,UAAU,QAAQ;GAC9B,QAAQ,IAAI,UAAU,QAAQ;GAE9B,QAAQ,MAAM;EAChB;EAEA,MAAM,GAAG,YAAY,YAAqB;GACxC,IAAI,WAAW,CAAC,aAAa,OAAO,GAClC;GAGF,UAAU;GACV,aAAa,oBAAoB;GAEjC,6BAA6B,EAAE,gBAAgB,QAAQ,eAAe,CAAC,CAAC,CAAC,OAAO,UAAU;IAGxF,QAAQ,MAAM,KAAK;GACrB,CAAC;GAMD,MAAM,QAAQ,QAAQ,SAAS,CAAC;GAEhC,IAAI,MAAM,SAAS,GAAG;IAEpB,MAAM,UADa,MAAM,SAAS,KAAK,KAAK,MAAM,SAAS,KAAK,IAE5D,UAAU,MAAM,KAAK,IAAI,MACzB,yBAAyB,MAAM,KAAK,IAAI;IAE5C,QAAQ,IAAI,KAAK,OAAO,IAAI,OAAO,GAAG;GACxC;EACF,CAAC;EAMD,MAAM,GAAG,UAAU,UAAU;GAC3B,QAAQ,MAAM,KAAK;GACnB,8BAA8B,GAAG,IAAI;GAErC,OAAO;IAAE,UAAU;IAAG,OAAO;GAAM,CAAC;EACtC,CAAC;EAKD,MAAM,WAAW,iBAAiC;GAChD,IAAI,gBAAgB;IAClB,MAAM,KAAK,SAAS;IACpB;GACF;GAEA,iBAAiB;GACjB,MAAM,KAAK,MAAM;EACnB;EAEA,MAAM,YAAY,QAAQ,SAAS;EACnC,MAAM,WAAW,QAAQ,QAAQ;EAIjC,MAAM,WACJ,QAAQ,aAAa,gBACX;GACJ,iBAAiB;EACnB,IACA,QAAQ,QAAQ;EAEtB,QAAQ,GAAG,WAAW,SAAS;EAC/B,QAAQ,GAAG,UAAU,QAAQ;EAC7B,QAAQ,GAAG,UAAU,QAAQ;EAO7B,MAAM,GAAG,UAAU,SAAS;GAQ1B,IAAI,CAAC,WAAW,CAAC,gBAAgB;IAC/B,MAAM,WAAW,SAAS,KAAK,SAAS,OAAO,IAAI;IAEnD,8BAA8B,UAAU,cAAc;IAEtD,OAAO,OAAO;KAAE;KAAU,OAAO;IAAM,CAAC;GAC1C;GAEA,OAAO;IAAE,UAAU,QAAQ;IAAG,OAAO;GAAQ,CAAC;EAChD,CAAC;CACH,CAAC;AACH"}
|
|
@@ -16,7 +16,7 @@ import { WarlockConfig } from "../warlock-config/types.mjs";
|
|
|
16
16
|
* that; widening them to `string | undefined` pushed the lie downstream into
|
|
17
17
|
* `start-production.command.ts`, which then could not trust its own config.
|
|
18
18
|
*/
|
|
19
|
-
type BuilderStrippedKeys = "singleBundle" | "esmShim" | "banner";
|
|
19
|
+
type BuilderStrippedKeys = "singleBundle" | "esmShim" | "banner" | "routeRegistrationTimeoutMs";
|
|
20
20
|
type ResolvedBuildConfig = Required<Omit<NonNullable<WarlockConfig["build"]>, "outDirectory" | BuilderStrippedKeys>> & Partial<Pick<NonNullable<WarlockConfig["build"]>, BuilderStrippedKeys>> & {
|
|
21
21
|
/** Absolute path to the bundled entry file (`{outdir}/{outFile}`) */entryPath: string;
|
|
22
22
|
};
|
|
@@ -62,9 +62,24 @@ function resolveBuildConfig() {
|
|
|
62
62
|
};
|
|
63
63
|
return {
|
|
64
64
|
...merged,
|
|
65
|
+
routeRegistrationTimeoutMs: resolveRouteRegistrationTimeoutMs(merged.routeRegistrationTimeoutMs),
|
|
65
66
|
entryPath: path.resolve(merged.outdir, merged.outFile)
|
|
66
67
|
};
|
|
67
68
|
}
|
|
69
|
+
const DEFAULT_ROUTE_REGISTRATION_TIMEOUT_MS = 3e4;
|
|
70
|
+
const ROUTE_REGISTRATION_TIMEOUT_ENV = "WARLOCK_ROUTE_REGISTRATION_TIMEOUT_MS";
|
|
71
|
+
function resolveRouteRegistrationTimeoutMs(configured) {
|
|
72
|
+
const environment = process.env[ROUTE_REGISTRATION_TIMEOUT_ENV];
|
|
73
|
+
const value = environment === void 0 ? configured : environment;
|
|
74
|
+
const source = environment === void 0 ? "build.routeRegistrationTimeoutMs" : ROUTE_REGISTRATION_TIMEOUT_ENV;
|
|
75
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
76
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
77
|
+
const parsed = Number(value);
|
|
78
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
79
|
+
}
|
|
80
|
+
console.warn(`Invalid ${source} value ${JSON.stringify(value)}; using ${DEFAULT_ROUTE_REGISTRATION_TIMEOUT_MS}ms.`);
|
|
81
|
+
return DEFAULT_ROUTE_REGISTRATION_TIMEOUT_MS;
|
|
82
|
+
}
|
|
68
83
|
|
|
69
84
|
//#endregion
|
|
70
85
|
export { resolveBuildConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-build-config.mjs","names":[],"sources":["../../../../../../../core/src/production/resolve-build-config.ts"],"sourcesContent":["import path from \"path\";\nimport { defaultWarlockConfigurations } from \"../warlock-config/default-configurations\";\nimport { normalizeBuildConfig } from \"../warlock-config/normalize-build-config\";\nimport type { WarlockConfig } from \"../warlock-config/types\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\n\n/**\n * Keys the builder deletes before handing the options to esbuild — ours\n * (`singleBundle`, `esmShim`), which esbuild would reject as unknown, and\n * `banner`, which is re-merged and re-applied after the user spread.\n *\n * They are optional here for a concrete reason: `Required<…>` makes `delete`\n * a type error (TS2790). Naming exactly these three says which keys are\n * removable, rather than casting at each `delete` — a cast there would also\n * hide a genuine mistake.\n *\n * `outFile` and `entryPath` stay REQUIRED even though the builder deletes\n * them too. They are always present after resolution and callers depend on\n * that; widening them to `string | undefined` pushed the lie downstream into\n * `start-production.command.ts`, which then could not trust its own config.\n */\ntype BuilderStrippedKeys = \"singleBundle\" | \"esmShim\" | \"banner\";\n\nexport type ResolvedBuildConfig = Required<\n Omit<NonNullable<WarlockConfig[\"build\"]>, \"outDirectory\" | BuilderStrippedKeys>\n> &\n Partial<Pick<NonNullable<WarlockConfig[\"build\"]>, BuilderStrippedKeys>> & {\n /** Absolute path to the bundled entry file (`{outdir}/{outFile}`) */\n entryPath: string;\n };\n\n/**\n * esbuild options the build config may not carry, and why.\n *\n * Both apply the named compiler options to EVERY file in the bundle, not just\n * the app's own sources — workspace packages compiled under their own tsconfig\n * are silently recompiled under the app's. An app-level\n * `verbatimModuleSyntax: true` reaching package sources that never opted into\n * it strips the imports their decorators rely on, and the build succeeds only\n * to die at import time with `Class extends value undefined`.\n */\nconst FORBIDDEN_BUILD_OPTIONS = {\n tsconfig: \"points esbuild at a tsconfig file\",\n tsconfigRaw: \"inlines compiler options\",\n} as const;\n\n/**\n * Raised when the build config carries an esbuild option the framework owns.\n *\n * The typed config already omits these keys; this catches the forms the type\n * cannot see — an `as any` cast, or a plain `warlock.config.js`. Rejected\n * rather than deleted: dropping the key quietly would leave the author\n * believing a setting applied when it never did.\n */\nexport class ForbiddenBuildOptionError extends Error {\n public constructor(public readonly option: keyof typeof FORBIDDEN_BUILD_OPTIONS) {\n super(\n [\n `build.${option} is not a supported warlock build option.`,\n \"\",\n `Setting it ${FORBIDDEN_BUILD_OPTIONS[option]} for every file in the bundle,`,\n \"including workspace packages that were compiled under their own tsconfig.\",\n \"Options such as `verbatimModuleSyntax` then apply to sources that never\",\n \"opted into them, and the build succeeds but crashes at import time with\",\n \"`Class extends value undefined`.\",\n \"\",\n `Remove \\`${option}\\` from the \\`build\\` block of your warlock config. The builder`,\n \"already reads the app's tsconfig for the parts it needs (path aliases); to\",\n \"change how your own sources compile, edit that tsconfig.json instead.\",\n ].join(\"\\n\"),\n );\n\n this.name = \"ForbiddenBuildOptionError\";\n }\n}\n\n/**\n * Resolve the build config with framework defaults applied.\n *\n * Both the production builder and `warlock start` call this so they\n * agree on where the bundle lives — previously each had its own local\n * fallbacks and they drifted (`.warlock/production` vs `dist`),\n * letting `build` and `start` look at different paths.\n */\nexport function resolveBuildConfig(): ResolvedBuildConfig {\n // Normalised here rather than only in `defineConfig` because a project may\n // export a plain config object that never went through it.\n const userBuild = normalizeBuildConfig(warlockConfigManager.get(\"build\") ?? {});\n\n // Before the merge, so the option cannot reach the esbuild spread in\n // production-builder — which overrides framework defaults with user config.\n for (const option of Object.keys(FORBIDDEN_BUILD_OPTIONS) as Array<\n keyof typeof FORBIDDEN_BUILD_OPTIONS\n >) {\n if (option in userBuild) {\n throw new ForbiddenBuildOptionError(option);\n }\n }\n\n const defaults = defaultWarlockConfigurations.build!;\n const merged = { ...defaults, ...userBuild } as ResolvedBuildConfig;\n\n return {\n ...merged,\n entryPath: path.resolve(merged.outdir, merged.outFile),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyCA,MAAM,0BAA0B;CAC9B,UAAU;CACV,aAAa;AACf;;;;;;;;;AAUA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,AAAgB,QAA8C;EAC/E,MACE;GACE,SAAS,OAAO;GAChB;GACA,cAAc,wBAAwB,QAAQ;GAC9C;GACA;GACA;GACA;GACA;GACA,YAAY,OAAO;GACnB;GACA;EACF,CAAC,CAAC,KAAK,IAAI,CACb;EAfiC;EAiBjC,KAAK,OAAO;CACd;AACF;;;;;;;;;AAUA,SAAgB,qBAA0C;CAGxD,MAAM,YAAY,qBAAqB,qBAAqB,IAAI,OAAO,KAAK,CAAC,CAAC;CAI9E,KAAK,MAAM,UAAU,OAAO,KAAK,uBAAuB,GAGtD,IAAI,UAAU,WACZ,MAAM,IAAI,0BAA0B,MAAM;CAK9C,MAAM,SAAS;EAAE,GADA,6BAA6B;EAChB,GAAG;CAAU;CAE3C,OAAO;EACL,GAAG;EACH,WAAW,KAAK,QAAQ,OAAO,QAAQ,OAAO,OAAO;CACvD;AACF"}
|
|
1
|
+
{"version":3,"file":"resolve-build-config.mjs","names":[],"sources":["../../../../../../../core/src/production/resolve-build-config.ts"],"sourcesContent":["import path from \"path\";\nimport { defaultWarlockConfigurations } from \"../warlock-config/default-configurations\";\nimport { normalizeBuildConfig } from \"../warlock-config/normalize-build-config\";\nimport type { WarlockConfig } from \"../warlock-config/types\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\n\n/**\n * Keys the builder deletes before handing the options to esbuild — ours\n * (`singleBundle`, `esmShim`), which esbuild would reject as unknown, and\n * `banner`, which is re-merged and re-applied after the user spread.\n *\n * They are optional here for a concrete reason: `Required<…>` makes `delete`\n * a type error (TS2790). Naming exactly these three says which keys are\n * removable, rather than casting at each `delete` — a cast there would also\n * hide a genuine mistake.\n *\n * `outFile` and `entryPath` stay REQUIRED even though the builder deletes\n * them too. They are always present after resolution and callers depend on\n * that; widening them to `string | undefined` pushed the lie downstream into\n * `start-production.command.ts`, which then could not trust its own config.\n */\ntype BuilderStrippedKeys = \"singleBundle\" | \"esmShim\" | \"banner\" | \"routeRegistrationTimeoutMs\";\n\nexport type ResolvedBuildConfig = Required<\n Omit<NonNullable<WarlockConfig[\"build\"]>, \"outDirectory\" | BuilderStrippedKeys>\n> &\n Partial<Pick<NonNullable<WarlockConfig[\"build\"]>, BuilderStrippedKeys>> & {\n /** Absolute path to the bundled entry file (`{outdir}/{outFile}`) */\n entryPath: string;\n };\n\n/**\n * esbuild options the build config may not carry, and why.\n *\n * Both apply the named compiler options to EVERY file in the bundle, not just\n * the app's own sources — workspace packages compiled under their own tsconfig\n * are silently recompiled under the app's. An app-level\n * `verbatimModuleSyntax: true` reaching package sources that never opted into\n * it strips the imports their decorators rely on, and the build succeeds only\n * to die at import time with `Class extends value undefined`.\n */\nconst FORBIDDEN_BUILD_OPTIONS = {\n tsconfig: \"points esbuild at a tsconfig file\",\n tsconfigRaw: \"inlines compiler options\",\n} as const;\n\n/**\n * Raised when the build config carries an esbuild option the framework owns.\n *\n * The typed config already omits these keys; this catches the forms the type\n * cannot see — an `as any` cast, or a plain `warlock.config.js`. Rejected\n * rather than deleted: dropping the key quietly would leave the author\n * believing a setting applied when it never did.\n */\nexport class ForbiddenBuildOptionError extends Error {\n public constructor(public readonly option: keyof typeof FORBIDDEN_BUILD_OPTIONS) {\n super(\n [\n `build.${option} is not a supported warlock build option.`,\n \"\",\n `Setting it ${FORBIDDEN_BUILD_OPTIONS[option]} for every file in the bundle,`,\n \"including workspace packages that were compiled under their own tsconfig.\",\n \"Options such as `verbatimModuleSyntax` then apply to sources that never\",\n \"opted into them, and the build succeeds but crashes at import time with\",\n \"`Class extends value undefined`.\",\n \"\",\n `Remove \\`${option}\\` from the \\`build\\` block of your warlock config. The builder`,\n \"already reads the app's tsconfig for the parts it needs (path aliases); to\",\n \"change how your own sources compile, edit that tsconfig.json instead.\",\n ].join(\"\\n\"),\n );\n\n this.name = \"ForbiddenBuildOptionError\";\n }\n}\n\n/**\n * Resolve the build config with framework defaults applied.\n *\n * Both the production builder and `warlock start` call this so they\n * agree on where the bundle lives — previously each had its own local\n * fallbacks and they drifted (`.warlock/production` vs `dist`),\n * letting `build` and `start` look at different paths.\n */\nexport function resolveBuildConfig(): ResolvedBuildConfig {\n // Normalised here rather than only in `defineConfig` because a project may\n // export a plain config object that never went through it.\n const userBuild = normalizeBuildConfig(warlockConfigManager.get(\"build\") ?? {});\n\n // Before the merge, so the option cannot reach the esbuild spread in\n // production-builder — which overrides framework defaults with user config.\n for (const option of Object.keys(FORBIDDEN_BUILD_OPTIONS) as Array<\n keyof typeof FORBIDDEN_BUILD_OPTIONS\n >) {\n if (option in userBuild) {\n throw new ForbiddenBuildOptionError(option);\n }\n }\n\n const defaults = defaultWarlockConfigurations.build!;\n const merged = { ...defaults, ...userBuild } as ResolvedBuildConfig;\n\n return {\n ...merged,\n routeRegistrationTimeoutMs: resolveRouteRegistrationTimeoutMs(merged.routeRegistrationTimeoutMs),\n entryPath: path.resolve(merged.outdir, merged.outFile),\n };\n}\n\nconst DEFAULT_ROUTE_REGISTRATION_TIMEOUT_MS = 30_000;\nconst ROUTE_REGISTRATION_TIMEOUT_ENV = \"WARLOCK_ROUTE_REGISTRATION_TIMEOUT_MS\";\n\nfunction resolveRouteRegistrationTimeoutMs(configured: unknown): number {\n const environment = process.env[ROUTE_REGISTRATION_TIMEOUT_ENV];\n const value = environment === undefined ? configured : environment;\n const source = environment === undefined ? \"build.routeRegistrationTimeoutMs\" : ROUTE_REGISTRATION_TIMEOUT_ENV;\n\n if (typeof value === \"number\" && Number.isFinite(value) && value > 0) return value;\n if (typeof value === \"string\" && value.trim() !== \"\") {\n const parsed = Number(value);\n if (Number.isFinite(parsed) && parsed > 0) return parsed;\n }\n\n console.warn(\n `Invalid ${source} value ${JSON.stringify(value)}; using ${DEFAULT_ROUTE_REGISTRATION_TIMEOUT_MS}ms.`,\n );\n return DEFAULT_ROUTE_REGISTRATION_TIMEOUT_MS;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyCA,MAAM,0BAA0B;CAC9B,UAAU;CACV,aAAa;AACf;;;;;;;;;AAUA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,AAAgB,QAA8C;EAC/E,MACE;GACE,SAAS,OAAO;GAChB;GACA,cAAc,wBAAwB,QAAQ;GAC9C;GACA;GACA;GACA;GACA;GACA,YAAY,OAAO;GACnB;GACA;EACF,CAAC,CAAC,KAAK,IAAI,CACb;EAfiC;EAiBjC,KAAK,OAAO;CACd;AACF;;;;;;;;;AAUA,SAAgB,qBAA0C;CAGxD,MAAM,YAAY,qBAAqB,qBAAqB,IAAI,OAAO,KAAK,CAAC,CAAC;CAI9E,KAAK,MAAM,UAAU,OAAO,KAAK,uBAAuB,GAGtD,IAAI,UAAU,WACZ,MAAM,IAAI,0BAA0B,MAAM;CAK9C,MAAM,SAAS;EAAE,GADA,6BAA6B;EAChB,GAAG;CAAU;CAE3C,OAAO;EACL,GAAG;EACH,4BAA4B,kCAAkC,OAAO,0BAA0B;EAC/F,WAAW,KAAK,QAAQ,OAAO,QAAQ,OAAO,OAAO;CACvD;AACF;AAEA,MAAM,wCAAwC;AAC9C,MAAM,iCAAiC;AAEvC,SAAS,kCAAkC,YAA6B;CACtE,MAAM,cAAc,QAAQ,IAAI;CAChC,MAAM,QAAQ,gBAAgB,SAAY,aAAa;CACvD,MAAM,SAAS,gBAAgB,SAAY,qCAAqC;CAEhF,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CAC7E,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI;EACpD,MAAM,SAAS,OAAO,KAAK;EAC3B,IAAI,OAAO,SAAS,MAAM,KAAK,SAAS,GAAG,OAAO;CACpD;CAEA,QAAQ,KACN,WAAW,OAAO,SAAS,KAAK,UAAU,KAAK,EAAE,UAAU,sCAAsC,IACnG;CACA,OAAO;AACT"}
|
|
@@ -25,8 +25,11 @@ process.once("message", async (message) => {
|
|
|
25
25
|
await filesOrchestrator.initializeAll();
|
|
26
26
|
filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());
|
|
27
27
|
await loadConfigFiles(true);
|
|
28
|
-
for (const file of filesOrchestrator.files.values()) if (file.type === "model")
|
|
29
|
-
|
|
28
|
+
for (const file of filesOrchestrator.files.values()) if (file.type === "model") {
|
|
29
|
+
await sendProgress(file.relativePath);
|
|
30
|
+
await filesOrchestrator.moduleLoader.loadModule(file, "model");
|
|
31
|
+
}
|
|
32
|
+
await filesOrchestrator.moduleLoader.loadAll({ onBeforeLoad: (file) => sendProgress(file.relativePath) });
|
|
30
33
|
await sendAndExit({
|
|
31
34
|
type: "route-registration:snapshot",
|
|
32
35
|
snapshot: {
|
|
@@ -59,6 +62,15 @@ async function sendAndExit(message, exitCode = 0) {
|
|
|
59
62
|
process.disconnect?.();
|
|
60
63
|
process.exit(exitCode);
|
|
61
64
|
}
|
|
65
|
+
async function sendProgress(module) {
|
|
66
|
+
if (typeof process.send !== "function") return;
|
|
67
|
+
await new Promise((resolve, reject) => {
|
|
68
|
+
process.send({
|
|
69
|
+
type: "route-registration:progress",
|
|
70
|
+
module
|
|
71
|
+
}, (error) => error ? reject(error) : resolve());
|
|
72
|
+
});
|
|
73
|
+
}
|
|
62
74
|
|
|
63
75
|
//#endregion
|
|
64
76
|
export { };
|
|
@@ -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 filesOrchestrator.moduleLoader.loadModule(file, \"model\");\n }\n }\n\n await filesOrchestrator.moduleLoader.loadAll();\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"],"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,
|
|
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"}
|
|
@@ -17,6 +17,7 @@ const MAX_DIAGNOSTIC_BYTES = 16384;
|
|
|
17
17
|
function collectRouteRegistrationSnapshot(options = {}) {
|
|
18
18
|
const cwd = options.cwd ?? process.cwd();
|
|
19
19
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
20
|
+
const startedAt = Date.now();
|
|
20
21
|
const forkProcess = options.forkProcess ?? defaultForkProcess;
|
|
21
22
|
const entry = options.childEntry ?? (options.forkProcess ? "<injected-route-registration-child>" : resolveCompiledChildEntry());
|
|
22
23
|
return new Promise((resolve, reject) => {
|
|
@@ -25,6 +26,7 @@ function collectRouteRegistrationSnapshot(options = {}) {
|
|
|
25
26
|
let receivedSnapshot;
|
|
26
27
|
let stdout = "";
|
|
27
28
|
let stderr = "";
|
|
29
|
+
let pendingModule;
|
|
28
30
|
const append = (current, chunk) => {
|
|
29
31
|
const remaining = MAX_DIAGNOSTIC_BYTES - Buffer.byteLength(current);
|
|
30
32
|
return remaining <= 0 ? current : current + chunk.toString("utf8", 0, remaining);
|
|
@@ -46,7 +48,8 @@ function collectRouteRegistrationSnapshot(options = {}) {
|
|
|
46
48
|
stderr = append(stderr, chunk);
|
|
47
49
|
});
|
|
48
50
|
const timeout = setTimeout(() => {
|
|
49
|
-
|
|
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)}`));
|
|
50
53
|
}, timeoutMs);
|
|
51
54
|
timeout.unref?.();
|
|
52
55
|
child.on("error", (error) => {
|
|
@@ -58,6 +61,10 @@ function collectRouteRegistrationSnapshot(options = {}) {
|
|
|
58
61
|
finish(/* @__PURE__ */ new Error(`Route registration child failed: ${message.message}${formatDiagnostics(stdout, stderr)}`));
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
64
|
+
if (message.type === "route-registration:progress") {
|
|
65
|
+
pendingModule = message.module;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
61
68
|
try {
|
|
62
69
|
const snapshot = validateSnapshot(message.snapshot);
|
|
63
70
|
if (receivedSnapshot) {
|
|
@@ -120,7 +127,7 @@ function resolveCompiledChildEntry() {
|
|
|
120
127
|
throw new Error("Could not locate @warlock.js/core package root for the route-registration child.");
|
|
121
128
|
}
|
|
122
129
|
function isChildMessage(value) {
|
|
123
|
-
return typeof value === "object" && value !== null && "type" in value && (value.type === "route-registration:snapshot" || value.type === "route-registration:error");
|
|
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");
|
|
124
131
|
}
|
|
125
132
|
function validateSnapshot(value) {
|
|
126
133
|
if (!isRecord(value) || value.version !== PROTOCOL_VERSION || !Array.isArray(value.routes)) throw new Error("Route registration child sent an invalid snapshot protocol payload.");
|
|
@@ -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\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 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\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 finish(\n new Error(\n `Route registration child timed out after ${timeoutMs}ms.${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 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 );\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 { 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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"}
|
|
@@ -124,7 +124,7 @@ var ResourceFieldBuilder = class ResourceFieldBuilder {
|
|
|
124
124
|
if (value === void 0 || value === null) return this.isNullable ? null : this.defaultValue;
|
|
125
125
|
if (this.condition && !this.condition()) return this.isNullable ? null : this.defaultValue;
|
|
126
126
|
switch (this.type) {
|
|
127
|
-
case "string": return String(value);
|
|
127
|
+
case "string": return typeof value?.toHexString === "function" ? value.toHexString() : String(value);
|
|
128
128
|
case "number": {
|
|
129
129
|
const num = Number(value);
|
|
130
130
|
return isNaN(num) ? this.isNullable ? null : void 0 : num;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-field-builder.mjs","names":[],"sources":["../../../../../../../core/src/resource/resource-field-builder.ts"],"sourcesContent":["import { isObject } from \"@mongez/supportive-is\";\nimport dayjs from \"dayjs\";\nimport relativeTime from \"dayjs/plugin/relativeTime.js\";\nimport { storage } from \"../storage\";\n\n// `humanTime` date options call dayjs(...).fromNow(), which lives in the\n// relativeTime plugin. dayjs.extend is idempotent (a plugin registers itself\n// once), so extending here at module load is safe to repeat.\ndayjs.extend(relativeTime);\nimport { uploadsUrl, url } from \"../utils/urls\";\nimport { type LocalizedObject } from \"./../utils/get-localized\";\nimport {\n type ResourceFieldBuilderDateOutputOptions,\n type ResourceOutputValueCastType,\n} from \"./types\";\n\nexport class ResourceFieldBuilder {\n /**\n * Field value\n */\n protected fieldValue?: unknown;\n\n /**\n * Whether the value is nullable\n * If set to false and value is null, it will be returned as undefined\n */\n protected isNullable = false;\n\n /**\n * Whether this field is an array\n * When true, transform() maps over each element using the base type\n */\n protected isArrayField = false;\n\n /**\n * Default value\n */\n protected defaultValue?: unknown;\n\n /**\n * Date format\n */\n protected dateFormat = \"DD-MM-YYYY hh:mm:ss A\";\n\n /**\n * Input key\n */\n protected inputKeyToUse?: string;\n\n /**\n * Add a condition before transforming the value\n */\n protected condition?: () => boolean;\n\n /**\n * Define how date fields are returned\n * If type of the date options is string then it will be returned as a string\n * otherwise it will be returned as an object contains the date options\n */\n protected dateOptionsInput: ResourceFieldBuilderDateOutputOptions = {\n format: true,\n timestamp: true,\n timezone: false,\n locale: false,\n offset: false,\n humanTime: true,\n iso: true,\n };\n\n /**\n * Constructor\n */\n public constructor(protected readonly type: ResourceOutputValueCastType) {\n //\n }\n\n /**\n * Parse a cast type string (including suffixes) into a configured builder.\n * Suffix order: [] before ? (e.g. \"string[]?\")\n * Parsing strips right-to-left: ? first, then [].\n */\n public static fromCastType(castType: string): ResourceFieldBuilder {\n let baseType = castType;\n let nullable = false;\n let isArray = false;\n\n if (baseType.endsWith(\"?\")) {\n nullable = true;\n baseType = baseType.slice(0, -1);\n }\n\n if (baseType.endsWith(\"[]\")) {\n isArray = true;\n baseType = baseType.slice(0, -2);\n }\n\n const builder = new ResourceFieldBuilder(baseType as ResourceOutputValueCastType);\n if (nullable) builder.nullable();\n if (isArray) builder.array();\n return builder;\n }\n\n /**\n * Set input key\n * Will be used in transformation if provided\n */\n public setInputKey(key: string) {\n this.inputKeyToUse = key;\n\n return this;\n }\n\n /**\n * Add a condition before transforming the value\n */\n public when(condition: () => boolean) {\n this.condition = condition;\n return this;\n }\n\n /**\n * Set whether the value is nullable\n */\n public nullable() {\n this.isNullable = true;\n return this;\n }\n\n /**\n * Mark this field as an array\n * transform() will map over each element using the base type\n */\n public array() {\n this.isArrayField = true;\n return this;\n }\n\n /**\n * Get input key\n */\n public getInputKey() {\n return this.inputKeyToUse;\n }\n\n /**\n * Set default value\n */\n public default(value: unknown) {\n this.defaultValue = value;\n\n return this;\n }\n\n /**\n * Set field format\n */\n public format(format: string) {\n this.dateFormat = format;\n\n return this;\n }\n\n /**\n * Set date options\n * This will override current date options\n */\n public dateOptions(options: ResourceFieldBuilderDateOutputOptions) {\n this.dateOptionsInput = options;\n\n return this;\n }\n\n /**\n * Transform the value.\n * When isArrayField is true, maps over each element using transformSingleValue.\n */\n public transform(value: any, locale?: string) {\n if (this.isArrayField) {\n if (!Array.isArray(value)) {\n return this.isNullable ? null : [];\n }\n\n return value\n .map((item) => this.transformSingleValue(item, locale))\n .filter((v) => v !== undefined);\n }\n\n return this.transformSingleValue(value, locale);\n }\n\n /**\n * Transform a single value according to the base type.\n */\n protected transformSingleValue(value: any, locale?: string) {\n if (value === undefined || value === null) {\n return this.isNullable ? null : this.defaultValue;\n }\n\n if (this.condition && !this.condition()) {\n return this.isNullable ? null : this.defaultValue;\n }\n\n switch (this.type) {\n case \"string\":\n return String(value);\n case \"number\": {\n const num = Number(value);\n return isNaN(num) ? (this.isNullable ? null : undefined) : num;\n }\n case \"boolean\":\n return Boolean(value);\n case \"float\": {\n const float = parseFloat(value);\n return isNaN(float) ? (this.isNullable ? null : undefined) : float;\n }\n\n case \"int\": {\n const int = parseInt(value);\n return isNaN(int) ? (this.isNullable ? null : undefined) : int;\n }\n\n case \"date\":\n return this.transformDate(value as string | Date, locale);\n case \"localized\":\n return this.transformLocalized(value as LocalizedObject[], locale);\n case \"url\":\n return url(value as string);\n case \"uploadsUrl\":\n return uploadsUrl(value as string);\n case \"storageUrl\":\n return storage.url(value as string);\n case \"object\":\n return isObject(value) && !Array.isArray(value) && Object.keys(value).length > 0\n ? value\n : this.isNullable\n ? null\n : undefined;\n case \"array\":\n return Array.isArray(value) ? value : this.isNullable ? null : undefined;\n }\n }\n\n /**\n * Transform date value\n */\n protected transformDate(value: string | Date, locale?: string) {\n // Bad data (\"\" or garbage) must not throw and turn the response into a 500\n if (!dayjs((value as any)?.iso || value).isValid()) {\n return this.isNullable ? null : undefined;\n }\n\n if (typeof this.dateOptionsInput === \"string\") {\n if (this.dateOptionsInput === \"format\") {\n return dayjs(value).format(this.dateFormat);\n }\n\n if (this.dateOptionsInput === \"iso\") {\n return dayjs(value).toISOString();\n }\n\n if (this.dateOptionsInput === \"timestamp\") {\n return dayjs(value).valueOf();\n }\n\n if (this.dateOptionsInput === \"humanTime\") {\n return (dayjs as any)(value).fromNow();\n }\n\n if (this.dateOptionsInput === \"locale\") {\n if (!locale) {\n return dayjs(value).format(this.dateFormat);\n }\n\n return dayjs(value).locale(locale).format(this.dateFormat);\n }\n }\n\n // now manage it as an object based on date options what's marked as true\n const output: {\n format?: string;\n timestamp?: number;\n humanTime?: string;\n locale?: string;\n iso?: string;\n } = {};\n\n let dayjsObject = dayjs((value as any)?.iso || value);\n\n if (locale) {\n dayjsObject = dayjsObject.locale(locale);\n }\n\n if (this.dateOptionsInput.iso) {\n output.iso = dayjsObject.toISOString();\n }\n\n if (this.dateOptionsInput.format) {\n output.format = dayjsObject.format(this.dateFormat);\n }\n\n if (this.dateOptionsInput.timestamp) {\n output.timestamp = dayjsObject.valueOf();\n }\n\n if (this.dateOptionsInput.humanTime) {\n output.humanTime = (dayjsObject as any).fromNow();\n }\n\n if (this.dateOptionsInput.locale) {\n output.locale = dayjsObject.format(this.dateFormat);\n }\n\n if (this.dateOptionsInput.iso) {\n output.iso = dayjsObject.toISOString();\n }\n\n return output;\n }\n\n /**\n * Transform localized value\n */\n protected transformLocalized(value: LocalizedObject[] | string, locale?: string) {\n if (typeof value === \"string\") {\n return value;\n }\n\n if (!locale) {\n return value[0]?.value || value;\n }\n\n return value.find((item) => item.localeCode === locale)?.value;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-field-builder.mjs","names":[],"sources":["../../../../../../../core/src/resource/resource-field-builder.ts"],"sourcesContent":["import { isObject } from \"@mongez/supportive-is\";\nimport dayjs from \"dayjs\";\nimport relativeTime from \"dayjs/plugin/relativeTime.js\";\nimport { storage } from \"../storage\";\n\n// `humanTime` date options call dayjs(...).fromNow(), which lives in the\n// relativeTime plugin. dayjs.extend is idempotent (a plugin registers itself\n// once), so extending here at module load is safe to repeat.\ndayjs.extend(relativeTime);\nimport { uploadsUrl, url } from \"../utils/urls\";\nimport { type LocalizedObject } from \"./../utils/get-localized\";\nimport {\n type ResourceFieldBuilderDateOutputOptions,\n type ResourceOutputValueCastType,\n} from \"./types\";\n\nexport class ResourceFieldBuilder {\n /**\n * Field value\n */\n protected fieldValue?: unknown;\n\n /**\n * Whether the value is nullable\n * If set to false and value is null, it will be returned as undefined\n */\n protected isNullable = false;\n\n /**\n * Whether this field is an array\n * When true, transform() maps over each element using the base type\n */\n protected isArrayField = false;\n\n /**\n * Default value\n */\n protected defaultValue?: unknown;\n\n /**\n * Date format\n */\n protected dateFormat = \"DD-MM-YYYY hh:mm:ss A\";\n\n /**\n * Input key\n */\n protected inputKeyToUse?: string;\n\n /**\n * Add a condition before transforming the value\n */\n protected condition?: () => boolean;\n\n /**\n * Define how date fields are returned\n * If type of the date options is string then it will be returned as a string\n * otherwise it will be returned as an object contains the date options\n */\n protected dateOptionsInput: ResourceFieldBuilderDateOutputOptions = {\n format: true,\n timestamp: true,\n timezone: false,\n locale: false,\n offset: false,\n humanTime: true,\n iso: true,\n };\n\n /**\n * Constructor\n */\n public constructor(protected readonly type: ResourceOutputValueCastType) {\n //\n }\n\n /**\n * Parse a cast type string (including suffixes) into a configured builder.\n * Suffix order: [] before ? (e.g. \"string[]?\")\n * Parsing strips right-to-left: ? first, then [].\n */\n public static fromCastType(castType: string): ResourceFieldBuilder {\n let baseType = castType;\n let nullable = false;\n let isArray = false;\n\n if (baseType.endsWith(\"?\")) {\n nullable = true;\n baseType = baseType.slice(0, -1);\n }\n\n if (baseType.endsWith(\"[]\")) {\n isArray = true;\n baseType = baseType.slice(0, -2);\n }\n\n const builder = new ResourceFieldBuilder(baseType as ResourceOutputValueCastType);\n if (nullable) builder.nullable();\n if (isArray) builder.array();\n return builder;\n }\n\n /**\n * Set input key\n * Will be used in transformation if provided\n */\n public setInputKey(key: string) {\n this.inputKeyToUse = key;\n\n return this;\n }\n\n /**\n * Add a condition before transforming the value\n */\n public when(condition: () => boolean) {\n this.condition = condition;\n return this;\n }\n\n /**\n * Set whether the value is nullable\n */\n public nullable() {\n this.isNullable = true;\n return this;\n }\n\n /**\n * Mark this field as an array\n * transform() will map over each element using the base type\n */\n public array() {\n this.isArrayField = true;\n return this;\n }\n\n /**\n * Get input key\n */\n public getInputKey() {\n return this.inputKeyToUse;\n }\n\n /**\n * Set default value\n */\n public default(value: unknown) {\n this.defaultValue = value;\n\n return this;\n }\n\n /**\n * Set field format\n */\n public format(format: string) {\n this.dateFormat = format;\n\n return this;\n }\n\n /**\n * Set date options\n * This will override current date options\n */\n public dateOptions(options: ResourceFieldBuilderDateOutputOptions) {\n this.dateOptionsInput = options;\n\n return this;\n }\n\n /**\n * Transform the value.\n * When isArrayField is true, maps over each element using transformSingleValue.\n */\n public transform(value: any, locale?: string) {\n if (this.isArrayField) {\n if (!Array.isArray(value)) {\n return this.isNullable ? null : [];\n }\n\n return value\n .map((item) => this.transformSingleValue(item, locale))\n .filter((v) => v !== undefined);\n }\n\n return this.transformSingleValue(value, locale);\n }\n\n /**\n * Transform a single value according to the base type.\n */\n protected transformSingleValue(value: any, locale?: string) {\n if (value === undefined || value === null) {\n return this.isNullable ? null : this.defaultValue;\n }\n\n if (this.condition && !this.condition()) {\n return this.isNullable ? null : this.defaultValue;\n }\n\n switch (this.type) {\n case \"string\":\n return typeof value?.toHexString === \"function\" ? value.toHexString() : String(value);\n case \"number\": {\n const num = Number(value);\n return isNaN(num) ? (this.isNullable ? null : undefined) : num;\n }\n case \"boolean\":\n return Boolean(value);\n case \"float\": {\n const float = parseFloat(value);\n return isNaN(float) ? (this.isNullable ? null : undefined) : float;\n }\n\n case \"int\": {\n const int = parseInt(value);\n return isNaN(int) ? (this.isNullable ? null : undefined) : int;\n }\n\n case \"date\":\n return this.transformDate(value as string | Date, locale);\n case \"localized\":\n return this.transformLocalized(value as LocalizedObject[], locale);\n case \"url\":\n return url(value as string);\n case \"uploadsUrl\":\n return uploadsUrl(value as string);\n case \"storageUrl\":\n return storage.url(value as string);\n case \"object\":\n return isObject(value) && !Array.isArray(value) && Object.keys(value).length > 0\n ? value\n : this.isNullable\n ? null\n : undefined;\n case \"array\":\n return Array.isArray(value) ? value : this.isNullable ? null : undefined;\n }\n }\n\n /**\n * Transform date value\n */\n protected transformDate(value: string | Date, locale?: string) {\n // Bad data (\"\" or garbage) must not throw and turn the response into a 500\n if (!dayjs((value as any)?.iso || value).isValid()) {\n return this.isNullable ? null : undefined;\n }\n\n if (typeof this.dateOptionsInput === \"string\") {\n if (this.dateOptionsInput === \"format\") {\n return dayjs(value).format(this.dateFormat);\n }\n\n if (this.dateOptionsInput === \"iso\") {\n return dayjs(value).toISOString();\n }\n\n if (this.dateOptionsInput === \"timestamp\") {\n return dayjs(value).valueOf();\n }\n\n if (this.dateOptionsInput === \"humanTime\") {\n return (dayjs as any)(value).fromNow();\n }\n\n if (this.dateOptionsInput === \"locale\") {\n if (!locale) {\n return dayjs(value).format(this.dateFormat);\n }\n\n return dayjs(value).locale(locale).format(this.dateFormat);\n }\n }\n\n // now manage it as an object based on date options what's marked as true\n const output: {\n format?: string;\n timestamp?: number;\n humanTime?: string;\n locale?: string;\n iso?: string;\n } = {};\n\n let dayjsObject = dayjs((value as any)?.iso || value);\n\n if (locale) {\n dayjsObject = dayjsObject.locale(locale);\n }\n\n if (this.dateOptionsInput.iso) {\n output.iso = dayjsObject.toISOString();\n }\n\n if (this.dateOptionsInput.format) {\n output.format = dayjsObject.format(this.dateFormat);\n }\n\n if (this.dateOptionsInput.timestamp) {\n output.timestamp = dayjsObject.valueOf();\n }\n\n if (this.dateOptionsInput.humanTime) {\n output.humanTime = (dayjsObject as any).fromNow();\n }\n\n if (this.dateOptionsInput.locale) {\n output.locale = dayjsObject.format(this.dateFormat);\n }\n\n if (this.dateOptionsInput.iso) {\n output.iso = dayjsObject.toISOString();\n }\n\n return output;\n }\n\n /**\n * Transform localized value\n */\n protected transformLocalized(value: LocalizedObject[] | string, locale?: string) {\n if (typeof value === \"string\") {\n return value;\n }\n\n if (!locale) {\n return value[0]?.value || value;\n }\n\n return value.find((item) => item.localeCode === locale)?.value;\n }\n}\n"],"mappings":"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"}
|
|
@@ -170,6 +170,11 @@ declare class Resource implements ResourceContract {
|
|
|
170
170
|
* Transform a single array item according to the given schema
|
|
171
171
|
*/
|
|
172
172
|
protected transformArrayItem(item: any, schema: Record<string, ResourceFieldConfig>, locale?: string): GenericObject;
|
|
173
|
+
/**
|
|
174
|
+
* Resolve one schema field from a model or plain object. Mongo records use `_id`
|
|
175
|
+
* internally, while resources expose `id` by convention.
|
|
176
|
+
*/
|
|
177
|
+
protected resolveSourceValue(source: GenericObject | Resource | Model, outputKey: string, inputKey?: string): any;
|
|
173
178
|
/**
|
|
174
179
|
* Get a input value for the given key
|
|
175
180
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.mts","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"mappings":";;;;;;;;AAoBA;UAAiB,gBAAA;;;;EAIf,QAAA,EAAU,aAAA;EAeA;;;EAVV,IAAA,EAAM,aAAA;EA8BU;;;EAzBhB,YAAA,EAAc,aAAA;EAwCgB;;;EAnC9B,MAAA,IAAU,aAAA;EAuDkB;;;EAlD5B,SAAA,CAAU,KAAA,OAAY,IAAA,EAAM,2BAAA,EAA6B,MAAA;EA4Db;;;EAvD5C,GAAA,CAAI,GAAA,UAAa,YAAA;EApBX;;;EAyBN,GAAA,CAAI,GAAA,UAAa,KAAA,QAAa,gBAAA;EAfpB;;;EAoBV,OAAA,CAAQ,MAAA,EAAQ,MAAA,SAAe,mBAAA,IAAuB,mBAAA;EAfhC;;;EAoBtB,MAAA,CAAO,QAAA,YAAoB,oBAAA;EAfV;;;EAoBjB,IAAA,CAAK,QAAA,YAAoB,oBAAA;EAfK;;;EAoB9B,SAAA,CAAU,QAAA,YAAoB,oBAAA;EAftB;;;EAoBR,GAAA,CAAI,QAAA,YAAoB,oBAAA;EAfG;;;EAoB3B,UAAA,CAAW,QAAA,YAAoB,oBAAA;EAV/B;;;EAeA,MAAA,CAAO,QAAA,YAAoB,oBAAA;EAVvB;;;EAeJ,OAAA,CAAQ,QAAA,YAAoB,oBAAA;EAVG;;;EAe/B,KAAA,CAAM,QAAA,YAAoB,oBAAA;EAL1B;;;EAUA,GAAA,CAAI,QAAA,YAAoB,oBAAA;AAAA;;;;UAMT,mBAAA;EAAA,KACV,YAAA,EAAc,aAAA,GAAgB,QAAA,GAAW,KAAA,GAAQ,gBAAA;AAAA;AAAA,cAG3C,QAAA,YAAoB,gBAAA;EA+DL,YAAA,EAAc,aAAA,GAAgB,QAAA,GAAW,KAAA;;;;EA3D5D,QAAA,EAAU,aAAA;EAPqC;;;EAY/C,IAAA,EAAM,aAAA;EAZsB;;;;EAAA,UAkBzB,SAAA,GAAY,GAAA;EAlBgD;AAGxE;;;EAHwE,OAwBxD,MAAA,EAAQ,MAAA,SAAe,mBAAA;EA0CmB;;;;EAAA,OApC1C,YAAA,EAAc,MAAA,SAAe,mBAAA;EANN;;;;;EAAA,OAavB,eAAA,CAAgB,MAAA,EAAQ,MAAA,SAAe,mBAAA,aAAgC,MAAA,SAAe,mBAAA;EAAA;;;cA6B1E,YAAA,EAAc,aAAA,GAAgB,QAAA,GAAW,KAAA;EAAA;;;EAa5D,MAAA,IAAM,aAAA;
|
|
1
|
+
{"version":3,"file":"resource.d.mts","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"mappings":"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"}
|
|
@@ -66,8 +66,10 @@ var Resource = class Resource {
|
|
|
66
66
|
transformOutput() {
|
|
67
67
|
const localeCode = useRequestStore()?.request?.locale;
|
|
68
68
|
const parsedSchema = this.resolveParsedSchema();
|
|
69
|
+
const source = this.originalData instanceof Model ? this.originalData : this.resource;
|
|
69
70
|
for (const [outputKey, outputSettings] of Object.entries(parsedSchema)) {
|
|
70
|
-
const
|
|
71
|
+
const inputKey = outputSettings instanceof ResourceFieldBuilder ? outputSettings.getInputKey() : void 0;
|
|
72
|
+
const inputValue = this.resolveSourceValue(source, outputKey, inputKey);
|
|
71
73
|
const outputValue = this.transformValue(inputValue, outputSettings, localeCode);
|
|
72
74
|
if (outputValue !== void 0) this.set(outputKey, outputValue);
|
|
73
75
|
}
|
|
@@ -108,10 +110,8 @@ var Resource = class Resource {
|
|
|
108
110
|
outputValue = value.map((item) => new outputSettings(item).toJSON()).filter((v) => v !== void 0);
|
|
109
111
|
} else outputValue = new outputSettings(value).toJSON();
|
|
110
112
|
} else if (typeof outputSettings === "function") outputValue = outputSettings.call(this, value, this);
|
|
111
|
-
else if (outputSettings instanceof ResourceFieldBuilder)
|
|
112
|
-
|
|
113
|
-
outputValue = outputSettings.transform(inputKey ? this.get(inputKey) : value, locale);
|
|
114
|
-
} 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);
|
|
113
|
+
else if (outputSettings instanceof ResourceFieldBuilder) outputValue = outputSettings.transform(value, locale);
|
|
114
|
+
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);
|
|
115
115
|
else outputValue = this.transformArrayItem(value, outputSettings.schema, locale);
|
|
116
116
|
return outputValue;
|
|
117
117
|
}
|
|
@@ -156,13 +156,24 @@ var Resource = class Resource {
|
|
|
156
156
|
fieldKey = outputSettings[0];
|
|
157
157
|
valueTransformType = outputSettings[1];
|
|
158
158
|
}
|
|
159
|
-
const
|
|
159
|
+
const inputKey = valueTransformType instanceof ResourceFieldBuilder ? valueTransformType.getInputKey() : void 0;
|
|
160
|
+
const inputValue = this.resolveSourceValue(item, outputKey, inputKey ?? fieldKey);
|
|
160
161
|
const outputValue = this.transformValue(inputValue, valueTransformType, locale);
|
|
161
162
|
if (outputValue !== void 0) set(transformedItem, outputKey, outputValue);
|
|
162
163
|
}
|
|
163
164
|
return transformedItem;
|
|
164
165
|
}
|
|
165
166
|
/**
|
|
167
|
+
* Resolve one schema field from a model or plain object. Mongo records use `_id`
|
|
168
|
+
* internally, while resources expose `id` by convention.
|
|
169
|
+
*/
|
|
170
|
+
resolveSourceValue(source, outputKey, inputKey) {
|
|
171
|
+
const key = inputKey ?? outputKey;
|
|
172
|
+
const read = (field) => source instanceof Model ? source.get(field) : get(source, field);
|
|
173
|
+
const value = read(key);
|
|
174
|
+
return outputKey === "id" && key === "id" && value === void 0 ? read("_id") : value;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
166
177
|
* Get a input value for the given key
|
|
167
178
|
*/
|
|
168
179
|
get(key, defaultValue) {
|
|
@@ -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\n for (const [outputKey, outputSettings] of Object.entries(parsedSchema)) {\n const inputValue = this.get(outputKey);\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 const inputKey = outputSettings.getInputKey();\n outputValue = outputSettings.transform(inputKey ? this.get(inputKey) : 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 inputValue = get(item, 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 * 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 {\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.mts","names":[],"sources":["../../../../../../../core/src/router/router.ts"],"mappings":";;;;;;;cAiIa,MAAA;;AAAb;;UAIU,MAAA;EA8BqB;;;;;;;;EAAA,QApBrB,aAAA;EAiEgD;;;;EAAA,QA3DhD,2BAAA;EAiII;;;EAAA,eAxHG,QAAA;EAkJJ;;;EAAA,UA7ID,iBAAA,EAAmB,oBAAA;EAyQkC;;;EAAA,UApQrD,cAAA,EAAgB,MAAA,WAAiB,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,eAAA;EAkRJ;;;;EAAA,UA3QrD,MAAA,EAAQ,YAAA;EAgSiB;;;EAAA,OAvRrB,WAAA,IAAW,MAAA;EAAA,QAQlB,WAAA;EA6R8C;;;EAtR9C,cAAA,CAAe,QAAA,GAAW,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,eAAA;EAyS5C;;;EAhSN,aAAA,CAAc,QAAA,GAAW,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,eAAA;EAiS1C;;;EAxRP,QAAA,CAAS,IAAA,UAAc,EAAA,UAAY,YAAA;EA+cxB;;;EAtcX,SAAA,CAAU,OAAA,EAAS,oBAAA;EAucvB;;;EA9bI,IAAA,CAAK,IAAA,UAAc,QAAA,UAAkB,SAAA;EA2fvB;;;EAlfd,UAAA,CAAW,IAAA,UAAc,QAAA,UAAkB,SAAA;EAunBb;;;EA9mB9B,KAAA,CAAM,KAAA,EAAO,MAAA,kBAAwB,SAAA;
|
|
1
|
+
{"version":3,"file":"router.d.mts","names":[],"sources":["../../../../../../../core/src/router/router.ts"],"mappings":"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"}
|
package/esm/router/router.mjs
CHANGED
|
@@ -561,6 +561,7 @@ var Router = class Router {
|
|
|
561
561
|
return routeRegistry;
|
|
562
562
|
};
|
|
563
563
|
server.addHook("onRequest", async (fastifyRequest, fastifyReply) => {
|
|
564
|
+
if (fastifyRequest.routeOptions.url !== "*") return;
|
|
564
565
|
const match = resolveRouteRegistry().find(fastifyRequest.method, fastifyRequest.url);
|
|
565
566
|
if (!match) return;
|
|
566
567
|
fastifyRequest.matchedDevRoute = match;
|