@warlock.js/core 5.30.0 → 5.31.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 +142 -36
- package/README.md +28 -23
- package/bin/source-load-hook.mjs +63 -0
- package/bin/warlock.js +102 -42
- package/esm/cli/commands/add.command.mjs +4 -2
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/agents-md/agents-md-command.action.mjs +69 -0
- package/esm/cli/commands/agents-md/agents-md-command.action.mjs.map +1 -0
- package/esm/cli/commands/agents-md.command.mjs +38 -0
- package/esm/cli/commands/agents-md.command.mjs.map +1 -0
- package/esm/cli/commands/board/board-command.action.mjs +279 -0
- package/esm/cli/commands/board/board-command.action.mjs.map +1 -0
- package/esm/cli/commands/board.command.mjs +78 -0
- package/esm/cli/commands/board.command.mjs.map +1 -0
- package/esm/cli/commands/build.command.mjs +6 -1
- package/esm/cli/commands/build.command.mjs.map +1 -1
- package/esm/cli/commands/check/check-command.action.mjs +57 -0
- package/esm/cli/commands/check/check-command.action.mjs.map +1 -0
- package/esm/cli/commands/check/child-typecheck.mjs +66 -0
- package/esm/cli/commands/check/child-typecheck.mjs.map +1 -0
- package/esm/cli/commands/check/format-problems.mjs +43 -0
- package/esm/cli/commands/check/format-problems.mjs.map +1 -0
- package/esm/cli/commands/check/typecheck-command.action.mjs +61 -0
- package/esm/cli/commands/check/typecheck-command.action.mjs.map +1 -0
- package/esm/cli/commands/check.command.mjs +50 -0
- package/esm/cli/commands/check.command.mjs.map +1 -0
- package/esm/cli/commands/dev-server.command.mjs +9 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs +8 -3
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/check.types.d.mts +76 -0
- package/esm/cli/commands/doctor/check.types.d.mts.map +1 -0
- package/esm/cli/commands/doctor/checks/config.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/connectors.check.mjs +2 -0
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs +3 -0
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +4 -0
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/routes.check.mjs +3 -0
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/doctor-command.action.mjs +14 -6
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor/format-report.mjs +30 -2
- package/esm/cli/commands/doctor/format-report.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +4 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/explain/explain-command.action.mjs +64 -0
- package/esm/cli/commands/explain/explain-command.action.mjs.map +1 -0
- package/esm/cli/commands/explain.command.mjs +23 -0
- package/esm/cli/commands/explain.command.mjs.map +1 -0
- package/esm/cli/commands/generate/generate.command.mjs +124 -124
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/endpoint.generator.mjs +311 -0
- package/esm/cli/commands/generate/generators/endpoint.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/migration.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs +1 -1
- package/esm/cli/commands/generate/templates/endpoint-stubs.mjs +341 -0
- package/esm/cli/commands/generate/templates/endpoint-stubs.mjs.map +1 -0
- package/esm/cli/commands/generate/templates/stubs.mjs +19 -6
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/endpoint-schema.mjs +137 -0
- package/esm/cli/commands/generate/utils/endpoint-schema.mjs.map +1 -0
- package/esm/cli/commands/generate/utils/name-parser.mjs +1 -1
- package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/writer.mjs +38 -13
- package/esm/cli/commands/generate/utils/writer.mjs.map +1 -1
- package/esm/cli/commands/inspect/inspect-command.action.mjs +72 -0
- package/esm/cli/commands/inspect/inspect-command.action.mjs.map +1 -0
- package/esm/cli/commands/inspect.command.mjs +39 -0
- package/esm/cli/commands/inspect.command.mjs.map +1 -0
- package/esm/cli/commands/mcp/mcp-command.action.mjs +103 -0
- package/esm/cli/commands/mcp/mcp-command.action.mjs.map +1 -0
- package/esm/cli/commands/mcp.command.mjs +76 -0
- package/esm/cli/commands/mcp.command.mjs.map +1 -0
- package/esm/cli/commands/openapi-generator.action.mjs.map +1 -1
- package/esm/cli/commands/openapi-generator.command.mjs.map +1 -1
- package/esm/cli/commands/postman-generator.action.mjs.map +1 -1
- package/esm/cli/commands/postman-generator.command.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-diff.action.mjs +1 -1
- package/esm/cli/commands/test.command.mjs +131 -0
- package/esm/cli/commands/test.command.mjs.map +1 -0
- package/esm/cli/commands/trace/trace-command.action.mjs +145 -0
- package/esm/cli/commands/trace/trace-command.action.mjs.map +1 -0
- package/esm/cli/commands/trace.command.mjs +60 -0
- package/esm/cli/commands/trace.command.mjs.map +1 -0
- package/esm/cli/commands/typecheck.command.mjs +40 -0
- package/esm/cli/commands/typecheck.command.mjs.map +1 -0
- package/esm/cli/commands/upgrade.command.mjs +46 -0
- package/esm/cli/commands/upgrade.command.mjs.map +1 -0
- package/esm/cli/flush-and-exit.mjs +34 -0
- package/esm/cli/flush-and-exit.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +23 -1
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/cli/parse-cli-args.mjs +1 -0
- package/esm/cli/parse-cli-args.mjs.map +1 -1
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs +34 -13
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-schema.d.mts +67 -0
- package/esm/config/config-schema.d.mts.map +1 -0
- package/esm/config/config-schema.mjs +77 -0
- package/esm/config/config-schema.mjs.map +1 -0
- package/esm/config/index.d.mts +6 -0
- package/esm/config/index.mjs +1 -0
- package/esm/connectors/assert-no-reserved-connector-names.d.mts.map +1 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs +2 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
- package/esm/connectors/assert-unique-connector-names.d.mts.map +1 -1
- package/esm/connectors/assert-unique-connector-names.mjs +26 -2
- package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +3 -2
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/single-server-warnings.mjs.map +1 -1
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/container/index.d.mts.map +1 -1
- package/esm/container/index.mjs.map +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.d.mts +0 -28
- package/esm/database/models/database-log/database-log.d.mts.map +1 -1
- package/esm/database/seeds/seeder.errors.d.mts +4 -0
- package/esm/database/seeds/seeder.errors.d.mts.map +1 -1
- package/esm/database/seeds/seeder.errors.mjs +2 -0
- package/esm/database/seeds/seeder.errors.mjs.map +1 -1
- package/esm/dev-server/boot-precondition-error.mjs +1 -0
- package/esm/dev-server/boot-precondition-error.mjs.map +1 -1
- package/esm/dev-server/create-worker.mjs +15 -9
- package/esm/dev-server/create-worker.mjs.map +1 -1
- package/esm/dev-server/dev-agent-tooling.mjs +135 -0
- package/esm/dev-server/dev-agent-tooling.mjs.map +1 -0
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +8 -0
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +4 -5
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +1 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +2 -3
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +33 -0
- package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs.map +1 -0
- package/esm/dev-server/health-checker/default-health-checkers.mjs +31 -0
- package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -0
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +9 -3
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs +15 -4
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
- package/esm/dev-server/health-checker/oxlint-runner.mjs +302 -0
- package/esm/dev-server/health-checker/oxlint-runner.mjs.map +1 -0
- package/esm/dev-server/health-checker/workers/oxlint-health.worker.d.mts +1 -0
- package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs +42 -0
- package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs.map +1 -0
- package/esm/dev-server/loader/loader-port-acks.mjs +1 -1
- package/esm/dev-server/loader/loader-port-acks.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs +1 -1
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/loader/resolve-hook.mjs +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs +1 -1
- package/esm/dev-server/model-resource-typings.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs +1 -0
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/route-types-publisher.mjs +1 -1
- package/esm/dev-server/start-development-server.mjs.map +1 -1
- package/esm/dev-server/translation-keys-sources.mjs +1 -1
- package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +3 -3
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/types.d.mts.map +1 -1
- package/esm/errors/container-key-missing-error.mjs +1 -0
- package/esm/errors/container-key-missing-error.mjs.map +1 -1
- package/esm/errors/error-codes.d.mts +653 -0
- package/esm/errors/error-codes.d.mts.map +1 -0
- package/esm/errors/error-codes.mjs +634 -0
- package/esm/errors/error-codes.mjs.map +1 -0
- package/esm/errors/esbuild-binary-missing-error.mjs +1 -0
- package/esm/errors/esbuild-binary-missing-error.mjs.map +1 -1
- package/esm/errors/index.d.mts +3 -1
- package/esm/errors/index.mjs +3 -1
- package/esm/errors/unknown-locale-error.mjs +1 -0
- package/esm/errors/unknown-locale-error.mjs.map +1 -1
- package/esm/errors/warlock-error.d.mts +23 -0
- package/esm/errors/warlock-error.d.mts.map +1 -0
- package/esm/errors/warlock-error.mjs +22 -0
- package/esm/errors/warlock-error.mjs.map +1 -0
- package/esm/generations/add-command.action.mjs +19 -3
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +1 -1
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/bull-board.feature.mjs +1 -1
- package/esm/generations/features/bull-board.feature.mjs.map +1 -1
- package/esm/generations/features/mysql.feature.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs +5 -2
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/queue.feature.mjs +1 -1
- package/esm/generations/features/queue.feature.mjs.map +1 -1
- package/esm/generations/features/react-email.feature.mjs +1 -1
- package/esm/generations/features/react-email.feature.mjs.map +1 -1
- package/esm/generations/features/shadcn.feature.mjs +1 -1
- package/esm/generations/features/shadcn.feature.mjs.map +1 -1
- package/esm/generations/features/shared/link-ai-package-import.mjs +1 -1
- package/esm/generations/features/shared/link-ai-package-import.mjs.map +1 -1
- package/esm/generations/features/shared/merge-web-sitemap-config.mjs.map +1 -1
- package/esm/generations/features/shared/patch-tsconfig-include.mjs +1 -1
- package/esm/generations/features/shared/patch-tsconfig-include.mjs.map +1 -1
- package/esm/generations/features/shared/relocate-conflicting-home-route.mjs +1 -1
- package/esm/generations/features/shared/relocate-conflicting-home-route.mjs.map +1 -1
- package/esm/generations/features/sitemap.feature.mjs +1 -1
- package/esm/generations/features/sitemap.feature.mjs.map +1 -1
- package/esm/generations/features/tailwind.feature.mjs +1 -1
- package/esm/generations/features/tailwind.feature.mjs.map +1 -1
- package/esm/generations/features/test.feature.mjs +11 -6
- package/esm/generations/features/test.feature.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +1 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/generation-fs.mjs +73 -0
- package/esm/generations/generation-fs.mjs.map +1 -0
- package/esm/generations/generation-run.mjs +185 -0
- package/esm/generations/generation-run.mjs.map +1 -0
- package/esm/generations/stubs.mjs +155 -13
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/bounded-stream-queue.d.mts +10 -0
- package/esm/http/bounded-stream-queue.d.mts.map +1 -0
- package/esm/http/bounded-stream-queue.mjs +97 -0
- package/esm/http/bounded-stream-queue.mjs.map +1 -0
- package/esm/http/build-rate-limit-options.d.mts +13 -0
- package/esm/http/build-rate-limit-options.d.mts.map +1 -1
- package/esm/http/build-rate-limit-options.mjs.map +1 -1
- package/esm/http/conditional-request.d.mts +86 -0
- package/esm/http/conditional-request.d.mts.map +1 -0
- package/esm/http/conditional-request.mjs +121 -0
- package/esm/http/conditional-request.mjs.map +1 -0
- package/esm/http/csp.d.mts +2 -0
- package/esm/http/csp.d.mts.map +1 -1
- package/esm/http/csp.mjs +1 -0
- package/esm/http/csp.mjs.map +1 -1
- package/esm/http/csrf-origin-policy.mjs.map +1 -1
- package/esm/http/database/RequestLog.d.mts +24 -84
- package/esm/http/database/RequestLog.d.mts.map +1 -1
- package/esm/http/errors/errors.d.mts +2 -0
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +1 -0
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/escape-header-value.d.mts +18 -0
- package/esm/http/escape-header-value.d.mts.map +1 -0
- package/esm/http/escape-header-value.mjs +27 -0
- package/esm/http/escape-header-value.mjs.map +1 -0
- package/esm/http/flush-pending-cookies.mjs.map +1 -1
- package/esm/http/index.d.mts +5 -2
- package/esm/http/index.mjs +4 -1
- package/esm/http/lazy-static.mjs +194 -0
- package/esm/http/lazy-static.mjs.map +1 -0
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/plugins.d.mts.map +1 -1
- package/esm/http/plugins.mjs +7 -4
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/rate-limit-cache-store.mjs +2 -1
- package/esm/http/rate-limit-cache-store.mjs.map +1 -1
- package/esm/http/request-log-snapshot.d.mts +12 -5
- package/esm/http/request-log-snapshot.d.mts.map +1 -1
- package/esm/http/request-log-snapshot.mjs +83 -4
- package/esm/http/request-log-snapshot.mjs.map +1 -1
- package/esm/http/request.d.mts +2 -2
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +5 -4
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +11 -1
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +164 -76
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/safe-redirect.d.mts +51 -0
- package/esm/http/safe-redirect.d.mts.map +1 -0
- package/esm/http/safe-redirect.mjs +80 -0
- package/esm/http/safe-redirect.mjs.map +1 -0
- package/esm/http/serialized.d.mts.map +1 -1
- package/esm/http/server.d.mts +3 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs +14 -4
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/stream-transport.mjs +4 -2
- package/esm/http/stream-transport.mjs.map +1 -1
- package/esm/http/types.d.mts +44 -11
- package/esm/http/types.d.mts.map +1 -1
- package/esm/http/uploads/generate-image-variant.mjs +1 -1
- package/esm/http/uploads/generate-image-variants.d.mts +10 -7
- package/esm/http/uploads/generate-image-variants.d.mts.map +1 -1
- package/esm/http/uploads/generate-image-variants.mjs +61 -24
- package/esm/http/uploads/generate-image-variants.mjs.map +1 -1
- package/esm/http/uploads/image-variant-cache-entries.mjs +78 -0
- package/esm/http/uploads/image-variant-cache-entries.mjs.map +1 -0
- package/esm/http/uploads/image-variant-cache.mjs +72 -30
- package/esm/http/uploads/image-variant-cache.mjs.map +1 -1
- package/esm/http/uploads/image-variant-types.d.mts +2 -0
- package/esm/http/uploads/image-variant-types.d.mts.map +1 -1
- package/esm/http/uploads/image-variants-config-error.d.mts +3 -1
- package/esm/http/uploads/image-variants-config-error.d.mts.map +1 -1
- package/esm/http/uploads/image-variants-config-error.mjs +3 -2
- package/esm/http/uploads/image-variants-config-error.mjs.map +1 -1
- package/esm/http/uploads/load-variant-source.mjs +1 -1
- package/esm/http/uploads/load-variant-source.mjs.map +1 -1
- package/esm/http/uploads/purge-image-variants.d.mts +7 -2
- package/esm/http/uploads/purge-image-variants.d.mts.map +1 -1
- package/esm/http/uploads/purge-image-variants.mjs +16 -16
- package/esm/http/uploads/purge-image-variants.mjs.map +1 -1
- package/esm/http/uploads/remote-variant-source.mjs +115 -0
- package/esm/http/uploads/remote-variant-source.mjs.map +1 -0
- package/esm/http/uploads/resolve-image-variant.d.mts +3 -4
- package/esm/http/uploads/resolve-image-variant.d.mts.map +1 -1
- package/esm/http/uploads/resolve-image-variant.mjs +10 -18
- package/esm/http/uploads/resolve-image-variant.mjs.map +1 -1
- package/esm/http/uploads/resolve-image-variants-config.mjs +5 -3
- package/esm/http/uploads/resolve-image-variants-config.mjs.map +1 -1
- package/esm/http/uploads/resolve-remote-image-variant.mjs +89 -0
- package/esm/http/uploads/resolve-remote-image-variant.mjs.map +1 -0
- package/esm/http/uploads/trim-image-variant-cache.mjs +193 -0
- package/esm/http/uploads/trim-image-variant-cache.mjs.map +1 -0
- package/esm/http/uploads/uploaded-file.controller.d.mts +5 -3
- package/esm/http/uploads/uploaded-file.controller.d.mts.map +1 -1
- package/esm/http/uploads/uploaded-file.controller.mjs +16 -8
- package/esm/http/uploads/uploaded-file.controller.mjs.map +1 -1
- package/esm/http/uploads/variant-cache-key.mjs +1 -1
- package/esm/http/uploads-types.d.mts +6 -2
- package/esm/http/uploads-types.d.mts.map +1 -1
- package/esm/index.d.mts +17 -6
- package/esm/index.mjs +14 -3
- package/esm/localization/parse-locale-dictionary.d.mts +2 -0
- package/esm/localization/parse-locale-dictionary.d.mts.map +1 -1
- package/esm/localization/parse-locale-dictionary.mjs +1 -0
- package/esm/localization/parse-locale-dictionary.mjs.map +1 -1
- package/esm/mail/config.d.mts +12 -1
- package/esm/mail/config.d.mts.map +1 -1
- package/esm/mail/config.mjs +16 -1
- package/esm/mail/config.mjs.map +1 -1
- package/esm/mail/index.d.mts +1 -1
- package/esm/mail/index.mjs +1 -1
- package/esm/net/index.d.mts +2 -0
- package/esm/net/index.mjs +3 -0
- package/esm/openapi/build-openapi-document.d.mts.map +1 -1
- package/esm/openapi/build-openapi-document.mjs +8 -5
- package/esm/openapi/build-openapi-document.mjs.map +1 -1
- package/esm/openapi/error-responses.mjs +28 -2
- package/esm/openapi/error-responses.mjs.map +1 -1
- package/esm/openapi/postman/build-request-body.mjs.map +1 -1
- package/esm/openapi/postman/build-request-url.mjs.map +1 -1
- package/esm/openapi/postman/build-saved-responses.mjs.map +1 -1
- package/esm/openapi/postman/example-from-schema.d.mts.map +1 -1
- package/esm/openapi/postman/example-from-schema.mjs.map +1 -1
- package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -1
- package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -1
- package/esm/openapi/postman/schema-refs.mjs.map +1 -1
- package/esm/openapi/postman/uuid-v5.mjs.map +1 -1
- package/esm/openapi/request-schemas.mjs.map +1 -1
- package/esm/openapi/response-schemas.mjs.map +1 -1
- package/esm/openapi/schema-registry.d.mts +1 -0
- package/esm/openapi/schema-registry.mjs.map +1 -1
- package/esm/openapi/security.mjs.map +1 -1
- package/esm/openapi/types.d.mts +3 -2
- package/esm/openapi/types.d.mts.map +1 -1
- package/esm/production/assert-generated-imports.mjs +1 -0
- package/esm/production/assert-generated-imports.mjs.map +1 -1
- package/esm/production/production-builder.mjs +12 -4
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs +1 -0
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/repositories/contracts/types.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/resource/types.d.mts.map +1 -1
- package/esm/restful/restful.d.mts +5 -5
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/build-route-rate-limit.mjs.map +1 -1
- package/esm/router/dev-dispatch.mjs +31 -12
- package/esm/router/dev-dispatch.mjs.map +1 -1
- package/esm/router/index.d.mts +4 -2
- package/esm/router/index.mjs +2 -0
- package/esm/router/log-request-lifecycle.mjs.map +1 -1
- package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
- package/esm/router/resolve-route-rate-limit.mjs +47 -0
- package/esm/router/resolve-route-rate-limit.mjs.map +1 -0
- package/esm/router/resolve-route-validation.mjs +19 -0
- package/esm/router/resolve-route-validation.mjs.map +1 -0
- package/esm/router/route-guard-names.mjs +30 -0
- package/esm/router/route-guard-names.mjs.map +1 -0
- package/esm/router/route-name-taken-error.d.mts +14 -0
- package/esm/router/route-name-taken-error.d.mts.map +1 -0
- package/esm/router/route-name-taken-error.mjs +18 -0
- package/esm/router/route-name-taken-error.mjs.map +1 -0
- package/esm/router/router.d.mts +32 -13
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +71 -38
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +137 -17
- package/esm/router/types.d.mts.map +1 -1
- package/esm/router/unguarded-route-diagnostics.d.mts +40 -0
- package/esm/router/unguarded-route-diagnostics.d.mts.map +1 -0
- package/esm/router/unguarded-route-diagnostics.mjs +95 -0
- package/esm/router/unguarded-route-diagnostics.mjs.map +1 -0
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/storage/drivers/local-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.mjs.map +1 -1
- package/esm/storage/local-disk-roots.mjs +24 -0
- package/esm/storage/local-disk-roots.mjs.map +1 -0
- package/esm/storage/storage.d.mts +14 -0
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/storage.mjs +22 -0
- package/esm/storage/storage.mjs.map +1 -1
- package/esm/storage/types.d.mts.map +1 -1
- package/esm/storage/utils/storage-not-initialized-error.d.mts +2 -0
- package/esm/storage/utils/storage-not-initialized-error.d.mts.map +1 -1
- package/esm/storage/utils/storage-not-initialized-error.mjs +1 -0
- package/esm/storage/utils/storage-not-initialized-error.mjs.map +1 -1
- package/esm/tests/authenticated-test-client.d.mts +32 -0
- package/esm/tests/authenticated-test-client.d.mts.map +1 -0
- package/esm/tests/authenticated-test-client.mjs +40 -0
- package/esm/tests/authenticated-test-client.mjs.map +1 -0
- package/esm/tests/factories.d.mts +59 -0
- package/esm/tests/factories.d.mts.map +1 -0
- package/esm/tests/factories.mjs +105 -0
- package/esm/tests/factories.mjs.map +1 -0
- package/esm/tests/fakes/cache-fake.d.mts +29 -0
- package/esm/tests/fakes/cache-fake.d.mts.map +1 -0
- package/esm/tests/fakes/cache-fake.mjs +79 -0
- package/esm/tests/fakes/cache-fake.mjs.map +1 -0
- package/esm/tests/fakes/clock-fake.d.mts +32 -0
- package/esm/tests/fakes/clock-fake.d.mts.map +1 -0
- package/esm/tests/fakes/clock-fake.mjs +69 -0
- package/esm/tests/fakes/clock-fake.mjs.map +1 -0
- package/esm/tests/fakes/fake-assertion-error.d.mts +13 -0
- package/esm/tests/fakes/fake-assertion-error.d.mts.map +1 -0
- package/esm/tests/fakes/fake-assertion-error.mjs +20 -0
- package/esm/tests/fakes/fake-assertion-error.mjs.map +1 -0
- package/esm/tests/fakes/ids-fake.d.mts +22 -0
- package/esm/tests/fakes/ids-fake.d.mts.map +1 -0
- package/esm/tests/fakes/ids-fake.mjs +57 -0
- package/esm/tests/fakes/ids-fake.mjs.map +1 -0
- package/esm/tests/fakes/index.d.mts +8 -0
- package/esm/tests/fakes/index.mjs +10 -0
- package/esm/tests/fakes/mail-fake.d.mts +41 -0
- package/esm/tests/fakes/mail-fake.d.mts.map +1 -0
- package/esm/tests/fakes/mail-fake.mjs +111 -0
- package/esm/tests/fakes/mail-fake.mjs.map +1 -0
- package/esm/tests/fakes/queue-fake.d.mts +48 -0
- package/esm/tests/fakes/queue-fake.d.mts.map +1 -0
- package/esm/tests/fakes/queue-fake.mjs +134 -0
- package/esm/tests/fakes/queue-fake.mjs.map +1 -0
- package/esm/tests/fakes/storage-fake.d.mts +31 -0
- package/esm/tests/fakes/storage-fake.d.mts.map +1 -0
- package/esm/tests/fakes/storage-fake.mjs +122 -0
- package/esm/tests/fakes/storage-fake.mjs.map +1 -0
- package/esm/tests/fakes/test-fakes.d.mts +39 -0
- package/esm/tests/fakes/test-fakes.d.mts.map +1 -0
- package/esm/tests/fakes/test-fakes.mjs +90 -0
- package/esm/tests/fakes/test-fakes.mjs.map +1 -0
- package/esm/tests/index.d.mts +14 -2
- package/esm/tests/index.mjs +15 -2
- package/esm/tests/snapshot-response.d.mts +34 -0
- package/esm/tests/snapshot-response.d.mts.map +1 -0
- package/esm/tests/snapshot-response.mjs +42 -0
- package/esm/tests/snapshot-response.mjs.map +1 -0
- package/esm/tests/start-http-development-server.d.mts +22 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +36 -3
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts +20 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-helpers.mjs +40 -27
- package/esm/tests/test-helpers.mjs.map +1 -1
- package/esm/tests/test-http-server.d.mts +62 -0
- package/esm/tests/test-http-server.d.mts.map +1 -0
- package/esm/tests/test-http-server.mjs +119 -0
- package/esm/tests/test-http-server.mjs.map +1 -0
- package/esm/tests/vitest-setup.d.mts +11 -0
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +12 -4
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/tooling/agents-md/agents-md-markers.d.mts +27 -0
- package/esm/tooling/agents-md/agents-md-markers.d.mts.map +1 -0
- package/esm/tooling/agents-md/agents-md-markers.mjs +59 -0
- package/esm/tooling/agents-md/agents-md-markers.mjs.map +1 -0
- package/esm/tooling/agents-md/agents-md-refresher.d.mts +36 -0
- package/esm/tooling/agents-md/agents-md-refresher.d.mts.map +1 -0
- package/esm/tooling/agents-md/agents-md-refresher.mjs +72 -0
- package/esm/tooling/agents-md/agents-md-refresher.mjs.map +1 -0
- package/esm/tooling/agents-md/index.mjs +7 -0
- package/esm/tooling/agents-md/installed-skills.d.mts +30 -0
- package/esm/tooling/agents-md/installed-skills.d.mts.map +1 -0
- package/esm/tooling/agents-md/installed-skills.mjs +74 -0
- package/esm/tooling/agents-md/installed-skills.mjs.map +1 -0
- package/esm/tooling/agents-md/render-agents-md.d.mts +33 -0
- package/esm/tooling/agents-md/render-agents-md.d.mts.map +1 -0
- package/esm/tooling/agents-md/render-agents-md.mjs +195 -0
- package/esm/tooling/agents-md/render-agents-md.mjs.map +1 -0
- package/esm/tooling/agents-md/write-agents-md.d.mts +33 -0
- package/esm/tooling/agents-md/write-agents-md.d.mts.map +1 -0
- package/esm/tooling/agents-md/write-agents-md.mjs +49 -0
- package/esm/tooling/agents-md/write-agents-md.mjs.map +1 -0
- package/esm/tooling/board/board-message.d.mts +31 -0
- package/esm/tooling/board/board-message.d.mts.map +1 -0
- package/esm/tooling/board/board-message.mjs +86 -0
- package/esm/tooling/board/board-message.mjs.map +1 -0
- package/esm/tooling/board/board.d.mts +109 -0
- package/esm/tooling/board/board.d.mts.map +1 -0
- package/esm/tooling/board/board.mjs +406 -0
- package/esm/tooling/board/board.mjs.map +1 -0
- package/esm/tooling/board/board.types.d.mts +72 -0
- package/esm/tooling/board/board.types.d.mts.map +1 -0
- package/esm/tooling/board/board.types.mjs +54 -0
- package/esm/tooling/board/board.types.mjs.map +1 -0
- package/esm/tooling/check/changed-files.d.mts +33 -0
- package/esm/tooling/check/changed-files.d.mts.map +1 -0
- package/esm/tooling/check/changed-files.mjs +73 -0
- package/esm/tooling/check/changed-files.mjs.map +1 -0
- package/esm/tooling/check/check-report.types.d.mts +62 -0
- package/esm/tooling/check/check-report.types.d.mts.map +1 -0
- package/esm/tooling/check/check-report.types.mjs +37 -0
- package/esm/tooling/check/check-report.types.mjs.map +1 -0
- package/esm/tooling/check/code-style-conventions.mjs +106 -0
- package/esm/tooling/check/code-style-conventions.mjs.map +1 -0
- package/esm/tooling/check/conventions-step.d.mts +34 -0
- package/esm/tooling/check/conventions-step.d.mts.map +1 -0
- package/esm/tooling/check/conventions-step.mjs +184 -0
- package/esm/tooling/check/conventions-step.mjs.map +1 -0
- package/esm/tooling/check/environment-step.d.mts +18 -0
- package/esm/tooling/check/environment-step.d.mts.map +1 -0
- package/esm/tooling/check/environment-step.mjs +78 -0
- package/esm/tooling/check/environment-step.mjs.map +1 -0
- package/esm/tooling/check/index.d.mts +8 -0
- package/esm/tooling/check/index.mjs +10 -0
- package/esm/tooling/check/lint-step.d.mts +19 -0
- package/esm/tooling/check/lint-step.d.mts.map +1 -0
- package/esm/tooling/check/lint-step.mjs +111 -0
- package/esm/tooling/check/lint-step.mjs.map +1 -0
- package/esm/tooling/check/request-input.mjs +132 -0
- package/esm/tooling/check/request-input.mjs.map +1 -0
- package/esm/tooling/check/run-check.d.mts +35 -0
- package/esm/tooling/check/run-check.d.mts.map +1 -0
- package/esm/tooling/check/run-check.mjs +192 -0
- package/esm/tooling/check/run-check.mjs.map +1 -0
- package/esm/tooling/check/run-tests.d.mts +48 -0
- package/esm/tooling/check/run-tests.d.mts.map +1 -0
- package/esm/tooling/check/run-tests.mjs +93 -0
- package/esm/tooling/check/run-tests.mjs.map +1 -0
- package/esm/tooling/check/security-conventions.mjs +648 -0
- package/esm/tooling/check/security-conventions.mjs.map +1 -0
- package/esm/tooling/check/source-ast.mjs +195 -0
- package/esm/tooling/check/source-ast.mjs.map +1 -0
- package/esm/tooling/check/source-files.mjs +27 -0
- package/esm/tooling/check/source-files.mjs.map +1 -0
- package/esm/tooling/check/tests-step.d.mts +34 -0
- package/esm/tooling/check/tests-step.d.mts.map +1 -0
- package/esm/tooling/check/tests-step.mjs +177 -0
- package/esm/tooling/check/tests-step.mjs.map +1 -0
- package/esm/tooling/dev-state/dev-server-state.d.mts +39 -0
- package/esm/tooling/dev-state/dev-server-state.d.mts.map +1 -0
- package/esm/tooling/dev-state/dev-server-state.mjs +67 -0
- package/esm/tooling/dev-state/dev-server-state.mjs.map +1 -0
- package/esm/tooling/devtools/devtools-client.d.mts +91 -0
- package/esm/tooling/devtools/devtools-client.d.mts.map +1 -0
- package/esm/tooling/devtools/devtools-client.mjs +155 -0
- package/esm/tooling/devtools/devtools-client.mjs.map +1 -0
- package/esm/tooling/generate/generation.d.mts +78 -0
- package/esm/tooling/generate/generation.d.mts.map +1 -0
- package/esm/tooling/generate/generation.mjs +123 -0
- package/esm/tooling/generate/generation.mjs.map +1 -0
- package/esm/tooling/generate/generator-spec.d.mts +57 -0
- package/esm/tooling/generate/generator-spec.d.mts.map +1 -0
- package/esm/tooling/generate/generator-spec.mjs +102 -0
- package/esm/tooling/generate/generator-spec.mjs.map +1 -0
- package/esm/tooling/generate/index.mjs +6 -0
- package/esm/tooling/generate/project-files.mjs +121 -0
- package/esm/tooling/generate/project-files.mjs.map +1 -0
- package/esm/tooling/generate/run-cli.d.mts +40 -0
- package/esm/tooling/generate/run-cli.d.mts.map +1 -0
- package/esm/tooling/generate/run-cli.mjs +93 -0
- package/esm/tooling/generate/run-cli.mjs.map +1 -0
- package/esm/tooling/generate/unified-diff.d.mts +37 -0
- package/esm/tooling/generate/unified-diff.d.mts.map +1 -0
- package/esm/tooling/generate/unified-diff.mjs +161 -0
- package/esm/tooling/generate/unified-diff.mjs.map +1 -0
- package/esm/tooling/index.d.mts +41 -0
- package/esm/tooling/index.mjs +45 -0
- package/esm/tooling/inspect/boot-for-inspect.d.mts +23 -0
- package/esm/tooling/inspect/boot-for-inspect.d.mts.map +1 -0
- package/esm/tooling/inspect/boot-for-inspect.mjs +63 -0
- package/esm/tooling/inspect/boot-for-inspect.mjs.map +1 -0
- package/esm/tooling/inspect/collect-config.mjs +77 -0
- package/esm/tooling/inspect/collect-config.mjs.map +1 -0
- package/esm/tooling/inspect/collect-models.d.mts +9 -0
- package/esm/tooling/inspect/collect-models.d.mts.map +1 -0
- package/esm/tooling/inspect/collect-models.mjs +154 -0
- package/esm/tooling/inspect/collect-models.mjs.map +1 -0
- package/esm/tooling/inspect/collect-packages.d.mts +10 -0
- package/esm/tooling/inspect/collect-packages.d.mts.map +1 -0
- package/esm/tooling/inspect/collect-packages.mjs +157 -0
- package/esm/tooling/inspect/collect-packages.mjs.map +1 -0
- package/esm/tooling/inspect/collect-pages.mjs +108 -0
- package/esm/tooling/inspect/collect-pages.mjs.map +1 -0
- package/esm/tooling/inspect/collect-routes.mjs +168 -0
- package/esm/tooling/inspect/collect-routes.mjs.map +1 -0
- package/esm/tooling/inspect/event-recorder.d.mts +37 -0
- package/esm/tooling/inspect/event-recorder.d.mts.map +1 -0
- package/esm/tooling/inspect/event-recorder.mjs +91 -0
- package/esm/tooling/inspect/event-recorder.mjs.map +1 -0
- package/esm/tooling/inspect/index.d.mts +7 -0
- package/esm/tooling/inspect/index.mjs +7 -0
- package/esm/tooling/inspect/inspect-app.d.mts +53 -0
- package/esm/tooling/inspect/inspect-app.d.mts.map +1 -0
- package/esm/tooling/inspect/inspect-app.mjs +92 -0
- package/esm/tooling/inspect/inspect-app.mjs.map +1 -0
- package/esm/tooling/inspect/inspect-report.schema.d.mts +15 -0
- package/esm/tooling/inspect/inspect-report.schema.d.mts.map +1 -0
- package/esm/tooling/inspect/inspect-report.schema.mjs +463 -0
- package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -0
- package/esm/tooling/inspect/inspect-report.types.d.mts +196 -0
- package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -0
- package/esm/tooling/inspect/inspect-report.types.mjs +33 -0
- package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -0
- package/esm/tooling/mcp/create-mcp-context.d.mts +26 -0
- package/esm/tooling/mcp/create-mcp-context.d.mts.map +1 -0
- package/esm/tooling/mcp/create-mcp-context.mjs +75 -0
- package/esm/tooling/mcp/create-mcp-context.mjs.map +1 -0
- package/esm/tooling/mcp/db-schema.d.mts +49 -0
- package/esm/tooling/mcp/db-schema.d.mts.map +1 -0
- package/esm/tooling/mcp/db-schema.mjs +56 -0
- package/esm/tooling/mcp/db-schema.mjs.map +1 -0
- package/esm/tooling/mcp/docs-search.d.mts +72 -0
- package/esm/tooling/mcp/docs-search.d.mts.map +1 -0
- package/esm/tooling/mcp/docs-search.mjs +178 -0
- package/esm/tooling/mcp/docs-search.mjs.map +1 -0
- package/esm/tooling/mcp/index.mjs +9 -0
- package/esm/tooling/mcp/load-ai-tools.d.mts +62 -0
- package/esm/tooling/mcp/load-ai-tools.d.mts.map +1 -0
- package/esm/tooling/mcp/load-ai-tools.mjs +44 -0
- package/esm/tooling/mcp/load-ai-tools.mjs.map +1 -0
- package/esm/tooling/mcp/mcp-tools.d.mts +60 -0
- package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -0
- package/esm/tooling/mcp/mcp-tools.mjs +513 -0
- package/esm/tooling/mcp/mcp-tools.mjs.map +1 -0
- package/esm/tooling/mcp/start-mcp-server.d.mts +56 -0
- package/esm/tooling/mcp/start-mcp-server.d.mts.map +1 -0
- package/esm/tooling/mcp/start-mcp-server.mjs +126 -0
- package/esm/tooling/mcp/start-mcp-server.mjs.map +1 -0
- package/esm/tooling/mcp/tool-input.d.mts +75 -0
- package/esm/tooling/mcp/tool-input.d.mts.map +1 -0
- package/esm/tooling/mcp/tool-input.mjs +72 -0
- package/esm/tooling/mcp/tool-input.mjs.map +1 -0
- package/esm/tooling/problem.d.mts +45 -0
- package/esm/tooling/problem.d.mts.map +1 -0
- package/esm/tooling/problem.mjs +28 -0
- package/esm/tooling/problem.mjs.map +1 -0
- package/esm/tooling/trace/trace-report.types.d.mts +71 -0
- package/esm/tooling/trace/trace-report.types.d.mts.map +1 -0
- package/esm/tooling/trace/trace-report.types.mjs +7 -0
- package/esm/tooling/trace/trace-report.types.mjs.map +1 -0
- package/esm/tooling/trace/trace-request.d.mts +83 -0
- package/esm/tooling/trace/trace-request.d.mts.map +1 -0
- package/esm/tooling/trace/trace-request.mjs +241 -0
- package/esm/tooling/trace/trace-request.mjs.map +1 -0
- package/esm/tooling/typecheck/index.d.mts +1 -0
- package/esm/tooling/typecheck/index.mjs +3 -0
- package/esm/tooling/typecheck/run-typecheck.d.mts +59 -0
- package/esm/tooling/typecheck/run-typecheck.d.mts.map +1 -0
- package/esm/tooling/typecheck/run-typecheck.mjs +153 -0
- package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -0
- package/esm/upgrade/codemods/head-viewport.mjs +46 -0
- package/esm/upgrade/codemods/head-viewport.mjs.map +1 -0
- package/esm/upgrade/codemods/helpers.mjs +81 -0
- package/esm/upgrade/codemods/helpers.mjs.map +1 -0
- package/esm/upgrade/codemods/index.mjs +34 -0
- package/esm/upgrade/codemods/index.mjs.map +1 -0
- package/esm/upgrade/codemods/logger-sync-calls.mjs +57 -0
- package/esm/upgrade/codemods/logger-sync-calls.mjs.map +1 -0
- package/esm/upgrade/codemods/package-json.mjs +122 -0
- package/esm/upgrade/codemods/package-json.mjs.map +1 -0
- package/esm/upgrade/codemods/request-log-snapshot.mjs +82 -0
- package/esm/upgrade/codemods/request-log-snapshot.mjs.map +1 -0
- package/esm/upgrade/codemods/review-sites.mjs +262 -0
- package/esm/upgrade/codemods/review-sites.mjs.map +1 -0
- package/esm/upgrade/diff.mjs +119 -0
- package/esm/upgrade/diff.mjs.map +1 -0
- package/esm/upgrade/format-upgrade-report.mjs +32 -0
- package/esm/upgrade/format-upgrade-report.mjs.map +1 -0
- package/esm/upgrade/load-ts-morph.mjs +48 -0
- package/esm/upgrade/load-ts-morph.mjs.map +1 -0
- package/esm/upgrade/notes.mjs +16 -0
- package/esm/upgrade/notes.mjs.map +1 -0
- package/esm/upgrade/run-upgrade.mjs +246 -0
- package/esm/upgrade/run-upgrade.mjs.map +1 -0
- package/esm/upgrade/upgrade-command.action.mjs +37 -0
- package/esm/upgrade/upgrade-command.action.mjs.map +1 -0
- package/esm/upgrade/version.mjs +39 -0
- package/esm/upgrade/version.mjs.map +1 -0
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/clock.d.mts +33 -0
- package/esm/utils/clock.d.mts.map +1 -0
- package/esm/utils/clock.mjs +27 -0
- package/esm/utils/clock.mjs.map +1 -0
- package/esm/utils/framework-vesion.mjs +1 -1
- package/esm/utils/framework-vesion.mjs.map +1 -1
- package/esm/utils/ids.d.mts +32 -0
- package/esm/utils/ids.d.mts.map +1 -0
- package/esm/utils/ids.mjs +33 -0
- package/esm/utils/ids.mjs.map +1 -0
- package/esm/utils/index.d.mts +3 -0
- package/esm/utils/index.mjs +3 -0
- package/esm/utils/runtime.d.mts +59 -0
- package/esm/utils/runtime.d.mts.map +1 -0
- package/esm/utils/runtime.mjs +85 -0
- package/esm/utils/runtime.mjs.map +1 -0
- package/esm/validation/index.d.mts +1 -1
- package/esm/validation/types.d.mts +57 -2
- package/esm/validation/types.d.mts.map +1 -1
- package/esm/validation/validateAll.mjs +19 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/validation/validation-failure-status.mjs +15 -0
- package/esm/validation/validation-failure-status.mjs.map +1 -0
- package/esm/validation/validators/file-validator.d.mts +0 -16
- package/esm/validation/validators/file-validator.d.mts.map +1 -1
- package/esm/warlock-config/bundle-config.mjs +1 -1
- package/esm/warlock-config/types.d.mts +27 -3
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/llms-full.txt +3166 -736
- package/llms.txt +16 -6
- package/package.json +43 -13
- package/schemas/inspect-report.schema.json +554 -0
- package/skills/README.md +9 -0
- package/skills/add-connector/SKILL.md +26 -38
- package/skills/benchmark-code/SKILL.md +30 -24
- package/skills/build-restful/SKILL.md +14 -14
- package/skills/build-url/SKILL.md +18 -18
- package/skills/configure-app/SKILL.md +107 -14
- package/skills/create-controller/SKILL.md +25 -12
- package/skills/create-module/SKILL.md +3 -3
- package/skills/define-resource/SKILL.md +32 -29
- package/skills/encrypt-data/SKILL.md +21 -19
- package/skills/generate-openapi/SKILL.md +50 -24
- package/skills/hash-password/SKILL.md +2 -2
- package/skills/health-checks/SKILL.md +24 -7
- package/skills/index.md +9 -4
- package/skills/lower-stage3-decorators/SKILL.md +4 -4
- package/skills/make-endpoint/SKILL.md +110 -0
- package/skills/model-factories/SKILL.md +92 -0
- package/skills/process-image/SKILL.md +32 -30
- package/skills/register-route/SKILL.md +109 -10
- package/skills/request-memo/SKILL.md +1 -1
- package/skills/request-tracing/SKILL.md +4 -1
- package/skills/resolve-path/SKILL.md +45 -44
- package/skills/retry-operation/SKILL.md +20 -11
- package/skills/run-app/SKILL.md +106 -22
- package/skills/send-response/SKILL.md +179 -14
- package/skills/store-file/SKILL.md +93 -53
- package/skills/test-fakes/SKILL.md +123 -0
- package/skills/test-http/SKILL.md +125 -21
- package/skills/test-service/SKILL.md +41 -1
- package/skills/update-packages/SKILL.md +29 -18
- package/skills/upgrade-from-5-29/SKILL.md +413 -0
- package/skills/upload-file/SKILL.md +31 -33
- package/skills/use-app-context/SKILL.md +38 -31
- package/skills/use-middleware/SKILL.md +45 -25
- package/skills/use-model-transformers/SKILL.md +37 -28
- package/skills/use-repository/SKILL.md +2 -2
- package/skills/validate-input/SKILL.md +72 -28
- package/skills/warlock-agents-md/SKILL.md +96 -0
- package/skills/warlock-board/SKILL.md +123 -0
- package/skills/warlock-check/SKILL.md +132 -0
- package/skills/warlock-conventions/SKILL.md +115 -0
- package/skills/warlock-doctor/SKILL.md +55 -22
- package/skills/warlock-inspect/SKILL.md +134 -0
- package/skills/warlock-mcp/SKILL.md +120 -0
- package/skills/warlock-routes/SKILL.md +18 -11
- package/skills/warlock-trace/SKILL.md +81 -0
- package/skills/wire-socket/SKILL.md +8 -8
- package/skills/write-seeder/SKILL.md +46 -32
- package/skills/write-use-case/SKILL.md +40 -27
- package/esm/http/uploads/matches-if-none-match.mjs +0 -13
- package/esm/http/uploads/matches-if-none-match.mjs.map +0 -1
package/esm/http/server.mjs
CHANGED
|
@@ -26,17 +26,19 @@ function startHttpServer(options) {
|
|
|
26
26
|
const trustProxy = resolveTrustProxy(config.get("http.trustProxy", false));
|
|
27
27
|
const bodyLimit = config.get("http.bodyLimit") ?? void 0;
|
|
28
28
|
const configuredRewriteUrl = options?.rewriteUrl;
|
|
29
|
-
const
|
|
29
|
+
const http2 = options?.http2 === true;
|
|
30
|
+
const requestedForceClose = options?.forceCloseConnections ?? config.get("http.gracefulShutdown.forceCloseConnections", http2 ? false : "idle");
|
|
31
|
+
const forceCloseConnections = http2 && requestedForceClose === "idle" ? false : requestedForceClose;
|
|
30
32
|
server = Fastify({
|
|
31
33
|
trustProxy,
|
|
32
34
|
...bodyLimit !== void 0 && { bodyLimit },
|
|
33
|
-
forceCloseConnections,
|
|
34
35
|
...options,
|
|
36
|
+
forceCloseConnections,
|
|
35
37
|
rewriteUrl(request) {
|
|
36
38
|
return normalizeRequestUrl(configuredRewriteUrl ? configuredRewriteUrl.call(this, request) : request.url ?? "/");
|
|
37
39
|
}
|
|
38
40
|
});
|
|
39
|
-
if (forceCloseConnections === "idle") trackConnectionsForDrain(server.server);
|
|
41
|
+
if (http2 || forceCloseConnections === "idle") trackConnectionsForDrain(server.server);
|
|
40
42
|
return server;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
@@ -52,11 +54,12 @@ const drainStates = /* @__PURE__ */ new WeakMap();
|
|
|
52
54
|
* retired by {@link reapIdleConnections} after it finishes instead.
|
|
53
55
|
*/
|
|
54
56
|
function retireAfterResponse(response) {
|
|
57
|
+
if ("stream" in response) return;
|
|
55
58
|
if (response.headersSent || response.writableEnded) return;
|
|
56
59
|
response.setHeader("Connection", "close");
|
|
57
60
|
}
|
|
58
61
|
function reapIdleConnections(rawServer) {
|
|
59
|
-
if (typeof rawServer.closeIdleConnections === "function") rawServer.closeIdleConnections();
|
|
62
|
+
if ("closeIdleConnections" in rawServer && typeof rawServer.closeIdleConnections === "function") rawServer.closeIdleConnections();
|
|
60
63
|
}
|
|
61
64
|
/**
|
|
62
65
|
* Track in-flight responses on a Node HTTP server so a graceful drain can
|
|
@@ -74,9 +77,15 @@ function trackConnectionsForDrain(rawServer) {
|
|
|
74
77
|
if (!rawServer || typeof rawServer.on !== "function" || drainStates.has(rawServer)) return;
|
|
75
78
|
const state = {
|
|
76
79
|
inFlight: /* @__PURE__ */ new Set(),
|
|
80
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
77
81
|
draining: false
|
|
78
82
|
};
|
|
79
83
|
drainStates.set(rawServer, state);
|
|
84
|
+
rawServer.on("session", (session) => {
|
|
85
|
+
state.sessions.add(session);
|
|
86
|
+
session.once("close", () => state.sessions.delete(session));
|
|
87
|
+
if (state.draining) session.close();
|
|
88
|
+
});
|
|
80
89
|
rawServer.on("request", (_request, response) => {
|
|
81
90
|
state.inFlight.add(response);
|
|
82
91
|
if (state.draining) retireAfterResponse(response);
|
|
@@ -100,6 +109,7 @@ function beginHttpDrain(rawServer) {
|
|
|
100
109
|
if (!state) return false;
|
|
101
110
|
state.draining = true;
|
|
102
111
|
for (const response of state.inFlight) retireAfterResponse(response);
|
|
112
|
+
for (const session of state.sessions) session.close();
|
|
103
113
|
reapIdleConnections(rawServer);
|
|
104
114
|
return true;
|
|
105
115
|
}
|
package/esm/http/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":[],"sources":["../../../../../../../../packages/core/src/http/server.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport Fastify, { type FastifyServerOptions } from \"fastify\";\nimport type { Server as NodeHttpServer, ServerResponse } from \"node:http\";\nimport { normalizeRequestPath } from \"../router/normalize-request-path\";\nimport type { HttpServerOptions } from \"./types\";\n\nexport type FastifyInstance = ReturnType<typeof Fastify>;\n\n// Instantiate Fastify server\nlet server: FastifyInstance | undefined = undefined;\n\n/** Fastify's accepted, non-null `trustProxy` configuration shape. */\ntype TrustProxy = NonNullable<FastifyServerOptions[\"trustProxy\"]>;\ntype TrustProxyPredicate = (address: string, hop: number) => boolean;\n\nfunction isTrustProxyPredicate(value: unknown): value is TrustProxyPredicate {\n return typeof value === \"function\";\n}\n\nfunction isTrustProxyList(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((entry) => typeof entry === \"string\" && entry.trim().length > 0)\n );\n}\n\nfunction resolveTrustProxy(value: unknown): TrustProxy {\n if (value === undefined || value === null) return false;\n\n if (typeof value === \"boolean\" || isTrustProxyPredicate(value)) return value;\n\n if (typeof value === \"string\" && value.trim().length > 0) return value;\n\n if (isTrustProxyList(value)) return value;\n\n const received = Array.isArray(value) ? \"array\" : typeof value;\n\n throw new TypeError(\n \"Invalid http.trustProxy configuration: expected a boolean, a non-empty IP/CIDR string, \" +\n `a non-empty string array, or a predicate function; received ${received}.`,\n );\n}\n\nfunction normalizeRequestUrl(url: string): string {\n const queryIndex = url.indexOf(\"?\");\n\n if (queryIndex === -1) return normalizeRequestPath(url);\n\n return normalizeRequestPath(url.slice(0, queryIndex)) + url.slice(queryIndex);\n}\n\nexport function startHttpServer(options?: HttpServerOptions): FastifyInstance {\n // `config.set(key, undefined)` stores null rather than unsetting, so an app\n // that clears a key is treated the same as an app that never configured it.\n const trustProxy = resolveTrustProxy(config.get(\"http.trustProxy\", false));\n\n const bodyLimit = config.get(\"http.bodyLimit\") ?? undefined;\n const configuredRewriteUrl = options?.rewriteUrl;\n const forceCloseConnections =\n options?.forceCloseConnections ??\n config.get(\"http.gracefulShutdown.forceCloseConnections\", \"idle\");\n\n server = Fastify({\n // `X-Forwarded-For` is client-settable and spoofable, and `request.ip` is\n // what @fastify/rate-limit keys its buckets on — so trusting it by default\n // makes rate limiting bypassable on any deployment NOT behind a proxy that\n // strips the header. Apps behind such a proxy opt in explicitly.\n //\n // Validated shapes resolve exactly as Fastify does: `true`, a CIDR/IP list\n // (string, comma-separated string or array), or a predicate.\n trustProxy,\n // No default: an app that configures nothing keeps Fastify's own 1MB limit\n // rather than the historical 200GB, which silently removed the protection\n // Fastify provides. Per-route caps go through `serverOptions.bodyLimit`;\n // the `maxBodySize()` middleware runs AFTER parsing and cannot reject\n // before the bytes are resident.\n ...(bodyLimit !== undefined && { bodyLimit }),\n // Close idle keep-alive connections on shutdown while letting in-flight\n // requests finish — the basis for graceful draining. Override via\n // `http.gracefulShutdown.forceCloseConnections`.\n forceCloseConnections,\n ...options,\n // Fastify calls this before route matching in both production `scan()` and\n // the development wildcard dispatcher. Compose an app-supplied rewrite\n // first, then apply the one framework request-path normalizer so the two\n // modes cannot disagree about a terminal slash.\n rewriteUrl(request) {\n const rewritten = configuredRewriteUrl\n ? configuredRewriteUrl.call(this, request)\n : (request.url ?? \"/\");\n\n return normalizeRequestUrl(rewritten);\n },\n });\n\n // `\"idle\"` promises that idle keep-alive connections close on shutdown. Fastify\n // only reaps the ones idle at the moment close starts; a connection whose\n // request finishes DURING the drain stays open and holds `close()` until\n // the shutdown budget expires (WR-014). Track responses so the drain can\n // retire those connections as they go idle.\n if (forceCloseConnections === \"idle\") {\n trackConnectionsForDrain(server.server as NodeHttpServer);\n }\n\n return server;\n}\n\n/**\n * Expose the server to be publicly accessible\n */\nexport function getHttpServer(): FastifyInstance {\n return server;\n}\n\n/**\n * Minimal shape needed to close a server — lets {@link closeServerWithTimeout}\n * be unit-tested with a fake instead of a real Fastify instance.\n */\nexport type ClosableServer = {\n close: () => Promise<unknown>;\n /** The underlying Node server, when there is one (a Fastify instance's `.server`). */\n server?: unknown;\n};\n\n/**\n * Per-server drain state: the responses currently in flight, and whether a\n * graceful shutdown has begun.\n */\ntype DrainState = {\n inFlight: Set<ServerResponse>;\n draining: boolean;\n};\n\nconst drainStates = new WeakMap<object, DrainState>();\n\n/**\n * Mark a response so its connection closes once it is sent. Only possible\n * before the headers go out; a response that already streams its headers is\n * retired by {@link reapIdleConnections} after it finishes instead.\n */\nfunction retireAfterResponse(response: ServerResponse): void {\n if (response.headersSent || response.writableEnded) return;\n\n response.setHeader(\"Connection\", \"close\");\n}\n\nfunction reapIdleConnections(rawServer: NodeHttpServer): void {\n if (typeof rawServer.closeIdleConnections === \"function\") {\n rawServer.closeIdleConnections();\n }\n}\n\n/**\n * Track in-flight responses on a Node HTTP server so a graceful drain can\n * close keep-alive connections that become idle AFTER `close()` started.\n *\n * Node's `server.close()` (and Fastify's `forceCloseConnections: \"idle\"`)\n * closes only the connections idle at that moment. A request in flight when\n * shutdown begins finishes its response, the client keeps the connection\n * alive, and `close()` waits on it until the shutdown budget runs out. Active\n * responses are never cut short: each one is told to close its connection\n * when it is done (`Connection: close`), and every connection is reaped as\n * soon as its response has finished.\n */\nexport function trackConnectionsForDrain(rawServer: NodeHttpServer): void {\n if (!rawServer || typeof rawServer.on !== \"function\" || drainStates.has(rawServer)) return;\n\n const state: DrainState = { inFlight: new Set(), draining: false };\n\n drainStates.set(rawServer, state);\n\n rawServer.on(\"request\", (_request, response: ServerResponse) => {\n state.inFlight.add(response);\n\n if (state.draining) retireAfterResponse(response);\n\n response.once(\"close\", () => {\n state.inFlight.delete(response);\n\n // The socket becomes idle once Node finishes handling this response,\n // which happens after the response's own `close` event.\n if (state.draining) setImmediate(() => reapIdleConnections(rawServer));\n });\n });\n}\n\n/**\n * Begin a graceful drain on a server tracked by {@link trackConnectionsForDrain}:\n * every in-flight response closes its connection when it is done, and idle\n * keep-alive connections are closed now and whenever another one goes idle.\n * A server that is not tracked is left alone.\n *\n * @returns Whether the server was tracked (and is now draining).\n */\nexport function beginHttpDrain(rawServer: unknown): boolean {\n if (!rawServer || typeof rawServer !== \"object\") return false;\n\n const state = drainStates.get(rawServer);\n\n if (!state) return false;\n\n state.draining = true;\n\n for (const response of state.inFlight) {\n retireAfterResponse(response);\n }\n\n reapIdleConnections(rawServer as NodeHttpServer);\n\n return true;\n}\n\n/**\n * Close a server, bounded by a timeout. Fastify's `close()` stops accepting new\n * requests (it answers 503 while closing) and drains the in-flight ones; this\n * wraps it so a single stuck request can't hang shutdown forever. When the\n * server's connections are tracked (the default `\"idle\"` mode), keep-alive\n * connections that go idle during the drain are closed too, so a finished\n * request does not hold the shutdown open.\n *\n * @returns `true` if the server drained cleanly, `false` if the timeout fired first.\n */\nexport async function closeServerWithTimeout(\n server: ClosableServer,\n timeoutMs: number,\n): Promise<boolean> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n beginHttpDrain(server.server);\n\n const drained = server.close().then(() => true);\n\n const timedOut = new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs);\n });\n\n try {\n return await Promise.race([drained, timedOut]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.mjs","names":["createServer"],"sources":["../../../../../../../../packages/core/src/http/server.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport Fastify, { type FastifyServerOptions } from \"fastify\";\nimport type { Server as NodeHttpServer, ServerResponse } from \"node:http\";\nimport type {\n Http2Server,\n Http2SecureServer,\n Http2ServerResponse,\n ServerHttp2Session,\n} from \"node:http2\";\n\ntype DrainServer = NodeHttpServer | Http2Server | Http2SecureServer;\ntype DrainResponse = ServerResponse | Http2ServerResponse;\nimport { normalizeRequestPath } from \"../router/normalize-request-path\";\nimport type { HttpServerOptions } from \"./types\";\n\nexport type FastifyInstance = ReturnType<typeof Fastify>;\n\n// Instantiate Fastify server\nlet server: FastifyInstance | undefined = undefined;\n\n/** Fastify's accepted, non-null `trustProxy` configuration shape. */\ntype TrustProxy = NonNullable<FastifyServerOptions[\"trustProxy\"]>;\ntype TrustProxyPredicate = (address: string, hop: number) => boolean;\n\nfunction isTrustProxyPredicate(value: unknown): value is TrustProxyPredicate {\n return typeof value === \"function\";\n}\n\nfunction isTrustProxyList(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((entry) => typeof entry === \"string\" && entry.trim().length > 0)\n );\n}\n\nfunction resolveTrustProxy(value: unknown): TrustProxy {\n if (value === undefined || value === null) return false;\n\n if (typeof value === \"boolean\" || isTrustProxyPredicate(value)) return value;\n\n if (typeof value === \"string\" && value.trim().length > 0) return value;\n\n if (isTrustProxyList(value)) return value;\n\n const received = Array.isArray(value) ? \"array\" : typeof value;\n\n throw new TypeError(\n \"Invalid http.trustProxy configuration: expected a boolean, a non-empty IP/CIDR string, \" +\n `a non-empty string array, or a predicate function; received ${received}.`,\n );\n}\n\nfunction normalizeRequestUrl(url: string): string {\n const queryIndex = url.indexOf(\"?\");\n\n if (queryIndex === -1) return normalizeRequestPath(url);\n\n return normalizeRequestPath(url.slice(0, queryIndex)) + url.slice(queryIndex);\n}\n\nexport function startHttpServer(options?: HttpServerOptions): FastifyInstance {\n // `config.set(key, undefined)` stores null rather than unsetting, so an app\n // that clears a key is treated the same as an app that never configured it.\n const trustProxy = resolveTrustProxy(config.get(\"http.trustProxy\", false));\n\n const bodyLimit = config.get(\"http.bodyLimit\") ?? undefined;\n const configuredRewriteUrl = options?.rewriteUrl;\n const http2 = options?.http2 === true;\n const requestedForceClose =\n options?.forceCloseConnections ??\n config.get(\"http.gracefulShutdown.forceCloseConnections\", http2 ? false : \"idle\");\n // HTTP/2 has sessions rather than closeIdleConnections(); session tracking\n // below supplies graceful draining even when Fastify cannot use idle mode.\n const forceCloseConnections =\n http2 && requestedForceClose === \"idle\" ? false : requestedForceClose;\n\n // Keep the framework's established HTTP-compatible reply facade while\n // accepting Fastify's HTTP/1 and HTTP/2 option overloads at this boundary.\n const createServer = Fastify as (options: HttpServerOptions) => FastifyInstance;\n server = createServer({\n // `X-Forwarded-For` is client-settable and spoofable, and `request.ip` is\n // what @fastify/rate-limit keys its buckets on — so trusting it by default\n // makes rate limiting bypassable on any deployment NOT behind a proxy that\n // strips the header. Apps behind such a proxy opt in explicitly.\n //\n // Validated shapes resolve exactly as Fastify does: `true`, a CIDR/IP list\n // (string, comma-separated string or array), or a predicate.\n trustProxy,\n // No default: an app that configures nothing keeps Fastify's own 1MB limit\n // rather than the historical 200GB, which silently removed the protection\n // Fastify provides. Per-route caps go through `serverOptions.bodyLimit`;\n // the `maxBodySize()` middleware runs AFTER parsing and cannot reject\n // before the bytes are resident.\n ...(bodyLimit !== undefined && { bodyLimit }),\n // Close idle keep-alive connections on shutdown while letting in-flight\n // requests finish — the basis for graceful draining. Override via\n // `http.gracefulShutdown.forceCloseConnections`.\n ...options,\n forceCloseConnections,\n // Fastify calls this before route matching in both production `scan()` and\n // the development wildcard dispatcher. Compose an app-supplied rewrite\n // first, then apply the one framework request-path normalizer so the two\n // modes cannot disagree about a terminal slash.\n rewriteUrl(request) {\n const rewritten = configuredRewriteUrl\n ? configuredRewriteUrl.call(this, request)\n : (request.url ?? \"/\");\n\n return normalizeRequestUrl(rewritten);\n },\n });\n\n // `\"idle\"` promises that idle keep-alive connections close on shutdown. Fastify\n // only reaps the ones idle at the moment close starts; a connection whose\n // request finishes DURING the drain stays open and holds `close()` until\n // the shutdown budget expires. Track responses so the drain can\n // retire those connections as they go idle.\n if (http2 || forceCloseConnections === \"idle\") {\n trackConnectionsForDrain(server.server as DrainServer);\n }\n\n return server;\n}\n\n/**\n * Expose the server to be publicly accessible\n */\nexport function getHttpServer(): FastifyInstance {\n return server;\n}\n\n/**\n * Minimal shape needed to close a server — lets {@link closeServerWithTimeout}\n * be unit-tested with a fake instead of a real Fastify instance.\n */\nexport type ClosableServer = {\n close: () => Promise<unknown>;\n /** The underlying Node server, when there is one (a Fastify instance's `.server`). */\n server?: unknown;\n};\n\n/**\n * Per-server drain state: the responses currently in flight, and whether a\n * graceful shutdown has begun.\n */\ntype DrainState = {\n inFlight: Set<DrainResponse>;\n sessions: Set<ServerHttp2Session>;\n draining: boolean;\n};\n\nconst drainStates = new WeakMap<object, DrainState>();\n\n/**\n * Mark a response so its connection closes once it is sent. Only possible\n * before the headers go out; a response that already streams its headers is\n * retired by {@link reapIdleConnections} after it finishes instead.\n */\nfunction retireAfterResponse(response: DrainResponse): void {\n // HTTP/2 drains the session with GOAWAY; connection headers are forbidden.\n if (\"stream\" in response) return;\n if (response.headersSent || response.writableEnded) return;\n\n response.setHeader(\"Connection\", \"close\");\n}\n\nfunction reapIdleConnections(rawServer: DrainServer): void {\n if (\"closeIdleConnections\" in rawServer && typeof rawServer.closeIdleConnections === \"function\") {\n rawServer.closeIdleConnections();\n }\n}\n\n/**\n * Track in-flight responses on a Node HTTP server so a graceful drain can\n * close keep-alive connections that become idle AFTER `close()` started.\n *\n * Node's `server.close()` (and Fastify's `forceCloseConnections: \"idle\"`)\n * closes only the connections idle at that moment. A request in flight when\n * shutdown begins finishes its response, the client keeps the connection\n * alive, and `close()` waits on it until the shutdown budget runs out. Active\n * responses are never cut short: each one is told to close its connection\n * when it is done (`Connection: close`), and every connection is reaped as\n * soon as its response has finished.\n */\nexport function trackConnectionsForDrain(rawServer: DrainServer): void {\n if (!rawServer || typeof rawServer.on !== \"function\" || drainStates.has(rawServer)) return;\n\n const state: DrainState = { inFlight: new Set(), sessions: new Set(), draining: false };\n\n drainStates.set(rawServer, state);\n\n rawServer.on(\"session\", (session: ServerHttp2Session) => {\n state.sessions.add(session);\n session.once(\"close\", () => state.sessions.delete(session));\n if (state.draining) session.close();\n });\n\n rawServer.on(\"request\", (_request: unknown, response: DrainResponse) => {\n state.inFlight.add(response);\n\n if (state.draining) retireAfterResponse(response);\n\n response.once(\"close\", () => {\n state.inFlight.delete(response);\n\n // The socket becomes idle once Node finishes handling this response,\n // which happens after the response's own `close` event.\n if (state.draining) setImmediate(() => reapIdleConnections(rawServer));\n });\n });\n}\n\n/**\n * Begin a graceful drain on a server tracked by {@link trackConnectionsForDrain}:\n * every in-flight response closes its connection when it is done, and idle\n * keep-alive connections are closed now and whenever another one goes idle.\n * A server that is not tracked is left alone.\n *\n * @returns Whether the server was tracked (and is now draining).\n */\nexport function beginHttpDrain(rawServer: unknown): boolean {\n if (!rawServer || typeof rawServer !== \"object\") return false;\n\n const state = drainStates.get(rawServer);\n\n if (!state) return false;\n\n state.draining = true;\n\n for (const response of state.inFlight) {\n retireAfterResponse(response);\n }\n\n // close() sends GOAWAY and waits for active streams before closing the socket.\n for (const session of state.sessions) session.close();\n\n reapIdleConnections(rawServer as DrainServer);\n\n return true;\n}\n\n/**\n * Close a server, bounded by a timeout. Fastify's `close()` stops accepting new\n * requests (it answers 503 while closing) and drains the in-flight ones; this\n * wraps it so a single stuck request can't hang shutdown forever. When the\n * server's connections are tracked (the default `\"idle\"` mode), keep-alive\n * connections that go idle during the drain are closed too, so a finished\n * request does not hold the shutdown open.\n *\n * @returns `true` if the server drained cleanly, `false` if the timeout fired first.\n */\nexport async function closeServerWithTimeout(\n server: ClosableServer,\n timeoutMs: number,\n): Promise<boolean> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n beginHttpDrain(server.server);\n\n const drained = server.close().then(() => true);\n\n const timedOut = new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs);\n });\n\n try {\n return await Promise.race([drained, timedOut]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ function createStreamTransport(raw, options) {
|
|
|
6
6
|
let disconnected = false;
|
|
7
7
|
const disconnectHandlers = [];
|
|
8
8
|
const disconnectController = new AbortController();
|
|
9
|
-
|
|
9
|
+
const disconnect = () => {
|
|
10
10
|
if (closed) return;
|
|
11
11
|
closed = true;
|
|
12
12
|
disconnected = true;
|
|
@@ -17,7 +17,8 @@ function createStreamTransport(raw, options) {
|
|
|
17
17
|
} catch (error) {
|
|
18
18
|
log.error("http", "stream", error, { source: "onDisconnect handler" });
|
|
19
19
|
}
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
|
+
raw.on("close", disconnect);
|
|
21
22
|
return {
|
|
22
23
|
signal: disconnectController.signal,
|
|
23
24
|
get closed() {
|
|
@@ -32,6 +33,7 @@ function createStreamTransport(raw, options) {
|
|
|
32
33
|
finish() {
|
|
33
34
|
closed = true;
|
|
34
35
|
},
|
|
36
|
+
disconnect,
|
|
35
37
|
onDisconnect(handler) {
|
|
36
38
|
disconnectHandlers.push(handler);
|
|
37
39
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-transport.mjs","names":[],"sources":["../../../../../../../../packages/core/src/http/stream-transport.ts"],"sourcesContent":["import type { ServerResponse } from \"node:http\";\nimport { log } from \"@warlock.js/logger\";\n\n/**\n * Shared transport bookkeeping for the long-lived responses\n * (`response.sse()` and `response.stream()`): client-disconnect detection,\n * an `AbortSignal` for the work feeding the stream, and backpressure
|
|
1
|
+
{"version":3,"file":"stream-transport.mjs","names":[],"sources":["../../../../../../../../packages/core/src/http/stream-transport.ts"],"sourcesContent":["import type { ServerResponse } from \"node:http\";\nimport { log } from \"@warlock.js/logger\";\n\n/**\n * Shared transport bookkeeping for the long-lived responses\n * (`response.sse()` and `response.stream()`): client-disconnect detection,\n * an `AbortSignal` for the work feeding the stream, and backpressure.\n */\nexport type StreamTransport = {\n /** Aborted when the client disconnects (not when the stream is ended). */\n readonly signal: AbortSignal;\n /** `true` once the stream was ended or the client disconnected. */\n readonly closed: boolean;\n /** `true` once the client disconnected before the stream was ended. */\n readonly disconnected: boolean;\n /** Whether the transport holds more than its high-water mark. */\n readonly backpressured: boolean;\n /** Mark the stream as ended by the server. */\n finish(): void;\n /** Abort the producer and notify disconnect handlers when a slow client is disconnected. */\n disconnect(): void;\n /** Call `handler` when the client disconnects. */\n onDisconnect(handler: () => void): void;\n /**\n * Resolve once the transport has flushed what it buffered: `true` when the\n * client can take more, `false` when the stream ended or the client left.\n */\n waitForDrain(): Promise<boolean>;\n};\n\nexport function createStreamTransport(\n raw: ServerResponse,\n options: { disconnectMessage: string; onClientDisconnected?: () => void },\n): StreamTransport {\n let closed = false;\n let disconnected = false;\n const disconnectHandlers: Array<() => void> = [];\n const disconnectController = new AbortController();\n\n // Without this, background jobs keep writing to a dead socket after the\n // client drops. `close` also fires after a normal `end()`; `closed` is\n // already set by then.\n const disconnect = () => {\n if (closed) return;\n\n closed = true;\n disconnected = true;\n options.onClientDisconnected?.();\n disconnectController.abort(new DOMException(options.disconnectMessage, \"AbortError\"));\n\n // Each handler is isolated: this runs inside the socket's `close`\n // listener, so a throwing handler would otherwise become an uncaught\n // exception (taking the process down) and skip the handlers after it.\n for (const handler of disconnectHandlers) {\n try {\n handler();\n } catch (error) {\n log.error(\"http\", \"stream\", error as Error, { source: \"onDisconnect handler\" });\n }\n }\n };\n raw.on(\"close\", disconnect);\n\n return {\n signal: disconnectController.signal,\n\n get closed() {\n return closed;\n },\n\n get disconnected() {\n return disconnected;\n },\n\n get backpressured() {\n return !closed && raw.writableNeedDrain;\n },\n\n finish() {\n closed = true;\n },\n\n disconnect,\n\n onDisconnect(handler) {\n disconnectHandlers.push(handler);\n },\n\n waitForDrain() {\n if (closed) return Promise.resolve(false);\n\n if (!raw.writableNeedDrain) return Promise.resolve(true);\n\n return new Promise<boolean>((resolve) => {\n const settle = (drained: boolean) => {\n raw.off(\"drain\", onDrain);\n raw.off(\"close\", onClose);\n resolve(drained);\n };\n const onDrain = () => settle(!closed);\n const onClose = () => settle(false);\n\n raw.once(\"drain\", onDrain);\n raw.once(\"close\", onClose);\n });\n },\n };\n}\n\n/**\n * The most recent `limit` items, for logging/debugging a stream after it\n * ended. `limit` 0 (or missing/NaN) keeps nothing, so a long-lived stream\n * does not grow with its own history.\n */\nexport function createRetainedTail<T>(limit: number | undefined): {\n push(item: T): void;\n items: T[];\n} {\n const max = limit === undefined || Number.isNaN(limit) ? 0 : Math.max(0, Math.floor(limit));\n const items: T[] = [];\n\n return {\n items,\n push(item) {\n if (max === 0) return;\n\n items.push(item);\n\n if (items.length > max) items.shift();\n },\n };\n}\n"],"mappings":";;;AA8BA,SAAgB,sBACd,KACA,SACiB;CACjB,IAAI,SAAS;CACb,IAAI,eAAe;CACnB,MAAM,qBAAwC,CAAC;CAC/C,MAAM,uBAAuB,IAAI,gBAAgB;CAKjD,MAAM,mBAAmB;EACvB,IAAI,QAAQ;EAEZ,SAAS;EACT,eAAe;EACf,QAAQ,uBAAuB;EAC/B,qBAAqB,MAAM,IAAI,aAAa,QAAQ,mBAAmB,YAAY,CAAC;EAKpF,KAAK,MAAM,WAAW,oBACpB,IAAI;GACF,QAAQ;EACV,SAAS,OAAO;GACd,IAAI,MAAM,QAAQ,UAAU,OAAgB,EAAE,QAAQ,uBAAuB,CAAC;EAChF;CAEJ;CACA,IAAI,GAAG,SAAS,UAAU;CAE1B,OAAO;EACL,QAAQ,qBAAqB;EAE7B,IAAI,SAAS;GACX,OAAO;EACT;EAEA,IAAI,eAAe;GACjB,OAAO;EACT;EAEA,IAAI,gBAAgB;GAClB,OAAO,CAAC,UAAU,IAAI;EACxB;EAEA,SAAS;GACP,SAAS;EACX;EAEA;EAEA,aAAa,SAAS;GACpB,mBAAmB,KAAK,OAAO;EACjC;EAEA,eAAe;GACb,IAAI,QAAQ,OAAO,QAAQ,QAAQ,KAAK;GAExC,IAAI,CAAC,IAAI,mBAAmB,OAAO,QAAQ,QAAQ,IAAI;GAEvD,OAAO,IAAI,SAAkB,YAAY;IACvC,MAAM,UAAU,YAAqB;KACnC,IAAI,IAAI,SAAS,OAAO;KACxB,IAAI,IAAI,SAAS,OAAO;KACxB,QAAQ,OAAO;IACjB;IACA,MAAM,gBAAgB,OAAO,CAAC,MAAM;IACpC,MAAM,gBAAgB,OAAO,KAAK;IAElC,IAAI,KAAK,SAAS,OAAO;IACzB,IAAI,KAAK,SAAS,OAAO;GAC3B,CAAC;EACH;CACF;AACF;;;;;;AAOA,SAAgB,mBAAsB,OAGpC;CACA,MAAM,MAAM,UAAU,UAAa,OAAO,MAAM,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;CAC1F,MAAM,QAAa,CAAC;CAEpB,OAAO;EACL;EACA,KAAK,MAAM;GACT,IAAI,QAAQ,GAAG;GAEf,MAAM,KAAK,IAAI;GAEf,IAAI,MAAM,SAAS,KAAK,MAAM,MAAM;EACtC;CACF;AACF"}
|
package/esm/http/types.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpRateLimitConfig } from "./build-rate-limit-options.mjs";
|
|
2
|
+
import { StreamQueueOptions } from "./bounded-stream-queue.mjs";
|
|
2
3
|
import { HttpLoggingConfig } from "./request-log-snapshot.mjs";
|
|
3
4
|
import { Response } from "./response.mjs";
|
|
4
5
|
import { CspConfig } from "./csp.mjs";
|
|
@@ -9,6 +10,7 @@ import { FastifyHttpOptions } from "fastify";
|
|
|
9
10
|
import { CookieSerializeOptions } from "@fastify/cookie";
|
|
10
11
|
import { FastifyCorsOptions } from "@fastify/cors";
|
|
11
12
|
import { Server } from "node:http";
|
|
13
|
+
import { SecureServerOptions } from "node:http2";
|
|
12
14
|
import React from "react";
|
|
13
15
|
|
|
14
16
|
//#region ../../packages/core/src/http/types.d.ts
|
|
@@ -188,7 +190,11 @@ interface PartialMiddleware {
|
|
|
188
190
|
* `gracefulShutdown.forceCloseConnections`) have their own `http.*` keys;
|
|
189
191
|
* setting them here as well overrides those keys.
|
|
190
192
|
*/
|
|
191
|
-
type HttpServerOptions = FastifyHttpOptions<Server
|
|
193
|
+
type HttpServerOptions = Omit<FastifyHttpOptions<Server>, "http2"> & {
|
|
194
|
+
/** Enable HTTP/2 while retaining the framework's HTTP-compatible reply API. */http2?: boolean;
|
|
195
|
+
https?: SecureServerOptions;
|
|
196
|
+
http2SessionTimeout?: number;
|
|
197
|
+
};
|
|
192
198
|
/**
|
|
193
199
|
* Http Configurations list
|
|
194
200
|
*/
|
|
@@ -228,6 +234,14 @@ interface HttpConfigurations {
|
|
|
228
234
|
* handling. See {@link HttpErrorMapper}.
|
|
229
235
|
*/
|
|
230
236
|
errorMapper?: HttpErrorMapper;
|
|
237
|
+
/**
|
|
238
|
+
* In `warlock dev`, warn once per route about POST/PUT/PATCH/DELETE routes
|
|
239
|
+
* that have no auth middleware and no `public: true`. Development only;
|
|
240
|
+
* production never checks. Set `false` to silence the warning entirely.
|
|
241
|
+
*
|
|
242
|
+
* @default true
|
|
243
|
+
*/
|
|
244
|
+
warnUnguardedRoutes?: boolean;
|
|
231
245
|
/**
|
|
232
246
|
* Server port
|
|
233
247
|
*/
|
|
@@ -453,6 +467,13 @@ interface HttpConfigurations {
|
|
|
453
467
|
* @default "/ready"
|
|
454
468
|
*/
|
|
455
469
|
readinessPath?: string;
|
|
470
|
+
/**
|
|
471
|
+
* Include built-in liveness/readiness probes in the global rate-limit policy.
|
|
472
|
+
* Leave false so client traffic cannot exhaust infrastructure probe quotas.
|
|
473
|
+
*
|
|
474
|
+
* @default false
|
|
475
|
+
*/
|
|
476
|
+
rateLimit?: boolean;
|
|
456
477
|
};
|
|
457
478
|
/**
|
|
458
479
|
* Host
|
|
@@ -485,7 +506,8 @@ interface HttpConfigurations {
|
|
|
485
506
|
/**
|
|
486
507
|
* Options for `response.stream()`.
|
|
487
508
|
*/
|
|
488
|
-
type ResponseStreamOptions = {
|
|
509
|
+
type ResponseStreamOptions = StreamQueueOptions & {
|
|
510
|
+
/** Application/cache headers override defaults before flushing; framing and connection headers are ignored. */headers?: Record<string, string>;
|
|
489
511
|
/**
|
|
490
512
|
* How many of the most recent chunks (from `send()`, `write()` and
|
|
491
513
|
* `render()`) to keep on `response.body` after `end()`, for logging and
|
|
@@ -503,11 +525,13 @@ type ResponseStreamController = {
|
|
|
503
525
|
* down to the client's pace instead of buffering. A silent no-op after the
|
|
504
526
|
* client disconnected; throws after `end()`.
|
|
505
527
|
*/
|
|
506
|
-
send: (data: string | Uint8Array) => void;
|
|
528
|
+
send: (data: string | Uint8Array) => void; /** Bounded synchronous write; false when closed or overflow rejects this chunk. */
|
|
529
|
+
enqueue: (data: string | Uint8Array) => boolean;
|
|
507
530
|
/**
|
|
508
531
|
* Send data and wait until the transport can take more. Resolves `true`
|
|
509
532
|
* when it can, `false` when the stream ended or the client disconnected —
|
|
510
|
-
* stop producing then.
|
|
533
|
+
* stop producing then. In drop mode, true means the stream is ready to continue;
|
|
534
|
+
* inspect dropped to detect rejected output and schedule a resync.
|
|
511
535
|
*/
|
|
512
536
|
write: (data: string | Uint8Array) => Promise<boolean>;
|
|
513
537
|
/**
|
|
@@ -536,7 +560,8 @@ type ResponseStreamController = {
|
|
|
536
560
|
/**
|
|
537
561
|
* Whether the transport is currently holding more than its high-water mark.
|
|
538
562
|
*/
|
|
539
|
-
backpressured: boolean;
|
|
563
|
+
backpressured: boolean; /** Chunks/events/comments rejected by onOverflow: "drop", including send/write/render paths. */
|
|
564
|
+
readonly dropped: number;
|
|
540
565
|
/**
|
|
541
566
|
* Detect whether stream is ended (by `end()` or a client disconnect)
|
|
542
567
|
*/
|
|
@@ -545,7 +570,8 @@ type ResponseStreamController = {
|
|
|
545
570
|
/**
|
|
546
571
|
* Options for `response.sse()`.
|
|
547
572
|
*/
|
|
548
|
-
type ResponseSSEOptions = {
|
|
573
|
+
type ResponseSSEOptions = StreamQueueOptions & {
|
|
574
|
+
/** Application/cache headers override defaults before flushing; framing and connection headers are ignored. */headers?: Record<string, string>;
|
|
549
575
|
/**
|
|
550
576
|
* How many of the most recent events to keep on `response.body` after
|
|
551
577
|
* `end()`, for logging and debugging. Sent events are otherwise not kept,
|
|
@@ -555,6 +581,10 @@ type ResponseSSEOptions = {
|
|
|
555
581
|
*/
|
|
556
582
|
retainEvents?: number;
|
|
557
583
|
};
|
|
584
|
+
/** Wire serialization for one SSE event. Raw strings are split into safe data lines. */
|
|
585
|
+
type ResponseSSEMessageOptions = {
|
|
586
|
+
raw?: boolean;
|
|
587
|
+
};
|
|
558
588
|
type ResponseSSEController = {
|
|
559
589
|
/**
|
|
560
590
|
* Send an SSE event to the client. Does not wait for the client; for
|
|
@@ -564,13 +594,15 @@ type ResponseSSEController = {
|
|
|
564
594
|
* @param data - Event data (will be JSON stringified)
|
|
565
595
|
* @param id - Optional event ID for client-side Last-Event-ID tracking
|
|
566
596
|
*/
|
|
567
|
-
send: (event: string, data: any, id?: string) => ResponseSSEController;
|
|
597
|
+
send: (event: string | undefined, data: any, id?: string, options?: ResponseSSEMessageOptions) => ResponseSSEController; /** Bounded synchronous event; undefined event omits the event field. */
|
|
598
|
+
enqueue: (event: string | undefined, data: unknown, id?: string, options?: ResponseSSEMessageOptions) => boolean;
|
|
568
599
|
/**
|
|
569
600
|
* Send an SSE event and wait until the transport can take more.
|
|
570
601
|
* Resolves `true` when it can, `false` when the stream ended or the client
|
|
571
|
-
* disconnected — stop producing then.
|
|
602
|
+
* disconnected — stop producing then. In drop mode, true means ready to continue;
|
|
603
|
+
* inspect dropped to detect rejected output and schedule a resync.
|
|
572
604
|
*/
|
|
573
|
-
write: (event: string, data: any, id?: string) => Promise<boolean>;
|
|
605
|
+
write: (event: string | undefined, data: any, id?: string, options?: ResponseSSEMessageOptions) => Promise<boolean>;
|
|
574
606
|
/**
|
|
575
607
|
* Wait until output buffered for the client has been flushed. Resolves
|
|
576
608
|
* `true` immediately when nothing is backed up, `false` when the stream
|
|
@@ -585,7 +617,8 @@ type ResponseSSEController = {
|
|
|
585
617
|
/**
|
|
586
618
|
* Whether the transport is currently holding more than its high-water mark.
|
|
587
619
|
*/
|
|
588
|
-
backpressured: boolean;
|
|
620
|
+
backpressured: boolean; /** Chunks/events/comments rejected by onOverflow: "drop", including send/write/render paths. */
|
|
621
|
+
readonly dropped: number;
|
|
589
622
|
/**
|
|
590
623
|
* Send a comment to keep the connection alive (invisible to client)
|
|
591
624
|
*/
|
|
@@ -605,5 +638,5 @@ type ResponseSSEController = {
|
|
|
605
638
|
ended: boolean;
|
|
606
639
|
};
|
|
607
640
|
//#endregion
|
|
608
|
-
export { DecodedAccessToken, HttpConfigurations, HttpServerOptions, PartialMiddleware, RequestEvent, RequestLocals, ResponseEvent, ResponseSSEController, ResponseSSEOptions, ResponseStreamController, ResponseStreamOptions, ReturnedResponse };
|
|
641
|
+
export { DecodedAccessToken, HttpConfigurations, HttpServerOptions, PartialMiddleware, RequestEvent, RequestLocals, ResponseEvent, ResponseSSEController, ResponseSSEMessageOptions, ResponseSSEOptions, ResponseStreamController, ResponseStreamOptions, ReturnedResponse };
|
|
609
642
|
//# sourceMappingURL=types.d.mts.map
|
package/esm/http/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/http/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/http/types.ts"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StorageDriverName } from "../../storage/types.mjs";
|
|
1
2
|
import { GeneratedImageDescriptor } from "./image-variant-types.mjs";
|
|
2
3
|
|
|
3
4
|
//#region ../../packages/core/src/http/uploads/generate-image-variants.d.ts
|
|
@@ -9,7 +10,9 @@ type GenerateImageVariantsOptions = {
|
|
|
9
10
|
* Limit generation to these variant names, instead of every variant in
|
|
10
11
|
* `uploads.images.variants`.
|
|
11
12
|
*/
|
|
12
|
-
variants?: readonly string[];
|
|
13
|
+
variants?: readonly string[]; /** Disk holding the source; defaults to the active disk. */
|
|
14
|
+
disk?: StorageDriverName; /** Route prefix serving variants of this disk (and originals for local disks). Default /uploads. */
|
|
15
|
+
urlPrefix?: string;
|
|
13
16
|
};
|
|
14
17
|
/**
|
|
15
18
|
* Render every configured variant of one uploaded image, right after it is
|
|
@@ -25,15 +28,15 @@ type GenerateImageVariantsOptions = {
|
|
|
25
28
|
* The returned descriptor is plain JSON, safe to store next to the record
|
|
26
29
|
* that owns the image, or to carry through loader data. On local storage the
|
|
27
30
|
* `url`/`urls` already point at `uploadedFileController`; an app serving
|
|
28
|
-
* uploads from object storage
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* uploads from object storage use the driver URL for the original, and
|
|
32
|
+
* `options.urlPrefix` for the route serving locally cached derivatives.
|
|
33
|
+
* Ingest downloads a remote source once for all variants and formats.
|
|
34
|
+
* On-demand cache hits use ETag metadata; sources without ETags are hashed.
|
|
32
35
|
*
|
|
33
36
|
* @param relativePath - path of the saved upload, relative to the storage root
|
|
34
37
|
* @param options.variants - limit generation to these variant names
|
|
35
|
-
* @throws ImageVariantsConfigError when
|
|
36
|
-
*
|
|
38
|
+
* @throws ImageVariantsConfigError when `uploads.images` is not
|
|
39
|
+
* configured — this is server misconfiguration, not
|
|
37
40
|
* a bad request
|
|
38
41
|
* @throws HttpError(400) when a name in `options.variants` is not declared in
|
|
39
42
|
* `uploads.images.variants`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-image-variants.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/generate-image-variants.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate-image-variants.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/generate-image-variants.ts"],"mappings":";;;;;;AAqCA;KAAY,4BAAA;;;;;EAKV,QAAA,sBAMA;EAHA,IAAA,GAAO,iBAAiB,EAGf;EAAT,SAAA;AAAA;;;;;;;;;;;;;AAuEiC;;;;;;;;;;;;;;;;;;;;iBAHb,qBAAA,CACpB,YAAA,UACA,OAAA,GAAS,4BAAA,GACR,OAAA,CAAQ,wBAAA"}
|
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
import { HttpError } from "../errors/errors.mjs";
|
|
2
|
+
import { ImageVariantsConfigError } from "./image-variants-config-error.mjs";
|
|
2
3
|
import { uploadsConfig } from "../uploads-config.mjs";
|
|
3
4
|
import { variantCacheKey, variantCachePath, variantGenerationTag, variantSourceDirectory, variantSourceKey } from "./variant-cache-key.mjs";
|
|
4
5
|
import { pruneStaleVariantGenerationsIfCurrent, retryOnVanishedDirectory, warnIfVariantCacheIsPublic } from "./image-variant-cache.mjs";
|
|
5
|
-
import { storage } from "../../storage/storage.mjs";
|
|
6
|
-
import "../../storage/index.mjs";
|
|
7
6
|
import { Image } from "../../image/image.mjs";
|
|
8
7
|
import "../../image/index.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import { ImageVariantsConfigError } from "./image-variants-config-error.mjs";
|
|
8
|
+
import { localDiskRoot } from "./local-disk-root.mjs";
|
|
11
9
|
import { loadVariantSource } from "./load-variant-source.mjs";
|
|
10
|
+
import { remoteVariantDisk, remoteVariantSource } from "./remote-variant-source.mjs";
|
|
11
|
+
import { generateImageVariant } from "./generate-image-variant.mjs";
|
|
12
12
|
import { resolveImageVariantsConfig } from "./resolve-image-variants-config.mjs";
|
|
13
|
-
import {
|
|
13
|
+
import { freshImageVariant, scheduleImageVariantCacheTrim } from "./trim-image-variant-cache.mjs";
|
|
14
14
|
import { variantGenerations } from "./variant-generations.mjs";
|
|
15
|
+
import { renderRemoteImageVariant } from "./resolve-remote-image-variant.mjs";
|
|
16
|
+
import { resolveVariantCandidate } from "./resolve-variant-candidate.mjs";
|
|
15
17
|
import fs from "node:fs/promises";
|
|
16
18
|
|
|
17
19
|
//#region ../../packages/core/src/http/uploads/generate-image-variants.ts
|
|
18
|
-
async function exists(target) {
|
|
19
|
-
try {
|
|
20
|
-
await fs.access(target);
|
|
21
|
-
return true;
|
|
22
|
-
} catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
20
|
function encodeUploadsPath(relativePath) {
|
|
27
21
|
return relativePath.split("/").map(encodeURIComponent).join("/");
|
|
28
22
|
}
|
|
@@ -58,15 +52,15 @@ function outputFormatsFor(sourceFormat, configuredFormats) {
|
|
|
58
52
|
* The returned descriptor is plain JSON, safe to store next to the record
|
|
59
53
|
* that owns the image, or to carry through loader data. On local storage the
|
|
60
54
|
* `url`/`urls` already point at `uploadedFileController`; an app serving
|
|
61
|
-
* uploads from object storage
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
55
|
+
* uploads from object storage use the driver URL for the original, and
|
|
56
|
+
* `options.urlPrefix` for the route serving locally cached derivatives.
|
|
57
|
+
* Ingest downloads a remote source once for all variants and formats.
|
|
58
|
+
* On-demand cache hits use ETag metadata; sources without ETags are hashed.
|
|
65
59
|
*
|
|
66
60
|
* @param relativePath - path of the saved upload, relative to the storage root
|
|
67
61
|
* @param options.variants - limit generation to these variant names
|
|
68
|
-
* @throws ImageVariantsConfigError when
|
|
69
|
-
*
|
|
62
|
+
* @throws ImageVariantsConfigError when `uploads.images` is not
|
|
63
|
+
* configured — this is server misconfiguration, not
|
|
70
64
|
* a bad request
|
|
71
65
|
* @throws HttpError(400) when a name in `options.variants` is not declared in
|
|
72
66
|
* `uploads.images.variants`
|
|
@@ -77,8 +71,8 @@ function outputFormatsFor(sourceFormat, configuredFormats) {
|
|
|
77
71
|
* @throws HttpError(415) when the source is not jpeg, png, webp or avif
|
|
78
72
|
*/
|
|
79
73
|
async function generateImageVariants(relativePath, options = {}) {
|
|
80
|
-
const storageRoot =
|
|
81
|
-
if (!storageRoot
|
|
74
|
+
const storageRoot = localDiskRoot(options.disk);
|
|
75
|
+
if (!storageRoot) return generateRemoteImageVariants(relativePath, options);
|
|
82
76
|
const images = resolveImageVariantsConfig(uploadsConfig("images"), storageRoot);
|
|
83
77
|
if (!images) throw new ImageVariantsConfigError("uploads.images is not configured.");
|
|
84
78
|
const names = options.variants ?? Object.keys(images.variants);
|
|
@@ -93,7 +87,7 @@ async function generateImageVariants(relativePath, options = {}) {
|
|
|
93
87
|
});
|
|
94
88
|
const { width: sourceWidth, height: sourceHeight } = await image.metadata();
|
|
95
89
|
warnIfVariantCacheIsPublic(images.cacheDirectory);
|
|
96
|
-
const src =
|
|
90
|
+
const src = `${(options.urlPrefix ?? "/uploads").replace(/\/$/, "")}/${encodeUploadsPath(source.relativePath)}`;
|
|
97
91
|
const variants = {};
|
|
98
92
|
for (const name of names) {
|
|
99
93
|
const variant = images.variants[name];
|
|
@@ -114,14 +108,15 @@ async function generateImageVariants(relativePath, options = {}) {
|
|
|
114
108
|
sourceKey,
|
|
115
109
|
generation
|
|
116
110
|
}, hash, format);
|
|
117
|
-
if (!await
|
|
118
|
-
if (await
|
|
111
|
+
if (!await freshImageVariant(target, images.maxCacheAge)) await variantGenerations.run(target, async () => {
|
|
112
|
+
if (await freshImageVariant(target, images.maxCacheAge)) return;
|
|
119
113
|
await retryOnVanishedDirectory(() => generateImageVariant({
|
|
120
114
|
image: image.clone(),
|
|
121
115
|
variant,
|
|
122
116
|
format,
|
|
123
117
|
targetPath: target
|
|
124
118
|
}));
|
|
119
|
+
scheduleImageVariantCacheTrim(images, target);
|
|
125
120
|
await pruneStaleVariantGenerationsIfCurrent(source.absolutePath, variantSourceDirectory(images.cacheDirectory, sourceKey), generation);
|
|
126
121
|
});
|
|
127
122
|
if (dimensions === void 0) dimensions = await readDerivativeDimensions(target);
|
|
@@ -143,6 +138,48 @@ async function generateImageVariants(relativePath, options = {}) {
|
|
|
143
138
|
...images.formats.length > 0 ? { formats: images.formats } : {}
|
|
144
139
|
};
|
|
145
140
|
}
|
|
141
|
+
async function generateRemoteImageVariants(relativePath, options) {
|
|
142
|
+
const disk = remoteVariantDisk(options.disk);
|
|
143
|
+
const images = resolveImageVariantsConfig(uploadsConfig("images"), disk.cacheRoot);
|
|
144
|
+
if (!images) throw new ImageVariantsConfigError("uploads.images is not configured.");
|
|
145
|
+
const names = options.variants ?? Object.keys(images.variants);
|
|
146
|
+
for (const name of names) if (!Object.prototype.hasOwnProperty.call(images.variants, name)) throw new HttpError(400, `Unknown variant "${name}".`);
|
|
147
|
+
const source = await remoteVariantSource(relativePath, disk, images);
|
|
148
|
+
if (!source) throw new HttpError(404, "File Not Found");
|
|
149
|
+
try {
|
|
150
|
+
const { image, sourceFormat } = await source.load();
|
|
151
|
+
const { width, height } = await image.metadata();
|
|
152
|
+
const variantSrc = `${(options.urlPrefix ?? "/uploads").replace(/\/$/, "")}/${encodeUploadsPath(source.relativePath)}`;
|
|
153
|
+
const variants = {};
|
|
154
|
+
for (const name of names) {
|
|
155
|
+
let dimensions;
|
|
156
|
+
for (const format of outputFormatsFor(sourceFormat, images.formats)) {
|
|
157
|
+
const resolved = await renderRemoteImageVariant({
|
|
158
|
+
path: relativePath,
|
|
159
|
+
variant: name,
|
|
160
|
+
...format === sourceFormat ? {} : { format }
|
|
161
|
+
}, disk, images, source);
|
|
162
|
+
if (!resolved) throw new HttpError(404, "File Not Found");
|
|
163
|
+
dimensions ??= await readDerivativeDimensions(resolved.absolutePath);
|
|
164
|
+
}
|
|
165
|
+
variants[name] = {
|
|
166
|
+
width: dimensions?.width ?? 0,
|
|
167
|
+
height: dimensions?.height ?? 0,
|
|
168
|
+
url: variantUrl(variantSrc, name),
|
|
169
|
+
...images.formats.length ? { urls: Object.fromEntries(images.formats.map((format) => [format, variantUrl(variantSrc, name, format)])) } : {}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
src: disk.driver.url(source.relativePath),
|
|
174
|
+
width: width ?? 0,
|
|
175
|
+
height: height ?? 0,
|
|
176
|
+
variants,
|
|
177
|
+
...images.formats.length ? { formats: images.formats } : {}
|
|
178
|
+
};
|
|
179
|
+
} finally {
|
|
180
|
+
source.release();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
146
183
|
|
|
147
184
|
//#endregion
|
|
148
185
|
export { generateImageVariants };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-image-variants.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/generate-image-variants.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { Image } from \"../../image\";\nimport { storage } from \"../../storage\";\nimport { HttpError } from \"../errors\";\nimport { uploadsConfig } from \"../uploads-config\";\nimport type { ImageVariantOutputFormat } from \"../uploads-types\";\nimport { generateImageVariant } from \"./generate-image-variant\";\nimport {\n pruneStaleVariantGenerationsIfCurrent,\n retryOnVanishedDirectory,\n warnIfVariantCacheIsPublic,\n} from \"./image-variant-cache\";\nimport { ImageVariantsConfigError } from \"./image-variants-config-error\";\nimport type {\n GeneratedImageDescriptor,\n GeneratedImageVariant,\n VariantOutputFormat,\n} from \"./image-variant-types\";\nimport { loadVariantSource } from \"./load-variant-source\";\nimport { resolveImageVariantsConfig } from \"./resolve-image-variants-config\";\nimport { resolveVariantCandidate } from \"./resolve-variant-candidate\";\nimport {\n variantCacheKey,\n variantCachePath,\n variantGenerationTag,\n variantSourceDirectory,\n variantSourceKey,\n} from \"./variant-cache-key\";\nimport { variantGenerations } from \"./variant-generations\";\n\n/**\n * Options for `generateImageVariants`\n */\nexport type GenerateImageVariantsOptions = {\n /**\n * Limit generation to these variant names, instead of every variant in\n * `uploads.images.variants`.\n */\n variants?: readonly string[];\n};\n\nasync function exists(target: string): Promise<boolean> {\n try {\n await fs.access(target);\n\n return true;\n } catch {\n return false;\n }\n}\n\nfunction encodeUploadsPath(relativePath: string): string {\n return relativePath.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\nfunction variantUrl(src: string, name: string, format?: VariantOutputFormat): string {\n const query = format\n ? `variant=${encodeURIComponent(name)}&format=${format}`\n : `variant=${encodeURIComponent(name)}`;\n\n return `${src}?${query}`;\n}\n\nasync function readDerivativeDimensions(\n target: string,\n): Promise<{ width: number; height: number }> {\n const derivative = new Image(await fs.readFile(target));\n const { width, height } = await derivative.metadata();\n\n return { width: width ?? 0, height: height ?? 0 };\n}\n\n/**\n * Every distinct format a variant is rendered in: the source format plus\n * whatever `uploads.images.formats` adds, with duplicates dropped so a format\n * that is both the source format and an allowed format is generated once.\n */\nfunction outputFormatsFor(\n sourceFormat: VariantOutputFormat,\n configuredFormats: readonly ImageVariantOutputFormat[],\n): VariantOutputFormat[] {\n return [...new Set<VariantOutputFormat>([sourceFormat, ...configuredFormats])];\n}\n\n/**\n * Render every configured variant of one uploaded image, right after it is\n * saved.\n *\n * Uses the exact same normalized config (`uploads.images`), cache key and\n * derivative path as `uploadedFileController`, so a variant generated here is\n * already on disk the first time `GET /uploads/<path>?variant=<name>` is\n * requested — no second render. `variantGenerations` (the single-flight used\n * by the route) is reused, so a concurrent request for a variant this call is\n * still generating joins the same run instead of starting a second one.\n *\n * The returned descriptor is plain JSON, safe to store next to the record\n * that owns the image, or to carry through loader data. On local storage the\n * `url`/`urls` already point at `uploadedFileController`; an app serving\n * uploads from object storage or a CDN should not proxy the remote files\n * through this route at all — store the descriptor once generation runs\n * against the same bucket the CDN serves from, or resolve its URLs through a\n * custom `ImageLoader` on the client instead.\n *\n * @param relativePath - path of the saved upload, relative to the storage root\n * @param options.variants - limit generation to these variant names\n * @throws ImageVariantsConfigError when there is no local storage root, or\n * `uploads.images` is not configured — both are server misconfiguration, not\n * a bad request\n * @throws HttpError(400) when a name in `options.variants` is not declared in\n * `uploads.images.variants`\n * @throws HttpError(404) when `relativePath` does not resolve to a file\n * inside the storage root\n * @throws HttpError(413) when the source is over `maxSourceBytes` or\n * `maxSourcePixels`\n * @throws HttpError(415) when the source is not jpeg, png, webp or avif\n */\nexport async function generateImageVariants(\n relativePath: string,\n options: GenerateImageVariantsOptions = {},\n): Promise<GeneratedImageDescriptor> {\n const storageRoot = storage.root();\n\n if (!storageRoot || storageRoot === \".\") {\n throw new ImageVariantsConfigError(\n \"generateImageVariants needs a local storage driver with a root.\",\n );\n }\n\n const images = resolveImageVariantsConfig(uploadsConfig(\"images\"), storageRoot);\n\n if (!images) {\n throw new ImageVariantsConfigError(\"uploads.images is not configured.\");\n }\n\n const names = options.variants ?? Object.keys(images.variants);\n\n for (const name of names) {\n if (!Object.prototype.hasOwnProperty.call(images.variants, name)) {\n throw new HttpError(400, `Unknown variant \"${name}\".`);\n }\n }\n\n const candidate = await resolveVariantCandidate(relativePath, storageRoot, images);\n\n if (!candidate) {\n throw new HttpError(404, \"File Not Found\");\n }\n\n const { source, stats, sourceFormat } = candidate;\n\n const image = await loadVariantSource(source.absolutePath, {\n maxSourceBytes: images.maxSourceBytes,\n maxSourcePixels: images.maxSourcePixels,\n expectedFormat: sourceFormat,\n });\n\n const { width: sourceWidth, height: sourceHeight } = await image.metadata();\n warnIfVariantCacheIsPublic(images.cacheDirectory);\n\n const src = `/uploads/${encodeUploadsPath(source.relativePath)}`;\n const variants: Record<string, GeneratedImageVariant> = {};\n\n for (const name of names) {\n const variant = images.variants[name];\n if (variant === undefined) {\n throw new HttpError(400, `Unknown variant \"${name}\".`);\n }\n const formats = outputFormatsFor(sourceFormat, images.formats);\n let dimensions: { width: number; height: number } | undefined;\n\n for (const format of formats) {\n const hash = variantCacheKey({\n relativePath: source.relativePath,\n sourceSize: stats.size,\n sourceMtimeMs: stats.mtimeMs,\n variant,\n format,\n });\n const sourceKey = variantSourceKey(source.root, source.relativePath);\n const generation = variantGenerationTag(stats.size, stats.mtimeMs);\n const target = variantCachePath(\n images.cacheDirectory,\n { sourceKey, generation },\n hash,\n format,\n );\n\n if (!(await exists(target))) {\n // keyed on the target, which includes the disk root (see resolveImageVariant)\n await variantGenerations.run(target, async () => {\n if (await exists(target)) return;\n\n await retryOnVanishedDirectory(() =>\n generateImageVariant({ image: image.clone(), variant, format, targetPath: target }),\n );\n await pruneStaleVariantGenerationsIfCurrent(\n source.absolutePath,\n variantSourceDirectory(images.cacheDirectory, sourceKey),\n generation,\n );\n });\n }\n\n if (dimensions === undefined) {\n dimensions = await readDerivativeDimensions(target);\n }\n }\n\n const urls: Partial<Record<ImageVariantOutputFormat, string>> = {};\n\n for (const format of images.formats) {\n urls[format] = variantUrl(src, name, format);\n }\n\n variants[name] = {\n width: dimensions?.width ?? variant.width,\n height:\n dimensions?.height ??\n variant.height ??\n Math.round(variant.width * ((sourceHeight ?? 1) / (sourceWidth ?? 1))),\n url: variantUrl(src, name),\n ...(Object.keys(urls).length > 0 ? { urls } : {}),\n };\n }\n\n return {\n src,\n width: sourceWidth ?? 0,\n height: sourceHeight ?? 0,\n variants,\n ...(images.formats.length > 0 ? { formats: images.formats } : {}),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generate-image-variants.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/http/uploads/generate-image-variants.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { Image } from \"../../image\";\nimport type { StorageDriverName } from \"../../storage/types\";\nimport { localDiskRoot } from \"./local-disk-root\";\nimport { remoteVariantDisk, remoteVariantSource } from \"./remote-variant-source\";\nimport { renderRemoteImageVariant } from \"./resolve-remote-image-variant\";\nimport { HttpError } from \"../errors\";\nimport { uploadsConfig } from \"../uploads-config\";\nimport type { ImageVariantOutputFormat } from \"../uploads-types\";\nimport { generateImageVariant } from \"./generate-image-variant\";\nimport {\n pruneStaleVariantGenerationsIfCurrent,\n retryOnVanishedDirectory,\n warnIfVariantCacheIsPublic,\n} from \"./image-variant-cache\";\nimport { ImageVariantsConfigError } from \"./image-variants-config-error\";\nimport type {\n GeneratedImageDescriptor,\n GeneratedImageVariant,\n VariantOutputFormat,\n} from \"./image-variant-types\";\nimport { loadVariantSource } from \"./load-variant-source\";\nimport { resolveImageVariantsConfig } from \"./resolve-image-variants-config\";\nimport { resolveVariantCandidate } from \"./resolve-variant-candidate\";\nimport {\n variantCacheKey,\n variantCachePath,\n variantGenerationTag,\n variantSourceDirectory,\n variantSourceKey,\n} from \"./variant-cache-key\";\nimport { freshImageVariant, scheduleImageVariantCacheTrim } from \"./trim-image-variant-cache\";\nimport { variantGenerations } from \"./variant-generations\";\n\n/**\n * Options for `generateImageVariants`\n */\nexport type GenerateImageVariantsOptions = {\n /**\n * Limit generation to these variant names, instead of every variant in\n * `uploads.images.variants`.\n */\n variants?: readonly string[];\n\n /** Disk holding the source; defaults to the active disk. */\n disk?: StorageDriverName;\n\n /** Route prefix serving variants of this disk (and originals for local disks). Default /uploads. */\n urlPrefix?: string;\n};\n\nfunction encodeUploadsPath(relativePath: string): string {\n return relativePath.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\nfunction variantUrl(src: string, name: string, format?: VariantOutputFormat): string {\n const query = format\n ? `variant=${encodeURIComponent(name)}&format=${format}`\n : `variant=${encodeURIComponent(name)}`;\n\n return `${src}?${query}`;\n}\n\nasync function readDerivativeDimensions(\n target: string,\n): Promise<{ width: number; height: number }> {\n const derivative = new Image(await fs.readFile(target));\n const { width, height } = await derivative.metadata();\n\n return { width: width ?? 0, height: height ?? 0 };\n}\n\n/**\n * Every distinct format a variant is rendered in: the source format plus\n * whatever `uploads.images.formats` adds, with duplicates dropped so a format\n * that is both the source format and an allowed format is generated once.\n */\nfunction outputFormatsFor(\n sourceFormat: VariantOutputFormat,\n configuredFormats: readonly ImageVariantOutputFormat[],\n): VariantOutputFormat[] {\n return [...new Set<VariantOutputFormat>([sourceFormat, ...configuredFormats])];\n}\n\n/**\n * Render every configured variant of one uploaded image, right after it is\n * saved.\n *\n * Uses the exact same normalized config (`uploads.images`), cache key and\n * derivative path as `uploadedFileController`, so a variant generated here is\n * already on disk the first time `GET /uploads/<path>?variant=<name>` is\n * requested — no second render. `variantGenerations` (the single-flight used\n * by the route) is reused, so a concurrent request for a variant this call is\n * still generating joins the same run instead of starting a second one.\n *\n * The returned descriptor is plain JSON, safe to store next to the record\n * that owns the image, or to carry through loader data. On local storage the\n * `url`/`urls` already point at `uploadedFileController`; an app serving\n * uploads from object storage use the driver URL for the original, and\n * `options.urlPrefix` for the route serving locally cached derivatives.\n * Ingest downloads a remote source once for all variants and formats.\n * On-demand cache hits use ETag metadata; sources without ETags are hashed.\n *\n * @param relativePath - path of the saved upload, relative to the storage root\n * @param options.variants - limit generation to these variant names\n * @throws ImageVariantsConfigError when `uploads.images` is not\n * configured — this is server misconfiguration, not\n * a bad request\n * @throws HttpError(400) when a name in `options.variants` is not declared in\n * `uploads.images.variants`\n * @throws HttpError(404) when `relativePath` does not resolve to a file\n * inside the storage root\n * @throws HttpError(413) when the source is over `maxSourceBytes` or\n * `maxSourcePixels`\n * @throws HttpError(415) when the source is not jpeg, png, webp or avif\n */\nexport async function generateImageVariants(\n relativePath: string,\n options: GenerateImageVariantsOptions = {},\n): Promise<GeneratedImageDescriptor> {\n const storageRoot = localDiskRoot(options.disk);\n\n if (!storageRoot) return generateRemoteImageVariants(relativePath, options);\n\n const images = resolveImageVariantsConfig(uploadsConfig(\"images\"), storageRoot);\n\n if (!images) {\n throw new ImageVariantsConfigError(\"uploads.images is not configured.\");\n }\n\n const names = options.variants ?? Object.keys(images.variants);\n\n for (const name of names) {\n if (!Object.prototype.hasOwnProperty.call(images.variants, name)) {\n throw new HttpError(400, `Unknown variant \"${name}\".`);\n }\n }\n\n const candidate = await resolveVariantCandidate(relativePath, storageRoot, images);\n\n if (!candidate) {\n throw new HttpError(404, \"File Not Found\");\n }\n\n const { source, stats, sourceFormat } = candidate;\n\n const image = await loadVariantSource(source.absolutePath, {\n maxSourceBytes: images.maxSourceBytes,\n maxSourcePixels: images.maxSourcePixels,\n expectedFormat: sourceFormat,\n });\n\n const { width: sourceWidth, height: sourceHeight } = await image.metadata();\n warnIfVariantCacheIsPublic(images.cacheDirectory);\n\n const src = `${(options.urlPrefix ?? \"/uploads\").replace(/\\/$/, \"\")}/${encodeUploadsPath(source.relativePath)}`;\n const variants: Record<string, GeneratedImageVariant> = {};\n\n for (const name of names) {\n const variant = images.variants[name];\n if (variant === undefined) {\n throw new HttpError(400, `Unknown variant \"${name}\".`);\n }\n const formats = outputFormatsFor(sourceFormat, images.formats);\n let dimensions: { width: number; height: number } | undefined;\n\n for (const format of formats) {\n const hash = variantCacheKey({\n relativePath: source.relativePath,\n sourceSize: stats.size,\n sourceMtimeMs: stats.mtimeMs,\n variant,\n format,\n });\n const sourceKey = variantSourceKey(source.root, source.relativePath);\n const generation = variantGenerationTag(stats.size, stats.mtimeMs);\n const target = variantCachePath(\n images.cacheDirectory,\n { sourceKey, generation },\n hash,\n format,\n );\n\n if (!(await freshImageVariant(target, images.maxCacheAge))) {\n // keyed on the target, which includes the disk root (see resolveImageVariant)\n await variantGenerations.run(target, async () => {\n if (await freshImageVariant(target, images.maxCacheAge)) return;\n\n await retryOnVanishedDirectory(() =>\n generateImageVariant({ image: image.clone(), variant, format, targetPath: target }),\n );\n scheduleImageVariantCacheTrim(images, target);\n await pruneStaleVariantGenerationsIfCurrent(\n source.absolutePath,\n variantSourceDirectory(images.cacheDirectory, sourceKey),\n generation,\n );\n });\n }\n\n if (dimensions === undefined) {\n dimensions = await readDerivativeDimensions(target);\n }\n }\n\n const urls: Partial<Record<ImageVariantOutputFormat, string>> = {};\n\n for (const format of images.formats) {\n urls[format] = variantUrl(src, name, format);\n }\n\n variants[name] = {\n width: dimensions?.width ?? variant.width,\n height:\n dimensions?.height ??\n variant.height ??\n Math.round(variant.width * ((sourceHeight ?? 1) / (sourceWidth ?? 1))),\n url: variantUrl(src, name),\n ...(Object.keys(urls).length > 0 ? { urls } : {}),\n };\n }\n\n return {\n src,\n width: sourceWidth ?? 0,\n height: sourceHeight ?? 0,\n variants,\n ...(images.formats.length > 0 ? { formats: images.formats } : {}),\n };\n}\n\nasync function generateRemoteImageVariants(\n relativePath: string,\n options: GenerateImageVariantsOptions,\n): Promise<GeneratedImageDescriptor> {\n const disk = remoteVariantDisk(options.disk);\n const images = resolveImageVariantsConfig(uploadsConfig(\"images\"), disk.cacheRoot);\n if (!images) throw new ImageVariantsConfigError(\"uploads.images is not configured.\");\n const names = options.variants ?? Object.keys(images.variants);\n for (const name of names) {\n if (!Object.prototype.hasOwnProperty.call(images.variants, name))\n throw new HttpError(400, `Unknown variant \"${name}\".`);\n }\n const source = await remoteVariantSource(relativePath, disk, images);\n if (!source) throw new HttpError(404, \"File Not Found\");\n try {\n const { image, sourceFormat } = await source.load();\n const { width, height } = await image.metadata();\n const variantSrc = `${(options.urlPrefix ?? \"/uploads\").replace(/\\/$/, \"\")}/${encodeUploadsPath(source.relativePath)}`;\n const variants: Record<string, GeneratedImageVariant> = {};\n for (const name of names) {\n let dimensions: { width: number; height: number } | undefined;\n for (const format of outputFormatsFor(sourceFormat, images.formats)) {\n const resolved = await renderRemoteImageVariant(\n {\n path: relativePath,\n variant: name,\n ...(format === sourceFormat ? {} : { format }),\n },\n disk,\n images,\n source,\n );\n if (!resolved) throw new HttpError(404, \"File Not Found\");\n dimensions ??= await readDerivativeDimensions(resolved.absolutePath);\n }\n variants[name] = {\n width: dimensions?.width ?? 0,\n height: dimensions?.height ?? 0,\n url: variantUrl(variantSrc, name),\n ...(images.formats.length\n ? {\n urls: Object.fromEntries(\n images.formats.map((format) => [format, variantUrl(variantSrc, name, format)]),\n ),\n }\n : {}),\n };\n }\n return {\n src: disk.driver.url(source.relativePath),\n width: width ?? 0,\n height: height ?? 0,\n variants,\n ...(images.formats.length ? { formats: images.formats } : {}),\n };\n } finally {\n source.release();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmDA,SAAS,kBAAkB,cAA8B;CACvD,OAAO,aAAa,MAAM,GAAG,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,KAAK,GAAG;AACjE;AAEA,SAAS,WAAW,KAAa,MAAc,QAAsC;CAKnF,OAAO,GAAG,IAAI,GAJA,SACV,WAAW,mBAAmB,IAAI,EAAE,UAAU,WAC9C,WAAW,mBAAmB,IAAI;AAGxC;AAEA,eAAe,yBACb,QAC4C;CAE5C,MAAM,EAAE,OAAO,WAAW,MAAM,IADT,MAAM,MAAM,GAAG,SAAS,MAAM,CACZ,CAAC,CAAC,SAAS;CAEpD,OAAO;EAAE,OAAO,SAAS;EAAG,QAAQ,UAAU;CAAE;AAClD;;;;;;AAOA,SAAS,iBACP,cACA,mBACuB;CACvB,OAAO,CAAC,GAAG,IAAI,IAAyB,CAAC,cAAc,GAAG,iBAAiB,CAAC,CAAC;AAC/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,eAAsB,sBACpB,cACA,UAAwC,CAAC,GACN;CACnC,MAAM,cAAc,cAAc,QAAQ,IAAI;CAE9C,IAAI,CAAC,aAAa,OAAO,4BAA4B,cAAc,OAAO;CAE1E,MAAM,SAAS,2BAA2B,cAAc,QAAQ,GAAG,WAAW;CAE9E,IAAI,CAAC,QACH,MAAM,IAAI,yBAAyB,mCAAmC;CAGxE,MAAM,QAAQ,QAAQ,YAAY,OAAO,KAAK,OAAO,QAAQ;CAE7D,KAAK,MAAM,QAAQ,OACjB,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,OAAO,UAAU,IAAI,GAC7D,MAAM,IAAI,UAAU,KAAK,oBAAoB,KAAK,GAAG;CAIzD,MAAM,YAAY,MAAM,wBAAwB,cAAc,aAAa,MAAM;CAEjF,IAAI,CAAC,WACH,MAAM,IAAI,UAAU,KAAK,gBAAgB;CAG3C,MAAM,EAAE,QAAQ,OAAO,iBAAiB;CAExC,MAAM,QAAQ,MAAM,kBAAkB,OAAO,cAAc;EACzD,gBAAgB,OAAO;EACvB,iBAAiB,OAAO;EACxB,gBAAgB;CAClB,CAAC;CAED,MAAM,EAAE,OAAO,aAAa,QAAQ,iBAAiB,MAAM,MAAM,SAAS;CAC1E,2BAA2B,OAAO,cAAc;CAEhD,MAAM,MAAM,IAAI,QAAQ,aAAa,WAAU,CAAE,QAAQ,OAAO,EAAE,EAAE,GAAG,kBAAkB,OAAO,YAAY;CAC5G,MAAM,WAAkD,CAAC;CAEzD,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,OAAO,SAAS;EAChC,IAAI,YAAY,QACd,MAAM,IAAI,UAAU,KAAK,oBAAoB,KAAK,GAAG;EAEvD,MAAM,UAAU,iBAAiB,cAAc,OAAO,OAAO;EAC7D,IAAI;EAEJ,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,OAAO,gBAAgB;IAC3B,cAAc,OAAO;IACrB,YAAY,MAAM;IAClB,eAAe,MAAM;IACrB;IACA;GACF,CAAC;GACD,MAAM,YAAY,iBAAiB,OAAO,MAAM,OAAO,YAAY;GACnE,MAAM,aAAa,qBAAqB,MAAM,MAAM,MAAM,OAAO;GACjE,MAAM,SAAS,iBACb,OAAO,gBACP;IAAE;IAAW;GAAW,GACxB,MACA,MACF;GAEA,IAAI,CAAE,MAAM,kBAAkB,QAAQ,OAAO,WAAW,GAEtD,MAAM,mBAAmB,IAAI,QAAQ,YAAY;IAC/C,IAAI,MAAM,kBAAkB,QAAQ,OAAO,WAAW,GAAG;IAEzD,MAAM,+BACJ,qBAAqB;KAAE,OAAO,MAAM,MAAM;KAAG;KAAS;KAAQ,YAAY;IAAO,CAAC,CACpF;IACA,8BAA8B,QAAQ,MAAM;IAC5C,MAAM,sCACJ,OAAO,cACP,uBAAuB,OAAO,gBAAgB,SAAS,GACvD,UACF;GACF,CAAC;GAGH,IAAI,eAAe,QACjB,aAAa,MAAM,yBAAyB,MAAM;EAEtD;EAEA,MAAM,OAA0D,CAAC;EAEjE,KAAK,MAAM,UAAU,OAAO,SAC1B,KAAK,UAAU,WAAW,KAAK,MAAM,MAAM;EAG7C,SAAS,QAAQ;GACf,OAAO,YAAY,SAAS,QAAQ;GACpC,QACE,YAAY,UACZ,QAAQ,UACR,KAAK,MAAM,QAAQ,UAAU,gBAAgB,MAAM,eAAe,GAAG;GACvE,KAAK,WAAW,KAAK,IAAI;GACzB,GAAI,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;EACjD;CACF;CAEA,OAAO;EACL;EACA,OAAO,eAAe;EACtB,QAAQ,gBAAgB;EACxB;EACA,GAAI,OAAO,QAAQ,SAAS,IAAI,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;CACjE;AACF;AAEA,eAAe,4BACb,cACA,SACmC;CACnC,MAAM,OAAO,kBAAkB,QAAQ,IAAI;CAC3C,MAAM,SAAS,2BAA2B,cAAc,QAAQ,GAAG,KAAK,SAAS;CACjF,IAAI,CAAC,QAAQ,MAAM,IAAI,yBAAyB,mCAAmC;CACnF,MAAM,QAAQ,QAAQ,YAAY,OAAO,KAAK,OAAO,QAAQ;CAC7D,KAAK,MAAM,QAAQ,OACjB,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,OAAO,UAAU,IAAI,GAC7D,MAAM,IAAI,UAAU,KAAK,oBAAoB,KAAK,GAAG;CAEzD,MAAM,SAAS,MAAM,oBAAoB,cAAc,MAAM,MAAM;CACnE,IAAI,CAAC,QAAQ,MAAM,IAAI,UAAU,KAAK,gBAAgB;CACtD,IAAI;EACF,MAAM,EAAE,OAAO,iBAAiB,MAAM,OAAO,KAAK;EAClD,MAAM,EAAE,OAAO,WAAW,MAAM,MAAM,SAAS;EAC/C,MAAM,aAAa,IAAI,QAAQ,aAAa,WAAU,CAAE,QAAQ,OAAO,EAAE,EAAE,GAAG,kBAAkB,OAAO,YAAY;EACnH,MAAM,WAAkD,CAAC;EACzD,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI;GACJ,KAAK,MAAM,UAAU,iBAAiB,cAAc,OAAO,OAAO,GAAG;IACnE,MAAM,WAAW,MAAM,yBACrB;KACE,MAAM;KACN,SAAS;KACT,GAAI,WAAW,eAAe,CAAC,IAAI,EAAE,OAAO;IAC9C,GACA,MACA,QACA,MACF;IACA,IAAI,CAAC,UAAU,MAAM,IAAI,UAAU,KAAK,gBAAgB;IACxD,eAAe,MAAM,yBAAyB,SAAS,YAAY;GACrE;GACA,SAAS,QAAQ;IACf,OAAO,YAAY,SAAS;IAC5B,QAAQ,YAAY,UAAU;IAC9B,KAAK,WAAW,YAAY,IAAI;IAChC,GAAI,OAAO,QAAQ,SACf,EACE,MAAM,OAAO,YACX,OAAO,QAAQ,KAAK,WAAW,CAAC,QAAQ,WAAW,YAAY,MAAM,MAAM,CAAC,CAAC,CAC/E,EACF,IACA,CAAC;GACP;EACF;EACA,OAAO;GACL,KAAK,KAAK,OAAO,IAAI,OAAO,YAAY;GACxC,OAAO,SAAS;GAChB,QAAQ,UAAU;GAClB;GACA,GAAI,OAAO,QAAQ,SAAS,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;EAC7D;CACF,UAAU;EACR,OAAO,QAAQ;CACjB;AACF"}
|