@fragno-dev/upload 0.1.1 → 0.1.3
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/README.md +148 -12
- package/dist/browser/client/clients.js +17 -9
- package/dist/browser/client/clients.js.map +1 -1
- package/dist/browser/client/helpers.d.ts +15 -6
- package/dist/browser/client/helpers.d.ts.map +1 -1
- package/dist/browser/client/helpers.js +176 -30
- package/dist/browser/client/helpers.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/{@nanostores_query@0.3.4_nanostores@1.1.0 → @nanostores_query@0.3.4_nanostores@1.2.0}/node_modules/@nanostores/query/dist/nanoquery.js +6 -6
- package/dist/browser/client/node_modules/.pnpm/{@nanostores_query@0.3.4_nanostores@1.1.0 → @nanostores_query@0.3.4_nanostores@1.2.0}/node_modules/@nanostores/query/dist/nanoquery.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/{@nanostores_solid@1.1.1_nanostores@1.1.0_solid-js@1.9.10 → @nanostores_solid@1.1.1_nanostores@1.2.0_solid-js@1.9.10}/node_modules/@nanostores/solid/dist/index.js +2 -2
- package/dist/browser/client/node_modules/.pnpm/{@nanostores_solid@1.1.1_nanostores@1.1.0_solid-js@1.9.10 → @nanostores_solid@1.1.1_nanostores@1.2.0_solid-js@1.9.10}/node_modules/@nanostores/solid/dist/index.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/atom/index.js +2 -1
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/atom/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/clean-stores/index.js +6 -0
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/clean-stores/index.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/computed/index.js +8 -5
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/computed/index.js.map +1 -0
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/lifecycle/index.js +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/lifecycle/index.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/listen-keys/index.js +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/listen-keys/index.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/map/index.js +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/map/index.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/task/index.js +1 -1
- package/dist/browser/client/node_modules/.pnpm/{nanostores@1.1.0 → nanostores@1.2.0}/node_modules/nanostores/task/index.js.map +1 -1
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/warn/index.js +16 -0
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/warn/index.js.map +1 -0
- package/dist/browser/client/packages/fragment-upload/src/definition.js +1 -42
- package/dist/browser/client/packages/fragment-upload/src/definition.js.map +1 -1
- package/dist/browser/client/packages/fragment-upload/src/routes/files.js +12 -5
- package/dist/browser/client/packages/fragment-upload/src/routes/files.js.map +1 -1
- package/dist/browser/client/packages/fragment-upload/src/routes/shared.js +3 -4
- package/dist/browser/client/packages/fragment-upload/src/routes/shared.js.map +1 -1
- package/dist/browser/client/packages/fragment-upload/src/routes/uploads.js +32 -21
- package/dist/browser/client/packages/fragment-upload/src/routes/uploads.js.map +1 -1
- package/dist/browser/client/packages/fragment-upload/src/schema.js +33 -3
- package/dist/browser/client/packages/fragment-upload/src/schema.js.map +1 -1
- package/dist/browser/client/packages/fragment-upload/src/types.d.ts +1 -2
- package/dist/browser/client/packages/fragment-upload/src/types.d.ts.map +1 -1
- package/dist/browser/client/packages/fragno/dist/client/client.js +28 -12
- package/dist/browser/client/packages/fragno/dist/client/client.js.map +1 -1
- package/dist/browser/client/packages/fragno/dist/client/client.svelte.js +11 -3
- package/dist/browser/client/packages/fragno/dist/client/client.svelte.js.map +1 -1
- package/dist/browser/client/packages/fragno/dist/client/react.js +104 -12
- package/dist/browser/client/packages/fragno/dist/client/react.js.map +1 -1
- package/dist/browser/client/packages/fragno/dist/client/solid.js +25 -11
- package/dist/browser/client/packages/fragno/dist/client/solid.js.map +1 -1
- package/dist/browser/client/packages/fragno/dist/client/vanilla.js +21 -1
- package/dist/browser/client/packages/fragno/dist/client/vanilla.js.map +1 -1
- package/dist/browser/client/packages/fragno/dist/client/vue.js +19 -11
- package/dist/browser/client/packages/fragno/dist/client/vue.js.map +1 -1
- package/dist/browser/client/react.d.ts +215 -192
- package/dist/browser/client/react.d.ts.map +1 -1
- package/dist/browser/client/react.js.map +1 -1
- package/dist/browser/client/solid.d.ts +218 -196
- package/dist/browser/client/solid.d.ts.map +1 -1
- package/dist/browser/client/solid.js.map +1 -1
- package/dist/browser/client/svelte.d.ts +216 -193
- package/dist/browser/client/svelte.d.ts.map +1 -1
- package/dist/browser/client/svelte.js.map +1 -1
- package/dist/browser/client/vanilla.d.ts +217 -195
- package/dist/browser/client/vanilla.d.ts.map +1 -1
- package/dist/browser/client/vanilla.js.map +1 -1
- package/dist/browser/client/vue.d.ts +217 -194
- package/dist/browser/client/vue.d.ts.map +1 -1
- package/dist/browser/client/vue.js.map +1 -1
- package/dist/cli/commands/files/delete.d.ts +4 -4
- package/dist/cli/commands/files/delete.d.ts.map +1 -1
- package/dist/cli/commands/files/delete.js +8 -10
- package/dist/cli/commands/files/delete.js.map +1 -1
- package/dist/cli/commands/files/download-url.d.ts +4 -4
- package/dist/cli/commands/files/download-url.d.ts.map +1 -1
- package/dist/cli/commands/files/download-url.js +8 -10
- package/dist/cli/commands/files/download-url.js.map +1 -1
- package/dist/cli/commands/files/download.d.ts +4 -4
- package/dist/cli/commands/files/download.d.ts.map +1 -1
- package/dist/cli/commands/files/download.js +10 -12
- package/dist/cli/commands/files/download.js.map +1 -1
- package/dist/cli/commands/files/get.d.ts +4 -4
- package/dist/cli/commands/files/get.d.ts.map +1 -1
- package/dist/cli/commands/files/get.js +8 -10
- package/dist/cli/commands/files/get.js.map +1 -1
- package/dist/cli/commands/files/list.d.ts +4 -4
- package/dist/cli/commands/files/list.d.ts.map +1 -1
- package/dist/cli/commands/files/list.js +6 -8
- package/dist/cli/commands/files/list.js.map +1 -1
- package/dist/cli/commands/files/update.d.ts +4 -4
- package/dist/cli/commands/files/update.d.ts.map +1 -1
- package/dist/cli/commands/files/update.js +8 -10
- package/dist/cli/commands/files/update.js.map +1 -1
- package/dist/cli/commands/files/upload.d.ts +4 -4
- package/dist/cli/commands/files/upload.d.ts.map +1 -1
- package/dist/cli/commands/files/upload.js +10 -12
- package/dist/cli/commands/files/upload.js.map +1 -1
- package/dist/cli/commands/uploads/abort.d.ts +2 -2
- package/dist/cli/commands/uploads/abort.d.ts.map +1 -1
- package/dist/cli/commands/uploads/abort.js.map +1 -1
- package/dist/cli/commands/uploads/complete.d.ts +2 -2
- package/dist/cli/commands/uploads/complete.d.ts.map +1 -1
- package/dist/cli/commands/uploads/complete.js.map +1 -1
- package/dist/cli/commands/uploads/content.d.ts +2 -2
- package/dist/cli/commands/uploads/content.d.ts.map +1 -1
- package/dist/cli/commands/uploads/content.js +1 -1
- package/dist/cli/commands/uploads/content.js.map +1 -1
- package/dist/cli/commands/uploads/create.d.ts +4 -4
- package/dist/cli/commands/uploads/create.d.ts.map +1 -1
- package/dist/cli/commands/uploads/create.js +8 -11
- package/dist/cli/commands/uploads/create.js.map +1 -1
- package/dist/cli/commands/uploads/get.d.ts +2 -2
- package/dist/cli/commands/uploads/get.d.ts.map +1 -1
- package/dist/cli/commands/uploads/get.js.map +1 -1
- package/dist/cli/commands/uploads/parts-complete.d.ts +2 -2
- package/dist/cli/commands/uploads/parts-complete.d.ts.map +1 -1
- package/dist/cli/commands/uploads/parts-complete.js.map +1 -1
- package/dist/cli/commands/uploads/parts-list.d.ts +2 -2
- package/dist/cli/commands/uploads/parts-list.d.ts.map +1 -1
- package/dist/cli/commands/uploads/parts-list.js.map +1 -1
- package/dist/cli/commands/uploads/parts-urls.d.ts +2 -2
- package/dist/cli/commands/uploads/parts-urls.d.ts.map +1 -1
- package/dist/cli/commands/uploads/parts-urls.js.map +1 -1
- package/dist/cli/commands/uploads/progress.d.ts +2 -2
- package/dist/cli/commands/uploads/progress.d.ts.map +1 -1
- package/dist/cli/commands/uploads/progress.js.map +1 -1
- package/dist/cli/commands/uploads/transfer.d.ts +4 -4
- package/dist/cli/commands/uploads/transfer.d.ts.map +1 -1
- package/dist/cli/commands/uploads/transfer.js +9 -12
- package/dist/cli/commands/uploads/transfer.js.map +1 -1
- package/dist/cli/index.d.ts +13 -13
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +14 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/client.js +22 -5
- package/dist/cli/utils/client.js.map +1 -1
- package/dist/cli/utils/options.js +7 -43
- package/dist/cli/utils/options.js.map +1 -1
- package/dist/node/cli/commands/files/delete.d.ts +4 -4
- package/dist/node/cli/commands/files/delete.d.ts.map +1 -1
- package/dist/node/cli/commands/files/delete.js +8 -10
- package/dist/node/cli/commands/files/delete.js.map +1 -1
- package/dist/node/cli/commands/files/download-url.d.ts +4 -4
- package/dist/node/cli/commands/files/download-url.d.ts.map +1 -1
- package/dist/node/cli/commands/files/download-url.js +8 -10
- package/dist/node/cli/commands/files/download-url.js.map +1 -1
- package/dist/node/cli/commands/files/download.d.ts +4 -4
- package/dist/node/cli/commands/files/download.d.ts.map +1 -1
- package/dist/node/cli/commands/files/download.js +9 -11
- package/dist/node/cli/commands/files/download.js.map +1 -1
- package/dist/node/cli/commands/files/get.d.ts +4 -4
- package/dist/node/cli/commands/files/get.d.ts.map +1 -1
- package/dist/node/cli/commands/files/get.js +8 -10
- package/dist/node/cli/commands/files/get.js.map +1 -1
- package/dist/node/cli/commands/files/list.d.ts +4 -4
- package/dist/node/cli/commands/files/list.d.ts.map +1 -1
- package/dist/node/cli/commands/files/list.js +6 -8
- package/dist/node/cli/commands/files/list.js.map +1 -1
- package/dist/node/cli/commands/files/update.d.ts +4 -4
- package/dist/node/cli/commands/files/update.d.ts.map +1 -1
- package/dist/node/cli/commands/files/update.js +8 -10
- package/dist/node/cli/commands/files/update.js.map +1 -1
- package/dist/node/cli/commands/files/upload.d.ts +4 -4
- package/dist/node/cli/commands/files/upload.d.ts.map +1 -1
- package/dist/node/cli/commands/files/upload.js +9 -11
- package/dist/node/cli/commands/files/upload.js.map +1 -1
- package/dist/node/cli/commands/uploads/abort.d.ts +2 -2
- package/dist/node/cli/commands/uploads/abort.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/abort.js.map +1 -1
- package/dist/node/cli/commands/uploads/complete.d.ts +2 -2
- package/dist/node/cli/commands/uploads/complete.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/complete.js.map +1 -1
- package/dist/node/cli/commands/uploads/content.d.ts +2 -2
- package/dist/node/cli/commands/uploads/content.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/content.js.map +1 -1
- package/dist/node/cli/commands/uploads/create.d.ts +4 -4
- package/dist/node/cli/commands/uploads/create.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/create.js +8 -11
- package/dist/node/cli/commands/uploads/create.js.map +1 -1
- package/dist/node/cli/commands/uploads/get.d.ts +2 -2
- package/dist/node/cli/commands/uploads/get.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/get.js.map +1 -1
- package/dist/node/cli/commands/uploads/parts-complete.d.ts +2 -2
- package/dist/node/cli/commands/uploads/parts-complete.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/parts-complete.js.map +1 -1
- package/dist/node/cli/commands/uploads/parts-list.d.ts +2 -2
- package/dist/node/cli/commands/uploads/parts-list.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/parts-list.js.map +1 -1
- package/dist/node/cli/commands/uploads/parts-urls.d.ts +2 -2
- package/dist/node/cli/commands/uploads/parts-urls.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/parts-urls.js.map +1 -1
- package/dist/node/cli/commands/uploads/progress.d.ts +2 -2
- package/dist/node/cli/commands/uploads/progress.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/progress.js.map +1 -1
- package/dist/node/cli/commands/uploads/transfer.d.ts +4 -4
- package/dist/node/cli/commands/uploads/transfer.d.ts.map +1 -1
- package/dist/node/cli/commands/uploads/transfer.js +8 -11
- package/dist/node/cli/commands/uploads/transfer.js.map +1 -1
- package/dist/node/cli/index.d.ts +13 -13
- package/dist/node/cli/index.d.ts.map +1 -1
- package/dist/node/cli/index.js +14 -14
- package/dist/node/cli/index.js.map +1 -1
- package/dist/node/cli/utils/client.js +22 -5
- package/dist/node/cli/utils/client.js.map +1 -1
- package/dist/node/cli/utils/options.js +7 -43
- package/dist/node/cli/utils/options.js.map +1 -1
- package/dist/node/client/clients.d.ts +217 -194
- package/dist/node/client/clients.d.ts.map +1 -1
- package/dist/node/client/clients.js +17 -9
- package/dist/node/client/clients.js.map +1 -1
- package/dist/node/client/helpers.d.ts +15 -6
- package/dist/node/client/helpers.d.ts.map +1 -1
- package/dist/node/client/helpers.js +176 -30
- package/dist/node/client/helpers.js.map +1 -1
- package/dist/node/client/react.d.ts +217 -194
- package/dist/node/client/react.d.ts.map +1 -1
- package/dist/node/client/react.js.map +1 -1
- package/dist/node/client/solid.d.ts +218 -196
- package/dist/node/client/solid.d.ts.map +1 -1
- package/dist/node/client/solid.js.map +1 -1
- package/dist/node/client/svelte.d.ts +216 -193
- package/dist/node/client/svelte.d.ts.map +1 -1
- package/dist/node/client/svelte.js.map +1 -1
- package/dist/node/client/vanilla.d.ts +217 -195
- package/dist/node/client/vanilla.d.ts.map +1 -1
- package/dist/node/client/vanilla.js.map +1 -1
- package/dist/node/client/vue.d.ts +217 -194
- package/dist/node/client/vue.d.ts.map +1 -1
- package/dist/node/client/vue.js.map +1 -1
- package/dist/node/config.d.ts +6 -6
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/config.js.map +1 -1
- package/dist/node/definition.d.ts +588 -219
- package/dist/node/definition.d.ts.map +1 -1
- package/dist/node/definition.js +27 -3
- package/dist/node/definition.js.map +1 -1
- package/dist/node/file-key.d.ts +19 -0
- package/dist/node/file-key.d.ts.map +1 -0
- package/dist/node/file-key.js +47 -0
- package/dist/node/file-key.js.map +1 -0
- package/dist/node/index.d.ts +582 -175
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +3 -2
- package/dist/node/index.js.map +1 -1
- package/dist/node/routes/files.js +99 -64
- package/dist/node/routes/files.js.map +1 -1
- package/dist/node/routes/index.d.ts +1497 -721
- package/dist/node/routes/index.d.ts.map +1 -1
- package/dist/node/routes/shared.js +5 -9
- package/dist/node/routes/shared.js.map +1 -1
- package/dist/node/routes/uploads.js +105 -47
- package/dist/node/routes/uploads.js.map +1 -1
- package/dist/node/schema.d.ts +6 -6
- package/dist/node/schema.d.ts.map +1 -1
- package/dist/node/schema.js +12 -3
- package/dist/node/schema.js.map +1 -1
- package/dist/node/services/files.d.ts +6 -2
- package/dist/node/services/files.d.ts.map +1 -1
- package/dist/node/services/files.js +22 -20
- package/dist/node/services/files.js.map +1 -1
- package/dist/node/services/helpers.js +37 -15
- package/dist/node/services/helpers.js.map +1 -1
- package/dist/node/services/uploads.d.ts +10 -5
- package/dist/node/services/uploads.d.ts.map +1 -1
- package/dist/node/services/uploads.js +340 -63
- package/dist/node/services/uploads.js.map +1 -1
- package/dist/node/storage/fs.d.ts.map +1 -1
- package/dist/node/storage/fs.js +16 -10
- package/dist/node/storage/fs.js.map +1 -1
- package/dist/node/storage/object-key.js +36 -0
- package/dist/node/storage/object-key.js.map +1 -0
- package/dist/node/storage/r2-binding.d.ts +59 -0
- package/dist/node/storage/r2-binding.d.ts.map +1 -0
- package/dist/node/storage/r2-binding.js +245 -0
- package/dist/node/storage/r2-binding.js.map +1 -0
- package/dist/node/storage/r2.d.ts +6 -5
- package/dist/node/storage/r2.d.ts.map +1 -1
- package/dist/node/storage/s3.d.ts.map +1 -1
- package/dist/node/storage/s3.js +16 -10
- package/dist/node/storage/s3.js.map +1 -1
- package/dist/node/storage/types.d.ts +6 -5
- package/dist/node/storage/types.d.ts.map +1 -1
- package/dist/node/types.d.ts +1 -2
- package/dist/node/types.d.ts.map +1 -1
- package/package.json +26 -46
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/atom/index.js.map +0 -1
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/clean-stores/index.js +0 -6
- package/dist/browser/client/node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/computed/index.js.map +0 -1
- package/dist/browser/client/packages/fragment-upload/src/keys.d.ts +0 -7
- package/dist/browser/client/packages/fragment-upload/src/keys.d.ts.map +0 -1
- package/dist/browser/client/packages/fragment-upload/src/keys.js +0 -28
- package/dist/browser/client/packages/fragment-upload/src/keys.js.map +0 -1
- package/dist/browser/index-BdjKPO4J.d.ts +0 -177
- package/dist/browser/index-BdjKPO4J.d.ts.map +0 -1
- package/dist/browser/index.js +0 -3
- package/dist/browser/src-vdNJUbjT.js +0 -1982
- package/dist/browser/src-vdNJUbjT.js.map +0 -1
- package/dist/cli/keys.js +0 -32
- package/dist/cli/keys.js.map +0 -1
- package/dist/node/keys.d.ts +0 -12
- package/dist/node/keys.d.ts.map +0 -1
- package/dist/node/keys.js +0 -63
- package/dist/node/keys.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.js","names":["defineFragment","UploadFragmentConfig","
|
|
1
|
+
{"version":3,"file":"definition.js","names":["defineFragment","UploadFragmentConfig","uploadFragmentDefinition","extend","x","withDependencies","providesBaseService","build"],"sources":["../../../../../../src/definition.ts"],"sourcesContent":["import { defineFragment } from \"@fragno-dev/core\";\nimport { withDatabase } from \"@fragno-dev/db\";\n\nimport type { UploadFragmentConfig } from \"./config\";\nimport { resolveUploadFragmentConfig } from \"./config\";\nimport { uploadSchema } from \"./schema\";\nimport { createFileServices, createUploadServices } from \"./services\";\nimport type { UploadStatus } from \"./types\";\n\nconst buildMissingDeletedFileObjectKeyError = (input: { provider: string; fileKey: string }) =>\n `Missing persisted objectKey for deleted file '${input.provider}:${input.fileKey}'. Refusing to reconstruct storage key from provider/fileKey.`;\n\nconst buildMissingCleanupObjectKeyError = (input: { provider: string; fileKey: string }) =>\n `Missing storage cleanup objectKey for '${input.provider}:${input.fileKey}'. Refusing to reconstruct storage key from provider/fileKey.`;\n\nexport const uploadFragmentDefinition = defineFragment<UploadFragmentConfig>(\"upload\")\n .extend(withDatabase(uploadSchema))\n .withDependencies(({ config }) => ({\n resolvedConfig: resolveUploadFragmentConfig(config),\n }))\n .provideHooks(({ defineHook, config }) => {\n const resolvedConfig = resolveUploadFragmentConfig(config);\n\n return {\n onFileReady: defineHook(async function (payload) {\n await config.onFileReady?.(payload, this.idempotencyKey);\n }),\n onUploadFailed: defineHook(async function (payload) {\n await config.onUploadFailed?.(payload, this.idempotencyKey);\n }),\n cleanupStorageObject: defineHook(async function (payload) {\n const objectKey =\n typeof payload.objectKey === \"string\" && payload.objectKey.length > 0\n ? payload.objectKey\n : null;\n\n if (!objectKey) {\n throw new Error(\n buildMissingCleanupObjectKeyError({\n provider: payload.provider,\n fileKey: payload.fileKey,\n }),\n );\n }\n\n await resolvedConfig.storage.deleteObject({ storageKey: objectKey });\n }),\n onFileDeleted: defineHook(async function (payload) {\n const payloadObjectKey =\n typeof payload.objectKey === \"string\" && payload.objectKey.length > 0\n ? payload.objectKey\n : undefined;\n const persistedObjectKey =\n payloadObjectKey ??\n (await this.handlerTx()\n .retrieve(({ forSchema }) =>\n forSchema(uploadSchema).findFirst(\"file\", (b) =>\n b.whereIndex(\"idx_file_provider_key\", (eb) =>\n eb.and(eb(\"provider\", \"=\", payload.provider), eb(\"key\", \"=\", payload.fileKey)),\n ),\n ),\n )\n .transformRetrieve(([file]) => {\n if (\n file?.status === \"deleted\" &&\n typeof file.objectKey === \"string\" &&\n file.objectKey.length > 0\n ) {\n return file.objectKey;\n }\n return null;\n })\n .execute());\n\n if (!persistedObjectKey) {\n throw new Error(\n buildMissingDeletedFileObjectKeyError({\n provider: payload.provider,\n fileKey: payload.fileKey,\n }),\n );\n }\n\n await resolvedConfig.storage.deleteObject({ storageKey: persistedObjectKey });\n await config.onFileDeleted?.(\n {\n ...payload,\n objectKey: persistedObjectKey,\n },\n this.idempotencyKey,\n );\n }),\n onUploadTimeout: defineHook(async function (payload) {\n const now = new Date();\n\n if (!payload.uploadId) {\n return;\n }\n\n const result = await this.handlerTx()\n .retrieve(({ forSchema }) =>\n forSchema(uploadSchema).findFirst(\"upload\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", payload.uploadId)),\n ),\n )\n .mutate(({ forSchema, retrieveResult: [upload] }) => {\n if (!upload) {\n return { shouldNotify: false as const };\n }\n\n const status = upload.status as UploadStatus;\n if (\n status === \"completed\" ||\n status === \"aborted\" ||\n status === \"failed\" ||\n status === \"expired\"\n ) {\n return { shouldNotify: false as const };\n }\n\n if (upload.expiresAt.getTime() > now.getTime()) {\n return { shouldNotify: false as const };\n }\n\n const uow = forSchema(uploadSchema);\n uow.update(\"upload\", upload.id, (b) =>\n b.set({\n status: \"expired\",\n updatedAt: now,\n errorCode: \"UPLOAD_EXPIRED\",\n errorMessage: \"Upload expired\",\n }),\n );\n\n return {\n shouldNotify: true as const,\n payload: {\n provider: upload.provider,\n fileKey: upload.key,\n objectKey: upload.objectKey,\n uploadId: payload.uploadId,\n uploaderId: upload.uploaderId,\n sizeBytes: Number(upload.expectedSizeBytes),\n contentType: upload.contentType,\n },\n };\n })\n .transform(({ mutateResult }) => mutateResult)\n .execute();\n\n if (result.shouldNotify) {\n await config.onUploadFailed?.(result.payload, this.idempotencyKey);\n }\n }),\n };\n })\n .providesBaseService(({ defineService, deps }) => {\n return defineService({\n ...createUploadServices(deps.resolvedConfig),\n ...createFileServices(deps.resolvedConfig),\n });\n })\n .build();\n"],"mappings":";;;AAeA,MAAaE,2BAA2BF,eAAqC,SAAS,CACnFG,QAAMC,MAAAA,EAA4B,CAClCC,uBAAgB,GAEd,CAyIFC,0BAAmB,GAKlB,CACDC,OAAO"}
|
|
@@ -15,6 +15,7 @@ const errorCodes = [
|
|
|
15
15
|
"UPLOAD_ALREADY_ACTIVE",
|
|
16
16
|
"FILE_ALREADY_EXISTS",
|
|
17
17
|
"FILE_NOT_FOUND",
|
|
18
|
+
"FILE_DELETED",
|
|
18
19
|
"UPLOAD_EXPIRED",
|
|
19
20
|
"UPLOAD_INVALID_STATE",
|
|
20
21
|
"SIGNED_URL_UNSUPPORTED",
|
|
@@ -37,6 +38,7 @@ const fileRoutesFactory = defineRoutes(uploadFragmentDefinition).create(({ servi
|
|
|
37
38
|
method: "GET",
|
|
38
39
|
path: "/files",
|
|
39
40
|
queryParameters: [
|
|
41
|
+
"provider",
|
|
40
42
|
"prefix",
|
|
41
43
|
"cursor",
|
|
42
44
|
"pageSize",
|
|
@@ -53,14 +55,16 @@ const fileRoutesFactory = defineRoutes(uploadFragmentDefinition).create(({ servi
|
|
|
53
55
|
}),
|
|
54
56
|
defineRoute({
|
|
55
57
|
method: "GET",
|
|
56
|
-
path: "/files
|
|
58
|
+
path: "/files/by-key",
|
|
59
|
+
queryParameters: ["provider", "key"],
|
|
57
60
|
outputSchema: fileMetadataSchema,
|
|
58
61
|
errorCodes,
|
|
59
62
|
handler: () => {}
|
|
60
63
|
}),
|
|
61
64
|
defineRoute({
|
|
62
65
|
method: "PATCH",
|
|
63
|
-
path: "/files
|
|
66
|
+
path: "/files/by-key",
|
|
67
|
+
queryParameters: ["provider", "key"],
|
|
64
68
|
inputSchema: updateFileSchema,
|
|
65
69
|
outputSchema: fileMetadataSchema,
|
|
66
70
|
errorCodes,
|
|
@@ -68,14 +72,16 @@ const fileRoutesFactory = defineRoutes(uploadFragmentDefinition).create(({ servi
|
|
|
68
72
|
}),
|
|
69
73
|
defineRoute({
|
|
70
74
|
method: "DELETE",
|
|
71
|
-
path: "/files
|
|
75
|
+
path: "/files/by-key",
|
|
76
|
+
queryParameters: ["provider", "key"],
|
|
72
77
|
outputSchema: z.object({ ok: z.literal(true) }),
|
|
73
78
|
errorCodes,
|
|
74
79
|
handler: () => {}
|
|
75
80
|
}),
|
|
76
81
|
defineRoute({
|
|
77
82
|
method: "GET",
|
|
78
|
-
path: "/files
|
|
83
|
+
path: "/files/by-key/download-url",
|
|
84
|
+
queryParameters: ["provider", "key"],
|
|
79
85
|
outputSchema: z.object({
|
|
80
86
|
url: z.string(),
|
|
81
87
|
headers: z.record(z.string(), z.string()).optional(),
|
|
@@ -86,7 +92,8 @@ const fileRoutesFactory = defineRoutes(uploadFragmentDefinition).create(({ servi
|
|
|
86
92
|
}),
|
|
87
93
|
defineRoute({
|
|
88
94
|
method: "GET",
|
|
89
|
-
path: "/files
|
|
95
|
+
path: "/files/by-key/content",
|
|
96
|
+
queryParameters: ["provider", "key"],
|
|
90
97
|
errorCodes,
|
|
91
98
|
handler: () => {}
|
|
92
99
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","names":["defineRoutes","FragnoRouteConfig","z","uploadFragmentDefinition","fileMetadataSchema","visibilitySchema","updateFileSchema","object","filename","string","min","optional","visibility","tags","array","nullable","metadata","record","unknown","errorCodes","const","FileErrorCode","ErrorFn","Parameters","Code","fileRoutesFactory","create","services","defineRoute","config","method","path","contentType","outputSchema","handler","queryParameters","files","cursor","hasNextPage","boolean","inputSchema","ok","literal","url","headers","expiresAt","date"],"sources":["../../../../../../../src/routes/files.ts"],"sourcesContent":["import { defineRoutes } from \"@fragno-dev/core\";\nimport type { FragnoRouteConfig } from \"@fragno-dev/core\";\nimport { z } from \"zod\";\nimport { uploadFragmentDefinition } from \"../definition\";\nimport { resolveUploadFragmentConfig } from \"../config\";\nimport { resolveFileKeyInput } from \"../services/helpers\";\nimport {\n checksumSchema,\n fileKeyPartsSchema,\n fileMetadataSchema,\n toFileMetadata,\n visibilitySchema,\n} from \"./shared\";\n\nconst listQuerySchema = z.object({\n prefix: z.string().optional(),\n cursor: z.string().optional(),\n pageSize: z.coerce.number().min(1).max(100).catch(25),\n status: z.enum([\"ready\", \"deleted\"]).optional(),\n uploaderId: z.string().optional(),\n});\n\nconst updateFileSchema = z.object({\n filename: z.string().min(1).optional(),\n visibility: visibilitySchema.optional(),\n tags: z.array(z.string()).nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n});\n\nconst errorCodes = [\n \"UPLOAD_NOT_FOUND\",\n \"UPLOAD_ALREADY_ACTIVE\",\n \"FILE_ALREADY_EXISTS\",\n \"FILE_NOT_FOUND\",\n \"UPLOAD_EXPIRED\",\n \"UPLOAD_INVALID_STATE\",\n \"SIGNED_URL_UNSUPPORTED\",\n \"STORAGE_ERROR\",\n \"INVALID_FILE_KEY\",\n \"INVALID_CHECKSUM\",\n \"INVALID_REQUEST\",\n] as const;\n\ntype FileErrorCode = (typeof errorCodes)[number];\n\ntype ErrorFn<Code extends string> = Parameters<\n FragnoRouteConfig<\"GET\", \"/__error\", undefined, undefined, Code>[\"handler\"]\n>[1][\"error\"];\n\nconst handleServiceError = <Code extends FileErrorCode>(\n err: unknown,\n error: ErrorFn<Code>,\n): Response => {\n if (!(err instanceof Error)) {\n throw err;\n }\n\n switch (err.message) {\n case \"FILE_NOT_FOUND\":\n return error({ message: \"File not found\", code: \"FILE_NOT_FOUND\" as Code }, 404);\n case \"UPLOAD_NOT_FOUND\":\n return error({ message: \"Upload not found\", code: \"UPLOAD_NOT_FOUND\" as Code }, 404);\n case \"FILE_ALREADY_EXISTS\":\n return error({ message: \"File already exists\", code: \"FILE_ALREADY_EXISTS\" as Code }, 409);\n case \"UPLOAD_ALREADY_ACTIVE\":\n return error(\n { message: \"Upload already active\", code: \"UPLOAD_ALREADY_ACTIVE\" as Code },\n 409,\n );\n case \"UPLOAD_EXPIRED\":\n return error({ message: \"Upload expired\", code: \"UPLOAD_EXPIRED\" as Code }, 410);\n case \"UPLOAD_INVALID_STATE\":\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" as Code }, 409);\n case \"INVALID_FILE_KEY\":\n return error({ message: \"Invalid file key\", code: \"INVALID_FILE_KEY\" as Code }, 400);\n case \"INVALID_CHECKSUM\":\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" as Code }, 400);\n case \"INVALID_REQUEST\":\n return error({ message: \"Invalid request\", code: \"INVALID_REQUEST\" as Code }, 400);\n default:\n throw err;\n }\n};\n\nconst parseJson = <T>(value: FormDataEntryValue | null): T | undefined => {\n if (value === null || typeof value !== \"string\" || value.length === 0) {\n return undefined;\n }\n try {\n return JSON.parse(value) as T;\n } catch {\n return undefined;\n }\n};\n\nconst parseTags = (value: FormDataEntryValue | null): string[] | undefined => {\n if (value === null) {\n return undefined;\n }\n if (typeof value !== \"string\") {\n return undefined;\n }\n const parsed = parseJson<unknown>(value);\n if (Array.isArray(parsed)) {\n return parsed.filter((tag) => typeof tag === \"string\") as string[];\n }\n if (typeof value === \"string\" && value.length > 0) {\n return [value];\n }\n return undefined;\n};\n\nconst parseMetadata = (value: FormDataEntryValue | null): Record<string, unknown> | undefined => {\n const parsed = parseJson<unknown>(value);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return undefined;\n};\n\nexport const fileRoutesFactory = defineRoutes(uploadFragmentDefinition).create(\n ({ services, defineRoute, config }) => {\n const getResolvedConfig = () => resolveUploadFragmentConfig(config);\n\n const parseListQuery = (query: URLSearchParams) => {\n const result = listQuerySchema.safeParse({\n prefix: query.get(\"prefix\") || undefined,\n cursor: query.get(\"cursor\") || undefined,\n pageSize: query.get(\"pageSize\"),\n status: query.get(\"status\") || undefined,\n uploaderId: query.get(\"uploaderId\") || undefined,\n });\n if (!result.success) {\n throw new Error(\"INVALID_REQUEST\");\n }\n const params = result.data;\n\n if (params.prefix && !params.prefix.endsWith(\".\")) {\n throw new Error(\"INVALID_FILE_KEY\");\n }\n\n return params;\n };\n\n return [\n defineRoute({\n method: \"POST\",\n path: \"/files\",\n contentType: \"multipart/form-data\",\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function (context, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n const form = context.formData();\n const file = form.get(\"file\");\n if (!(file instanceof Blob)) {\n return error({ message: \"File is required\", code: \"INVALID_REQUEST\" }, 400);\n }\n\n let keyParts: z.infer<typeof fileKeyPartsSchema> | undefined;\n if (form.has(\"keyParts\")) {\n const parsed = parseJson<unknown>(form.get(\"keyParts\"));\n const result = fileKeyPartsSchema.safeParse(parsed);\n if (!result.success) {\n return error({ message: \"Invalid file key\", code: \"INVALID_FILE_KEY\" }, 400);\n }\n keyParts = result.data;\n }\n\n const fileKeyValue = form.get(\"fileKey\");\n const fileKey = typeof fileKeyValue === \"string\" ? fileKeyValue : undefined;\n\n const checksumValue = form.get(\"checksum\");\n const parsedChecksum = parseJson<unknown>(checksumValue);\n const checksumResult = checksumSchema.safeParse(parsedChecksum);\n if (!checksumResult.success) {\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" }, 400);\n }\n\n const tags = parseTags(form.get(\"tags\"));\n const metadata = parseMetadata(form.get(\"metadata\"));\n\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({ keyParts, fileKey: fileKey ?? undefined });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n const checksumForStorage = checksumResult.data ?? null;\n const checksumForRecord = checksumResult.data ?? undefined;\n\n let storageInit;\n\n const uploaderIdValue = form.get(\"uploaderId\");\n const uploaderId = typeof uploaderIdValue === \"string\" ? uploaderIdValue : undefined;\n const visibilityValue = form.get(\"visibility\");\n const parsedVisibility =\n typeof visibilityValue === \"string\" ? visibilityValue : undefined;\n const visibilityResult = visibilitySchema.optional().safeParse(parsedVisibility);\n if (!visibilityResult.success) {\n return error({ message: \"Invalid request\", code: \"INVALID_REQUEST\" }, 400);\n }\n const visibility = visibilityResult.data;\n const filenameValue = form.get(\"filename\");\n const filename =\n typeof filenameValue === \"string\" && filenameValue\n ? filenameValue\n : file instanceof File && file.name\n ? file.name\n : \"upload\";\n const contentType = file.type || \"application/octet-stream\";\n\n const createInput = {\n fileKey: resolvedKey.fileKey,\n keyParts: resolvedKey.fileKeyParts,\n filename,\n sizeBytes: file.size,\n contentType,\n checksum: checksumForRecord,\n tags,\n visibility,\n uploaderId,\n metadata,\n };\n\n try {\n await this.handlerTx()\n .withServiceCalls(() => [\n services.checkUploadAvailability(createInput, { allowIdempotentReuse: false }),\n ])\n .execute();\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n storageInit = await resolvedConfig.storage.initUpload({\n fileKey: resolvedKey.fileKey,\n fileKeyParts: resolvedKey.fileKeyParts,\n sizeBytes: BigInt(file.size),\n contentType,\n checksum: checksumForStorage,\n metadata: metadata ?? null,\n });\n } catch {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n if (storageInit.strategy === \"direct-multipart\") {\n if (resolvedConfig.storage.abortMultipartUpload && storageInit.storageUploadId) {\n try {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: storageInit.storageKey,\n storageUploadId: storageInit.storageUploadId,\n });\n } catch {\n // Ignore abort failures; the request is still invalid for this endpoint.\n }\n }\n\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n let created;\n try {\n created = await this.handlerTx()\n .withServiceCalls(() => [\n services.createUploadRecord({\n ...createInput,\n storageInit,\n allowIdempotentReuse: false,\n }),\n ])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n const createdUpload = created.result;\n\n try {\n if (createdUpload.strategy === \"proxy\") {\n if (!resolvedConfig.storage.writeStream) {\n throw new Error(\"STORAGE_ERROR\");\n }\n await resolvedConfig.storage.writeStream({\n storageKey: storageInit.storageKey,\n body: file.stream(),\n contentType,\n sizeBytes: BigInt(file.size),\n });\n } else if (createdUpload.strategy === \"direct-single\") {\n if (!storageInit.uploadUrl) {\n throw new Error(\"STORAGE_ERROR\");\n }\n const response = await fetch(storageInit.uploadUrl, {\n method: \"PUT\",\n headers: storageInit.uploadHeaders,\n body: file,\n });\n if (!response.ok) {\n throw new Error(\"STORAGE_ERROR\");\n }\n } else {\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n if (resolvedConfig.storage.finalizeUpload) {\n await resolvedConfig.storage.finalizeUpload({\n storageKey: storageInit.storageKey,\n expectedSizeBytes: BigInt(file.size),\n checksum: checksumResult.data ?? null,\n });\n }\n } catch {\n await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadFailed(\n createdUpload.uploadId,\n \"STORAGE_ERROR\",\n \"Storage upload failed\",\n ),\n ])\n .execute();\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n try {\n const completed = await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadComplete(createdUpload.uploadId, createdUpload.fileKey, {\n sizeBytes: BigInt(file.size),\n }),\n ])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(completed.file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files\",\n queryParameters: [\"prefix\", \"cursor\", \"pageSize\", \"status\", \"uploaderId\"],\n outputSchema: z.object({\n files: z.array(fileMetadataSchema),\n cursor: z.string().optional(),\n hasNextPage: z.boolean(),\n }),\n errorCodes,\n handler: async function ({ query }, { json, error }) {\n let params;\n try {\n params = parseListQuery(query);\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n const result = await this.handlerTx()\n .withServiceCalls(() => [\n services.listFiles({\n prefix: params.prefix,\n pageSize: params.pageSize,\n cursor: params.cursor,\n status: params.status,\n uploaderId: params.uploaderId,\n }),\n ])\n .transform(({ serviceResult: [files] }) => files)\n .execute();\n\n return json({\n files: result.items.map(toFileMetadata),\n cursor: result.cursor?.encode(),\n hasNextPage: result.hasNextPage,\n });\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files/:fileKey\",\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({ fileKey: pathParams.fileKey });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n const file = await this.handlerTx()\n .withServiceCalls(() => [services.getFileByKey(resolvedKey.fileKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"PATCH\",\n path: \"/files/:fileKey\",\n inputSchema: updateFileSchema,\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({ fileKey: pathParams.fileKey });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n const file = await this.handlerTx()\n .withServiceCalls(() => [services.updateFile(resolvedKey.fileKey, payload)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"DELETE\",\n path: \"/files/:fileKey\",\n outputSchema: z.object({ ok: z.literal(true) }),\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({ fileKey: pathParams.fileKey });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n const file = await this.handlerTx()\n .withServiceCalls(() => [services.getFileByKey(resolvedKey.fileKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n try {\n await resolvedConfig.storage.deleteObject({ storageKey: file.storageKey });\n } catch {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n await this.handlerTx()\n .withServiceCalls(() => [\n services.markFileDeleted(resolvedKey.fileKey, resolvedKey.fileKeyParts),\n ])\n .execute();\n\n return json({ ok: true });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files/:fileKey/download-url\",\n outputSchema: z.object({\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n expiresAt: z.date(),\n }),\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({ fileKey: pathParams.fileKey });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n if (!resolvedConfig.storage.getDownloadUrl) {\n return error(\n { message: \"Signed URLs are not supported\", code: \"SIGNED_URL_UNSUPPORTED\" },\n 400,\n );\n }\n\n try {\n const file = await this.handlerTx()\n .withServiceCalls(() => [services.getFileByKey(resolvedKey.fileKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n let result;\n try {\n result = await resolvedConfig.storage.getDownloadUrl({\n storageKey: file.storageKey,\n expiresInSeconds: resolvedConfig.signedUrlExpiresInSeconds,\n contentType: file.contentType ?? undefined,\n });\n } catch {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n return json(result);\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files/:fileKey/content\",\n errorCodes,\n handler: async function ({ pathParams }, { error }) {\n const resolvedConfig = getResolvedConfig();\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({ fileKey: pathParams.fileKey });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n if (!resolvedConfig.storage.getDownloadStream) {\n return error(\n { message: \"Download streaming unsupported\", code: \"SIGNED_URL_UNSUPPORTED\" },\n 400,\n );\n }\n\n try {\n const file = await this.handlerTx()\n .withServiceCalls(() => [services.getFileByKey(resolvedKey.fileKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n try {\n return await resolvedConfig.storage.getDownloadStream({\n storageKey: file.storageKey,\n });\n } catch {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n ];\n },\n);\n"],"mappings":";;;;;;AAsBA,MAAMM,mBAAmBJ,EAAEK,OAAO;CAChCC,UAAUN,EAAEO,QAAQ,CAACC,IAAI,EAAE,CAACC,UAAU;CACtCC,YAAYP,iBAAiBM,UAAU;CACvCE,MAAMX,EAAEY,MAAMZ,EAAEO,QAAQ,CAAC,CAACM,UAAU,CAACJ,UAAU;CAC/CK,UAAUd,EAAEe,OAAOf,EAAEO,QAAQ,EAAEP,EAAEgB,SAAS,CAAC,CAACH,UAAU,CAACJ,UAAS;CACjE,CAAC;AAEF,MAAMQ,aAAa;CACjB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AA+ED,MAAaM,oBAAoBzB,aAAaG,yBAAyB,CAACuB,QACrE,EAAEC,UAAUC,aAAaC,aAAa;AAuBrC,QAAO;EACLD,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNC,aAAa;GACbC,cAAc7B;GACde;GACAe,eAAO;GAiMR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNI,iBAAiB;IAAC;IAAU;IAAU;IAAY;IAAU;IAAa;GACzEF,cAAc/B,EAAEK,OAAO;IACrB6B,OAAOlC,EAAEY,MAAMV,mBAAmB;IAClCiC,QAAQnC,EAAEO,QAAQ,CAACE,UAAU;IAC7B2B,aAAapC,EAAEqC,SAAQ;IACxB,CAAC;GACFpB;GACAe,eAAO;GA2BR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNE,cAAc7B;GACde;GACAe,eAAO;GAmBR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNS,aAAalC;GACb2B,cAAc7B;GACde;GACAe,eAAO;GAoBR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNE,cAAc/B,EAAEK,OAAO,EAAEkC,IAAIvC,EAAEwC,QAAQ,KAAI,EAAG,CAAC;GAC/CvB;GACAe,eAAO;GAgCR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNE,cAAc/B,EAAEK,OAAO;IACrBoC,KAAKzC,EAAEO,QAAQ;IACfmC,SAAS1C,EAAEe,OAAOf,EAAEO,QAAQ,EAAEP,EAAEO,QAAQ,CAAC,CAACE,UAAU;IACpDkC,WAAW3C,EAAE4C,MAAK;IACnB,CAAC;GACF3B;GACAe,eAAO;GAsCR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNZ;GACAe,eAAO;GAiCR,CAAC;EACH;EAEJ"}
|
|
1
|
+
{"version":3,"file":"files.js","names":["z","defineRoutes","FragnoRouteConfig","uploadFragmentDefinition","fileMetadataSchema","visibilitySchema","updateFileSchema","object","filename","string","min","optional","visibility","tags","array","nullable","metadata","record","unknown","errorCodes","const","FileErrorCode","ErrorFn","Parameters","Code","fileRoutesFactory","create","services","defineRoute","config","method","path","contentType","outputSchema","handler","queryParameters","files","cursor","hasNextPage","boolean","inputSchema","ok","literal","url","headers","expiresAt","date"],"sources":["../../../../../../../src/routes/files.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport { defineRoutes } from \"@fragno-dev/core\";\nimport type { FragnoRouteConfig } from \"@fragno-dev/core\";\n\nimport { resolveUploadFragmentConfig } from \"../config\";\nimport { uploadFragmentDefinition } from \"../definition\";\nimport { resolveFileKeyInput } from \"../services/helpers\";\nimport { buildStorageObjectVersionSegment } from \"../storage/object-key\";\nimport {\n checksumSchema,\n fileMetadataSchema,\n providerNamespaceSchema,\n toFileMetadata,\n visibilitySchema,\n} from \"./shared\";\n\nconst legacyFileKeyPartsSchema = z.array(z.union([z.string(), z.number().int()]));\n\nconst listQuerySchema = z.object({\n provider: providerNamespaceSchema.optional(),\n prefix: z.string().optional(),\n cursor: z.string().optional(),\n pageSize: z.coerce.number().min(1).max(100).catch(25),\n status: z.enum([\"ready\", \"deleted\"]).optional(),\n uploaderId: z.string().optional(),\n});\n\nconst byKeyQuerySchema = z.object({\n provider: providerNamespaceSchema,\n key: z.string().min(1),\n});\n\nconst updateFileSchema = z.object({\n filename: z.string().min(1).optional(),\n visibility: visibilitySchema.optional(),\n tags: z.array(z.string()).nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n});\n\nconst errorCodes = [\n \"UPLOAD_NOT_FOUND\",\n \"UPLOAD_ALREADY_ACTIVE\",\n \"FILE_ALREADY_EXISTS\",\n \"FILE_NOT_FOUND\",\n \"FILE_DELETED\",\n \"UPLOAD_EXPIRED\",\n \"UPLOAD_INVALID_STATE\",\n \"SIGNED_URL_UNSUPPORTED\",\n \"STORAGE_ERROR\",\n \"INVALID_FILE_KEY\",\n \"INVALID_CHECKSUM\",\n \"INVALID_REQUEST\",\n] as const;\n\ntype FileErrorCode = (typeof errorCodes)[number];\n\ntype ErrorFn<Code extends string> = Parameters<\n FragnoRouteConfig<\"GET\", \"/__error\", undefined, undefined, Code>[\"handler\"]\n>[1][\"error\"];\n\nconst handleServiceError = <Code extends FileErrorCode>(\n err: unknown,\n error: ErrorFn<Code>,\n): Response => {\n if (!(err instanceof Error)) {\n throw err;\n }\n\n switch (err.message) {\n case \"FILE_NOT_FOUND\":\n return error({ message: \"File not found\", code: \"FILE_NOT_FOUND\" as Code }, 404);\n case \"FILE_DELETED\":\n return error({ message: \"File deleted\", code: \"FILE_DELETED\" as Code }, 410);\n case \"UPLOAD_NOT_FOUND\":\n return error({ message: \"Upload not found\", code: \"UPLOAD_NOT_FOUND\" as Code }, 404);\n case \"FILE_ALREADY_EXISTS\":\n return error({ message: \"File already exists\", code: \"FILE_ALREADY_EXISTS\" as Code }, 409);\n case \"UPLOAD_ALREADY_ACTIVE\":\n return error(\n {\n message: \"Upload already active\",\n code: \"UPLOAD_ALREADY_ACTIVE\" as Code,\n },\n 409,\n );\n case \"UPLOAD_EXPIRED\":\n return error({ message: \"Upload expired\", code: \"UPLOAD_EXPIRED\" as Code }, 410);\n case \"UPLOAD_INVALID_STATE\":\n return error(\n {\n message: \"Upload invalid state\",\n code: \"UPLOAD_INVALID_STATE\" as Code,\n },\n 409,\n );\n case \"INVALID_FILE_KEY\":\n return error({ message: \"Invalid file key\", code: \"INVALID_FILE_KEY\" as Code }, 400);\n case \"INVALID_CHECKSUM\":\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" as Code }, 400);\n case \"INVALID_REQUEST\":\n return error({ message: \"Invalid request\", code: \"INVALID_REQUEST\" as Code }, 400);\n case \"STORAGE_ERROR\":\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" as Code }, 502);\n default:\n throw err;\n }\n};\n\nconst parseJson = <T>(value: FormDataEntryValue | null): T | undefined => {\n if (value === null || typeof value !== \"string\" || value.length === 0) {\n return undefined;\n }\n try {\n return JSON.parse(value) as T;\n } catch {\n return undefined;\n }\n};\n\nconst parseTags = (value: FormDataEntryValue | null): string[] | undefined => {\n if (value === null) {\n return undefined;\n }\n if (typeof value !== \"string\") {\n return undefined;\n }\n const parsed = parseJson<unknown>(value);\n if (Array.isArray(parsed)) {\n return parsed.filter((tag) => typeof tag === \"string\") as string[];\n }\n if (typeof value === \"string\" && value.length > 0) {\n return [value];\n }\n return undefined;\n};\n\nconst parseMetadata = (value: FormDataEntryValue | null): Record<string, unknown> | undefined => {\n const parsed = parseJson<unknown>(value);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return undefined;\n};\n\nconst assertFileAvailable = <T extends { status: string }>(file: T) => {\n if (file.status === \"deleted\") {\n throw new Error(\"FILE_DELETED\");\n }\n return file;\n};\n\nexport const fileRoutesFactory = defineRoutes(uploadFragmentDefinition).create(\n ({ services, defineRoute, config }) => {\n const getResolvedConfig = () => resolveUploadFragmentConfig(config);\n\n const parseListQuery = (query: URLSearchParams) => {\n const result = listQuerySchema.safeParse({\n provider: query.has(\"provider\") ? query.get(\"provider\") : undefined,\n prefix: query.get(\"prefix\") || undefined,\n cursor: query.get(\"cursor\") || undefined,\n pageSize: query.get(\"pageSize\"),\n status: query.get(\"status\") || undefined,\n uploaderId: query.get(\"uploaderId\") || undefined,\n });\n if (!result.success) {\n throw new Error(\"INVALID_REQUEST\");\n }\n return result.data;\n };\n\n const parseByKeyQuery = (query: URLSearchParams) => {\n const result = byKeyQuerySchema.safeParse({\n provider: query.get(\"provider\"),\n key: query.get(\"key\"),\n });\n if (!result.success) {\n throw new Error(\"INVALID_REQUEST\");\n }\n\n try {\n const resolvedKey = resolveFileKeyInput({ fileKey: result.data.key });\n return {\n provider: result.data.provider,\n fileKey: resolvedKey.fileKey,\n };\n } catch {\n throw new Error(\"INVALID_FILE_KEY\");\n }\n };\n\n return [\n defineRoute({\n method: \"POST\",\n path: \"/files\",\n contentType: \"multipart/form-data\",\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function (context, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n const form = context.formData();\n const file = form.get(\"file\");\n if (!(file instanceof Blob)) {\n return error({ message: \"File is required\", code: \"INVALID_REQUEST\" }, 400);\n }\n\n const providerValue = form.get(\"provider\");\n const providerResult = providerNamespaceSchema.safeParse(providerValue);\n if (!providerResult.success) {\n return error({ message: \"Invalid request\", code: \"INVALID_REQUEST\" }, 400);\n }\n const provider = providerResult.data;\n\n let keyParts: z.infer<typeof legacyFileKeyPartsSchema> | undefined;\n if (form.has(\"keyParts\")) {\n const parsed = parseJson<unknown>(form.get(\"keyParts\"));\n const result = legacyFileKeyPartsSchema.safeParse(parsed);\n if (!result.success) {\n return error({ message: \"Invalid file key\", code: \"INVALID_FILE_KEY\" }, 400);\n }\n keyParts = result.data;\n }\n\n const fileKeyValue = form.get(\"fileKey\");\n const fileKey = typeof fileKeyValue === \"string\" ? fileKeyValue : undefined;\n\n const checksumValue = form.get(\"checksum\");\n const parsedChecksum = parseJson<unknown>(checksumValue);\n const checksumResult = checksumSchema.safeParse(parsedChecksum);\n if (!checksumResult.success) {\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" }, 400);\n }\n\n const tags = parseTags(form.get(\"tags\"));\n const metadata = parseMetadata(form.get(\"metadata\"));\n\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({\n keyParts,\n fileKey: fileKey ?? undefined,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n const checksumForStorage = checksumResult.data ?? null;\n const checksumForRecord = checksumResult.data ?? undefined;\n\n let storageInit;\n\n const uploaderIdValue = form.get(\"uploaderId\");\n const uploaderId = typeof uploaderIdValue === \"string\" ? uploaderIdValue : undefined;\n const visibilityValue = form.get(\"visibility\");\n const parsedVisibility =\n typeof visibilityValue === \"string\" ? visibilityValue : undefined;\n const visibilityResult = visibilitySchema.optional().safeParse(parsedVisibility);\n if (!visibilityResult.success) {\n return error({ message: \"Invalid request\", code: \"INVALID_REQUEST\" }, 400);\n }\n const visibility = visibilityResult.data;\n const filenameValue = form.get(\"filename\");\n const filename =\n typeof filenameValue === \"string\" && filenameValue\n ? filenameValue\n : file instanceof File && file.name\n ? file.name\n : \"upload\";\n const contentType = file.type || \"application/octet-stream\";\n\n const objectKeyVersionSegment = buildStorageObjectVersionSegment();\n\n const createInput = {\n provider,\n fileKey: resolvedKey.fileKey,\n keyParts: resolvedKey.fileKeyParts,\n filename,\n sizeBytes: file.size,\n contentType,\n checksum: checksumForRecord,\n tags,\n visibility,\n uploaderId,\n metadata,\n };\n\n try {\n storageInit = await resolvedConfig.storage.initUpload({\n provider,\n fileKey: resolvedKey.fileKey,\n sizeBytes: BigInt(file.size),\n contentType,\n checksum: checksumForStorage,\n metadata: metadata ?? null,\n objectKeyVersionSegment,\n });\n } catch {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n if (storageInit.strategy === \"direct-multipart\") {\n if (resolvedConfig.storage.abortMultipartUpload && storageInit.storageUploadId) {\n try {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: storageInit.storageKey,\n storageUploadId: storageInit.storageUploadId,\n });\n } catch {\n // Ignore abort failures; the request is still invalid for this endpoint.\n }\n }\n\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n try {\n if (storageInit.strategy === \"proxy\") {\n if (!resolvedConfig.storage.writeStream) {\n throw new Error(\"STORAGE_ERROR\");\n }\n await resolvedConfig.storage.writeStream({\n storageKey: storageInit.storageKey,\n body: file.stream(),\n contentType,\n sizeBytes: BigInt(file.size),\n });\n } else if (storageInit.strategy === \"direct-single\") {\n if (!storageInit.uploadUrl) {\n throw new Error(\"STORAGE_ERROR\");\n }\n const response = await fetch(storageInit.uploadUrl, {\n method: \"PUT\",\n headers: storageInit.uploadHeaders,\n body: file,\n });\n if (!response.ok) {\n throw new Error(\"STORAGE_ERROR\");\n }\n } else {\n return error(\n {\n message: \"Upload invalid state\",\n code: \"UPLOAD_INVALID_STATE\",\n },\n 409,\n );\n }\n\n if (resolvedConfig.storage.finalizeUpload) {\n await resolvedConfig.storage.finalizeUpload({\n storageKey: storageInit.storageKey,\n expectedSizeBytes: BigInt(file.size),\n checksum: checksumResult.data ?? null,\n });\n }\n } catch {\n await this.handlerTx()\n .withServiceCalls(() => [\n services.createFailedUpload({\n ...createInput,\n storageInit,\n errorCode: \"STORAGE_ERROR\",\n errorMessage: \"Storage upload failed\",\n }),\n ])\n .execute();\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n try {\n const completed = await this.handlerTx()\n .withServiceCalls(() => [\n services.createCompletedUpload({\n ...createInput,\n storageInit,\n completedSizeBytes: BigInt(file.size),\n }),\n ])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(completed.file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files\",\n queryParameters: [\"provider\", \"prefix\", \"cursor\", \"pageSize\", \"status\", \"uploaderId\"],\n outputSchema: z.object({\n files: z.array(fileMetadataSchema),\n cursor: z.string().optional(),\n hasNextPage: z.boolean(),\n }),\n errorCodes,\n handler: async function ({ query }, { json, error }) {\n let params;\n try {\n params = parseListQuery(query);\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n const result = await this.handlerTx()\n .withServiceCalls(() => [\n services.listFiles({\n provider: params.provider,\n prefix: params.prefix,\n pageSize: params.pageSize,\n cursor: params.cursor,\n status: params.status,\n uploaderId: params.uploaderId,\n }),\n ])\n .transform(({ serviceResult: [files] }) => files)\n .execute();\n\n return json({\n files: result.items.map(toFileMetadata),\n cursor: result.cursor?.encode(),\n hasNextPage: result.hasNextPage,\n });\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files/by-key\",\n queryParameters: [\"provider\", \"key\"],\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function ({ query }, { json, error }) {\n let byKey;\n try {\n byKey = parseByKeyQuery(query);\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n const file = await this.handlerTx()\n .withServiceCalls(() => [services.getFileByKey(byKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"PATCH\",\n path: \"/files/by-key\",\n queryParameters: [\"provider\", \"key\"],\n inputSchema: updateFileSchema,\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function ({ query, input }, { json, error }) {\n const payload = await input.valid();\n let byKey;\n try {\n byKey = parseByKeyQuery(query);\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n const file = await this.handlerTx()\n .withServiceCalls(() => [services.updateFile(byKey, payload)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"DELETE\",\n path: \"/files/by-key\",\n queryParameters: [\"provider\", \"key\"],\n outputSchema: z.object({ ok: z.literal(true) }),\n errorCodes,\n handler: async function ({ query }, { json, error }) {\n let byKey;\n try {\n byKey = parseByKeyQuery(query);\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n await this.handlerTx()\n .withServiceCalls(() => [services.markFileDeleted(byKey)])\n .execute();\n\n return json({ ok: true });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files/by-key/download-url\",\n queryParameters: [\"provider\", \"key\"],\n outputSchema: z.object({\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n expiresAt: z.date(),\n }),\n errorCodes,\n handler: async function ({ query }, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n let byKey;\n try {\n byKey = parseByKeyQuery(query);\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n if (!resolvedConfig.storage.getDownloadUrl) {\n return error(\n {\n message: \"Signed URLs are not supported\",\n code: \"SIGNED_URL_UNSUPPORTED\",\n },\n 400,\n );\n }\n\n try {\n const file = assertFileAvailable(\n await this.handlerTx()\n .withServiceCalls(() => [services.getFileByKey(byKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute(),\n );\n\n let result;\n try {\n result = await resolvedConfig.storage.getDownloadUrl({\n storageKey: file.objectKey,\n expiresInSeconds: resolvedConfig.signedUrlExpiresInSeconds,\n contentType: file.contentType ?? undefined,\n });\n } catch {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n return json(result);\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/files/by-key/content\",\n queryParameters: [\"provider\", \"key\"],\n errorCodes,\n handler: async function ({ query }, { error }) {\n const resolvedConfig = getResolvedConfig();\n let byKey;\n try {\n byKey = parseByKeyQuery(query);\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n if (!resolvedConfig.storage.getDownloadStream) {\n return error(\n {\n message: \"Download streaming unsupported\",\n code: \"SIGNED_URL_UNSUPPORTED\",\n },\n 400,\n );\n }\n\n try {\n const file = assertFileAvailable(\n await this.handlerTx()\n .withServiceCalls(() => [services.getFileByKey(byKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute(),\n );\n\n try {\n return await resolvedConfig.storage.getDownloadStream({\n storageKey: file.objectKey,\n });\n } catch {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n ];\n },\n);\n"],"mappings":";;;;;;AAiCA,MAAMM,mBAAmBN,EAAEO,OAAO;CAChCC,UAAUR,EAAES,QAAQ,CAACC,IAAI,EAAE,CAACC,UAAU;CACtCC,YAAYP,iBAAiBM,UAAU;CACvCE,MAAMb,EAAEc,MAAMd,EAAES,QAAQ,CAAC,CAACM,UAAU,CAACJ,UAAU;CAC/CK,UAAUhB,EAAEiB,OAAOjB,EAAES,QAAQ,EAAET,EAAEkB,SAAS,CAAC,CAACH,UAAU,CAACJ,UAAS;CACjE,CAAC;AAEF,MAAMQ,aAAa;CACjB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAmGD,MAAaM,oBAAoBxB,aAAaE,yBAAyB,CAACuB,QACrE,EAAEC,UAAUC,aAAaC,aAAa;AAsCrC,QAAO;EACLD,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNC,aAAa;GACbC,cAAc7B;GACde;GACAe,eAAO;GA4LR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNI,iBAAiB;IAAC;IAAY;IAAU;IAAU;IAAY;IAAU;IAAa;GACrFF,cAAcjC,EAAEO,OAAO;IACrB6B,OAAOpC,EAAEc,MAAMV,mBAAmB;IAClCiC,QAAQrC,EAAES,QAAQ,CAACE,UAAU;IAC7B2B,aAAatC,EAAEuC,SAAQ;IACxB,CAAC;GACFpB;GACAe,eAAO;GA4BR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNI,iBAAiB,CAAC,YAAY,MAAM;GACpCF,cAAc7B;GACde;GACAe,eAAO;GAmBR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNI,iBAAiB,CAAC,YAAY,MAAM;GACpCK,aAAalC;GACb2B,cAAc7B;GACde;GACAe,eAAO;GAoBR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNI,iBAAiB,CAAC,YAAY,MAAM;GACpCF,cAAcjC,EAAEO,OAAO,EAAEkC,IAAIzC,EAAE0C,QAAQ,KAAI,EAAG,CAAC;GAC/CvB;GACAe,eAAO;GAkBR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNI,iBAAiB,CAAC,YAAY,MAAM;GACpCF,cAAcjC,EAAEO,OAAO;IACrBoC,KAAK3C,EAAES,QAAQ;IACfmC,SAAS5C,EAAEiB,OAAOjB,EAAES,QAAQ,EAAET,EAAES,QAAQ,CAAC,CAACE,UAAU;IACpDkC,WAAW7C,EAAE8C,MAAK;IACnB,CAAC;GACF3B;GACAe,eAAO;GA2CR,CAAC;EAEFN,YAAY;GACVE,QAAQ;GACRC,MAAM;GACNI,iBAAiB,CAAC,YAAY,MAAM;GACpChB;GACAe,eAAO;GAsCR,CAAC;EACH;EAEJ"}
|
|
@@ -2,11 +2,11 @@ import "../schema.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/routes/shared.ts
|
|
5
|
-
const fileKeyPartsSchema = z.array(z.union([z.string(), z.number().int()]));
|
|
6
5
|
const checksumSchema = z.object({
|
|
7
6
|
algo: z.enum(["sha256", "md5"]),
|
|
8
7
|
value: z.string()
|
|
9
8
|
}).nullable().optional();
|
|
9
|
+
const providerNamespaceSchema = z.string().trim().min(1).refine((value) => value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\"), { message: "Invalid provider" });
|
|
10
10
|
const visibilitySchema = z.enum([
|
|
11
11
|
"private",
|
|
12
12
|
"public",
|
|
@@ -14,7 +14,6 @@ const visibilitySchema = z.enum([
|
|
|
14
14
|
]);
|
|
15
15
|
const fileMetadataSchema = z.object({
|
|
16
16
|
fileKey: z.string(),
|
|
17
|
-
fileKeyParts: fileKeyPartsSchema,
|
|
18
17
|
uploaderId: z.string().nullable(),
|
|
19
18
|
filename: z.string(),
|
|
20
19
|
sizeBytes: z.number(),
|
|
@@ -27,7 +26,7 @@ const fileMetadataSchema = z.object({
|
|
|
27
26
|
tags: z.array(z.string()).nullable(),
|
|
28
27
|
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
29
28
|
status: z.enum(["ready", "deleted"]),
|
|
30
|
-
|
|
29
|
+
provider: providerNamespaceSchema,
|
|
31
30
|
createdAt: z.string(),
|
|
32
31
|
updatedAt: z.string(),
|
|
33
32
|
completedAt: z.string().nullable(),
|
|
@@ -37,5 +36,5 @@ const fileMetadataSchema = z.object({
|
|
|
37
36
|
});
|
|
38
37
|
|
|
39
38
|
//#endregion
|
|
40
|
-
export { checksumSchema,
|
|
39
|
+
export { checksumSchema, fileMetadataSchema, providerNamespaceSchema, visibilitySchema };
|
|
41
40
|
//# sourceMappingURL=shared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","names":["
|
|
1
|
+
{"version":3,"file":"shared.js","names":["TableToColumnValues","z","uploadSchema","FileMetadata","FileRow","tables","file","FileMetadataSource","Omit","id","checksumSchema","object","algo","enum","value","string","nullable","optional","providerNamespaceSchema","trim","min","refine","includes","message","visibilitySchema","fileMetadataSchema","fileKey","uploaderId","filename","sizeBytes","number","contentType","checksum","visibility","tags","array","metadata","record","unknown","status","provider","createdAt","updatedAt","completedAt","deletedAt","errorCode","errorMessage","toFileMetadata","toIsoString","Date","toISOString","key","Number"],"sources":["../../../../../../../src/routes/shared.ts"],"sourcesContent":["import type { TableToColumnValues } from \"@fragno-dev/db/query\";\nimport { z } from \"zod\";\n\nimport { uploadSchema } from \"../schema\";\nimport type { FileMetadata } from \"../types\";\n\ntype FileRow = TableToColumnValues<typeof uploadSchema.tables.file>;\ntype FileMetadataSource = Omit<FileRow, \"id\"> & { id?: unknown };\n\nexport const checksumSchema = z\n .object({\n algo: z.enum([\"sha256\", \"md5\"]),\n value: z.string(),\n })\n .nullable()\n .optional();\n\nexport const providerNamespaceSchema = z\n .string()\n .trim()\n .min(1)\n .refine(\n (value) => value !== \".\" && value !== \"..\" && !value.includes(\"/\") && !value.includes(\"\\\\\"),\n {\n message: \"Invalid provider\",\n },\n );\n\nexport const visibilitySchema = z.enum([\"private\", \"public\", \"unlisted\"]);\n\nexport const fileMetadataSchema = z.object({\n fileKey: z.string(),\n uploaderId: z.string().nullable(),\n filename: z.string(),\n sizeBytes: z.number(),\n contentType: z.string(),\n checksum: z\n .object({\n algo: z.enum([\"sha256\", \"md5\"]),\n value: z.string(),\n })\n .nullable(),\n visibility: visibilitySchema,\n tags: z.array(z.string()).nullable(),\n metadata: z.record(z.string(), z.unknown()).nullable(),\n status: z.enum([\"ready\", \"deleted\"]),\n provider: providerNamespaceSchema,\n createdAt: z.string(),\n updatedAt: z.string(),\n completedAt: z.string().nullable(),\n deletedAt: z.string().nullable(),\n errorCode: z.string().nullable(),\n errorMessage: z.string().nullable(),\n});\n\nexport const toFileMetadata = (file: FileMetadataSource): FileMetadata => {\n const toIsoString = (value: Date | null | undefined) => (value ? value.toISOString() : null);\n\n return {\n fileKey: file.key,\n uploaderId: file.uploaderId,\n filename: file.filename,\n sizeBytes: Number(file.sizeBytes),\n contentType: file.contentType,\n checksum: file.checksum as FileMetadata[\"checksum\"],\n visibility: file.visibility as FileMetadata[\"visibility\"],\n tags: file.tags as FileMetadata[\"tags\"],\n metadata: file.metadata as FileMetadata[\"metadata\"],\n status: file.status as FileMetadata[\"status\"],\n provider: file.provider,\n createdAt: file.createdAt.toISOString(),\n updatedAt: file.updatedAt.toISOString(),\n completedAt: toIsoString(file.completedAt),\n deletedAt: toIsoString(file.deletedAt),\n errorCode: file.errorCode,\n errorMessage: file.errorMessage,\n };\n};\n"],"mappings":";;;;AASA,MAAaU,iBAAiBT,EAC3BU,OAAO;CACNC,MAAMX,EAAEY,KAAK,CAAC,UAAU,MAAM,CAAC;CAC/BC,OAAOb,EAAEc,QAAO;CACjB,CAAC,CACDC,UAAU,CACVC,UAAU;AAEb,MAAaC,0BAA0BjB,EACpCc,QAAQ,CACRI,MAAM,CACNC,IAAI,EAAE,CACNC,QACEP,UAAUA,UAAU,OAAOA,UAAU,QAAQ,CAACA,MAAMQ,SAAS,IAAI,IAAI,CAACR,MAAMQ,SAAS,KAAK,EAC3F,EACEC,SAAS,oBAEb,CAAC;AAEH,MAAaC,mBAAmBvB,EAAEY,KAAK;CAAC;CAAW;CAAU;CAAW,CAAC;AAEzE,MAAaY,qBAAqBxB,EAAEU,OAAO;CACzCe,SAASzB,EAAEc,QAAQ;CACnBY,YAAY1B,EAAEc,QAAQ,CAACC,UAAU;CACjCY,UAAU3B,EAAEc,QAAQ;CACpBc,WAAW5B,EAAE6B,QAAQ;CACrBC,aAAa9B,EAAEc,QAAQ;CACvBiB,UAAU/B,EACPU,OAAO;EACNC,MAAMX,EAAEY,KAAK,CAAC,UAAU,MAAM,CAAC;EAC/BC,OAAOb,EAAEc,QAAO;EACjB,CAAC,CACDC,UAAU;CACbiB,YAAYT;CACZU,MAAMjC,EAAEkC,MAAMlC,EAAEc,QAAQ,CAAC,CAACC,UAAU;CACpCoB,UAAUnC,EAAEoC,OAAOpC,EAAEc,QAAQ,EAAEd,EAAEqC,SAAS,CAAC,CAACtB,UAAU;CACtDuB,QAAQtC,EAAEY,KAAK,CAAC,SAAS,UAAU,CAAC;CACpC2B,UAAUtB;CACVuB,WAAWxC,EAAEc,QAAQ;CACrB2B,WAAWzC,EAAEc,QAAQ;CACrB4B,aAAa1C,EAAEc,QAAQ,CAACC,UAAU;CAClC4B,WAAW3C,EAAEc,QAAQ,CAACC,UAAU;CAChC6B,WAAW5C,EAAEc,QAAQ,CAACC,UAAU;CAChC8B,cAAc7C,EAAEc,QAAQ,CAACC,UAAS;CACnC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { uploadFragmentDefinition } from "../definition.js";
|
|
2
|
-
import { checksumSchema,
|
|
2
|
+
import { checksumSchema, fileMetadataSchema, providerNamespaceSchema } from "./shared.js";
|
|
3
3
|
import { defineRoutes } from "@fragno-dev/core";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
@@ -10,8 +10,10 @@ const uploadStrategySchema = z.enum([
|
|
|
10
10
|
"proxy"
|
|
11
11
|
]);
|
|
12
12
|
const safeIntSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
13
|
+
const legacyFileKeyPartsSchema = z.array(z.union([z.string(), z.number().int()]));
|
|
13
14
|
const createUploadInputSchema = z.object({
|
|
14
|
-
|
|
15
|
+
provider: providerNamespaceSchema.optional(),
|
|
16
|
+
keyParts: legacyFileKeyPartsSchema.optional(),
|
|
15
17
|
fileKey: z.string().optional(),
|
|
16
18
|
filename: z.string().min(1),
|
|
17
19
|
sizeBytes: safeIntSchema,
|
|
@@ -40,7 +42,32 @@ const completeUploadSchema = z.object({ parts: z.array(z.object({
|
|
|
40
42
|
partNumber: z.number().int().min(1),
|
|
41
43
|
etag: z.string().min(1)
|
|
42
44
|
})).optional() });
|
|
43
|
-
const
|
|
45
|
+
const uploadRouteDataSchema = z.object({
|
|
46
|
+
provider: z.string(),
|
|
47
|
+
upload: z.object({
|
|
48
|
+
mode: z.enum(["single", "multipart"]),
|
|
49
|
+
transport: z.enum(["direct", "proxy"]),
|
|
50
|
+
uploadUrl: z.string().optional(),
|
|
51
|
+
uploadHeaders: z.record(z.string(), z.string()).optional(),
|
|
52
|
+
partSizeBytes: z.number().optional(),
|
|
53
|
+
maxParts: z.number().optional(),
|
|
54
|
+
statusEndpoint: z.string(),
|
|
55
|
+
progressEndpoint: z.string(),
|
|
56
|
+
partsEndpoint: z.string().optional(),
|
|
57
|
+
partsCompleteEndpoint: z.string().optional(),
|
|
58
|
+
completeEndpoint: z.string(),
|
|
59
|
+
abortEndpoint: z.string(),
|
|
60
|
+
contentEndpoint: z.string().optional()
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
const createUploadOutputSchema = uploadRouteDataSchema.extend({
|
|
64
|
+
uploadId: z.string(),
|
|
65
|
+
fileKey: z.string(),
|
|
66
|
+
status: z.enum(["created", "in_progress"]),
|
|
67
|
+
strategy: uploadStrategySchema,
|
|
68
|
+
expiresAt: z.date()
|
|
69
|
+
});
|
|
70
|
+
const uploadStatusSchema = uploadRouteDataSchema.extend({
|
|
44
71
|
uploadId: z.string(),
|
|
45
72
|
fileKey: z.string(),
|
|
46
73
|
status: z.enum([
|
|
@@ -70,6 +97,7 @@ const errorCodes = [
|
|
|
70
97
|
"FILE_ALREADY_EXISTS",
|
|
71
98
|
"UPLOAD_EXPIRED",
|
|
72
99
|
"UPLOAD_INVALID_STATE",
|
|
100
|
+
"PROVIDER_MISMATCH",
|
|
73
101
|
"INVALID_FILE_KEY",
|
|
74
102
|
"INVALID_CHECKSUM",
|
|
75
103
|
"INVALID_REQUEST",
|
|
@@ -81,24 +109,7 @@ const uploadRoutesFactory = defineRoutes(uploadFragmentDefinition).create(({ ser
|
|
|
81
109
|
method: "POST",
|
|
82
110
|
path: "/uploads",
|
|
83
111
|
inputSchema: createUploadInputSchema,
|
|
84
|
-
outputSchema:
|
|
85
|
-
uploadId: z.string(),
|
|
86
|
-
fileKey: z.string(),
|
|
87
|
-
status: z.enum(["created", "in_progress"]),
|
|
88
|
-
strategy: uploadStrategySchema,
|
|
89
|
-
expiresAt: z.date(),
|
|
90
|
-
upload: z.object({
|
|
91
|
-
mode: z.enum(["single", "multipart"]),
|
|
92
|
-
transport: z.enum(["direct", "proxy"]),
|
|
93
|
-
uploadUrl: z.string().optional(),
|
|
94
|
-
uploadHeaders: z.record(z.string(), z.string()).optional(),
|
|
95
|
-
partSizeBytes: z.number().optional(),
|
|
96
|
-
maxParts: z.number().optional(),
|
|
97
|
-
partsEndpoint: z.string().optional(),
|
|
98
|
-
completeEndpoint: z.string(),
|
|
99
|
-
contentEndpoint: z.string().optional()
|
|
100
|
-
})
|
|
101
|
-
}),
|
|
112
|
+
outputSchema: createUploadOutputSchema,
|
|
102
113
|
errorCodes,
|
|
103
114
|
handler: () => {}
|
|
104
115
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploads.js","names":["defineRoutes","FragnoRouteConfig","z","uploadFragmentDefinition","checksumSchema","fileKeyPartsSchema","fileMetadataSchema","uploadStrategySchema","enum","safeIntSchema","number","int","min","max","Number","MAX_SAFE_INTEGER","createUploadInputSchema","object","keyParts","optional","fileKey","string","filename","sizeBytes","contentType","checksum","tags","array","visibility","uploaderId","metadata","record","unknown","progressSchema","bytesUploaded","partsUploaded","partNumbersSchema","partNumbers","completePartsSchema","parts","partNumber","etag","completeUploadSchema","uploadStatusSchema","uploadId","status","strategy","expectedSizeBytes","partSizeBytes","nullable","expiresAt","date","createdAt","updatedAt","completedAt","errorCode","errorMessage","errorCodes","const","UploadErrorCode","ErrorFn","Parameters","Code","uploadRoutesFactory","create","services","defineRoute","config","method","path","inputSchema","outputSchema","upload","mode","transport","uploadUrl","uploadHeaders","maxParts","partsEndpoint","completeEndpoint","contentEndpoint","handler","url","headers","ok","literal"],"sources":["../../../../../../../src/routes/uploads.ts"],"sourcesContent":["import { defineRoutes } from \"@fragno-dev/core\";\nimport type { FragnoRouteConfig } from \"@fragno-dev/core\";\nimport { z } from \"zod\";\nimport { resolveUploadFragmentConfig } from \"../config\";\nimport { uploadFragmentDefinition } from \"../definition\";\nimport { resolveFileKeyInput } from \"../services/helpers\";\nimport { checksumSchema, fileKeyPartsSchema, fileMetadataSchema, toFileMetadata } from \"./shared\";\nimport type { UploadStatus, UploadStrategy } from \"../types\";\nimport type { UploadChecksum } from \"../storage/types\";\n\nconst uploadStrategySchema = z.enum([\"direct-single\", \"direct-multipart\", \"proxy\"]);\nconst safeIntSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);\n\nconst createUploadInputSchema = z.object({\n keyParts: fileKeyPartsSchema.optional(),\n fileKey: z.string().optional(),\n filename: z.string().min(1),\n sizeBytes: safeIntSchema,\n contentType: z.string().min(1),\n checksum: checksumSchema.optional(),\n tags: z.array(z.string()).optional(),\n visibility: z.enum([\"private\", \"public\", \"unlisted\"]).optional(),\n uploaderId: z.string().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst progressSchema = z.object({\n bytesUploaded: safeIntSchema.optional(),\n partsUploaded: z.number().int().min(0).optional(),\n});\n\nconst partNumbersSchema = z.object({\n partNumbers: z.array(z.number().int().min(1)).min(1),\n});\n\nconst completePartsSchema = z.object({\n parts: z\n .array(\n z.object({\n partNumber: z.number().int().min(1),\n etag: z.string().min(1),\n sizeBytes: safeIntSchema,\n }),\n )\n .min(1),\n});\n\nconst completeUploadSchema = z.object({\n parts: z\n .array(\n z.object({\n partNumber: z.number().int().min(1),\n etag: z.string().min(1),\n }),\n )\n .optional(),\n});\n\nconst uploadStatusSchema = z.object({\n uploadId: z.string(),\n fileKey: z.string(),\n status: z.enum([\"created\", \"in_progress\", \"completed\", \"aborted\", \"failed\", \"expired\"]),\n strategy: uploadStrategySchema,\n expectedSizeBytes: z.number(),\n bytesUploaded: z.number(),\n partsUploaded: z.number(),\n partSizeBytes: z.number().nullable(),\n expiresAt: z.date(),\n createdAt: z.date(),\n updatedAt: z.date(),\n completedAt: z.date().nullable(),\n errorCode: z.string().nullable(),\n errorMessage: z.string().nullable(),\n});\n\nconst errorCodes = [\n \"UPLOAD_NOT_FOUND\",\n \"UPLOAD_ALREADY_ACTIVE\",\n \"UPLOAD_METADATA_MISMATCH\",\n \"FILE_ALREADY_EXISTS\",\n \"UPLOAD_EXPIRED\",\n \"UPLOAD_INVALID_STATE\",\n \"INVALID_FILE_KEY\",\n \"INVALID_CHECKSUM\",\n \"INVALID_REQUEST\",\n \"STORAGE_ERROR\",\n] as const;\n\ntype UploadErrorCode = (typeof errorCodes)[number];\n\ntype ErrorFn<Code extends string> = Parameters<\n FragnoRouteConfig<\"GET\", \"/__error\", undefined, undefined, Code>[\"handler\"]\n>[1][\"error\"];\n\nconst handleServiceError = <Code extends UploadErrorCode>(\n err: unknown,\n error: ErrorFn<Code>,\n): Response => {\n if (!(err instanceof Error)) {\n throw err;\n }\n\n switch (err.message) {\n case \"UPLOAD_NOT_FOUND\":\n return error({ message: \"Upload not found\", code: \"UPLOAD_NOT_FOUND\" as Code }, 404);\n case \"FILE_ALREADY_EXISTS\":\n return error({ message: \"File already exists\", code: \"FILE_ALREADY_EXISTS\" as Code }, 409);\n case \"UPLOAD_ALREADY_ACTIVE\":\n return error(\n { message: \"Upload already active\", code: \"UPLOAD_ALREADY_ACTIVE\" as Code },\n 409,\n );\n case \"UPLOAD_METADATA_MISMATCH\":\n return error(\n { message: \"Upload metadata mismatch\", code: \"UPLOAD_METADATA_MISMATCH\" as Code },\n 409,\n );\n case \"UPLOAD_EXPIRED\":\n return error({ message: \"Upload expired\", code: \"UPLOAD_EXPIRED\" as Code }, 410);\n case \"UPLOAD_INVALID_STATE\":\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" as Code }, 409);\n case \"INVALID_FILE_KEY\":\n return error({ message: \"Invalid file key\", code: \"INVALID_FILE_KEY\" as Code }, 400);\n case \"INVALID_CHECKSUM\":\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" as Code }, 400);\n case \"INVALID_REQUEST\":\n return error({ message: \"Invalid request\", code: \"INVALID_REQUEST\" as Code }, 400);\n case \"STORAGE_ERROR\":\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" as Code }, 502);\n default:\n throw err;\n }\n};\n\nexport const uploadRoutesFactory = defineRoutes(uploadFragmentDefinition).create(\n ({ services, defineRoute, config }) => {\n const getResolvedConfig = () => resolveUploadFragmentConfig(config);\n\n return [\n defineRoute({\n method: \"POST\",\n path: \"/uploads\",\n inputSchema: createUploadInputSchema,\n outputSchema: z.object({\n uploadId: z.string(),\n fileKey: z.string(),\n status: z.enum([\"created\", \"in_progress\"]),\n strategy: uploadStrategySchema,\n expiresAt: z.date(),\n upload: z.object({\n mode: z.enum([\"single\", \"multipart\"]),\n transport: z.enum([\"direct\", \"proxy\"]),\n uploadUrl: z.string().optional(),\n uploadHeaders: z.record(z.string(), z.string()).optional(),\n partSizeBytes: z.number().optional(),\n maxParts: z.number().optional(),\n partsEndpoint: z.string().optional(),\n completeEndpoint: z.string(),\n contentEndpoint: z.string().optional(),\n }),\n }),\n errorCodes,\n handler: async function ({ input }, { json, error }) {\n const payload = await input.valid();\n const resolvedConfig = getResolvedConfig();\n\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({\n keyParts: payload.keyParts,\n fileKey: payload.fileKey,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n try {\n const existing = await this.handlerTx()\n .withServiceCalls(() => [\n services.checkUploadAvailability(payload, { allowIdempotentReuse: true }),\n ])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n if (existing) {\n return json(existing);\n }\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n let storageInit;\n try {\n storageInit = await resolvedConfig.storage.initUpload({\n fileKey: resolvedKey.fileKey,\n fileKeyParts: resolvedKey.fileKeyParts,\n sizeBytes: BigInt(payload.sizeBytes),\n contentType: payload.contentType,\n checksum: payload.checksum ?? null,\n metadata: payload.metadata ?? null,\n });\n } catch (err) {\n if (err instanceof Error && err.message === \"INVALID_CHECKSUM\") {\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" }, 400);\n }\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n try {\n const result = await this.handlerTx()\n .withServiceCalls(() => [\n services.createUploadRecord({\n ...payload,\n storageInit,\n allowIdempotentReuse: true,\n }),\n ])\n .transform(({ serviceResult: [created] }) => created)\n .execute();\n\n if (\n result.reused &&\n storageInit.strategy === \"direct-multipart\" &&\n resolvedConfig.storage.abortMultipartUpload &&\n storageInit.storageUploadId\n ) {\n try {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: storageInit.storageKey,\n storageUploadId: storageInit.storageUploadId,\n });\n } catch {\n // Ignore abort failures for races.\n }\n }\n\n return json(result.result);\n } catch (err) {\n if (\n storageInit.strategy === \"direct-multipart\" &&\n resolvedConfig.storage.abortMultipartUpload &&\n storageInit.storageUploadId\n ) {\n try {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: storageInit.storageKey,\n storageUploadId: storageInit.storageUploadId,\n });\n } catch {\n // Ignore abort failures for races.\n }\n }\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/uploads/:uploadId\",\n outputSchema: uploadStatusSchema,\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n try {\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStatus(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json({\n uploadId: upload.id.toString(),\n fileKey: upload.fileKey,\n status: upload.status as UploadStatus,\n strategy: upload.strategy as UploadStrategy,\n expectedSizeBytes: Number(upload.expectedSizeBytes),\n bytesUploaded: Number(upload.bytesUploaded),\n partsUploaded: upload.partsUploaded,\n partSizeBytes: upload.partSizeBytes,\n expiresAt: upload.expiresAt,\n createdAt: upload.createdAt,\n updatedAt: upload.updatedAt,\n completedAt: upload.completedAt,\n errorCode: upload.errorCode,\n errorMessage: upload.errorMessage,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/progress\",\n inputSchema: progressSchema,\n outputSchema: z.object({\n bytesUploaded: z.number(),\n partsUploaded: z.number(),\n }),\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n try {\n const result = await this.handlerTx()\n .withServiceCalls(() => [services.recordUploadProgress(pathParams.uploadId, payload)])\n .transform(({ serviceResult: [updated] }) => updated)\n .execute();\n\n return json({\n bytesUploaded: Number(result.bytesUploaded),\n partsUploaded: result.partsUploaded,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/parts\",\n inputSchema: partNumbersSchema,\n outputSchema: z.object({\n parts: z.array(\n z.object({\n partNumber: z.number(),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n }),\n ),\n }),\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n const resolvedConfig = getResolvedConfig();\n try {\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n if (upload.strategy !== \"direct-multipart\") {\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n if (!resolvedConfig.storage.getPartUploadUrls) {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n const parts = await resolvedConfig.storage.getPartUploadUrls({\n storageKey: upload.storageKey,\n storageUploadId: upload.storageUploadId ?? \"\",\n partNumbers: payload.partNumbers,\n partSizeBytes: upload.partSizeBytes ?? 0,\n });\n\n return json({ parts });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/uploads/:uploadId/parts\",\n outputSchema: z.object({\n parts: z.array(\n z.object({\n partNumber: z.number(),\n etag: z.string(),\n sizeBytes: z.number(),\n createdAt: z.date(),\n }),\n ),\n }),\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n try {\n const parts = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadParts(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const typedParts = parts as {\n partNumber: number;\n etag: string;\n sizeBytes: bigint;\n createdAt: Date;\n }[];\n\n return json({\n parts: typedParts.map((part) => ({\n partNumber: part.partNumber,\n etag: part.etag,\n sizeBytes: Number(part.sizeBytes),\n createdAt: part.createdAt,\n })),\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/parts/complete\",\n inputSchema: completePartsSchema,\n outputSchema: z.object({\n bytesUploaded: z.number(),\n partsUploaded: z.number(),\n }),\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n try {\n const result = await this.handlerTx()\n .withServiceCalls(() => [services.recordUploadParts(pathParams.uploadId, payload)])\n .transform(({ serviceResult: [updated] }) => updated)\n .execute();\n\n return json({\n bytesUploaded: Number(result.bytesUploaded),\n partsUploaded: result.partsUploaded,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/complete\",\n inputSchema: completeUploadSchema,\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n const resolvedConfig = getResolvedConfig();\n try {\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const existingFile = await this.handlerTx()\n .withServiceCalls(() => [services.findFileByKey(upload.fileKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n if (existingFile) {\n return error({ message: \"File already exists\", code: \"FILE_ALREADY_EXISTS\" }, 409);\n }\n\n let finalizeResult: { sizeBytes?: bigint } | undefined;\n\n if (upload.strategy === \"direct-multipart\") {\n if (!resolvedConfig.storage.completeMultipartUpload) {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n if (!payload.parts || payload.parts.length === 0) {\n return error(\n { message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" },\n 409,\n );\n }\n\n await resolvedConfig.storage.completeMultipartUpload({\n storageKey: upload.storageKey,\n storageUploadId: upload.storageUploadId ?? \"\",\n parts: payload.parts,\n });\n } else if (resolvedConfig.storage.finalizeUpload) {\n finalizeResult = await resolvedConfig.storage.finalizeUpload({\n storageKey: upload.storageKey,\n expectedSizeBytes: upload.expectedSizeBytes,\n checksum: upload.checksum as UploadChecksum | null,\n });\n }\n\n const completed = await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadComplete(\n upload.id.toString(),\n upload.fileKey,\n finalizeResult?.sizeBytes ? { sizeBytes: finalizeResult.sizeBytes } : undefined,\n ),\n ])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(completed.file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/abort\",\n outputSchema: z.object({ ok: z.literal(true) }),\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n try {\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n if (\n upload.strategy === \"direct-multipart\" &&\n resolvedConfig.storage.abortMultipartUpload\n ) {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: upload.storageKey,\n storageUploadId: upload.storageUploadId ?? \"\",\n });\n }\n\n await this.handlerTx()\n .withServiceCalls(() => [services.markUploadAborted(upload.id.toString())])\n .execute();\n\n return json({ ok: true });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"PUT\",\n path: \"/uploads/:uploadId/content\",\n contentType: \"application/octet-stream\",\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function (context, { json, error }) {\n const { pathParams } = context;\n const resolvedConfig = getResolvedConfig();\n try {\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n if (upload.strategy !== \"proxy\") {\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n const existingFile = await this.handlerTx()\n .withServiceCalls(() => [services.findFileByKey(upload.fileKey)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n if (existingFile) {\n return error({ message: \"File already exists\", code: \"FILE_ALREADY_EXISTS\" }, 409);\n }\n\n if (!resolvedConfig.storage.writeStream) {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n let result: Awaited<ReturnType<NonNullable<typeof resolvedConfig.storage.writeStream>>>;\n try {\n result = await resolvedConfig.storage.writeStream({\n storageKey: upload.storageKey,\n body: context.bodyStream(),\n contentType: upload.contentType,\n sizeBytes: upload.expectedSizeBytes,\n });\n } catch {\n await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadFailed(\n upload.id.toString(),\n \"STORAGE_ERROR\",\n \"Storage upload failed\",\n ),\n ])\n .execute();\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n const completed = await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadComplete(\n upload.id.toString(),\n upload.fileKey,\n result?.sizeBytes ? { sizeBytes: result.sizeBytes } : undefined,\n ),\n ])\n .transform(({ serviceResult: [done] }) => done)\n .execute();\n\n return json(toFileMetadata(completed.file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n ];\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"uploads.js","names":["z","defineRoutes","FragnoRouteConfig","uploadFragmentDefinition","checksumSchema","fileMetadataSchema","providerNamespaceSchema","uploadStrategySchema","enum","safeIntSchema","number","int","min","max","Number","MAX_SAFE_INTEGER","legacyFileKeyPartsSchema","array","union","string","createUploadInputSchema","object","provider","optional","keyParts","fileKey","filename","sizeBytes","contentType","checksum","tags","visibility","uploaderId","metadata","record","unknown","progressSchema","bytesUploaded","partsUploaded","partNumbersSchema","partNumbers","completePartsSchema","parts","partNumber","etag","completeUploadSchema","uploadRouteDataSchema","upload","mode","transport","uploadUrl","uploadHeaders","partSizeBytes","maxParts","statusEndpoint","progressEndpoint","partsEndpoint","partsCompleteEndpoint","completeEndpoint","abortEndpoint","contentEndpoint","createUploadOutputSchema","extend","uploadId","status","strategy","expiresAt","date","uploadStatusSchema","expectedSizeBytes","nullable","createdAt","updatedAt","completedAt","errorCode","errorMessage","errorCodes","const","UploadErrorCode","ErrorFn","Parameters","Code","uploadRoutesFactory","create","services","defineRoute","config","method","path","inputSchema","outputSchema","handler","url","headers","ok","literal"],"sources":["../../../../../../../src/routes/uploads.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport { defineRoutes } from \"@fragno-dev/core\";\nimport type { FragnoRouteConfig } from \"@fragno-dev/core\";\n\nimport { resolveUploadFragmentConfig } from \"../config\";\nimport { uploadFragmentDefinition } from \"../definition\";\nimport { resolveFileKeyInput } from \"../services/helpers\";\nimport { buildUploadSessionRouteData } from \"../services/uploads\";\nimport { buildStorageObjectVersionSegment } from \"../storage/object-key\";\nimport type { UploadChecksum } from \"../storage/types\";\nimport type { UploadStatus, UploadStrategy } from \"../types\";\nimport {\n checksumSchema,\n fileMetadataSchema,\n providerNamespaceSchema,\n toFileMetadata,\n} from \"./shared\";\n\nconst uploadStrategySchema = z.enum([\"direct-single\", \"direct-multipart\", \"proxy\"]);\nconst safeIntSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);\nconst legacyFileKeyPartsSchema = z.array(z.union([z.string(), z.number().int()]));\n\nconst createUploadInputSchema = z.object({\n provider: providerNamespaceSchema.optional(),\n keyParts: legacyFileKeyPartsSchema.optional(),\n fileKey: z.string().optional(),\n filename: z.string().min(1),\n sizeBytes: safeIntSchema,\n contentType: z.string().min(1),\n checksum: checksumSchema.optional(),\n tags: z.array(z.string()).optional(),\n visibility: z.enum([\"private\", \"public\", \"unlisted\"]).optional(),\n uploaderId: z.string().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst progressSchema = z.object({\n bytesUploaded: safeIntSchema.optional(),\n partsUploaded: z.number().int().min(0).optional(),\n});\n\nconst partNumbersSchema = z.object({\n partNumbers: z.array(z.number().int().min(1)).min(1),\n});\n\nconst completePartsSchema = z.object({\n parts: z\n .array(\n z.object({\n partNumber: z.number().int().min(1),\n etag: z.string().min(1),\n sizeBytes: safeIntSchema,\n }),\n )\n .min(1),\n});\n\nconst completeUploadSchema = z.object({\n parts: z\n .array(\n z.object({\n partNumber: z.number().int().min(1),\n etag: z.string().min(1),\n }),\n )\n .optional(),\n});\n\nconst uploadRouteDataSchema = z.object({\n provider: z.string(),\n upload: z.object({\n mode: z.enum([\"single\", \"multipart\"]),\n transport: z.enum([\"direct\", \"proxy\"]),\n uploadUrl: z.string().optional(),\n uploadHeaders: z.record(z.string(), z.string()).optional(),\n partSizeBytes: z.number().optional(),\n maxParts: z.number().optional(),\n statusEndpoint: z.string(),\n progressEndpoint: z.string(),\n partsEndpoint: z.string().optional(),\n partsCompleteEndpoint: z.string().optional(),\n completeEndpoint: z.string(),\n abortEndpoint: z.string(),\n contentEndpoint: z.string().optional(),\n }),\n});\n\nconst createUploadOutputSchema = uploadRouteDataSchema.extend({\n uploadId: z.string(),\n fileKey: z.string(),\n status: z.enum([\"created\", \"in_progress\"]),\n strategy: uploadStrategySchema,\n expiresAt: z.date(),\n});\n\nconst uploadStatusSchema = uploadRouteDataSchema.extend({\n uploadId: z.string(),\n fileKey: z.string(),\n status: z.enum([\"created\", \"in_progress\", \"completed\", \"aborted\", \"failed\", \"expired\"]),\n strategy: uploadStrategySchema,\n expectedSizeBytes: z.number(),\n bytesUploaded: z.number(),\n partsUploaded: z.number(),\n partSizeBytes: z.number().nullable(),\n expiresAt: z.date(),\n createdAt: z.date(),\n updatedAt: z.date(),\n completedAt: z.date().nullable(),\n errorCode: z.string().nullable(),\n errorMessage: z.string().nullable(),\n});\n\nconst errorCodes = [\n \"UPLOAD_NOT_FOUND\",\n \"UPLOAD_ALREADY_ACTIVE\",\n \"UPLOAD_METADATA_MISMATCH\",\n \"FILE_ALREADY_EXISTS\",\n \"UPLOAD_EXPIRED\",\n \"UPLOAD_INVALID_STATE\",\n \"PROVIDER_MISMATCH\",\n \"INVALID_FILE_KEY\",\n \"INVALID_CHECKSUM\",\n \"INVALID_REQUEST\",\n \"STORAGE_ERROR\",\n] as const;\n\ntype UploadErrorCode = (typeof errorCodes)[number];\n\ntype ErrorFn<Code extends string> = Parameters<\n FragnoRouteConfig<\"GET\", \"/__error\", undefined, undefined, Code>[\"handler\"]\n>[1][\"error\"];\n\nconst rejectInactiveUpload = (\n upload: { status: UploadStatus; expiresAt: Date },\n error: ErrorFn<UploadErrorCode>,\n): Response | null => {\n const now = Date.now();\n\n if (upload.status === \"completed\") {\n return error({ message: \"File already exists\", code: \"FILE_ALREADY_EXISTS\" }, 409);\n }\n\n if (upload.status === \"expired\" || upload.expiresAt.getTime() <= now) {\n return error({ message: \"Upload expired\", code: \"UPLOAD_EXPIRED\" }, 410);\n }\n\n if (upload.status === \"aborted\" || upload.status === \"failed\") {\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n return null;\n};\n\nconst rejectProviderMismatch = (\n upload: { provider: string },\n activeProvider: string,\n error: ErrorFn<UploadErrorCode>,\n): Response | null => {\n if (upload.provider === activeProvider) {\n return null;\n }\n\n return error({ message: \"Upload provider mismatch\", code: \"PROVIDER_MISMATCH\" }, 409);\n};\n\nconst handleServiceError = <Code extends UploadErrorCode>(\n err: unknown,\n error: ErrorFn<Code>,\n): Response => {\n if (!(err instanceof Error)) {\n throw err;\n }\n\n switch (err.message) {\n case \"UPLOAD_NOT_FOUND\":\n return error({ message: \"Upload not found\", code: \"UPLOAD_NOT_FOUND\" as Code }, 404);\n case \"FILE_ALREADY_EXISTS\":\n return error({ message: \"File already exists\", code: \"FILE_ALREADY_EXISTS\" as Code }, 409);\n case \"UPLOAD_ALREADY_ACTIVE\":\n return error(\n {\n message: \"Upload already active\",\n code: \"UPLOAD_ALREADY_ACTIVE\" as Code,\n },\n 409,\n );\n case \"UPLOAD_METADATA_MISMATCH\":\n return error(\n {\n message: \"Upload metadata mismatch\",\n code: \"UPLOAD_METADATA_MISMATCH\" as Code,\n },\n 409,\n );\n case \"UPLOAD_EXPIRED\":\n return error({ message: \"Upload expired\", code: \"UPLOAD_EXPIRED\" as Code }, 410);\n case \"UPLOAD_INVALID_STATE\":\n return error(\n {\n message: \"Upload invalid state\",\n code: \"UPLOAD_INVALID_STATE\" as Code,\n },\n 409,\n );\n case \"PROVIDER_MISMATCH\":\n return error(\n {\n message: \"Upload provider mismatch\",\n code: \"PROVIDER_MISMATCH\" as Code,\n },\n 409,\n );\n case \"INVALID_FILE_KEY\":\n return error({ message: \"Invalid file key\", code: \"INVALID_FILE_KEY\" as Code }, 400);\n case \"INVALID_CHECKSUM\":\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" as Code }, 400);\n case \"INVALID_REQUEST\":\n return error({ message: \"Invalid request\", code: \"INVALID_REQUEST\" as Code }, 400);\n case \"STORAGE_ERROR\":\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" as Code }, 502);\n default:\n throw err;\n }\n};\n\nexport const uploadRoutesFactory = defineRoutes(uploadFragmentDefinition).create(\n ({ services, defineRoute, config }) => {\n const getResolvedConfig = () => resolveUploadFragmentConfig(config);\n\n return [\n defineRoute({\n method: \"POST\",\n path: \"/uploads\",\n inputSchema: createUploadInputSchema,\n outputSchema: createUploadOutputSchema,\n errorCodes,\n handler: async function ({ input }, { json, error }) {\n const payload = await input.valid();\n const resolvedConfig = getResolvedConfig();\n const provider = payload.provider ?? resolvedConfig.storage.name;\n\n let resolvedKey;\n try {\n resolvedKey = resolveFileKeyInput({\n keyParts: payload.keyParts,\n fileKey: payload.fileKey,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n\n const objectKeyVersionSegment = buildStorageObjectVersionSegment();\n\n let storageInit;\n try {\n storageInit = await resolvedConfig.storage.initUpload({\n provider,\n fileKey: resolvedKey.fileKey,\n sizeBytes: BigInt(payload.sizeBytes),\n contentType: payload.contentType,\n checksum: payload.checksum ?? null,\n metadata: payload.metadata ?? null,\n objectKeyVersionSegment,\n });\n } catch (err) {\n if (err instanceof Error && err.message === \"INVALID_CHECKSUM\") {\n return error({ message: \"Invalid checksum\", code: \"INVALID_CHECKSUM\" }, 400);\n }\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n try {\n const result = await this.handlerTx()\n .withServiceCalls(() => [\n services.createUploadRecord({\n ...payload,\n provider,\n storageInit,\n allowIdempotentReuse: true,\n }),\n ])\n .transform(({ serviceResult: [created] }) => created)\n .execute();\n\n if (\n result.reused &&\n storageInit.strategy === \"direct-multipart\" &&\n resolvedConfig.storage.abortMultipartUpload &&\n storageInit.storageUploadId\n ) {\n try {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: storageInit.storageKey,\n storageUploadId: storageInit.storageUploadId,\n });\n } catch {\n // Ignore abort failures for races.\n }\n }\n\n return json(result.result);\n } catch (err) {\n if (\n storageInit.strategy === \"direct-multipart\" &&\n resolvedConfig.storage.abortMultipartUpload &&\n storageInit.storageUploadId\n ) {\n try {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: storageInit.storageKey,\n storageUploadId: storageInit.storageUploadId,\n });\n } catch {\n // Ignore abort failures for races.\n }\n }\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/uploads/:uploadId\",\n outputSchema: uploadStatusSchema,\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n try {\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStatus(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const uploadHeaders = upload.uploadHeaders as Record<string, string> | null;\n\n return json({\n uploadId: upload.id.toString(),\n fileKey: upload.key,\n ...buildUploadSessionRouteData(resolvedConfig.storage, {\n uploadId: upload.id.toString(),\n provider: upload.provider,\n strategy: upload.strategy as UploadStrategy,\n uploadUrl: upload.uploadUrl ?? undefined,\n uploadHeaders: uploadHeaders ?? undefined,\n partSizeBytes: upload.partSizeBytes ?? undefined,\n }),\n status: upload.status as UploadStatus,\n strategy: upload.strategy as UploadStrategy,\n expectedSizeBytes: Number(upload.expectedSizeBytes),\n bytesUploaded: Number(upload.bytesUploaded),\n partsUploaded: upload.partsUploaded,\n partSizeBytes: upload.partSizeBytes,\n expiresAt: upload.expiresAt,\n createdAt: upload.createdAt,\n updatedAt: upload.updatedAt,\n completedAt: upload.completedAt,\n errorCode: upload.errorCode,\n errorMessage: upload.errorMessage,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/progress\",\n inputSchema: progressSchema,\n outputSchema: z.object({\n bytesUploaded: z.number(),\n partsUploaded: z.number(),\n }),\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n try {\n const result = await this.handlerTx()\n .withServiceCalls(() => [services.recordUploadProgress(pathParams.uploadId, payload)])\n .transform(({ serviceResult: [updated] }) => updated)\n .execute();\n\n return json({\n bytesUploaded: Number(result.bytesUploaded),\n partsUploaded: result.partsUploaded,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/parts\",\n inputSchema: partNumbersSchema,\n outputSchema: z.object({\n parts: z.array(\n z.object({\n partNumber: z.number(),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n }),\n ),\n }),\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n const resolvedConfig = getResolvedConfig();\n try {\n // Rule of Fragno exception: read -> storage I/O -> mutate.\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const providerMismatch = rejectProviderMismatch(\n upload,\n resolvedConfig.storage.name,\n error,\n );\n if (providerMismatch) {\n return providerMismatch;\n }\n\n if (upload.strategy !== \"direct-multipart\") {\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n if (!resolvedConfig.storage.getPartUploadUrls) {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n const parts = await resolvedConfig.storage.getPartUploadUrls({\n storageKey: upload.objectKey,\n storageUploadId: upload.storageUploadId ?? \"\",\n partNumbers: payload.partNumbers,\n partSizeBytes: upload.partSizeBytes ?? 0,\n });\n\n return json({ parts });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"GET\",\n path: \"/uploads/:uploadId/parts\",\n outputSchema: z.object({\n parts: z.array(\n z.object({\n partNumber: z.number(),\n etag: z.string(),\n sizeBytes: z.number(),\n createdAt: z.date(),\n }),\n ),\n }),\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n try {\n const parts = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadParts(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const typedParts = parts as {\n partNumber: number;\n etag: string;\n sizeBytes: bigint;\n createdAt: Date;\n }[];\n\n return json({\n parts: typedParts.map((part) => ({\n partNumber: part.partNumber,\n etag: part.etag,\n sizeBytes: Number(part.sizeBytes),\n createdAt: part.createdAt,\n })),\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/parts/complete\",\n inputSchema: completePartsSchema,\n outputSchema: z.object({\n bytesUploaded: z.number(),\n partsUploaded: z.number(),\n }),\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n try {\n const result = await this.handlerTx()\n .withServiceCalls(() => [services.recordUploadParts(pathParams.uploadId, payload)])\n .transform(({ serviceResult: [updated] }) => updated)\n .execute();\n\n return json({\n bytesUploaded: Number(result.bytesUploaded),\n partsUploaded: result.partsUploaded,\n });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/complete\",\n inputSchema: completeUploadSchema,\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function ({ pathParams, input }, { json, error }) {\n const payload = await input.valid();\n const resolvedConfig = getResolvedConfig();\n try {\n // Rule of Fragno exception: read -> storage I/O -> mutate.\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const providerMismatch = rejectProviderMismatch(\n upload,\n resolvedConfig.storage.name,\n error,\n );\n if (providerMismatch) {\n return providerMismatch;\n }\n\n const inactiveResponse = rejectInactiveUpload(\n {\n status: upload.status as UploadStatus,\n expiresAt: upload.expiresAt,\n },\n error,\n );\n if (inactiveResponse) {\n return inactiveResponse;\n }\n\n let finalizeResult: { sizeBytes?: bigint } | undefined;\n\n if (upload.strategy === \"direct-multipart\") {\n if (!resolvedConfig.storage.completeMultipartUpload) {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n if (!payload.parts || payload.parts.length === 0) {\n return error(\n {\n message: \"Upload invalid state\",\n code: \"UPLOAD_INVALID_STATE\",\n },\n 409,\n );\n }\n\n await resolvedConfig.storage.completeMultipartUpload({\n storageKey: upload.objectKey,\n storageUploadId: upload.storageUploadId ?? \"\",\n parts: payload.parts,\n });\n } else if (resolvedConfig.storage.finalizeUpload) {\n finalizeResult = await resolvedConfig.storage.finalizeUpload({\n storageKey: upload.objectKey,\n expectedSizeBytes: upload.expectedSizeBytes,\n checksum: upload.checksum as UploadChecksum | null,\n });\n }\n\n const completed = await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadCompleteFromSnapshot(\n upload,\n finalizeResult?.sizeBytes ? { sizeBytes: finalizeResult.sizeBytes } : undefined,\n ),\n ])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n return json(toFileMetadata(completed.file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"POST\",\n path: \"/uploads/:uploadId/abort\",\n outputSchema: z.object({ ok: z.literal(true) }),\n errorCodes,\n handler: async function ({ pathParams }, { json, error }) {\n const resolvedConfig = getResolvedConfig();\n try {\n // Rule of Fragno exception: read -> storage I/O -> mutate.\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const providerMismatch = rejectProviderMismatch(\n upload,\n resolvedConfig.storage.name,\n error,\n );\n if (providerMismatch) {\n return providerMismatch;\n }\n\n if (\n upload.strategy === \"direct-multipart\" &&\n resolvedConfig.storage.abortMultipartUpload\n ) {\n await resolvedConfig.storage.abortMultipartUpload({\n storageKey: upload.objectKey,\n storageUploadId: upload.storageUploadId ?? \"\",\n });\n }\n\n await this.handlerTx()\n .withServiceCalls(() => [services.markUploadAbortedFromSnapshot(upload)])\n .execute();\n\n return json({ ok: true });\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n\n defineRoute({\n method: \"PUT\",\n path: \"/uploads/:uploadId/content\",\n contentType: \"application/octet-stream\",\n outputSchema: fileMetadataSchema,\n errorCodes,\n handler: async function (context, { json, error }) {\n const { pathParams } = context;\n const resolvedConfig = getResolvedConfig();\n try {\n // Rule of Fragno exception: read -> storage I/O -> mutate.\n const upload = await this.handlerTx()\n .withServiceCalls(() => [services.getUploadStorageInfo(pathParams.uploadId)])\n .transform(({ serviceResult: [result] }) => result)\n .execute();\n\n const providerMismatch = rejectProviderMismatch(\n upload,\n resolvedConfig.storage.name,\n error,\n );\n if (providerMismatch) {\n return providerMismatch;\n }\n\n if (upload.strategy !== \"proxy\") {\n return error({ message: \"Upload invalid state\", code: \"UPLOAD_INVALID_STATE\" }, 409);\n }\n\n const inactiveResponse = rejectInactiveUpload(\n {\n status: upload.status as UploadStatus,\n expiresAt: upload.expiresAt,\n },\n error,\n );\n if (inactiveResponse) {\n return inactiveResponse;\n }\n\n if (!resolvedConfig.storage.writeStream) {\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n let result: Awaited<ReturnType<NonNullable<typeof resolvedConfig.storage.writeStream>>>;\n try {\n result = await resolvedConfig.storage.writeStream({\n storageKey: upload.objectKey,\n body: context.bodyStream(),\n contentType: upload.contentType,\n sizeBytes: upload.expectedSizeBytes,\n });\n } catch {\n await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadFailedFromSnapshot(\n upload,\n \"STORAGE_ERROR\",\n \"Storage upload failed\",\n ),\n ])\n .execute();\n return error({ message: \"Storage error\", code: \"STORAGE_ERROR\" }, 502);\n }\n\n const completed = await this.handlerTx()\n .withServiceCalls(() => [\n services.markUploadCompleteFromSnapshot(\n upload,\n result?.sizeBytes ? { sizeBytes: result.sizeBytes } : undefined,\n ),\n ])\n .transform(({ serviceResult: [done] }) => done)\n .execute();\n\n return json(toFileMetadata(completed.file));\n } catch (err) {\n return handleServiceError(err, error);\n }\n },\n }),\n ];\n },\n);\n"],"mappings":"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"}
|
|
@@ -1,8 +1,38 @@
|
|
|
1
|
-
import { schema } from "@fragno-dev/db/schema";
|
|
1
|
+
import { column, idColumn, referenceColumn, schema } from "@fragno-dev/db/schema";
|
|
2
2
|
|
|
3
3
|
//#region src/schema.ts
|
|
4
|
-
const uploadSchema = schema("upload", (s) =>
|
|
4
|
+
const uploadSchema = schema("upload", (s) => {
|
|
5
|
+
return s.addTable("file", (t) => {
|
|
6
|
+
return t.addColumn("id", idColumn()).addColumn("key", column("string")).addColumn("provider", column("string")).addColumn("uploaderId", column("string").nullable()).addColumn("filename", column("string")).addColumn("sizeBytes", column("bigint")).addColumn("contentType", column("string")).addColumn("checksum", column("json").nullable()).addColumn("visibility", column("string")).addColumn("tags", column("json").nullable()).addColumn("metadata", column("json").nullable()).addColumn("status", column("string")).addColumn("objectKey", column("string")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).addColumn("completedAt", column("timestamp").nullable()).addColumn("deletedAt", column("timestamp").nullable()).addColumn("errorCode", column("string").nullable()).addColumn("errorMessage", column("string").nullable()).createIndex("idx_file_provider_key", ["provider", "key"], { unique: true }).createIndex("idx_file_provider_key_status", [
|
|
7
|
+
"provider",
|
|
8
|
+
"key",
|
|
9
|
+
"status"
|
|
10
|
+
]).createIndex("idx_file_provider_key_uploaderId", [
|
|
11
|
+
"provider",
|
|
12
|
+
"key",
|
|
13
|
+
"uploaderId"
|
|
14
|
+
]).createIndex("idx_file_provider_key_status_uploaderId", [
|
|
15
|
+
"provider",
|
|
16
|
+
"key",
|
|
17
|
+
"status",
|
|
18
|
+
"uploaderId"
|
|
19
|
+
]).createIndex("idx_file_uploaderId", ["uploaderId"]).createIndex("idx_file_createdAt", ["createdAt"]).createIndex("idx_file_status_createdAt", ["status", "createdAt"]);
|
|
20
|
+
}).addTable("upload", (t) => {
|
|
21
|
+
return t.addColumn("id", idColumn()).addColumn("key", column("string")).addColumn("provider", column("string")).addColumn("uploaderId", column("string").nullable()).addColumn("filename", column("string")).addColumn("expectedSizeBytes", column("bigint")).addColumn("contentType", column("string")).addColumn("checksum", column("json").nullable()).addColumn("visibility", column("string")).addColumn("tags", column("json").nullable()).addColumn("metadata", column("json").nullable()).addColumn("status", column("string")).addColumn("strategy", column("string")).addColumn("objectKey", column("string")).addColumn("storageUploadId", column("string").nullable()).addColumn("uploadUrl", column("string").nullable()).addColumn("uploadHeaders", column("json").nullable()).addColumn("bytesUploaded", column("bigint").defaultTo(0n)).addColumn("partsUploaded", column("integer").defaultTo(0)).addColumn("partSizeBytes", column("integer").nullable()).addColumn("expiresAt", column("timestamp")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).addColumn("completedAt", column("timestamp").nullable()).addColumn("errorCode", column("string").nullable()).addColumn("errorMessage", column("string").nullable()).createIndex("idx_upload_provider_key", ["provider", "key"]).createIndex("idx_upload_status", ["status"]).createIndex("idx_upload_expiresAt", ["expiresAt"]);
|
|
22
|
+
}).addTable("upload_part", (t) => {
|
|
23
|
+
return t.addColumn("id", idColumn()).addColumn("uploadId", referenceColumn()).addColumn("partNumber", column("integer")).addColumn("etag", column("string")).addColumn("sizeBytes", column("bigint")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_upload_part_upload", ["uploadId"]).createIndex("idx_upload_part_number", ["uploadId", "partNumber"], { unique: true });
|
|
24
|
+
}).addReference("upload", {
|
|
25
|
+
type: "one",
|
|
26
|
+
from: {
|
|
27
|
+
table: "upload_part",
|
|
28
|
+
column: "uploadId"
|
|
29
|
+
},
|
|
30
|
+
to: {
|
|
31
|
+
table: "upload",
|
|
32
|
+
column: "id"
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
5
36
|
|
|
6
37
|
//#endregion
|
|
7
|
-
export { uploadSchema };
|
|
8
38
|
//# sourceMappingURL=schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":["schema","uploadSchema","s"],"sources":["../../../../../../src/schema.ts"],"sourcesContent":["import { column, idColumn, referenceColumn, schema } from \"@fragno-dev/db/schema\";\n\nexport const uploadSchema = schema(\"upload\", (s) => {\n return s\n .addTable(\"file\", (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"
|
|
1
|
+
{"version":3,"file":"schema.js","names":["column","idColumn","referenceColumn","schema","uploadSchema","s","addTable","t","addColumn","nullable","defaultTo","b","now","createIndex","unique","addReference","type","from","table","to"],"sources":["../../../../../../src/schema.ts"],"sourcesContent":["import { column, idColumn, referenceColumn, schema } from \"@fragno-dev/db/schema\";\n\nexport const uploadSchema = schema(\"upload\", (s) => {\n return s\n .addTable(\"file\", (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"key\", column(\"string\"))\n .addColumn(\"provider\", column(\"string\"))\n .addColumn(\"uploaderId\", column(\"string\").nullable())\n .addColumn(\"filename\", column(\"string\"))\n .addColumn(\"sizeBytes\", column(\"bigint\"))\n .addColumn(\"contentType\", column(\"string\"))\n .addColumn(\"checksum\", column(\"json\").nullable())\n .addColumn(\"visibility\", column(\"string\"))\n .addColumn(\"tags\", column(\"json\").nullable())\n .addColumn(\"metadata\", column(\"json\").nullable())\n .addColumn(\"status\", column(\"string\"))\n .addColumn(\"objectKey\", column(\"string\"))\n .addColumn(\n \"createdAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .addColumn(\n \"updatedAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .addColumn(\"completedAt\", column(\"timestamp\").nullable())\n .addColumn(\"deletedAt\", column(\"timestamp\").nullable())\n .addColumn(\"errorCode\", column(\"string\").nullable())\n .addColumn(\"errorMessage\", column(\"string\").nullable())\n .createIndex(\"idx_file_provider_key\", [\"provider\", \"key\"], {\n unique: true,\n })\n .createIndex(\"idx_file_provider_key_status\", [\"provider\", \"key\", \"status\"])\n .createIndex(\"idx_file_provider_key_uploaderId\", [\"provider\", \"key\", \"uploaderId\"])\n .createIndex(\"idx_file_provider_key_status_uploaderId\", [\n \"provider\",\n \"key\",\n \"status\",\n \"uploaderId\",\n ])\n .createIndex(\"idx_file_uploaderId\", [\"uploaderId\"])\n .createIndex(\"idx_file_createdAt\", [\"createdAt\"])\n .createIndex(\"idx_file_status_createdAt\", [\"status\", \"createdAt\"]);\n })\n .addTable(\"upload\", (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"key\", column(\"string\"))\n .addColumn(\"provider\", column(\"string\"))\n .addColumn(\"uploaderId\", column(\"string\").nullable())\n .addColumn(\"filename\", column(\"string\"))\n .addColumn(\"expectedSizeBytes\", column(\"bigint\"))\n .addColumn(\"contentType\", column(\"string\"))\n .addColumn(\"checksum\", column(\"json\").nullable())\n .addColumn(\"visibility\", column(\"string\"))\n .addColumn(\"tags\", column(\"json\").nullable())\n .addColumn(\"metadata\", column(\"json\").nullable())\n .addColumn(\"status\", column(\"string\"))\n .addColumn(\"strategy\", column(\"string\"))\n .addColumn(\"objectKey\", column(\"string\"))\n .addColumn(\"storageUploadId\", column(\"string\").nullable())\n .addColumn(\"uploadUrl\", column(\"string\").nullable())\n .addColumn(\"uploadHeaders\", column(\"json\").nullable())\n .addColumn(\"bytesUploaded\", column(\"bigint\").defaultTo(0n))\n .addColumn(\"partsUploaded\", column(\"integer\").defaultTo(0))\n .addColumn(\"partSizeBytes\", column(\"integer\").nullable())\n .addColumn(\"expiresAt\", column(\"timestamp\"))\n .addColumn(\n \"createdAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .addColumn(\n \"updatedAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .addColumn(\"completedAt\", column(\"timestamp\").nullable())\n .addColumn(\"errorCode\", column(\"string\").nullable())\n .addColumn(\"errorMessage\", column(\"string\").nullable())\n .createIndex(\"idx_upload_provider_key\", [\"provider\", \"key\"])\n .createIndex(\"idx_upload_status\", [\"status\"])\n .createIndex(\"idx_upload_expiresAt\", [\"expiresAt\"]);\n })\n .addTable(\"upload_part\", (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"uploadId\", referenceColumn())\n .addColumn(\"partNumber\", column(\"integer\"))\n .addColumn(\"etag\", column(\"string\"))\n .addColumn(\"sizeBytes\", column(\"bigint\"))\n .addColumn(\n \"createdAt\",\n column(\"timestamp\").defaultTo((b) => b.now()),\n )\n .createIndex(\"idx_upload_part_upload\", [\"uploadId\"])\n .createIndex(\"idx_upload_part_number\", [\"uploadId\", \"partNumber\"], {\n unique: true,\n });\n })\n .addReference(\"upload\", {\n type: \"one\",\n from: { table: \"upload_part\", column: \"uploadId\" },\n to: { table: \"upload\", column: \"id\" },\n });\n});\n"],"mappings":"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"}
|