{"_id":"@bymax-one/nest-storage","_rev":"8-e4b3c6a672cd6b18393da88494ad03c9","name":"@bymax-one/nest-storage","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@bymax-one/nest-storage","version":"1.0.0","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-storage@1.0.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-storage#readme","bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"dist":{"shasum":"7d5a3bde09a056cd639836072426f066890fcdf6","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.0.0.tgz","fileCount":12,"integrity":"sha512-CFfuoDfqst9d+Vycu8VDsOIiJpFuNSIIc0ZBg3z8Iqw/7QbgLUHcGgQ4032Dtgs+03PHIxa4VuMBbRhzwg/wBA==","signatures":[{"sig":"MEUCIQDrw55jCOluNY9nlXlzXVn0iEir8IbVTFLvVDaImVZ9WAIgFYpJjpydqrM0zuhpwfGNmyUklfBUDN6jZuPin9LZAtQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":288512},"main":"./dist/server/index.cjs","type":"module","_from":"file:bymax-one-nest-storage-1.0.0.tgz","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"bymax.one","email":"bymaxone.core@gmail.com"},"_resolved":"/private/var/folders/zf/_zr7t0ms60x6rcwj5zxk90cr0000gn/T/862e7e92abb6dd658afe19a4f1c3ee7e/bymax-one-nest-storage-1.0.0.tgz","_integrity":"sha512-CFfuoDfqst9d+Vycu8VDsOIiJpFuNSIIc0ZBg3z8Iqw/7QbgLUHcGgQ4032Dtgs+03PHIxa4VuMBbRhzwg/wBA==","repository":{"url":"git+https://github.com/bymaxone/nest-storage.git","type":"git"},"_npmVersion":"11.13.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.4.2","tsup":"^8.5.1","eslint":"^9.39.5","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@arethetypeswrong/cli":"^0.18.2","@stryker-mutator/core":"^9","@testcontainers/minio":"^12","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@stryker-mutator/jest-runner":"^9","@aws-sdk/s3-request-presigner":"^3.1099.0","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0"},"peerDependenciesMeta":{"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"reflect-metadata":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/nest-storage_1.0.0_1785790152019_0.004666874787536468","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@bymax-one/nest-storage","version":"1.0.1","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-storage@1.0.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-storage#readme","bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"dist":{"shasum":"2093eef1773af461083f30a8630b858cb6378ccd","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.0.1.tgz","fileCount":12,"integrity":"sha512-zGX8CPUdPUVXQyd6uEtDV7AB2IpjdxqnmB6euNZSF9lRVz5X08xrXZJ/4P74/eAyHiBLfoe88Pi4O8hlMls7TQ==","signatures":[{"sig":"MEUCIH4ad6RIKRlWm3KP2kf2LGt6jRtZTVed7crWhFagW9IPAiEAjY4EdL7t1Dz+laKpN4lyqr704ChP8AI3mT8PF3UfsVo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-storage@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":308506},"main":"./dist/server/index.cjs","pnpm":{"overrides":{"qs":"6.15.2","uuid":"11.1.1","multer":"2.2.0","undici":"^8.9.0","esbuild":"0.28.1","fast-uri":"^3.1.4","protobufjs":"^7.6.5","brace-expansion@1":"^1.1.17","brace-expansion@2":"^2.1.3","brace-expansion@5":"^5.0.8"}},"type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"91657aae5051693bc6ae5ab4a3be99717f2be99a","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:253b8046-3992-4295-83a7-024037e44fe9"}},"repository":{"url":"git+https://github.com/bymaxone/nest-storage.git","type":"git"},"_npmVersion":"11.16.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.8.1","devDependencies":{"jest":"^30.4.2","tsup":"^8.5.1","eslint":"^9.39.5","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@arethetypeswrong/cli":"^0.18.2","@stryker-mutator/core":"^9","@testcontainers/minio":"^12","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@stryker-mutator/jest-runner":"^9","@aws-sdk/s3-request-presigner":"^3.1099.0","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0"},"peerDependenciesMeta":{"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"reflect-metadata":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/nest-storage_1.0.1_1785800215327_0.7931483878182448","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@bymax-one/nest-storage","version":"1.0.2","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-storage@1.0.2","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-storage#readme","bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"dist":{"shasum":"76d07fdc8e03afc733be85f07febefaeee4edcc4","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.0.2.tgz","fileCount":12,"integrity":"sha512-TlOjH4l5IL9YJi7mdlzL1Rxyf/PF9wWyC0Mjeie0f2zxsJ8a+5l4IS80EQekLjyb8SHo9wB5C8p8s50/+fRcfw==","signatures":[{"sig":"MEUCICd5wQAIgOD0v0YRiLOvNQT4bq96t+NWNSEyPdUEWCw2AiEA/bt5najfTEwfQqBV/HXfcUvK0itRoOnI7MiBwQ0bi0E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-storage@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":319275},"main":"./dist/server/index.cjs","pnpm":{"overrides":{"qs":"6.15.2","uuid":"11.1.1","multer":"2.2.0","undici":"^8.9.0","esbuild":"0.28.1","fast-uri":"^3.1.4","protobufjs":"^7.6.5","brace-expansion@1":"^1.1.17","brace-expansion@2":"^2.1.3","brace-expansion@5":"^5.0.8"}},"type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"1bd0761038b5312aaa926cf829ed3e211c72e2f5","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:253b8046-3992-4295-83a7-024037e44fe9"}},"repository":{"url":"git+https://github.com/bymaxone/nest-storage.git","type":"git"},"_npmVersion":"11.16.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.8.1","devDependencies":{"jest":"^30.4.2","tsup":"^8.5.1","eslint":"^9.39.5","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@arethetypeswrong/cli":"^0.18.2","@stryker-mutator/core":"^9","@testcontainers/minio":"^12","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@stryker-mutator/jest-runner":"^9","@aws-sdk/s3-request-presigner":"^3.1099.0","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0"},"peerDependenciesMeta":{"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"reflect-metadata":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/nest-storage_1.0.2_1785835847181_0.41672319981435524","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@bymax-one/nest-storage","version":"1.0.3","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-storage@1.0.3","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-storage#readme","bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"dist":{"shasum":"3c915cbc0f9006b119efd3beb97e883aef20f7a2","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.0.3.tgz","fileCount":12,"integrity":"sha512-wpGv2s9XRmlWxIgHUJVBJiq2QmthPm2c0ejyqFAX0nf3L8E36Z79mZnzvW9gztkQYWR1Rq24wT7LxpKUtWcm+w==","signatures":[{"sig":"MEYCIQCHPa8zE0e5MaRLQMXhFTczWbQ4YtLD8SfSv6S9A0VSsgIhAPHSHGGMzj/sYTDIrtb/K1UJZueb0cYQLzLZHUTMOint","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-storage@1.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":321128},"main":"./dist/server/index.cjs","pnpm":{"overrides":{"qs":"6.15.2","uuid":"11.1.1","multer":"2.2.0","undici":"^8.9.0","esbuild":"0.28.1","fast-uri":"^3.1.4","protobufjs":"^7.6.5","brace-expansion@1":"^1.1.17","brace-expansion@2":"^2.1.3","brace-expansion@5":"^5.0.8"}},"type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"b58265dfdc5bc734c5443265c3e420310d896d9f","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:253b8046-3992-4295-83a7-024037e44fe9"}},"repository":{"url":"git+https://github.com/bymaxone/nest-storage.git","type":"git"},"_npmVersion":"11.16.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.8.1","devDependencies":{"jest":"^30.4.2","tsup":"^8.5.1","eslint":"^9.39.5","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@arethetypeswrong/cli":"^0.18.2","@stryker-mutator/core":"^9","@testcontainers/minio":"^12","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@stryker-mutator/jest-runner":"^9","@aws-sdk/s3-request-presigner":"^3.1099.0","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0"},"peerDependenciesMeta":{"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"reflect-metadata":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/nest-storage_1.0.3_1785837536485_0.555302023111115","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@bymax-one/nest-storage","version":"1.0.4","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-storage@1.0.4","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-storage#readme","bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"dist":{"shasum":"9e2512042808b771b6447037e78de9bb4b68dba6","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.0.4.tgz","fileCount":12,"integrity":"sha512-uL9YcCIV1EM2nzXccrxfPAAOylYcgWqdpVLgjJzMDwrYEqseXCXYcDEVtK82RlHtwXtM2XcSZIDvIpjrieaEwQ==","signatures":[{"sig":"MEQCIHSusUQAyueIF2or47Uwvx6eNK5lQteJMgWA/978knKBAiBy6ABQY9se8F4SSz6G9On8ed3jnldeB1r1kHY1WtmYSA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-storage@1.0.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":323186},"main":"./dist/server/index.cjs","pnpm":{"overrides":{"qs":"6.15.2","uuid":"11.1.1","multer":"2.2.0","undici":"^8.9.0","esbuild":"0.28.1","fast-uri":"^3.1.4","protobufjs":"^7.6.5","brace-expansion@1":"^1.1.17","brace-expansion@2":"^2.1.3","brace-expansion@5":"^5.0.8"}},"type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"c0936e0b9b5953aeeb9a032dd010fad04ae006d0","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:253b8046-3992-4295-83a7-024037e44fe9"}},"repository":{"url":"git+https://github.com/bymaxone/nest-storage.git","type":"git"},"_npmVersion":"11.16.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.8.1","devDependencies":{"jest":"^30.4.2","tsup":"^8.5.1","eslint":"^9.39.5","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@arethetypeswrong/cli":"^0.18.2","@stryker-mutator/core":"^9","@testcontainers/minio":"^12","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@stryker-mutator/jest-runner":"^9","@aws-sdk/s3-request-presigner":"^3.1099.0","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0"},"peerDependenciesMeta":{"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"reflect-metadata":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/nest-storage_1.0.4_1785858206790_0.4647789117981198","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@bymax-one/nest-storage","version":"1.0.5","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-storage@1.0.5","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-storage#readme","bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"dist":{"shasum":"0f75a7c126a7eba0433cb781ffff2244e51975ba","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.0.5.tgz","fileCount":12,"integrity":"sha512-Tdb2Zzyh9p4EZAShqkXGe9KKmhy3lx5ZVvVUgH4n8bawGLOIJ3+SDFbasnz8v/Fxtmcra7V70MNafvd/3071zw==","signatures":[{"sig":"MEQCIDJUEu+JX2x7lErnHSOHeV8mnDCi7hT76/PVw0OQF7DiAiAXJdjBg2UCXeSGPkUo3Wk5QMiz8Xz+GCqaRCO1X0cFdA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-storage@1.0.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":323683},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"ad7b81cf9406f5caed45b9af1a39aa2d932681ff","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:253b8046-3992-4295-83a7-024037e44fe9"}},"repository":{"url":"git+https://github.com/bymaxone/nest-storage.git","type":"git"},"_npmVersion":"11.16.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","tsup":"^8.5.1","eslint":"^9.39.5","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@arethetypeswrong/cli":"^0.18.2","@stryker-mutator/core":"^9","@testcontainers/minio":"^12","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@stryker-mutator/jest-runner":"^9","@aws-sdk/s3-request-presigner":"^3.1099.0","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0"},"peerDependenciesMeta":{"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"reflect-metadata":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/nest-storage_1.0.5_1786015796591_0.6331253229025371","host":"s3://npm-registry-packages-npm-production"}},"1.0.6":{"name":"@bymax-one/nest-storage","version":"1.0.6","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-storage@1.0.6","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-storage#readme","bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"dist":{"shasum":"16b259ce6b53372c2d4fa1527dd9fbd1ab5d82ad","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.0.6.tgz","fileCount":12,"integrity":"sha512-bggsbfMKYZhsVRl3axjhaa6k8F2EE1xFCYdv7PGyKXLOcKkTTFiRX0CipvC+e7I/RP8BTKvQmqvNh+570UeBaQ==","signatures":[{"sig":"MEUCIQDsdf/LeTMC7v6fyKS+1jYRz+OdgUKETe0I+fzZi7S3AQIgftyUeojMGYPbA/buQEZoZYvJ+/CBHmZPr2GvRwiw4MM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-storage@1.0.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":325737},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"bbee61dd0ac85d30a33f70860ac67027d10f3374","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:253b8046-3992-4295-83a7-024037e44fe9"}},"repository":{"url":"git+https://github.com/bymaxone/nest-storage.git","type":"git"},"_npmVersion":"11.16.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","tsup":"^8.5.1","eslint":"^9.39.5","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@arethetypeswrong/cli":"^0.18.2","@stryker-mutator/core":"^9","@testcontainers/minio":"^12","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@stryker-mutator/jest-runner":"^9","@aws-sdk/s3-request-presigner":"^3.1099.0","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0"},"peerDependenciesMeta":{"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"reflect-metadata":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/nest-storage_1.0.6_1786194019845_0.3854136964575574","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@bymax-one/nest-storage","version":"1.1.0","description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","homepage":"https://github.com/bymaxone/nest-storage#readme","repository":{"type":"git","url":"git+https://github.com/bymaxone/nest-storage.git"},"bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"type":"module","sideEffects":false,"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"scripts":{"build":"pnpm clean && tsup","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:published":"node scripts/check-published-surface.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","clean":"rm -rf dist coverage","lint":"eslint src","lint:fix":"eslint src --fix","mutation":"stryker run","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","mutation:dry-run":"stryker run --dryRunOnly","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","release":"npm publish --provenance --access public","size":"node scripts/check-size.mjs","test":"jest","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:cov:all":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","test:watch":"jest --watch","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json"},"dependencies":{},"peerDependencies":{"@nestjs/common":"^11.0.16","@nestjs/core":"^11.1.18","@aws-sdk/client-s3":"^3.700.0","@aws-sdk/lib-storage":"^3.700.0","@aws-sdk/s3-request-presigner":"^3.700.0","reflect-metadata":"^0.2.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":false},"@nestjs/core":{"optional":false},"@aws-sdk/client-s3":{"optional":false},"@aws-sdk/lib-storage":{"optional":false},"@aws-sdk/s3-request-presigner":{"optional":false},"reflect-metadata":{"optional":false}},"devDependencies":{"@arethetypeswrong/cli":"^0.18.2","@aws-sdk/client-s3":"^3.1099.0","@aws-sdk/lib-storage":"^3.1099.0","@aws-sdk/s3-request-presigner":"^3.1099.0","@eslint/js":"^9.39.4","@nestjs/common":"^11.1.28","@nestjs/core":"^11.1.28","@nestjs/platform-express":"^11.1.28","@nestjs/testing":"^11.1.28","@stryker-mutator/core":"^9","@stryker-mutator/jest-runner":"^9","@stryker-mutator/typescript-checker":"^9","@testcontainers/minio":"^12","@types/express":"^5.0.6","@types/jest":"^30.0.0","@types/node":"^24.13.3","@types/supertest":"^7.2.1","eslint":"^9.39.5","eslint-config-prettier":"^10.1.8","jest":"^30.4.2","prettier":"^3.9.6","reflect-metadata":"^0.2.2","supertest":"^7.2.2","testcontainers":"^12","ts-jest":"^29.4.12","ts-node":"^10.9.2","tsup":"^8.5.1","typescript":"^5.9.3","typescript-eslint":"^8.65.0"},"keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"packageManager":"pnpm@11.20.0","engines":{"node":">=24.0.0"},"publishConfig":{"access":"public","provenance":true,"registry":"https://registry.npmjs.org/"},"main":"./dist/server/index.cjs","module":"./dist/server/index.mjs","types":"./dist/server/index.d.cts","typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"]}},"gitHead":"2c65105cf71c1f61997a5b21ff691202bc4909bf","_id":"@bymax-one/nest-storage@1.1.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-2hDRqbu66TDz8yV4mZizrvCie2mK+0/C1/a8JtNmMEb99Vpo+UU9jibnVVZKTALRkZK+flOCVouhHE841QiVdA==","shasum":"efbefb599485f6424ec7e4a98c3da330b6ef2836","tarball":"https://registry.npmjs.org/@bymax-one/nest-storage/-/nest-storage-1.1.0.tgz","fileCount":12,"unpackedSize":326760,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-storage@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC2RfXdafZ/wgMK4E1vXI00Xmb2tiqf/RFoo9wvdEQdCgIhAJL1WmNnIghTkktXxDIVTMKOcCtjey7GL2sx7VcojcVp"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:253b8046-3992-4295-83a7-024037e44fe9"}},"directories":{},"maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nest-storage_1.1.0_1786446223658_0.5479111582574965"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-03T20:49:11.851Z","modified":"2026-08-11T11:03:44.123Z","1.0.0":"2026-08-03T20:49:12.164Z","1.0.1":"2026-08-03T23:36:55.445Z","1.0.2":"2026-08-04T09:30:47.417Z","1.0.3":"2026-08-04T09:58:56.657Z","1.0.4":"2026-08-04T15:43:26.930Z","1.0.5":"2026-08-06T11:29:56.726Z","1.0.6":"2026-08-08T13:00:19.995Z","1.1.0":"2026-08-11T11:03:43.796Z"},"bugs":{"url":"https://github.com/bymaxone/nest-storage/issues"},"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","homepage":"https://github.com/bymaxone/nest-storage#readme","keywords":["nestjs","storage","s3","aws-s3","cloudflare-r2","backblaze-b2","digitalocean-spaces","minio","wasabi","object-storage","presigned-url","multipart-upload"],"repository":{"type":"git","url":"git+https://github.com/bymaxone/nest-storage.git"},"description":"Provider-agnostic S3-compatible object storage for NestJS. Works with AWS S3, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, MinIO, Wasabi.","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"readme":"<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/%40bymax--one-nest--storage-000000?style=for-the-badge&logo=nestjs&logoColor=E0234E\" alt=\"@bymax-one/nest-storage\" />\n</p>\n\n<h1 align=\"center\">@bymax-one/nest-storage</h1>\n\n<p align=\"center\">\n  <strong>Provider-agnostic S3-compatible object storage for NestJS</strong><br />\n  <sub>AWS S3 · Cloudflare R2 · Backblaze B2 · DigitalOcean Spaces · MinIO · Wasabi · Presigned URLs · Multipart · Virus Scanning · Zero Runtime Dependencies</sub>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-storage\"><img src=\"https://img.shields.io/npm/v/@bymax-one/nest-storage?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm version\" /></a>\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-storage\"><img src=\"https://img.shields.io/npm/dm/@bymax-one/nest-storage?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm downloads\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-storage/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/bymaxone/nest-storage/ci.yml?branch=main&style=flat-square&colorA=000000&label=CI\" alt=\"CI status\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-storage/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square&colorA=000000\" alt=\"coverage\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-storage/blob/main/docs/mutation_testing_results.md\"><img src=\"https://img.shields.io/badge/mutation-100%25-brightgreen?style=flat-square&colorA=000000\" alt=\"mutation score\" /></a>\n  <a href=\"https://scorecard.dev/viewer/?uri=github.com/bymaxone/nest-storage\"><img src=\"https://api.scorecard.dev/projects/github.com/bymaxone/nest-storage/badge?style=flat-square\" alt=\"OpenSSF Scorecard\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-storage/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/bymaxone/nest-storage?style=flat-square&colorA=000000&colorB=000000\" alt=\"license\" /></a>\n  <a href=\"https://www.typescriptlang.org/\"><img src=\"https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white\" alt=\"TypeScript\" /></a>\n  <a href=\"https://nodejs.org/\"><img src=\"https://img.shields.io/badge/Node.js-24%2B-339933?style=flat-square&logo=node.js&logoColor=white\" alt=\"Node.js\" /></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/bymaxone/nest-storage\">GitHub</a> ·\n  <a href=\"https://github.com/bymaxone/nest-storage/issues\">Issues</a> ·\n  <a href=\"#-quick-start\">Quick Start</a> ·\n  <a href=\"#-api-reference\">API Reference</a> ·\n  <a href=\"https://github.com/bymaxone/nest-storage-example\">Example App</a>\n</p>\n\n---\n\n## ✨ Overview\n\n`@bymax-one/nest-storage` is a NestJS dynamic module that wraps the AWS SDK v3\n(`@aws-sdk/client-s3`) behind one typed surface for object storage. Instead of building an\n`S3Client`, deciding per provider whether path-style addressing is required, and hand-rolling\nmultipart, presigning, validation and key normalization in every service, you install one\nlibrary and get all of it — against AWS S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces,\nMinIO or Wasabi, with the same code.\n\nThe library has **zero direct dependencies** — the three `@aws-sdk/*` packages and `@nestjs/*`\narrive as peer dependencies, so you control exact versions and the supply-chain surface stays\nminimal.\n\n### Why nest-storage?\n\n- **One engine, six providers.** `providerRecipes` differ only in endpoint, region and the flags\n  each provider needs (`forcePathStyle` for MinIO, `auto` region for R2). There is no\n  per-provider code path, so switching provider is a configuration change and every operation\n  behaves the same way afterwards.\n- **The key guard is one chokepoint.** Every caller-supplied key passes through\n  `KeyResolverService` before it reaches S3 — `..` segments, a leading `/`, null bytes and empty\n  keys are refused, and only then is `keyPrefix` prepended. A tenant cannot climb out of its\n  prefix by naming a key.\n- **Uploads are refused before they are stored.** MIME allowlist and size limit first, then any\n  `IUploadValidator` you register, then the `IFileScanner` if configured — pre-upload,\n  post-upload, or both.\n- **Presigned URLs cannot outlive what SigV4 allows.** TTLs are clamped and the hard ceiling of\n  604 800 s is enforced locally, rather than handed to a signature the provider rejects at use\n  time.\n\n---\n\n## 🔥 Features\n\n### ⬆️ Uploading\n\n- ✅ **Multipart upload** — `@aws-sdk/lib-storage` with automatic abort on failure\n  (`leavePartsOnError: false`), progress events, and a configurable threshold and part size\n- ✅ **Content validation** — MIME allowlist with wildcards (`image/*`, `text/*`) and a size\n  limit, refused before a byte is stored\n- ✅ **Pluggable validators** — register any number of `IUploadValidator`s; each runs after the\n  built-in checks and can refuse with its own reason\n- ✅ **Virus scanning hook** — `IFileScanner` pre-upload, post-upload or both, for ClamAV, AWS\n  Macie or anything that answers `'clean' | 'infected' | 'unknown'`\n- ✅ **Idempotent retries** — an in-memory LRU (1 000 entries / 24 h by default) returns the\n  cached `UploadResult` for a repeated `idempotencyKey` instead of storing the object twice\n\n### 🔗 Access & URLs\n\n- ✅ **Presigned GET / PUT** — with optional content-type and length conditions on the PUT\n- ✅ **Presigned multipart** — one signed URL per part plus the upload id, so a browser can\n  upload directly without the bytes passing through your process\n- ✅ **TTL clamping** — SigV4's hard ceiling of 604 800 s (7 days) is enforced locally, not\n  discovered when the provider rejects the signature\n- ✅ **Public URL composition** — `getPublicUrl` builds from `cdnBaseUrl` or `publicBaseUrl` for\n  buckets that are public by policy; it composes, it does not sign\n\n### 🗄️ Objects & Lifecycle\n\n- ✅ **Streaming download** — `download` returns a stream; the body is never buffered unless you\n  ask for `downloadBuffer`\n- ✅ **Metadata without transfer** — `head` and `exists` answer from object metadata alone\n- ✅ **Paginated listing** — `list` over continuation tokens, with the prefix stripped back off\n  the keys it returns\n- ✅ **Server-side copy** — `copy` moves bytes inside the provider; they never reach this process\n- ✅ **Bulk delete with partial results** — `deleteMany` reports what failed instead of throwing\n  away the successes\n\n### 🧩 Developer Experience\n\n- ✅ **Six provider recipes** — `awsS3`, `cloudflareR2`, `backblazeB2`, `digitalOceanSpaces`,\n  `minio`, `wasabi`\n- ✅ **Zero runtime dependencies** — `@aws-sdk/*` and `@nestjs/*` all arrive as peer\n  dependencies, so you pin the versions\n- ✅ **Two subpaths** — the server surface, and `./shared` carrying types and the error catalog\n  that pull in nothing at all\n- ✅ **Escape hatch** — `BYMAX_STORAGE_S3_CLIENT` injects the raw `S3Client` for the\n  provider-specific operations this surface deliberately does not wrap\n- ✅ **Multi-tenant key prefix** — `keyPrefix` prepended after normalization, so it cannot be\n  escaped by the key\n- ✅ **Server-side encryption** — AES256 or `aws:kms`, globally or per upload\n- ✅ **Typed end to end** — TypeScript `strict` with `exactOptionalPropertyTypes` and\n  `noUncheckedIndexedAccess`; zero `any`\n\n---\n\n## 📦 Subpath Exports\n\n| Subpath    | Contents                                                                                                                                                                           |\n| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `.`        | Server runtime: `BymaxStorageModule`, `StorageService`, `SignedUrlService`, `providerRecipes`, DI tokens, interfaces, `StorageException`, `NoOpUploadValidator`, `NoOpFileScanner` |\n| `./shared` | Framework-free types (`UploadResult`, `ObjectMetadata`, `ListedObject`, `SignedUrlResult`) + `STORAGE_ERROR_CODES` + `StorageErrorCode`                                            |\n\n```\n@bymax-one/nest-storage          (server — NestJS + @aws-sdk/client-s3)\n        │\n        └── re-exports ──▶ @bymax-one/nest-storage/shared   (zero dependencies)\n```\n\nBoth subpaths ship ESM **and** CommonJS with declarations for each format, so a\n`require()` consumer receives CommonJS declarations rather than ESM ones. The\n`pnpm check:exports` gate verifies this against the packed tarball.\n\n### Peer dependency matrix\n\n| Subpath      | Required peers                                                                                                                                                                  |\n| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `.` (server) | `@nestjs/common ^11.0.16`, `@nestjs/core ^11.1.18`, `@aws-sdk/client-s3 ^3.700`, `@aws-sdk/lib-storage ^3.700`, `@aws-sdk/s3-request-presigner ^3.700`, `reflect-metadata ^0.2` |\n| `./shared`   | None                                                                                                                                                                            |\n\n---\n\n> [!TIP]\n> A reference application lives in\n> [`bymaxone/nest-storage-example`](https://github.com/bymaxone/nest-storage-example).\n\n---\n\n## 🚀 Quick Start\n\n### 1 — AWS S3\n\n```typescript\nimport { Module } from '@nestjs/common'\nimport { BymaxStorageModule, providerRecipes } from '@bymax-one/nest-storage'\n\n@Module({\n  imports: [\n    BymaxStorageModule.forRoot({\n      ...providerRecipes.awsS3({\n        region: 'us-east-1',\n        bucket: 'my-bucket',\n        accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n      }),\n      // AWS keeps the SDK default checksum behaviour — no opt-out needed.\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n```typescript\nimport { Injectable } from '@nestjs/common'\nimport { StorageService } from '@bymax-one/nest-storage'\n\n@Injectable()\nexport class AssetService {\n  constructor(private readonly storage: StorageService) {}\n\n  async uploadFile(key: string, buffer: Buffer, contentType: string) {\n    return this.storage.upload({ key, body: buffer, contentType, size: buffer.length })\n  }\n}\n```\n\n### 2 — Cloudflare R2\n\n```typescript\nimport { BymaxStorageModule, providerRecipes } from '@bymax-one/nest-storage'\n\nBymaxStorageModule.forRoot({\n  ...providerRecipes.cloudflareR2({\n    accountId: process.env.R2_ACCOUNT_ID!,\n    bucket: 'my-bucket',\n    accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n    // Required — the *.r2.cloudflarestorage.com API host does not serve public reads.\n    // Configure a Custom Domain in the R2 dashboard and supply it here.\n    customDomain: 'https://cdn.example.com',\n  }),\n})\n```\n\n> **Note:** `customDomain` is the `publicBaseUrl` for R2 and is **required** — there is no working default because the S3 API endpoint (`*.r2.cloudflarestorage.com`) does not serve public object reads. The recipe also sets `requestChecksumCalculation`/`responseChecksumValidation` to `'WHEN_REQUIRED'` to prevent the SDK's default CRC32 headers from being sent (R2 rejects them — see [Provider Compatibility](#provider-compatibility--the-1-trap) below).\n\n### 3 — DigitalOcean Spaces with CDN\n\n```typescript\nimport { BymaxStorageModule, providerRecipes } from '@bymax-one/nest-storage'\n\nBymaxStorageModule.forRoot({\n  ...providerRecipes.digitalOceanSpaces({\n    region: 'nyc3',\n    bucket: 'my-bucket',\n    accessKeyId: process.env.DO_ACCESS_KEY_ID!,\n    secretAccessKey: process.env.DO_SECRET_ACCESS_KEY!,\n  }),\n  // The recipe sets cdnBaseUrl to *.cdn.digitaloceanspaces.com automatically.\n  // Override if you use a custom CDN domain:\n  // cdnBaseUrl: 'https://cdn.example.com',\n})\n```\n\n### 4 — MinIO (local development)\n\n```typescript\nimport { BymaxStorageModule, providerRecipes } from '@bymax-one/nest-storage'\n\nBymaxStorageModule.forRoot({\n  ...providerRecipes.minio({\n    endpoint: 'http://localhost:9000',\n    bucket: 'dev-bucket',\n    accessKeyId: 'minioadmin',\n    secretAccessKey: 'minioadmin',\n  }),\n  // forcePathStyle: true is set by the recipe automatically.\n})\n```\n\n---\n\n## ⚙️ Configuration\n\nThe full configuration reference lives in `docs/technical_specification.md` §4. Key options:\n\n| Option                         | Type                                              | Default           | Notes                                          |\n| ------------------------------ | ------------------------------------------------- | ----------------- | ---------------------------------------------- |\n| `endpoint`                     | `string`                                          | —                 | Required. S3-compatible endpoint URL           |\n| `region`                       | `string`                                          | —                 | Required. Provider region or `'auto'` (R2)     |\n| `bucket`                       | `string`                                          | —                 | Default bucket (can be overridden per-call)    |\n| `credentials`                  | `{ accessKeyId, secretAccessKey, sessionToken? }` | —                 | Load from env / Secrets Manager                |\n| `forcePathStyle`               | `boolean`                                         | `false`           | Set to `true` for MinIO and self-hosted        |\n| `publicBaseUrl`                | `string`                                          | —                 | Base URL for public `getPublicUrl()` results   |\n| `cdnBaseUrl`                   | `string`                                          | —                 | CDN edge URL (preferred over `publicBaseUrl`)  |\n| `keyPrefix`                    | `string`                                          | —                 | Prepended to every resolved key (multi-tenant) |\n| `maxAttempts`                  | `number`                                          | `3`               | SDK v3 retry count                             |\n| `serverSideEncryption`         | `'AES256' \\| 'aws:kms' \\| 'NONE'`                 | —                 | Global SSE policy                              |\n| `requestChecksumCalculation`   | `'WHEN_SUPPORTED' \\| 'WHEN_REQUIRED'`             | SDK default       | Set to `'WHEN_REQUIRED'` for non-AWS providers |\n| `responseChecksumValidation`   | `'WHEN_SUPPORTED' \\| 'WHEN_REQUIRED'`             | SDK default       | Set to `'WHEN_REQUIRED'` for non-AWS providers |\n| `signedUrls.defaultTtlSeconds` | `number`                                          | `3600`            | Default signed-URL TTL                         |\n| `signedUrls.maxTtlSeconds`     | `number`                                          | `604800`          | Hard ceiling; clamped to 7 days at init        |\n| `multipart.thresholdBytes`     | `number`                                          | `10 MiB`          | Switch to multipart above this size            |\n| `multipart.partSizeBytes`      | `number`                                          | `8 MiB`           | Part size (S3 minimum: 5 MiB)                  |\n| `validation.mimeWhitelist`     | `string[]`                                        | sensible defaults | Wildcards: `'image/*'`                         |\n| `validation.maxSizeBytes`      | `number`                                          | —                 | Maximum upload size in bytes                   |\n\n> **Do not use `maxRetries` or `signatureVersion`** — these are AWS SDK v2 options that do not exist in v3. The v3 SDK is SigV4-only. Use `maxAttempts` (default `3`) for retry configuration.\n\n---\n\n## 🧩 Provider Recipes\n\n| Recipe                                    | Provider            | Notes                                                                                 |\n| ----------------------------------------- | ------------------- | ------------------------------------------------------------------------------------- |\n| `providerRecipes.awsS3(...)`              | AWS S3              | Keeps the SDK default checksum mode (`'WHEN_SUPPORTED'`); sets SSE-AES256             |\n| `providerRecipes.cloudflareR2(...)`       | Cloudflare R2       | `customDomain` required; checksums `'WHEN_REQUIRED'`; `region: 'auto'`                |\n| `providerRecipes.backblazeB2(...)`        | Backblaze B2        | `forcePathStyle: false` (B2 supports virtual-hosted); checksums `'WHEN_REQUIRED'`     |\n| `providerRecipes.digitalOceanSpaces(...)` | DigitalOcean Spaces | Sets `cdnBaseUrl` to `*.cdn.digitaloceanspaces.com`; checksums `'WHEN_REQUIRED'`      |\n| `providerRecipes.minio(...)`              | MinIO / self-hosted | `forcePathStyle: true`; checksums `'WHEN_REQUIRED'`; region defaults to `'us-east-1'` |\n| `providerRecipes.wasabi(...)`             | Wasabi Hot Cloud    | Virtual-hosted; checksums `'WHEN_REQUIRED'`                                           |\n\n### Provider Compatibility — The #1 Trap\n\n`@aws-sdk/client-s3` ≥ 3.729.0 defaults `requestChecksumCalculation` to `'WHEN_SUPPORTED'`, which causes the SDK to send `x-amz-checksum-crc32` integrity headers on every `PutObject` and `UploadPart` call. **Cloudflare R2, Backblaze B2, MinIO, DigitalOcean Spaces, and Wasabi reject these headers** — the default upload path fails out of the box on exactly the providers this library targets.\n\nEvery non-AWS provider recipe sets both `requestChecksumCalculation` and `responseChecksumValidation` to `'WHEN_REQUIRED'` to suppress the headers. If you build a custom config instead of using a recipe, you must add this opt-out manually:\n\n```typescript\nBymaxStorageModule.forRoot({\n  endpoint: 'https://...',\n  region: '...',\n  bucket: '...',\n  credentials: { ... },\n  requestChecksumCalculation: 'WHEN_REQUIRED',\n  responseChecksumValidation: 'WHEN_REQUIRED',\n})\n```\n\n### Public Access / ACLs\n\n`publicRead: true` (or `defaultPublicRead: true` in the config) sends `ACL: 'public-read'` on PutObject. However:\n\n- **Modern AWS S3 buckets** (Object Ownership = \"Bucket owner enforced\", the default since April 2023) return **HTTP 400 `AccessControlListNotSupported`** when an ACL header is present.\n- **Cloudflare R2** ignores ACLs entirely — `publicRead: true` is a **silent no-op**; configure a Custom Domain in the R2 dashboard for public delivery.\n\nFor public access on either provider, use a **bucket policy**, a **CDN**, or **signed URLs** rather than ACLs.\n\n---\n\n## ⬆️ Upload\n\n### Single-shot / Buffer\n\n```typescript\nimport { StorageService } from '@bymax-one/nest-storage'\nimport { randomUUID } from 'node:crypto'\n\nconst result = await storage.upload({\n  key: `avatars/${randomUUID()}.jpg`,\n  body: fileBuffer,\n  contentType: 'image/jpeg',\n  size: fileBuffer.length,\n  metadata: { userId: '42' },\n  serverSideEncryption: 'AES256',\n})\nconsole.log(result.publicUrl) // https://...\n```\n\n### Stream with Progress\n\n```typescript\nimport { createReadStream, statSync } from 'node:fs'\n\nawait storage.upload({\n  key: 'videos/event-2026.mp4',\n  body: createReadStream('/tmp/video.mp4'),\n  contentType: 'video/mp4',\n  size: statSync('/tmp/video.mp4').size,\n  onProgress: (e) => console.log(`${e.loaded}/${e.total ?? '?'} bytes`),\n})\n```\n\nUploads are automatically routed to multipart when `size >= multipart.thresholdBytes` (default 10 MiB). A `Readable` without a `size` always uses multipart. Multipart aborts automatically on failure (`leavePartsOnError: false`).\n\n### Idempotency\n\n```typescript\nconst key = `uploads/${randomUUID()}.png`\nconst opts = {\n  key,\n  body: buf,\n  contentType: 'image/png',\n  size: buf.length,\n  idempotencyKey: 'order-42-avatar',\n}\n\nconst r1 = await storage.upload(opts)\nconst r2 = await storage.upload(opts) // returns cached result instantly\nconsole.log(r2.fromIdempotencyCache) // true\n```\n\n---\n\n## ⬇️ Download\n\n### As a Stream\n\n```typescript\nimport { Controller, Get, Param, Res } from '@nestjs/common'\nimport type { Response } from 'express'\n\n@Get(':key')\nasync download(@Param('key') key: string, @Res() res: Response) {\n  const { stream, metadata } = await this.storage.download({ key })\n  res.setHeader('Content-Type', metadata.contentType)\n  res.setHeader('Content-Length', String(metadata.size))\n  stream.pipe(res)\n}\n```\n\n### As a Buffer\n\n```typescript\n// Use only for files < 10 MB — loads the entire object into memory.\nconst { buffer, metadata } = await this.storage.downloadBuffer({ key: 'docs/report.pdf' })\n```\n\n---\n\n## 🔗 Signed URLs (GET / PUT / Multipart)\n\n> **Security:** signed URLs are temporary credentials. **Never log them** — a logged signed URL is accessible to anyone with log access.\n\n### Presigned GET\n\n```typescript\nimport { SignedUrlService } from '@bymax-one/nest-storage'\n\nconst result = await signedUrls.getDownloadUrl({\n  key: 'invoices/2026-001.pdf',\n  ttlSeconds: 900,\n  responseContentDisposition: 'attachment; filename=\"invoice.pdf\"',\n})\n// result.url — share this with the client; expires at result.expiresAt\n```\n\n### Presigned PUT (direct browser upload)\n\n```typescript\n// Backend — issue a signed upload URL\n@Post('signed-put')\nasync getSignedPut(@Body() body: { folder: string; contentType: string }) {\n  const key = `${body.folder}/${randomUUID()}`\n  const result = await this.signedUrls.getUploadUrl({\n    key,\n    contentType: body.contentType,\n    ttlSeconds: 600,\n    maxSizeBytes: 10 * 1024 * 1024,\n  })\n  // NEVER log result.url — it is a temporary credential\n  return { uploadUrl: result.url, key, expiresAt: result.expiresAt }\n}\n```\n\n> **Caveat:** a signed PUT bypasses the library's local MIME/size validation. Pair it with a presigner `maxSizeBytes` (Content-Length-Range policy), a `HEAD` check after upload, and an `IFileScanner` post-upload hook.\n\n```typescript\n// Frontend\nconst { uploadUrl, key } = await fetchJson('/uploads/signed-put', { folder, contentType })\nawait fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': contentType }, body: file })\nawait fetchJson('/uploads/confirm', { key }) // backend persists metadata\n```\n\n### Presigned Multipart\n\n```typescript\nconst { uploadId, parts, key } = await signedUrls.getMultipartUploadUrls({\n  key: 'videos/large.mp4',\n  contentType: 'video/mp4',\n  partCount: 10,\n  ttlSeconds: 3600,\n})\n// Upload each part with the corresponding signed URL, then call CompleteMultipartUpload.\n```\n\n> **TTL clamp:** per-request `ttlSeconds` values above `signedUrls.maxTtlSeconds` are silently clamped; `maxTtlSeconds` is itself clamped to 604 800 s (7-day SigV4 ceiling) at module init. A `ttlSeconds` ≤ 0 throws `STORAGE_SIGNED_URL_TTL_INVALID`.\n\n---\n\n## ✅ Validation\n\n```typescript\nimport { Injectable } from '@nestjs/common'\nimport type { IUploadValidator } from '@bymax-one/nest-storage'\n\n/** Magic-byte PDF check — reads the first 4 bytes of the stream. */\n@Injectable()\nexport class PdfValidator implements IUploadValidator {\n  async validate(input: {\n    body: Buffer | NodeJS.ReadableStream\n    readBytes: (n: number) => Promise<Buffer>\n  }) {\n    const header = await input.readBytes(4)\n    if (header.toString('ascii', 0, 4) !== '%PDF') {\n      return { valid: false, reason: 'Not a valid PDF (magic bytes mismatch)' }\n    }\n    return { valid: true }\n  }\n}\n```\n\nRegister it in the module:\n\n```typescript\nBymaxStorageModule.forRoot({\n  ...providerRecipes.awsS3({ ... }),\n  validation: {\n    mimeWhitelist: ['application/pdf'],\n    maxSizeBytes: 25 * 1024 * 1024,\n  },\n  validators: [{ useClass: PdfValidator }],\n})\n```\n\n---\n\n## 🦠 Virus Scanning (`IFileScanner`)\n\n```typescript\nimport type { IFileScanner, FileScanResult } from '@bymax-one/nest-storage'\n\n/** ClamAV stub — delegate to clamd over a socket in production. */\n@Injectable()\nexport class ClamAvScanner implements IFileScanner {\n  async scan(_input: unknown): Promise<FileScanResult> {\n    // Replace with real clamd socket call\n    return { status: 'clean', engine: 'clamav-0.103' }\n  }\n}\n```\n\n```typescript\nBymaxStorageModule.forRoot({\n  ...providerRecipes.awsS3({ ... }),\n  scanner: {\n    impl: new ClamAvScanner(),\n    mode: 'pre-upload',       // 'pre-upload' | 'post-upload' | 'both'\n    rejectOnUnknown: false,   // treat inconclusive scans as pass (true = reject)\n  },\n})\n```\n\n---\n\n## ♻️ Lifecycle Operations\n\n### List / Paginate\n\n```typescript\nconst page1 = await storage.list({ prefix: 'avatars/', maxKeys: 100 })\n// { objects: [...], nextContinuationToken?: '...' }\nif (page1.nextContinuationToken) {\n  const page2 = await storage.list({\n    prefix: 'avatars/',\n    maxKeys: 100,\n    continuationToken: page1.nextContinuationToken,\n  })\n}\n```\n\n### Copy (server-side)\n\n```typescript\nconst { etag } = await storage.copy({\n  sourceKey: 'drafts/file.pdf',\n  destinationKey: 'published/file.pdf',\n})\n```\n\n### Delete / Batch Delete\n\n```typescript\nawait storage.delete('temp/file.png') // idempotent — no error on 404\n\nconst { deleted, failed } = await storage.deleteMany(['a.png', 'b.png', 'c.png'])\n```\n\n### Raw S3Client (Advanced)\n\nFor provider-specific operations not covered by the public API, inject the raw client:\n\n```typescript\nimport { Inject, Injectable } from '@nestjs/common'\nimport { S3Client } from '@aws-sdk/client-s3'\nimport { BYMAX_STORAGE_S3_CLIENT } from '@bymax-one/nest-storage'\n\n@Injectable()\nexport class AdvancedStorageService {\n  constructor(@Inject(BYMAX_STORAGE_S3_CLIENT) private readonly s3: S3Client) {}\n\n  async customOperation() {\n    // Direct S3Client call — bypass the public API when needed.\n  }\n}\n```\n\n### `forRootAsync`\n\n```typescript\nimport { ConfigModule, ConfigService } from '@nestjs/config'\n\nBymaxStorageModule.forRootAsync({\n  imports: [ConfigModule],\n  inject: [ConfigService],\n  useFactory: (config: ConfigService) => ({\n    ...providerRecipes.awsS3({\n      region: config.getOrThrow('AWS_REGION'),\n      bucket: config.getOrThrow('AWS_BUCKET'),\n      accessKeyId: config.getOrThrow('AWS_ACCESS_KEY_ID'),\n      secretAccessKey: config.getOrThrow('AWS_SECRET_ACCESS_KEY'),\n    }),\n    keyPrefix: config.get('STORAGE_KEY_PREFIX'),\n    serverSideEncryption: 'AES256',\n  }),\n})\n```\n\n---\n\n## 🚨 Error Codes\n\nAll errors are thrown as `StorageException extends HttpException`. The response body is `{ error: { code, message, details? } }`.\n\n| Code                             | HTTP | When                                                                                                                                                                                                               |\n| -------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `STORAGE_NOT_CONFIGURED`         | 503  | Credentials missing and an operation was called                                                                                                                                                                    |\n| `STORAGE_KEY_INVALID`            | 400  | Path traversal (`..`), key starts with `/`, or empty after normalization                                                                                                                                           |\n| `STORAGE_BODY_MISSING`           | 400  | `upload()` called without a body                                                                                                                                                                                   |\n| `STORAGE_CONTENT_TYPE_REQUIRED`  | 400  | `upload()` called without `contentType`                                                                                                                                                                            |\n| `STORAGE_MIME_NOT_ALLOWED`       | 415  | `contentType` outside `mimeWhitelist`                                                                                                                                                                              |\n| `STORAGE_SIZE_EXCEEDED`          | 413  | `size > maxSizeBytes`                                                                                                                                                                                              |\n| `STORAGE_VALIDATION_FAILED`      | 400  | `IUploadValidator` rejected the file (`details.reason`)                                                                                                                                                            |\n| `STORAGE_SCAN_INFECTED`          | 422  | Scanner returned `'infected'` (`details.threat`)                                                                                                                                                                   |\n| `STORAGE_SCAN_INCONCLUSIVE`      | 422  | Scanner returned `'unknown'` and `rejectOnUnknown: true`                                                                                                                                                           |\n| `STORAGE_OBJECT_NOT_FOUND`       | 404  | `head()`, `download()`, or `copy()` on a nonexistent key                                                                                                                                                           |\n| `STORAGE_PROVIDER_ERROR`         | 502  | AWS SDK error (network, 5xx, throttling, `AccessControlListNotSupported`); `details` carries `awsCode`, `awsMessage`, `httpStatus`, `requestId` plus the operation context (`op`, `bucket`, and `key` or `prefix`) |\n| `STORAGE_SIGNED_URL_TTL_INVALID` | 400  | Per-request `ttlSeconds` ≤ 0                                                                                                                                                                                       |\n| `STORAGE_PART_TOO_SMALL`         | 400  | Multipart with part < 5 MiB (S3 limit)                                                                                                                                                                             |\n| `STORAGE_INVALID_PART_COUNT`     | 400  | `getMultipartUploadUrls()` with `parts <= 0` (`details.provided`)                                                                                                                                                  |\n| `STORAGE_BUCKET_UNDEFINED`       | 400  | Operation without a bucket and no default in config                                                                                                                                                                |\n| `STORAGE_MULTIPART_ABORTED`      | 500  | Multipart upload failed and was aborted                                                                                                                                                                            |\n| `STORAGE_INVALID_CONFIG`         | 500  | `BymaxStorageModuleOptions` validation failed at initialization                                                                                                                                                    |\n| `STORAGE_TIMEOUT`                | 504  | Request exceeded `requestTimeoutMs`                                                                                                                                                                                |\n\n```typescript\nimport { StorageException, STORAGE_ERROR_CODES } from '@bymax-one/nest-storage'\n\ntry {\n  await storage.upload({ ... })\n} catch (err) {\n  if (err instanceof StorageException) {\n    const body = err.getResponse() as { error: { code: string; message: string } }\n    console.error(body.error.code) // e.g. 'STORAGE_MIME_NOT_ALLOWED'\n  }\n}\n```\n\n---\n\n## 📖 API Reference\n\nEvery operation below is documented with a runnable example in the sections above.\nThis is the index.\n\n### `StorageService`\n\n| Method           | Signature                                                     | Notes                                                        |\n| ---------------- | ------------------------------------------------------------- | ------------------------------------------------------------ |\n| `upload`         | `(options: UploadOptions) => Promise<UploadResult>`           | Multipart via `@aws-sdk/lib-storage`; aborts on failure      |\n| `download`       | `(options: DownloadOptions) => Promise<{ stream, metadata }>` | Streaming; the body is never buffered                        |\n| `downloadBuffer` | `(options: DownloadOptions) => Promise<{ buffer, metadata }>` | Buffers — bounded by the caller's own memory                 |\n| `head`           | `(key, options?) => Promise<ObjectMetadata>`                  | Metadata without transferring the body                       |\n| `exists`         | `(key, options?) => Promise<boolean>`                         | `head` reduced to a boolean                                  |\n| `getPublicUrl`   | `(key, options?) => string`                                   | Composed, not signed — for buckets that are public by policy |\n| `delete`         | `(key, options?) => Promise<void>`                            | Idempotent, as S3 delete is                                  |\n| `deleteMany`     | `(keys: string[], options?) => Promise<DeleteManyResult>`     | Partial failures are reported, not thrown                    |\n| `list`           | `(options: ListOptions) => Promise<ListResult>`               | Paginated by continuation token                              |\n| `copy`           | `(options: CopyOptions) => Promise<{ key, etag }>`            | Server-side; the bytes never reach this process              |\n\n### `SignedUrlService`\n\n| Method                   | Signature                                                                     | Notes                                                         |\n| ------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------- |\n| `getDownloadUrl`         | `(options: SignedGetUrlOptions) => Promise<SignedUrlResult>`                  | GET presign                                                   |\n| `getUploadUrl`           | `(options: SignedPutUrlOptions) => Promise<SignedUrlResult>`                  | PUT presign, with optional content-type and length conditions |\n| `getMultipartUploadUrls` | `(options: MultipartUploadUrlsOptions) => Promise<MultipartUploadUrlsResult>` | One signed URL per part, plus the upload id                   |\n\n### Key normalization\n\n`KeyResolverService` is internal — it is not exported, and it is not something a\nconsumer calls. It is named here because every method above passes the key you\nsupply through it first: the guard below, then `keyPrefix`. The key you read back in\n`UploadResult` and `ListedObject` is the resolved one.\n\n### DI tokens\n\n`BYMAX_STORAGE_OPTIONS` · `BYMAX_STORAGE_S3_CLIENT` · `BYMAX_STORAGE_FILE_SCANNER` ·\n`BYMAX_STORAGE_UPLOAD_VALIDATORS` · `BYMAX_STORAGE_IDEMPOTENCY_CACHE` ·\n`BYMAX_STORAGE_LOGGER` — all `Symbol()`, so no string token can collide with them.\n\n---\n\n## 🏗️ Architecture\n\n```\n                 BymaxStorageModule.forRoot / forRootAsync\n                                    │\n                          validateOptions (fail fast)\n                     credentials tolerated empty on purpose\n                                    │\n                    ┌───────────────┴────────────────┐\n                    │        S3ClientProvider        │\n                    │  one @aws-sdk/client-s3 client │\n                    │  built from a provider recipe  │\n                    └───────────────┬────────────────┘\n                                    │\n                        BYMAX_STORAGE_S3_CLIENT\n                     (escape hatch — the raw S3Client)\n                                    │\n          ┌─────────────────────────┴─────────────────────────┐\n          │                                                   │\n    StorageService                                     SignedUrlService\n  upload · download                                  GET · PUT · multipart\n  head · exists · list                                   TTL-clamped\n  copy · delete · deleteMany                         SigV4 ceiling 7 days\n          │                                                   │\n          └──────────────┬────────────────────────────────────┘\n                         │\n                  KeyResolverService\n        every caller key passes here before S3:\n        refuse `..`, leading `/`, NUL, empty → then keyPrefix\n                         │\n    ┌────────────────────┼────────────────────┐\n    │                    │                    │\nIUploadValidator[]   IFileScanner       IdempotencyCache\nMIME + size, then    pre- and/or        LRU, in-process,\nyours                post-upload        collapses a retry\n   (yours)             (yours)          within one instance\n```\n\nOne engine, six recipes. `providerRecipes` differ only in endpoint, region and the\nhandful of flags each provider needs (`forcePathStyle` for MinIO, `auto` region for\nR2) — there is no per-provider code path, so a provider swap is a configuration\nchange and every operation behaves the same way afterwards.\n\nThe idempotency cache is **in-process**. It collapses a retried `upload` inside one\ninstance; it does not coordinate between replicas.\n\n### Design Principles\n\n| Principle                        | Description                                                                                                                                                                                                                           |\n| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 🔑 **One key chokepoint**        | Every caller-supplied key passes through `KeyResolverService` before it reaches S3. `..`, a leading `/`, null bytes and empty keys are refused, and `keyPrefix` is prepended after normalization — so it cannot be escaped by the key |\n| 🧩 **One engine, six recipes**   | `providerRecipes` differ in endpoint, region and per-provider flags only. There is no per-provider code path, so a provider swap is configuration and every operation behaves the same afterwards                                     |\n| 🚫 **Refuse before storing**     | MIME allowlist and size limit, then registered `IUploadValidator`s, then the `IFileScanner`. An upload that will be rejected is rejected before a byte is written                                                                     |\n| ⏱️ **Bounded signatures**        | Presign TTLs are clamped and SigV4's 604 800 s ceiling is enforced locally, not discovered when the provider rejects the signature                                                                                                    |\n| 💥 **Fail at boot, mostly**      | Options are validated before any client is built. Credentials are the deliberate exception: empty values are tolerated so a development workflow boots, and operations then fail with `STORAGE_NOT_CONFIGURED`                        |\n| 🧊 **Zero runtime dependencies** | `dependencies` is `{}`. You choose the exact `@aws-sdk/*` versions and the supply-chain surface stays yours                                                                                                                           |\n\n---\n\n## 🔐 Security Model\n\nThis library turns caller input into object keys, signs URLs that grant access on their\nown, and holds the credentials for a bucket. Its security contract is about what a key can\nreach, how long a signature lives, and what leaves in an error.\n\n### The object key is the attack surface\n\nEverything a caller sends becomes part of a key, and a key is a path.\n`KeyResolverService.normalize` is the single chokepoint: it refuses an empty key, a key\ncontaining a null byte, a key starting with `/`, and any key with a `..` segment; it\ncollapses duplicate slashes; and only then does it prepend `keyPrefix`. Nothing in this\nlibrary composes a key any other way, so a tenant cannot climb out of its prefix by naming\none.\n\n### Presigned URLs are bearer credentials\n\nAnyone holding one has the access it encodes, for as long as it lives. TTLs are clamped,\nand SigV4's hard ceiling of 604 800 s (7 days) is enforced here rather than passed through\nto a signature the provider would reject at use time. Treat a signed URL like a password\nwith an expiry, not like a link.\n\n### Credentials stay where they were put\n\nThey arrive through module options and are handed to the SDK. This library never reads\n`process.env`, never logs them, and never places them — or a signed URL — in an exception.\n\nThey are also not reachable by serializing the objects that hold them. The resolved\noptions are injected into every service, so `credentials` is attached as a non-enumerable\naccessor: `JSON.stringify`, object spread, `util.inspect` and `util.inspect` with\n`showHidden` all omit it. Those are the paths taken by code that renders a provider it was\nhanded incidentally — a structured logger formatting its arguments, an error reporter\ncapturing the scope of a throw. Reading on purpose is unchanged.\n\n### Error payloads carry the operation, and that includes the key\n\nWhat `aws-error-mapper` puts in `details` is the provider's error code and message, the\nHTTP status, the request id, and the operation context the call site supplies: which\noperation, which bucket, and the resolved key or prefix. That is enough to diagnose a\nfailure, and it means an object key reaches whatever consumes the exception — so if a key\nis itself sensitive in your deployment, do not log the envelope verbatim.\n\n### Uploads are refused before they are stored\n\nMIME allowlist (wildcards included) and size limit run first, then any `IUploadValidator`\nyou register, then the `IFileScanner` if configured. A scanner can run pre-upload,\npost-upload, or both — the post-upload position exists because some scanners only accept an\nobject they can fetch.\n\n### `keyPrefix` is a namespace, not an access boundary\n\nIt scopes the keys this library composes; it does not restrict what the credentials can\nreach. Anything holding the bucket's credentials can read every prefix in it. Isolation\nthat must survive a compromised client belongs in IAM policies or separate buckets.\n\n---\n\n## 🛡️ Security Table\n\n| Layer            | Implementation                                                                                                                                         |\n| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Object keys      | Guarded and normalized in one place; `..`, leading `/`, null bytes and empty keys refused                                                              |\n| Multi-tenancy    | `keyPrefix` prepended after normalization, so it cannot be escaped by the key                                                                          |\n| Credentials      | Injected options only; never read from `process.env`, never logged, never in an exception; held in a non-enumerable accessor, so serializing a service omits them |\n| Error payloads   | Provider code and message, HTTP status, request id, and the operation context (`op`, `bucket`, `key`/`prefix`) — never credentials, never a signed URL |\n| Presign lifetime | TTL clamped, SigV4's 7-day ceiling enforced locally                                                                                                    |\n| Encryption       | Server-side (AES256 / `aws:kms`) configurable globally or per upload                                                                                   |\n| Content          | MIME allowlist + size limit, then registered validators, then the scanner                                                                              |\n| Supply chain     | `dependencies: {}`; third-party Actions pinned by commit SHA (org-internal reusables by tag); CodeQL, OSV-Scanner and OpenSSF Scorecard                |\n\n> [!IMPORTANT]\n> **`keyPrefix` is not an access boundary.** It scopes the keys this library composes;\n> it does not restrict what the credentials can reach. Anything holding the bucket's\n> credentials can read every prefix in it. Enforce tenant isolation with IAM policies\n> or separate buckets when the boundary has to hold against the application itself.\n\n---\n\n## 🧱 Tech Stack\n\n- **Runtime:** Node.js 24+\n- **Framework:** NestJS 11 (`ConfigurableModuleBuilder`, `@Global()`, `Symbol()` tokens)\n- **Storage engine:** `@aws-sdk/client-s3 ^3.700` (peer), with `@aws-sdk/lib-storage`\n  for multipart and `@aws-sdk/s3-request-presigner` for presigns — all peers\n- **Providers:** AWS S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces, MinIO, Wasabi\n- **Build:** tsup — ESM + CJS per subpath, with `.d.ts` _and_ `.d.cts` declarations\n- **Tests:** Jest + Testcontainers (MinIO, E2E) + Stryker (mutation)\n- **TypeScript:** 5.x strict (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`), zero `any`\n\n---\n\n## 🧪 Testing & Quality\n\nObject storage is where a bug is expensive to discover later — a key written to the wrong\nplace stays there — so the suite is held to a bar beyond \"the tests pass\".\n\n- ✅ **100% line coverage** — statements, branches, functions and lines, enforced as a gate\n- ✅ **100% mutation score** — verified with [Stryker](https://stryker-mutator.io/) at\n  `break: 95`; every survivor was killed by a strengthened assertion, and the eight provable\n  equivalents are documented inline\n- ✅ **Real object storage in e2e** — MinIO through Testcontainers, so multipart, presigning\n  and listing are exercised against an actual S3 API rather than a mock\n- ✅ **Published-artifact gates** — `check:exports` resolves the types the way each module\n  system does, `check:runtime` loads every subpath from the packed tarball in ESM and\n  CommonJS, and `check:published` compiles this README's snippets against `dist/`\n- ✅ **Every suppression is justified** — no coverage directives anywhere; the six\n  `// Stryker disable` comments in the production source each name why the mutant they\n  silence is provably equivalent, and `check:mutants` proves each reason is written in the\n  grammar Stryker parses, so the mutation report carries it in full rather than the\n  `Ignored using a comment` fallback\n\n```bash\npnpm test          # unit tests (Jest)\npnpm test:cov      # unit tests with the 100% coverage gate\npnpm test:e2e      # end-to-end against MinIO (requires Docker)\npnpm mutation      # Stryker mutation testing (break: 95)\npnpm typecheck     # tsc strict check\npnpm lint          # ESLint\n```\n\n---\n\n## 🤝 Contributing\n\nPull requests are welcome. Please open an issue first for significant changes.\n\n- Read [`docs/technical_specification.md`](docs/technical_specification.md) for architecture decisions.\n- Run `pnpm test:cov` and `pnpm lint` before opening a PR.\n- Please use Conventional Commits for the message; nothing enforces it here, so it is a convention rather than a gate.\n\n---\n\n## 🔒 Security Policy\n\nIf you discover a security vulnerability, please **do not** open a public issue. Instead, email us\nat **support@bymax.one** with details. We take security seriously and will respond promptly. See\n[`SECURITY.md`](SECURITY.md) for the full policy, including the storage-specific security goals.\n\n---\n\n## 📄 License\n\n[MIT](./LICENSE) © [Bymax One](https://github.com/bymaxone)\n\n---\n\n<p align=\"center\">\n  <sub>Built with ❤️ by <a href=\"https://github.com/bymaxone\">Bymax One</a></sub>\n</p>\n","readmeFilename":"README.md"}