@warlock.js/core 5.19.1 → 5.21.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 +1136 -1073
- package/esm/benchmark/benchmark.mjs +34 -25
- package/esm/benchmark/benchmark.mjs.map +1 -1
- package/esm/benchmark/profiler.d.mts.map +1 -1
- package/esm/benchmark/profiler.mjs +8 -3
- package/esm/benchmark/profiler.mjs.map +1 -1
- package/esm/bootstrap.d.mts.map +1 -1
- package/esm/bootstrap.mjs +3 -0
- package/esm/bootstrap.mjs.map +1 -1
- package/esm/cache/database-cache-driver.d.mts +2 -2
- package/esm/cache/database-cache-driver.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.mjs +3 -2
- package/esm/cache/database-cache-driver.mjs.map +1 -1
- package/esm/cli/cli-commands.manager.mjs +16 -14
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/commands/add.command.mjs +1 -1
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +3 -2
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs +20 -0
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs +4 -2
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/controller.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs +22 -6
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs +22 -7
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +8 -6
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/repository.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/repository.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/resource.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/service.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/templates/stubs.mjs +7 -7
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/writer.mjs +25 -1
- package/esm/cli/commands/generate/utils/writer.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs +5 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes/with-console-on-stderr.mjs +24 -0
- package/esm/cli/commands/routes/with-console-on-stderr.mjs.map +1 -0
- package/esm/cli/commands/routes.command.mjs +3 -4
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +5 -5
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs +2 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs +1 -0
- package/esm/cli/commands/update.command.mjs.map +1 -1
- package/esm/cli/parse-cli-args.mjs +13 -1
- package/esm/cli/parse-cli-args.mjs.map +1 -1
- package/esm/commands/cli-command.d.mts.map +1 -1
- package/esm/commands/cli-command.mjs +4 -2
- package/esm/commands/cli-command.mjs.map +1 -1
- package/esm/commands/index.mjs +3 -0
- package/esm/config/config-key-from-path.mjs +20 -0
- package/esm/config/config-key-from-path.mjs.map +1 -0
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs +2 -3
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/cache-connector.d.mts.map +1 -1
- package/esm/connectors/cache-connector.mjs +5 -0
- package/esm/connectors/cache-connector.mjs.map +1 -1
- package/esm/connectors/connectors-manager.d.mts +4 -0
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +30 -2
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/single-server-warnings.mjs +33 -0
- package/esm/connectors/single-server-warnings.mjs.map +1 -0
- package/esm/connectors/socket-connector.d.mts +29 -0
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs +77 -9
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/storage.connector.d.mts.map +1 -1
- package/esm/connectors/storage.connector.mjs +6 -0
- package/esm/connectors/storage.connector.mjs.map +1 -1
- package/esm/connectors/types.d.mts +4 -2
- package/esm/connectors/types.d.mts.map +1 -1
- package/esm/connectors/types.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.d.mts +47 -24
- package/esm/database/models/database-log/database-log.d.mts.map +1 -1
- package/esm/database/models/database-log/database-log.mjs +36 -4
- package/esm/database/models/database-log/database-log.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs +10 -2
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +15 -4
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs +26 -6
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.d.mts.map +1 -1
- package/esm/dev-server/file-manager.mjs +2 -2
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +3 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +64 -5
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs +31 -9
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
- package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +11 -7
- package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +1 -1
- package/esm/dev-server/loader/load-hook.mjs +1 -1
- package/esm/dev-server/loader/register-loader.mjs +1 -1
- package/esm/dev-server/manifest-manager.d.mts.map +1 -1
- package/esm/dev-server/manifest-manager.mjs +12 -6
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +9 -0
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/register-dev-shortcuts.mjs +2 -1
- package/esm/dev-server/register-dev-shortcuts.mjs.map +1 -1
- package/esm/dev-server/route-types-publisher.mjs +60 -0
- package/esm/dev-server/route-types-publisher.mjs.map +1 -0
- package/esm/dev-server/shortcuts.mjs +2 -2
- package/esm/dev-server/shortcuts.mjs.map +1 -1
- package/esm/dev-server/special-file-patterns.mjs +18 -0
- package/esm/dev-server/special-file-patterns.mjs.map +1 -0
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs +2 -5
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/start-development-server.mjs +1 -2
- package/esm/dev-server/start-development-server.mjs.map +1 -1
- package/esm/dev-server/supervisor.mjs +23 -3
- package/esm/dev-server/supervisor.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs +22 -12
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +10 -9
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/encryption/encrypt.d.mts.map +1 -1
- package/esm/encryption/encrypt.mjs +16 -5
- package/esm/encryption/encrypt.mjs.map +1 -1
- package/esm/encryption/hash.d.mts.map +1 -1
- package/esm/encryption/hash.mjs +2 -0
- package/esm/encryption/hash.mjs.map +1 -1
- package/esm/encryption/password.d.mts.map +1 -1
- package/esm/encryption/password.mjs +3 -1
- package/esm/encryption/password.mjs.map +1 -1
- package/esm/generations/features/index.mjs +1 -1
- package/esm/generations/features/mysql.feature.mjs +9 -2
- package/esm/generations/features/mysql.feature.mjs.map +1 -1
- package/esm/generations/features/test.feature.mjs +2 -1
- package/esm/generations/features/test.feature.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +4 -2
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/http/build-rate-limit-options.d.mts +37 -0
- package/esm/http/build-rate-limit-options.d.mts.map +1 -0
- package/esm/http/build-rate-limit-options.mjs +37 -0
- package/esm/http/build-rate-limit-options.mjs.map +1 -0
- package/esm/http/csrf-origin-policy.d.mts.map +1 -1
- package/esm/http/csrf-origin-policy.mjs +5 -2
- package/esm/http/csrf-origin-policy.mjs.map +1 -1
- package/esm/http/flush-pending-cookies.mjs +52 -0
- package/esm/http/flush-pending-cookies.mjs.map +1 -0
- package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs +4 -2
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.d.mts +5 -0
- package/esm/http/middleware/idempotency.middleware.d.mts.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs +35 -3
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +14 -3
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.d.mts +4 -3
- package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs +4 -3
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/middleware/utils/cidr-match.d.mts +1 -1
- package/esm/http/middleware/utils/cidr-match.d.mts.map +1 -1
- package/esm/http/middleware/utils/cidr-match.mjs +11 -2
- package/esm/http/middleware/utils/cidr-match.mjs.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
- package/esm/http/plugins.d.mts.map +1 -1
- package/esm/http/plugins.mjs +13 -5
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/request.d.mts +12 -1
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +35 -7
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +16 -0
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +75 -37
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/types.d.mts +26 -14
- package/esm/http/types.d.mts.map +1 -1
- package/esm/http/uploads/uploaded-file.controller.mjs +1 -1
- package/esm/http/uploads-config.mjs +2 -1
- package/esm/http/uploads-config.mjs.map +1 -1
- package/esm/http/uploads-types.d.mts +3 -3
- package/esm/image/image.d.mts +1 -1
- package/esm/image/image.d.mts.map +1 -1
- package/esm/image/image.mjs +25 -15
- package/esm/image/image.mjs.map +1 -1
- package/esm/index.d.mts +6 -4
- package/esm/index.mjs +7 -4
- package/esm/logger/logger.mjs +6 -1
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/config.d.mts.map +1 -1
- package/esm/mail/config.mjs +31 -21
- package/esm/mail/config.mjs.map +1 -1
- package/esm/mail/react-mail.mjs +6 -3
- package/esm/mail/react-mail.mjs.map +1 -1
- package/esm/mail/send-mail.d.mts.map +1 -1
- package/esm/mail/send-mail.mjs +40 -19
- package/esm/mail/send-mail.mjs.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/build-contributions.mjs +3 -0
- package/esm/production/build-contributions.mjs.map +1 -1
- package/esm/production/esbuild-plugins.mjs +3 -2
- package/esm/production/esbuild-plugins.mjs.map +1 -1
- package/esm/production/production-builder.mjs +49 -37
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs +12 -5
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/route-registration-child.d.mts +1 -0
- package/esm/production/route-registration-child.mjs +65 -0
- package/esm/production/route-registration-child.mjs.map +1 -0
- package/esm/production/route-registration-snapshot.mjs +166 -0
- package/esm/production/route-registration-snapshot.mjs.map +1 -0
- package/esm/repositories/adapters/cascade/cascade-adapter.d.mts +3 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.mjs +22 -12
- package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +3 -3
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/filter-applicator.mjs +88 -61
- package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/index.d.mts +1 -1
- package/esm/repositories/adapters/cascade/index.mjs +1 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts +3 -3
- package/esm/repositories/contracts/types.d.mts +5 -0
- package/esm/repositories/contracts/types.d.mts.map +1 -1
- package/esm/repositories/index.d.mts +1 -1
- package/esm/repositories/index.mjs +1 -1
- package/esm/repositories/repository.manager.d.mts +65 -3
- package/esm/repositories/repository.manager.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs +146 -57
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/resource/resource-field-builder.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.mjs +1 -0
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts +10 -3
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs +19 -5
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/restful/restful.d.mts +12 -5
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs +47 -16
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/build-route-rate-limit.mjs +22 -0
- package/esm/router/build-route-rate-limit.mjs.map +1 -0
- package/esm/router/dev-dispatch.mjs +60 -0
- package/esm/router/dev-dispatch.mjs.map +1 -0
- package/esm/router/route-builder.mjs +4 -1
- package/esm/router/route-builder.mjs.map +1 -1
- package/esm/router/route-registry.d.mts.map +1 -1
- package/esm/router/route-registry.mjs +2 -2
- package/esm/router/route-registry.mjs.map +1 -1
- package/esm/router/router.d.mts +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +57 -39
- package/esm/router/router.mjs.map +1 -1
- package/esm/socket/index.d.mts +1 -1
- package/esm/socket/types.d.mts +25 -3
- package/esm/socket/types.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.d.mts +26 -0
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs +100 -16
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/storage/drivers/do-spaces-driver.d.mts +7 -0
- package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
- package/esm/storage/drivers/do-spaces-driver.mjs +9 -2
- package/esm/storage/drivers/do-spaces-driver.mjs.map +1 -1
- package/esm/storage/drivers/local-driver.d.mts +16 -5
- package/esm/storage/drivers/local-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.mjs +71 -28
- package/esm/storage/drivers/local-driver.mjs.map +1 -1
- package/esm/storage/index.d.mts +3 -1
- package/esm/storage/index.mjs +4 -2
- package/esm/storage/scoped-storage.d.mts +27 -1
- package/esm/storage/scoped-storage.d.mts.map +1 -1
- package/esm/storage/scoped-storage.mjs +40 -8
- package/esm/storage/scoped-storage.mjs.map +1 -1
- package/esm/storage/storage.d.mts +11 -5
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/storage.mjs +39 -16
- package/esm/storage/storage.mjs.map +1 -1
- package/esm/storage/types.d.mts +43 -4
- package/esm/storage/types.d.mts.map +1 -1
- package/esm/storage/utils/classify-ip-address.d.mts +13 -0
- package/esm/storage/utils/classify-ip-address.d.mts.map +1 -0
- package/esm/storage/utils/classify-ip-address.mjs +98 -0
- package/esm/storage/utils/classify-ip-address.mjs.map +1 -0
- package/esm/storage/utils/safe-fetch.d.mts +3 -12
- package/esm/storage/utils/safe-fetch.d.mts.map +1 -1
- package/esm/storage/utils/safe-fetch.mjs +3 -48
- package/esm/storage/utils/safe-fetch.mjs.map +1 -1
- package/esm/storage/utils/storage-capability-error.d.mts +25 -0
- package/esm/storage/utils/storage-capability-error.d.mts.map +1 -0
- package/esm/storage/utils/storage-capability-error.mjs +26 -0
- package/esm/storage/utils/storage-capability-error.mjs.map +1 -0
- package/esm/updater/update-warlock-packages.mjs +11 -1
- package/esm/updater/update-warlock-packages.mjs.map +1 -1
- package/esm/use-cases/use-case-broadcast.mjs +7 -1
- package/esm/use-cases/use-case-broadcast.mjs.map +1 -1
- package/esm/use-cases/use-cases-registry.mjs +21 -2
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/database-log.mjs +0 -3
- package/esm/utils/database-log.mjs.map +1 -1
- package/esm/utils/queue.d.mts +6 -4
- package/esm/utils/queue.d.mts.map +1 -1
- package/esm/utils/queue.mjs +20 -9
- package/esm/utils/queue.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-id.mjs +6 -2
- package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
- package/esm/validation/database/unique-except-current-id.mjs +6 -2
- package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
- package/esm/validation/init.mjs +6 -2
- package/esm/validation/init.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +3 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +14 -1
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs +3 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/esm/warlock-config/warn-deprecated-config.mjs +25 -0
- package/esm/warlock-config/warn-deprecated-config.mjs.map +1 -0
- package/llms-full.txt +71 -42
- package/llms.txt +41 -41
- package/package.json +21 -15
- package/skills/add-connector/SKILL.md +5 -1
- package/skills/benchmark-code/SKILL.md +1 -1
- package/skills/build-restful/SKILL.md +1 -1
- package/skills/build-url/SKILL.md +1 -1
- package/skills/configure-app/SKILL.md +7 -1
- package/skills/create-controller/SKILL.md +1 -1
- package/skills/create-module/SKILL.md +1 -1
- package/skills/define-resource/SKILL.md +1 -1
- package/skills/encrypt-data/SKILL.md +1 -1
- package/skills/hash-password/SKILL.md +1 -1
- package/skills/health-checks/SKILL.md +1 -1
- package/skills/index.md +30 -0
- package/skills/lower-stage3-decorators/SKILL.md +1 -1
- package/skills/process-image/SKILL.md +1 -1
- package/skills/register-route/SKILL.md +1 -1
- package/skills/request-memo/SKILL.md +1 -1
- package/skills/request-tracing/SKILL.md +1 -1
- package/skills/resolve-path/SKILL.md +1 -1
- package/skills/retry-operation/SKILL.md +1 -1
- package/skills/run-app/SKILL.md +1 -1
- package/skills/send-mail/SKILL.md +1 -1
- package/skills/send-response/SKILL.md +1 -1
- package/skills/store-file/SKILL.md +11 -1
- package/skills/test-http/SKILL.md +1 -1
- package/skills/test-service/SKILL.md +1 -1
- package/skills/update-packages/SKILL.md +1 -1
- package/skills/upload-file/SKILL.md +1 -1
- package/skills/use-app-context/SKILL.md +1 -1
- package/skills/use-localization/SKILL.md +1 -1
- package/skills/use-middleware/SKILL.md +7 -2
- package/skills/use-model-transformers/SKILL.md +1 -1
- package/skills/use-repository/SKILL.md +1 -1
- package/skills/use-request-locals/SKILL.md +1 -1
- package/skills/validate-input/SKILL.md +1 -1
- package/skills/warlock-conventions/SKILL.md +1 -1
- package/skills/warlock-doctor/SKILL.md +1 -1
- package/skills/warlock-routes/SKILL.md +1 -1
- package/skills/wire-socket/SKILL.md +5 -1
- package/skills/write-cli-command/SKILL.md +1 -1
- package/skills/write-middleware/SKILL.md +1 -1
- package/skills/write-seeder/SKILL.md +1 -1
- package/skills/write-use-case/SKILL.md +1 -1
- package/esm/_virtual/_rolldown/runtime.mjs +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-cli-args.mjs","names":[],"sources":["../../../../../../../core/src/cli/parse-cli-args.ts"],"sourcesContent":["import { toCamelCase } from \"@mongez/reinforcements\";\n\nexport type ParsedCliArgs = {\n name: string;\n args: string[];\n options: Record<string, string | boolean>;\n};\n\n/**\n * The slice of a declared command option this parser needs.\n *\n * `CLICommand.commandOptions` (`ResolvedCLICommandOption[]`) satisfies it, but\n * the parser stays independent of the command classes so it can be unit-tested\n * and reused with a hand-written schema.\n */\nexport type CliOptionSchema = {\n name?: string;\n alias?: string;\n type?: \"string\" | \"boolean\" | \"number\";\n};\n\n/**\n * Thrown when a declared boolean option is given a value that is neither\n * true-ish nor false-ish.\n *\n * We refuse to guess. Guessing is exactly what produced #21: `--rollback=false`\n * was kept as the truthy string `\"false\"` and dropped every table. An option\n * that gates a destructive action must fail loudly on input it cannot read.\n */\nexport class CliOptionValueError extends Error {\n public constructor(\n public readonly optionToken: string,\n public readonly value: string,\n ) {\n super(\n `Invalid value ${JSON.stringify(value)} for boolean option ${optionToken}. ` +\n `Expected one of: true, false, 1, 0, yes, no — or pass ${optionToken} on its own for true.`,\n );\n\n this.name = \"CliOptionValueError\";\n }\n}\n\nconst TRUE_VALUES = new Set([\"true\", \"1\", \"yes\"]);\nconst FALSE_VALUES = new Set([\"false\", \"0\", \"no\"]);\n\n/**\n * Collect the camelCased keys (names AND aliases) of every option the command\n * declared as `type: \"boolean\"`.\n *\n * Both sides are camelCased because declared names keep their raw kebab form\n * (`\"pending-only\"`) while the parser emits camelCase keys (`pendingOnly`).\n */\nfunction collectBooleanKeys(schema: CliOptionSchema[]): Set<string> {\n const keys = new Set<string>();\n\n for (const option of schema) {\n if (option.type !== \"boolean\") continue;\n\n if (option.name) {\n keys.add(toCamelCase(option.name));\n }\n\n if (option.alias) {\n keys.add(toCamelCase(option.alias));\n }\n }\n\n return keys;\n}\n\n/**\n * Turn the value written after `=` into a real boolean, or throw.\n */\nfunction toBoolean(optionToken: string, value: string): boolean {\n const normalized = value.toLowerCase();\n\n if (TRUE_VALUES.has(normalized)) return true;\n if (FALSE_VALUES.has(normalized)) return false;\n\n throw new CliOptionValueError(optionToken, value);\n}\n\n/**\n * Parse CLI arguments from process.argv\n *\n * Called with a `schema` (the resolved command's declared options), every\n * option declared `type: \"boolean\"` is resolved as a boolean:\n * `--flag` → true, `--flag=false|0|no` → false, `--flag=true|1|yes` → true,\n * and `--flag <token>` leaves `<token>` as a POSITIONAL instead of swallowing\n * it as the flag's value. Anything else throws {@link CliOptionValueError}.\n *\n * Called without a schema — which is how the command name is discovered,\n * before any command object exists — behaviour is unchanged: every value stays\n * a raw string and a bare `--flag` still consumes the following non-dash token.\n * Nothing is blanket-coerced here on purpose: `--name=false` on a string-typed\n * option must survive as the string `\"false\"`.\n *\n * Options the schema does not declare keep the schema-less behaviour.\n *\n * @example\n * parseCliArgs([\"node\", \"warlock\", \"migrate\", \"--rollback\", \"file.ts\"])\n * // Returns: { name: \"migrate\", args: [], options: { rollback: \"file.ts\" } }\n *\n * @example\n * // `rollback` declared as type: \"boolean\" — the file stays a positional\n * parseCliArgs([\"node\", \"warlock\", \"migrate\", \"--rollback\", \"file.ts\"], migrateOptions)\n * // Returns: { name: \"migrate\", args: [\"file.ts\"], options: { rollback: true } }\n *\n * @example\n * parseCliArgs([\"node\", \"warlock\", \"dev\", \"--port=3000\", \"--fresh\"])\n * // Returns: { name: \"dev\", args: [], options: { port: \"3000\", fresh: true } }\n */\nexport function parseCliArgs(argv: string[], schema: CliOptionSchema[] = []): ParsedCliArgs {\n // Command is at index 2 (after \"node\" and script path)\n // But if index 2 starts with \"-\", it's an option, not a command\n const potentialCommand = argv[2] || \"\";\n const isFirstArgOption = potentialCommand.startsWith(\"-\");\n const command = isFirstArgOption ? \"\" : potentialCommand;\n const args: string[] = [];\n const options: Record<string, string | boolean> = {};\n const booleanKeys = collectBooleanKeys(schema);\n\n // Parse arguments starting from index 3 (or 2 if first arg was an option)\n const startIndex = isFirstArgOption ? 2 : 3;\n for (let i = startIndex; i < argv.length; i++) {\n const arg = argv[i];\n\n // `i < argv.length` bounds this, but the compiler does not narrow an index\n // read from a loop condition. Skipping rather than coercing: this file\n // already refuses to guess about an operator's input (see\n // `CliOptionValueError` above), and an `?? \"\"` here would silently push an\n // empty POSITIONAL onto `args` — which for a command that takes a file or\n // a table name is a scope the operator never typed.\n if (arg === undefined) continue;\n\n if (arg.startsWith(\"--\")) {\n // Long option: --key or --key=value\n const withoutDashes = arg.slice(2);\n const equalIndex = withoutDashes.indexOf(\"=\");\n\n if (equalIndex !== -1) {\n // --key=value format\n const key = toCamelCase(withoutDashes.slice(0, equalIndex));\n const value = withoutDashes.slice(equalIndex + 1);\n options[key] = booleanKeys.has(key)\n ? toBoolean(`--${withoutDashes.slice(0, equalIndex)}`, value)\n : value;\n } else {\n // --key format (check if next arg is a value)\n const key = toCamelCase(withoutDashes);\n const nextArg = argv[i + 1];\n\n // A declared boolean NEVER consumes the next token: `--rollback\n // users.ts` means \"rollback, and here is a positional\", not \"rollback\n // the file users.ts\". Swallowing it silently discarded the operator's\n // only stated scope and rolled back everything.\n if (booleanKeys.has(key)) {\n options[key] = true;\n } else if (nextArg && !nextArg.startsWith(\"-\")) {\n // If next arg exists and doesn't start with -, treat it as value\n options[key] = nextArg;\n i++; // Skip next arg\n } else {\n options[key] = true;\n }\n }\n } else if (arg.startsWith(\"-\") && arg.length > 1) {\n // Short option: -f, -t=value, or -abc (multiple flags)\n const flags = arg.slice(1);\n const equalIndex = flags.indexOf(\"=\");\n\n if (equalIndex !== -1) {\n // -t=value format\n const rawKey = flags.slice(0, equalIndex);\n const key = toCamelCase(rawKey);\n const value = flags.slice(equalIndex + 1);\n options[key] = booleanKeys.has(key) ? toBoolean(`-${rawKey}`, value) : value;\n } else if (flags.length === 1) {\n // Single flag: -f\n const key = toCamelCase(flags);\n const nextArg = argv[i + 1];\n\n if (booleanKeys.has(key)) {\n options[key] = true;\n } else if (nextArg && !nextArg.startsWith(\"-\")) {\n options[key] = nextArg;\n i++; // Skip next arg\n } else {\n options[key] = true;\n }\n } else {\n // Multiple flags: -abc becomes { a: true, b: true, c: true }\n // A bundle carries no value and never consumes the next token, so a\n // declared boolean already resolves to `true` here.\n for (const flag of flags) {\n options[toCamelCase(flag)] = true;\n }\n }\n } else {\n // Positional argument\n args.push(arg);\n }\n }\n\n return { name: command, args, options };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parse-cli-args.mjs","names":[],"sources":["../../../../../../../core/src/cli/parse-cli-args.ts"],"sourcesContent":["import { toCamelCase } from \"@mongez/reinforcements\";\n\nexport type ParsedCliArgs = {\n name: string;\n args: string[];\n options: Record<string, string | boolean>;\n};\n\n/**\n * The slice of a declared command option this parser needs.\n *\n * `CLICommand.commandOptions` (`ResolvedCLICommandOption[]`) satisfies it, but\n * the parser stays independent of the command classes so it can be unit-tested\n * and reused with a hand-written schema.\n */\nexport type CliOptionSchema = {\n name?: string;\n alias?: string;\n type?: \"string\" | \"boolean\" | \"number\";\n};\n\n/**\n * Thrown when a declared boolean option is given a value that is neither\n * true-ish nor false-ish.\n *\n * We refuse to guess. Guessing is exactly what produced #21: `--rollback=false`\n * was kept as the truthy string `\"false\"` and dropped every table. An option\n * that gates a destructive action must fail loudly on input it cannot read.\n */\nexport class CliOptionValueError extends Error {\n public constructor(\n public readonly optionToken: string,\n public readonly value: string,\n ) {\n super(\n `Invalid value ${JSON.stringify(value)} for boolean option ${optionToken}. ` +\n `Expected one of: true, false, 1, 0, yes, no — or pass ${optionToken} on its own for true.`,\n );\n\n this.name = \"CliOptionValueError\";\n }\n}\n\nconst TRUE_VALUES = new Set([\"true\", \"1\", \"yes\"]);\nconst FALSE_VALUES = new Set([\"false\", \"0\", \"no\"]);\n\n/**\n * Collect the camelCased keys (names AND aliases) of every option the command\n * declared as `type: \"boolean\"`.\n *\n * Both sides are camelCased because declared names keep their raw kebab form\n * (`\"pending-only\"`) while the parser emits camelCase keys (`pendingOnly`).\n */\nfunction collectBooleanKeys(schema: CliOptionSchema[]): Set<string> {\n const keys = new Set<string>();\n\n for (const option of schema) {\n if (option.type !== \"boolean\") continue;\n\n if (option.name) {\n keys.add(toCamelCase(option.name));\n }\n\n if (option.alias) {\n keys.add(toCamelCase(option.alias));\n }\n }\n\n return keys;\n}\n\n/**\n * Collect the declared aliases longer than one character (`-st`, `-rs`, `-pm`).\n *\n * A cluster equal to one of these is that alias, not a bundle of single-letter\n * flags, so it has to be matched before the bundle split.\n */\nfunction collectMultiCharAliases(schema: CliOptionSchema[]): Set<string> {\n const aliases = new Set<string>();\n\n for (const option of schema) {\n if (option.alias && option.alias.length > 1) {\n aliases.add(option.alias);\n }\n }\n\n return aliases;\n}\n\n/**\n * Turn the value written after `=` into a real boolean, or throw.\n */\nfunction toBoolean(optionToken: string, value: string): boolean {\n const normalized = value.toLowerCase();\n\n if (TRUE_VALUES.has(normalized)) return true;\n if (FALSE_VALUES.has(normalized)) return false;\n\n throw new CliOptionValueError(optionToken, value);\n}\n\n/**\n * Parse CLI arguments from process.argv\n *\n * Called with a `schema` (the resolved command's declared options), every\n * option declared `type: \"boolean\"` is resolved as a boolean:\n * `--flag` → true, `--flag=false|0|no` → false, `--flag=true|1|yes` → true,\n * and `--flag <token>` leaves `<token>` as a POSITIONAL instead of swallowing\n * it as the flag's value. Anything else throws {@link CliOptionValueError}.\n *\n * Called without a schema — which is how the command name is discovered,\n * before any command object exists — behaviour is unchanged: every value stays\n * a raw string and a bare `--flag` still consumes the following non-dash token.\n * Nothing is blanket-coerced here on purpose: `--name=false` on a string-typed\n * option must survive as the string `\"false\"`.\n *\n * Options the schema does not declare keep the schema-less behaviour.\n *\n * @example\n * parseCliArgs([\"node\", \"warlock\", \"migrate\", \"--rollback\", \"file.ts\"])\n * // Returns: { name: \"migrate\", args: [], options: { rollback: \"file.ts\" } }\n *\n * @example\n * // `rollback` declared as type: \"boolean\" — the file stays a positional\n * parseCliArgs([\"node\", \"warlock\", \"migrate\", \"--rollback\", \"file.ts\"], migrateOptions)\n * // Returns: { name: \"migrate\", args: [\"file.ts\"], options: { rollback: true } }\n *\n * @example\n * parseCliArgs([\"node\", \"warlock\", \"dev\", \"--port=3000\", \"--fresh\"])\n * // Returns: { name: \"dev\", args: [], options: { port: \"3000\", fresh: true } }\n */\nexport function parseCliArgs(argv: string[], schema: CliOptionSchema[] = []): ParsedCliArgs {\n // Command is at index 2 (after \"node\" and script path)\n // But if index 2 starts with \"-\", it's an option, not a command\n const potentialCommand = argv[2] || \"\";\n const isFirstArgOption = potentialCommand.startsWith(\"-\");\n const command = isFirstArgOption ? \"\" : potentialCommand;\n const args: string[] = [];\n const options: Record<string, string | boolean> = {};\n const booleanKeys = collectBooleanKeys(schema);\n const multiCharAliases = collectMultiCharAliases(schema);\n\n // Parse arguments starting from index 3 (or 2 if first arg was an option)\n const startIndex = isFirstArgOption ? 2 : 3;\n for (let i = startIndex; i < argv.length; i++) {\n const arg = argv[i];\n\n // `i < argv.length` bounds this, but the compiler does not narrow an index\n // read from a loop condition. Skipping rather than coercing: this file\n // already refuses to guess about an operator's input (see\n // `CliOptionValueError` above), and an `?? \"\"` here would silently push an\n // empty POSITIONAL onto `args` — which for a command that takes a file or\n // a table name is a scope the operator never typed.\n if (arg === undefined) continue;\n\n if (arg.startsWith(\"--\")) {\n // Long option: --key or --key=value\n const withoutDashes = arg.slice(2);\n const equalIndex = withoutDashes.indexOf(\"=\");\n\n if (equalIndex !== -1) {\n // --key=value format\n const key = toCamelCase(withoutDashes.slice(0, equalIndex));\n const value = withoutDashes.slice(equalIndex + 1);\n options[key] = booleanKeys.has(key)\n ? toBoolean(`--${withoutDashes.slice(0, equalIndex)}`, value)\n : value;\n } else {\n // --key format (check if next arg is a value)\n const key = toCamelCase(withoutDashes);\n const nextArg = argv[i + 1];\n\n // A declared boolean NEVER consumes the next token: `--rollback\n // users.ts` means \"rollback, and here is a positional\", not \"rollback\n // the file users.ts\". Swallowing it silently discarded the operator's\n // only stated scope and rolled back everything.\n if (booleanKeys.has(key)) {\n options[key] = true;\n } else if (nextArg && !nextArg.startsWith(\"-\")) {\n // If next arg exists and doesn't start with -, treat it as value\n options[key] = nextArg;\n i++; // Skip next arg\n } else {\n options[key] = true;\n }\n }\n } else if (arg.startsWith(\"-\") && arg.length > 1) {\n // Short option: -f, -t=value, or -abc (multiple flags)\n const flags = arg.slice(1);\n const equalIndex = flags.indexOf(\"=\");\n\n if (equalIndex !== -1) {\n // -t=value format\n const rawKey = flags.slice(0, equalIndex);\n const key = toCamelCase(rawKey);\n const value = flags.slice(equalIndex + 1);\n options[key] = booleanKeys.has(key) ? toBoolean(`-${rawKey}`, value) : value;\n } else if (flags.length === 1 || multiCharAliases.has(flags)) {\n // Single flag: -f, or a declared multi-character alias: -st\n const key = toCamelCase(flags);\n const nextArg = argv[i + 1];\n\n if (booleanKeys.has(key)) {\n options[key] = true;\n } else if (nextArg && !nextArg.startsWith(\"-\")) {\n options[key] = nextArg;\n i++; // Skip next arg\n } else {\n options[key] = true;\n }\n } else {\n // Multiple flags: -abc becomes { a: true, b: true, c: true }\n // A bundle carries no value and never consumes the next token, so a\n // declared boolean already resolves to `true` here.\n for (const flag of flags) {\n options[toCamelCase(flag)] = true;\n }\n }\n } else {\n // Positional argument\n args.push(arg);\n }\n }\n\n return { name: command, args, options };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-command.d.mts","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"cli-command.d.mts","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"mappings":";;;cAWa,UAAA;EAqDF,IAAA;EArDE;;;EAIJ,aAAA,GAAgB,gBAAA;EAKA;;;EAAhB,aAAA,GAAgB,gBAAA;EAyDD;;;;EAnDf,gBAAA,GAAmB,gBAAA;EA0HT;;;EArHV,cAAA,GAAiB,iBAAA;EAqOA;;;EAhOjB,kBAAA;EAwOqC;;;EAnOrC,cAAA,EAAgB,wBAAA;EArBhB;;;;;EA4BA,mBAAA;EAZA;;;EAiBA,YAAA;EAAA;;;EAKA,YAAA;EAOL;;;cADO,IAAA,UACP,WAAA;EAoBK;;;EARA,MAAA,CAAO,MAAA,EAAQ,gBAAA;EAyBf;;;EAjBA,WAAA,CAAY,WAAA;EAyBL;;;EAjBP,UAAA,CAAW,YAAA;EAkCX;;;;EAzBA,KAAA,CAAM,KAAA;EA0CN;;;EAlCA,MAAA,CAAO,MAAA,EAAQ,gBAAA;EAoCpB;;;;EA3BK,SAAA,CAAU,MAAA,EAAQ,gBAAA;EA8Df;;;EAtDH,OAAA,CAAQ,OAAA,EAAS,gBAAA;EA+GhB;;;EAtGD,aAAA,CAAc,YAAA;EAoIR;;;EA5HN,MAAA,CAAO,MAAA,EAAQ,gBAAA;EACf,MAAA,CACL,IAAA,UACA,WAAA,WACA,OAAA,GAAU,IAAA,CAAK,gBAAA;EAwH2B;AAS9C;;;;;;;;EAT8C,UAvFlC,WAAA,CAAY,MAAA,EAAQ,gBAAA,GAAmB,wBAAA;;;;;;;;;UAyDzC,iBAAA;;;;EAsBD,OAAA,CAAQ,OAAA,EAAS,iBAAA;;;;EAQX,OAAA,CAAQ,IAAA,EAAM,iBAAA,GAAiB,OAAA;AAAA;AAAA,iBAS9B,OAAA,CAAQ,OAAA,EAAS,iBAAA,GAAiB,UAAA"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { toCamelCase } from "@mongez/reinforcements";
|
|
2
|
+
|
|
1
3
|
//#region ../core/src/commands/cli-command.ts
|
|
2
4
|
var CLICommand = class {
|
|
3
5
|
/**
|
|
@@ -111,8 +113,8 @@ var CLICommand = class {
|
|
|
111
113
|
if (alias === "h" || name === "help") throw new Error("Help option is not allowed, it's reserved for displaying command help");
|
|
112
114
|
return {
|
|
113
115
|
...option,
|
|
114
|
-
name,
|
|
115
|
-
alias
|
|
116
|
+
name: toCamelCase(name),
|
|
117
|
+
alias: alias.includes("-") ? toCamelCase(alias) : alias
|
|
116
118
|
};
|
|
117
119
|
}
|
|
118
120
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-command.mjs","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"sourcesContent":["import type {\n CLICommandAction,\n CLICommandOption,\n CLICommandOptions,\n CLICommandPreload,\n CLICommandSource,\n CommandActionData,\n ResolvedCLICommandOption,\n} from \"./types\";\n\nexport class CLICommand {\n /**\n * Command source\n */\n public commandSource?: CLICommandSource;\n\n /**\n * Command action\n */\n public commandAction?: CLICommandAction;\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public commandPreAction?: CLICommandAction;\n\n /**\n * Command preload\n */\n public commandPreload?: CLICommandPreload;\n\n /**\n * Command description\n */\n public commandDescription?: string;\n\n /**\n * Command options\n */\n public commandOptions: ResolvedCLICommandOption[] = [];\n\n /**\n * Command relative path\n * Available only for project commands\n * Auto injected by the framework itself\n */\n public commandRelativePath?: string;\n\n /**\n * Determine if the command is persistent\n */\n public isPersistent: boolean = false;\n\n /**\n * Command alias (short name)\n */\n public commandAlias?: string;\n\n /**\n * Constructor\n */\n public constructor(\n public name: string,\n description?: string,\n ) {\n if (description) {\n this.commandDescription = description;\n }\n\n return this;\n }\n\n /**\n * Add command source\n */\n public source(source: CLICommandSource): this {\n this.commandSource = source;\n return this;\n }\n\n /**\n * Set command description\n */\n public description(description: string): this {\n this.commandDescription = description;\n return this;\n }\n\n /**\n * Determine if the command is persistent\n */\n public persistent(isPersistent = true): this {\n this.isPersistent = isPersistent;\n return this;\n }\n\n /**\n * Set command alias (short name)\n * @example .alias(\"m\") for \"migrate\"\n */\n public alias(alias: string): this {\n this.commandAlias = alias;\n return this;\n }\n\n /**\n * Command action\n */\n public action(action: CLICommandAction): this {\n this.commandAction = action;\n return this;\n }\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public preAction(action: CLICommandAction): this {\n this.commandPreAction = action;\n return this;\n }\n\n /**\n * Add command options\n */\n public options(options: CLICommandOption[]): this {\n options.map((option) => this.option(option));\n\n return this;\n }\n\n /**\n * Add command relative path\n */\n public $relativePath(relativePath: string) {\n this.commandRelativePath = relativePath;\n return this;\n }\n\n /**\n * Add command option\n */\n public option(option: CLICommandOption): this;\n public option(\n name: string,\n description?: string,\n options?: Omit<CLICommandOption, \"name\" | \"text\" | \"description\">,\n ): this;\n public option(\n ...args:\n | [CLICommandOption]\n | [string, string?, Omit<CLICommandOption, \"name\" | \"text\" | \"description\">?]\n ): this {\n let option: CLICommandOption;\n if (args.length === 1) {\n option = args[0] as CLICommandOption;\n } else {\n option = {\n text: args[0],\n description: args[1],\n ...args[2],\n name: \"\",\n };\n }\n\n this.commandOptions.push(this.parseOption(option));\n\n return this;\n }\n\n /**\n * Parse option name and alias if exists\n *\n * Supports formats:\n * - \"--port, -p\" → name: \"port\", alias: \"p\"\n * - \"-p, --port\" → name: \"port\", alias: \"p\"\n * - \"--port\" → name: \"port\", alias: undefined\n * - \"-p\" → name: \"p\", alias: undefined\n */\n protected parseOption(option: CLICommandOption): ResolvedCLICommandOption {\n const text = option.text.trim();\n\n // Split by comma to check for alias\n const parts = text.split(\",\").map((part) => part.trim());\n\n let name = \"\";\n let alias = \"\";\n const firstPart = parts[0];\n\n if (!firstPart) {\n throw new Error(\"Command option text cannot be empty\");\n }\n\n if (parts.length === 1) {\n // Single option: \"--port\" or \"-p\"\n name = this.extractOptionName(firstPart);\n } else if (parts.length === 2) {\n // Two options: \"--port, -p\" or \"-p, --port\"\n const first = firstPart;\n const second = parts[1];\n\n if (!second) {\n throw new Error(\"Command option alias cannot be empty\");\n }\n\n // Determine which is the long form (name) and which is short (alias)\n if (first.startsWith(\"--\")) {\n name = this.extractOptionName(first);\n alias = this.extractOptionName(second);\n } else {\n name = this.extractOptionName(second);\n alias = this.extractOptionName(first);\n }\n }\n\n if (alias === \"h\" || name === \"help\") {\n throw new Error(\"Help option is not allowed, it's reserved for displaying command help\");\n }\n\n return {\n ...option,\n name,\n alias,\n };\n }\n\n /**\n * Extract option name from text (removes -- or -)\n *\n * @example\n * extractOptionName(\"--port\") → \"port\"\n * extractOptionName(\"-p\") → \"p\"\n * extractOptionName(\"--port=3000\") → \"port\"\n */\n private extractOptionName(text: string): string {\n // Remove leading dashes\n let name = text.replace(/^-+/, \"\");\n\n // Remove value assignment if exists (e.g., \"--port=3000\" → \"port\")\n const equalIndex = name.indexOf(\"=\");\n if (equalIndex !== -1) {\n name = name.slice(0, equalIndex);\n }\n\n // Remove angle brackets if exists (e.g., \"--port <number>\" → \"port\")\n const spaceIndex = name.indexOf(\" \");\n if (spaceIndex !== -1) {\n name = name.slice(0, spaceIndex);\n }\n\n return name.trim();\n }\n\n /**\n * Command preload\n */\n public preload(options: CLICommandPreload) {\n this.commandPreload = options;\n return this;\n }\n\n /**\n * Execute the command\n */\n public async execute(data: CommandActionData) {\n if (!this.commandAction) {\n throw new Error(`Command \"${this.name}\" has no action defined`);\n }\n\n await this.commandAction(data);\n }\n}\n\nexport function command(options: CLICommandOptions) {\n const commandInstnace = new CLICommand(options.name, options.description);\n\n if (options.preload) {\n commandInstnace.preload(options.preload);\n }\n\n if (options.persistent) {\n commandInstnace.persistent(options.persistent);\n }\n\n if (options.alias) {\n commandInstnace.alias(options.alias);\n }\n\n commandInstnace.action(options.action);\n\n if (options.options) {\n commandInstnace.options(options.options);\n }\n\n if (options.preAction) {\n commandInstnace.preAction(options.preAction);\n }\n\n return commandInstnace;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli-command.mjs","names":[],"sources":["../../../../../../../core/src/commands/cli-command.ts"],"sourcesContent":["import { toCamelCase } from \"@mongez/reinforcements\";\nimport type {\n CLICommandAction,\n CLICommandOption,\n CLICommandOptions,\n CLICommandPreload,\n CLICommandSource,\n CommandActionData,\n ResolvedCLICommandOption,\n} from \"./types\";\n\nexport class CLICommand {\n /**\n * Command source\n */\n public commandSource?: CLICommandSource;\n\n /**\n * Command action\n */\n public commandAction?: CLICommandAction;\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public commandPreAction?: CLICommandAction;\n\n /**\n * Command preload\n */\n public commandPreload?: CLICommandPreload;\n\n /**\n * Command description\n */\n public commandDescription?: string;\n\n /**\n * Command options\n */\n public commandOptions: ResolvedCLICommandOption[] = [];\n\n /**\n * Command relative path\n * Available only for project commands\n * Auto injected by the framework itself\n */\n public commandRelativePath?: string;\n\n /**\n * Determine if the command is persistent\n */\n public isPersistent: boolean = false;\n\n /**\n * Command alias (short name)\n */\n public commandAlias?: string;\n\n /**\n * Constructor\n */\n public constructor(\n public name: string,\n description?: string,\n ) {\n if (description) {\n this.commandDescription = description;\n }\n\n return this;\n }\n\n /**\n * Add command source\n */\n public source(source: CLICommandSource): this {\n this.commandSource = source;\n return this;\n }\n\n /**\n * Set command description\n */\n public description(description: string): this {\n this.commandDescription = description;\n return this;\n }\n\n /**\n * Determine if the command is persistent\n */\n public persistent(isPersistent = true): this {\n this.isPersistent = isPersistent;\n return this;\n }\n\n /**\n * Set command alias (short name)\n * @example .alias(\"m\") for \"migrate\"\n */\n public alias(alias: string): this {\n this.commandAlias = alias;\n return this;\n }\n\n /**\n * Command action\n */\n public action(action: CLICommandAction): this {\n this.commandAction = action;\n return this;\n }\n\n /**\n * Command pre action\n * This will be executed before loading preloaders\n */\n public preAction(action: CLICommandAction): this {\n this.commandPreAction = action;\n return this;\n }\n\n /**\n * Add command options\n */\n public options(options: CLICommandOption[]): this {\n options.map((option) => this.option(option));\n\n return this;\n }\n\n /**\n * Add command relative path\n */\n public $relativePath(relativePath: string) {\n this.commandRelativePath = relativePath;\n return this;\n }\n\n /**\n * Add command option\n */\n public option(option: CLICommandOption): this;\n public option(\n name: string,\n description?: string,\n options?: Omit<CLICommandOption, \"name\" | \"text\" | \"description\">,\n ): this;\n public option(\n ...args:\n | [CLICommandOption]\n | [string, string?, Omit<CLICommandOption, \"name\" | \"text\" | \"description\">?]\n ): this {\n let option: CLICommandOption;\n if (args.length === 1) {\n option = args[0] as CLICommandOption;\n } else {\n option = {\n text: args[0],\n description: args[1],\n ...args[2],\n name: \"\",\n };\n }\n\n this.commandOptions.push(this.parseOption(option));\n\n return this;\n }\n\n /**\n * Parse option name and alias if exists\n *\n * Supports formats:\n * - \"--port, -p\" → name: \"port\", alias: \"p\"\n * - \"-p, --port\" → name: \"port\", alias: \"p\"\n * - \"--port\" → name: \"port\", alias: undefined\n * - \"-p\" → name: \"p\", alias: undefined\n */\n protected parseOption(option: CLICommandOption): ResolvedCLICommandOption {\n const text = option.text.trim();\n\n // Split by comma to check for alias\n const parts = text.split(\",\").map((part) => part.trim());\n\n let name = \"\";\n let alias = \"\";\n const firstPart = parts[0];\n\n if (!firstPart) {\n throw new Error(\"Command option text cannot be empty\");\n }\n\n if (parts.length === 1) {\n // Single option: \"--port\" or \"-p\"\n name = this.extractOptionName(firstPart);\n } else if (parts.length === 2) {\n // Two options: \"--port, -p\" or \"-p, --port\"\n const first = firstPart;\n const second = parts[1];\n\n if (!second) {\n throw new Error(\"Command option alias cannot be empty\");\n }\n\n // Determine which is the long form (name) and which is short (alias)\n if (first.startsWith(\"--\")) {\n name = this.extractOptionName(first);\n alias = this.extractOptionName(second);\n } else {\n name = this.extractOptionName(second);\n alias = this.extractOptionName(first);\n }\n }\n\n if (alias === \"h\" || name === \"help\") {\n throw new Error(\"Help option is not allowed, it's reserved for displaying command help\");\n }\n\n // The parser emits camelCase keys, so the resolved name must match them.\n return {\n ...option,\n name: toCamelCase(name),\n // Short flags are case-sensitive (`-L` vs `-l`); only kebab aliases fold.\n alias: alias.includes(\"-\") ? toCamelCase(alias) : alias,\n };\n }\n\n /**\n * Extract option name from text (removes -- or -)\n *\n * @example\n * extractOptionName(\"--port\") → \"port\"\n * extractOptionName(\"-p\") → \"p\"\n * extractOptionName(\"--port=3000\") → \"port\"\n */\n private extractOptionName(text: string): string {\n // Remove leading dashes\n let name = text.replace(/^-+/, \"\");\n\n // Remove value assignment if exists (e.g., \"--port=3000\" → \"port\")\n const equalIndex = name.indexOf(\"=\");\n if (equalIndex !== -1) {\n name = name.slice(0, equalIndex);\n }\n\n // Remove angle brackets if exists (e.g., \"--port <number>\" → \"port\")\n const spaceIndex = name.indexOf(\" \");\n if (spaceIndex !== -1) {\n name = name.slice(0, spaceIndex);\n }\n\n return name.trim();\n }\n\n /**\n * Command preload\n */\n public preload(options: CLICommandPreload) {\n this.commandPreload = options;\n return this;\n }\n\n /**\n * Execute the command\n */\n public async execute(data: CommandActionData) {\n if (!this.commandAction) {\n throw new Error(`Command \"${this.name}\" has no action defined`);\n }\n\n await this.commandAction(data);\n }\n}\n\nexport function command(options: CLICommandOptions) {\n const commandInstnace = new CLICommand(options.name, options.description);\n\n if (options.preload) {\n commandInstnace.preload(options.preload);\n }\n\n if (options.persistent) {\n commandInstnace.persistent(options.persistent);\n }\n\n if (options.alias) {\n commandInstnace.alias(options.alias);\n }\n\n commandInstnace.action(options.action);\n\n if (options.options) {\n commandInstnace.options(options.options);\n }\n\n if (options.preAction) {\n commandInstnace.preAction(options.preAction);\n }\n\n return commandInstnace;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region ../core/src/config/config-key-from-path.ts
|
|
2
|
+
const CONFIG_FILE_PATTERN = /^src\/config\/(.+)\.(ts|tsx)$/;
|
|
3
|
+
/**
|
|
4
|
+
* The key a config file is registered under, from its project-relative path.
|
|
5
|
+
* The single naming rule shared by dev, the production builder and typings:
|
|
6
|
+
* the raw path under `src/config`, extension dropped, nothing case-converted.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* configKeyFromPath("src/config/rate-limit.ts"); // "rate-limit"
|
|
10
|
+
* configKeyFromPath("src/config/mail/smtp.ts"); // "mail/smtp"
|
|
11
|
+
*
|
|
12
|
+
* @returns the key, or `undefined` when the path is not a config file
|
|
13
|
+
*/
|
|
14
|
+
function configKeyFromPath(relativePath) {
|
|
15
|
+
return CONFIG_FILE_PATTERN.exec(relativePath.replace(/\\/g, "/"))?.[1];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { configKeyFromPath };
|
|
20
|
+
//# sourceMappingURL=config-key-from-path.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-key-from-path.mjs","names":[],"sources":["../../../../../../../core/src/config/config-key-from-path.ts"],"sourcesContent":["const CONFIG_FILE_PATTERN = /^src\\/config\\/(.+)\\.(ts|tsx)$/;\n\n/**\n * The key a config file is registered under, from its project-relative path.\n * The single naming rule shared by dev, the production builder and typings:\n * the raw path under `src/config`, extension dropped, nothing case-converted.\n *\n * @example\n * configKeyFromPath(\"src/config/rate-limit.ts\"); // \"rate-limit\"\n * configKeyFromPath(\"src/config/mail/smtp.ts\"); // \"mail/smtp\"\n *\n * @returns the key, or `undefined` when the path is not a config file\n */\nexport function configKeyFromPath(relativePath: string): string | undefined {\n return CONFIG_FILE_PATTERN.exec(relativePath.replace(/\\\\/g, \"/\"))?.[1];\n}\n"],"mappings":";AAAA,MAAM,sBAAsB;;;;;;;;;;;;AAa5B,SAAgB,kBAAkB,cAA0C;CAC1E,OAAO,oBAAoB,KAAK,aAAa,QAAQ,OAAO,GAAG,CAAC,CAAC,GAAG;AACtE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.mts","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"config-loader.d.mts","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"mappings":";AAeA;;;;AAAA,KAAY,oBAAA,IAAwB,WAAA,oBAA+B,OAAO"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { configSpecialHandlers } from "./config-special-handlers.mjs";
|
|
2
|
+
import { configKeyFromPath } from "./config-key-from-path.mjs";
|
|
2
3
|
import config from "@mongez/config";
|
|
3
4
|
import { colors } from "@mongez/copper";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
@@ -37,9 +38,7 @@ var ConfigLoader = class {
|
|
|
37
38
|
await this.loadConfig(file);
|
|
38
39
|
}
|
|
39
40
|
getConfigName(relativePath) {
|
|
40
|
-
const
|
|
41
|
-
if (!match) throw new Error(`Invalid config file path: ${relativePath}`);
|
|
42
|
-
const configName = match[1];
|
|
41
|
+
const configName = configKeyFromPath(relativePath);
|
|
43
42
|
if (!configName) throw new Error(`Invalid config file path: ${relativePath}`);
|
|
44
43
|
return configName;
|
|
45
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.mjs","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { pathToFileURL } from \"node:url\";\r\nimport type { FileManager } from \"../dev-server/file-manager\";\r\nimport { configSpecialHandlers } from \"./config-special-handlers\";\r\n\r\n/**\r\n * Special Config Handler\r\n * A function that handles special configuration loading\r\n */\r\n// Returns unknown, not void: ConfigSpecialHandlers.execute() RETURNS handler(config)\r\n// rather than discarding it, and a test pins that (\"returns the handler's resolved\r\n// value\"). The implementation and the test agreed; only this type said handlers\r\n// produce nothing, which made any value-returning handler fail to typecheck.\r\nexport type SpecialConfigHandler = (configValue: any) => unknown | Promise<unknown>;\r\n\r\n/**\r\n * Config Loader\r\n * Dynamically loads all configuration files and registers them with @mongez/config\r\n * Supports special handlers for configs that require additional processing\r\n */\r\nexport class ConfigLoader {\r\n public async loadAll(configFiles: FileManager[]): Promise<void> {\r\n for (const file of configFiles) {\r\n await this.loadConfig(file);\r\n }\r\n }\r\n\r\n /**\r\n * Load a single configuration file.\r\n *\r\n * The ESM loader hook stamps a version token onto the URL so that each HMR\r\n * cycle gets a fresh module — no manual cache-busting needed here.\r\n */\r\n public async loadConfig(file: FileManager): Promise<void> {\r\n const configName = this.getConfigName(file.relativePath);\r\n\r\n try {\r\n const fileUrl = pathToFileURL(file.absolutePath).href;\r\n const configModule = await import(fileUrl);\r\n const configValue = configModule.default;\r\n\r\n if (configValue === undefined) {\r\n console.log(\r\n colors.red(`config error: `),\r\n `Config file ${colors.yellow(file.relativePath)} does not have a default export`,\r\n );\r\n return;\r\n }\r\n\r\n config.set(configName, configValue);\r\n await configSpecialHandlers.execute(configName, configValue);\r\n } catch (error) {\r\n throw error;\r\n }\r\n }\r\n\r\n public async reloadConfig(file: FileManager): Promise<void> {\r\n await this.loadConfig(file);\r\n }\r\n\r\n private getConfigName(relativePath: string): string {\r\n const
|
|
1
|
+
{"version":3,"file":"config-loader.mjs","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { pathToFileURL } from \"node:url\";\r\nimport type { FileManager } from \"../dev-server/file-manager\";\r\nimport { configKeyFromPath } from \"./config-key-from-path\";\r\nimport { configSpecialHandlers } from \"./config-special-handlers\";\r\n\r\n/**\r\n * Special Config Handler\r\n * A function that handles special configuration loading\r\n */\r\n// Returns unknown, not void: ConfigSpecialHandlers.execute() RETURNS handler(config)\r\n// rather than discarding it, and a test pins that (\"returns the handler's resolved\r\n// value\"). The implementation and the test agreed; only this type said handlers\r\n// produce nothing, which made any value-returning handler fail to typecheck.\r\nexport type SpecialConfigHandler = (configValue: any) => unknown | Promise<unknown>;\r\n\r\n/**\r\n * Config Loader\r\n * Dynamically loads all configuration files and registers them with @mongez/config\r\n * Supports special handlers for configs that require additional processing\r\n */\r\nexport class ConfigLoader {\r\n public async loadAll(configFiles: FileManager[]): Promise<void> {\r\n for (const file of configFiles) {\r\n await this.loadConfig(file);\r\n }\r\n }\r\n\r\n /**\r\n * Load a single configuration file.\r\n *\r\n * The ESM loader hook stamps a version token onto the URL so that each HMR\r\n * cycle gets a fresh module — no manual cache-busting needed here.\r\n */\r\n public async loadConfig(file: FileManager): Promise<void> {\r\n const configName = this.getConfigName(file.relativePath);\r\n\r\n try {\r\n const fileUrl = pathToFileURL(file.absolutePath).href;\r\n const configModule = await import(fileUrl);\r\n const configValue = configModule.default;\r\n\r\n if (configValue === undefined) {\r\n console.log(\r\n colors.red(`config error: `),\r\n `Config file ${colors.yellow(file.relativePath)} does not have a default export`,\r\n );\r\n return;\r\n }\r\n\r\n config.set(configName, configValue);\r\n await configSpecialHandlers.execute(configName, configValue);\r\n } catch (error) {\r\n throw error;\r\n }\r\n }\r\n\r\n public async reloadConfig(file: FileManager): Promise<void> {\r\n await this.loadConfig(file);\r\n }\r\n\r\n private getConfigName(relativePath: string): string {\r\n const configName = configKeyFromPath(relativePath);\r\n\r\n if (!configName) {\r\n throw new Error(`Invalid config file path: ${relativePath}`);\r\n }\r\n\r\n return configName;\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAa,eAAb,MAA0B;CACxB,MAAa,QAAQ,aAA2C;EAC9D,KAAK,MAAM,QAAQ,aACjB,MAAM,KAAK,WAAW,IAAI;CAE9B;;;;;;;CAQA,MAAa,WAAW,MAAkC;EACxD,MAAM,aAAa,KAAK,cAAc,KAAK,YAAY;EAEvD,IAAI;GAGF,MAAM,eAAc,MADO,OADX,cAAc,KAAK,YAAY,CAAC,CAAC,MAEjB,CAAC;GAEjC,IAAI,gBAAgB,QAAW;IAC7B,QAAQ,IACN,OAAO,IAAI,gBAAgB,GAC3B,eAAe,OAAO,OAAO,KAAK,YAAY,EAAE,gCAClD;IACA;GACF;GAEA,OAAO,IAAI,YAAY,WAAW;GAClC,MAAM,sBAAsB,QAAQ,YAAY,WAAW;EAC7D,SAAS,OAAO;GACd,MAAM;EACR;CACF;CAEA,MAAa,aAAa,MAAkC;EAC1D,MAAM,KAAK,WAAW,IAAI;CAC5B;CAEA,AAAQ,cAAc,cAA8B;EAClD,MAAM,aAAa,kBAAkB,YAAY;EAEjD,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,6BAA6B,cAAc;EAG7D,OAAO;CACT;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/cache-connector.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"cache-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/cache-connector.ts"],"mappings":";;;;;;AAaA;;cAAa,cAAA,SAAuB,aAAA;EAAA,SAClB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,KAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,KAAA;EAuCL;;;EAAA,mBAlCN,YAAA;EARe;;;EAarB,KAAA,IAAS,OAAA;EAXE;;;;;;;;EAwCX,QAAA,IAAY,OAAA;AAAA"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { environment } from "../utils/environment.mjs";
|
|
1
2
|
import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
|
|
2
3
|
import { BaseConnector } from "./base-connector.mjs";
|
|
4
|
+
import { memoryCacheWarning } from "./single-server-warnings.mjs";
|
|
3
5
|
import config from "@mongez/config";
|
|
6
|
+
import { log } from "@warlock.js/logger";
|
|
4
7
|
import { cache } from "@warlock.js/cache";
|
|
5
8
|
|
|
6
9
|
//#region ../core/src/connectors/cache-connector.ts
|
|
@@ -24,6 +27,8 @@ var CacheConnector = class extends BaseConnector {
|
|
|
24
27
|
if (!cacheConfig) return;
|
|
25
28
|
cache.setCacheConfigurations(cacheConfig);
|
|
26
29
|
await cache.init();
|
|
30
|
+
const warning = memoryCacheWarning(cache.currentDriver?.name ?? cacheConfig.default, cache.currentDriver?.constructor?.name, environment(), config.get("cache.silenceSingleServerWarning") === true);
|
|
31
|
+
if (warning) log.warn("cache", "single-server", warning);
|
|
27
32
|
this.active = true;
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/cache-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport type { CacheDriver } from \"@warlock.js/cache\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Cache Connector\n * Manages cache engine connection lifecycle\n */\nexport class CacheConnector extends BaseConnector {\n public readonly name = \"cache\";\n public readonly priority = ConnectorPriority.CACHE;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger cache restart\n */\n protected readonly watchedFiles = [\"src/config/cache.ts\", \"src/config/cache.tsx\"];\n\n /**\n * Initialize cache connection\n */\n public async start(): Promise<void> {\n const cacheConfig = config.get(\"cache\");\n\n if (!cacheConfig) return;\n\n cache.setCacheConfigurations(cacheConfig);\n\n await cache.init();\n\n this.active = true;\n }\n\n /**\n * Shutdown cache connection\n *\n * Disconnects every loaded cache driver so external clients (e.g. a Redis\n * connection) are released instead of left dangling — mirroring the\n * database and herald connectors, which disconnect all registered\n * connections on shutdown.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n // Disconnect every driver the manager loaded (not just the current one),\n // so secondary drivers selected via `cache.use(\"...\")` also release their\n // connections.\n const drivers: CacheDriver<unknown, unknown>[] = Object.values(cache.loadedDrivers);\n\n for (const driver of drivers) {\n await driver.disconnect();\n }\n\n this.active = false;\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"cache-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/cache-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport type { CacheDriver } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport { environment } from \"../utils/environment\";\nimport { BaseConnector } from \"./base-connector\";\nimport { memoryCacheWarning } from \"./single-server-warnings\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Cache Connector\n * Manages cache engine connection lifecycle\n */\nexport class CacheConnector extends BaseConnector {\n public readonly name = \"cache\";\n public readonly priority = ConnectorPriority.CACHE;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger cache restart\n */\n protected readonly watchedFiles = [\"src/config/cache.ts\", \"src/config/cache.tsx\"];\n\n /**\n * Initialize cache connection\n */\n public async start(): Promise<void> {\n const cacheConfig = config.get(\"cache\");\n\n if (!cacheConfig) return;\n\n cache.setCacheConfigurations(cacheConfig);\n\n await cache.init();\n\n const warning = memoryCacheWarning(\n cache.currentDriver?.name ?? cacheConfig.default,\n cache.currentDriver?.constructor?.name,\n environment(),\n config.get(\"cache.silenceSingleServerWarning\") === true,\n );\n\n if (warning) log.warn(\"cache\", \"single-server\", warning);\n\n this.active = true;\n }\n\n /**\n * Shutdown cache connection\n *\n * Disconnects every loaded cache driver so external clients (e.g. a Redis\n * connection) are released instead of left dangling — mirroring the\n * database and herald connectors, which disconnect all registered\n * connections on shutdown.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n // Disconnect every driver the manager loaded (not just the current one),\n // so secondary drivers selected via `cache.use(\"...\")` also release their\n // connections.\n const drivers: CacheDriver<unknown, unknown>[] = Object.values(cache.loadedDrivers);\n\n for (const driver of drivers) {\n await driver.disconnect();\n }\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,iBAAb,cAAoC,cAAc;;;cACzB;;;sBAOW,CAAC,uBAAuB,sBAAsB;;;;;CAKhF,MAAa,QAAuB;EAClC,MAAM,cAAc,OAAO,IAAI,OAAO;EAEtC,IAAI,CAAC,aAAa;EAElB,MAAM,uBAAuB,WAAW;EAExC,MAAM,MAAM,KAAK;EAEjB,MAAM,UAAU,mBACd,MAAM,eAAe,QAAQ,YAAY,SACzC,MAAM,eAAe,aAAa,MAClC,YAAY,GACZ,OAAO,IAAI,kCAAkC,MAAM,IACrD;EAEA,IAAI,SAAS,IAAI,KAAK,SAAS,iBAAiB,OAAO;EAEvD,KAAK,SAAS;CAChB;;;;;;;;;CAUA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAMF,MAAM,UAA2C,OAAO,OAAO,MAAM,aAAa;EAElF,KAAK,MAAM,UAAU,SACnB,MAAM,OAAO,WAAW;EAG1B,KAAK,SAAS;CAChB;AACF"}
|
|
@@ -78,6 +78,10 @@ declare class ConnectorsManager {
|
|
|
78
78
|
* Shutdown all connectors
|
|
79
79
|
*/
|
|
80
80
|
shutdown(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Shut one connector down, isolating and reporting its failure.
|
|
83
|
+
*/
|
|
84
|
+
private shutdownOne;
|
|
81
85
|
/**
|
|
82
86
|
* Shutdown connectors on process kill
|
|
83
87
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectors-manager.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"connectors-manager.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"mappings":";;;cAsBa,iBAAA;EAAA;;;EAAA,iBAIM,UAAA;EAwEF;;;;;;;;;;;;EAAA,iBA1DE,YAAA;;;;;EAiDV;;;;;;;EAAA,QAvBC,eAAA;EAkDK;;;;;;;;EAlCN,aAAA,CAAc,IAAA;EA0EyB;;;EAnEvC,QAAA,IAAY,UAAA,EAAY,SAAA;EA8EN;;;EArElB,IAAA,IAAQ,SAAA;EAkJa;AAyB9B;;;;AAAwD;;EAhK/C,GAAA,CAAI,IAAA,EAAM,aAAA;;;;EAOJ,KAAA,CAAM,eAAA,GAAkB,aAAA,KAAkB,OAAA;;;;;;;;;;EAuB1C,UAAA,CAAW,KAAA,EAAO,uBAAA,GAA0B,OAAA;;;;EAiB5C,YAAA,CAAa,kBAAA,EAAoB,aAAA,KAAkB,OAAA;;;;EAWnD,QAAA,IAAY,OAAA;;;;UA8CX,WAAA;;;;;;;;;;;;;EA+BP,qBAAA;AAAA;AAAA,cAyBI,iBAAA,EAAiB,iBAA0B"}
|
|
@@ -11,9 +11,14 @@ import { MailerConnector } from "./mail-connector.mjs";
|
|
|
11
11
|
import { NotificationsConnector } from "./notifications-connector.mjs";
|
|
12
12
|
import { SocketConnector } from "./socket-connector.mjs";
|
|
13
13
|
import { StorageConnector } from "./storage.connector.mjs";
|
|
14
|
+
import config from "@mongez/config";
|
|
14
15
|
import { log } from "@warlock.js/logger";
|
|
15
16
|
|
|
16
17
|
//#region ../core/src/connectors/connectors-manager.ts
|
|
18
|
+
/**
|
|
19
|
+
* Overall shutdown budget in ms. Override via `app.shutdownTimeout`.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_SHUTDOWN_TIMEOUT = 1e4;
|
|
17
22
|
var ConnectorsManager = class {
|
|
18
23
|
/**
|
|
19
24
|
* Constructor
|
|
@@ -111,14 +116,37 @@ var ConnectorsManager = class {
|
|
|
111
116
|
*/
|
|
112
117
|
async shutdown() {
|
|
113
118
|
await Application.runShutdownHooks();
|
|
114
|
-
|
|
119
|
+
const reversed = [...this.connectors].reverse();
|
|
120
|
+
const ordered = [...reversed.filter((connector) => connector.name === "http"), ...reversed.filter((connector) => connector.name !== "http")];
|
|
121
|
+
const timeout = config.get("app.shutdownTimeout", DEFAULT_SHUTDOWN_TIMEOUT);
|
|
122
|
+
let current;
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
console.error(`Shutdown timed out after ${timeout}ms; connector still shutting down: ${current ?? "none"}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}, timeout);
|
|
127
|
+
timer.unref?.();
|
|
128
|
+
try {
|
|
129
|
+
for (const connector of ordered) {
|
|
130
|
+
current = connector.name;
|
|
131
|
+
await this.shutdownOne(connector);
|
|
132
|
+
}
|
|
133
|
+
current = void 0;
|
|
134
|
+
} finally {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
}
|
|
137
|
+
await log.flush();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Shut one connector down, isolating and reporting its failure.
|
|
141
|
+
*/
|
|
142
|
+
async shutdownOne(connector) {
|
|
143
|
+
try {
|
|
115
144
|
await connector.shutdown();
|
|
116
145
|
} catch (error) {
|
|
117
146
|
try {
|
|
118
147
|
await log.error("connectors", "shutdown", error, { connector: connector.name });
|
|
119
148
|
} catch {}
|
|
120
149
|
}
|
|
121
|
-
await log.flush();
|
|
122
150
|
}
|
|
123
151
|
/**
|
|
124
152
|
* Shutdown connectors on process kill
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectors-manager.mjs","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"sourcesContent":["import { Application } from \"../application\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AccessConnector } from \"./access-connector\";\r\nimport { AiConnector } from \"./ai-connector\";\r\nimport { CacheConnector } from \"./cache-connector\";\r\nimport { DatabaseConnector } from \"./database-connector\";\r\nimport { HeraldConnector } from \"./herald-connector\";\r\nimport { HttpConnector } from \"./http-connector\";\r\nimport { LoggerConnector } from \"./logger-connector\";\r\nimport { MailerConnector } from \"./mail-connector\";\r\nimport { NotificationsConnector } from \"./notifications-connector\";\r\nimport { SocketConnector } from \"./socket-connector\";\r\nimport { StorageConnector } from \"./storage.connector\";\r\nimport { ConnectorLifecyclePhase } from \"./types\";\r\nimport type { Connector, ConnectorName } from \"./types\";\r\n\r\nexport class ConnectorsManager {\r\n /**\r\n * Connectors list\r\n */\r\n private readonly connectors: Connector[] = [];\r\n\r\n /**\r\n * The names the framework's own connectors hold.\r\n *\r\n * Filled by the constructor's own registration calls rather than declared\r\n * beside them, so there is exactly ONE place a built-in name exists: the\r\n * connector that owns it. A second list — in the builder, in a config\r\n * validator, in a test double — is a list that drifts the moment a built-in\r\n * is added or renamed, and it drifts silently, because nothing compares the\r\n * two. Both halves of a connector's life read this one through\r\n * {@link isBuiltInName}: the build refuses a `connectors` array that claims\r\n * a built-in name, and so does registration.\r\n */\r\n private readonly builtInNames = new Set<string>();\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor() {\r\n this.registerBuiltIn(new LoggerConnector());\r\n this.registerBuiltIn(new MailerConnector());\r\n this.registerBuiltIn(new HttpConnector());\r\n this.registerBuiltIn(new DatabaseConnector());\r\n this.registerBuiltIn(new HeraldConnector());\r\n this.registerBuiltIn(new CacheConnector());\r\n this.registerBuiltIn(new StorageConnector());\r\n this.registerBuiltIn(new SocketConnector());\r\n this.registerBuiltIn(new NotificationsConnector());\r\n this.registerBuiltIn(new AccessConnector());\r\n this.registerBuiltIn(new AiConnector());\r\n }\r\n\r\n /**\r\n * Register a framework-owned connector and reserve its name.\r\n *\r\n * Private on purpose: an app connector must not be able to reserve a name,\r\n * and the set is only correct if every entry in it came from a connector\r\n * this class constructed itself.\r\n */\r\n private registerBuiltIn(...connectors: Connector[]): void {\r\n for (const connector of connectors) {\r\n this.builtInNames.add(connector.name);\r\n }\r\n\r\n this.register(...connectors);\r\n }\r\n\r\n /**\r\n * Whether the given name belongs to a framework built-in connector.\r\n *\r\n * Reserved, not merely taken: a configured connector carrying one of these\r\n * names would be skipped by {@link has} at boot while the build had already\r\n * drained its contribution, so the artifact would carry a connector the\r\n * server never runs. Callers refuse the name instead of skipping it.\r\n */\r\n public isBuiltInName(name: string): boolean {\r\n return this.builtInNames.has(name);\r\n }\r\n\r\n /**\r\n * Register a connector\r\n */\r\n public register(...connectors: Connector[]): void {\r\n this.connectors.push(...connectors);\r\n // sort connectors by priority\r\n this.connectors.sort((a, b) => a.priority - b.priority);\r\n }\r\n\r\n /**\r\n * Get all connectors\r\n */\r\n public list(): Connector[] {\r\n return this.connectors;\r\n }\r\n\r\n /**\r\n * Whether a connector with the given name is already registered.\r\n *\r\n * Names are the identity of a connector here, which is what lets a caller\r\n * that may run more than once per process register idempotently instead of\r\n * stacking duplicates that would each boot and shut down.\r\n */\r\n public has(name: ConnectorName): boolean {\r\n return this.connectors.some((connector) => connector.name === name);\r\n }\r\n\r\n /**\r\n * start all connectors\r\n */\r\n public async start(connectorsNames?: ConnectorName[]): Promise<void> {\r\n const connectorsList = connectorsNames\r\n ? this.connectors.filter((connector) => connectorsNames.includes(connector.name))\r\n : this.connectors;\r\n\r\n for (const connector of connectorsList) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of connectorsList) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors in the given lifecycle phase.\r\n *\r\n * The production builder and dev preload split startup around app\r\n * code: early phase before app imports, late phase after. Within a\r\n * phase, all connectors `boot()` first, then all `start()`, so\r\n * cross-connector wiring inside the phase still works (e.g. socket\r\n * reads http's instance during its own boot).\r\n */\r\n public async startPhase(phase: ConnectorLifecyclePhase): Promise<void> {\r\n const phaseConnectors = this.connectors.filter(\r\n (connector) => connector.lifecyclePhase === phase,\r\n );\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors except the given ones\r\n */\r\n public async startWithout(excludedConnectors: ConnectorName[]): Promise<void> {\r\n await this.start(\r\n this.connectors\r\n .filter((connector) => !excludedConnectors.includes(connector.name))\r\n .map((connector) => connector.name),\r\n );\r\n }\r\n\r\n /**\r\n * Shutdown all connectors\r\n */\r\n public async shutdown(): Promise<void> {\r\n // App-owned teardown runs first, while every connector (db, cache, http) is\r\n // still up, so cleanup hooks can use them. Idempotent + error-isolated.\r\n await Application.runShutdownHooks();\r\n\r\n // Shut down connectors in reverse priority order. Copy the list first —\r\n // `reverse()` mutates in place, so reversing the live array would corrupt\r\n // the order on a second shutdown pass.\r\n for (const connector of [...this.connectors].reverse()) {\r\n try {\r\n await connector.shutdown();\r\n } catch (error) {\r\n try {\r\n // Awaited deliberately: the caller (`shutdownOnProcessKill`) calls\r\n // `process.exit(0)` as soon as this resolves, so an un-awaited log is\r\n // a log that never happens.\r\n await log.error(\"connectors\", \"shutdown\", error as Error, { connector: connector.name });\r\n } catch {\r\n // Reporting a failure must never become a worse failure than the one\r\n // being reported. `Logger.log()` fans out to channels without\r\n // isolating them, so a channel that throws synchronously rejects this\r\n // call — and this call sits INSIDE the catch, so that rejection\r\n // escapes `shutdown()` entirely: the remaining connectors are never\r\n // torn down and `process.exit(0)` never runs, leaving the process\r\n // alive on the handles they still hold.\r\n //\r\n // Swallowed rather than re-reported: the only channel we could report\r\n // it through is the one that just threw.\r\n }\r\n }\r\n }\r\n\r\n // `Logger.log()` hands each entry to `channel.log()` without awaiting it, so\r\n // awaiting the calls above is not enough on its own — a buffered or async\r\n // channel (file, Sentry) still loses the entry when the process exits.\r\n // Drained once here rather than per failure: shutdown is exactly when\r\n // buffered logs must reach their destination, and re-draining every channel\r\n // once per failed connector buys nothing.\r\n await log.flush();\r\n }\r\n\r\n /**\r\n * Shutdown connectors on process kill\r\n *\r\n * Registers signal handlers that run an idempotent graceful shutdown:\r\n * - SIGINT: Ctrl+C on Unix, also caught on Windows but unreliable in child processes\r\n * - SIGTERM: Termination signal (Unix primarily)\r\n * - SIGHUP: registered on Windows (`win32`) only, to catch Ctrl+C / console close\r\n *\r\n * Note: no `beforeExit` handler is registered — `beforeExit` cannot reliably\r\n * await async teardown (the process exits once the callback returns), so\r\n * shutdown is driven entirely by the signal handlers above.\r\n */\r\n public shutdownOnProcessKill(): void {\r\n let isShuttingDown = false;\r\n\r\n const gracefulShutdown = async (signal: string) => {\r\n if (isShuttingDown) return;\r\n isShuttingDown = true;\r\n\r\n console.log(`\\nExiting...`);\r\n await this.shutdown();\r\n process.exit(0);\r\n };\r\n\r\n // Unix signals\r\n process.on(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\r\n process.on(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\r\n\r\n // Windows-specific: handle when process is about to exit\r\n // This is more reliable on Windows for spawned child processes\r\n if (process.platform === \"win32\") {\r\n // Handle Ctrl+C on Windows specifically\r\n process.on(\"SIGHUP\", () => gracefulShutdown(\"SIGHUP\"));\r\n }\r\n }\r\n}\r\n\r\nexport const connectorsManager = new ConnectorsManager();\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connectors-manager.mjs","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { Application } from \"../application\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AccessConnector } from \"./access-connector\";\r\nimport { AiConnector } from \"./ai-connector\";\r\nimport { CacheConnector } from \"./cache-connector\";\r\nimport { DatabaseConnector } from \"./database-connector\";\r\nimport { HeraldConnector } from \"./herald-connector\";\r\nimport { HttpConnector } from \"./http-connector\";\r\nimport { LoggerConnector } from \"./logger-connector\";\r\nimport { MailerConnector } from \"./mail-connector\";\r\nimport { NotificationsConnector } from \"./notifications-connector\";\r\nimport { SocketConnector } from \"./socket-connector\";\r\nimport { StorageConnector } from \"./storage.connector\";\r\nimport { ConnectorLifecyclePhase } from \"./types\";\r\nimport type { Connector, ConnectorName } from \"./types\";\r\n\r\n/**\r\n * Overall shutdown budget in ms. Override via `app.shutdownTimeout`.\r\n */\r\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\r\n\r\nexport class ConnectorsManager {\r\n /**\r\n * Connectors list\r\n */\r\n private readonly connectors: Connector[] = [];\r\n\r\n /**\r\n * The names the framework's own connectors hold.\r\n *\r\n * Filled by the constructor's own registration calls rather than declared\r\n * beside them, so there is exactly ONE place a built-in name exists: the\r\n * connector that owns it. A second list — in the builder, in a config\r\n * validator, in a test double — is a list that drifts the moment a built-in\r\n * is added or renamed, and it drifts silently, because nothing compares the\r\n * two. Both halves of a connector's life read this one through\r\n * {@link isBuiltInName}: the build refuses a `connectors` array that claims\r\n * a built-in name, and so does registration.\r\n */\r\n private readonly builtInNames = new Set<string>();\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor() {\r\n this.registerBuiltIn(new LoggerConnector());\r\n this.registerBuiltIn(new MailerConnector());\r\n this.registerBuiltIn(new HttpConnector());\r\n this.registerBuiltIn(new DatabaseConnector());\r\n this.registerBuiltIn(new HeraldConnector());\r\n this.registerBuiltIn(new CacheConnector());\r\n this.registerBuiltIn(new StorageConnector());\r\n this.registerBuiltIn(new SocketConnector());\r\n this.registerBuiltIn(new NotificationsConnector());\r\n this.registerBuiltIn(new AccessConnector());\r\n this.registerBuiltIn(new AiConnector());\r\n }\r\n\r\n /**\r\n * Register a framework-owned connector and reserve its name.\r\n *\r\n * Private on purpose: an app connector must not be able to reserve a name,\r\n * and the set is only correct if every entry in it came from a connector\r\n * this class constructed itself.\r\n */\r\n private registerBuiltIn(...connectors: Connector[]): void {\r\n for (const connector of connectors) {\r\n this.builtInNames.add(connector.name);\r\n }\r\n\r\n this.register(...connectors);\r\n }\r\n\r\n /**\r\n * Whether the given name belongs to a framework built-in connector.\r\n *\r\n * Reserved, not merely taken: a configured connector carrying one of these\r\n * names would be skipped by {@link has} at boot while the build had already\r\n * drained its contribution, so the artifact would carry a connector the\r\n * server never runs. Callers refuse the name instead of skipping it.\r\n */\r\n public isBuiltInName(name: string): boolean {\r\n return this.builtInNames.has(name);\r\n }\r\n\r\n /**\r\n * Register a connector\r\n */\r\n public register(...connectors: Connector[]): void {\r\n this.connectors.push(...connectors);\r\n // sort connectors by priority\r\n this.connectors.sort((a, b) => a.priority - b.priority);\r\n }\r\n\r\n /**\r\n * Get all connectors\r\n */\r\n public list(): Connector[] {\r\n return this.connectors;\r\n }\r\n\r\n /**\r\n * Whether a connector with the given name is already registered.\r\n *\r\n * Names are the identity of a connector here, which is what lets a caller\r\n * that may run more than once per process register idempotently instead of\r\n * stacking duplicates that would each boot and shut down.\r\n */\r\n public has(name: ConnectorName): boolean {\r\n return this.connectors.some((connector) => connector.name === name);\r\n }\r\n\r\n /**\r\n * start all connectors\r\n */\r\n public async start(connectorsNames?: ConnectorName[]): Promise<void> {\r\n const connectorsList = connectorsNames\r\n ? this.connectors.filter((connector) => connectorsNames.includes(connector.name))\r\n : this.connectors;\r\n\r\n for (const connector of connectorsList) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of connectorsList) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors in the given lifecycle phase.\r\n *\r\n * The production builder and dev preload split startup around app\r\n * code: early phase before app imports, late phase after. Within a\r\n * phase, all connectors `boot()` first, then all `start()`, so\r\n * cross-connector wiring inside the phase still works (e.g. socket\r\n * reads http's instance during its own boot).\r\n */\r\n public async startPhase(phase: ConnectorLifecyclePhase): Promise<void> {\r\n const phaseConnectors = this.connectors.filter(\r\n (connector) => connector.lifecyclePhase === phase,\r\n );\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors except the given ones\r\n */\r\n public async startWithout(excludedConnectors: ConnectorName[]): Promise<void> {\r\n await this.start(\r\n this.connectors\r\n .filter((connector) => !excludedConnectors.includes(connector.name))\r\n .map((connector) => connector.name),\r\n );\r\n }\r\n\r\n /**\r\n * Shutdown all connectors\r\n */\r\n public async shutdown(): Promise<void> {\r\n // App-owned teardown runs first, while every connector (db, cache, http) is\r\n // still up, so cleanup hooks can use them. Idempotent + error-isolated.\r\n await Application.runShutdownHooks();\r\n\r\n // HTTP stops accepting and drains in-flight requests FIRST, while storage,\r\n // socket, etc. are still up; then the rest go in reverse priority. Copy the\r\n // list first — `reverse()` mutates in place, so reversing the live array\r\n // would corrupt the order on a second shutdown pass.\r\n const reversed = [...this.connectors].reverse();\r\n const ordered = [\r\n ...reversed.filter((connector) => connector.name === \"http\"),\r\n ...reversed.filter((connector) => connector.name !== \"http\"),\r\n ];\r\n\r\n const timeout = config.get(\"app.shutdownTimeout\", DEFAULT_SHUTDOWN_TIMEOUT);\r\n let current: string | undefined;\r\n\r\n const timer = setTimeout(() => {\r\n // Sync write: the process exits right after, so an async log would be lost.\r\n console.error(\r\n `Shutdown timed out after ${timeout}ms; connector still shutting down: ${current ?? \"none\"}`,\r\n );\r\n process.exit(1);\r\n }, timeout);\r\n timer.unref?.();\r\n\r\n try {\r\n for (const connector of ordered) {\r\n current = connector.name;\r\n await this.shutdownOne(connector);\r\n }\r\n current = undefined;\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n\r\n // `Logger.log()` hands each entry to `channel.log()` without awaiting it, so\r\n // awaiting the calls above is not enough on its own — a buffered or async\r\n // channel (file, Sentry) still loses the entry when the process exits.\r\n await log.flush();\r\n }\r\n\r\n /**\r\n * Shut one connector down, isolating and reporting its failure.\r\n */\r\n private async shutdownOne(connector: Connector): Promise<void> {\r\n try {\r\n await connector.shutdown();\r\n } catch (error) {\r\n try {\r\n // Awaited deliberately: the caller (`shutdownOnProcessKill`) calls\r\n // `process.exit(0)` as soon as this resolves, so an un-awaited log is\r\n // a log that never happens.\r\n await log.error(\"connectors\", \"shutdown\", error as Error, { connector: connector.name });\r\n } catch {\r\n // Reporting a failure must never become a worse failure than the one\r\n // being reported: a channel that throws synchronously rejects this call\r\n // INSIDE the catch, which would abort the remaining teardown. Swallowed\r\n // rather than re-reported: the only channel we could report it through\r\n // is the one that just threw.\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown connectors on process kill\r\n *\r\n * Registers signal handlers that run an idempotent graceful shutdown:\r\n * - SIGINT: Ctrl+C on Unix, also caught on Windows but unreliable in child processes\r\n * - SIGTERM: Termination signal (Unix primarily)\r\n * - SIGHUP: registered on Windows (`win32`) only, to catch Ctrl+C / console close\r\n *\r\n * Note: no `beforeExit` handler is registered — `beforeExit` cannot reliably\r\n * await async teardown (the process exits once the callback returns), so\r\n * shutdown is driven entirely by the signal handlers above.\r\n */\r\n public shutdownOnProcessKill(): void {\r\n let isShuttingDown = false;\r\n\r\n const gracefulShutdown = async (signal: string) => {\r\n if (isShuttingDown) return;\r\n isShuttingDown = true;\r\n\r\n console.log(`\\nExiting...`);\r\n await this.shutdown();\r\n process.exit(0);\r\n };\r\n\r\n // Unix signals\r\n process.on(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\r\n process.on(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\r\n\r\n // Windows-specific: handle when process is about to exit\r\n // This is more reliable on Windows for spawned child processes\r\n if (process.platform === \"win32\") {\r\n // Handle Ctrl+C on Windows specifically\r\n process.on(\"SIGHUP\", () => gracefulShutdown(\"SIGHUP\"));\r\n }\r\n }\r\n}\r\n\r\nexport const connectorsManager = new ConnectorsManager();\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region ../core/src/connectors/single-server-warnings.ts
|
|
2
|
+
const IN_PROCESS_CACHE_NAMES = [
|
|
3
|
+
"memory",
|
|
4
|
+
"memoryextended",
|
|
5
|
+
"lru"
|
|
6
|
+
];
|
|
7
|
+
const IN_PROCESS_CACHE_CLASSES = [
|
|
8
|
+
"memorycachedriver",
|
|
9
|
+
"memoryextendedcachedriver",
|
|
10
|
+
"lrumemorycachedriver"
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Warning for a production app whose default cache driver keeps data in this
|
|
14
|
+
* process only. Returns `undefined` when there is nothing to warn about.
|
|
15
|
+
*/
|
|
16
|
+
function memoryCacheWarning(defaultDriverName, driverClassName, env, silenced = false) {
|
|
17
|
+
if (env !== "production" || silenced || !defaultDriverName) return void 0;
|
|
18
|
+
const byName = IN_PROCESS_CACHE_NAMES.includes(defaultDriverName.toLowerCase());
|
|
19
|
+
const byClass = !!driverClassName && IN_PROCESS_CACHE_CLASSES.includes(driverClassName.toLowerCase());
|
|
20
|
+
if (!byName && !byClass) return void 0;
|
|
21
|
+
return `cache: the default driver '${defaultDriverName}' keeps data in this process only. With more than one server, repository cache clears, locks and counters don't reach the other servers. Use the redis or pg cache driver in production, or set cache.silenceSingleServerWarning = true.`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Warning for a production app whose default storage driver is the local disk.
|
|
25
|
+
*/
|
|
26
|
+
function localStorageWarning(defaultDriverName, env, silenced = false) {
|
|
27
|
+
if (env !== "production" || silenced || defaultDriverName !== "local") return void 0;
|
|
28
|
+
return "storage: the default driver 'local' keeps files on this server's disk only. With more than one server, uploads written on one server are missing on the others. Use an S3 or R2 storage driver in production, or set storage.silenceSingleServerWarning = true.";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { localStorageWarning, memoryCacheWarning };
|
|
33
|
+
//# sourceMappingURL=single-server-warnings.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"single-server-warnings.mjs","names":[],"sources":["../../../../../../../core/src/connectors/single-server-warnings.ts"],"sourcesContent":["import type { Environment } from \"../utils/environment\";\n\nconst IN_PROCESS_CACHE_NAMES = [\"memory\", \"memoryextended\", \"lru\"];\nconst IN_PROCESS_CACHE_CLASSES = [\"memorycachedriver\", \"memoryextendedcachedriver\", \"lrumemorycachedriver\"];\n\n/**\n * Warning for a production app whose default cache driver keeps data in this\n * process only. Returns `undefined` when there is nothing to warn about.\n */\nexport function memoryCacheWarning(\n defaultDriverName: string | undefined,\n driverClassName: string | undefined,\n env: Environment,\n silenced = false,\n): string | undefined {\n if (env !== \"production\" || silenced || !defaultDriverName) return undefined;\n\n const byName = IN_PROCESS_CACHE_NAMES.includes(defaultDriverName.toLowerCase());\n const byClass =\n !!driverClassName && IN_PROCESS_CACHE_CLASSES.includes(driverClassName.toLowerCase());\n\n if (!byName && !byClass) return undefined;\n\n return (\n `cache: the default driver '${defaultDriverName}' keeps data in this process only. ` +\n \"With more than one server, repository cache clears, locks and counters don't reach the other servers. \" +\n \"Use the redis or pg cache driver in production, or set cache.silenceSingleServerWarning = true.\"\n );\n}\n\n/**\n * Warning for a production app whose default storage driver is the local disk.\n */\nexport function localStorageWarning(\n defaultDriverName: string | undefined,\n env: Environment,\n silenced = false,\n): string | undefined {\n if (env !== \"production\" || silenced || defaultDriverName !== \"local\") return undefined;\n\n return (\n \"storage: the default driver 'local' keeps files on this server's disk only. \" +\n \"With more than one server, uploads written on one server are missing on the others. \" +\n \"Use an S3 or R2 storage driver in production, or set storage.silenceSingleServerWarning = true.\"\n );\n}\n"],"mappings":";AAEA,MAAM,yBAAyB;CAAC;CAAU;CAAkB;AAAK;AACjE,MAAM,2BAA2B;CAAC;CAAqB;CAA6B;AAAsB;;;;;AAM1G,SAAgB,mBACd,mBACA,iBACA,KACA,WAAW,OACS;CACpB,IAAI,QAAQ,gBAAgB,YAAY,CAAC,mBAAmB,OAAO;CAEnE,MAAM,SAAS,uBAAuB,SAAS,kBAAkB,YAAY,CAAC;CAC9E,MAAM,UACJ,CAAC,CAAC,mBAAmB,yBAAyB,SAAS,gBAAgB,YAAY,CAAC;CAEtF,IAAI,CAAC,UAAU,CAAC,SAAS,OAAO;CAEhC,OACE,8BAA8B,kBAAkB;AAIpD;;;;AAKA,SAAgB,oBACd,mBACA,KACA,WAAW,OACS;CACpB,IAAI,QAAQ,gBAAgB,YAAY,sBAAsB,SAAS,OAAO;CAE9E,OACE;AAIJ"}
|
|
@@ -27,6 +27,20 @@ declare class SocketConnector extends BaseConnector {
|
|
|
27
27
|
* created one itself (http absent). See {@link shutdown}.
|
|
28
28
|
*/
|
|
29
29
|
protected ownsRawServer: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The shared server socket.io attached to, kept for the detach.
|
|
32
|
+
*/
|
|
33
|
+
protected sharedServer?: any;
|
|
34
|
+
/**
|
|
35
|
+
* Listener changes socket.io/engine.io made on the shared server at attach:
|
|
36
|
+
* the listeners they added, and the ones they removed (engine.io lifts the
|
|
37
|
+
* existing `request` listeners into its own wrapper). Undone on detach so
|
|
38
|
+
* repeated dev restarts don't stack engines on Fastify's server.
|
|
39
|
+
*/
|
|
40
|
+
protected attachedListeners: {
|
|
41
|
+
added: Array<[string, (...args: any[]) => void]>;
|
|
42
|
+
removed: Array<[string, (...args: any[]) => void]>;
|
|
43
|
+
};
|
|
30
44
|
/**
|
|
31
45
|
* Boot the connector
|
|
32
46
|
*/
|
|
@@ -35,6 +49,11 @@ declare class SocketConnector extends BaseConnector {
|
|
|
35
49
|
* Initialize Socket server
|
|
36
50
|
*/
|
|
37
51
|
start(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Restart re-boots too: the closed `io` must be replaced and the new
|
|
54
|
+
* `socket` config read, otherwise a config edit in dev restarts nothing.
|
|
55
|
+
*/
|
|
56
|
+
restart(): Promise<void>;
|
|
38
57
|
/**
|
|
39
58
|
* Shutdown Socket server
|
|
40
59
|
*
|
|
@@ -45,6 +64,16 @@ declare class SocketConnector extends BaseConnector {
|
|
|
45
64
|
* race to `close()` the same socket.
|
|
46
65
|
*/
|
|
47
66
|
shutdown(): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Current listeners of the events socket.io touches on a node server.
|
|
69
|
+
*/
|
|
70
|
+
private snapshotListeners;
|
|
71
|
+
private diffListeners;
|
|
72
|
+
/**
|
|
73
|
+
* Undo what attaching did to the shared server, leaving listeners other
|
|
74
|
+
* consumers added since (e.g. Vite's HMR upgrade handler) untouched.
|
|
75
|
+
*/
|
|
76
|
+
private restoreListeners;
|
|
48
77
|
}
|
|
49
78
|
//#endregion
|
|
50
79
|
export { SocketConnector };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/socket-connector.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"socket-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/socket-connector.ts"],"mappings":";;;;;;;AAkCA;;cAAa,eAAA,SAAwB,aAAA;EAAA,SACnB,IAAA,EAAM,aAAA;EAAA,SACN,QAAA,GAAQ,iBAAA,CAAA,MAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,IAAA;EASX;;;;;EAAA,mBAFA,YAAA;EAAA,UAET,MAAA,GAAS,MAAA;EAZgB;;;;;;;;EAAA,UAsBzB,aAAA;EAnBM;;;EAAA,UAwBN,YAAA;EAfA;;;;;;EAAA,UAuBA,iBAAA;IACR,KAAA,EAAO,KAAA,cAAmB,IAAA;IAC1B,OAAA,EAAS,KAAA,cAAmB,IAAA;EAAA;EAAA;;;EAMjB,IAAA,IAAI,OAAA;EA2EK;;;EAAT,KAAA,IAAS,OAAA;EA+BG;;;;EAfZ,OAAA,IAAW,OAAA;EA4EA;;;;;;;;;EA7DX,QAAA,IAAY,OAAA;;;;UA8BjB,iBAAA;EAAA,QAUA,aAAA;;;;;UAqBA,gBAAA;AAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { environment } from "../utils/environment.mjs";
|
|
1
2
|
import { container } from "../container/index.mjs";
|
|
2
3
|
import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
|
|
3
4
|
import { BaseConnector } from "./base-connector.mjs";
|
|
@@ -23,6 +24,7 @@ Or manually:
|
|
|
23
24
|
pnpm add socket.io
|
|
24
25
|
yarn add socket.io
|
|
25
26
|
`.trim();
|
|
27
|
+
const SINGLE_SERVER_WARNING = "socket: no adapter configured — broadcasts only reach clients connected to this server. Use @socket.io/redis-adapter (socket.adapter) behind a load balancer, with sticky sessions for the polling transport.";
|
|
26
28
|
/**
|
|
27
29
|
* Socket Connector
|
|
28
30
|
* Manages Socket server (Socket.IO) lifecycle
|
|
@@ -35,6 +37,10 @@ var SocketConnector = class extends BaseConnector {
|
|
|
35
37
|
this.lifecyclePhase = "late";
|
|
36
38
|
this.watchedFiles = ["src/config/socket.ts"];
|
|
37
39
|
this.ownsRawServer = false;
|
|
40
|
+
this.attachedListeners = {
|
|
41
|
+
added: [],
|
|
42
|
+
removed: []
|
|
43
|
+
};
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
40
46
|
* Boot the connector
|
|
@@ -55,15 +61,28 @@ var SocketConnector = class extends BaseConnector {
|
|
|
55
61
|
server = fastify.server;
|
|
56
62
|
this.ownsRawServer = false;
|
|
57
63
|
} else {
|
|
58
|
-
server = socketConfig.ssl ? createServer$1(
|
|
64
|
+
server = socketConfig.ssl ? createServer$1({
|
|
65
|
+
key: socketConfig.ssl.key,
|
|
66
|
+
cert: socketConfig.ssl.cert
|
|
67
|
+
}) : createServer();
|
|
68
|
+
server.on("error", (error) => {
|
|
69
|
+
log.error("socket", "connection", error, { port: socketConfig.port });
|
|
70
|
+
});
|
|
59
71
|
server.listen(socketConfig.port);
|
|
60
72
|
this.ownsRawServer = true;
|
|
61
73
|
}
|
|
62
74
|
container.set("socket.rawServer", server);
|
|
75
|
+
const before = this.snapshotListeners(server);
|
|
63
76
|
this.socket = new SocketServer(server, {
|
|
64
77
|
destroyUpgrade: false,
|
|
65
78
|
...socketConfig.options
|
|
66
79
|
});
|
|
80
|
+
if (!this.ownsRawServer) {
|
|
81
|
+
this.sharedServer = server;
|
|
82
|
+
this.attachedListeners = this.diffListeners(before, this.snapshotListeners(server));
|
|
83
|
+
}
|
|
84
|
+
if (socketConfig.adapter) this.socket.adapter(await socketConfig.adapter(this.socket));
|
|
85
|
+
else if (environment() === "production" && !socketConfig.silenceSingleServerWarning) log.warn("socket", "adapter", SINGLE_SERVER_WARNING);
|
|
67
86
|
container.set("socket", this.socket);
|
|
68
87
|
}
|
|
69
88
|
/**
|
|
@@ -75,6 +94,15 @@ var SocketConnector = class extends BaseConnector {
|
|
|
75
94
|
this.active = true;
|
|
76
95
|
}
|
|
77
96
|
/**
|
|
97
|
+
* Restart re-boots too: the closed `io` must be replaced and the new
|
|
98
|
+
* `socket` config read, otherwise a config edit in dev restarts nothing.
|
|
99
|
+
*/
|
|
100
|
+
async restart() {
|
|
101
|
+
await this.shutdown();
|
|
102
|
+
await this.boot();
|
|
103
|
+
await this.start();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
78
106
|
* Shutdown Socket server
|
|
79
107
|
*
|
|
80
108
|
* Always closes the Socket.IO layer (and awaits the drain). The underlying
|
|
@@ -86,18 +114,58 @@ var SocketConnector = class extends BaseConnector {
|
|
|
86
114
|
async shutdown() {
|
|
87
115
|
if (!this.active) return;
|
|
88
116
|
const socket = container.tryGet("socket");
|
|
89
|
-
if (socket) await new Promise((resolve) => {
|
|
117
|
+
if (socket) if (this.ownsRawServer) await new Promise((resolve) => {
|
|
90
118
|
socket.close(() => resolve());
|
|
91
119
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
});
|
|
120
|
+
else {
|
|
121
|
+
socket.of("/").disconnectSockets(true);
|
|
122
|
+
socket.engine.close();
|
|
123
|
+
this.restoreListeners();
|
|
124
|
+
}
|
|
99
125
|
this.active = false;
|
|
100
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Current listeners of the events socket.io touches on a node server.
|
|
129
|
+
*/
|
|
130
|
+
snapshotListeners(server) {
|
|
131
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
132
|
+
for (const event of [
|
|
133
|
+
"request",
|
|
134
|
+
"upgrade",
|
|
135
|
+
"close",
|
|
136
|
+
"listening"
|
|
137
|
+
]) snapshot.set(event, server?.listeners?.(event)?.slice() ?? []);
|
|
138
|
+
return snapshot;
|
|
139
|
+
}
|
|
140
|
+
diffListeners(before, after) {
|
|
141
|
+
const added = [];
|
|
142
|
+
const removed = [];
|
|
143
|
+
for (const [event, listeners] of before) {
|
|
144
|
+
const now = after.get(event) ?? [];
|
|
145
|
+
for (const listener of now) if (!listeners.includes(listener)) added.push([event, listener]);
|
|
146
|
+
for (const listener of listeners) if (!now.includes(listener)) removed.push([event, listener]);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
added,
|
|
150
|
+
removed
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Undo what attaching did to the shared server, leaving listeners other
|
|
155
|
+
* consumers added since (e.g. Vite's HMR upgrade handler) untouched.
|
|
156
|
+
*/
|
|
157
|
+
restoreListeners() {
|
|
158
|
+
const server = this.sharedServer;
|
|
159
|
+
if (!server) return;
|
|
160
|
+
const { added, removed } = this.attachedListeners;
|
|
161
|
+
for (const [event, listener] of added) server.removeListener(event, listener);
|
|
162
|
+
for (const [event, listener] of removed) server.on(event, listener);
|
|
163
|
+
this.attachedListeners = {
|
|
164
|
+
added: [],
|
|
165
|
+
removed: []
|
|
166
|
+
};
|
|
167
|
+
this.sharedServer = void 0;
|
|
168
|
+
}
|
|
101
169
|
};
|
|
102
170
|
|
|
103
171
|
//#endregion
|