{"_id":"bucket","_rev":"28-e0db05984276db3b322cfdbe873b6cd1","name":"bucket","dist-tags":{"latest":"0.11.4"},"versions":{"0.0.1":{"name":"bucket","version":"0.0.1","author":{"name":"Alex Bosworth","email":"alex.bosworth@gmail.com"},"_id":"bucket@0.0.1","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"}],"dist":{"shasum":"d09ac2c33052dd23d68cc7029f11d076a0c4f362","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.0.1.tgz","integrity":"sha512-PlMxG/TPQxTgQKCMIApdN2N0RuOvwoMTHTq1kpnkQLjybTzLbQ66KIHfNGF9uf/xei1GaBPvP5K1a8AoO0KoNQ==","signatures":[{"sig":"MEYCIQDJlGC+ZigL0qG9kMemRBdnuJwrawU24tPLR63poyVNUQIhALNqFzlWZU1JDlt18HNxtwjMWDLE+/1OIbPKswbTRpAI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2lMtCRA9TVsSAnZWagAAhh8P/3OIryUfFsuCFSR7ZQl7\nYfLsAnn5C1ltn1nznCt3/IA3LJL9DhYRXZc824enXWQkj5J0fum8HyXYMO6j\nHPSEkNmARtHXWCLLDVtGH30SjfzSaPsPtHVvM7Rz6AvqeorJIZv5JfCPW3/Z\nR68cajK6eBgOT02/2AUvq5oQW8ui8HAtTqO1JNY293frLMrQmb6fBx46dhmQ\nQ6dlOzR0d91ZJGeB5Q4EKqN4Vtz7yXCPeiwIuv2Nrb4YM30Fk3kBELHzM7SA\n1zyNNqw9/2Vfmeyj/cR5K28K80tYJLJnxT0E1q4N6OguI2cdwWDP9XrceBNd\nsw227IZJChq/5/6rEdvAKvO6Co6aJJUhmlbGH2HctaibqqAjNwDARpGLGmyA\n3Rxt9vRZrucOaL4fyiWz4fVR+rhswxlhPFCDbxS/fd6wRh5krQSA004wuBQJ\nBTD8S3NJsZKu6/gav3S5h8SNavZ3+e/CwJq+eDVv4kUDstoo+z0tKz5ZOkM2\nfDTuKF2DX9VXG7dLgRgtUOBzIBL1BZzL7pVTZJz/xMPWZNtJvB61uRhOOml0\nPny05QS1vcukkZaPxdCoaGhVEJidN1P+fPFpjeMwThqlTi0A2NQkU1nDjf16\n39SmpurRkLbPd+yCD/9jqFXgtmbKos2Oa4VO8MqMhVH/RMkBZl4rlYTORu+O\nExId\r\n=a0IB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","engine":"node >= 0.4.1","engines":{"node":"*"},"scripts":{},"repository":{"url":"git://github.com/alexbosworth/bucket.git","type":"git"},"_npmVersion":"1.0.22","description":"a s3 library that uses deferred callbacks","directories":{},"_nodeVersion":"v0.4.9","_npmJsonOpts":{"file":"/Users/alex/.npm/bucket/0.0.1/package/package.json","wscript":false,"serverjs":false,"contributors":false},"dependencies":{"xml2js":"0.1.10"},"_defaultsLoaded":true,"devDependencies":{},"_engineSupported":true},"0.1.0":{"name":"bucket","version":"0.1.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.1.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://github.com/franciscop/bucket#readme","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"b7e20d1df6b082458e8ec79856609f4966ea7f52","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.1.0.tgz","fileCount":35,"integrity":"sha512-BQNqFZTtOOPd2BZ2QMvISn3ZdS1OSD/QsZmBIy/FedWLxOPBq64EVDDWdtvmwgq+pLXbZJpgNPrQ+5YCrxeqWw==","signatures":[{"sig":"MEUCIQCALZ/08/Q3/Sy4DQwjk618P7vOoM4pra91KVE/e6xYywIgSw0Jwk0TUzmKoj+Tn9j+MiW/Djq3YS7eno/YOn6wPi4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":217933},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"ad787f5d3a2e11883ac3e4b5f76405c69a6812f0","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","emulators:up":"docker compose -f docker-compose.emulators.yml up -d","emulators:down":"docker compose -f docker-compose.emulators.yml down -v","test:emulators":"npm run emulators:setup && bun --env-file=.env.emulators test test/index.test.ts --timeout 30000","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API:","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.1.0_1782058552956_0.29315205928411703","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"bucket","version":"0.1.1","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.1.1","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"a50b9fdeaa91441208dd502797d1e14968d097a9","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.1.1.tgz","fileCount":35,"integrity":"sha512-hsofNbTEhNGp2VKbiq4WavWj6iP/LtPw0MHA+895GCUTqv7uZv2UWe1jsc28CpR6x/gNWLMNtLXigRgCG9nlew==","signatures":[{"sig":"MEYCIQCrWOfYmxrzuU3E19w0DHa+kvldRwTIiIm25YYMWiv27gIhAN9FuzB21AGLs1pRoEFiZJLmUpK0hcwF3XVIe56R6Aol","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":218377},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"9aa198611cdd195683b09014428db26eecad5d01","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","emulators:up":"docker compose -f docker-compose.emulators.yml up -d","emulators:down":"docker compose -f docker-compose.emulators.yml down -v","test:emulators":"npm run emulators:setup && bun --env-file=.env.emulators test test/index.test.ts --timeout 30000","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API:","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help","Documentation":"/documentation"},"title":"🪣 Bucket - A unified API for any file storage solution","homepage":"https://bucketjs.com/"},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.1.1_1782059788252_0.5766710502217489","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"bucket","version":"0.2.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.2.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"10ced5e42bcff88de5f5411874716640f76779bc","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.2.0.tgz","fileCount":65,"integrity":"sha512-kHn0v2xNw1m2rYnMYM0sPhKG92zXzZXQbINFZ9evcCuk+z4Klw4NJgyhNzflpn++3IU4cRwZ4irED/3PzrF6ew==","signatures":[{"sig":"MEQCICY64mNe8cJPB5wjBPrhNMgwSSt/aGHeT0pv8sbpx5s/AiAB62tP/bYV4QAi0AZmsPRTJjjFTABkvmNXjnG+eUyFHA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":524006},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"a08152f7f5543b875247dcb3f9a7feb1a0e9c965","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","emulators:up":"docker compose -f docker-compose.emulators.yml up -d","emulators:down":"docker compose -f docker-compose.emulators.yml down -v","test:emulators":"npm run emulators:setup && bun --env-file=.env.emulators test test/index.test.ts --timeout 30000","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"},"title":"🪣 Bucket - A unified API for any file storage solution","homepage":"https://bucketjs.com/"},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.2.0_1783148193548_0.06762001816532748","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"bucket","version":"0.3.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.3.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"89784197919a1867d865c88cef1cc0c79c95d0e2","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.3.0.tgz","fileCount":117,"integrity":"sha512-HiV53NiUeYy7ETEAA1mU8Wvb4YJYZwX8UButmAqGT1QnPvHzY9bMO0cWAvXKuZEWwIC9oTqKbA15iGt5uGLmbw==","signatures":[{"sig":"MEUCIQCDk8tl3CqRAy/L6py24mEaw6o9/+3npHn3pPMx0CGPUQIgSUzAOs0guZ5uL2dZNwo2p19/t4O6tSQ1cZH3Ie/otYw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1096941},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"34b4811cd99ffa74c8cd8f43aba314a39c7e0c57","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","emulators:up":"docker compose -f docker-compose.emulators.yml up -d","emulators:down":"docker compose -f docker-compose.emulators.yml down -v","test:emulators":"npm run emulators:setup && bun --env-file=.env.emulators test test/index.test.ts --timeout 30000","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"},"title":"🪣 Bucket - A unified API for any file storage solution","homepage":"https://bucketjs.com/"},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.3.0_1783170402273_0.6094999418717706","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"bucket","version":"0.4.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.4.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"f2a003f2bfbe837d994b13739bb70b13980ad7e6","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.4.0.tgz","fileCount":152,"integrity":"sha512-txuoNkkeGlffIvLDwqmjYO881dqSTDpiXHwLGTcTAedyVMc5Tom5qtOBjUFcHG/Ge9POMgB/qQhZypSHnbPLEA==","signatures":[{"sig":"MEUCIAoJmmQROuOfd/ZCtGE+ZVZcKujzbtZRk2j3szYbIl/BAiEA2E5BLv8isPsnkZfJEbEAsDmaVf72YH1H9EeDCsy92BQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1522756},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"edfdcdb4ee519596eb8148e906c87aed785b266c","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"},"title":"🪣 Bucket - A unified API for any file storage solution","homepage":"https://bucketjs.com/"},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.4.0_1783227103324_0.5994159784770525","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"bucket","version":"0.5.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.5.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"3ef1384aaa614fceedd884c23de2b9b3ab275583","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.5.0.tgz","fileCount":187,"integrity":"sha512-E/aTwaXp15dUJWQwAxMzEHHFIJEcwv4NASSl7SEyJRKnoiRh42FD44LrvRlgiuKG77c6hQzzG7t8oL5NHSa2MA==","signatures":[{"sig":"MEUCICdd93wugJinOE9r08V17cbaS8YSA6VZczZfjqR+7YI5AiEA6EA6kSgMYRhTxMUJg6zWvuRyrDCC1XhvHIE8aHLo/zQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1930992},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"e52193cf184b3d1ee12b45dbf57da2ac994ca089","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.5.0_1785232384265_0.36025878062288164","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"bucket","version":"0.6.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.6.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"e3b7fc4dca845c04601789253472e034c66890c3","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.6.0.tgz","fileCount":188,"integrity":"sha512-Hvs8+5Rzu2JlSMT/2tGAj96VkWt/dII06aKLUu2T4PEEQdYE9qd5oRpIfanuSjNjYe3tkZF22poi45yeRSh9EA==","signatures":[{"sig":"MEQCIH7wK321P29et4ccxRb2wGPE7gdoMSq532tVFNw3JHSoAiB2KFtvWF3c5zzPEnpxocx9hg01PLuBAPzp08we+HEoTQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1940720},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"7e92a49f67e3faa24891e9702dc5a56fc566a881","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.6.0_1785285975575_0.9641567610811803","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"bucket","version":"0.7.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.7.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"cc29bd7cc078652d8e82807279b7df9e59fb9b72","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.7.0.tgz","fileCount":198,"integrity":"sha512-ssxnQCux0Ufxvdh5FdFVgiJ552ujqgM23Am/LLoq6yXz5rhYiXfzAtrYuirV6bb4Ueixy4oQS7wAteCNv7BNvQ==","signatures":[{"sig":"MEYCIQC437pz9WLHqobwaX7TWfc3XWcerB9YiWEFpFIl+wpXGwIhAMcBPJWaF5uEb3XsDk7AMObgTwBBR8E9Gvwv0Hm6K4bX","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2054280},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"02520788cba4db1aceb77981612b9c1144a0dd05","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.7.0_1785433073021_0.31507363367380314","host":"s3://npm-registry-packages-npm-production"}},"0.7.1":{"name":"bucket","version":"0.7.1","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.7.1","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"4ab79a37e3fba4c019d158e91f21608bf1b3819a","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.7.1.tgz","fileCount":204,"integrity":"sha512-kYdMtKkim37m+xMSwvN2SeY+uMDENVyBghMPpoE5mGl2XD4McYKXXJhqB7ZTpbnOdJzHGqSqOdoT8/avTD9Mxw==","signatures":[{"sig":"MEQCICyIfzblWipG3z8Ac6MpFclzrB0qOgmSWs0gutCYxcQ9AiB9H0uQ4Yx9hngqnAIFAUocqUZiANRI2rLR1zzZgvY6eQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2134549},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"1627effcb4b3c5d2c629f221d6df9dde1ceb9127","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.7.1_1785685178004_0.16758747654118666","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"bucket","version":"0.8.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.8.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"2ab4cbb4d660c25cbce667eeb267f9a23bbbafbb","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.8.0.tgz","fileCount":28,"integrity":"sha512-a4OKSaf3DGpVrxsbCpeI9SPkHh+5VsOl1Gvqs7ey7jS2ev9J89sGuHHVEwLftLie2ufhq7Z9tx5ygxZhgULMNA==","signatures":[{"sig":"MEUCIQCexd2Q7DT4vAnm6Fu0psgU6cv1vSOt07KpDXh2jVOYJgIgEbEaALBbx1uCcDi8wpJ5g53+z3pvV7nSR+/JUbIBPVE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":184586},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"75d3be535bec56038d278edff136649ceecf1745","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20 --clean","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.8.0_1787931191775_0.2457520952179646","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"bucket","version":"0.9.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.9.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"1472a5d5ae1d447ff23cfa5a5f1467a747bba67b","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.9.0.tgz","fileCount":28,"integrity":"sha512-US7XTwSa3idW1LWshq+27Eyi/253IdPngBch2QUisfH/qyByZZ22682N2/CYk+jbWE8dyAMDCX0Q2jyLCIQgfg==","signatures":[{"sig":"MEQCIAjce6rvc18FFmNHB3ISWhql/dd/pl+1dg2ubZlqRKzWAiB7Tt+khsJGU7sjUQtEOVnlnVPfF/JFWI/YhRXH3IoBjA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIGk39s4lOZl3Ysu+aV1wYUwf2jyR6JghMdSzkggrsWbMAiEAiQXn/b0aEdFmtk2GQxLgjRVViWY6h2+twr5UP6sqqU0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":197233},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"5d253276b70a165da599fb5bb0d76787575e3cdf","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup index.ts s3/index.ts b2/index.ts r2/index.ts fs/index.ts gcs/index.ts azure/index.ts --format esm --dts --out-dir dist --target node20 --clean","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.9.0_1789703420656_0.07331609580712461","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"bucket","version":"0.10.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.10.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"0c4547a9a48205d089feaa09724a4a4622007c08","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.10.0.tgz","fileCount":33,"integrity":"sha512-3MwslWhEqUF7UpVGr1SpLoi1mCwBy6GI/H+RvS2uoFqCLgfl3xSrBWmeVQOzuPJxzMwCeviFcEscEGVk97dSDQ==","signatures":[{"sig":"MEYCIQCE1jSRgbHUr3knaFWkXP2FrjfjQQk6gYqQMJrTTw85jAIhAL9M0Q5dIGa0+THOk6Me8DeZcZSXW7WKWgxY1bz4f3eV","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDTLD7Nd5u14Utp5XnYads/ag1lgXp5DUnZe7PBOXYPGgIhAJSoZZ/pwO1YBrPQoTcJkspw7Srbupm+6siThB2rpWmp","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":196672},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"},"./memory":{"types":"./dist/memory/index.d.ts","import":"./dist/memory/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"4aefa6f6cddc9563d9fb42bd082b16f4ba75b48f","scripts":{"lint":"prettier --check .","test":"npm run lint && bun test --timeout 30000","build":"tsup src/index.ts src/s3/index.ts src/b2/index.ts src/r2/index.ts src/fs/index.ts src/gcs/index.ts src/azure/index.ts src/memory/index.ts --format esm --dts --out-dir dist --target node20 --clean","start":"bun test --watch","prepare":"npm run build","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.10.0_1789798142689_0.11416317073132709","host":"s3://npm-registry-packages-npm-production"}},"0.10.1":{"name":"bucket","version":"0.10.1","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.10.1","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"b67f7a774c1430ce4234f5dd34e760992d5aee78","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.10.1.tgz","fileCount":33,"integrity":"sha512-65uYvT6kg5A2sy1rGtJkK6CJ/LhZLZIlm3NXXmyRnXCve4U9IFel/CoL8ebeq8WQFjuLhIfqzDdzltYi4Dlj7Q==","signatures":[{"sig":"MEUCICRJ3OT8CU0QhNKHKkMFDV13GZtQekzTp2Dlq4mJuYQRAiEAj00EHPf7Mh2xe/C94pww5DKdJe59Y57xSm9swUQlcE8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCACU1y+sWr30UfQIxKeE+1h/mBuean2urmJ/+vkQIT3AIgRD6M7hkWJtg0yvBvz207waSIMCMRNiAremOe4hioBVc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":196983},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./b2":{"types":"./dist/b2/index.d.ts","import":"./dist/b2/index.js"},"./fs":{"types":"./dist/fs/index.d.ts","import":"./dist/fs/index.js"},"./r2":{"types":"./dist/r2/index.d.ts","import":"./dist/r2/index.js"},"./s3":{"types":"./dist/s3/index.d.ts","import":"./dist/s3/index.js"},"./gcs":{"types":"./dist/gcs/index.d.ts","import":"./dist/gcs/index.js"},"./azure":{"types":"./dist/azure/index.d.ts","import":"./dist/azure/index.js"},"./memory":{"types":"./dist/memory/index.d.ts","import":"./dist/memory/index.js"}},"funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"5a03764fc744a5a071fa56496b7d64b5590d6d03","scripts":{"lint":"prettier --check .","test":"npm run lint && npm run typecheck && bun test --timeout 30000","build":"tsup src/index.ts src/s3/index.ts src/b2/index.ts src/r2/index.ts src/fs/index.ts src/gcs/index.ts src/azure/index.ts src/memory/index.ts --format esm --dts --out-dir dist --target node20 --clean","start":"bun test --watch","prepare":"npm run build","typecheck":"tsc --noEmit","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.10.1_1789798827814_0.5675941918091354","host":"s3://npm-registry-packages-npm-production"}},"0.11.0":{"name":"bucket","version":"0.11.0","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.11.0","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"36cb3dbac227d9db302b018c1160d340c83bb50c","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.11.0.tgz","fileCount":4,"integrity":"sha512-OMfWGPy/kpS9hrVKp5G3U0bN3+sdUqZP2RUkhQ/9DeKvvZ0DRRZYmsQXm8rynUmIB/bLebUcBx5r/opbqe7+9A==","signatures":[{"sig":"MEUCIHNU318U3slXQX0XYhPk5H5MDyLm/kKnM279Kekh5jodAiEA2DiEdw+9CVyAJMNxGPlekSztpZ2Xodulg21foZawMaw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQCQrw6+S0+zOiP9s4Ie26WvAxc+n9QwOpzu0Uf5RmGrTwIhAMYAluaGCNcs+FripTRHZxTdiO6JQOleAsWEI13XPidB","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":188514},"main":"./index.js","type":"module","types":"./index.d.ts","funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"2059c2da81f1bf122217416f59853d2987dc3e3e","scripts":{"lint":"prettier --check .","test":"npm run lint && npm run typecheck && bun test --timeout 30000","build":"tsup src/index.ts --format esm --dts --out-dir . --target node20","start":"bun test --watch","prepare":"npm run build","typecheck":"tsc --noEmit","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.11.0_1790000957491_0.9434772960181517","host":"s3://npm-registry-packages-npm-production"}},"0.11.1":{"name":"bucket","version":"0.11.1","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.11.1","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"12d628be23ee766352f73078ef4044b056719f14","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.11.1.tgz","fileCount":4,"integrity":"sha512-rn26efLfFI9mMdKenEG/no1NzCa3jA5Pr0u9C7qqHDDqB8VNMrZlmqyrOgZ/7J7wuhhoYzxTdq/KlmH3N7wQIg==","signatures":[{"sig":"MEUCIQDQ1E49wrYEMUgQTJ/VaKaUze8Q73AiReFo9/UlSTSCqQIgKRy7FoZGGDhj8pcfkoj/8ZUOKvgfH4Klj1hnWiKenYE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIGz4gktmh1rhQ6uj9+R358ZE/k8cNAnMVacOYvRoCg5GAiEA56Xn3H+fJb3qps7b1fVWV0fJCgh/fkPRKEiLWYREta0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":189131},"main":"./index.js","type":"module","types":"./index.d.ts","funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"75645560118c402d55d83e5e4a357264e909bd12","scripts":{"lint":"prettier --check .","test":"npm run lint && npm run typecheck && bun test --timeout 30000","build":"tsup src/index.ts --format esm --dts --out-dir . --target node20","start":"bun test --watch","prepare":"npm run build","typecheck":"tsc --noEmit","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.11.1_1790005429247_0.38869360436238365","host":"s3://npm-registry-packages-npm-production"}},"0.11.2":{"name":"bucket","version":"0.11.2","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.11.2","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"f3fc0a31af15818f491ceeb7df6a2a6c23138961","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.11.2.tgz","fileCount":4,"integrity":"sha512-c0zBsMZmJ3E+ddHCk5d0xOUFh2VWDFNMLfKT6o26+AqTkIH54NHQpfAHe6GBln7baRkB6k90DFhIBppyvbtU7Q==","signatures":[{"sig":"MEQCIE+QtsksAcVt2QV2Ik+jlDklOM9rAh7L37inEXT3bhGnAiACsm/UJW94hvTn+LEpWtdjznRwvM4/xlK8ueu0EfGknQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIDZK+y1qmzRk6omNIMHGkJ5oOzyvo1a6rZRJlFC1YwGSAiA/9xa+TO38VMY2wkhY7gT/pNsj08mCxDKywYgeMbT6+A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":189472},"main":"./index.js","type":"module","types":"./index.d.ts","funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"7c6411e05a17a0b8323466823a7488cc7d9a42b9","scripts":{"lint":"prettier --check .","test":"npm run lint && npm run typecheck && bun test --timeout 30000","build":"tsup src/index.ts --format esm --dts --out-dir . --target node20","start":"bun test --watch","prepare":"npm run build","typecheck":"tsc --noEmit","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.11.2_1790163135532_0.47608366521384893","host":"s3://npm-registry-packages-npm-production"}},"0.11.3":{"name":"bucket","version":"0.11.3","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.11.3","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"c98c0d48e0998f617485df85f0710db93be2f06e","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.11.3.tgz","fileCount":4,"integrity":"sha512-dHH6kS0KzLKfbo58j5+Q0P1Q+opVs0EJBrlf/GKPdODpv+rWTGrrkPVEFh8n1s2AVW5C/nQsJjlUeUWULeqXXw==","signatures":[{"sig":"MEYCIQCIQETDiOxmcjv7m+WBgsk5S/Vqh2kv/6N40NmkRjcVMAIhAMq3OELivv+857kRH3D9/FK7nTfGSlZMsIi1FEK3XTO1","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDOza1B3P6vo0pgsOB+DxQuO4EeLxIvH9E0t2vKGSMo7QIhAJEaAYOVmqrD4B61iusqVCT3BWNTQeb/X80npoSvlBuH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":190135},"main":"./index.js","type":"module","types":"./index.d.ts","funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"613af828c0fc35a97d65aa28ecd15d34df86bff8","scripts":{"lint":"prettier --check .","test":"npm run lint && npm run typecheck && bun test --timeout 30000","build":"tsup src/index.ts --format esm --dts --out-dir . --target node20","start":"bun test --watch","prepare":"npm run build","typecheck":"tsc --noEmit","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.11.3_1790195478841_0.8391095785803204","host":"s3://npm-registry-packages-npm-production"}},"0.11.4":{"name":"bucket","version":"0.11.4","keywords":[],"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","_id":"bucket@0.11.4","maintainers":[{"name":"alexbosworth","email":"alex.bosworth+npm@gmail.com"},{"name":"franciscop","email":"public@francisco.io"}],"homepage":"https://bucketjs.com","bugs":{"url":"https://github.com/franciscop/bucket/issues"},"dist":{"shasum":"c82db0141dc4fa726684c9fe5f61c49757d744e6","tarball":"https://registry.npmjs.org/bucket/-/bucket-0.11.4.tgz","fileCount":4,"integrity":"sha512-YY/hMH2YnCYTGHIueubYGjbTngkJLCYeTyxHR8UR3VKmDUfutjXsEj5iwbKMFtRaBk8fsjUV6n9qz6W4S8TmJg==","signatures":[{"sig":"MEYCIQDSaTjEXUpPfTDiWWefVnLGHInIHT2VKgc+pD8Ckhf//QIhAPThXxDuSzzoXDq4aOMcHh3a0vyvru6Tb6kZ4TXq3Om8","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIAeA76IBRWg6wnSrneKkhf247dtAnt5ztyLU8Cz3VH6NAiAO4il/jdZbyY2vzh5jM6S9GPUxEA1SFovqA3C5BD/wCg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":192794},"main":"./index.js","type":"module","types":"./index.d.ts","funding":"https://www.paypal.me/franciscopresencia/19","gitHead":"85f1e916378417c3f7ee4a30616217418eb72b21","scripts":{"lint":"prettier --check .","test":"npm run lint && npm run typecheck && bun test --timeout 30000","build":"tsup src/index.ts --format esm --dts --out-dir . --target node20","start":"bun test --watch","prepare":"npm run build","typecheck":"tsc --noEmit","test:emulators":"bun --env-file=.env.emulators test/emulators.ts","emulators:setup":"bun --env-file=.env.emulators test/setup-emulators.ts"},"_npmUser":{"name":"franciscop","email":"public@francisco.io"},"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"_npmVersion":"11.6.2","description":"Access any of the popular file storage solutions with a unified API","directories":{},"sideEffects":false,"_nodeVersion":"24.13.0","dependencies":{},"_hasShrinkwrap":false,"devDependencies":{"aws4":"^1.13.2","tsup":"^8.5.1","azurite":"^3.35.0","prettier":"^3.8.1","@types/bun":"^1.3.0","typescript":"^6.0.2","@types/aws4":"^1.11.6","@types/node":"^25.5.2"},"documentation.page":{"menu":{"Github":"https://github.com/franciscop/bucket","Issues":"https://github.com/franciscop/bucket/issues","Get help":"https://superpeer.com/francisco/-/javascript-and-react-help"}},"_npmOperationalInternal":{"tmp":"tmp/bucket_0.11.4_1790196729524_0.9943141031255258","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2011-09-10T08:05:09.652Z","modified":"2026-09-23T20:52:51.378Z","0.0.1":"2011-09-10T08:05:14.449Z","0.1.0":"2026-06-21T16:15:53.132Z","0.1.1":"2026-06-21T16:36:28.387Z","0.2.0":"2026-07-04T06:56:33.699Z","0.3.0":"2026-07-04T13:06:42.428Z","0.4.0":"2026-07-05T04:51:43.493Z","0.5.0":"2026-07-28T09:53:04.392Z","0.6.0":"2026-07-29T00:46:15.797Z","0.7.0":"2026-07-30T17:37:53.257Z","0.7.1":"2026-08-02T15:39:38.250Z","0.8.0":"2026-08-28T15:33:11.927Z","0.9.0":"2026-09-18T03:50:20.739Z","0.10.0":"2026-09-19T06:09:02.805Z","0.10.1":"2026-09-19T06:20:27.898Z","0.11.0":"2026-09-21T14:29:17.672Z","0.11.1":"2026-09-21T15:43:49.323Z","0.11.2":"2026-09-23T11:32:15.619Z","0.11.3":"2026-09-23T20:31:18.941Z","0.11.4":"2026-09-23T20:52:09.638Z"},"bugs":{"url":"https://github.com/franciscop/bucket/issues"},"author":{"url":"https://francisco.io/","name":"Francisco Presencia","email":"public@francisco.io"},"license":"MIT","homepage":"https://bucketjs.com","keywords":[],"repository":{"url":"git+https://github.com/franciscop/bucket.git","type":"git"},"description":"Access any of the popular file storage solutions with a unified API","maintainers":[{"email":"public@francisco.io","name":"franciscop"}],"readme":"# Bucket [![bucket](https://img.shields.io/npm/v/bucket?label=bucket&color=greenlime)](https://www.npmjs.com/package/bucket) [![tests](https://github.com/franciscop/bucket/workflows/tests/badge.svg)](https://github.com/franciscop/bucket/actions)\n\nA small library to talk to any of the popular file storage solutions with a unified API:\n\n```js\nimport { S3 } from \"bucket\"; // or R2, GCS, Azure, B2, FS, Memory\n\nconst bucket = S3(\"bucket-name\", { id, secret });\n\nconst file = bucket.file(\"demo.txt\");\nawait file.write(\"hello world\");\nconsole.log(await file.text());\n```\n\nEvery provider behaves the same, so the code above works unchanged against any of them. That includes a \"filesystem\" bucket treating a local folder as a bucket, and an in-memory one for tests. Because they share an API, a file can be piped straight from one provider into another:\n\n```js\n// More complex example with streams and pipes\nimport { FS, S3 } from \"bucket\";\n\nconst local = FS(\"./public/\");\nconst aws = S3(\"mybucketname\", { id, secret });\n\nconst source = local.file(\"local.txt\").stream();\nconst target = aws.file(\"newfile.txt\").writable();\nawait source.pipeTo(target);\n```\n\n## Getting Started\n\nFirst install the library:\n\n```sh\nnpm install bucket\n```\n\nThen decide which bucket you're going to use, and grab its credentials. Put them in a gitignored `.env`, let's say S3, so put these variables:\n\n```sh\nAWS_ACCESS_KEY_ID=\nAWS_SECRET_ACCESS_KEY=\nAWS_BUCKET=\n# Optional\nAWS_REGION=\nAWS_SESSION_TOKEN=\nAWS_ENDPOINT_URL=\nAWS_PUBLIC_URL=\n```\n\nFinally, you can import and initialize the library:\n\n```ts\nimport { S3 } from \"bucket\";\n\n// Read the variables automatically\nconst bucket = S3(\"bucket-name\");\n\n// Or inject them manually\nconst bucket = S3(\"bucket-name\", {\n  id: \"...\",\n  secret: \"...\",\n  region: \"us-east-1\",\n  url: \"...\",\n});\n```\n\nTo use it, see the next section [Bucket API](#bucket-api), but here's a quick example reading and writing a JSON file:\n\n```ts\nconst data = await bucket.file(\"test.json\").json();\n// data is a JS object here, since we're parsing it as .json()\nawait bucket.file(\"output.json\").write(JSON.stringify(data));\n```\n\n## Bucket API\n\nEach service exports a factory that creates a bucket instance attached to a single bucket:\n\n```js\nS3(\"my-bucket-name\", { id, secret, region });\nS3(); // bucket name and credentials from env vars\n```\n\nThe first argument is always the bucket name; the second is a config object with credentials. All fields fall back to environment variables, so in most setups you can omit them entirely. See [Services](#services) for the env var names and options of each provider.\n\n```js\nimport { S3 } from \"bucket\";\n\nconst bucket = S3(\"my-bucket-name\", {\n  id: \"access-key-id\",\n  secret: \"secret-access-key\",\n  region: \"us-east-1\",\n});\nawait bucket.file(\"hello.txt\").write(\"hello world\");\n```\n\nThere is a single entry point, and every provider is a named export from it:\n\n```js\nimport { S3, FS, Memory } from \"bucket\";\n\nconst aws = S3(\"my-bucket\", { id, secret });\nconst local = FS(\"./uploads\");\nconst fake = Memory(); // for tests\n```\n\nThe three abbreviated names are also exported spelled out, if you prefer reading them: `FS` is `FileSystem`, `R2` is `CloudflareR2`, and `B2` is `BackBlaze`. They are the same function, so pick whichever reads better.\n\nThe default export is the same set as one object, for code that picks a backend at runtime:\n\n```js\nimport bucket from \"bucket\";\n\nconst engine = process.env.STORAGE ?? \"FS\";\nconst files = bucket[engine](\"my-bucket\");\n```\n\nEvery bucket instance has the same methods:\n\n- [`.info()`](#info): display the information about the current bucket.\n- [`.list(filter?)`](#list): return the list of all files in the bucket.\n- [`.scan(filter?)`](#scan): async generator that lazily yields files (streams pages).\n- [`.count(filter?)`](#count): return the Number of items in the bucket.\n- [`.remove(filter)`](#remove): delete all files matching the filter, returning them.\n- [`.folder(path)`](#folder): a Bucket scoped to a path prefix (see below).\n- [`.file(path)`](#file): creates a BucketFile instance for the given path.\n- [`.create(body, options?)`](#create): writes the body under a random file name.\n\n### .info()\n\nRetrieves basic information about the bucket:\n\n```js\nawait bucket.info();\n```\n\nEvery provider resolves to the same `BucketInfo` shape: the provider `type`, the bucket `name`, its base `url`, and the account or credential `id`.\n\n```js\nconst info = await bucket.info();\n// {\n//   id: \"access-key-id\",\n//   name: \"my-bucket-name\",\n//   type: \"S3\",\n//   url: \"https://my-bucket-name.s3.us-east-1.amazonaws.com\"\n// }\n```\n\n### .list()\n\nReturns all the files in the bucket as an array of `BucketFile`:\n\n```js\nawait bucket.list();\nawait bucket.list(/\\.jpe?g$/);\n```\n\nAccepts an optional `RegExp` to filter by pattern; to scope to a path prefix, use [`.folder()`](#folder), whose filters match below the folder.\n\n```js\nconst logs = await bucket.folder(\"logs\").list(/\\.log$/);\nconsole.log(logs.map((file) => file.path)); // [\"logs/access.log\", ...]\n```\n\nYou can also iterate the bucket directly with `for await`, which streams pages lazily and stops fetching if you `break`:\n\n```js\nfor await (const file of bucket) {\n  if (file.name.endsWith(\".txt\")) {\n    console.log(await file.text());\n  }\n}\n```\n\n#### Related methods\n\n- [`.scan(filter?)`](#scan): stream the listing page by page instead of buffering it.\n- [`.count(filter?)`](#count): just the number of matches.\n\n### .scan()\n\nLazily yields the files in the bucket, fetching provider pages as they are consumed:\n\n```js\nfor await (const file of bucket.scan()) { ... }\nfor await (const file of bucket.scan(/\\.log$/)) { ... }\n```\n\nUnlike the bare `for await (const f of bucket)` form it accepts an optional `RegExp` filter, so you can filter while streaming. That is ideal for very large buckets or when you may stop early; `list()` is simply `scan()` collected into an array, and the bare iteration delegates to it.\n\n```js\nfor await (const file of bucket.scan(/\\.log$/)) {\n  if (await shouldStop(file)) break; // no further pages are fetched\n}\n```\n\n#### Related methods\n\n- [`.list(filter?)`](#list): the whole listing as an array.\n\n### .count()\n\nCounts the files in the bucket:\n\n```js\nawait bucket.count();\nawait bucket.count(/\\.jpe?g$/);\n```\n\nAccepts the same filter as `.list()`.\n\n```js\nconst images = await bucket.count(/\\.jpe?g$/);\nconsole.log(`There are ${images} images`);\n```\n\n#### Related methods\n\n- [`.list(filter?)`](#list): the matching files themselves.\n\n### .remove()\n\nDeletes every file matching the filter, returning the deleted files:\n\n```js\nawait bucket.remove(/\\.tmp$/);\nawait bucket.remove(/./); // empties the bucket\n```\n\nThe filter is required and must be a `RegExp`. Anything else, including no\nargument at all and a plain string, throws a `BucketError` with code\n`\"INVALID_FILTER\"` before a single request is made. This is deliberate: an\n`undefined` variable can never silently become \"delete everything\", and strings\nstay free to mean something narrower later.\n\nScope it to a folder instead of writing the prefix into the pattern:\n\n```js\nconst deleted = await bucket.folder(\"cache\").remove(/./); // everything under cache/\nconsole.log(`removed ${deleted.length} files`);\n```\n\nOn S3 and R2 the deletion is batched into as few requests as possible.\n\nRemoving is about the path, not the bytes. Afterwards the path stops resolving\neverywhere: `.exists()` is `false`, reads throw `NOT_FOUND`, `.info()` is\n`null`, and it is gone from `.list()`, `.scan()` and `.count()`. Removing the\nsame path twice is a no-op, not an error.\n\nOn a versioned bucket the earlier versions are kept, so removal is reversible\nand keeps costing storage until a lifecycle rule expires them. Backblaze B2 is\nalways versioned: `.remove()` hides the file rather than deleting a version,\nwhich stops the path resolving without uncovering the version before it.\n\n#### Related methods\n\n- [`file.remove()`](#fileremove): delete a single file.\n\n### .folder()\n\nReturns a `Bucket` scoped to a path prefix, synchronously and without any network requests:\n\n```js\nbucket.folder(\"public\");\nbucket.folder(\"../\"); // navigate to the parent folder\n```\n\nIt behaves like any other bucket, but every operation is confined to that folder: `.file()` resolves names inside it, and `.list()`, `.count()`, `.remove()`, and iteration only see files within it. Folders nest, and the prefix is normalized (`\"./public/\"` and `\"public\"` are equivalent). `folder(\"../\")` navigates to the parent folder and `folder(\"/\")` returns to the bucket root; navigation is bounded by the bucket root, so a path that would climb above it throws a `BucketError` with code `\"INVALID_PATH\"`.\n\n```js\nconst assets = bucket.folder(\"public\");\nawait assets.file(\"favicon.ico\").write(icon); // stored at \"public/favicon.ico\"\nconst styles = await assets.folder(\"css\").list(); // only files under \"public/css/\"\n```\n\nFile paths are always the full path from the bucket root, on every provider including the filesystem, so `assets.file(\"favicon.ico\").path` is `\"public/favicon.ico\"`. A `RegExp` passed to a folder's `.list()` is matched against the path below the folder, so `assets.list(/^favicon/)` matches `public/favicon.ico`.\n\n#### Related methods\n\n- [`.file(path)`](#file): a handle to a single file.\n- [`.list(filter?)`](#list): list the folder's contents.\n\n### .file()\n\nCreates a [`BucketFile`](#file-api) handle for the given path, synchronously and without any network requests:\n\n```js\nbucket.file(\"hello.txt\");\nbucket.file(\"photos/avatar.jpg\");\n```\n\nThe handle mirrors the `Blob` read API (`.text()`, `.json()`, `.arrayBuffer()`, `.bytes()`, `.blob()`, `.stream()`), but it is a **lazy remote handle, not a `Blob` itself**, so to hand it to `FormData`, `Response`, or `fetch`, materialize it first with `await file.blob()` (buffered) or `file.stream()` (streaming). See [Guides](#guides). It does not check whether the file exists, and it has two properties set immediately:\n\n- `name`: the filename without the directory, e.g. `\"avatar.jpg\"`\n- `path`: the full path within the bucket, e.g. `\"photos/avatar.jpg\"`\n\n```js\nconst file = bucket.file(\"photos/avatar.jpg\");\nconsole.log(file.name); // \"avatar.jpg\"\nconsole.log(await file.text()); // or .json(), or .stream(), etc\n```\n\nPaths are resolved within the bucket: `.` and `..` segments are applied and a leading `/` means the bucket root. The resolved path must stay inside the bucket; anything else throws a `BucketError` with code `\"INVALID_PATH\"`:\n\n```js\nbucket.file(\"photos/../a.txt\"); // same file as bucket.file(\"a.txt\")\nbucket.file(\"/a.txt\"); // leading \"/\" means the bucket root\nbucket.file(\"../outside.txt\"); // throws BucketError INVALID_PATH\n```\n\n#### Related methods\n\n- [`.folder(path)`](#folder): scope a whole bucket to a prefix instead.\n- [`.create(body, options?)`](#create): let the library pick the name.\n\n### .create()\n\nWrites the body under a random file name, resolving to the new file:\n\n```js\nawait bucket.create(body);\nawait bucket.create(body, { type: \"image/png\" });\n```\n\nThe name is a 21-character alphanumeric id, unique enough that you never need to check for collisions. Everything else works like [`file.write()`](#filewritebody-options), including the options. Combined with [`.folder()`](#folder) it is the usual way to accept an upload without trusting a client-supplied name:\n\n```js\nconst file = await bucket.folder(\"avatars\").create(upload);\nfile.path; // \"avatars/V1StGXR8Z5jdHi6BmyT2a.png\"\nawait db.user.update({ avatar: file.path });\n```\n\nThe extension comes from `type` when you pass one, and otherwise from a name the body carries of its own, which means a `File` or a file from any bucket. A body with neither (a string, a `Buffer`, a bare `Blob`, a stream) gets an id with no extension, since guessing one from the bytes would be wrong as often as right.\n\n```js\n// A multipart parser gives you the type but no name worth trusting\nawait bucket.create(part.body, { type: part.mimetype }); // fO0k2lRz8qWpAx3cVbNmY.png\n```\n\n#### Related methods\n\n- [`.file(path)`](#file): choose the name yourself.\n\n## File API\n\nThe file handle, returned by [`bucket.file()`](#file) and as every item of [`list()`](#list) and [`scan()`](#scan). The type is named `BucketFile` to differentiate it from the browser's native `File` object. It has `name` and `path` set synchronously, and everything else is a method:\n\n- **Info**\n  - `.name`: the filename without the directory.\n  - `.path`: the full path within the bucket.\n  - `.info()`: returns the file's metadata (`size`, `type`, `modified`, `version`, `metadata`), or `null` if the file does not exist.\n  - `.exists()`: checks whether a file exists, returning `true` if it does.\n\n- **Read**\n  - `.text()`: read the contents of the file as a string.\n  - `.json()`: read the contents of the file as parsed JSON.\n  - `.arrayBuffer()`: read the contents of the file as an `ArrayBuffer`.\n  - `.blob()`: read the contents of the file as a `Blob`.\n  - `.bytes()`: read the contents of the file as a `Uint8Array`.\n  - `.slice(start, end?)`: a read-only view of a byte range.\n  - `.stream()`: returns a web `ReadableStream`.\n  - `.nodeReadable()`: returns a Node.js `Readable` stream.\n\n- **Write** (each resolves to a file: the one written, copied, moved, renamed, or deleted)\n  - `.write(body, options?)`: writes content to the file.\n  - `.copyTo(path)`: creates a duplicate of the file with a different name.\n  - `.moveTo(path)`: moves the file to a different location.\n  - `.rename(name)`: renames the file within the same folder.\n  - `.remove()`: deletes the file (alias: `.unlink()`).\n  - `.writable()`: returns a web `WritableStream`.\n  - `.nodeWritable()`: returns a Node.js `Writable` stream.\n\n- **URLs**\n  - `.publicUrl()`: the permanent public URL of the file (or `null`).\n  - `.signedUrl(opts)`: a time-limited download URL.\n  - `.uploadUrl(opts)`: a time-limited upload URL.\n\nURL availability per provider:\n\n|            | `publicUrl()` | `signedUrl()` | `uploadUrl()` |\n| ---------- | :-----------: | :-----------: | :-----------: |\n| **S3**     |      ✅       |      ✅       |      ✅       |\n| **GCS**    |      ✅       |      ✅       |      ✅       |\n| **Azure**  |      ✅       |      ✅       |      ✅       |\n| **B2**     |      ✅       |      ✅       |      ❌       |\n| **R2**     |      ⚙️       |      ✅       |      ✅       |\n| **FS**     |      ⚙️       |      ❌       |      ❌       |\n| **Memory** |      ⚙️       |      ❌       |      ❌       |\n\n- ✅: returns the provider's own URL. For `publicUrl()` that URL only answers if the bucket or object is publicly readable. Signing needs a key: GCS returns `null` without a [service-account private key](#google-cloud-storage), Azure without an account key (managed identity).\n- ⚙️: returns a URL only when you set the [`publicUrl` config option](#filepublicurl), and `null` otherwise. These three have no address of their own: R2's storage endpoint rejects unsigned requests, and nothing serves a local directory or an in-memory Map. Setting `publicUrl` works on every provider, not just these.\n- ❌: always returns `null`. B2 uploads require auth headers, so a standalone upload URL cannot exist (use `.write()`), and there is nothing to sign for a local directory or an in-memory Map.\n\n### file.info()\n\nRetrieves the file's metadata, or `null` when the file does not exist:\n\n```js\nawait bucket.file(\"photo.jpg\").info();\n// null, or:\n// {\n//   size: 175888,        // bytes; respects .slice() ranges\n//   type: \"image/jpeg\",  // MIME type, null when unknown\n//   modified: Date,      // when the content was last written\n//   version: \"...\",      // provider version id, or null (see below)\n//   metadata: {},        // custom metadata, lowercase keys\n//   cacheControl: \"...\", // only when the file was written with one\n//   disposition: \"...\"   // only when the file was written with one\n// }\n```\n\nOnly a missing file resolves to `null`; other failures, like permissions or network errors, still throw. The `version` field is the provider's version identifier: the fileId on Backblaze, `generation` on GCS, `VersionId` on S3 and Azure when the bucket has versioning enabled, and `null` otherwise (always `null` for the local filesystem). The `metadata` field holds the custom key-value metadata set with `write(..., { metadata })`; keys are normalized to lowercase on both write and read so they round-trip consistently. `cacheControl` and `disposition` report what the file was written with, and are absent when it was written without them.\n\nEverything `write()` accepts round-trips through `info()` on every provider except the local filesystem, which has no metadata store and so reports `metadata: {}`, no `cacheControl` and no `disposition` whatever you wrote.\n\n```js\nconst info = await bucket.file(\"photo.jpg\").info();\nif (!info) throw new Error(\"photo.jpg is missing\");\nconsole.log(`${info.size} bytes of ${info.type}, written ${info.modified}`);\n```\n\n#### Related methods\n\n- [`.exists()`](#fileexists): just the boolean.\n\n### file.exists()\n\nChecks whether the file exists:\n\n```js\nawait bucket.file(\"photo.jpg\").exists(); // true or false\n```\n\nShorthand for `(await file.info()) !== null`.\n\n```js\nconst photo = bucket.file(\"photo.jpg\");\nif (await photo.exists()) { ... }\n```\n\n#### Related methods\n\n- [`.info()`](#fileinfo): the full metadata.\n\n### file.text()\n\nReads the full contents of the file, decoded as UTF-8:\n\n```js\nawait bucket.file(\"readme.txt\").text();\n```\n\nMatches the `Blob`/`Response` API. Throws `NOT_FOUND` if the file does not exist.\n\n### file.json()\n\nReads the file contents parsed as JSON:\n\n```js\nawait bucket.file(\"config.json\").json();\n```\n\nMatches the `Blob`/`Response` API. Throws `NOT_FOUND` if the file does not exist.\n\n### file.arrayBuffer()\n\nReads the raw binary contents as an `ArrayBuffer`:\n\n```js\nawait bucket.file(\"photo.jpg\").arrayBuffer();\n```\n\nWorks in any runtime (see [Which runtimes are supported?](#which-runtimes-are-supported)). Matches the `Blob`/`Response` API.\n\n```js\nconst buf = await bucket.file(\"photo.jpg\").arrayBuffer();\nconst data = new Uint8Array(buf); // or Buffer.from(buf) in Node.js\n```\n\n### file.blob()\n\nReads the file contents as a `Blob`:\n\n```js\nawait bucket.file(\"photo.jpg\").blob();\n```\n\nUseful for passing to `FormData`, `Response`, or browser APIs; the Blob carries the file's content-type.\n\n```js\nconst blob = await bucket.file(\"photo.jpg\").blob();\nconst formData = new FormData();\nformData.append(\"photo\", blob, \"photo.jpg\");\n```\n\n### file.bytes()\n\nReads the raw binary contents as a `Uint8Array`:\n\n```js\nawait bucket.file(\"photo.jpg\").bytes();\n```\n\nWorks in any runtime. Matches the `Blob`/`Response` API.\n\n### file.slice()\n\nReturns a **read-only view of a byte range** of the file, synchronously:\n\n```js\nbucket.file(\"big.csv\").slice(0, 1024); // first 1 KiB\nbucket.file(\"big.csv\").slice(1024); // from 1 KiB to EOF\n```\n\nIt works like [`Blob.slice()`](https://developer.mozilla.org/docs/Web/API/Blob/slice): `end` is exclusive and defaults to the end of the file. It returns a `BucketFile`, so every read method (`.text()`, `.bytes()`, `.arrayBuffer()`, `.blob()`, `.stream()`, `.nodeReadable()`) reads only that range. Remote providers translate it to an HTTP `Range` request; the filesystem reads only those bytes. Ranges are clamped to the file size and compose (`file.slice(0, 100).slice(10, 20)`).\n\n```js\nconst head = await bucket.file(\"big.csv\").slice(0, 1024).bytes();\n```\n\n`info().size` on a slice reports the **clamped slice length** (the bytes this view yields), while every other field (`type`, `modified`, `version`, `metadata`) still describes the underlying file:\n\n```js\n(await bucket.file(\"data.txt\").slice(0, 4).info()).size; // 4\n```\n\nThis makes range serving a one-liner, e.g. answering an HTTP `Range` request:\n\n```js\nconst [start, end] = parseRange(req.headers.get(\"range\")); // inclusive\nconst info = await file.info();\nif (!info) return new Response(\"Not Found\", { status: 404 });\nconst { size, type } = info;\nreturn new Response(file.slice(start, end + 1).stream(), {\n  status: 206,\n  headers: {\n    \"Content-Type\": type,\n    \"Content-Range\": `bytes ${start}-${end}/${size}`,\n    \"Content-Length\": String(end - start + 1),\n  },\n});\n```\n\n### file.write(body, options?)\n\nWrites content to the file, replacing anything already there:\n\n```js\nawait file.write(\"hello world\"); // string\nawait file.write(new Uint8Array([1, 2, 3])); // Uint8Array or Buffer\nawait file.write(blob); // Blob\nawait file.write(stream); // web ReadableStream or Node.js Readable\nawait file.write(bucket.file(\"original.txt\")); // another BucketFile (copies it)\n```\n\nIntermediate directories are created automatically. **Content-type** is inferred from the file extension (e.g. `.jpg` → `image/jpeg`, `.json` → `application/json`). You can override it and set other metadata through the optional second argument, where `type` takes either a MIME type or an extension (`\"image/png\"`, `\"png\"` and `\".png\"` are equivalent):\n\n| Option         | Type                     | Description                                                                |\n| -------------- | ------------------------ | -------------------------------------------------------------------------- |\n| `type`         | `string`                 | MIME type or extension (overrides auto-detection)                          |\n| `cacheControl` | `string`                 | `Cache-Control` header value, e.g. `\"public, max-age=31536000\"`            |\n| `disposition`  | `string`                 | `Content-Disposition` header value, e.g. `\"attachment; filename=file.pdf\"` |\n| `metadata`     | `Record<string, string>` | Provider-specific key/value metadata                                       |\n\n```js\nawait bucket.file(\"image.jpg\").write(data, {\n  type: \"image/jpeg\",\n  cacheControl: \"public, max-age=31536000\",\n  disposition: \"inline\",\n  metadata: { author: \"alice\" },\n});\n```\n\n> **Note:** The filesystem has no metadata store, so it ignores every option here except the content itself. Use the [Memory provider](#memory) when developing against metadata: it is the one provider that ignores nothing.\n\nSize is not something you have to think about: anything past ~8 MiB is chunked internally, in bounded memory. See [Large file uploads](#large-file-uploads).\n\nIt resolves to the file itself, so a write can be the start of a chain:\n\n```js\nconst file = await bucket.file(\"notes.txt\").write(\"hello\");\nconsole.log(await file.text()); // \"hello\"\n```\n\n#### Related methods\n\n- [`bucket.create(body, options?)`](#create): write under a generated name.\n\n### file.copyTo(path)\n\nCreates a duplicate of the file at a new path, keeping the original, and resolves to the copy:\n\n```js\nawait file.copyTo(\"backup/photo.jpg\");\nawait file.copyTo(\"../published/\"); // trailing \"/\" keeps the file name\nawait file.copyTo(otherBucket.file(\"photo.jpg\")); // into another bucket\n```\n\nThe string destination resolves against the bucket or folder the file came from: `\"../\"` navigates toward the bucket root, a leading `/` means the bucket root, and a trailing `/` copies into that folder keeping the file name. Destinations outside the bucket throw a `BucketError` with code `\"INVALID_PATH\"`. Pass a `BucketFile` instead of a string to copy into another bucket, even one from a different provider.\n\n```js\nconst doc = bucket.folder(\"drafts\").file(\"doc.md\");\nconst copy = await doc.copyTo(\"copy.md\"); // drafts/copy.md\nconst published = await doc.copyTo(\"../published/\"); // published/doc.md\npublished.path; // \"published/doc.md\"\n```\n\n#### Related methods\n\n- [`.moveTo(path)`](#filemovetopath): same, removing the original.\n- [`.rename(name)`](#filerenamename): change only the name.\n\n### file.moveTo(path)\n\nMoves the file to a new path, removing the original, and resolves to the moved file:\n\n```js\nawait file.moveTo(\"photos/avatar.jpg\");\nawait file.moveTo(otherBucket.file(\"avatar.jpg\")); // into another bucket\n```\n\nThe destination follows the same rules as [`copyTo()`](#filecopytopath).\n\n```js\nconst avatar = await bucket.file(\"tmp/upload.jpg\").moveTo(\"photos/avatar.jpg\");\navatar.path; // \"photos/avatar.jpg\"\n```\n\nA move is a copy followed by a [`remove()`](#fileremove) of the source, so on a versioned bucket it leaves the source path's history behind exactly as a plain remove would. The bytes live at the new path and the old path stops resolving, but its earlier versions stay until a lifecycle rule expires them. On the filesystem it is a single atomic rename instead, with nothing left behind.\n\n#### Related methods\n\n- [`.copyTo(path)`](#filecopytopath): same, keeping the original.\n\n### file.rename(name)\n\nRenames the file within the same directory, resolving to the renamed file:\n\n```js\nconst renamed = await bucket.file(\"photos/old-name.jpg\").rename(\"new-name.jpg\");\nrenamed.path; // \"photos/new-name.jpg\"\n```\n\nThrows if `name` is empty, `\".\"`, `\"..\"`, or contains a `/`; use `.moveTo()` to change directories.\n\n#### Related methods\n\n- [`.moveTo(path)`](#filemovetopath): move anywhere in the bucket.\n\n### file.remove()\n\nDeletes the file, resolving to it:\n\n```js\nconst gone = await bucket.file(\"temp.txt\").remove();\ngone.path; // \"temp.txt\", useful for logging what was deleted\n```\n\nIt takes no arguments. Afterwards the path stops resolving: `.exists()` is\n`false`, reads throw `NOT_FOUND`, and `.info()` is `null`. Removing a file that\nis already gone is a no-op, not an error.\n\nOn a versioned bucket the earlier versions are kept, so removal is reversible\nand keeps costing storage until a lifecycle rule expires them. Backblaze B2 is\nalways versioned: `.remove()` hides the file rather than deleting a version,\nwhich stops the path resolving without uncovering the version before it.\n\nAlias: `.unlink()`, matching Bun's `S3File`.\n\n#### Related methods\n\n- [`bucket.remove(filter)`](#remove): delete many files at once.\n\n### file.stream()\n\nReturns a web `ReadableStream<Uint8Array>` of the file contents, synchronously:\n\n```js\nbucket.file(\"video.mp4\").stream();\n```\n\nWorks in any runtime and matches `Blob.stream()`, so it plugs straight into `Response`, `pipeTo()`, and other web APIs.\n\n```js\nconst stream = bucket.file(\"video.mp4\").stream();\nreturn new Response(stream); // stream it straight to an HTTP response\n```\n\n#### Related methods\n\n- [`.nodeReadable()`](#filenodereadable): the Node.js flavor.\n\n### file.nodeReadable()\n\nReturns a Node.js `Readable` stream of the file contents:\n\n```js\nbucket.file(\"data.csv\").nodeReadable();\n```\n\nUse it with Node.js `pipeline()` or any library that expects a Node stream.\n\n```js\nimport { pipeline } from \"node:stream/promises\";\nimport { createGzip } from \"node:zlib\";\n\nawait pipeline(\n  bucket.file(\"data.csv\").nodeReadable(),\n  createGzip(),\n  bucket.file(\"data.csv.gz\").nodeWritable(),\n);\n```\n\n#### Related methods\n\n- [`.stream()`](#filestream): the web flavor.\n\n### file.writable()\n\nReturns a web `WritableStream<Uint8Array>` that writes to the file, synchronously:\n\n```js\nbucket.file(\"output.txt\").writable();\n```\n\nUse it as the target of `.pipeTo()` from any web `ReadableStream`. The stream uploads in ~8 MiB chunks with backpressure, so arbitrarily large files upload with constant memory.\n\n```js\nconst stream = bucket.file(\"output.txt\").writable();\nawait readableStream.pipeTo(stream);\n```\n\n#### Related methods\n\n- [`.nodeWritable()`](#filenodewritable): the Node.js flavor.\n\n### file.nodeWritable()\n\nReturns a Node.js `Writable` stream that writes to the file:\n\n```js\nbucket.file(\"output.txt\").nodeWritable();\n```\n\nUse it with Node.js `pipeline()` or any library that writes to a Node stream.\n\n```js\nimport { pipeline } from \"node:stream/promises\";\n\nawait pipeline(\n  bucket.file(\"input.txt\").nodeReadable(),\n  bucket.file(\"output.txt\").nodeWritable(),\n);\n```\n\n#### Related methods\n\n- [`.writable()`](#filewritable): the web flavor.\n\n### file.publicUrl()\n\nRetrieves the file's permanent, unauthenticated URL, or `null`:\n\n```js\nawait bucket.file(\"logo.png\").publicUrl();\n// \"https://my-bucket.s3.us-east-1.amazonaws.com/logo.png\" or null\n```\n\nEvery provider takes a `publicUrl` config option: the origin the bucket is served from. Set it and `publicUrl()` returns `${publicUrl}/${file.path}`, which is how you put a CDN in front of a bucket (CloudFront over S3, Front Door over Azure, nginx over a local directory) and how dev and production return working URLs from the same code:\n\n```js\nconst bucket = dev\n  ? FS(\"./public\", { publicUrl: \"http://localhost:3000/static\" })\n  : S3(\"my-bucket\", { publicUrl: \"https://cdn.example.com\" });\n\nawait bucket.file(\"logo.png\").publicUrl();\n// dev:  \"http://localhost:3000/static/logo.png\"\n// prod: \"https://cdn.example.com/logo.png\"\n```\n\nIt is a declaration of where the bucket is served, not a promise that the URL resolves: the library does not serve the files, and a CDN pointed at the wrong bucket produces a wrong URL just as a local directory nobody is serving does.\n\nWithout it you get the provider's canonical address, which only answers if the bucket or object is publicly readable, and `null` where there is no such address: the local filesystem, and R2 (whose storage endpoint rejects unsigned requests). See the availability table at the top of this chapter for a per-provider summary.\n\n```js\n// Serve a public URL when available, falling back to a temporary signed one:\nconst src =\n  (await file.publicUrl()) ?? (await file.signedUrl({ expires: \"1h\" }));\n```\n\n#### Related methods\n\n- [`.signedUrl(opts)`](#filesignedurlopts): a time-limited URL for private files.\n\n### file.signedUrl(opts)\n\nCreates a time-limited signed URL to download the file:\n\n```js\nawait file.signedUrl({ expires: 3600 }); // seconds\nawait file.signedUrl({ expires: \"15min\" }); // or a duration string\n```\n\nThe URL is cryptographically signed with your credentials and grants anyone holding it read access until it expires, so a private object can be shared without opening the bucket. Returns `null` when the credentials cannot sign: on GCS without a [service-account private key](#google-cloud-storage), on Azure when authenticating with managed identity instead of an account key, and always on the filesystem and in-memory providers, which have nothing to sign for.\n\n```js\nconst url = await bucket.file(\"invoice.pdf\").signedUrl({ expires: \"15min\" });\nawait sendEmail({ to: user.email, link: url });\n```\n\n#### Related methods\n\n- [`.uploadUrl(opts)`](#fileuploadurlopts): the same for uploads.\n- [`.publicUrl()`](#filepublicurl): the permanent address of public files.\n\n### file.uploadUrl(opts)\n\nCreates a time-limited signed URL that accepts a `PUT` upload to this path:\n\n```js\nawait file.uploadUrl({ expires: 300 }); // seconds\nawait file.uploadUrl({ expires: \"5min\" }); // or a duration string\n```\n\nIt lets a browser upload directly to the bucket without ever seeing your credentials. Same `expires` and `null` rules as `signedUrl()`, plus `null` on Backblaze, whose API has no standalone upload URLs (use `.write()` instead).\n\n```js\n// Server: hand the browser a one-off upload address\nconst url = await bucket.file(`uploads/${crypto.randomUUID()}.jpg`).uploadUrl({\n  expires: \"5min\",\n});\n\n// Browser: upload straight to the bucket\nawait fetch(url, { method: \"PUT\", body: fileInput.files[0] });\n```\n\n#### Related methods\n\n- [`.signedUrl(opts)`](#filesignedurlopts): the same for downloads.\n- [`.write(body)`](#filewritebody-options): upload through your server instead.\n\n## Services\n\nAll services share the same API. The only difference is how you initialize the bucket. The small differences are noted in each section, please familiarize yourself with the ones you use:\n\n### Filesystem FS\n\nTreats a local folder as a bucket. Useful for development, testing, or when you just want a consistent file API over local disk.\n\n```js\nimport { FS } from \"bucket\"; // also exported as FileSystem\n\nconst bucket = FS(\"./my-folder\");\n```\n\nThe path is resolved relative to the current working directory. No credentials needed.\n\nIt takes one option, `publicUrl` (or `FS_PUBLIC_URL`): the origin whatever serves the directory is reachable at, so `file.publicUrl()` returns a working URL in development instead of `null`.\n\n```js\nFS(\"./public\", { publicUrl: \"http://localhost:3000/static\" });\n```\n\nPaths are bucket-relative, exactly like the remote providers: a leading `/` means the bucket root (the folder above), never the filesystem root, and `file.path` is the path within the bucket. The real location on disk is `join(root, file.path)`. Nothing ever resolves outside the root folder; escapes throw a `BucketError` with code `\"INVALID_PATH\"`. The check is lexical: a symlink inside the folder that points outside is not caught.\n\nAs a safety net, passing the bucket's own OS path back in throws instead of silently nesting: `FS(\"/data\").file(\"/data/a.png\")` is almost always a mistake for `file(\"a.png\")`, so it throws `INVALID_PATH` with the suggested fix rather than creating `/data/data/a.png`.\n\nStreaming writes go to a temporary `.tmp-` sibling and are renamed into place on completion, so a file is never observable half-written; `list()` skips these temp entries.\n\n### Memory\n\nAn in-memory bucket backed by a `Map`, for tests: fast, no disk, nothing to clean up, and isolated per instance.\n\n```js\nimport { Memory } from \"bucket\";\n\nconst bucket = Memory();\nawait bucket.file(\"hello.txt\").write(\"hello\");\n```\n\nIt is for tests only. It is not a cache, not a scratch bucket, and not for production: the data lives in one process's heap, dies with it, and is never shared. Two `Memory()` calls are two separate buckets, which is what lets tests run in parallel without colliding.\n\nIt keeps everything a write is given, exactly as the remote providers do: `type`, `metadata`, `cacheControl` and `disposition` all round-trip through `info()`. The filesystem is the one provider that drops them, so develop against Memory rather than `FS` if you use metadata:\n\n```js\nconst file = await bucket.file(\"report.txt\").write(\"a,b\", {\n  type: \"text/csv\",\n  metadata: { owner: \"ana\" },\n});\n\n(await file.info()).type; // \"text/csv\", not \"text/plain\"\n```\n\nEverything else matches the remote providers exactly: folders and path resolution, the error codes, `AbortSignal` support, both stream flavours, and `publicUrl` (which returns `null` unless you set the option, since nothing serves these bytes). `signedUrl()` and `uploadUrl()` are always `null`: there is no endpoint to sign for. Nothing is versioned, so `remove()` deletes.\n\n| Option      | Env var             |\n| ----------- | ------------------- |\n| `publicUrl` | `MEMORY_PUBLIC_URL` |\n\n### Backblaze B2\n\n```js\nimport { B2 } from \"bucket\"; // also exported as BackBlaze\n\nconst bucket = B2(\"my-bucket-name\", {\n  id: \"...\", // Application Key ID\n  secret: \"...\", // Application Key\n});\n```\n\nBoth a master key and a key restricted to one bucket work. A restricted key already identifies its bucket, so the name can be omitted; a key that is not restricted needs the `listBuckets` capability, since the bucket is resolved by name when authenticating.\n\nEnvironment variable fallbacks:\n\n| Option      | Env var                 |\n| ----------- | ----------------------- |\n| bucket name | `B2_BUCKET`             |\n| `id`        | `B2_APPLICATION_KEY_ID` |\n| `secret`    | `B2_APPLICATION_KEY`    |\n| `publicUrl` | `B2_PUBLIC_URL`         |\n\n### AWS S3\n\n```js\nimport { S3 } from \"bucket\";\n\nconst bucket = S3(\"my-bucket-name\", {\n  id: \"...\", // Access Key ID\n  secret: \"...\", // Secret Access Key\n  region: \"us-east-1\", // defaults to us-east-1\n  url: \"...\", // optional: endpoint, without the bucket\n});\n```\n\nEnvironment variable fallbacks:\n\n| Option         | Env var                 |\n| -------------- | ----------------------- |\n| bucket name    | `AWS_BUCKET`            |\n| `id`           | `AWS_ACCESS_KEY_ID`     |\n| `secret`       | `AWS_SECRET_ACCESS_KEY` |\n| `region`       | `AWS_REGION`            |\n| `sessionToken` | `AWS_SESSION_TOKEN`     |\n| `url`          | `AWS_ENDPOINT_URL`      |\n| `publicUrl`    | `AWS_PUBLIC_URL`        |\n\n`sessionToken` is the third part of a temporary STS credential. You rarely set it by hand: leave `id` and `secret` unset and the credentials are resolved for you, from the environment variables on Lambda, from the container credentials endpoint on ECS, and from the instance metadata service (IMDSv2) on EC2.\n\nThe `url` option is the endpoint **without** the bucket, which lets you point at any S3-compatible service:\n\n```js\nS3(\"my-bucket\", { url: \"http://127.0.0.1:9000\" });\n// requests go to http://127.0.0.1:9000/my-bucket/<key>\n```\n\nWith no `url`, requests use AWS's virtual-hosted endpoint, `https://my-bucket.s3.<region>.amazonaws.com`. With one, the bucket is appended as a path segment, which is what MinIO, DigitalOcean Spaces and Ceph expect. Leave it unset for real AWS: S3 has deprecated path-style addressing.\n\n### Cloudflare R2\n\n```js\nimport { R2 } from \"bucket\"; // also exported as CloudflareR2\n\nconst bucket = R2(\"my-bucket\", {\n  id: \"...\", // Access Key ID\n  secret: \"...\", // Secret Access Key\n  account: \"...\", // Cloudflare account id\n});\n```\n\nThe endpoint is derived from `account`: `https://<account>.r2.cloudflarestorage.com/my-bucket`. Pass `url` instead for a custom endpoint or an emulator, without the bucket name, which gets appended. Giving both is fine if they agree; if they disagree, or if neither is set, the constructor throws a `BucketError` with code `\"INVALID_CONFIG\"`.\n\nR2's storage endpoint is never publicly readable, so `file.publicUrl()` returns `null` unless you set `publicUrl` to the bucket's public domain (the `r2.dev` subdomain or a custom domain configured in Cloudflare):\n\n```js\nconst bucket = R2(\"my-bucket\", { publicUrl: \"https://cdn.example.com\" });\nawait bucket.file(\"logo.png\").publicUrl();\n// \"https://cdn.example.com/logo.png\"\n```\n\nEnvironment variable fallbacks:\n\n| Option         | Env var                |\n| -------------- | ---------------------- |\n| bucket name    | `R2_BUCKET`            |\n| `account`      | `R2_ACCOUNT_ID`        |\n| `id`           | `R2_ACCESS_KEY_ID`     |\n| `secret`       | `R2_SECRET_ACCESS_KEY` |\n| `region`       | `R2_REGION`            |\n| `sessionToken` | `R2_SESSION_TOKEN`     |\n| `url`          | `R2_URL`               |\n| `publicUrl`    | `R2_PUBLIC_URL`        |\n\n### Google Cloud Storage\n\n```js\nimport { GCS } from \"bucket\";\n\nconst bucket = GCS(\"my-bucket\");\n```\n\nCredentials are resolved automatically, in order:\n\n1. `GOOGLE_APPLICATION_CREDENTIALS` (path to a service-account JSON file)\n2. `GCS_CLIENT_EMAIL` + `GCS_PRIVATE_KEY`\n3. The GCP metadata server (Cloud Run, GKE, Compute Engine)\n\n| Option        | Env var                          |\n| ------------- | -------------------------------- |\n| bucket name   | `GCS_BUCKET`                     |\n| service email | `GCS_CLIENT_EMAIL`               |\n| private key   | `GCS_PRIVATE_KEY`                |\n| credentials   | `GOOGLE_APPLICATION_CREDENTIALS` |\n| `publicUrl`   | `GCS_PUBLIC_URL`                 |\n\nSigning uses the private key directly, so `signedUrl()` and `uploadUrl()` return `null` under the metadata server. The user credentials written by `gcloud auth application-default login` carry no private key either and cannot sign at all, so point `GOOGLE_APPLICATION_CREDENTIALS` at a service-account JSON file when you need signed URLs.\n\nPass `{ url, anonymous }` (or set `GCS_URL` / `GCS_ANONYMOUS`) to point at an emulator such as fake-gcs-server:\n\n```js\nconst bucket = GCS(\"my-bucket\", {\n  url: \"http://localhost:4443\",\n  anonymous: true,\n});\n```\n\n### Azure Blob Storage\n\n```js\nimport { Azure } from \"bucket\";\n\nconst bucket = Azure(\"my-container\", {\n  account: \"my-account\",\n  key: \"base64-account-key\",\n});\n```\n\nYou can also pass a full connection string, or omit the key to use Managed Identity on Azure-hosted infrastructure:\n\n```js\n// Connection string (its BlobEndpoint is honoured automatically)\nconst bucket = Azure(\"my-container\", {\n  connectionString:\n    \"DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;\",\n});\n\n// Managed Identity, no key needed\nconst bucket = Azure(\"my-container\", { account: \"my-account\" });\n```\n\n| Option             | Env var                   |\n| ------------------ | ------------------------- |\n| container name     | `AZURE_CONTAINER`         |\n| `account`          | `AZURE_ACCOUNT`           |\n| `key`              | `AZURE_KEY`               |\n| `url`              | `AZURE_URL`               |\n| `connectionString` | `AZURE_CONNECTION_STRING` |\n| `publicUrl`        | `AZURE_PUBLIC_URL`        |\n\nThe `url` option points at the Azurite emulator or a custom/sovereign cloud, e.g. `http://127.0.0.1:10000/devstoreaccount1`.\n\n### More?\n\nOpen an [issue or PR](https://github.com/franciscop/bucket) if you'd like to see another service supported.\n\n## Guides\n\nA `BucketFile` is a **lazy remote handle, not a `Blob`**. It exposes the same read methods as a `Blob`, but to hand it to a Web API materialize it first:\n\n- **`file.stream()`**: a web `ReadableStream`, for streaming bodies (no buffering).\n- **`await file.blob()`**: a real `Blob`, for APIs that need one (`FormData`).\n\n> Passing the `BucketFile` object _itself_ to `new Response(file)` or `FormData.append(name, file)` will **not** work: it is not a `Blob`, and would serialize as empty. Always use `.stream()` or `.blob()`.\n\n### Serve over HTTP\n\n`file.stream()` is a web `ReadableStream`, which is exactly what `Response` accepts, so bytes reach the client without the server ever holding the file:\n\n```js\n// Bun.serve, Next.js, Hono, or any fetch handler\nexport default {\n  fetch(req) {\n    return new Response(bucket.file(\"video.mp4\").stream(), {\n      headers: { \"content-type\": \"video/mp4\" },\n    });\n  },\n};\n```\n\nHardcoding the type only works when you already know it. [`info()`](#fileinfo) returns the real one along with the size, and doubles as the existence check:\n\n```js\nasync fetch(req) {\n  const file = bucket.file(new URL(req.url).pathname.slice(1));\n  const info = await file.info();\n  if (!info) return new Response(\"Not found\", { status: 404 });\n\n  return new Response(file.stream(), {\n    headers: {\n      \"content-type\": info.type ?? \"application/octet-stream\",\n      \"content-length\": String(info.size),\n      \"last-modified\": info.modified.toUTCString(),\n    },\n  });\n}\n```\n\nThat costs one extra round trip to the provider, so skip it when the type is known and a missing file can simply throw. To answer `Range` requests (video scrubbing, resumable downloads) serve [`file.slice()`](#fileslice) with a `206` instead.\n\nProxying spends your server's bandwidth on every byte. When the client can talk to the provider directly, redirect to a [`signedUrl()`](#filesignedurlopts) and let it do the transfer:\n\n```js\nconst url = await file.signedUrl({ expires: \"15min\" });\nreturn Response.redirect(url, 302);\n```\n\n### Attach to `FormData`\n\n`FormData` needs a real `Blob`, so materialize the file with `await file.blob()`. Pass `file.name` as the third argument: without it the part carries no usable filename (`blob` on Node, empty on Bun) and servers commonly reject it or store it under the wrong name.\n\n```js\nconst form = new FormData();\nconst file = bucket.file(\"avatar.png\");\nform.append(\"avatar\", await file.blob(), file.name);\n\nawait fetch(\"https://api.example.com/upload\", { method: \"POST\", body: form });\n```\n\nThe blob carries the file's content type, so the part arrives as `image/png` rather than as opaque bytes.\n\nThis holds the whole file in memory, which is inherent to `FormData`: it needs the total length before it can build the multipart body. For large files send the body as a stream instead, as below.\n\n### Streaming with fetch()\n\nPassing `file.stream()` as a request body hands a file to another service without buffering it anywhere in between, so size stops mattering:\n\n```js\nawait fetch(\"https://api.example.com/ingest\", {\n  method: \"PUT\",\n  body: bucket.file(\"big.csv\").stream(),\n  duplex: \"half\", // required when the body is a stream\n});\n```\n\n`duplex: \"half\"` is mandatory whenever the body is a stream: `fetch` throws before sending anything without it.\n\nSince the length is not known up front the request is sent chunked, with no `Content-Length`. Most APIs accept that, but some require a length and will reject it; send `await file.blob()` in that case and accept the buffering. Streaming request bodies work on Node, Bun and Deno; in browsers they are Chromium-only and need HTTP/2.\n\n### Store fetch() file\n\n```js\n// Buffered\nconst res = await fetch(\"https://example.com/image.png\");\nawait bucket.file(\"image.png\").write(await res.blob());\n\n// Or streamed, without holding it all in memory\nawait res.body.pipeTo(bucket.file(\"image.png\").writable());\n\n// Straight from an inbound upload in a server handler\nasync fetch(req) {\n  await bucket.file(\"upload.bin\").write(req.body); // req.body is a ReadableStream\n  return new Response(\"ok\");\n}\n```\n\n### Accept an upload\n\nAn upload's file name is chosen by whoever is uploading, so it should never become a path. [`create()`](#create) writes the body under a generated name and hands back the file to store a reference to. What you pass it depends on what the framework parsed for you:\n\n- a `File` or `Blob`: pass it straight through, the extension comes along with it.\n- a stream: pass it with `{ type }`, since a stream carries no name.\n\nFrameworks built on web standards (Hono, Next.js, Elysia, Bun, Deno, SvelteKit) give you a `File` from `FormData`:\n\n```js\n// Bun.serve, Hono, Next.js route handlers, Elysia, ...\nasync fetch(req) {\n  const form = await req.formData();\n  const file = await bucket.folder(\"avatars\").create(form.get(\"avatar\"));\n  return Response.json({ path: file.path }); // \"avatars/V1StGXR8Z5jdHi6BmyT2a.png\"\n}\n```\n\n### Fastify uploads\n\n`@fastify/multipart` is stream-based, so `part.file` is a Node stream and the type comes from `part.mimetype`. Uploading from the stream means the file never has to fit in memory:\n\n```js\nimport Fastify from \"fastify\";\nimport multipart from \"@fastify/multipart\";\n\nconst app = Fastify();\nconst uploads = bucket.folder(\"uploads\");\n\napp.register(multipart, {\n  attachFieldsToBody: \"keyValues\",\n  async onFile(part) {\n    part.value = await uploads.create(part.file, { type: part.mimetype });\n  },\n});\n\napp.post(\"/upload\", async (req) => {\n  return { path: req.body.profile.path }; // req.body.profile is a BucketFile\n});\n```\n\n`part.mimetype` comes from the client's own headers, so validate it against an allowlist if you will serve the file back.\n\n### Express and Multer\n\nMulter takes a storage engine, which is where the upload goes. This one streams to the bucket and puts the `BucketFile` on `req.file`, plus the `path` and `size` that Multer users expect:\n\n```js\nimport { Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\n\nfunction bucketStorage(bucket) {\n  return {\n    _handleFile(req, file, callback) {\n      let size = 0;\n      const meter = new Transform({\n        transform(chunk, encoding, done) {\n          size += chunk.length;\n          done(null, chunk);\n        },\n      });\n      Promise.all([\n        pipeline(file.stream, meter),\n        bucket.create(meter, { type: file.mimetype }),\n      ])\n        .then(([, bucketFile]) => {\n          callback(null, { bucketFile, path: bucketFile.path, size });\n        })\n        .catch((error) => {\n          meter.destroy(error);\n          callback(error);\n        });\n    },\n    // Called when Multer rolls an upload back\n    _removeFile(req, file, callback) {\n      if (!file.bucketFile) return callback(null);\n      file.bucketFile.remove().then(() => callback(null), callback);\n    },\n  };\n}\n\nconst upload = multer({ storage: bucketStorage(bucket.folder(\"uploads\")) });\n\napp.post(\"/upload\", upload.single(\"profile\"), (req, res) => {\n  res.json({ path: req.file.path }); // req.file.bucketFile is the BucketFile\n});\n```\n\nThe same engine works with NestJS, whose `FileInterceptor` takes a Multer `storage`.\n\n### Formidable uploads\n\nFormidable's `fileWriteStreamHandler` has to return a `Writable` synchronously, so bridge it with a `PassThrough` and keep the upload's promise on the file that Formidable hands you:\n\n```js\nimport formidable from \"formidable\";\nimport { PassThrough } from \"node:stream\";\n\nconst uploads = bucket.folder(\"uploads\");\n\nconst form = formidable({\n  fileWriteStreamHandler(file) {\n    const stream = new PassThrough();\n    file.bucketFile = uploads.create(stream, { type: file.mimetype });\n    return stream;\n  },\n});\n\nconst [fields, files] = await form.parse(req);\nconst profile = await files.profile[0].bucketFile;\nconsole.log(profile.path); // \"uploads/81K2ladhL1tVdNrDvhPLR.png\"\n```\n\nPutting the promise on `file` rather than on a variable outside the handler keeps each upload tied to its own field, so a form with several files works unchanged. Await every one of them: an upload nobody awaits turns a failure into an unhandled rejection.\n\n### Large file uploads\n\nThere is nothing to do. Large files are chunked internally using whatever mechanism each provider expects, and streamed straight to disk on the filesystem, so uploading a small file and a huge one is the same call:\n\n```js\nawait bucket.file(\"notes.txt\").write(\"hello\"); // a few bytes\nawait bucket.file(\"backup.tar\").write(stream); // tens of gigabytes\n```\n\nUnder ~8 MiB the body goes out as a single request, exactly as it always did. Past that, the upload switches to the provider's own chunked protocol:\n\n| Provider   | Used internally                                       |\n| ---------- | ----------------------------------------------------- |\n| S3, R2     | Multipart upload (`UploadPart`, `CompleteMultipart…`) |\n| Backblaze  | Large files (`b2_start_large_file`, `b2_upload_part`) |\n| Azure      | Blocks (`Put Block`, `Put Block List`)                |\n| GCS        | Resumable upload session                              |\n| Filesystem | Streamed to disk, nothing to chunk                    |\n| Memory     | Held as one buffer, nothing to chunk                  |\n\nMemory stays at roughly one chunk no matter how big the file is, so this streams a file far larger than the machine's RAM:\n\n```js\nimport { createReadStream } from \"node:fs\";\n\nawait bucket.file(\"backup.tar\").write(createReadStream(\"./backup.tar\"));\n```\n\nThe same holds for [`writable()`](#filewritable) and [`nodeWritable()`](#filenodewritable), and for copying between providers, where the bytes are streamed rather than buffered:\n\n```js\nawait r2.file(\"backup.tar\").write(s3.file(\"backup.tar\"));\n```\n\nA write that fails or is aborted partway cleans up the chunks it already uploaded, so a broken upload leaves nothing behind: no partial object, and no half-finished multipart session quietly accruing storage charges.\n\nProviders cap how many chunks an upload may have, which sets the ceiling: 10,000 on S3, R2 and Backblaze puts it around 78 GB per file, and Azure's 50,000 blocks around 390 GB. A single request would have stopped at ~5 GB, and at whatever fits in memory.\n\n### Combine buckets\n\n`write()` accepts a `BucketFile` from **any** provider, so moving data between services is one call:\n\n```js\nimport { S3, FS } from \"bucket\";\n\nconst s3 = S3(\"my-bucket\");\nconst disk = FS(\"./downloads\");\n\nawait disk.file(\"report.pdf\").write(s3.file(\"report.pdf\")); // download S3 → disk\nawait s3.file(\"report.pdf\").write(disk.file(\"report.pdf\")); // upload disk → S3\n\n// Or stream between them without buffering\nawait s3.file(\"a.bin\").stream().pipeTo(disk.file(\"a.bin\").writable());\n```\n\n**Direction:** `dst.write(src)` is a _pull_, so the file you call it on is the destination and it reads from the argument. `src.copyTo(dst)` and `src.moveTo(dst)` _push_ instead, and both accept either a path in the same bucket or a file from any other one.\n\n### Bun's File\n\n`Bun.file()` and `bucket.file()` are both lazy handles over the same `Blob` read API, one on disk and one in a bucket, so they compose in either direction:\n\n```js\n// Local file → bucket\nawait bucket.file(\"photo.jpg\").write(Bun.file(\"./local/photo.jpg\"));\n\n// Bucket → local file\nawait Bun.write(\"./local/photo.jpg\", await bucket.file(\"photo.jpg\").blob());\n```\n\nBoth of those hold the file in memory as it transfers, which is fine for photos and documents. Stream instead when the size is unbounded:\n\n```js\n// Local file → bucket\nawait Bun.file(\"./video.mp4\")\n  .stream()\n  .pipeTo(bucket.file(\"video.mp4\").writable());\n\n// Bucket → local file, through a FileSink\nconst sink = Bun.file(\"./video.mp4\").writer();\nfor await (const chunk of bucket.file(\"video.mp4\").stream()) sink.write(chunk);\nawait sink.end();\n```\n\n`Bun.file()` does not touch the disk until it is read, so passing one that does not exist fails at `write()` time, not before. Use `await Bun.file(path).exists()` first if the path is not yours.\n\n### Resize with sharp\n\n[sharp](https://sharp.pixelplumbing.com) is a Node transform stream, so it slots between one file's reader and another's writer. Nothing buffers the whole image, which is what makes this safe for photos of any size:\n\n```js\nimport { pipeline } from \"node:stream/promises\";\nimport sharp from \"sharp\";\n\nawait pipeline(\n  bucket.file(\"original.jpg\").nodeReadable(),\n  sharp().resize(200, 200),\n  bucket.file(\"thumbnail.jpg\").nodeWritable(),\n);\n```\n\nChanging format needs nothing extra: name the destination with the new extension and the stored content type follows from it.\n\n```js\nawait pipeline(\n  bucket.file(\"original.jpg\").nodeReadable(),\n  sharp().rotate().resize(400).webp(),\n  bucket.file(\"thumbnail.webp\").nodeWritable(), // stored as image/webp\n);\n```\n\n`.rotate()` with no arguments applies the image's EXIF orientation, which photos from phones rely on: without it a portrait shot can arrive sideways.\n\nBoth ends are just files, so the source and the result can live in different providers:\n\n```js\nawait pipeline(\n  s3.file(\"uploads/a.jpg\").nodeReadable(),\n  sharp().resize(800),\n  r2.file(\"thumbnails/a.jpg\").nodeWritable(),\n);\n```\n\nsharp runs anywhere Node does. On Bun, [`Bun.Image`](#resize-with-bunimage) does the same job with no dependency, working on bytes instead of streams.\n\n### Resize with `Bun.Image`\n\nBun ships a native image processor, [`Bun.Image`](https://bun.sh/docs/api/image), with no dependencies. It reads `Uint8Array` / `Buffer` / `ArrayBuffer` / `Blob` and outputs the same, so it plugs straight into a bucket file: read the bytes, transform, then write the result back.\n\n```js\nconst src = bucket.file(\"photos/original.jpg\");\n\n// Read the file into Bun.Image\nconst img = new Bun.Image(await src.bytes());\nconst { width, height, format } = await img.metadata();\n\n// Resize and re-encode, then hand the bytes back to the bucket\nconst thumb = await img.resize(200, 200).webp().toBuffer();\nawait bucket.file(\"photos/thumb.webp\").write(thumb, { type: \"image/webp\" });\n```\n\nThe transforms are chainable (`.resize()`, `.rotate()`, `.flip()`, `.flop()`, `.modulate()`), followed by a format (`.png()`, `.jpeg()`, `.webp()`, `.avif()`, `.heic()`) and an output (`.toBuffer()`, `.bytes()`, `.blob()`). Because both sides speak bytes, this works across providers too. For example, resize an upload sitting on S3 and store the thumbnail on R2:\n\n```js\nconst buf = await new Bun.Image(await s3.file(\"a.jpg\").bytes())\n  .resize(800)\n  .jpeg()\n  .toBuffer();\nawait r2.file(\"thumbnails/a.jpg\").write(buf, { type: \"image/jpeg\" });\n```\n\nA few things to know:\n\n- Read the dimensions from `await img.metadata()`. The sync `.width` / `.height` getters report `-1` until the image has been decoded.\n- `Bun.Image` buffers the whole image, so read with `.bytes()`, not `.stream()`.\n- It is Bun only. On Node or other runtimes use `sharp`, which streams and pipes through `.nodeReadable()` / `.nodeWritable()` directly (see [Resize with sharp](#resize-with-sharp) above).\n\n**TypeScript:** `Bun.Image` is not in `@types/bun` yet, so the compiler reports `Property 'Image' does not exist`. Add a small ambient declaration until the types ship:\n\n```ts\n// bun-image.d.ts\ndeclare namespace Bun {\n  class Image {\n    constructor(input: Uint8Array | ArrayBuffer | Buffer | Blob);\n    metadata(): Promise<{ width: number; height: number; format: string }>;\n    resize(width: number, height?: number): Bun.Image;\n    rotate(deg: number): Bun.Image;\n    flip(): Bun.Image;\n    flop(): Bun.Image;\n    modulate(o: {\n      brightness?: number;\n      saturation?: number;\n      hue?: number;\n    }): Bun.Image;\n    png(): Bun.Image;\n    jpeg(): Bun.Image;\n    webp(): Bun.Image;\n    avif(): Bun.Image;\n    heic(): Bun.Image;\n    toBuffer(): Promise<Buffer>;\n    bytes(): Promise<Uint8Array>;\n    blob(): Promise<Blob>;\n  }\n}\n```\n\n### Zip files\n\n```js\nimport { pipeline } from \"node:stream/promises\";\nimport { createGzip } from \"node:zlib\";\n\nawait pipeline(\n  bucket.file(\"data.csv\").nodeReadable(),\n  createGzip(),\n  bucket.file(\"data.csv.gz\").nodeWritable(),\n);\n```\n\n## FAQ\n\n### Does this library ship TypeScript types?\n\nYes. The library is written in TypeScript and ships types for all methods. No `@types/` package needed.\n\n```ts\nimport { S3 } from \"bucket\";\nimport type { Bucket, BucketFile, FileInfo } from \"bucket\";\n\nconst bucket: Bucket = S3(\"my-bucket\");\nconst file: BucketFile = bucket.file(\"photo.jpg\");\nconst info: FileInfo | null = await file.info();\n```\n\n### Which runtimes are supported?\n\nNode.js, Bun, and Deno, fully. Cloudflare Workers works with the remote providers when the `nodejs_compat` flag is enabled (the flag is required to deploy at all, since every provider imports `node:stream` for the `.nodeReadable()` / `.nodeWritable()` helpers). `FS` is not supported on Workers: there is no persistent disk, and recent Workers runtimes expose an in-memory `node:fs`, so writes may appear to succeed and then vanish.\n\nEverything else is Web standards: request signing uses **WebCrypto** (`crypto.subtle`), and reads/writes use the Web `fetch`, `Blob`, and Streams APIs, so there is no `node:crypto` dependency. Beyond `node:stream`, the only Node-specific imports are `node:fs` / `node:os` in the FS provider. For browsers, don't use this library directly (it would expose your credentials); hand the browser [`signedUrl()`](#filesignedurlopts) / [`uploadUrl()`](#fileuploadurlopts) links instead.\n\n### What happens when a file doesn't exist?\n\n`.info()` and `.exists()` never throw for a missing file: they return `null` and `false` respectively. All other read methods (`.text()`, `.json()`, `.arrayBuffer()`, etc.) will throw if the file doesn't exist.\n\n### What happens on a network or auth error?\n\nMethods throw a `BucketError` (a subclass of `Error`). Alongside the human-readable `message` it carries structured fields you can branch on:\n\n- `code`: a normalized, uppercase string, one of `\"NOT_FOUND\" | \"FORBIDDEN\" | \"UNAUTHORIZED\" | \"CONFLICT\" | \"INVALID_PATH\" | \"INVALID_FILTER\" | \"INVALID_CONFIG\" | \"ABORTED\" | \"UNKNOWN\"`. It means the same thing across every provider, including the filesystem.\n- `status`: the raw HTTP status, when the failure came from an HTTP response (absent for the filesystem).\n- `provider`: which backend produced it (e.g. `\"S3\"`). Absent for the `INVALID_*` codes, which are raised client-side before any provider is involved, and for `\"ABORTED\"`, which can happen at any point (including mid-request) and describes your own cancellation rather than anything a provider reported. `\"INVALID_CONFIG\"` is thrown by the constructor, so an unusable bucket fails where you build it rather than on its first request.\n\nThere is no automatic retry.\n\n```js\nimport { BucketError } from \"bucket\";\n\ntry {\n  const text = await bucket.file(\"data.txt\").text();\n} catch (err) {\n  if (err instanceof BucketError && err.code === \"NOT_FOUND\") {\n    // handle a missing file\n  }\n}\n```\n\n### What happens on a versioned bucket?\n\nRemoving is about the path, never the history. After `.remove()` the path stops resolving (`.exists()` is `false`, reads throw `NOT_FOUND`, `.info()` is `null`, and it is absent from `.list()`, `.scan()` and `.count()`), but the versions written before it are kept:\n\n| Service | Versioning    | What `.remove()` leaves behind               |\n| ------- | ------------- | -------------------------------------------- |\n| S3      | Optional      | A delete marker; earlier versions stay       |\n| GCS     | Optional      | The generations become noncurrent            |\n| Azure   | Optional      | The blob and its snapshots go, versions stay |\n| B2      | Always on     | A hide marker; every version stays           |\n| R2      | Not supported | Nothing, the object is gone                  |\n| FS      | Not supported | Nothing, the file is unlinked                |\n| Memory  | Not supported | Nothing, the entry is dropped                |\n\nWhere versioning is on, removal is reversible through the provider's own console or API, and the retained versions keep costing storage until a lifecycle rule expires them. Bucket never deletes a version itself, so it will not destroy history that the provider is keeping for you.\n\nWhere it is off, and always on R2, the filesystem and the in-memory bucket, `.remove()` is permanent, and so is an overwriting `.write()`. Turn versioning on at the provider if you need an undo.\n\nThe same applies to [`.moveTo()`](#filemovetopath) and [`.rename()`](#filerenamename), which remove the source once the copy lands: a move within a versioned bucket duplicates the bytes rather than relocating them.\n\nBackblaze B2 is always versioned, so it hides instead of deleting even when you never turned versioning on. Deleting its newest version would both destroy that version and uncover the one before it, resurrecting old content at a path you just removed.\n\n### Can I cancel an operation?\n\nYes. Every method that does I/O takes an `AbortSignal`, and an aborted operation rejects rather than resolving:\n\n```js\nconst controller = new AbortController();\nsetTimeout(() => controller.abort(), 100);\n\nconst text = await bucket.file(\"big.csv\").text({ signal: controller.signal });\n```\n\nReaders take it as `{ signal }`, writes through the existing options object (`write(content, { signal })`), and the bucket methods after the filter (`list(filter, { signal })`, `remove(/./, { signal })`). `scan()` rejects when you call it rather than on the first iteration, and stops a loop already running.\n\nAborting stops the client, not the server, so it is not a rollback. A single request may already have been committed by the provider even though your call rejects, and a multi-step call stops partway: `bucket.remove(/./, { signal })` leaves already-deleted files deleted, and `moveTo()` aborted between the copy and the remove leaves both copies in place. What is guaranteed is that no further request is made, and that an in-flight multipart upload is cancelled rather than left open and billed.\n\nTreat an abort as \"stop as soon as possible\", and check the state afterwards if you need to know how far it got.\n\nThe error suits both idioms. It is a `BucketError` with `code: \"ABORTED\"`, and its `name` mirrors the signal's own reason, so a timeout stays distinguishable from a cancel:\n\n```js\ntry {\n  await bucket.file(\"big.csv\").text({ signal: AbortSignal.timeout(5000) });\n} catch (err) {\n  err.code; // \"ABORTED\", the same across every provider\n  err.name; // \"TimeoutError\" here, \"AbortError\" for controller.abort()\n  err.cause; // the signal's own reason\n}\n```\n\n### What are \"web streams\" vs \"node streams\"?\n\nWhen Node.js was created, there was no native streaming in JavaScript. So Node.js built its own streaming system, now known as \"Node streams\". A few years later, the JavaScript standards body created an official streaming API, first shipped in browsers alongside `fetch()`, now known as \"web streams\".\n\nThe two are not directly compatible. Which one you need depends on what you're connecting to:\n\n- If you're piping to/from a web API (`fetch`, `Response`, `Request`): use web streams (`.stream()`, `.writable()`)\n- If you're using a Node.js library like `sharp`, `zlib`, `csv-parse`: use Node streams (`.nodeReadable()`, `.nodeWritable()`)\n\n```js\n// Web streams: pipe directly into a fetch response body\nconst stream = bucket.file(\"video.mp4\").stream();\nreturn new Response(stream);\n\n// Node streams: pipe through sharp (which uses Node streams)\nimport { pipeline } from \"node:stream/promises\";\nimport sharp from \"sharp\";\n\nawait pipeline(\n  bucket.file(\"photo.jpg\").nodeReadable(),\n  sharp().resize(300),\n  bucket.file(\"thumb.jpg\").nodeWritable(),\n);\n```\n","readmeFilename":"readme.md"}