@fragno-dev/upload 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +16 -0
- package/README.md +43 -0
- package/bin/run.js +5 -0
- package/dist/browser/client/clients.js +49 -0
- package/dist/browser/client/clients.js.map +1 -0
- package/dist/browser/client/helpers.d.ts +51 -0
- package/dist/browser/client/helpers.d.ts.map +1 -0
- package/dist/browser/client/helpers.js +242 -0
- package/dist/browser/client/helpers.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/@nanostores_query@0.3.4_nanostores@1.1.0/node_modules/@nanostores/query/dist/nanoquery.js +354 -0
- package/dist/browser/client/node_modules/.pnpm/@nanostores_query@0.3.4_nanostores@1.1.0/node_modules/@nanostores/query/dist/nanoquery.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/@nanostores_solid@1.1.1_nanostores@1.1.0_solid-js@1.9.10/node_modules/@nanostores/solid/dist/index.js +14 -0
- package/dist/browser/client/node_modules/.pnpm/@nanostores_solid@1.1.1_nanostores@1.1.0_solid-js@1.9.10/node_modules/@nanostores/solid/dist/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js +17 -0
- package/dist/browser/client/node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/atom/index.js +62 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/atom/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/clean-stores/index.js +6 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/clean-stores/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/computed/index.js +50 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/computed/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/lifecycle/index.js +99 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/lifecycle/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/listen-keys/index.js +11 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/listen-keys/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/map/index.js +25 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/map/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/task/index.js +24 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/task/index.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/definition.js +49 -0
- package/dist/browser/client/packages/fragment-upload/src/definition.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/keys.d.ts +7 -0
- package/dist/browser/client/packages/fragment-upload/src/keys.d.ts.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/keys.js +28 -0
- package/dist/browser/client/packages/fragment-upload/src/keys.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/files.js +98 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/files.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/index.js +9 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/index.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/shared.js +41 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/shared.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/uploads.js +186 -0
- package/dist/browser/client/packages/fragment-upload/src/routes/uploads.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/schema.js +8 -0
- package/dist/browser/client/packages/fragment-upload/src/schema.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/storage/types.d.ts +9 -0
- package/dist/browser/client/packages/fragment-upload/src/storage/types.d.ts.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/types.d.ts +31 -0
- package/dist/browser/client/packages/fragment-upload/src/types.d.ts.map +1 -0
- package/dist/browser/client/packages/fragno/dist/api/error.js +48 -0
- package/dist/browser/client/packages/fragno/dist/api/error.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/api/internal/path.js +76 -0
- package/dist/browser/client/packages/fragno/dist/api/internal/path.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/api/internal/response-stream.js +81 -0
- package/dist/browser/client/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/api/internal/route.js +10 -0
- package/dist/browser/client/packages/fragno/dist/api/internal/route.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/api/request-input-context.js +185 -0
- package/dist/browser/client/packages/fragno/dist/api/request-input-context.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/api/request-output-context.js +119 -0
- package/dist/browser/client/packages/fragno/dist/api/request-output-context.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/api/route.js +17 -0
- package/dist/browser/client/packages/fragno/dist/api/route.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/client-error.js +92 -0
- package/dist/browser/client/packages/fragno/dist/client/client-error.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/client.js +495 -0
- package/dist/browser/client/packages/fragno/dist/client/client.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/client.svelte.js +120 -0
- package/dist/browser/client/packages/fragno/dist/client/client.svelte.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/internal/fetcher-merge.js +36 -0
- package/dist/browser/client/packages/fragno/dist/client/internal/fetcher-merge.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/internal/ndjson-streaming.js +139 -0
- package/dist/browser/client/packages/fragno/dist/client/internal/ndjson-streaming.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/react.js +70 -0
- package/dist/browser/client/packages/fragno/dist/client/react.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/solid.js +108 -0
- package/dist/browser/client/packages/fragno/dist/client/solid.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/vanilla.js +96 -0
- package/dist/browser/client/packages/fragno/dist/client/vanilla.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/client/vue.js +120 -0
- package/dist/browser/client/packages/fragno/dist/client/vue.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/util/async.js +40 -0
- package/dist/browser/client/packages/fragno/dist/util/async.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/util/content-type.js +49 -0
- package/dist/browser/client/packages/fragno/dist/util/content-type.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/util/nanostores.js +31 -0
- package/dist/browser/client/packages/fragno/dist/util/nanostores.js.map +1 -0
- package/dist/browser/client/packages/fragno/dist/util/ssr.js +18 -0
- package/dist/browser/client/packages/fragno/dist/util/ssr.js.map +1 -0
- package/dist/browser/client/react.d.ts +295 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +11 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +303 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +11 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +295 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +11 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +296 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +11 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +295 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +11 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/index-BdjKPO4J.d.ts +177 -0
- package/dist/browser/index-BdjKPO4J.d.ts.map +1 -0
- package/dist/browser/index.js +3 -0
- package/dist/browser/src-vdNJUbjT.js +1982 -0
- package/dist/browser/src-vdNJUbjT.js.map +1 -0
- package/dist/cli/commands/files/delete.d.ts +40 -0
- package/dist/cli/commands/files/delete.d.ts.map +1 -0
- package/dist/cli/commands/files/delete.js +31 -0
- package/dist/cli/commands/files/delete.js.map +1 -0
- package/dist/cli/commands/files/download-url.d.ts +40 -0
- package/dist/cli/commands/files/download-url.d.ts.map +1 -0
- package/dist/cli/commands/files/download-url.js +31 -0
- package/dist/cli/commands/files/download-url.js.map +1 -0
- package/dist/cli/commands/files/download.d.ts +49 -0
- package/dist/cli/commands/files/download.d.ts.map +1 -0
- package/dist/cli/commands/files/download.js +65 -0
- package/dist/cli/commands/files/download.js.map +1 -0
- package/dist/cli/commands/files/get.d.ts +40 -0
- package/dist/cli/commands/files/get.d.ts.map +1 -0
- package/dist/cli/commands/files/get.js +31 -0
- package/dist/cli/commands/files/get.js.map +1 -0
- package/dist/cli/commands/files/list.d.ts +56 -0
- package/dist/cli/commands/files/list.d.ts.map +1 -0
- package/dist/cli/commands/files/list.js +53 -0
- package/dist/cli/commands/files/list.js.map +1 -0
- package/dist/cli/commands/files/update.d.ts +56 -0
- package/dist/cli/commands/files/update.d.ts.map +1 -0
- package/dist/cli/commands/files/update.js +53 -0
- package/dist/cli/commands/files/update.js.map +1 -0
- package/dist/cli/commands/files/upload.d.ts +73 -0
- package/dist/cli/commands/files/upload.d.ts.map +1 -0
- package/dist/cli/commands/files/upload.js +87 -0
- package/dist/cli/commands/files/upload.js.map +1 -0
- package/dist/cli/commands/uploads/abort.d.ts +37 -0
- package/dist/cli/commands/uploads/abort.d.ts.map +1 -0
- package/dist/cli/commands/uploads/abort.js +26 -0
- package/dist/cli/commands/uploads/abort.js.map +1 -0
- package/dist/cli/commands/uploads/complete.d.ts +41 -0
- package/dist/cli/commands/uploads/complete.d.ts.map +1 -0
- package/dist/cli/commands/uploads/complete.js +45 -0
- package/dist/cli/commands/uploads/complete.js.map +1 -0
- package/dist/cli/commands/uploads/content.d.ts +46 -0
- package/dist/cli/commands/uploads/content.d.ts.map +1 -0
- package/dist/cli/commands/uploads/content.js +43 -0
- package/dist/cli/commands/uploads/content.js.map +1 -0
- package/dist/cli/commands/uploads/create.d.ts +72 -0
- package/dist/cli/commands/uploads/create.d.ts.map +1 -0
- package/dist/cli/commands/uploads/create.js +84 -0
- package/dist/cli/commands/uploads/create.js.map +1 -0
- package/dist/cli/commands/uploads/get.d.ts +37 -0
- package/dist/cli/commands/uploads/get.d.ts.map +1 -0
- package/dist/cli/commands/uploads/get.js +26 -0
- package/dist/cli/commands/uploads/get.js.map +1 -0
- package/dist/cli/commands/uploads/parts-complete.d.ts +41 -0
- package/dist/cli/commands/uploads/parts-complete.d.ts.map +1 -0
- package/dist/cli/commands/uploads/parts-complete.js +44 -0
- package/dist/cli/commands/uploads/parts-complete.js.map +1 -0
- package/dist/cli/commands/uploads/parts-list.d.ts +37 -0
- package/dist/cli/commands/uploads/parts-list.d.ts.map +1 -0
- package/dist/cli/commands/uploads/parts-list.js +26 -0
- package/dist/cli/commands/uploads/parts-list.js.map +1 -0
- package/dist/cli/commands/uploads/parts-urls.d.ts +46 -0
- package/dist/cli/commands/uploads/parts-urls.d.ts.map +1 -0
- package/dist/cli/commands/uploads/parts-urls.js +57 -0
- package/dist/cli/commands/uploads/parts-urls.js.map +1 -0
- package/dist/cli/commands/uploads/progress.d.ts +45 -0
- package/dist/cli/commands/uploads/progress.d.ts.map +1 -0
- package/dist/cli/commands/uploads/progress.js +40 -0
- package/dist/cli/commands/uploads/progress.js.map +1 -0
- package/dist/cli/commands/uploads/transfer.d.ts +73 -0
- package/dist/cli/commands/uploads/transfer.d.ts.map +1 -0
- package/dist/cli/commands/uploads/transfer.js +170 -0
- package/dist/cli/commands/uploads/transfer.js.map +1 -0
- package/dist/cli/index.d.ts +27 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +198 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/keys.js +32 -0
- package/dist/cli/keys.js.map +1 -0
- package/dist/cli/utils/client.js +174 -0
- package/dist/cli/utils/client.js.map +1 -0
- package/dist/cli/utils/options.js +135 -0
- package/dist/cli/utils/options.js.map +1 -0
- package/dist/node/cli/commands/files/delete.d.ts +40 -0
- package/dist/node/cli/commands/files/delete.d.ts.map +1 -0
- package/dist/node/cli/commands/files/delete.js +31 -0
- package/dist/node/cli/commands/files/delete.js.map +1 -0
- package/dist/node/cli/commands/files/download-url.d.ts +40 -0
- package/dist/node/cli/commands/files/download-url.d.ts.map +1 -0
- package/dist/node/cli/commands/files/download-url.js +31 -0
- package/dist/node/cli/commands/files/download-url.js.map +1 -0
- package/dist/node/cli/commands/files/download.d.ts +49 -0
- package/dist/node/cli/commands/files/download.d.ts.map +1 -0
- package/dist/node/cli/commands/files/download.js +65 -0
- package/dist/node/cli/commands/files/download.js.map +1 -0
- package/dist/node/cli/commands/files/get.d.ts +40 -0
- package/dist/node/cli/commands/files/get.d.ts.map +1 -0
- package/dist/node/cli/commands/files/get.js +31 -0
- package/dist/node/cli/commands/files/get.js.map +1 -0
- package/dist/node/cli/commands/files/list.d.ts +56 -0
- package/dist/node/cli/commands/files/list.d.ts.map +1 -0
- package/dist/node/cli/commands/files/list.js +53 -0
- package/dist/node/cli/commands/files/list.js.map +1 -0
- package/dist/node/cli/commands/files/update.d.ts +56 -0
- package/dist/node/cli/commands/files/update.d.ts.map +1 -0
- package/dist/node/cli/commands/files/update.js +53 -0
- package/dist/node/cli/commands/files/update.js.map +1 -0
- package/dist/node/cli/commands/files/upload.d.ts +73 -0
- package/dist/node/cli/commands/files/upload.d.ts.map +1 -0
- package/dist/node/cli/commands/files/upload.js +87 -0
- package/dist/node/cli/commands/files/upload.js.map +1 -0
- package/dist/node/cli/commands/uploads/abort.d.ts +37 -0
- package/dist/node/cli/commands/uploads/abort.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/abort.js +26 -0
- package/dist/node/cli/commands/uploads/abort.js.map +1 -0
- package/dist/node/cli/commands/uploads/complete.d.ts +41 -0
- package/dist/node/cli/commands/uploads/complete.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/complete.js +45 -0
- package/dist/node/cli/commands/uploads/complete.js.map +1 -0
- package/dist/node/cli/commands/uploads/content.d.ts +46 -0
- package/dist/node/cli/commands/uploads/content.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/content.js +43 -0
- package/dist/node/cli/commands/uploads/content.js.map +1 -0
- package/dist/node/cli/commands/uploads/create.d.ts +72 -0
- package/dist/node/cli/commands/uploads/create.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/create.js +84 -0
- package/dist/node/cli/commands/uploads/create.js.map +1 -0
- package/dist/node/cli/commands/uploads/get.d.ts +37 -0
- package/dist/node/cli/commands/uploads/get.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/get.js +26 -0
- package/dist/node/cli/commands/uploads/get.js.map +1 -0
- package/dist/node/cli/commands/uploads/parts-complete.d.ts +41 -0
- package/dist/node/cli/commands/uploads/parts-complete.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/parts-complete.js +44 -0
- package/dist/node/cli/commands/uploads/parts-complete.js.map +1 -0
- package/dist/node/cli/commands/uploads/parts-list.d.ts +37 -0
- package/dist/node/cli/commands/uploads/parts-list.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/parts-list.js +26 -0
- package/dist/node/cli/commands/uploads/parts-list.js.map +1 -0
- package/dist/node/cli/commands/uploads/parts-urls.d.ts +46 -0
- package/dist/node/cli/commands/uploads/parts-urls.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/parts-urls.js +57 -0
- package/dist/node/cli/commands/uploads/parts-urls.js.map +1 -0
- package/dist/node/cli/commands/uploads/progress.d.ts +45 -0
- package/dist/node/cli/commands/uploads/progress.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/progress.js +40 -0
- package/dist/node/cli/commands/uploads/progress.js.map +1 -0
- package/dist/node/cli/commands/uploads/transfer.d.ts +73 -0
- package/dist/node/cli/commands/uploads/transfer.d.ts.map +1 -0
- package/dist/node/cli/commands/uploads/transfer.js +170 -0
- package/dist/node/cli/commands/uploads/transfer.js.map +1 -0
- package/dist/node/cli/index.d.ts +27 -0
- package/dist/node/cli/index.d.ts.map +1 -0
- package/dist/node/cli/index.js +198 -0
- package/dist/node/cli/index.js.map +1 -0
- package/dist/node/cli/utils/client.js +174 -0
- package/dist/node/cli/utils/client.js.map +1 -0
- package/dist/node/cli/utils/options.js +135 -0
- package/dist/node/cli/utils/options.js.map +1 -0
- package/dist/node/client/clients.d.ts +295 -0
- package/dist/node/client/clients.d.ts.map +1 -0
- package/dist/node/client/clients.js +49 -0
- package/dist/node/client/clients.js.map +1 -0
- package/dist/node/client/helpers.d.ts +51 -0
- package/dist/node/client/helpers.d.ts.map +1 -0
- package/dist/node/client/helpers.js +242 -0
- package/dist/node/client/helpers.js.map +1 -0
- package/dist/node/client/react.d.ts +295 -0
- package/dist/node/client/react.d.ts.map +1 -0
- package/dist/node/client/react.js +11 -0
- package/dist/node/client/react.js.map +1 -0
- package/dist/node/client/solid.d.ts +303 -0
- package/dist/node/client/solid.d.ts.map +1 -0
- package/dist/node/client/solid.js +11 -0
- package/dist/node/client/solid.js.map +1 -0
- package/dist/node/client/svelte.d.ts +295 -0
- package/dist/node/client/svelte.d.ts.map +1 -0
- package/dist/node/client/svelte.js +11 -0
- package/dist/node/client/svelte.js.map +1 -0
- package/dist/node/client/vanilla.d.ts +296 -0
- package/dist/node/client/vanilla.d.ts.map +1 -0
- package/dist/node/client/vanilla.js +11 -0
- package/dist/node/client/vanilla.js.map +1 -0
- package/dist/node/client/vue.d.ts +295 -0
- package/dist/node/client/vue.d.ts.map +1 -0
- package/dist/node/client/vue.js +11 -0
- package/dist/node/client/vue.js.map +1 -0
- package/dist/node/config.d.ts +41 -0
- package/dist/node/config.d.ts.map +1 -0
- package/dist/node/config.js +26 -0
- package/dist/node/config.js.map +1 -0
- package/dist/node/definition.d.ts +829 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +59 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/index.d.ts +1246 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +19 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/keys.d.ts +12 -0
- package/dist/node/keys.d.ts.map +1 -0
- package/dist/node/keys.js +63 -0
- package/dist/node/keys.js.map +1 -0
- package/dist/node/routes/files.js +450 -0
- package/dist/node/routes/files.js.map +1 -0
- package/dist/node/routes/index.d.ts +2023 -0
- package/dist/node/routes/index.d.ts.map +1 -0
- package/dist/node/routes/index.js +9 -0
- package/dist/node/routes/index.js.map +1 -0
- package/dist/node/routes/shared.js +66 -0
- package/dist/node/routes/shared.js.map +1 -0
- package/dist/node/routes/uploads.js +454 -0
- package/dist/node/routes/uploads.js.map +1 -0
- package/dist/node/schema.d.ts +14 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +30 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/services/files.d.ts +20 -0
- package/dist/node/services/files.d.ts.map +1 -0
- package/dist/node/services/files.js +93 -0
- package/dist/node/services/files.js.map +1 -0
- package/dist/node/services/helpers.js +36 -0
- package/dist/node/services/helpers.js.map +1 -0
- package/dist/node/services/index.js +4 -0
- package/dist/node/services/uploads.d.ts +50 -0
- package/dist/node/services/uploads.d.ts.map +1 -0
- package/dist/node/services/uploads.js +358 -0
- package/dist/node/services/uploads.js.map +1 -0
- package/dist/node/storage/fs.d.ts +16 -0
- package/dist/node/storage/fs.d.ts.map +1 -0
- package/dist/node/storage/fs.js +94 -0
- package/dist/node/storage/fs.js.map +1 -0
- package/dist/node/storage/r2.d.ts +103 -0
- package/dist/node/storage/r2.d.ts.map +1 -0
- package/dist/node/storage/r2.js +23 -0
- package/dist/node/storage/r2.js.map +1 -0
- package/dist/node/storage/s3.d.ts +44 -0
- package/dist/node/storage/s3.d.ts.map +1 -0
- package/dist/node/storage/s3.js +398 -0
- package/dist/node/storage/s3.js.map +1 -0
- package/dist/node/storage/types.d.ts +118 -0
- package/dist/node/storage/types.d.ts.map +1 -0
- package/dist/node/types.d.ts +32 -0
- package/dist/node/types.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { FileKeyEncoded, FileKeyParts } from "../keys.js";
|
|
2
|
+
import { StorageAdapterCapabilities, StorageAdapterRecommendations, UploadChecksum } from "./types.js";
|
|
3
|
+
import { S3CompatibleStorageAdapterOptions } from "./s3.js";
|
|
4
|
+
|
|
5
|
+
//#region src/storage/r2.d.ts
|
|
6
|
+
type R2StorageAdapterOptions = Omit<S3CompatibleStorageAdapterOptions, "maxMetadataBytes"> & {
|
|
7
|
+
maxMetadataBytes?: number;
|
|
8
|
+
};
|
|
9
|
+
declare function createR2StorageAdapter(options: R2StorageAdapterOptions): {
|
|
10
|
+
name: string;
|
|
11
|
+
limits: {
|
|
12
|
+
maxMetadataBytes: number;
|
|
13
|
+
maxSingleUploadBytes?: number;
|
|
14
|
+
maxMultipartUploadBytes?: number;
|
|
15
|
+
minMultipartPartSizeBytes?: number;
|
|
16
|
+
maxMultipartPartSizeBytes?: number;
|
|
17
|
+
maxMultipartParts?: number;
|
|
18
|
+
maxStorageKeyLengthBytes?: number;
|
|
19
|
+
};
|
|
20
|
+
capabilities: StorageAdapterCapabilities;
|
|
21
|
+
recommendations?: StorageAdapterRecommendations;
|
|
22
|
+
resolveStorageKey(input: {
|
|
23
|
+
fileKey: FileKeyEncoded;
|
|
24
|
+
fileKeyParts: FileKeyParts;
|
|
25
|
+
}): string;
|
|
26
|
+
initUpload(input: {
|
|
27
|
+
fileKey: FileKeyEncoded;
|
|
28
|
+
fileKeyParts: FileKeyParts;
|
|
29
|
+
sizeBytes: bigint;
|
|
30
|
+
contentType: string;
|
|
31
|
+
checksum?: UploadChecksum | null;
|
|
32
|
+
metadata?: Record<string, unknown> | null;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
strategy: "direct-single" | "direct-multipart" | "proxy";
|
|
35
|
+
storageKey: string;
|
|
36
|
+
storageUploadId?: string;
|
|
37
|
+
partSizeBytes?: number;
|
|
38
|
+
expiresAt: Date;
|
|
39
|
+
uploadUrl?: string;
|
|
40
|
+
uploadHeaders?: Record<string, string>;
|
|
41
|
+
}>;
|
|
42
|
+
getPartUploadUrls?(input: {
|
|
43
|
+
storageKey: string;
|
|
44
|
+
storageUploadId: string;
|
|
45
|
+
partNumbers: number[];
|
|
46
|
+
partSizeBytes: number;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
partNumber: number;
|
|
49
|
+
url: string;
|
|
50
|
+
headers?: Record<string, string>;
|
|
51
|
+
}[]>;
|
|
52
|
+
completeMultipartUpload?(input: {
|
|
53
|
+
storageKey: string;
|
|
54
|
+
storageUploadId: string;
|
|
55
|
+
parts: {
|
|
56
|
+
partNumber: number;
|
|
57
|
+
etag: string;
|
|
58
|
+
}[];
|
|
59
|
+
}): Promise<{
|
|
60
|
+
etag?: string;
|
|
61
|
+
}>;
|
|
62
|
+
abortMultipartUpload?(input: {
|
|
63
|
+
storageKey: string;
|
|
64
|
+
storageUploadId: string;
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
writeStream?(input: {
|
|
67
|
+
storageKey: string;
|
|
68
|
+
body: ReadableStream<Uint8Array>;
|
|
69
|
+
contentType?: string | null;
|
|
70
|
+
sizeBytes?: bigint | null;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
etag?: string;
|
|
73
|
+
sizeBytes?: bigint;
|
|
74
|
+
}>;
|
|
75
|
+
finalizeUpload?(input: {
|
|
76
|
+
storageKey: string;
|
|
77
|
+
expectedSizeBytes: bigint;
|
|
78
|
+
checksum?: UploadChecksum | null;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
sizeBytes?: bigint;
|
|
81
|
+
etag?: string;
|
|
82
|
+
}>;
|
|
83
|
+
deleteObject(input: {
|
|
84
|
+
storageKey: string;
|
|
85
|
+
}): Promise<void>;
|
|
86
|
+
getDownloadUrl?(input: {
|
|
87
|
+
storageKey: string;
|
|
88
|
+
expiresInSeconds: number;
|
|
89
|
+
contentDisposition?: string;
|
|
90
|
+
contentType?: string;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
url: string;
|
|
93
|
+
headers?: Record<string, string>;
|
|
94
|
+
expiresAt: Date;
|
|
95
|
+
}>;
|
|
96
|
+
getDownloadStream?(input: {
|
|
97
|
+
storageKey: string;
|
|
98
|
+
}): Promise<Response>;
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=r2.d.ts.map
|
|
101
|
+
//#endregion
|
|
102
|
+
export { R2StorageAdapterOptions, createR2StorageAdapter };
|
|
103
|
+
//# sourceMappingURL=r2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.d.ts","names":[],"sources":["../../../src/storage/r2.ts"],"sourcesContent":[],"mappings":";;;;;KAIY,uBAAA,GAA0B,KACpC;;;iBAMc,sBAAA,UAAgC;EAPpC,IAAA,EAAA,MAAA;EAAuB,MAAA,EAAA;IACjC,gBAAA,EAAA,MAAA;IADoC,oBAAA,CAAA,EAAA,MAAA;IAAI,uBAAA,CAAA,EAAA,MAAA;IAO1B,yBAAsB,CAAA,EAAA,MAAA;IAAA,yBAAA,CAAA,EAAA,MAAA;IAAU,iBAAA,CAAA,EAAA,MAAA;IAAuB,wBAAA,CAAA,EAAA,MAAA;;gBAAA;;;;;MAgBwvB,MAAA;;IAAiQ,OAAA,gBAAA;;IAAsR,SAAA,EAAA,MAAA;;;eAAvhB;;;;;;;IAAunD,SAAA,CAAA,EAAA,MAAA;oBAAt3C;;;;;;;;;;cAAsR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAgmC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createS3CompatibleStorageAdapter } from "./s3.js";
|
|
2
|
+
|
|
3
|
+
//#region src/storage/r2.ts
|
|
4
|
+
const DEFAULT_MAX_METADATA_BYTES = 8192;
|
|
5
|
+
function createR2StorageAdapter(options) {
|
|
6
|
+
const maxMetadataBytes = options.maxMetadataBytes ?? DEFAULT_MAX_METADATA_BYTES;
|
|
7
|
+
const adapter = createS3CompatibleStorageAdapter({
|
|
8
|
+
...options,
|
|
9
|
+
maxMetadataBytes
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
...adapter,
|
|
13
|
+
name: "r2",
|
|
14
|
+
limits: {
|
|
15
|
+
...adapter.limits,
|
|
16
|
+
maxMetadataBytes
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { createR2StorageAdapter };
|
|
23
|
+
//# sourceMappingURL=r2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.js","names":[],"sources":["../../../src/storage/r2.ts"],"sourcesContent":["import { createS3CompatibleStorageAdapter, type S3CompatibleStorageAdapterOptions } from \"./s3\";\n\nconst DEFAULT_MAX_METADATA_BYTES = 8_192;\n\nexport type R2StorageAdapterOptions = Omit<\n S3CompatibleStorageAdapterOptions,\n \"maxMetadataBytes\"\n> & {\n maxMetadataBytes?: number;\n};\n\nexport function createR2StorageAdapter(options: R2StorageAdapterOptions) {\n const maxMetadataBytes = options.maxMetadataBytes ?? DEFAULT_MAX_METADATA_BYTES;\n const adapter = createS3CompatibleStorageAdapter({\n ...options,\n maxMetadataBytes,\n });\n\n return {\n ...adapter,\n name: \"r2\",\n limits: {\n ...adapter.limits,\n maxMetadataBytes,\n },\n };\n}\n"],"mappings":";;;AAEA,MAAM,6BAA6B;AASnC,SAAgB,uBAAuB,SAAkC;CACvE,MAAM,mBAAmB,QAAQ,oBAAoB;CACrD,MAAM,UAAU,iCAAiC;EAC/C,GAAG;EACH;EACD,CAAC;AAEF,QAAO;EACL,GAAG;EACH,MAAM;EACN,QAAQ;GACN,GAAG,QAAQ;GACX;GACD;EACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { StorageAdapter } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/storage/s3.d.ts
|
|
4
|
+
type S3SignerInput = {
|
|
5
|
+
method: string;
|
|
6
|
+
url: string;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
body?: string | Uint8Array | null;
|
|
9
|
+
expiresInSeconds?: number;
|
|
10
|
+
};
|
|
11
|
+
type S3Signer = {
|
|
12
|
+
sign: (input: S3SignerInput) => Promise<{
|
|
13
|
+
url: string;
|
|
14
|
+
headers: Record<string, string>;
|
|
15
|
+
}>;
|
|
16
|
+
presign: (input: S3SignerInput) => Promise<{
|
|
17
|
+
url: string;
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
}>;
|
|
20
|
+
};
|
|
21
|
+
type S3CompatibleStorageAdapterOptions = {
|
|
22
|
+
bucket: string;
|
|
23
|
+
endpoint: string;
|
|
24
|
+
signer: S3Signer;
|
|
25
|
+
storageKeyPrefix?: string;
|
|
26
|
+
pathStyle?: boolean;
|
|
27
|
+
uploadExpiresInSeconds?: number;
|
|
28
|
+
signedUrlExpiresInSeconds?: number;
|
|
29
|
+
directUploadThresholdBytes?: number;
|
|
30
|
+
multipartThresholdBytes?: number;
|
|
31
|
+
multipartPartSizeBytes?: number;
|
|
32
|
+
maxSingleUploadBytes?: number;
|
|
33
|
+
maxMultipartUploadBytes?: number;
|
|
34
|
+
minMultipartPartSizeBytes?: number;
|
|
35
|
+
maxMultipartPartSizeBytes?: number;
|
|
36
|
+
maxMultipartParts?: number;
|
|
37
|
+
maxStorageKeyLengthBytes?: number;
|
|
38
|
+
maxMetadataBytes?: number;
|
|
39
|
+
};
|
|
40
|
+
declare function createS3CompatibleStorageAdapter(options: S3CompatibleStorageAdapterOptions): StorageAdapter;
|
|
41
|
+
//# sourceMappingURL=s3.d.ts.map
|
|
42
|
+
//#endregion
|
|
43
|
+
export { S3CompatibleStorageAdapterOptions, S3Signer, S3SignerInput, createS3CompatibleStorageAdapter };
|
|
44
|
+
//# sourceMappingURL=s3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.d.ts","names":[],"sources":["../../../src/storage/s3.ts"],"sourcesContent":[],"mappings":";;;KAmBY,aAAA;;EAAA,GAAA,EAAA,MAAA;EAAa,OAAA,CAAA,EAGb,MAHa,CAAA,MAAA,EAAA,MAAA,CAAA;MAGb,CAAA,EAAA,MAAA,GACM,UADN,GAAA,IAAA;kBACM,CAAA,EAAA,MAAA;CAAU;AAIhB,KAAA,QAAA,GAAQ;EAAA,IAAA,EAAA,CAAA,KAAA,EACJ,aADI,EAAA,GACc,OADd,CAAA;IACJ,GAAA,EAAA,MAAA;IAAkD,OAAA,EAAA,MAAA,CAAA,MAAA,EAAA,MAAA,CAAA;;SAC/C,EAAA,CAAA,KAAA,EAAA,aAAA,EAAA,GAAkB,OAAlB,CAAA;IAAmD,GAAA,EAAA,MAAA;IAAjC,OAAA,CAAA,EAAiC,MAAjC,CAAA,MAAA,EAAA,MAAA,CAAA;EAAO,CAAA,CAAA;AAG5C,CAAA;AAqRgB,KArRJ,iCAAA,GAqRoC;EAAA,MAAA,EAAA,MAAA;UACrC,EAAA,MAAA;QACR,EApRO,QAoRP;EAAc,gBAAA,CAAA,EAAA,MAAA;;;;;;;;;;;;;;;iBAFD,gCAAA,UACL,oCACR"}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { encodeFileKey } from "../keys.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
//#region src/storage/s3.ts
|
|
5
|
+
const BYTES_IN_MIB = 1024 * 1024;
|
|
6
|
+
const BYTES_IN_GIB = 1024 * BYTES_IN_MIB;
|
|
7
|
+
const BYTES_IN_TIB = 1024 * BYTES_IN_GIB;
|
|
8
|
+
const DEFAULT_MAX_SINGLE_UPLOAD_BYTES = 5 * BYTES_IN_GIB;
|
|
9
|
+
const DEFAULT_MAX_MULTIPART_UPLOAD_BYTES = 5 * BYTES_IN_TIB;
|
|
10
|
+
const DEFAULT_MIN_MULTIPART_PART_SIZE_BYTES = 5 * BYTES_IN_MIB;
|
|
11
|
+
const DEFAULT_MAX_MULTIPART_PART_SIZE_BYTES = 5 * BYTES_IN_GIB;
|
|
12
|
+
const DEFAULT_MAX_MULTIPART_PARTS = 1e4;
|
|
13
|
+
const DEFAULT_MAX_STORAGE_KEY_LENGTH_BYTES = 1024;
|
|
14
|
+
const DEFAULT_UPLOAD_EXPIRES_IN_SECONDS = 3600;
|
|
15
|
+
const DEFAULT_SIGNED_URL_EXPIRES_IN_SECONDS = 3600;
|
|
16
|
+
const DEFAULT_MULTIPART_PART_SIZE_BYTES = 8 * BYTES_IN_MIB;
|
|
17
|
+
const MAX_PRESIGNED_EXPIRES_IN_SECONDS = 3600 * 24 * 7;
|
|
18
|
+
const normalizePrefix = (prefix) => {
|
|
19
|
+
if (!prefix) return "";
|
|
20
|
+
const segments = prefix.split("/").filter(Boolean);
|
|
21
|
+
for (const segment of segments) if (segment === "." || segment === "..") throw new Error("Storage key prefix cannot include '.' or '..' segments");
|
|
22
|
+
return segments.join("/");
|
|
23
|
+
};
|
|
24
|
+
const resolveStorageKeyFromParts = (input) => {
|
|
25
|
+
const encoded = input.fileKeyParts.length > 0 ? encodeFileKey(input.fileKeyParts) : input.fileKey;
|
|
26
|
+
return encoded.length > 0 ? encoded.split(".").join("/") : "";
|
|
27
|
+
};
|
|
28
|
+
const encodePathSegments = (value) => value.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
29
|
+
const joinPathSegments = (...segments) => {
|
|
30
|
+
return segments.filter(Boolean).map((segment) => segment.replace(/^\/+|\/+$/g, "")).join("/");
|
|
31
|
+
};
|
|
32
|
+
const parseHex = (value, expectedBytes) => {
|
|
33
|
+
const trimmed = value.trim();
|
|
34
|
+
if (!/^[0-9a-f]+$/i.test(trimmed) || trimmed.length !== expectedBytes * 2) return null;
|
|
35
|
+
return Buffer.from(trimmed, "hex");
|
|
36
|
+
};
|
|
37
|
+
const parseBase64 = (value, expectedBytes) => {
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
if (!/^[A-Za-z0-9+/=]+$/.test(trimmed)) return null;
|
|
40
|
+
const buf = Buffer.from(trimmed, "base64");
|
|
41
|
+
if (buf.length !== expectedBytes) return null;
|
|
42
|
+
return buf;
|
|
43
|
+
};
|
|
44
|
+
const normalizeChecksum = (checksum) => {
|
|
45
|
+
if (!checksum) return null;
|
|
46
|
+
const expectedBytes = checksum.algo === "md5" ? 16 : 32;
|
|
47
|
+
const raw = checksum.value ?? "";
|
|
48
|
+
const buf = parseHex(raw, expectedBytes) ?? parseBase64(raw, expectedBytes);
|
|
49
|
+
if (!buf) throw new Error("INVALID_CHECKSUM");
|
|
50
|
+
return {
|
|
51
|
+
algo: checksum.algo,
|
|
52
|
+
hex: buf.toString("hex"),
|
|
53
|
+
base64: buf.toString("base64")
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const resolveChecksumHeaders = (checksum) => {
|
|
57
|
+
const normalized = normalizeChecksum(checksum);
|
|
58
|
+
if (!normalized) return {};
|
|
59
|
+
if (normalized.algo === "md5") return { "Content-MD5": normalized.base64 };
|
|
60
|
+
return { "x-amz-checksum-sha256": normalized.base64 };
|
|
61
|
+
};
|
|
62
|
+
const normalizeEtag = (etag) => {
|
|
63
|
+
if (!etag) return null;
|
|
64
|
+
const trimmed = etag.trim().replace(/^W\//, "").replace(/^"|"$/g, "");
|
|
65
|
+
if (!/^[0-9a-f]{32}$/i.test(trimmed)) return null;
|
|
66
|
+
return trimmed.toLowerCase();
|
|
67
|
+
};
|
|
68
|
+
const computeChecksumFromResponse = async (response, algo) => {
|
|
69
|
+
if (!response.body) throw new Error("STORAGE_ERROR");
|
|
70
|
+
const hash = createHash(algo);
|
|
71
|
+
const reader = response.body.getReader();
|
|
72
|
+
while (true) {
|
|
73
|
+
const { done, value } = await reader.read();
|
|
74
|
+
if (done) break;
|
|
75
|
+
if (value) hash.update(Buffer.from(value));
|
|
76
|
+
}
|
|
77
|
+
return hash.digest("hex");
|
|
78
|
+
};
|
|
79
|
+
const parseContentLength = (value) => {
|
|
80
|
+
if (!value) return null;
|
|
81
|
+
if (!/^\d+$/.test(value)) return null;
|
|
82
|
+
return BigInt(value);
|
|
83
|
+
};
|
|
84
|
+
const getHeaderValue = (headers, name) => {
|
|
85
|
+
const direct = headers.get(name);
|
|
86
|
+
if (direct !== null) return direct;
|
|
87
|
+
const target = name.toLowerCase();
|
|
88
|
+
for (const [key, value] of headers.entries()) if (key.toLowerCase() === target) return value;
|
|
89
|
+
return null;
|
|
90
|
+
};
|
|
91
|
+
const buildObjectUrl = (input) => {
|
|
92
|
+
const url = new URL(input.endpoint);
|
|
93
|
+
const encodedKey = encodePathSegments(input.storageKey);
|
|
94
|
+
const basePath = url.pathname === "/" ? "" : url.pathname.replace(/\/+$/g, "");
|
|
95
|
+
if (input.pathStyle) {
|
|
96
|
+
const path$1 = joinPathSegments(basePath, input.bucket, encodedKey);
|
|
97
|
+
url.pathname = path$1 ? `/${path$1}` : "/";
|
|
98
|
+
return url;
|
|
99
|
+
}
|
|
100
|
+
url.hostname = `${input.bucket}.${url.hostname}`;
|
|
101
|
+
const path = joinPathSegments(basePath, encodedKey);
|
|
102
|
+
url.pathname = path ? `/${path}` : "/";
|
|
103
|
+
return url;
|
|
104
|
+
};
|
|
105
|
+
const assertExpiresInSeconds = (value) => {
|
|
106
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error("Expiration must be a positive number of seconds");
|
|
107
|
+
if (value > MAX_PRESIGNED_EXPIRES_IN_SECONDS) throw new Error("Expiration exceeds SigV4 maximum of 7 days");
|
|
108
|
+
};
|
|
109
|
+
const resolveMetadataSize = (metadata) => {
|
|
110
|
+
if (!metadata) return 0;
|
|
111
|
+
const serialized = JSON.stringify(metadata);
|
|
112
|
+
return Buffer.byteLength(serialized, "utf8");
|
|
113
|
+
};
|
|
114
|
+
const parseUploadId = (payload) => {
|
|
115
|
+
const match = payload.match(/<UploadId>([^<]+)<\/UploadId>/);
|
|
116
|
+
if (!match) return null;
|
|
117
|
+
return match[1];
|
|
118
|
+
};
|
|
119
|
+
const escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
120
|
+
const buildCompleteMultipartBody = (parts) => {
|
|
121
|
+
return `<?xml version="1.0" encoding="UTF-8"?><CompleteMultipartUpload>${[...parts].sort((a, b) => a.partNumber - b.partNumber).map((part) => `<Part><PartNumber>${part.partNumber}</PartNumber><ETag>${escapeXml(part.etag)}</ETag></Part>`).join("")}</CompleteMultipartUpload>`;
|
|
122
|
+
};
|
|
123
|
+
const resolveMultipartPartSize = (input) => {
|
|
124
|
+
let partSize = input.desiredPartSizeBytes;
|
|
125
|
+
if (partSize < input.minPartSizeBytes) partSize = input.minPartSizeBytes;
|
|
126
|
+
if (partSize > input.maxPartSizeBytes) throw new Error("Multipart part size exceeds maximum allowed size");
|
|
127
|
+
const sizeNumber = Number(input.sizeBytes);
|
|
128
|
+
const minimumPartSizeForLimit = Math.ceil(sizeNumber / input.maxParts);
|
|
129
|
+
if (minimumPartSizeForLimit > partSize) partSize = minimumPartSizeForLimit;
|
|
130
|
+
if (partSize < input.minPartSizeBytes) partSize = input.minPartSizeBytes;
|
|
131
|
+
if (partSize > input.maxPartSizeBytes) throw new Error("Multipart part size exceeds maximum allowed size");
|
|
132
|
+
if (Math.ceil(sizeNumber / partSize) > input.maxParts) throw new Error("Multipart upload exceeds maximum number of parts");
|
|
133
|
+
return partSize;
|
|
134
|
+
};
|
|
135
|
+
function createS3CompatibleStorageAdapter(options) {
|
|
136
|
+
const storageKeyPrefix = normalizePrefix(options.storageKeyPrefix ?? "");
|
|
137
|
+
const pathStyle = options.pathStyle ?? false;
|
|
138
|
+
const maxStorageKeyLengthBytes = options.maxStorageKeyLengthBytes ?? DEFAULT_MAX_STORAGE_KEY_LENGTH_BYTES;
|
|
139
|
+
const maxSingleUploadBytes = options.maxSingleUploadBytes ?? DEFAULT_MAX_SINGLE_UPLOAD_BYTES;
|
|
140
|
+
const maxMultipartUploadBytes = options.maxMultipartUploadBytes ?? DEFAULT_MAX_MULTIPART_UPLOAD_BYTES;
|
|
141
|
+
const minMultipartPartSizeBytes = options.minMultipartPartSizeBytes ?? DEFAULT_MIN_MULTIPART_PART_SIZE_BYTES;
|
|
142
|
+
const maxMultipartPartSizeBytes = options.maxMultipartPartSizeBytes ?? DEFAULT_MAX_MULTIPART_PART_SIZE_BYTES;
|
|
143
|
+
const maxMultipartParts = options.maxMultipartParts ?? DEFAULT_MAX_MULTIPART_PARTS;
|
|
144
|
+
const uploadExpiresInSeconds = options.uploadExpiresInSeconds ?? DEFAULT_UPLOAD_EXPIRES_IN_SECONDS;
|
|
145
|
+
const signedUrlExpiresInSeconds = options.signedUrlExpiresInSeconds ?? DEFAULT_SIGNED_URL_EXPIRES_IN_SECONDS;
|
|
146
|
+
const directUploadThresholdBytes = options.directUploadThresholdBytes ?? maxSingleUploadBytes;
|
|
147
|
+
const multipartThresholdBytes = options.multipartThresholdBytes ?? maxSingleUploadBytes;
|
|
148
|
+
const multipartPartSizeBytes = options.multipartPartSizeBytes ?? DEFAULT_MULTIPART_PART_SIZE_BYTES;
|
|
149
|
+
assertExpiresInSeconds(uploadExpiresInSeconds);
|
|
150
|
+
assertExpiresInSeconds(signedUrlExpiresInSeconds);
|
|
151
|
+
const resolveStorageKey = (input) => {
|
|
152
|
+
const storageKey = [storageKeyPrefix, resolveStorageKeyFromParts(input)].filter(Boolean).join("/");
|
|
153
|
+
if (storageKey.length === 0) throw new Error("Storage key cannot be empty");
|
|
154
|
+
if (Buffer.byteLength(storageKey, "utf8") > maxStorageKeyLengthBytes) throw new Error("Storage key exceeds maximum length");
|
|
155
|
+
return storageKey;
|
|
156
|
+
};
|
|
157
|
+
const buildUrl = (storageKey, queryParams = {}) => {
|
|
158
|
+
const url = buildObjectUrl({
|
|
159
|
+
endpoint: options.endpoint,
|
|
160
|
+
bucket: options.bucket,
|
|
161
|
+
storageKey,
|
|
162
|
+
pathStyle
|
|
163
|
+
});
|
|
164
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
165
|
+
if (value === void 0) continue;
|
|
166
|
+
url.searchParams.set(key, value);
|
|
167
|
+
}
|
|
168
|
+
return url;
|
|
169
|
+
};
|
|
170
|
+
const validateMetadata = (metadata) => {
|
|
171
|
+
if (!metadata) return;
|
|
172
|
+
if (options.maxMetadataBytes === void 0) return;
|
|
173
|
+
if (resolveMetadataSize(metadata) > options.maxMetadataBytes) throw new Error("Metadata exceeds maximum size");
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
name: "s3",
|
|
177
|
+
capabilities: {
|
|
178
|
+
directUpload: true,
|
|
179
|
+
multipartUpload: true,
|
|
180
|
+
signedDownload: true,
|
|
181
|
+
proxyUpload: false
|
|
182
|
+
},
|
|
183
|
+
limits: {
|
|
184
|
+
maxSingleUploadBytes,
|
|
185
|
+
maxMultipartUploadBytes,
|
|
186
|
+
minMultipartPartSizeBytes,
|
|
187
|
+
maxMultipartPartSizeBytes,
|
|
188
|
+
maxMultipartParts,
|
|
189
|
+
maxStorageKeyLengthBytes,
|
|
190
|
+
maxMetadataBytes: options.maxMetadataBytes
|
|
191
|
+
},
|
|
192
|
+
recommendations: {
|
|
193
|
+
uploadExpiresInSeconds,
|
|
194
|
+
signedUrlExpiresInSeconds,
|
|
195
|
+
directUploadThresholdBytes,
|
|
196
|
+
multipartThresholdBytes,
|
|
197
|
+
multipartPartSizeBytes
|
|
198
|
+
},
|
|
199
|
+
resolveStorageKey,
|
|
200
|
+
initUpload: async ({ fileKey, fileKeyParts, sizeBytes, contentType, metadata, checksum }) => {
|
|
201
|
+
validateMetadata(metadata);
|
|
202
|
+
const storageKey = resolveStorageKey({
|
|
203
|
+
fileKey,
|
|
204
|
+
fileKeyParts
|
|
205
|
+
});
|
|
206
|
+
const expiresAt = new Date(Date.now() + uploadExpiresInSeconds * 1e3);
|
|
207
|
+
const sizeNumber = Number(sizeBytes);
|
|
208
|
+
if (Number.isNaN(sizeNumber) || sizeNumber < 0) throw new Error("Upload size must be a non-negative number");
|
|
209
|
+
const canMultipart = sizeNumber <= maxMultipartUploadBytes;
|
|
210
|
+
const canSingle = sizeNumber <= maxSingleUploadBytes;
|
|
211
|
+
if (!canMultipart) throw new Error("Upload exceeds maximum multipart size");
|
|
212
|
+
if (sizeNumber >= multipartThresholdBytes || !(canSingle && sizeNumber <= directUploadThresholdBytes)) {
|
|
213
|
+
if (!canMultipart) throw new Error("Upload exceeds maximum multipart size");
|
|
214
|
+
const partSizeBytes = resolveMultipartPartSize({
|
|
215
|
+
sizeBytes,
|
|
216
|
+
desiredPartSizeBytes: multipartPartSizeBytes,
|
|
217
|
+
minPartSizeBytes: minMultipartPartSizeBytes,
|
|
218
|
+
maxPartSizeBytes: maxMultipartPartSizeBytes,
|
|
219
|
+
maxParts: maxMultipartParts
|
|
220
|
+
});
|
|
221
|
+
const createUrl = buildUrl(storageKey, { uploads: "" });
|
|
222
|
+
const signed$1 = await options.signer.sign({
|
|
223
|
+
method: "POST",
|
|
224
|
+
url: createUrl.toString(),
|
|
225
|
+
headers: { "Content-Type": contentType }
|
|
226
|
+
});
|
|
227
|
+
const response = await fetch(signed$1.url, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: signed$1.headers
|
|
230
|
+
});
|
|
231
|
+
if (!response.ok) throw new Error(`Failed to create multipart upload (${response.status})`);
|
|
232
|
+
const uploadId = parseUploadId(await response.text());
|
|
233
|
+
if (!uploadId) throw new Error("Failed to parse multipart upload id");
|
|
234
|
+
return {
|
|
235
|
+
strategy: "direct-multipart",
|
|
236
|
+
storageKey,
|
|
237
|
+
storageUploadId: uploadId,
|
|
238
|
+
partSizeBytes,
|
|
239
|
+
expiresAt
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (!canSingle) throw new Error("Upload exceeds maximum single upload size");
|
|
243
|
+
const uploadUrl = buildUrl(storageKey);
|
|
244
|
+
const checksumHeaders = resolveChecksumHeaders(checksum);
|
|
245
|
+
const signed = await options.signer.presign({
|
|
246
|
+
method: "PUT",
|
|
247
|
+
url: uploadUrl.toString(),
|
|
248
|
+
headers: {
|
|
249
|
+
"Content-Type": contentType,
|
|
250
|
+
...checksumHeaders
|
|
251
|
+
},
|
|
252
|
+
expiresInSeconds: uploadExpiresInSeconds
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
strategy: "direct-single",
|
|
256
|
+
storageKey,
|
|
257
|
+
expiresAt,
|
|
258
|
+
uploadUrl: signed.url,
|
|
259
|
+
uploadHeaders: signed.headers
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
getPartUploadUrls: async ({ storageKey, storageUploadId, partNumbers }) => {
|
|
263
|
+
for (const partNumber of partNumbers) if (partNumber < 1 || partNumber > maxMultipartParts) throw new Error("INVALID_REQUEST");
|
|
264
|
+
return await Promise.all(partNumbers.map(async (partNumber) => {
|
|
265
|
+
const url = buildUrl(storageKey, {
|
|
266
|
+
partNumber: String(partNumber),
|
|
267
|
+
uploadId: storageUploadId
|
|
268
|
+
});
|
|
269
|
+
const signed = await options.signer.presign({
|
|
270
|
+
method: "PUT",
|
|
271
|
+
url: url.toString(),
|
|
272
|
+
expiresInSeconds: uploadExpiresInSeconds
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
partNumber,
|
|
276
|
+
url: signed.url,
|
|
277
|
+
headers: signed.headers
|
|
278
|
+
};
|
|
279
|
+
}));
|
|
280
|
+
},
|
|
281
|
+
completeMultipartUpload: async ({ storageKey, storageUploadId, parts }) => {
|
|
282
|
+
const url = buildUrl(storageKey, { uploadId: storageUploadId });
|
|
283
|
+
const body = buildCompleteMultipartBody(parts);
|
|
284
|
+
const signed = await options.signer.sign({
|
|
285
|
+
method: "POST",
|
|
286
|
+
url: url.toString(),
|
|
287
|
+
headers: { "Content-Type": "application/xml" },
|
|
288
|
+
body
|
|
289
|
+
});
|
|
290
|
+
const response = await fetch(signed.url, {
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: signed.headers,
|
|
293
|
+
body
|
|
294
|
+
});
|
|
295
|
+
if (!response.ok) throw new Error(`Failed to complete multipart upload (${response.status})`);
|
|
296
|
+
return { etag: response.headers.get("ETag") ?? void 0 };
|
|
297
|
+
},
|
|
298
|
+
abortMultipartUpload: async ({ storageKey, storageUploadId }) => {
|
|
299
|
+
const url = buildUrl(storageKey, { uploadId: storageUploadId });
|
|
300
|
+
const signed = await options.signer.sign({
|
|
301
|
+
method: "DELETE",
|
|
302
|
+
url: url.toString()
|
|
303
|
+
});
|
|
304
|
+
const response = await fetch(signed.url, {
|
|
305
|
+
method: "DELETE",
|
|
306
|
+
headers: signed.headers
|
|
307
|
+
});
|
|
308
|
+
if (!response.ok) throw new Error(`Failed to abort multipart upload (${response.status})`);
|
|
309
|
+
},
|
|
310
|
+
finalizeUpload: async ({ storageKey, expectedSizeBytes, checksum }) => {
|
|
311
|
+
const url = buildUrl(storageKey);
|
|
312
|
+
const signed = await options.signer.sign({
|
|
313
|
+
method: "HEAD",
|
|
314
|
+
url: url.toString()
|
|
315
|
+
});
|
|
316
|
+
const response = await fetch(signed.url, {
|
|
317
|
+
method: "HEAD",
|
|
318
|
+
headers: signed.headers
|
|
319
|
+
});
|
|
320
|
+
if (!response.ok) throw new Error("STORAGE_ERROR");
|
|
321
|
+
const sizeBytes = parseContentLength(getHeaderValue(response.headers, "content-length"));
|
|
322
|
+
if (sizeBytes === null) throw new Error("STORAGE_ERROR");
|
|
323
|
+
if (sizeBytes !== expectedSizeBytes) throw new Error("INVALID_CHECKSUM");
|
|
324
|
+
const normalized = normalizeChecksum(checksum);
|
|
325
|
+
if (normalized) if (normalized.algo === "md5") {
|
|
326
|
+
const etag = normalizeEtag(getHeaderValue(response.headers, "etag"));
|
|
327
|
+
if (etag) {
|
|
328
|
+
if (etag !== normalized.hex) throw new Error("INVALID_CHECKSUM");
|
|
329
|
+
} else {
|
|
330
|
+
const signedGet = await options.signer.sign({
|
|
331
|
+
method: "GET",
|
|
332
|
+
url: buildUrl(storageKey).toString()
|
|
333
|
+
});
|
|
334
|
+
const getResponse = await fetch(signedGet.url, {
|
|
335
|
+
method: "GET",
|
|
336
|
+
headers: signedGet.headers
|
|
337
|
+
});
|
|
338
|
+
if (!getResponse.ok) throw new Error("STORAGE_ERROR");
|
|
339
|
+
if (await computeChecksumFromResponse(getResponse, "md5") !== normalized.hex) throw new Error("INVALID_CHECKSUM");
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
const checksumHeader = getHeaderValue(response.headers, "x-amz-checksum-sha256");
|
|
343
|
+
if (checksumHeader) {
|
|
344
|
+
const remote = parseBase64(checksumHeader, 32);
|
|
345
|
+
if (!remote || remote.toString("base64") !== normalized.base64) throw new Error("INVALID_CHECKSUM");
|
|
346
|
+
} else {
|
|
347
|
+
const signedGet = await options.signer.sign({
|
|
348
|
+
method: "GET",
|
|
349
|
+
url: buildUrl(storageKey).toString()
|
|
350
|
+
});
|
|
351
|
+
const getResponse = await fetch(signedGet.url, {
|
|
352
|
+
method: "GET",
|
|
353
|
+
headers: signedGet.headers
|
|
354
|
+
});
|
|
355
|
+
if (!getResponse.ok) throw new Error("STORAGE_ERROR");
|
|
356
|
+
if (await computeChecksumFromResponse(getResponse, "sha256") !== normalized.hex) throw new Error("INVALID_CHECKSUM");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
sizeBytes,
|
|
361
|
+
etag: getHeaderValue(response.headers, "etag") ?? void 0
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
deleteObject: async ({ storageKey }) => {
|
|
365
|
+
const url = buildUrl(storageKey);
|
|
366
|
+
const signed = await options.signer.sign({
|
|
367
|
+
method: "DELETE",
|
|
368
|
+
url: url.toString()
|
|
369
|
+
});
|
|
370
|
+
const response = await fetch(signed.url, {
|
|
371
|
+
method: "DELETE",
|
|
372
|
+
headers: signed.headers
|
|
373
|
+
});
|
|
374
|
+
if (!response.ok) throw new Error(`Failed to delete object (${response.status})`);
|
|
375
|
+
},
|
|
376
|
+
getDownloadUrl: async ({ storageKey, expiresInSeconds, contentDisposition, contentType }) => {
|
|
377
|
+
assertExpiresInSeconds(expiresInSeconds);
|
|
378
|
+
const url = buildUrl(storageKey, {
|
|
379
|
+
"response-content-disposition": contentDisposition,
|
|
380
|
+
"response-content-type": contentType
|
|
381
|
+
});
|
|
382
|
+
const signed = await options.signer.presign({
|
|
383
|
+
method: "GET",
|
|
384
|
+
url: url.toString(),
|
|
385
|
+
expiresInSeconds
|
|
386
|
+
});
|
|
387
|
+
return {
|
|
388
|
+
url: signed.url,
|
|
389
|
+
headers: signed.headers,
|
|
390
|
+
expiresAt: new Date(Date.now() + expiresInSeconds * 1e3)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
//#endregion
|
|
397
|
+
export { createS3CompatibleStorageAdapter };
|
|
398
|
+
//# sourceMappingURL=s3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.js","names":["path","signed"],"sources":["../../../src/storage/s3.ts"],"sourcesContent":["import { encodeFileKey, type FileKeyEncoded, type FileKeyParts } from \"../keys\";\nimport { createHash } from \"node:crypto\";\nimport type { StorageAdapter, UploadChecksum } from \"./types\";\n\nconst BYTES_IN_MIB = 1024 * 1024;\nconst BYTES_IN_GIB = 1024 * BYTES_IN_MIB;\nconst BYTES_IN_TIB = 1024 * BYTES_IN_GIB;\n\nconst DEFAULT_MAX_SINGLE_UPLOAD_BYTES = 5 * BYTES_IN_GIB;\nconst DEFAULT_MAX_MULTIPART_UPLOAD_BYTES = 5 * BYTES_IN_TIB;\nconst DEFAULT_MIN_MULTIPART_PART_SIZE_BYTES = 5 * BYTES_IN_MIB;\nconst DEFAULT_MAX_MULTIPART_PART_SIZE_BYTES = 5 * BYTES_IN_GIB;\nconst DEFAULT_MAX_MULTIPART_PARTS = 10_000;\nconst DEFAULT_MAX_STORAGE_KEY_LENGTH_BYTES = 1024;\nconst DEFAULT_UPLOAD_EXPIRES_IN_SECONDS = 60 * 60;\nconst DEFAULT_SIGNED_URL_EXPIRES_IN_SECONDS = 60 * 60;\nconst DEFAULT_MULTIPART_PART_SIZE_BYTES = 8 * BYTES_IN_MIB;\nconst MAX_PRESIGNED_EXPIRES_IN_SECONDS = 60 * 60 * 24 * 7;\n\nexport type S3SignerInput = {\n method: string;\n url: string;\n headers?: Record<string, string>;\n body?: string | Uint8Array | null;\n expiresInSeconds?: number;\n};\n\nexport type S3Signer = {\n sign: (input: S3SignerInput) => Promise<{ url: string; headers: Record<string, string> }>;\n presign: (input: S3SignerInput) => Promise<{ url: string; headers?: Record<string, string> }>;\n};\n\nexport type S3CompatibleStorageAdapterOptions = {\n bucket: string;\n endpoint: string;\n signer: S3Signer;\n storageKeyPrefix?: string;\n pathStyle?: boolean;\n uploadExpiresInSeconds?: number;\n signedUrlExpiresInSeconds?: number;\n directUploadThresholdBytes?: number;\n multipartThresholdBytes?: number;\n multipartPartSizeBytes?: number;\n maxSingleUploadBytes?: number;\n maxMultipartUploadBytes?: number;\n minMultipartPartSizeBytes?: number;\n maxMultipartPartSizeBytes?: number;\n maxMultipartParts?: number;\n maxStorageKeyLengthBytes?: number;\n maxMetadataBytes?: number;\n};\n\nconst normalizePrefix = (prefix: string): string => {\n if (!prefix) {\n return \"\";\n }\n\n const segments = prefix.split(\"/\").filter(Boolean);\n for (const segment of segments) {\n if (segment === \".\" || segment === \"..\") {\n throw new Error(\"Storage key prefix cannot include '.' or '..' segments\");\n }\n }\n\n return segments.join(\"/\");\n};\n\nconst resolveStorageKeyFromParts = (input: {\n fileKey: FileKeyEncoded;\n fileKeyParts: FileKeyParts;\n}): string => {\n const encoded = input.fileKeyParts.length > 0 ? encodeFileKey(input.fileKeyParts) : input.fileKey;\n return encoded.length > 0 ? encoded.split(\".\").join(\"/\") : \"\";\n};\n\nconst encodePathSegments = (value: string) =>\n value\n .split(\"/\")\n .map((segment) => encodeURIComponent(segment))\n .join(\"/\");\n\nconst joinPathSegments = (...segments: (string | undefined)[]) => {\n const trimmed = segments.filter(Boolean).map((segment) => segment!.replace(/^\\/+|\\/+$/g, \"\"));\n return trimmed.join(\"/\");\n};\n\nconst parseHex = (value: string, expectedBytes: number) => {\n const trimmed = value.trim();\n if (!/^[0-9a-f]+$/i.test(trimmed) || trimmed.length !== expectedBytes * 2) {\n return null;\n }\n return Buffer.from(trimmed, \"hex\");\n};\n\nconst parseBase64 = (value: string, expectedBytes: number) => {\n const trimmed = value.trim();\n if (!/^[A-Za-z0-9+/=]+$/.test(trimmed)) {\n return null;\n }\n const buf = Buffer.from(trimmed, \"base64\");\n if (buf.length !== expectedBytes) {\n return null;\n }\n return buf;\n};\n\nconst normalizeChecksum = (checksum?: UploadChecksum | null) => {\n if (!checksum) {\n return null;\n }\n\n const expectedBytes = checksum.algo === \"md5\" ? 16 : 32;\n const raw = checksum.value ?? \"\";\n const buf = parseHex(raw, expectedBytes) ?? parseBase64(raw, expectedBytes);\n if (!buf) {\n throw new Error(\"INVALID_CHECKSUM\");\n }\n\n return {\n algo: checksum.algo,\n hex: buf.toString(\"hex\"),\n base64: buf.toString(\"base64\"),\n };\n};\n\nconst resolveChecksumHeaders = (checksum?: UploadChecksum | null): Record<string, string> => {\n const normalized = normalizeChecksum(checksum);\n if (!normalized) {\n return {};\n }\n\n if (normalized.algo === \"md5\") {\n return { \"Content-MD5\": normalized.base64 };\n }\n\n return { \"x-amz-checksum-sha256\": normalized.base64 };\n};\n\nconst normalizeEtag = (etag: string | null) => {\n if (!etag) {\n return null;\n }\n\n const trimmed = etag.trim().replace(/^W\\//, \"\").replace(/^\"|\"$/g, \"\");\n if (!/^[0-9a-f]{32}$/i.test(trimmed)) {\n return null;\n }\n\n return trimmed.toLowerCase();\n};\n\nconst computeChecksumFromResponse = async (\n response: Response,\n algo: \"md5\" | \"sha256\",\n): Promise<string> => {\n if (!response.body) {\n throw new Error(\"STORAGE_ERROR\");\n }\n\n const hash = createHash(algo);\n const reader = response.body.getReader();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n if (value) {\n hash.update(Buffer.from(value));\n }\n }\n\n return hash.digest(\"hex\");\n};\n\nconst parseContentLength = (value: string | null) => {\n if (!value) {\n return null;\n }\n if (!/^\\d+$/.test(value)) {\n return null;\n }\n return BigInt(value);\n};\n\nconst getHeaderValue = (headers: Headers, name: string) => {\n const direct = headers.get(name);\n if (direct !== null) {\n return direct;\n }\n const target = name.toLowerCase();\n for (const [key, value] of headers.entries()) {\n if (key.toLowerCase() === target) {\n return value;\n }\n }\n return null;\n};\n\nconst buildObjectUrl = (input: {\n endpoint: string;\n bucket: string;\n storageKey: string;\n pathStyle: boolean;\n}) => {\n const url = new URL(input.endpoint);\n const encodedKey = encodePathSegments(input.storageKey);\n const basePath = url.pathname === \"/\" ? \"\" : url.pathname.replace(/\\/+$/g, \"\");\n\n if (input.pathStyle) {\n const path = joinPathSegments(basePath, input.bucket, encodedKey);\n url.pathname = path ? `/${path}` : \"/\";\n return url;\n }\n\n url.hostname = `${input.bucket}.${url.hostname}`;\n const path = joinPathSegments(basePath, encodedKey);\n url.pathname = path ? `/${path}` : \"/\";\n return url;\n};\n\nconst assertExpiresInSeconds = (value: number) => {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(\"Expiration must be a positive number of seconds\");\n }\n if (value > MAX_PRESIGNED_EXPIRES_IN_SECONDS) {\n throw new Error(\"Expiration exceeds SigV4 maximum of 7 days\");\n }\n};\n\nconst resolveMetadataSize = (metadata?: Record<string, unknown> | null) => {\n if (!metadata) {\n return 0;\n }\n\n const serialized = JSON.stringify(metadata);\n return Buffer.byteLength(serialized, \"utf8\");\n};\n\nconst parseUploadId = (payload: string) => {\n const match = payload.match(/<UploadId>([^<]+)<\\/UploadId>/);\n if (!match) {\n return null;\n }\n\n return match[1];\n};\n\nconst escapeXml = (value: string) =>\n value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n\nconst buildCompleteMultipartBody = (parts: { partNumber: number; etag: string }[]) => {\n const sorted = [...parts].sort((a, b) => a.partNumber - b.partNumber);\n const entries = sorted\n .map(\n (part) =>\n `<Part><PartNumber>${part.partNumber}</PartNumber><ETag>${escapeXml(\n part.etag,\n )}</ETag></Part>`,\n )\n .join(\"\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?><CompleteMultipartUpload>${entries}</CompleteMultipartUpload>`;\n};\n\nconst resolveMultipartPartSize = (input: {\n sizeBytes: bigint;\n desiredPartSizeBytes: number;\n minPartSizeBytes: number;\n maxPartSizeBytes: number;\n maxParts: number;\n}) => {\n let partSize = input.desiredPartSizeBytes;\n\n if (partSize < input.minPartSizeBytes) {\n partSize = input.minPartSizeBytes;\n }\n\n if (partSize > input.maxPartSizeBytes) {\n throw new Error(\"Multipart part size exceeds maximum allowed size\");\n }\n\n const sizeNumber = Number(input.sizeBytes);\n const minimumPartSizeForLimit = Math.ceil(sizeNumber / input.maxParts);\n\n if (minimumPartSizeForLimit > partSize) {\n partSize = minimumPartSizeForLimit;\n }\n\n if (partSize < input.minPartSizeBytes) {\n partSize = input.minPartSizeBytes;\n }\n\n if (partSize > input.maxPartSizeBytes) {\n throw new Error(\"Multipart part size exceeds maximum allowed size\");\n }\n\n const partCount = Math.ceil(sizeNumber / partSize);\n if (partCount > input.maxParts) {\n throw new Error(\"Multipart upload exceeds maximum number of parts\");\n }\n\n return partSize;\n};\n\nexport function createS3CompatibleStorageAdapter(\n options: S3CompatibleStorageAdapterOptions,\n): StorageAdapter {\n const storageKeyPrefix = normalizePrefix(options.storageKeyPrefix ?? \"\");\n const pathStyle = options.pathStyle ?? false;\n const maxStorageKeyLengthBytes =\n options.maxStorageKeyLengthBytes ?? DEFAULT_MAX_STORAGE_KEY_LENGTH_BYTES;\n const maxSingleUploadBytes = options.maxSingleUploadBytes ?? DEFAULT_MAX_SINGLE_UPLOAD_BYTES;\n const maxMultipartUploadBytes =\n options.maxMultipartUploadBytes ?? DEFAULT_MAX_MULTIPART_UPLOAD_BYTES;\n const minMultipartPartSizeBytes =\n options.minMultipartPartSizeBytes ?? DEFAULT_MIN_MULTIPART_PART_SIZE_BYTES;\n const maxMultipartPartSizeBytes =\n options.maxMultipartPartSizeBytes ?? DEFAULT_MAX_MULTIPART_PART_SIZE_BYTES;\n const maxMultipartParts = options.maxMultipartParts ?? DEFAULT_MAX_MULTIPART_PARTS;\n const uploadExpiresInSeconds =\n options.uploadExpiresInSeconds ?? DEFAULT_UPLOAD_EXPIRES_IN_SECONDS;\n const signedUrlExpiresInSeconds =\n options.signedUrlExpiresInSeconds ?? DEFAULT_SIGNED_URL_EXPIRES_IN_SECONDS;\n const directUploadThresholdBytes = options.directUploadThresholdBytes ?? maxSingleUploadBytes;\n const multipartThresholdBytes = options.multipartThresholdBytes ?? maxSingleUploadBytes;\n const multipartPartSizeBytes =\n options.multipartPartSizeBytes ?? DEFAULT_MULTIPART_PART_SIZE_BYTES;\n\n assertExpiresInSeconds(uploadExpiresInSeconds);\n assertExpiresInSeconds(signedUrlExpiresInSeconds);\n\n const resolveStorageKey = (input: { fileKey: FileKeyEncoded; fileKeyParts: FileKeyParts }) => {\n const baseKey = resolveStorageKeyFromParts(input);\n const storageKey = [storageKeyPrefix, baseKey].filter(Boolean).join(\"/\");\n\n if (storageKey.length === 0) {\n throw new Error(\"Storage key cannot be empty\");\n }\n\n if (Buffer.byteLength(storageKey, \"utf8\") > maxStorageKeyLengthBytes) {\n throw new Error(\"Storage key exceeds maximum length\");\n }\n\n return storageKey;\n };\n\n const buildUrl = (storageKey: string, queryParams: Record<string, string | undefined> = {}) => {\n const url = buildObjectUrl({\n endpoint: options.endpoint,\n bucket: options.bucket,\n storageKey,\n pathStyle,\n });\n\n for (const [key, value] of Object.entries(queryParams)) {\n if (value === undefined) {\n continue;\n }\n url.searchParams.set(key, value);\n }\n\n return url;\n };\n\n const validateMetadata = (metadata?: Record<string, unknown> | null) => {\n if (!metadata) {\n return;\n }\n\n if (options.maxMetadataBytes === undefined) {\n return;\n }\n\n if (resolveMetadataSize(metadata) > options.maxMetadataBytes) {\n throw new Error(\"Metadata exceeds maximum size\");\n }\n };\n\n return {\n name: \"s3\",\n capabilities: {\n directUpload: true,\n multipartUpload: true,\n signedDownload: true,\n proxyUpload: false,\n },\n limits: {\n maxSingleUploadBytes,\n maxMultipartUploadBytes,\n minMultipartPartSizeBytes,\n maxMultipartPartSizeBytes,\n maxMultipartParts,\n maxStorageKeyLengthBytes,\n maxMetadataBytes: options.maxMetadataBytes,\n },\n recommendations: {\n uploadExpiresInSeconds,\n signedUrlExpiresInSeconds,\n directUploadThresholdBytes,\n multipartThresholdBytes,\n multipartPartSizeBytes,\n },\n resolveStorageKey,\n initUpload: async ({ fileKey, fileKeyParts, sizeBytes, contentType, metadata, checksum }) => {\n validateMetadata(metadata);\n const storageKey = resolveStorageKey({ fileKey, fileKeyParts });\n\n const expiresAt = new Date(Date.now() + uploadExpiresInSeconds * 1000);\n const sizeNumber = Number(sizeBytes);\n\n if (Number.isNaN(sizeNumber) || sizeNumber < 0) {\n throw new Error(\"Upload size must be a non-negative number\");\n }\n\n const canMultipart = sizeNumber <= maxMultipartUploadBytes;\n const canSingle = sizeNumber <= maxSingleUploadBytes;\n\n if (!canMultipart) {\n throw new Error(\"Upload exceeds maximum multipart size\");\n }\n\n const prefersMultipart = sizeNumber >= multipartThresholdBytes;\n const canUseSingle = canSingle && sizeNumber <= directUploadThresholdBytes;\n\n if (prefersMultipart || !canUseSingle) {\n if (!canMultipart) {\n throw new Error(\"Upload exceeds maximum multipart size\");\n }\n\n const partSizeBytes = resolveMultipartPartSize({\n sizeBytes,\n desiredPartSizeBytes: multipartPartSizeBytes,\n minPartSizeBytes: minMultipartPartSizeBytes,\n maxPartSizeBytes: maxMultipartPartSizeBytes,\n maxParts: maxMultipartParts,\n });\n\n const createUrl = buildUrl(storageKey, { uploads: \"\" });\n const signed = await options.signer.sign({\n method: \"POST\",\n url: createUrl.toString(),\n headers: {\n \"Content-Type\": contentType,\n },\n });\n\n const response = await fetch(signed.url, {\n method: \"POST\",\n headers: signed.headers,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create multipart upload (${response.status})`);\n }\n\n const payload = await response.text();\n const uploadId = parseUploadId(payload);\n if (!uploadId) {\n throw new Error(\"Failed to parse multipart upload id\");\n }\n\n return {\n strategy: \"direct-multipart\",\n storageKey,\n storageUploadId: uploadId,\n partSizeBytes,\n expiresAt,\n };\n }\n\n if (!canSingle) {\n throw new Error(\"Upload exceeds maximum single upload size\");\n }\n\n const uploadUrl = buildUrl(storageKey);\n const checksumHeaders = resolveChecksumHeaders(checksum);\n const signed = await options.signer.presign({\n method: \"PUT\",\n url: uploadUrl.toString(),\n headers: {\n \"Content-Type\": contentType,\n ...checksumHeaders,\n },\n expiresInSeconds: uploadExpiresInSeconds,\n });\n\n return {\n strategy: \"direct-single\",\n storageKey,\n expiresAt,\n uploadUrl: signed.url,\n uploadHeaders: signed.headers,\n };\n },\n getPartUploadUrls: async ({ storageKey, storageUploadId, partNumbers }) => {\n for (const partNumber of partNumbers) {\n if (partNumber < 1 || partNumber > maxMultipartParts) {\n throw new Error(\"INVALID_REQUEST\");\n }\n }\n\n const urls = await Promise.all(\n partNumbers.map(async (partNumber) => {\n const url = buildUrl(storageKey, {\n partNumber: String(partNumber),\n uploadId: storageUploadId,\n });\n const signed = await options.signer.presign({\n method: \"PUT\",\n url: url.toString(),\n expiresInSeconds: uploadExpiresInSeconds,\n });\n\n return {\n partNumber,\n url: signed.url,\n headers: signed.headers,\n };\n }),\n );\n\n return urls;\n },\n completeMultipartUpload: async ({ storageKey, storageUploadId, parts }) => {\n const url = buildUrl(storageKey, { uploadId: storageUploadId });\n const body = buildCompleteMultipartBody(parts);\n const signed = await options.signer.sign({\n method: \"POST\",\n url: url.toString(),\n headers: {\n \"Content-Type\": \"application/xml\",\n },\n body,\n });\n\n const response = await fetch(signed.url, {\n method: \"POST\",\n headers: signed.headers,\n body,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to complete multipart upload (${response.status})`);\n }\n\n const etag = response.headers.get(\"ETag\") ?? undefined;\n return { etag };\n },\n abortMultipartUpload: async ({ storageKey, storageUploadId }) => {\n const url = buildUrl(storageKey, { uploadId: storageUploadId });\n const signed = await options.signer.sign({\n method: \"DELETE\",\n url: url.toString(),\n });\n\n const response = await fetch(signed.url, {\n method: \"DELETE\",\n headers: signed.headers,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to abort multipart upload (${response.status})`);\n }\n },\n finalizeUpload: async ({ storageKey, expectedSizeBytes, checksum }) => {\n const url = buildUrl(storageKey);\n const signed = await options.signer.sign({\n method: \"HEAD\",\n url: url.toString(),\n });\n\n const response = await fetch(signed.url, {\n method: \"HEAD\",\n headers: signed.headers,\n });\n\n if (!response.ok) {\n throw new Error(\"STORAGE_ERROR\");\n }\n\n const sizeHeader = getHeaderValue(response.headers, \"content-length\");\n const sizeBytes = parseContentLength(sizeHeader);\n if (sizeBytes === null) {\n throw new Error(\"STORAGE_ERROR\");\n }\n if (sizeBytes !== expectedSizeBytes) {\n throw new Error(\"INVALID_CHECKSUM\");\n }\n\n const normalized = normalizeChecksum(checksum);\n if (normalized) {\n if (normalized.algo === \"md5\") {\n const etag = normalizeEtag(getHeaderValue(response.headers, \"etag\"));\n if (etag) {\n if (etag !== normalized.hex) {\n throw new Error(\"INVALID_CHECKSUM\");\n }\n } else {\n const signedGet = await options.signer.sign({\n method: \"GET\",\n url: buildUrl(storageKey).toString(),\n });\n const getResponse = await fetch(signedGet.url, {\n method: \"GET\",\n headers: signedGet.headers,\n });\n if (!getResponse.ok) {\n throw new Error(\"STORAGE_ERROR\");\n }\n const computed = await computeChecksumFromResponse(getResponse, \"md5\");\n if (computed !== normalized.hex) {\n throw new Error(\"INVALID_CHECKSUM\");\n }\n }\n } else {\n const checksumHeader = getHeaderValue(response.headers, \"x-amz-checksum-sha256\");\n if (checksumHeader) {\n const remote = parseBase64(checksumHeader, 32);\n if (!remote || remote.toString(\"base64\") !== normalized.base64) {\n throw new Error(\"INVALID_CHECKSUM\");\n }\n } else {\n const signedGet = await options.signer.sign({\n method: \"GET\",\n url: buildUrl(storageKey).toString(),\n });\n const getResponse = await fetch(signedGet.url, {\n method: \"GET\",\n headers: signedGet.headers,\n });\n if (!getResponse.ok) {\n throw new Error(\"STORAGE_ERROR\");\n }\n const computed = await computeChecksumFromResponse(getResponse, \"sha256\");\n if (computed !== normalized.hex) {\n throw new Error(\"INVALID_CHECKSUM\");\n }\n }\n }\n }\n\n const etag = getHeaderValue(response.headers, \"etag\");\n return { sizeBytes, etag: etag ?? undefined };\n },\n deleteObject: async ({ storageKey }) => {\n const url = buildUrl(storageKey);\n const signed = await options.signer.sign({\n method: \"DELETE\",\n url: url.toString(),\n });\n\n const response = await fetch(signed.url, {\n method: \"DELETE\",\n headers: signed.headers,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete object (${response.status})`);\n }\n },\n getDownloadUrl: async ({ storageKey, expiresInSeconds, contentDisposition, contentType }) => {\n assertExpiresInSeconds(expiresInSeconds);\n\n const url = buildUrl(storageKey, {\n \"response-content-disposition\": contentDisposition,\n \"response-content-type\": contentType,\n });\n\n const signed = await options.signer.presign({\n method: \"GET\",\n url: url.toString(),\n expiresInSeconds,\n });\n\n return {\n url: signed.url,\n headers: signed.headers,\n expiresAt: new Date(Date.now() + expiresInSeconds * 1000),\n };\n },\n };\n}\n"],"mappings":"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"}
|