@beignet/core 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -0
- package/README.md +821 -30
- package/dist/application/index.d.ts +28 -2
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +140 -12
- package/dist/application/index.js.map +1 -1
- package/dist/client/client.d.ts +2 -2
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +136 -48
- package/dist/client/client.js.map +1 -1
- package/dist/client/error-messages.d.ts +14 -0
- package/dist/client/error-messages.d.ts.map +1 -0
- package/dist/client/error-messages.js +23 -0
- package/dist/client/error-messages.js.map +1 -0
- package/dist/client/index.d.ts +8 -4
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +6 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +35 -5
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client-only.d.ts +8 -0
- package/dist/client-only.d.ts.map +1 -0
- package/dist/client-only.js +8 -0
- package/dist/client-only.js.map +1 -0
- package/dist/config/index.d.ts +5 -5
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/index.js.map +1 -1
- package/dist/contracts/catalog-errors.d.ts +27 -0
- package/dist/contracts/catalog-errors.d.ts.map +1 -0
- package/dist/contracts/catalog-errors.js +69 -0
- package/dist/contracts/catalog-errors.js.map +1 -0
- package/dist/contracts/contract-builder.d.ts +15 -12
- package/dist/contracts/contract-builder.d.ts.map +1 -1
- package/dist/contracts/contract-builder.js +15 -41
- package/dist/contracts/contract-builder.js.map +1 -1
- package/dist/contracts/contract-group.d.ts +11 -8
- package/dist/contracts/contract-group.d.ts.map +1 -1
- package/dist/contracts/contract-group.js +13 -40
- package/dist/contracts/contract-group.js.map +1 -1
- package/dist/contracts/contract-like.d.ts +1 -1
- package/dist/contracts/contract-like.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +13 -9
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +9 -5
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/openapi-meta.d.ts +48 -0
- package/dist/contracts/openapi-meta.d.ts.map +1 -1
- package/dist/contracts/openapi-meta.js +3 -0
- package/dist/contracts/openapi-meta.js.map +1 -1
- package/dist/contracts/path-template.d.ts +1 -1
- package/dist/contracts/path-template.js +2 -2
- package/dist/contracts/path-template.js.map +1 -1
- package/dist/contracts/schema-shape.d.ts +37 -0
- package/dist/contracts/schema-shape.d.ts.map +1 -0
- package/dist/contracts/schema-shape.js +61 -0
- package/dist/contracts/schema-shape.js.map +1 -0
- package/dist/contracts/success-status.d.ts +32 -0
- package/dist/contracts/success-status.d.ts.map +1 -0
- package/dist/contracts/success-status.js +18 -0
- package/dist/contracts/success-status.js.map +1 -0
- package/dist/contracts/types.d.ts +25 -5
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js.map +1 -1
- package/dist/contracts/utils.d.ts +1 -1
- package/dist/contracts/utils.d.ts.map +1 -1
- package/dist/contracts/utils.js +1 -1
- package/dist/contracts/utils.js.map +1 -1
- package/dist/domain/events.d.ts +1 -1
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js +1 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/domain/index.d.ts +3 -3
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +3 -3
- package/dist/domain/index.js.map +1 -1
- package/dist/errors/catalog.d.ts +9 -1
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +7 -1
- package/dist/errors/catalog.js.map +1 -1
- package/dist/errors/http.d.ts +10 -0
- package/dist/errors/http.d.ts.map +1 -1
- package/dist/errors/http.js +11 -1
- package/dist/errors/http.js.map +1 -1
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +4 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/response.d.ts +4 -1
- package/dist/errors/response.d.ts.map +1 -1
- package/dist/errors/response.js.map +1 -1
- package/dist/events/index.d.ts +10 -12
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +10 -10
- package/dist/events/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +5 -3
- package/dist/idempotency/index.d.ts.map +1 -1
- package/dist/idempotency/index.js.map +1 -1
- package/dist/jobs/index.d.ts +148 -16
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +174 -14
- package/dist/jobs/index.js.map +1 -1
- package/dist/notifications/index.d.ts +14 -16
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +14 -14
- package/dist/notifications/index.js.map +1 -1
- package/dist/openapi/index.d.ts +8 -3
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +41 -29
- package/dist/openapi/index.js.map +1 -1
- package/dist/openapi/schema-introspector.d.ts +37 -0
- package/dist/openapi/schema-introspector.d.ts.map +1 -1
- package/dist/openapi/schema-introspector.js +23 -17
- package/dist/openapi/schema-introspector.js.map +1 -1
- package/dist/outbox/index.d.ts +18 -4
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +104 -4
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/audit.d.ts +56 -10
- package/dist/ports/audit.d.ts.map +1 -1
- package/dist/ports/audit.js +71 -3
- package/dist/ports/audit.js.map +1 -1
- package/dist/ports/auth.d.ts +92 -0
- package/dist/ports/auth.d.ts.map +1 -1
- package/dist/ports/auth.js +92 -0
- package/dist/ports/auth.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/ports/events.d.ts.map +1 -1
- package/dist/ports/index.d.ts +62 -33
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +28 -34
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/policy.d.ts +32 -3
- package/dist/ports/policy.d.ts.map +1 -1
- package/dist/ports/policy.js +13 -2
- package/dist/ports/policy.js.map +1 -1
- package/dist/ports/testing.d.ts +1030 -2
- package/dist/ports/testing.d.ts.map +1 -1
- package/dist/ports/testing.js +1031 -1
- package/dist/ports/testing.js.map +1 -1
- package/dist/ports/unbound.d.ts +21 -0
- package/dist/ports/unbound.d.ts.map +1 -0
- package/dist/ports/unbound.js +57 -0
- package/dist/ports/unbound.js.map +1 -0
- package/dist/ports/unit-of-work.d.ts +1 -1
- package/dist/ports/unit-of-work.d.ts.map +1 -1
- package/dist/ports/unit-of-work.js +1 -1
- package/dist/ports/unit-of-work.js.map +1 -1
- package/dist/providers/index.d.ts +3 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -2
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +46 -5
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js +25 -6
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/providers/metadata.d.ts +39 -0
- package/dist/providers/metadata.d.ts.map +1 -0
- package/dist/providers/metadata.js +169 -0
- package/dist/providers/metadata.js.map +1 -0
- package/dist/providers/provider.d.ts +114 -9
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js +3 -20
- package/dist/providers/provider.js.map +1 -1
- package/dist/schedules/index.d.ts +94 -13
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +66 -12
- package/dist/schedules/index.js.map +1 -1
- package/dist/server/audit-context.d.ts +29 -0
- package/dist/server/audit-context.d.ts.map +1 -0
- package/dist/server/audit-context.js +44 -0
- package/dist/server/audit-context.js.map +1 -0
- package/dist/server/context.d.ts +141 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +39 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/contract-like.d.ts +1 -1
- package/dist/server/contract-like.d.ts.map +1 -1
- package/dist/server/contract-like.js +1 -1
- package/dist/server/contract-like.js.map +1 -1
- package/dist/server/health.d.ts +2 -2
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/hooks/auth.d.ts +89 -65
- package/dist/server/hooks/auth.d.ts.map +1 -1
- package/dist/server/hooks/auth.js +84 -55
- package/dist/server/hooks/auth.js.map +1 -1
- package/dist/server/hooks/cors.d.ts +1 -1
- package/dist/server/hooks/cors.d.ts.map +1 -1
- package/dist/server/hooks/errors.d.ts +2 -2
- package/dist/server/hooks/errors.d.ts.map +1 -1
- package/dist/server/hooks/errors.js +2 -2
- package/dist/server/hooks/errors.js.map +1 -1
- package/dist/server/hooks/idempotency.d.ts +78 -0
- package/dist/server/hooks/idempotency.d.ts.map +1 -0
- package/dist/server/hooks/idempotency.js +154 -0
- package/dist/server/hooks/idempotency.js.map +1 -0
- package/dist/server/hooks/index.d.ts +8 -7
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +6 -5
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/hooks/logging.d.ts +2 -2
- package/dist/server/hooks/logging.d.ts.map +1 -1
- package/dist/server/hooks/logging.js +1 -1
- package/dist/server/hooks/logging.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +25 -7
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +47 -12
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/hooks.d.ts +1 -1
- package/dist/server/hooks.d.ts.map +1 -1
- package/dist/server/hooks.js +1 -1
- package/dist/server/hooks.js.map +1 -1
- package/dist/server/http.d.ts +84 -6
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/index.d.ts +36 -12
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +24 -8
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts +108 -0
- package/dist/server/instrumentation.d.ts.map +1 -0
- package/dist/server/instrumentation.js +297 -0
- package/dist/server/instrumentation.js.map +1 -0
- package/dist/server/openapi.d.ts +3 -3
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/openapi.js +1 -1
- package/dist/server/openapi.js.map +1 -1
- package/dist/server/providers/index.d.ts +3 -3
- package/dist/server/providers/index.d.ts.map +1 -1
- package/dist/server/providers/index.js +3 -3
- package/dist/server/providers/index.js.map +1 -1
- package/dist/server/providers/loadProviderConfig.d.ts +2 -2
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +2 -2
- package/dist/server/providers/loadProviderConfig.js.map +1 -1
- package/dist/server/request-context.d.ts +67 -0
- package/dist/server/request-context.d.ts.map +1 -0
- package/dist/server/request-context.js +79 -0
- package/dist/server/request-context.js.map +1 -0
- package/dist/server/server-context.d.ts +38 -0
- package/dist/server/server-context.d.ts.map +1 -0
- package/dist/server/server-context.js +38 -0
- package/dist/server/server-context.js.map +1 -0
- package/dist/server/server.d.ts +148 -35
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +482 -145
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +2 -2
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +2 -2
- package/dist/server/types.js.map +1 -1
- package/dist/server/use-case-route.d.ts +263 -0
- package/dist/server/use-case-route.d.ts.map +1 -0
- package/dist/server/use-case-route.js +77 -0
- package/dist/server/use-case-route.js.map +1 -0
- package/dist/server-only.d.ts +8 -0
- package/dist/server-only.d.ts.map +1 -0
- package/dist/server-only.js +8 -0
- package/dist/server-only.js.map +1 -0
- package/dist/tasks/index.d.ts +139 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +98 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/testing/index.d.ts +611 -5
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +434 -4
- package/dist/testing/index.js.map +1 -1
- package/dist/tracing/index.d.ts +89 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +101 -0
- package/dist/tracing/index.js.map +1 -0
- package/dist/uploads/client.d.ts +278 -0
- package/dist/uploads/client.d.ts.map +1 -0
- package/dist/uploads/client.js +428 -0
- package/dist/uploads/client.js.map +1 -0
- package/dist/uploads/index.d.ts +361 -0
- package/dist/uploads/index.d.ts.map +1 -0
- package/dist/uploads/index.js +543 -0
- package/dist/uploads/index.js.map +1 -0
- package/package.json +34 -3
- package/src/application/index.ts +193 -10
- package/src/client/client.ts +148 -150
- package/src/client/error-messages.ts +35 -0
- package/src/client/index.ts +12 -4
- package/src/client/types.ts +44 -5
- package/src/client-only.ts +7 -0
- package/src/config/index.ts +6 -6
- package/src/contracts/catalog-errors.ts +115 -0
- package/src/contracts/contract-builder.ts +39 -76
- package/src/contracts/contract-group.ts +33 -68
- package/src/contracts/contract-like.ts +1 -1
- package/src/contracts/index.ts +24 -11
- package/src/contracts/openapi-meta.ts +55 -0
- package/src/contracts/path-template.ts +2 -2
- package/src/contracts/schema-shape.ts +75 -0
- package/src/contracts/success-status.ts +68 -0
- package/src/contracts/types.ts +32 -5
- package/src/contracts/utils.ts +5 -2
- package/src/domain/events.ts +6 -2
- package/src/domain/index.ts +3 -3
- package/src/errors/catalog.ts +9 -1
- package/src/errors/http.ts +11 -1
- package/src/errors/index.ts +4 -4
- package/src/errors/response.ts +4 -1
- package/src/events/index.ts +12 -26
- package/src/idempotency/index.ts +5 -3
- package/src/jobs/index.ts +340 -29
- package/src/notifications/index.ts +17 -27
- package/src/openapi/index.ts +73 -38
- package/src/openapi/schema-introspector.ts +68 -17
- package/src/outbox/index.ts +151 -6
- package/src/ports/audit.ts +120 -11
- package/src/ports/auth.ts +132 -0
- package/src/ports/events.ts +2 -2
- package/src/ports/index.ts +104 -35
- package/src/ports/policy.ts +50 -3
- package/src/ports/testing.ts +2220 -33
- package/src/ports/unbound.ts +64 -0
- package/src/ports/unit-of-work.ts +6 -2
- package/src/providers/index.ts +16 -3
- package/src/providers/instrumentation.ts +93 -8
- package/src/providers/metadata.ts +234 -0
- package/src/providers/provider.ts +168 -9
- package/src/schedules/index.ts +173 -23
- package/src/server/audit-context.ts +45 -0
- package/src/server/context.ts +224 -0
- package/src/server/contract-like.ts +1 -1
- package/src/server/health.ts +2 -2
- package/src/server/hooks/auth.ts +175 -158
- package/src/server/hooks/cors.ts +1 -1
- package/src/server/hooks/errors.ts +7 -4
- package/src/server/hooks/idempotency.ts +263 -0
- package/src/server/hooks/index.ts +15 -12
- package/src/server/hooks/logging.ts +3 -3
- package/src/server/hooks/rate-limit.ts +85 -17
- package/src/server/hooks.ts +1 -1
- package/src/server/http.ts +112 -6
- package/src/server/index.ts +63 -12
- package/src/server/instrumentation.ts +470 -0
- package/src/server/openapi.ts +4 -4
- package/src/server/providers/index.ts +6 -3
- package/src/server/providers/loadProviderConfig.ts +4 -4
- package/src/server/request-context.ts +116 -0
- package/src/server/server-context.ts +44 -0
- package/src/server/server.ts +1045 -229
- package/src/server/types.ts +2 -2
- package/src/server/use-case-route.ts +430 -0
- package/src/server-only.ts +7 -0
- package/src/tasks/index.ts +275 -0
- package/src/testing/index.ts +1153 -6
- package/src/tracing/index.ts +176 -0
- package/src/uploads/client.ts +861 -0
- package/src/uploads/index.ts +1071 -0
- package/dist/ports/mailer.d.ts +0 -6
- package/dist/ports/mailer.d.ts.map +0 -1
- package/dist/ports/mailer.js +0 -2
- package/dist/ports/mailer.js.map +0 -1
- package/dist/ports/schedules.d.ts +0 -9
- package/dist/ports/schedules.d.ts.map +0 -1
- package/dist/ports/schedules.js +0 -2
- package/dist/ports/schedules.js.map +0 -1
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CompleteUploadResult,
|
|
3
|
+
InferUploadMetadata,
|
|
4
|
+
InferUploadResult,
|
|
5
|
+
PreparedUploadFile,
|
|
6
|
+
PreparedUploadResultFile,
|
|
7
|
+
PrepareUploadResult,
|
|
8
|
+
UploadDef,
|
|
9
|
+
UploadFileConstraints,
|
|
10
|
+
UploadFileIntent,
|
|
11
|
+
UploadFromRegistry,
|
|
12
|
+
UploadManifestEntry,
|
|
13
|
+
UploadRegistry,
|
|
14
|
+
} from "./index.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Upload registry shape accepted by the typed browser client.
|
|
18
|
+
*
|
|
19
|
+
* Pass the type of a `defineUploads(...)` registry, or an upload definition
|
|
20
|
+
* array, to `createUploadClient<...>()`.
|
|
21
|
+
*/
|
|
22
|
+
export type UploadClientRegistry = UploadRegistry | readonly UploadDef[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Union of upload route names available in an upload registry.
|
|
26
|
+
*/
|
|
27
|
+
export type UploadClientName<Registry> =
|
|
28
|
+
UploadFromRegistry<Registry> extends UploadDef<infer Name>
|
|
29
|
+
? Name & string
|
|
30
|
+
: never;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Find one upload definition in a registry by its route name.
|
|
34
|
+
*/
|
|
35
|
+
export type UploadByName<Registry, Name extends string> =
|
|
36
|
+
UploadFromRegistry<Registry> extends infer Upload
|
|
37
|
+
? Upload extends UploadDef
|
|
38
|
+
? Upload extends { readonly name: Name }
|
|
39
|
+
? Upload
|
|
40
|
+
: never
|
|
41
|
+
: never
|
|
42
|
+
: never;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Transport strategy used by `upload(...)`.
|
|
46
|
+
*/
|
|
47
|
+
export type UploadClientStrategy = "auto" | "direct" | "server";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fetch-compatible function used for upload route and direct provider
|
|
51
|
+
* requests.
|
|
52
|
+
*/
|
|
53
|
+
export type UploadClientFetch = (
|
|
54
|
+
input: RequestInfo | URL,
|
|
55
|
+
init?: RequestInit,
|
|
56
|
+
) => Promise<Response>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Static or lazy headers sent to Beignet upload route requests.
|
|
60
|
+
*/
|
|
61
|
+
export type UploadClientHeaders =
|
|
62
|
+
| HeadersInit
|
|
63
|
+
| (() => HeadersInit | Promise<HeadersInit>);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Request options shared by Beignet upload route requests.
|
|
67
|
+
*/
|
|
68
|
+
export interface UploadClientRequestOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Browser credential policy for upload route requests.
|
|
71
|
+
*/
|
|
72
|
+
credentials?: RequestCredentials;
|
|
73
|
+
/**
|
|
74
|
+
* Browser request mode for upload route requests.
|
|
75
|
+
*/
|
|
76
|
+
mode?: RequestMode;
|
|
77
|
+
/**
|
|
78
|
+
* Browser cache policy for upload route requests.
|
|
79
|
+
*/
|
|
80
|
+
cache?: RequestCache;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Options for `createUploadClient(...)`.
|
|
85
|
+
*/
|
|
86
|
+
export interface CreateUploadClientOptions {
|
|
87
|
+
/**
|
|
88
|
+
* Base URL for the upload route.
|
|
89
|
+
*
|
|
90
|
+
* @default "/api/uploads"
|
|
91
|
+
*/
|
|
92
|
+
baseUrl?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Fetch implementation used for Beignet upload route requests.
|
|
95
|
+
*/
|
|
96
|
+
fetch?: UploadClientFetch;
|
|
97
|
+
/**
|
|
98
|
+
* Headers sent to Beignet upload route requests. These are not sent to
|
|
99
|
+
* provider-owned direct upload URLs.
|
|
100
|
+
*/
|
|
101
|
+
headers?: UploadClientHeaders;
|
|
102
|
+
/**
|
|
103
|
+
* Request options shared by Beignet upload route requests.
|
|
104
|
+
*/
|
|
105
|
+
request?: UploadClientRequestOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Optional client-safe upload metadata for UI helpers.
|
|
108
|
+
*/
|
|
109
|
+
manifest?: readonly UploadManifestEntry[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* File lifecycle event emitted by direct and server upload helpers.
|
|
114
|
+
*/
|
|
115
|
+
export interface UploadClientFileEvent {
|
|
116
|
+
/**
|
|
117
|
+
* Browser `File` object being uploaded.
|
|
118
|
+
*/
|
|
119
|
+
file: File;
|
|
120
|
+
/**
|
|
121
|
+
* File name supplied by the browser.
|
|
122
|
+
*/
|
|
123
|
+
fileName: string;
|
|
124
|
+
/**
|
|
125
|
+
* Zero-based index from the files array passed by the caller.
|
|
126
|
+
*/
|
|
127
|
+
index: number;
|
|
128
|
+
/**
|
|
129
|
+
* Prepared upload id when the file has gone through `prepare(...)`.
|
|
130
|
+
*/
|
|
131
|
+
uploadId?: string;
|
|
132
|
+
/**
|
|
133
|
+
* Storage key when the file has gone through `prepare(...)`.
|
|
134
|
+
*/
|
|
135
|
+
key?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Upload progress event emitted while a direct upload is in flight.
|
|
140
|
+
*/
|
|
141
|
+
export interface UploadClientProgressEvent extends UploadClientFileEvent {
|
|
142
|
+
/**
|
|
143
|
+
* Uploaded bytes.
|
|
144
|
+
*/
|
|
145
|
+
loaded: number;
|
|
146
|
+
/**
|
|
147
|
+
* Total bytes expected for the file.
|
|
148
|
+
*/
|
|
149
|
+
total: number;
|
|
150
|
+
/**
|
|
151
|
+
* Fraction from `0` to `1`.
|
|
152
|
+
*/
|
|
153
|
+
progress: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Per-call options shared by upload client route requests.
|
|
158
|
+
*/
|
|
159
|
+
export interface UploadClientRouteOptions {
|
|
160
|
+
/**
|
|
161
|
+
* Additional headers sent to Beignet upload route requests.
|
|
162
|
+
*/
|
|
163
|
+
headers?: UploadClientHeaders;
|
|
164
|
+
/**
|
|
165
|
+
* Additional request options sent to Beignet upload route requests.
|
|
166
|
+
*/
|
|
167
|
+
request?: UploadClientRequestOptions;
|
|
168
|
+
/**
|
|
169
|
+
* Abort signal used for route requests and direct uploads.
|
|
170
|
+
*/
|
|
171
|
+
signal?: AbortSignal;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Options for preparing an upload.
|
|
176
|
+
*/
|
|
177
|
+
export interface UploadClientPrepareOptions<Upload extends UploadDef>
|
|
178
|
+
extends UploadClientRouteOptions {
|
|
179
|
+
/**
|
|
180
|
+
* Metadata validated by the upload definition.
|
|
181
|
+
*/
|
|
182
|
+
metadata: InferUploadMetadata<Upload>;
|
|
183
|
+
/**
|
|
184
|
+
* Browser files to upload.
|
|
185
|
+
*/
|
|
186
|
+
files: readonly File[];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Options for direct, server, or automatic upload execution.
|
|
191
|
+
*/
|
|
192
|
+
export interface UploadClientUploadOptions<Upload extends UploadDef>
|
|
193
|
+
extends UploadClientPrepareOptions<Upload> {
|
|
194
|
+
/**
|
|
195
|
+
* Upload transport strategy. Auto probes for direct-upload instructions and
|
|
196
|
+
* falls back to server-handled multipart upload.
|
|
197
|
+
*
|
|
198
|
+
* @default "auto"
|
|
199
|
+
*/
|
|
200
|
+
strategy?: UploadClientStrategy;
|
|
201
|
+
/**
|
|
202
|
+
* Called when a file is about to be uploaded.
|
|
203
|
+
*/
|
|
204
|
+
onFileBegin?(event: UploadClientFileEvent): void;
|
|
205
|
+
/**
|
|
206
|
+
* Called with upload progress. Direct uploads use XHR when this callback is
|
|
207
|
+
* provided so browsers can report progress events.
|
|
208
|
+
*/
|
|
209
|
+
onProgress?(event: UploadClientProgressEvent): void;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Browser upload client typed by an upload registry.
|
|
214
|
+
*/
|
|
215
|
+
export interface UploadClient<Registry extends UploadClientRegistry> {
|
|
216
|
+
/**
|
|
217
|
+
* Validate metadata and file intent, authorize the upload, and receive
|
|
218
|
+
* storage keys plus direct upload instructions when a signer is configured.
|
|
219
|
+
*/
|
|
220
|
+
prepare<Name extends UploadClientName<Registry>>(
|
|
221
|
+
uploadName: Name,
|
|
222
|
+
options: UploadClientPrepareOptions<UploadByName<Registry, Name>>,
|
|
223
|
+
): Promise<PrepareUploadResult>;
|
|
224
|
+
/**
|
|
225
|
+
* Complete a prepared direct upload after objects have been written to
|
|
226
|
+
* storage.
|
|
227
|
+
*/
|
|
228
|
+
complete<Name extends UploadClientName<Registry>>(
|
|
229
|
+
uploadName: Name,
|
|
230
|
+
options: UploadClientRouteOptions & {
|
|
231
|
+
metadata: InferUploadMetadata<UploadByName<Registry, Name>>;
|
|
232
|
+
files: readonly PreparedUploadFile[];
|
|
233
|
+
},
|
|
234
|
+
): Promise<
|
|
235
|
+
CompleteUploadResult<InferUploadResult<UploadByName<Registry, Name>>>
|
|
236
|
+
>;
|
|
237
|
+
/**
|
|
238
|
+
* Upload files through the Beignet application server using multipart form
|
|
239
|
+
* data.
|
|
240
|
+
*/
|
|
241
|
+
server<Name extends UploadClientName<Registry>>(
|
|
242
|
+
uploadName: Name,
|
|
243
|
+
options: UploadClientUploadOptions<UploadByName<Registry, Name>>,
|
|
244
|
+
): Promise<
|
|
245
|
+
CompleteUploadResult<InferUploadResult<UploadByName<Registry, Name>>>
|
|
246
|
+
>;
|
|
247
|
+
/**
|
|
248
|
+
* Require a direct provider upload flow: prepare, PUT each file to its
|
|
249
|
+
* provider URL, then complete.
|
|
250
|
+
*/
|
|
251
|
+
direct<Name extends UploadClientName<Registry>>(
|
|
252
|
+
uploadName: Name,
|
|
253
|
+
options: UploadClientUploadOptions<UploadByName<Registry, Name>>,
|
|
254
|
+
): Promise<
|
|
255
|
+
CompleteUploadResult<InferUploadResult<UploadByName<Registry, Name>>>
|
|
256
|
+
>;
|
|
257
|
+
/**
|
|
258
|
+
* Upload using the selected strategy. The default `"auto"` strategy uses a
|
|
259
|
+
* direct provider flow when available and falls back to server multipart.
|
|
260
|
+
*/
|
|
261
|
+
upload<Name extends UploadClientName<Registry>>(
|
|
262
|
+
uploadName: Name,
|
|
263
|
+
options: UploadClientUploadOptions<UploadByName<Registry, Name>>,
|
|
264
|
+
): Promise<
|
|
265
|
+
CompleteUploadResult<InferUploadResult<UploadByName<Registry, Name>>>
|
|
266
|
+
>;
|
|
267
|
+
/**
|
|
268
|
+
* Return manifest-backed file constraints for UI controls.
|
|
269
|
+
*/
|
|
270
|
+
constraints<Name extends UploadClientName<Registry>>(
|
|
271
|
+
uploadName: Name,
|
|
272
|
+
): UploadFileConstraints | undefined;
|
|
273
|
+
/**
|
|
274
|
+
* Return a comma-delimited file input `accept` value from manifest content
|
|
275
|
+
* types.
|
|
276
|
+
*/
|
|
277
|
+
accept<Name extends UploadClientName<Registry>>(
|
|
278
|
+
uploadName: Name,
|
|
279
|
+
): string | undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Constructor options for `UploadClientError`.
|
|
284
|
+
*/
|
|
285
|
+
export interface UploadClientErrorOptions {
|
|
286
|
+
/**
|
|
287
|
+
* Client operation that failed.
|
|
288
|
+
*/
|
|
289
|
+
operation: string;
|
|
290
|
+
/**
|
|
291
|
+
* Upload route name involved in the failure.
|
|
292
|
+
*/
|
|
293
|
+
uploadName: string;
|
|
294
|
+
/**
|
|
295
|
+
* Human-readable error message.
|
|
296
|
+
*/
|
|
297
|
+
message: string;
|
|
298
|
+
/**
|
|
299
|
+
* HTTP status when a route or provider response was received.
|
|
300
|
+
*/
|
|
301
|
+
status?: number;
|
|
302
|
+
/**
|
|
303
|
+
* Machine-readable error code.
|
|
304
|
+
*/
|
|
305
|
+
code?: string;
|
|
306
|
+
/**
|
|
307
|
+
* Structured error details from the upload route, when available.
|
|
308
|
+
*/
|
|
309
|
+
details?: unknown;
|
|
310
|
+
/**
|
|
311
|
+
* Original error that caused the client failure.
|
|
312
|
+
*/
|
|
313
|
+
cause?: unknown;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Error thrown by upload client route requests or direct provider uploads.
|
|
318
|
+
*/
|
|
319
|
+
export class UploadClientError extends Error {
|
|
320
|
+
/**
|
|
321
|
+
* Client operation that failed.
|
|
322
|
+
*/
|
|
323
|
+
readonly operation: string;
|
|
324
|
+
/**
|
|
325
|
+
* Upload route name involved in the failure.
|
|
326
|
+
*/
|
|
327
|
+
readonly uploadName: string;
|
|
328
|
+
/**
|
|
329
|
+
* HTTP status when a route or provider response was received.
|
|
330
|
+
*/
|
|
331
|
+
readonly status?: number;
|
|
332
|
+
/**
|
|
333
|
+
* Machine-readable error code.
|
|
334
|
+
*/
|
|
335
|
+
readonly code?: string;
|
|
336
|
+
/**
|
|
337
|
+
* Structured error details from the upload route, when available.
|
|
338
|
+
*/
|
|
339
|
+
readonly details?: unknown;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Create an upload client error.
|
|
343
|
+
*/
|
|
344
|
+
constructor(options: UploadClientErrorOptions) {
|
|
345
|
+
super(options.message, { cause: options.cause });
|
|
346
|
+
this.name = "UploadClientError";
|
|
347
|
+
this.operation = options.operation;
|
|
348
|
+
this.uploadName = options.uploadName;
|
|
349
|
+
this.status = options.status;
|
|
350
|
+
this.code = options.code;
|
|
351
|
+
this.details = options.details;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Create a typed browser upload client for a Beignet upload route.
|
|
357
|
+
*/
|
|
358
|
+
export function createUploadClient<
|
|
359
|
+
Registry extends UploadClientRegistry = UploadRegistry,
|
|
360
|
+
>(options: CreateUploadClientOptions = {}): UploadClient<Registry> {
|
|
361
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl ?? "/api/uploads");
|
|
362
|
+
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
363
|
+
if (!fetchImpl) {
|
|
364
|
+
throw new Error("createUploadClient requires a fetch implementation.");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function routeHeaders(
|
|
368
|
+
routeOptions?: UploadClientRouteOptions,
|
|
369
|
+
contentType?: string,
|
|
370
|
+
): Promise<Headers> {
|
|
371
|
+
const headers = new Headers();
|
|
372
|
+
if (contentType) headers.set("content-type", contentType);
|
|
373
|
+
|
|
374
|
+
const shared = await resolveHeaders(options.headers);
|
|
375
|
+
const local = await resolveHeaders(routeOptions?.headers);
|
|
376
|
+
mergeHeaders(headers, shared);
|
|
377
|
+
mergeHeaders(headers, local);
|
|
378
|
+
return headers;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function routeRequest(
|
|
382
|
+
routeOptions?: UploadClientRouteOptions,
|
|
383
|
+
): UploadClientRequestOptions {
|
|
384
|
+
return {
|
|
385
|
+
...options.request,
|
|
386
|
+
...routeOptions?.request,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function prepare<Name extends UploadClientName<Registry>>(
|
|
391
|
+
uploadName: Name,
|
|
392
|
+
prepareOptions: UploadClientPrepareOptions<UploadByName<Registry, Name>>,
|
|
393
|
+
) {
|
|
394
|
+
return requestJson<PrepareUploadResult>({
|
|
395
|
+
fetchImpl,
|
|
396
|
+
url: actionUrl(baseUrl, uploadName, "prepare"),
|
|
397
|
+
operation: "prepare upload",
|
|
398
|
+
uploadName,
|
|
399
|
+
init: {
|
|
400
|
+
...routeRequest(prepareOptions),
|
|
401
|
+
method: "POST",
|
|
402
|
+
headers: await routeHeaders(prepareOptions, "application/json"),
|
|
403
|
+
signal: prepareOptions.signal,
|
|
404
|
+
body: JSON.stringify({
|
|
405
|
+
metadata: prepareOptions.metadata,
|
|
406
|
+
files: prepareOptions.files.map(fileIntentFromFile),
|
|
407
|
+
}),
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function complete<Name extends UploadClientName<Registry>>(
|
|
413
|
+
uploadName: Name,
|
|
414
|
+
completeOptions: UploadClientRouteOptions & {
|
|
415
|
+
metadata: InferUploadMetadata<UploadByName<Registry, Name>>;
|
|
416
|
+
files: readonly PreparedUploadFile[];
|
|
417
|
+
},
|
|
418
|
+
) {
|
|
419
|
+
return requestJson<
|
|
420
|
+
CompleteUploadResult<InferUploadResult<UploadByName<Registry, Name>>>
|
|
421
|
+
>({
|
|
422
|
+
fetchImpl,
|
|
423
|
+
url: actionUrl(baseUrl, uploadName, "complete"),
|
|
424
|
+
operation: "complete upload",
|
|
425
|
+
uploadName,
|
|
426
|
+
init: {
|
|
427
|
+
...routeRequest(completeOptions),
|
|
428
|
+
method: "POST",
|
|
429
|
+
headers: await routeHeaders(completeOptions, "application/json"),
|
|
430
|
+
signal: completeOptions.signal,
|
|
431
|
+
body: JSON.stringify({
|
|
432
|
+
metadata: completeOptions.metadata,
|
|
433
|
+
files: completeOptions.files.map(completeFileInput),
|
|
434
|
+
}),
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function server<Name extends UploadClientName<Registry>>(
|
|
440
|
+
uploadName: Name,
|
|
441
|
+
uploadOptions: UploadClientUploadOptions<UploadByName<Registry, Name>>,
|
|
442
|
+
) {
|
|
443
|
+
const formData = new FormData();
|
|
444
|
+
formData.set("metadata", JSON.stringify(uploadOptions.metadata));
|
|
445
|
+
uploadOptions.files.forEach((file) => {
|
|
446
|
+
formData.append("file", file);
|
|
447
|
+
});
|
|
448
|
+
uploadOptions.files.forEach((file, index) => {
|
|
449
|
+
uploadOptions.onFileBegin?.({
|
|
450
|
+
file,
|
|
451
|
+
fileName: file.name,
|
|
452
|
+
index,
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const result = await requestJson<
|
|
457
|
+
CompleteUploadResult<InferUploadResult<UploadByName<Registry, Name>>>
|
|
458
|
+
>({
|
|
459
|
+
fetchImpl,
|
|
460
|
+
url: actionUrl(baseUrl, uploadName, "upload"),
|
|
461
|
+
operation: "server upload",
|
|
462
|
+
uploadName,
|
|
463
|
+
init: {
|
|
464
|
+
...routeRequest(uploadOptions),
|
|
465
|
+
method: "POST",
|
|
466
|
+
headers: await routeHeaders(uploadOptions),
|
|
467
|
+
signal: uploadOptions.signal,
|
|
468
|
+
body: formData,
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
uploadOptions.files.forEach((file, index) => {
|
|
473
|
+
uploadOptions.onProgress?.({
|
|
474
|
+
file,
|
|
475
|
+
fileName: file.name,
|
|
476
|
+
index,
|
|
477
|
+
loaded: file.size,
|
|
478
|
+
total: file.size,
|
|
479
|
+
progress: 1,
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function direct<Name extends UploadClientName<Registry>>(
|
|
486
|
+
uploadName: Name,
|
|
487
|
+
uploadOptions: UploadClientUploadOptions<UploadByName<Registry, Name>>,
|
|
488
|
+
) {
|
|
489
|
+
const prepared = await prepare(uploadName, uploadOptions);
|
|
490
|
+
return directFromPrepared(uploadName, uploadOptions, prepared);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function directFromPrepared<Name extends UploadClientName<Registry>>(
|
|
494
|
+
uploadName: Name,
|
|
495
|
+
uploadOptions: UploadClientUploadOptions<UploadByName<Registry, Name>>,
|
|
496
|
+
prepared: PrepareUploadResult,
|
|
497
|
+
) {
|
|
498
|
+
if (prepared.mode !== "direct") {
|
|
499
|
+
throw new UploadClientError({
|
|
500
|
+
operation: "direct upload",
|
|
501
|
+
uploadName,
|
|
502
|
+
code: "DIRECT_UPLOAD_UNAVAILABLE",
|
|
503
|
+
message: `Upload "${uploadName}" did not return direct upload instructions.`,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
await Promise.all(
|
|
508
|
+
prepared.files.map((preparedFile, index) =>
|
|
509
|
+
uploadDirectFile({
|
|
510
|
+
fetchImpl,
|
|
511
|
+
uploadName,
|
|
512
|
+
preparedFile,
|
|
513
|
+
file: uploadOptions.files[index],
|
|
514
|
+
index,
|
|
515
|
+
signal: uploadOptions.signal,
|
|
516
|
+
onFileBegin: uploadOptions.onFileBegin,
|
|
517
|
+
onProgress: uploadOptions.onProgress,
|
|
518
|
+
}),
|
|
519
|
+
),
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
return complete(uploadName, {
|
|
523
|
+
metadata: uploadOptions.metadata,
|
|
524
|
+
files: prepared.files,
|
|
525
|
+
headers: uploadOptions.headers,
|
|
526
|
+
request: uploadOptions.request,
|
|
527
|
+
signal: uploadOptions.signal,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function upload<Name extends UploadClientName<Registry>>(
|
|
532
|
+
uploadName: Name,
|
|
533
|
+
uploadOptions: UploadClientUploadOptions<UploadByName<Registry, Name>>,
|
|
534
|
+
) {
|
|
535
|
+
const strategy = uploadOptions.strategy ?? "auto";
|
|
536
|
+
if (strategy === "server") return server(uploadName, uploadOptions);
|
|
537
|
+
if (strategy === "direct") return direct(uploadName, uploadOptions);
|
|
538
|
+
|
|
539
|
+
const prepared = await prepare(uploadName, uploadOptions);
|
|
540
|
+
if (prepared.mode === "direct") {
|
|
541
|
+
return directFromPrepared(uploadName, uploadOptions, prepared);
|
|
542
|
+
}
|
|
543
|
+
return server(uploadName, uploadOptions);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function constraints<Name extends UploadClientName<Registry>>(
|
|
547
|
+
uploadName: Name,
|
|
548
|
+
) {
|
|
549
|
+
return options.manifest?.find((entry) => entry.name === uploadName)?.file;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function accept<Name extends UploadClientName<Registry>>(uploadName: Name) {
|
|
553
|
+
return constraints(uploadName)?.contentTypes?.join(",");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
prepare,
|
|
558
|
+
complete,
|
|
559
|
+
server,
|
|
560
|
+
direct,
|
|
561
|
+
upload,
|
|
562
|
+
constraints,
|
|
563
|
+
accept,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function fileIntentFromFile(file: File): UploadFileIntent {
|
|
568
|
+
return {
|
|
569
|
+
name: file.name,
|
|
570
|
+
contentType: file.type || "application/octet-stream",
|
|
571
|
+
size: file.size,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function completeFileInput(file: PreparedUploadResultFile): PreparedUploadFile {
|
|
576
|
+
return {
|
|
577
|
+
name: file.name,
|
|
578
|
+
contentType: file.contentType,
|
|
579
|
+
size: file.size,
|
|
580
|
+
uploadId: file.uploadId,
|
|
581
|
+
key: file.key,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function normalizeBaseUrl(baseUrl: string): string {
|
|
586
|
+
return baseUrl.replace(/\/+$/, "");
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function actionUrl(
|
|
590
|
+
baseUrl: string,
|
|
591
|
+
uploadName: string,
|
|
592
|
+
action: string,
|
|
593
|
+
): string {
|
|
594
|
+
return `${baseUrl}/${encodeURIComponent(uploadName)}/${action}`;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async function resolveHeaders(
|
|
598
|
+
headers: UploadClientHeaders | undefined,
|
|
599
|
+
): Promise<HeadersInit | undefined> {
|
|
600
|
+
return typeof headers === "function" ? headers() : headers;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function mergeHeaders(target: Headers, source: HeadersInit | undefined): void {
|
|
604
|
+
if (!source) return;
|
|
605
|
+
new Headers(source).forEach((value, key) => {
|
|
606
|
+
target.set(key, value);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async function requestJson<Result>(args: {
|
|
611
|
+
fetchImpl: UploadClientFetch;
|
|
612
|
+
url: string;
|
|
613
|
+
operation: string;
|
|
614
|
+
uploadName: string;
|
|
615
|
+
init: RequestInit;
|
|
616
|
+
}): Promise<Result> {
|
|
617
|
+
let response: Response;
|
|
618
|
+
try {
|
|
619
|
+
response = await args.fetchImpl(args.url, args.init);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
throw new UploadClientError({
|
|
622
|
+
operation: args.operation,
|
|
623
|
+
uploadName: args.uploadName,
|
|
624
|
+
code: "UPLOAD_REQUEST_FAILED",
|
|
625
|
+
message: `Failed to ${args.operation} "${args.uploadName}".`,
|
|
626
|
+
cause: error,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const body = await parseJsonBody(response, args);
|
|
631
|
+
if (!response.ok) {
|
|
632
|
+
const envelope = uploadErrorEnvelope(body);
|
|
633
|
+
throw new UploadClientError({
|
|
634
|
+
operation: args.operation,
|
|
635
|
+
uploadName: args.uploadName,
|
|
636
|
+
status: response.status,
|
|
637
|
+
code: envelope?.code ?? "UPLOAD_REQUEST_FAILED",
|
|
638
|
+
message:
|
|
639
|
+
envelope?.message ??
|
|
640
|
+
`Failed to ${args.operation} "${args.uploadName}" (${response.status}).`,
|
|
641
|
+
details: envelope?.details,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return body as Result;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function parseJsonBody(
|
|
649
|
+
response: Response,
|
|
650
|
+
args: { operation: string; uploadName: string },
|
|
651
|
+
): Promise<unknown> {
|
|
652
|
+
const text = await response.text();
|
|
653
|
+
if (!text) return undefined;
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
return JSON.parse(text);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
throw new UploadClientError({
|
|
659
|
+
operation: args.operation,
|
|
660
|
+
uploadName: args.uploadName,
|
|
661
|
+
status: response.status,
|
|
662
|
+
code: "INVALID_UPLOAD_RESPONSE",
|
|
663
|
+
message: `Failed to parse upload response for "${args.uploadName}".`,
|
|
664
|
+
cause: error,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function uploadErrorEnvelope(
|
|
670
|
+
body: unknown,
|
|
671
|
+
): { code?: string; message?: string; details?: unknown } | undefined {
|
|
672
|
+
if (typeof body !== "object" || body === null || !("error" in body)) {
|
|
673
|
+
return undefined;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const error = body.error;
|
|
677
|
+
if (typeof error !== "object" || error === null) return undefined;
|
|
678
|
+
|
|
679
|
+
const errorRecord = error as Record<string, unknown>;
|
|
680
|
+
|
|
681
|
+
return {
|
|
682
|
+
code: typeof errorRecord.code === "string" ? errorRecord.code : undefined,
|
|
683
|
+
message:
|
|
684
|
+
typeof errorRecord.message === "string" ? errorRecord.message : undefined,
|
|
685
|
+
...("details" in errorRecord ? { details: errorRecord.details } : {}),
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async function uploadDirectFile(args: {
|
|
690
|
+
fetchImpl: UploadClientFetch;
|
|
691
|
+
uploadName: string;
|
|
692
|
+
preparedFile: PreparedUploadResultFile;
|
|
693
|
+
file: File | undefined;
|
|
694
|
+
index: number;
|
|
695
|
+
signal?: AbortSignal;
|
|
696
|
+
onFileBegin?(event: UploadClientFileEvent): void;
|
|
697
|
+
onProgress?(event: UploadClientProgressEvent): void;
|
|
698
|
+
}): Promise<void> {
|
|
699
|
+
const file = args.file;
|
|
700
|
+
if (!file) {
|
|
701
|
+
throw new UploadClientError({
|
|
702
|
+
operation: "direct upload",
|
|
703
|
+
uploadName: args.uploadName,
|
|
704
|
+
code: "INVALID_UPLOAD_FILE",
|
|
705
|
+
message: `Missing browser file for prepared upload "${args.preparedFile.key}".`,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (!args.preparedFile.direct) {
|
|
710
|
+
throw new UploadClientError({
|
|
711
|
+
operation: "direct upload",
|
|
712
|
+
uploadName: args.uploadName,
|
|
713
|
+
code: "DIRECT_UPLOAD_UNAVAILABLE",
|
|
714
|
+
message: `Upload "${args.uploadName}" did not include direct instructions for "${args.preparedFile.name}".`,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
args.onFileBegin?.({
|
|
719
|
+
file,
|
|
720
|
+
fileName: file.name,
|
|
721
|
+
index: args.index,
|
|
722
|
+
uploadId: args.preparedFile.uploadId,
|
|
723
|
+
key: args.preparedFile.key,
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
if (args.onProgress && typeof XMLHttpRequest !== "undefined") {
|
|
727
|
+
await uploadWithXhr({
|
|
728
|
+
uploadName: args.uploadName,
|
|
729
|
+
preparedFile: args.preparedFile,
|
|
730
|
+
file,
|
|
731
|
+
index: args.index,
|
|
732
|
+
signal: args.signal,
|
|
733
|
+
onProgress: args.onProgress,
|
|
734
|
+
});
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const response = await args.fetchImpl(args.preparedFile.direct.url, {
|
|
739
|
+
method: args.preparedFile.direct.method,
|
|
740
|
+
headers: args.preparedFile.direct.headers,
|
|
741
|
+
signal: args.signal,
|
|
742
|
+
body: file,
|
|
743
|
+
});
|
|
744
|
+
if (!response.ok) {
|
|
745
|
+
throw new UploadClientError({
|
|
746
|
+
operation: "direct upload",
|
|
747
|
+
uploadName: args.uploadName,
|
|
748
|
+
status: response.status,
|
|
749
|
+
code: "DIRECT_UPLOAD_FAILED",
|
|
750
|
+
message: `Direct upload failed for "${args.preparedFile.name}" (${response.status}).`,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
args.onProgress?.({
|
|
755
|
+
file,
|
|
756
|
+
fileName: file.name,
|
|
757
|
+
index: args.index,
|
|
758
|
+
uploadId: args.preparedFile.uploadId,
|
|
759
|
+
key: args.preparedFile.key,
|
|
760
|
+
loaded: file.size,
|
|
761
|
+
total: file.size,
|
|
762
|
+
progress: 1,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function uploadWithXhr(args: {
|
|
767
|
+
uploadName: string;
|
|
768
|
+
preparedFile: PreparedUploadResultFile;
|
|
769
|
+
file: File;
|
|
770
|
+
index: number;
|
|
771
|
+
signal?: AbortSignal;
|
|
772
|
+
onProgress?(event: UploadClientProgressEvent): void;
|
|
773
|
+
}): Promise<void> {
|
|
774
|
+
return new Promise((resolve, reject) => {
|
|
775
|
+
const xhr = new XMLHttpRequest();
|
|
776
|
+
const direct = args.preparedFile.direct;
|
|
777
|
+
if (!direct) {
|
|
778
|
+
reject(
|
|
779
|
+
new UploadClientError({
|
|
780
|
+
operation: "direct upload",
|
|
781
|
+
uploadName: args.uploadName,
|
|
782
|
+
code: "DIRECT_UPLOAD_UNAVAILABLE",
|
|
783
|
+
message: `Upload "${args.uploadName}" did not include direct instructions for "${args.preparedFile.name}".`,
|
|
784
|
+
}),
|
|
785
|
+
);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const abort = () => xhr.abort();
|
|
790
|
+
args.signal?.addEventListener("abort", abort, { once: true });
|
|
791
|
+
|
|
792
|
+
xhr.upload.onprogress = (event) => {
|
|
793
|
+
const total = event.lengthComputable ? event.total : args.file.size;
|
|
794
|
+
args.onProgress?.({
|
|
795
|
+
file: args.file,
|
|
796
|
+
fileName: args.file.name,
|
|
797
|
+
index: args.index,
|
|
798
|
+
uploadId: args.preparedFile.uploadId,
|
|
799
|
+
key: args.preparedFile.key,
|
|
800
|
+
loaded: event.loaded,
|
|
801
|
+
total,
|
|
802
|
+
progress: total > 0 ? event.loaded / total : 0,
|
|
803
|
+
});
|
|
804
|
+
};
|
|
805
|
+
xhr.onload = () => {
|
|
806
|
+
args.signal?.removeEventListener("abort", abort);
|
|
807
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
808
|
+
args.onProgress?.({
|
|
809
|
+
file: args.file,
|
|
810
|
+
fileName: args.file.name,
|
|
811
|
+
index: args.index,
|
|
812
|
+
uploadId: args.preparedFile.uploadId,
|
|
813
|
+
key: args.preparedFile.key,
|
|
814
|
+
loaded: args.file.size,
|
|
815
|
+
total: args.file.size,
|
|
816
|
+
progress: 1,
|
|
817
|
+
});
|
|
818
|
+
resolve();
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
reject(
|
|
823
|
+
new UploadClientError({
|
|
824
|
+
operation: "direct upload",
|
|
825
|
+
uploadName: args.uploadName,
|
|
826
|
+
status: xhr.status,
|
|
827
|
+
code: "DIRECT_UPLOAD_FAILED",
|
|
828
|
+
message: `Direct upload failed for "${args.preparedFile.name}" (${xhr.status}).`,
|
|
829
|
+
}),
|
|
830
|
+
);
|
|
831
|
+
};
|
|
832
|
+
xhr.onerror = () => {
|
|
833
|
+
args.signal?.removeEventListener("abort", abort);
|
|
834
|
+
reject(
|
|
835
|
+
new UploadClientError({
|
|
836
|
+
operation: "direct upload",
|
|
837
|
+
uploadName: args.uploadName,
|
|
838
|
+
code: "DIRECT_UPLOAD_FAILED",
|
|
839
|
+
message: `Direct upload failed for "${args.preparedFile.name}".`,
|
|
840
|
+
}),
|
|
841
|
+
);
|
|
842
|
+
};
|
|
843
|
+
xhr.onabort = () => {
|
|
844
|
+
args.signal?.removeEventListener("abort", abort);
|
|
845
|
+
reject(
|
|
846
|
+
new UploadClientError({
|
|
847
|
+
operation: "direct upload",
|
|
848
|
+
uploadName: args.uploadName,
|
|
849
|
+
code: "DIRECT_UPLOAD_ABORTED",
|
|
850
|
+
message: `Direct upload was aborted for "${args.preparedFile.name}".`,
|
|
851
|
+
}),
|
|
852
|
+
);
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
xhr.open(direct.method, direct.url);
|
|
856
|
+
for (const [key, value] of Object.entries(direct.headers ?? {})) {
|
|
857
|
+
xhr.setRequestHeader(key, value);
|
|
858
|
+
}
|
|
859
|
+
xhr.send(args.file);
|
|
860
|
+
});
|
|
861
|
+
}
|