@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,14 +1,16 @@
|
|
|
1
1
|
import { storagePath } from "../../utils/paths.mjs";
|
|
2
2
|
import { url } from "../../utils/urls.mjs";
|
|
3
|
+
import { StorageError } from "../utils/storage-error.mjs";
|
|
3
4
|
import { storageDriverContext } from "../context/storage-driver-context.mjs";
|
|
4
5
|
import { getMimeType } from "../utils/mime.mjs";
|
|
5
6
|
import { resolveWithinRoot } from "../utils/contain-path.mjs";
|
|
6
|
-
import { dirname, join, resolve } from "path";
|
|
7
|
+
import { basename, dirname, join, resolve } from "path";
|
|
7
8
|
import { ltrim } from "@mongez/reinforcements";
|
|
8
9
|
import { directoryExistsAsync, ensureDirectoryAsync, fileExistsAsync, removeDirectoryAsync, unlinkAsync } from "@warlock.js/fs";
|
|
9
|
-
import { createReadStream, createWriteStream } from "fs";
|
|
10
|
-
import { copyFile, readFile, readdir, rename, stat, writeFile } from "fs/promises";
|
|
10
|
+
import { createReadStream, createWriteStream, promises } from "fs";
|
|
11
|
+
import { copyFile, readFile, readdir, rename, stat, unlink, writeFile } from "fs/promises";
|
|
11
12
|
import crypto from "crypto";
|
|
13
|
+
import { Transform } from "stream";
|
|
12
14
|
import { pipeline } from "stream/promises";
|
|
13
15
|
|
|
14
16
|
//#region ../core/src/storage/drivers/local-driver.ts
|
|
@@ -26,7 +28,6 @@ var LocalDriver = class {
|
|
|
26
28
|
this.options = options;
|
|
27
29
|
this.name = "local";
|
|
28
30
|
this.urlPrefix = "";
|
|
29
|
-
this._metadata = /* @__PURE__ */ new Map();
|
|
30
31
|
this.root = resolve(options.root ?? storagePath());
|
|
31
32
|
this.urlPrefix = options.urlPrefix ?? "";
|
|
32
33
|
this.temporaryUrlPrefix = options.temporaryUrlPrefix ?? "/temp-files";
|
|
@@ -58,7 +59,43 @@ var LocalDriver = class {
|
|
|
58
59
|
const fileBuffer = await this.toBuffer(file);
|
|
59
60
|
const hash = this.calculateHash(fileBuffer);
|
|
60
61
|
await writeFile(absolutePath, new Uint8Array(fileBuffer));
|
|
61
|
-
|
|
62
|
+
const stats = await stat(absolutePath);
|
|
63
|
+
const mimeType = options?.mimeType || this.guessMimeType(location);
|
|
64
|
+
return {
|
|
65
|
+
path: location,
|
|
66
|
+
url: this.url(location),
|
|
67
|
+
size: stats.size,
|
|
68
|
+
hash,
|
|
69
|
+
mimeType,
|
|
70
|
+
driver: this.name
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Atomically put a file only if nothing exists at `location`.
|
|
75
|
+
*
|
|
76
|
+
* Content is written to a temp file in the same directory, then hard-linked
|
|
77
|
+
* to the target. `link()` fails with EEXIST if the target exists, so exactly
|
|
78
|
+
* one concurrent caller wins, the object never appears half-written, and the
|
|
79
|
+
* check is atomic on NFS too. Returns `null` only on EEXIST; other errors throw.
|
|
80
|
+
*/
|
|
81
|
+
async putIfAbsent(file, location, options) {
|
|
82
|
+
const absolutePath = this.getAbsolutePath(location);
|
|
83
|
+
const directory = dirname(absolutePath);
|
|
84
|
+
await ensureDirectoryAsync(directory);
|
|
85
|
+
const fileBuffer = typeof file === "string" ? Buffer.from(file) : file;
|
|
86
|
+
const hash = this.calculateHash(fileBuffer);
|
|
87
|
+
const tempPath = join(directory, `.${basename(absolutePath)}.${crypto.randomBytes(8).toString("hex")}.tmp`);
|
|
88
|
+
try {
|
|
89
|
+
await writeFile(tempPath, new Uint8Array(fileBuffer));
|
|
90
|
+
try {
|
|
91
|
+
await promises.link(tempPath, absolutePath);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error?.code === "EEXIST") return null;
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
await unlink(tempPath).catch(() => void 0);
|
|
98
|
+
}
|
|
62
99
|
const stats = await stat(absolutePath);
|
|
63
100
|
const mimeType = options?.mimeType || this.guessMimeType(location);
|
|
64
101
|
return {
|
|
@@ -76,10 +113,12 @@ var LocalDriver = class {
|
|
|
76
113
|
async putStream(stream, location, options) {
|
|
77
114
|
const absolutePath = this.getAbsolutePath(location);
|
|
78
115
|
await ensureDirectoryAsync(dirname(absolutePath));
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
116
|
+
const hasher = crypto.createHash("sha256");
|
|
117
|
+
await pipeline(stream, new Transform({ transform(chunk, _encoding, callback) {
|
|
118
|
+
hasher.update(chunk);
|
|
119
|
+
callback(null, chunk);
|
|
120
|
+
} }), createWriteStream(absolutePath));
|
|
121
|
+
const hash = hasher.digest("hex");
|
|
83
122
|
const stats = await stat(absolutePath);
|
|
84
123
|
const mimeType = options?.mimeType || this.guessMimeType(location);
|
|
85
124
|
return {
|
|
@@ -114,7 +153,6 @@ var LocalDriver = class {
|
|
|
114
153
|
const absolutePath = this.getAbsolutePath(location);
|
|
115
154
|
if (!await fileExistsAsync(absolutePath)) return false;
|
|
116
155
|
await unlinkAsync(absolutePath);
|
|
117
|
-
this._metadata.delete(location);
|
|
118
156
|
return true;
|
|
119
157
|
}
|
|
120
158
|
/**
|
|
@@ -144,8 +182,10 @@ var LocalDriver = class {
|
|
|
144
182
|
* contained within the storage root — the raw, caller-supplied argument is
|
|
145
183
|
* never handed to the filesystem (preventing `../` traversal deletes).
|
|
146
184
|
*/
|
|
147
|
-
async deleteDirectory(directoryPath) {
|
|
148
|
-
|
|
185
|
+
async deleteDirectory(directoryPath, options) {
|
|
186
|
+
const absolutePath = this.getAbsolutePath(directoryPath);
|
|
187
|
+
if (!options?.allowRoot && resolve(absolutePath) === this.root) throw new StorageError(`Refusing to delete the storage root ("${directoryPath}"). Pass { allowRoot: true } to confirm.`, { context: { directoryPath } });
|
|
188
|
+
await removeDirectoryAsync(absolutePath);
|
|
149
189
|
return true;
|
|
150
190
|
}
|
|
151
191
|
/**
|
|
@@ -159,7 +199,7 @@ var LocalDriver = class {
|
|
|
159
199
|
* Get public URL for file
|
|
160
200
|
*/
|
|
161
201
|
url(location) {
|
|
162
|
-
return url(this.urlPrefix + "/" + ltrim(location, "/"));
|
|
202
|
+
return url(this.urlPrefix + "/" + ltrim(this.applyPrefix(location), "/"));
|
|
163
203
|
}
|
|
164
204
|
/**
|
|
165
205
|
* Get a temporary signed URL that expires
|
|
@@ -251,20 +291,17 @@ var LocalDriver = class {
|
|
|
251
291
|
* Get file info/metadata without downloading
|
|
252
292
|
*/
|
|
253
293
|
async metadata(location) {
|
|
254
|
-
if (this._metadata.has(location)) return this._metadata.get(location);
|
|
255
294
|
const absolutePath = this.getAbsolutePath(location);
|
|
256
295
|
if (!await fileExistsAsync(absolutePath)) throw new Error(`File not found: ${absolutePath}`);
|
|
257
296
|
const stats = await stat(absolutePath);
|
|
258
|
-
|
|
259
|
-
this._metadata.set(location, {
|
|
297
|
+
return {
|
|
260
298
|
path: location,
|
|
261
|
-
name,
|
|
299
|
+
name: location.split("/").pop() || "",
|
|
262
300
|
size: stats.size,
|
|
263
301
|
isDirectory: stats.isDirectory(),
|
|
264
302
|
lastModified: stats.mtime,
|
|
265
303
|
mimeType: this.guessMimeType(location)
|
|
266
|
-
}
|
|
267
|
-
return this._metadata.get(location);
|
|
304
|
+
};
|
|
268
305
|
}
|
|
269
306
|
/**
|
|
270
307
|
* Get file size in bytes (shortcut for metadata().size)
|
|
@@ -281,9 +318,7 @@ var LocalDriver = class {
|
|
|
281
318
|
if (!await fileExistsAsync(fromPath)) throw new Error(`Source file not found: ${from}`);
|
|
282
319
|
await ensureDirectoryAsync(dirname(toPath));
|
|
283
320
|
await copyFile(fromPath, toPath);
|
|
284
|
-
this.
|
|
285
|
-
const fileBuffer = await readFile(toPath);
|
|
286
|
-
const hash = this.calculateHash(fileBuffer);
|
|
321
|
+
const hash = await this.hashFile(toPath);
|
|
287
322
|
const stats = await stat(toPath);
|
|
288
323
|
return {
|
|
289
324
|
path: to,
|
|
@@ -303,10 +338,7 @@ var LocalDriver = class {
|
|
|
303
338
|
if (!await fileExistsAsync(fromPath)) throw new Error(`Source file not found: ${from}`);
|
|
304
339
|
await ensureDirectoryAsync(dirname(toPath));
|
|
305
340
|
await rename(fromPath, toPath);
|
|
306
|
-
this.
|
|
307
|
-
this._metadata.delete(to);
|
|
308
|
-
const fileBuffer = await readFile(toPath);
|
|
309
|
-
const hash = this.calculateHash(fileBuffer);
|
|
341
|
+
const hash = await this.hashFile(toPath);
|
|
310
342
|
const stats = await stat(toPath);
|
|
311
343
|
return {
|
|
312
344
|
path: to,
|
|
@@ -365,14 +397,17 @@ var LocalDriver = class {
|
|
|
365
397
|
*/
|
|
366
398
|
getAbsolutePath(location) {
|
|
367
399
|
const prefixedLocation = this.applyPrefix(location);
|
|
368
|
-
|
|
400
|
+
const resolved = resolveWithinRoot(this.root, prefixedLocation);
|
|
401
|
+
const prefix = storageDriverContext.getPrefix() || this.options.prefix;
|
|
402
|
+
if (prefix) resolveWithinRoot(resolveWithinRoot(this.root, prefix.replace(/^[\\/]+|[\\/]+$/g, "")), resolved);
|
|
403
|
+
return resolved;
|
|
369
404
|
}
|
|
370
405
|
/**
|
|
371
406
|
* Convert various input types to Buffer
|
|
372
407
|
*/
|
|
373
408
|
async toBuffer(file) {
|
|
374
409
|
if (Buffer.isBuffer(file)) return file;
|
|
375
|
-
if (typeof file === "string") return
|
|
410
|
+
if (typeof file === "string") return Buffer.from(file);
|
|
376
411
|
return file.buffer();
|
|
377
412
|
}
|
|
378
413
|
/**
|
|
@@ -382,6 +417,14 @@ var LocalDriver = class {
|
|
|
382
417
|
return crypto.createHash("sha256").update(new Uint8Array(buffer)).digest("hex");
|
|
383
418
|
}
|
|
384
419
|
/**
|
|
420
|
+
* Calculate SHA-256 hash of a file by streaming it (constant memory)
|
|
421
|
+
*/
|
|
422
|
+
async hashFile(absolutePath) {
|
|
423
|
+
const hasher = crypto.createHash("sha256");
|
|
424
|
+
for await (const chunk of createReadStream(absolutePath)) hasher.update(chunk);
|
|
425
|
+
return hasher.digest("hex");
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
385
428
|
* Guess MIME type from file extension
|
|
386
429
|
*/
|
|
387
430
|
guessMimeType(location) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-driver.mjs","names":[],"sources":["../../../../../../../../core/src/storage/drivers/local-driver.ts"],"sourcesContent":["import {\n directoryExistsAsync,\n ensureDirectoryAsync,\n fileExistsAsync,\n removeDirectoryAsync,\n unlinkAsync,\n} from \"@warlock.js/fs\";\nimport { ltrim } from \"@mongez/reinforcements\";\nimport crypto from \"crypto\";\nimport { createReadStream, createWriteStream } from \"fs\";\nimport { copyFile, readFile, readdir, rename, stat, writeFile } from \"fs/promises\";\nimport { dirname, join, resolve } from \"path\";\nimport type { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { UploadedFile } from \"../../http\";\nimport { storagePath } from \"../../utils/paths\";\nimport { url } from \"../../utils/urls\";\nimport { storageDriverContext } from \"../context/storage-driver-context\";\nimport { resolveWithinRoot } from \"../utils/contain-path\";\nimport type {\n DeleteManyResult,\n ListOptions,\n LocalStorageDriverOptions,\n PutOptions,\n StorageDriverContract,\n StorageDriverType,\n StorageFileData,\n StorageFileInfo,\n TemporaryTokenPayload,\n TemporaryTokenValidation,\n} from \"../types\";\nimport { getMimeType } from \"../utils/mime\";\n\n/**\n * Local filesystem storage driver\n *\n * Stores files on the local filesystem with support for:\n * - File operations (put, get, delete, copy, move)\n * - Stream operations for large files\n * - Batch operations\n * - Signed temporary URLs\n */\nexport class LocalDriver implements StorageDriverContract {\n /**\n * Driver name\n */\n public readonly name: StorageDriverType = \"local\";\n\n /**\n * Root path for storage\n */\n protected root: string;\n\n /**\n * URL prefix for file URLs\n */\n protected urlPrefix: string = \"\";\n\n /**\n * URL prefix for temporary file URLs\n */\n protected temporaryUrlPrefix: string;\n\n /**\n * Secret key for signing temporary URLs\n */\n protected signatureKey?: string;\n\n /**\n * Cached Storage File Metadata\n */\n protected _metadata = new Map<string, StorageFileInfo>();\n\n public constructor(public options: LocalStorageDriverOptions = {}) {\n // Resolve the root to an absolute, normalized path up front so the\n // path-traversal containment check (resolveWithinRoot) has a stable\n // anchor to compare resolved locations against.\n this.root = resolve(options.root ?? storagePath());\n this.urlPrefix = options.urlPrefix ?? \"\";\n this.temporaryUrlPrefix = options.temporaryUrlPrefix ?? \"/temp-files\";\n this.signatureKey = options.signatureKey;\n }\n\n // ============================================================\n // Prefix Operations\n // ============================================================\n\n /**\n * Apply prefix to location path\n *\n * Priority: context prefix > driver options prefix > no prefix\n * This allows multi-tenant scenarios where context overrides driver config.\n *\n * @param location - Original location path\n * @returns Location with prefix applied if one exists\n */\n public applyPrefix(location: string): string {\n // Check context prefix first (highest priority)\n const contextPrefix = storageDriverContext.getPrefix();\n const prefix = contextPrefix || this.options.prefix;\n\n if (!prefix) {\n return location;\n }\n\n const cleanPrefix = prefix.replace(/\\/+$/, \"\");\n const cleanLocation = location.replace(/^\\/+/, \"\");\n\n // Avoid double-prefixing\n if (cleanLocation.startsWith(cleanPrefix + \"/\") || cleanLocation === cleanPrefix) {\n return cleanLocation;\n }\n\n return `${cleanPrefix}/${cleanLocation}`;\n }\n\n // ============================================================\n // Core File Operations\n // ============================================================\n\n /**\n * Put file to local storage\n */\n public async put(\n file: Buffer | string | UploadedFile,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n const fileBuffer = await this.toBuffer(file);\n const hash = this.calculateHash(fileBuffer);\n\n await writeFile(absolutePath, new Uint8Array(fileBuffer));\n\n // Invalidate any stale cached metadata for this location.\n this._metadata.delete(location);\n\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Put file from a readable stream (for large files)\n */\n public async putStream(\n stream: Readable,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n // Create write stream and pipe\n const writeStream = createWriteStream(absolutePath);\n await pipeline(stream, writeStream);\n\n // Invalidate any stale cached metadata for this location.\n this._metadata.delete(location);\n\n // Calculate hash and get stats\n const fileBuffer = await readFile(absolutePath);\n const hash = this.calculateHash(fileBuffer);\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Get file contents as Buffer\n */\n public async get(location: string): Promise<Buffer> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return readFile(absolutePath);\n }\n\n /**\n * Get file as a readable stream (for large files)\n */\n public async getStream(location: string): Promise<Readable> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return createReadStream(absolutePath);\n }\n\n /**\n * Delete a file\n */\n public async delete(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n return false;\n }\n\n await unlinkAsync(absolutePath);\n\n // Invalidate any stale cached metadata for this location.\n this._metadata.delete(location);\n\n return true;\n }\n\n /**\n * Delete multiple files at once\n */\n public async deleteMany(locations: string[]): Promise<DeleteManyResult[]> {\n const results: DeleteManyResult[] = [];\n\n for (const location of locations) {\n try {\n const deleted = await this.delete(location);\n results.push({ location, deleted });\n } catch (error) {\n results.push({\n location,\n deleted: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n\n return results;\n }\n\n /**\n * Delete directory\n *\n * The directory path is resolved through {@link getAbsolutePath} so it is\n * contained within the storage root — the raw, caller-supplied argument is\n * never handed to the filesystem (preventing `../` traversal deletes).\n */\n public async deleteDirectory(directoryPath: string) {\n const absolutePath = this.getAbsolutePath(directoryPath);\n\n await removeDirectoryAsync(absolutePath);\n\n return true;\n }\n\n /**\n * Check if file exists\n */\n public async exists(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n return Boolean(await fileExistsAsync(absolutePath));\n }\n\n // ============================================================\n // URL Operations\n // ============================================================\n\n /**\n * Get public URL for file\n */\n public url(location: string): string {\n return url(this.urlPrefix + \"/\" + ltrim(location, \"/\"));\n }\n\n /**\n * Get a temporary signed URL that expires\n * Returns a clean URL with encoded token: {temporaryUrlPrefix}/{token}\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration (default: 3600)\n */\n public async temporaryUrl(location: string, expiresIn = 3600): Promise<string> {\n if (!this.signatureKey) {\n throw new Error(\n \"Temporary URLs require a signatureKey in LocalDriver options. \" +\n \"Configure storage.drivers.local.signatureKey in your config.\",\n );\n }\n\n const token = this.encodeTemporaryToken(location, expiresIn);\n return `${this.temporaryUrlPrefix}/${token}`;\n }\n\n /**\n * Encode a temporary token containing path, expiry, and signature\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration\n */\n public encodeTemporaryToken(location: string, expiresIn: number): string {\n if (!this.signatureKey) {\n throw new Error(\"Temporary tokens require a signatureKey\");\n }\n\n const exp = Math.floor(Date.now() / 1000) + expiresIn;\n const sig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${location}:${exp}`)\n .digest(\"hex\");\n\n const payload: TemporaryTokenPayload = { path: location, exp, sig };\n const json = JSON.stringify(payload);\n\n // Use base64url encoding (URL-safe base64)\n return Buffer.from(json).toString(\"base64url\");\n }\n\n /**\n * Validate a temporary URL token\n * Returns a result object with validation status, file info, and convenience methods\n *\n * @param token - The token from the URL\n */\n public async validateTemporaryToken(token: string): Promise<TemporaryTokenValidation> {\n // Check signature key\n if (!this.signatureKey) {\n return { valid: false, error: \"missing_key\" };\n }\n\n // Decode token\n let payload: TemporaryTokenPayload;\n try {\n const json = Buffer.from(token, \"base64url\").toString(\"utf-8\");\n payload = JSON.parse(json);\n } catch {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Validate payload structure\n if (!payload.path || !payload.exp || !payload.sig) {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n return { valid: false, error: \"expired\" };\n }\n\n // Verify signature\n const expectedSig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${payload.path}:${payload.exp}`)\n .digest(\"hex\");\n\n const sigBuffer = Buffer.from(payload.sig, \"hex\");\n const expectedBuffer = Buffer.from(expectedSig, \"hex\");\n\n if (sigBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n const isValidSig = crypto.timingSafeEqual(\n new Uint8Array(sigBuffer),\n new Uint8Array(expectedBuffer),\n );\n\n if (!isValidSig) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n // Check file exists\n const absolutePath = this.getAbsolutePath(payload.path);\n if (!(await fileExistsAsync(absolutePath))) {\n return { valid: false, error: \"file_not_found\" };\n }\n\n // Build successful result with convenience methods\n const result: TemporaryTokenValidation = {\n valid: true,\n path: payload.path,\n absolutePath,\n expiresAt: new Date(payload.exp * 1000),\n mimeType: this.guessMimeType(payload.path),\n driver: this,\n getFile: () => this.get(payload.path),\n getStream: () => this.getStream(payload.path),\n };\n\n return result;\n }\n\n // ============================================================\n // Metadata Operations\n // ============================================================\n\n /**\n * Get file info/metadata without downloading\n */\n public async metadata(location: string): Promise<StorageFileInfo> {\n if (this._metadata.has(location)) {\n return this._metadata.get(location)!;\n }\n\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${absolutePath}`);\n }\n\n const stats = await stat(absolutePath);\n const name = location.split(\"/\").pop() || \"\";\n\n this._metadata.set(location, {\n path: location,\n name,\n size: stats.size,\n isDirectory: stats.isDirectory(),\n lastModified: stats.mtime,\n mimeType: this.guessMimeType(location),\n });\n\n return this._metadata.get(location)!;\n }\n\n /**\n * Get file size in bytes (shortcut for metadata().size)\n */\n public async size(location: string): Promise<number> {\n const metadata = await this.metadata(location);\n return metadata.size;\n }\n\n // ============================================================\n // File Operations\n // ============================================================\n\n /**\n * Copy file to a new location\n */\n public async copy(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await copyFile(fromPath, toPath);\n\n // Invalidate any stale cached metadata for the destination.\n this._metadata.delete(to);\n\n const fileBuffer = await readFile(toPath);\n const hash = this.calculateHash(fileBuffer);\n const stats = await stat(toPath);\n\n return {\n path: to,\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * Move file to a new location\n */\n public async move(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await rename(fromPath, toPath);\n\n // Invalidate any stale cached metadata for both source and destination.\n this._metadata.delete(from);\n this._metadata.delete(to);\n\n const fileBuffer = await readFile(toPath);\n const hash = this.calculateHash(fileBuffer);\n const stats = await stat(toPath);\n\n return {\n path: to,\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * List files in a directory\n */\n public async list(directory: string, options?: ListOptions): Promise<StorageFileInfo[]> {\n const absolutePath = this.getAbsolutePath(directory);\n const files: StorageFileInfo[] = [];\n\n // A listing target is a DIRECTORY, so guard with `directoryExistsAsync`.\n // `fileExistsAsync` resolves `false` for a folder, which would make `list`\n // always return [] for a real, populated directory.\n if (!(await directoryExistsAsync(absolutePath))) {\n return files;\n }\n\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n const entryPath = join(directory, entry.name);\n const entryStats = await stat(this.getAbsolutePath(entryPath));\n\n files.push({\n path: entryPath,\n name: entry.name,\n size: entryStats.size,\n isDirectory: entry.isDirectory(),\n lastModified: entryStats.mtime,\n mimeType: entry.isFile() ? this.guessMimeType(entry.name) : undefined,\n });\n\n // Recursively list subdirectories if requested\n if (options?.recursive && entry.isDirectory()) {\n const subFiles = await this.list(entryPath, options);\n files.push(...subFiles);\n }\n\n // Respect limit\n if (options?.limit && files.length >= options.limit) {\n break;\n }\n }\n\n return files;\n }\n\n // ============================================================\n // Path Operations\n // ============================================================\n\n /**\n * Get absolute filesystem path for a location\n */\n public path(location: string): string {\n return this.getAbsolutePath(location);\n }\n\n /**\n * Get the storage root directory\n */\n public getRoot(): string {\n return this.root;\n }\n\n // ============================================================\n // Utilities\n // ============================================================\n\n /**\n * Get absolute file path\n *\n * Routes through {@link resolveWithinRoot} so a location can never escape\n * the configured storage root via `..` traversal, an absolute path, or a\n * prefix breakout.\n */\n protected getAbsolutePath(location: string): string {\n const prefixedLocation = this.applyPrefix(location);\n return resolveWithinRoot(this.root, prefixedLocation);\n }\n\n /**\n * Convert various input types to Buffer\n */\n protected async toBuffer(file: Buffer | string | UploadedFile): Promise<Buffer> {\n if (Buffer.isBuffer(file)) {\n return file;\n }\n\n if (typeof file === \"string\") {\n return readFile(file);\n }\n\n return file.buffer();\n }\n\n /**\n * Calculate SHA-256 hash\n */\n protected calculateHash(buffer: Buffer): string {\n return crypto.createHash(\"sha256\").update(new Uint8Array(buffer)).digest(\"hex\");\n }\n\n /**\n * Guess MIME type from file extension\n */\n protected guessMimeType(location: string): string {\n return getMimeType(location);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-driver.mjs","names":["fsPromises"],"sources":["../../../../../../../../core/src/storage/drivers/local-driver.ts"],"sourcesContent":["import {\n directoryExistsAsync,\n ensureDirectoryAsync,\n fileExistsAsync,\n removeDirectoryAsync,\n unlinkAsync,\n} from \"@warlock.js/fs\";\nimport { ltrim } from \"@mongez/reinforcements\";\nimport crypto from \"crypto\";\nimport { createReadStream, createWriteStream, promises as fsPromises } from \"fs\";\nimport { copyFile, readFile, readdir, rename, stat, unlink, writeFile } from \"fs/promises\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { Transform, type Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { UploadedFile } from \"../../http\";\nimport { storagePath } from \"../../utils/paths\";\nimport { url } from \"../../utils/urls\";\nimport { storageDriverContext } from \"../context/storage-driver-context\";\nimport { resolveWithinRoot } from \"../utils/contain-path\";\nimport { StorageError } from \"../utils/storage-error\";\nimport type {\n DeleteManyResult,\n ListOptions,\n LocalStorageDriverOptions,\n PutOptions,\n StorageDriverContract,\n StorageDriverType,\n StorageFileData,\n StorageFileInfo,\n TemporaryTokenPayload,\n TemporaryTokenValidation,\n} from \"../types\";\nimport { getMimeType } from \"../utils/mime\";\n\n/**\n * Local filesystem storage driver\n *\n * Stores files on the local filesystem with support for:\n * - File operations (put, get, delete, copy, move)\n * - Stream operations for large files\n * - Batch operations\n * - Signed temporary URLs\n */\nexport class LocalDriver implements StorageDriverContract {\n /**\n * Driver name\n */\n public readonly name: StorageDriverType = \"local\";\n\n /**\n * Root path for storage\n */\n protected root: string;\n\n /**\n * URL prefix for file URLs\n */\n protected urlPrefix: string = \"\";\n\n /**\n * URL prefix for temporary file URLs\n */\n protected temporaryUrlPrefix: string;\n\n /**\n * Secret key for signing temporary URLs\n */\n protected signatureKey?: string;\n\n public constructor(public options: LocalStorageDriverOptions = {}) {\n // Resolve the root to an absolute, normalized path up front so the\n // path-traversal containment check (resolveWithinRoot) has a stable\n // anchor to compare resolved locations against.\n this.root = resolve(options.root ?? storagePath());\n this.urlPrefix = options.urlPrefix ?? \"\";\n this.temporaryUrlPrefix = options.temporaryUrlPrefix ?? \"/temp-files\";\n this.signatureKey = options.signatureKey;\n }\n\n // ============================================================\n // Prefix Operations\n // ============================================================\n\n /**\n * Apply prefix to location path\n *\n * Priority: context prefix > driver options prefix > no prefix\n * This allows multi-tenant scenarios where context overrides driver config.\n *\n * @param location - Original location path\n * @returns Location with prefix applied if one exists\n */\n public applyPrefix(location: string): string {\n // Check context prefix first (highest priority)\n const contextPrefix = storageDriverContext.getPrefix();\n const prefix = contextPrefix || this.options.prefix;\n\n if (!prefix) {\n return location;\n }\n\n const cleanPrefix = prefix.replace(/\\/+$/, \"\");\n const cleanLocation = location.replace(/^\\/+/, \"\");\n\n // Avoid double-prefixing\n if (cleanLocation.startsWith(cleanPrefix + \"/\") || cleanLocation === cleanPrefix) {\n return cleanLocation;\n }\n\n return `${cleanPrefix}/${cleanLocation}`;\n }\n\n // ============================================================\n // Core File Operations\n // ============================================================\n\n /**\n * Put file to local storage\n */\n public async put(\n file: Buffer | string | UploadedFile,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n const fileBuffer = await this.toBuffer(file);\n const hash = this.calculateHash(fileBuffer);\n\n await writeFile(absolutePath, new Uint8Array(fileBuffer));\n\n\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Atomically put a file only if nothing exists at `location`.\n *\n * Content is written to a temp file in the same directory, then hard-linked\n * to the target. `link()` fails with EEXIST if the target exists, so exactly\n * one concurrent caller wins, the object never appears half-written, and the\n * check is atomic on NFS too. Returns `null` only on EEXIST; other errors throw.\n */\n public async putIfAbsent(\n file: Buffer | string,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData | null> {\n const absolutePath = this.getAbsolutePath(location);\n const directory = dirname(absolutePath);\n\n await ensureDirectoryAsync(directory);\n\n // Unlike put(), a string here is CONTENT, never a source path (matches\n // the cloud driver): the objects this protects are small JSON bodies.\n const fileBuffer = typeof file === \"string\" ? Buffer.from(file) : file;\n const hash = this.calculateHash(fileBuffer);\n\n const tempPath = join(\n directory,\n `.${basename(absolutePath)}.${crypto.randomBytes(8).toString(\"hex\")}.tmp`,\n );\n\n try {\n await writeFile(tempPath, new Uint8Array(fileBuffer));\n\n try {\n await fsPromises.link(tempPath, absolutePath);\n } catch (error: any) {\n if (error?.code === \"EEXIST\") {\n return null;\n }\n\n throw error;\n }\n } finally {\n await unlink(tempPath).catch(() => undefined);\n }\n\n\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Put file from a readable stream (for large files)\n */\n public async putStream(\n stream: Readable,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n // Hash while streaming so large files are never read back into memory\n const hasher = crypto.createHash(\"sha256\");\n const hashTransform = new Transform({\n transform(chunk, _encoding, callback) {\n hasher.update(chunk);\n callback(null, chunk);\n },\n });\n\n const writeStream = createWriteStream(absolutePath);\n await pipeline(stream, hashTransform, writeStream);\n\n\n const hash = hasher.digest(\"hex\");\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Get file contents as Buffer\n */\n public async get(location: string): Promise<Buffer> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return readFile(absolutePath);\n }\n\n /**\n * Get file as a readable stream (for large files)\n */\n public async getStream(location: string): Promise<Readable> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return createReadStream(absolutePath);\n }\n\n /**\n * Delete a file\n */\n public async delete(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n return false;\n }\n\n await unlinkAsync(absolutePath);\n\n\n return true;\n }\n\n /**\n * Delete multiple files at once\n */\n public async deleteMany(locations: string[]): Promise<DeleteManyResult[]> {\n const results: DeleteManyResult[] = [];\n\n for (const location of locations) {\n try {\n const deleted = await this.delete(location);\n results.push({ location, deleted });\n } catch (error) {\n results.push({\n location,\n deleted: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n\n return results;\n }\n\n /**\n * Delete directory\n *\n * The directory path is resolved through {@link getAbsolutePath} so it is\n * contained within the storage root — the raw, caller-supplied argument is\n * never handed to the filesystem (preventing `../` traversal deletes).\n */\n public async deleteDirectory(directoryPath: string, options?: { allowRoot?: boolean }) {\n const absolutePath = this.getAbsolutePath(directoryPath);\n\n if (!options?.allowRoot && resolve(absolutePath) === this.root) {\n throw new StorageError(\n `Refusing to delete the storage root (\"${directoryPath}\"). Pass { allowRoot: true } to confirm.`,\n { context: { directoryPath } },\n );\n }\n\n await removeDirectoryAsync(absolutePath);\n\n return true;\n }\n\n /**\n * Check if file exists\n */\n public async exists(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n return Boolean(await fileExistsAsync(absolutePath));\n }\n\n // ============================================================\n // URL Operations\n // ============================================================\n\n /**\n * Get public URL for file\n */\n public url(location: string): string {\n return url(this.urlPrefix + \"/\" + ltrim(this.applyPrefix(location), \"/\"));\n }\n\n /**\n * Get a temporary signed URL that expires\n * Returns a clean URL with encoded token: {temporaryUrlPrefix}/{token}\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration (default: 3600)\n */\n public async temporaryUrl(location: string, expiresIn = 3600): Promise<string> {\n if (!this.signatureKey) {\n throw new Error(\n \"Temporary URLs require a signatureKey in LocalDriver options. \" +\n \"Configure storage.drivers.local.signatureKey in your config.\",\n );\n }\n\n const token = this.encodeTemporaryToken(location, expiresIn);\n return `${this.temporaryUrlPrefix}/${token}`;\n }\n\n /**\n * Encode a temporary token containing path, expiry, and signature\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration\n */\n public encodeTemporaryToken(location: string, expiresIn: number): string {\n if (!this.signatureKey) {\n throw new Error(\"Temporary tokens require a signatureKey\");\n }\n\n const exp = Math.floor(Date.now() / 1000) + expiresIn;\n const sig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${location}:${exp}`)\n .digest(\"hex\");\n\n const payload: TemporaryTokenPayload = { path: location, exp, sig };\n const json = JSON.stringify(payload);\n\n // Use base64url encoding (URL-safe base64)\n return Buffer.from(json).toString(\"base64url\");\n }\n\n /**\n * Validate a temporary URL token\n * Returns a result object with validation status, file info, and convenience methods\n *\n * @param token - The token from the URL\n */\n public async validateTemporaryToken(token: string): Promise<TemporaryTokenValidation> {\n // Check signature key\n if (!this.signatureKey) {\n return { valid: false, error: \"missing_key\" };\n }\n\n // Decode token\n let payload: TemporaryTokenPayload;\n try {\n const json = Buffer.from(token, \"base64url\").toString(\"utf-8\");\n payload = JSON.parse(json);\n } catch {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Validate payload structure\n if (!payload.path || !payload.exp || !payload.sig) {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n return { valid: false, error: \"expired\" };\n }\n\n // Verify signature\n const expectedSig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${payload.path}:${payload.exp}`)\n .digest(\"hex\");\n\n const sigBuffer = Buffer.from(payload.sig, \"hex\");\n const expectedBuffer = Buffer.from(expectedSig, \"hex\");\n\n if (sigBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n const isValidSig = crypto.timingSafeEqual(\n new Uint8Array(sigBuffer),\n new Uint8Array(expectedBuffer),\n );\n\n if (!isValidSig) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n // Check file exists\n const absolutePath = this.getAbsolutePath(payload.path);\n if (!(await fileExistsAsync(absolutePath))) {\n return { valid: false, error: \"file_not_found\" };\n }\n\n // Build successful result with convenience methods\n const result: TemporaryTokenValidation = {\n valid: true,\n path: payload.path,\n absolutePath,\n expiresAt: new Date(payload.exp * 1000),\n mimeType: this.guessMimeType(payload.path),\n driver: this,\n getFile: () => this.get(payload.path),\n getStream: () => this.getStream(payload.path),\n };\n\n return result;\n }\n\n // ============================================================\n // Metadata Operations\n // ============================================================\n\n /**\n * Get file info/metadata without downloading\n */\n public async metadata(location: string): Promise<StorageFileInfo> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${absolutePath}`);\n }\n\n const stats = await stat(absolutePath);\n const name = location.split(\"/\").pop() || \"\";\n\n return {\n path: location,\n name,\n size: stats.size,\n isDirectory: stats.isDirectory(),\n lastModified: stats.mtime,\n mimeType: this.guessMimeType(location),\n };\n }\n\n /**\n * Get file size in bytes (shortcut for metadata().size)\n */\n public async size(location: string): Promise<number> {\n const metadata = await this.metadata(location);\n return metadata.size;\n }\n\n // ============================================================\n // File Operations\n // ============================================================\n\n /**\n * Copy file to a new location\n */\n public async copy(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await copyFile(fromPath, toPath);\n\n\n const hash = await this.hashFile(toPath);\n const stats = await stat(toPath);\n\n return {\n path: to,\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * Move file to a new location\n */\n public async move(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await rename(fromPath, toPath);\n\n\n const hash = await this.hashFile(toPath);\n const stats = await stat(toPath);\n\n return {\n path: to,\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * List files in a directory\n */\n public async list(directory: string, options?: ListOptions): Promise<StorageFileInfo[]> {\n const absolutePath = this.getAbsolutePath(directory);\n const files: StorageFileInfo[] = [];\n\n // A listing target is a DIRECTORY, so guard with `directoryExistsAsync`.\n // `fileExistsAsync` resolves `false` for a folder, which would make `list`\n // always return [] for a real, populated directory.\n if (!(await directoryExistsAsync(absolutePath))) {\n return files;\n }\n\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n const entryPath = join(directory, entry.name);\n const entryStats = await stat(this.getAbsolutePath(entryPath));\n\n files.push({\n path: entryPath,\n name: entry.name,\n size: entryStats.size,\n isDirectory: entry.isDirectory(),\n lastModified: entryStats.mtime,\n mimeType: entry.isFile() ? this.guessMimeType(entry.name) : undefined,\n });\n\n // Recursively list subdirectories if requested\n if (options?.recursive && entry.isDirectory()) {\n const subFiles = await this.list(entryPath, options);\n files.push(...subFiles);\n }\n\n // Respect limit\n if (options?.limit && files.length >= options.limit) {\n break;\n }\n }\n\n return files;\n }\n\n // ============================================================\n // Path Operations\n // ============================================================\n\n /**\n * Get absolute filesystem path for a location\n */\n public path(location: string): string {\n return this.getAbsolutePath(location);\n }\n\n /**\n * Get the storage root directory\n */\n public getRoot(): string {\n return this.root;\n }\n\n // ============================================================\n // Utilities\n // ============================================================\n\n /**\n * Get absolute file path\n *\n * Routes through {@link resolveWithinRoot} so a location can never escape\n * the configured storage root via `..` traversal, an absolute path, or a\n * prefix breakout.\n */\n protected getAbsolutePath(location: string): string {\n const prefixedLocation = this.applyPrefix(location);\n const resolved = resolveWithinRoot(this.root, prefixedLocation);\n\n // The prefix is a containment boundary too: \"../t2/x\" under prefix \"t1\"\n // must not land in a sibling tenant's directory.\n const prefix = storageDriverContext.getPrefix() || this.options.prefix;\n\n if (prefix) {\n const prefixRoot = resolveWithinRoot(this.root, prefix.replace(/^[\\\\/]+|[\\\\/]+$/g, \"\"));\n resolveWithinRoot(prefixRoot, resolved);\n }\n\n return resolved;\n }\n\n /**\n * Convert various input types to Buffer\n */\n protected async toBuffer(file: Buffer | string | UploadedFile): Promise<Buffer> {\n if (Buffer.isBuffer(file)) {\n return file;\n }\n\n // A string is always content, never a source path\n if (typeof file === \"string\") {\n return Buffer.from(file);\n }\n\n return file.buffer();\n }\n\n /**\n * Calculate SHA-256 hash\n */\n protected calculateHash(buffer: Buffer): string {\n return crypto.createHash(\"sha256\").update(new Uint8Array(buffer)).digest(\"hex\");\n }\n\n /**\n * Calculate SHA-256 hash of a file by streaming it (constant memory)\n */\n protected async hashFile(absolutePath: string): Promise<string> {\n const hasher = crypto.createHash(\"sha256\");\n\n for await (const chunk of createReadStream(absolutePath)) {\n hasher.update(chunk);\n }\n\n return hasher.digest(\"hex\");\n }\n\n /**\n * Guess MIME type from file extension\n */\n protected guessMimeType(location: string): string {\n return getMimeType(location);\n }\n}\n"],"mappings":"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"}
|
package/esm/storage/index.d.mts
CHANGED
|
@@ -6,8 +6,10 @@ import { Storage, storage } from "./storage.mjs";
|
|
|
6
6
|
import { StorageDriverContextStore, storageDriverContext } from "./context/storage-driver-context.mjs";
|
|
7
7
|
import { resolveWithinRoot } from "./utils/contain-path.mjs";
|
|
8
8
|
import { MimeTypes, getMimeType } from "./utils/mime.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { isPrivateOrReservedIp } from "./utils/classify-ip-address.mjs";
|
|
10
|
+
import { SafeFetchOptions, SafeFetchResult, safeFetchToBuffer } from "./utils/safe-fetch.mjs";
|
|
10
11
|
import { StorageError, StorageErrorOptions } from "./utils/storage-error.mjs";
|
|
12
|
+
import { StorageCapabilityError } from "./utils/storage-capability-error.mjs";
|
|
11
13
|
import { StorageNotInitializedError } from "./utils/storage-not-initialized-error.mjs";
|
|
12
14
|
import { CloudDriver, loadS3 } from "./drivers/cloud-driver.mjs";
|
|
13
15
|
import { DOSpacesDriver } from "./drivers/do-spaces-driver.mjs";
|
package/esm/storage/index.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { storageConfig, storageConfigurations } from "./config.mjs";
|
|
2
2
|
import { StorageFile } from "./storage-file.mjs";
|
|
3
|
-
import { StorageNotInitializedError } from "./utils/storage-not-initialized-error.mjs";
|
|
4
3
|
import { StorageError } from "./utils/storage-error.mjs";
|
|
5
|
-
import {
|
|
4
|
+
import { StorageCapabilityError } from "./utils/storage-capability-error.mjs";
|
|
5
|
+
import { StorageNotInitializedError } from "./utils/storage-not-initialized-error.mjs";
|
|
6
|
+
import { isPrivateOrReservedIp } from "./utils/classify-ip-address.mjs";
|
|
7
|
+
import { safeFetchToBuffer } from "./utils/safe-fetch.mjs";
|
|
6
8
|
import { ScopedStorage } from "./scoped-storage.mjs";
|
|
7
9
|
import { storageDriverContext } from "./context/storage-driver-context.mjs";
|
|
8
10
|
import { MimeTypes, getMimeType } from "./utils/mime.mjs";
|
|
@@ -67,7 +67,8 @@ declare class ScopedStorage implements ScopedStorageContract {
|
|
|
67
67
|
* Accepts multiple input types and stores the file at the specified location.
|
|
68
68
|
* Returns a `StorageFile` instance for further operations.
|
|
69
69
|
*
|
|
70
|
-
* @param file - File content as Buffer, string
|
|
70
|
+
* @param file - File content as Buffer, string content, UploadedFile, or Readable stream.
|
|
71
|
+
* A string is ALWAYS stored as content; use `putFromPath()` to upload a local file
|
|
71
72
|
* @param location - Destination path in storage (e.g., "uploads/images/photo.jpg")
|
|
72
73
|
* @param options - Optional storage options
|
|
73
74
|
* @returns StorageFile instance with cached metadata
|
|
@@ -88,6 +89,31 @@ declare class ScopedStorage implements ScopedStorageContract {
|
|
|
88
89
|
* ```
|
|
89
90
|
*/
|
|
90
91
|
put(file: UploadedFile | Buffer | string | Readable, location: string, options?: PutOptions): Promise<StorageFile>;
|
|
92
|
+
/**
|
|
93
|
+
* Store a file read from a local filesystem path.
|
|
94
|
+
*
|
|
95
|
+
* Explicit counterpart to `put()`, where a string is always content.
|
|
96
|
+
* Only pass trusted paths, never user input.
|
|
97
|
+
*
|
|
98
|
+
* @param localPath - Path of the local file to upload
|
|
99
|
+
* @param location - Destination path in storage
|
|
100
|
+
* @param options - Optional storage options
|
|
101
|
+
*/
|
|
102
|
+
putFromPath(localPath: string, location: string, options?: PutOptions): Promise<StorageFile>;
|
|
103
|
+
/**
|
|
104
|
+
* Atomically store a file only if nothing exists at `location`.
|
|
105
|
+
*
|
|
106
|
+
* Of N concurrent callers for one location exactly one gets a `StorageFile`;
|
|
107
|
+
* the rest get `null` and write nothing. `null` means only "already exists" -
|
|
108
|
+
* every other failure throws, as `put()` does.
|
|
109
|
+
*
|
|
110
|
+
* @throws StorageCapabilityError when the driver has no `putIfAbsent`
|
|
111
|
+
*/
|
|
112
|
+
putIfAbsent(file: Buffer | string, location: string, options?: PutOptions): Promise<StorageFile | null>;
|
|
113
|
+
/**
|
|
114
|
+
* Whether the active driver implements `putIfAbsent()`
|
|
115
|
+
*/
|
|
116
|
+
supportsPutIfAbsent(): boolean;
|
|
91
117
|
/**
|
|
92
118
|
* Store a file from a readable stream
|
|
93
119
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scoped-storage.d.mts","names":[],"sources":["../../../../../../../core/src/storage/scoped-storage.ts"],"mappings":";;;;;;;;;AA2CA;;;;;;;;;;;;;;;;;;cAAa,aAAA,YAAyB,qBAAA;
|
|
1
|
+
{"version":3,"file":"scoped-storage.d.mts","names":[],"sources":["../../../../../../../core/src/storage/scoped-storage.ts"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { StorageFile } from "./storage-file.mjs";
|
|
2
|
+
import { StorageCapabilityError } from "./utils/storage-capability-error.mjs";
|
|
2
3
|
import { StorageNotInitializedError } from "./utils/storage-not-initialized-error.mjs";
|
|
3
4
|
import { safeFetchToBuffer } from "./utils/safe-fetch.mjs";
|
|
4
5
|
import path from "path";
|
|
5
|
-
import { fileExistsAsync } from "@warlock.js/fs";
|
|
6
6
|
import { createReadStream } from "fs";
|
|
7
7
|
import fs$1 from "fs/promises";
|
|
8
8
|
|
|
@@ -74,7 +74,8 @@ var ScopedStorage = class {
|
|
|
74
74
|
* Accepts multiple input types and stores the file at the specified location.
|
|
75
75
|
* Returns a `StorageFile` instance for further operations.
|
|
76
76
|
*
|
|
77
|
-
* @param file - File content as Buffer, string
|
|
77
|
+
* @param file - File content as Buffer, string content, UploadedFile, or Readable stream.
|
|
78
|
+
* A string is ALWAYS stored as content; use `putFromPath()` to upload a local file
|
|
78
79
|
* @param location - Destination path in storage (e.g., "uploads/images/photo.jpg")
|
|
79
80
|
* @param options - Optional storage options
|
|
80
81
|
* @returns StorageFile instance with cached metadata
|
|
@@ -100,6 +101,40 @@ var ScopedStorage = class {
|
|
|
100
101
|
return StorageFile.fromData(data, this.activeDriver);
|
|
101
102
|
}
|
|
102
103
|
/**
|
|
104
|
+
* Store a file read from a local filesystem path.
|
|
105
|
+
*
|
|
106
|
+
* Explicit counterpart to `put()`, where a string is always content.
|
|
107
|
+
* Only pass trusted paths, never user input.
|
|
108
|
+
*
|
|
109
|
+
* @param localPath - Path of the local file to upload
|
|
110
|
+
* @param location - Destination path in storage
|
|
111
|
+
* @param options - Optional storage options
|
|
112
|
+
*/
|
|
113
|
+
async putFromPath(localPath, location, options) {
|
|
114
|
+
return this.put(await fs$1.readFile(localPath), location, options);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Atomically store a file only if nothing exists at `location`.
|
|
118
|
+
*
|
|
119
|
+
* Of N concurrent callers for one location exactly one gets a `StorageFile`;
|
|
120
|
+
* the rest get `null` and write nothing. `null` means only "already exists" -
|
|
121
|
+
* every other failure throws, as `put()` does.
|
|
122
|
+
*
|
|
123
|
+
* @throws StorageCapabilityError when the driver has no `putIfAbsent`
|
|
124
|
+
*/
|
|
125
|
+
async putIfAbsent(file, location, options) {
|
|
126
|
+
const driver = this.activeDriver;
|
|
127
|
+
if (typeof driver.putIfAbsent !== "function") throw new StorageCapabilityError("putIfAbsent", driver.name);
|
|
128
|
+
const data = await driver.putIfAbsent(file, location, options);
|
|
129
|
+
return data ? StorageFile.fromData(data, driver) : null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Whether the active driver implements `putIfAbsent()`
|
|
133
|
+
*/
|
|
134
|
+
supportsPutIfAbsent() {
|
|
135
|
+
return typeof this.activeDriver.putIfAbsent === "function";
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
103
138
|
* Store a file from a readable stream
|
|
104
139
|
*
|
|
105
140
|
* Optimized for large files - streams data directly without full buffering.
|
|
@@ -153,8 +188,8 @@ var ScopedStorage = class {
|
|
|
153
188
|
allowedSchemes
|
|
154
189
|
});
|
|
155
190
|
if (!result.ok) throw new Error(`Failed to fetch file from ${url}: ${result.statusText}`);
|
|
156
|
-
if (!result.contentType) throw new Error(`Failed to fetch file from ${url}: missing content-type header`);
|
|
157
|
-
const mimeType = putOptions.mimeType || result.contentType;
|
|
191
|
+
if (!result.contentType && !putOptions.mimeType) throw new Error(`Failed to fetch file from ${url}: missing content-type header`);
|
|
192
|
+
const mimeType = putOptions.mimeType || result.contentType || void 0;
|
|
158
193
|
return this.put(result.buffer, location, {
|
|
159
194
|
...putOptions,
|
|
160
195
|
mimeType
|
|
@@ -639,10 +674,7 @@ var ScopedStorage = class {
|
|
|
639
674
|
async toBuffer(file) {
|
|
640
675
|
if (Buffer.isBuffer(file)) return file;
|
|
641
676
|
if (this.isReadable(file)) return this.streamToBuffer(file);
|
|
642
|
-
if (typeof file === "string")
|
|
643
|
-
if (await fileExistsAsync(file)) return fs$1.readFile(file);
|
|
644
|
-
return Buffer.from(file);
|
|
645
|
-
}
|
|
677
|
+
if (typeof file === "string") return Buffer.from(file);
|
|
646
678
|
return file.buffer();
|
|
647
679
|
}
|
|
648
680
|
/**
|