@gentleduck/upload 0.2.5 → 0.3.0

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.
Files changed (129) hide show
  1. package/README.md +297 -0
  2. package/dist/core/index.d.ts +2 -3
  3. package/dist/core/index.js +1 -1
  4. package/dist/core-BVgloTiP.js +10 -0
  5. package/dist/index-DCqyBVcx.d.ts +1441 -0
  6. package/dist/index-DsZHPmJm.d.ts +225 -0
  7. package/dist/index-KROXtLQh.d.ts +281 -0
  8. package/dist/index.d.ts +4 -5
  9. package/dist/index.js +1 -1
  10. package/dist/react/index.d.ts +2 -90
  11. package/dist/react/index.js +1 -1
  12. package/dist/react-euPrDv24.js +1 -0
  13. package/dist/strategies/index.d.ts +2 -56
  14. package/dist/strategies/index.js +1 -1
  15. package/dist/strategies-CUF0s68j.js +1 -0
  16. package/package.json +3 -2
  17. package/src/__tests__/emitter.test.ts +85 -0
  18. package/src/__tests__/fingerprint.test.ts +49 -0
  19. package/src/__tests__/guards.test.ts +40 -0
  20. package/src/__tests__/id.test.ts +67 -0
  21. package/src/__tests__/mime-sniff.test.ts +142 -0
  22. package/src/__tests__/persistence.test.ts +191 -0
  23. package/src/__tests__/sanitize-filename.test.ts +109 -0
  24. package/src/__tests__/sleep.test.ts +34 -0
  25. package/src/__tests__/static-analysis.test.ts +145 -0
  26. package/src/__tests__/store-libs.test.ts +192 -0
  27. package/src/__tests__/transport-fetch.test.ts +108 -0
  28. package/src/__tests__/transport-libs.test.ts +42 -0
  29. package/src/__tests__/transport.test.ts +182 -0
  30. package/src/core/__tests__/errors.test.ts +152 -0
  31. package/src/core/client.ts +3 -0
  32. package/src/core/contracts/contracts.types.ts +373 -0
  33. package/src/core/contracts/index.ts +3 -8
  34. package/src/core/contracts/transport/index.ts +3 -2
  35. package/src/core/contracts/transport/transport.fetch.ts +203 -0
  36. package/src/core/contracts/transport/transport.libs.ts +13 -39
  37. package/src/core/contracts/transport/transport.ts +96 -90
  38. package/src/core/contracts/transport/transport.types.ts +71 -35
  39. package/src/core/engine/__tests__/config-nan-bypass.test.ts +40 -0
  40. package/src/core/engine/engine.libs.ts +35 -0
  41. package/src/core/engine/engine.types.ts +490 -0
  42. package/src/core/engine/index.ts +3 -8
  43. package/src/core/engine/reducer.ts +93 -53
  44. package/src/core/engine/store/handlers/add-file.ts +43 -48
  45. package/src/core/engine/store/handlers/cancel.ts +13 -8
  46. package/src/core/engine/store/handlers/clean-up.ts +11 -8
  47. package/src/core/engine/store/handlers/create-intent.ts +82 -27
  48. package/src/core/engine/store/handlers/finalize.ts +30 -13
  49. package/src/core/engine/store/handlers/index.ts +7 -7
  50. package/src/core/engine/store/handlers/pause.ts +8 -6
  51. package/src/core/engine/store/handlers/run-upload.ts +54 -39
  52. package/src/core/engine/store/index.ts +27 -21
  53. package/src/core/engine/store/store.dispatch.ts +10 -8
  54. package/src/core/engine/store/store.libs.ts +197 -94
  55. package/src/core/engine/store/store.runtime.ts +52 -53
  56. package/src/core/engine/store/store.schedule.ts +27 -21
  57. package/src/core/engine/store/store.types.ts +177 -227
  58. package/src/core/engine/validation/__tests__/intent.test.ts +70 -0
  59. package/src/core/engine/validation/file.ts +8 -4
  60. package/src/core/engine/validation/index.ts +1 -0
  61. package/src/core/engine/validation/intent.ts +49 -27
  62. package/src/core/engine/validation/list.ts +14 -15
  63. package/src/core/engine/validation/mime.ts +39 -0
  64. package/src/core/errors.ts +271 -0
  65. package/src/core/index.ts +28 -5
  66. package/src/core/persistence/__tests__/deserialize-nan-bypass.test.ts +100 -0
  67. package/src/core/persistence/__tests__/persistence.test.ts +85 -0
  68. package/src/core/persistence/adapters.indexeddb.ts +9 -3
  69. package/src/core/persistence/adapters.local.ts +9 -3
  70. package/src/core/persistence/adapters.memory.ts +35 -13
  71. package/src/core/persistence/index.ts +5 -5
  72. package/src/core/persistence/persistence.ts +73 -60
  73. package/src/core/persistence/persistence.types.ts +140 -60
  74. package/src/core/utils/__tests__/guards.test.ts +93 -0
  75. package/src/core/utils/__tests__/url-safety.test.ts +112 -0
  76. package/src/core/utils/async.ts +7 -2
  77. package/src/core/utils/constants.ts +3 -9
  78. package/src/core/utils/emitter/emitter.ts +126 -0
  79. package/src/core/utils/emitter/emitter.types.ts +33 -16
  80. package/src/core/utils/emitter/index.ts +2 -74
  81. package/src/core/utils/fingerprint.ts +16 -5
  82. package/src/core/utils/guards.ts +91 -1
  83. package/src/core/utils/hash/__tests__/sha256.test.ts +61 -0
  84. package/src/core/utils/hash/index.ts +126 -0
  85. package/src/core/utils/hash/sha256.ts +179 -0
  86. package/src/core/utils/id.ts +33 -1
  87. package/src/core/utils/index.ts +15 -6
  88. package/src/core/utils/mime-sniff.ts +119 -0
  89. package/src/core/utils/sanitize-filename.ts +85 -0
  90. package/src/core/utils/url-safety.ts +156 -0
  91. package/src/react/__tests__/use-dropzone.test.ts +64 -0
  92. package/src/react/index.ts +5 -2
  93. package/src/react/upload-provider.tsx +59 -95
  94. package/src/react/uploader.types.ts +80 -0
  95. package/src/react/use-dropzone.tsx +226 -0
  96. package/src/react/use-uploader.tsx +97 -93
  97. package/src/strategies/_shared/__tests__/retry.test.ts +120 -0
  98. package/src/strategies/_shared/retry.ts +79 -0
  99. package/src/strategies/index.ts +9 -15
  100. package/src/strategies/multipart/__tests__/multipart.test.ts +229 -0
  101. package/src/strategies/multipart/index.ts +211 -116
  102. package/src/strategies/post/__tests__/post-url-safety.test.ts +119 -0
  103. package/src/strategies/post/index.ts +113 -71
  104. package/src/strategies/put/__tests__/put.test.ts +132 -0
  105. package/src/strategies/put/index.ts +139 -0
  106. package/src/strategies/registry/index.ts +12 -9
  107. package/src/strategies/tus/__tests__/tus.test.ts +159 -0
  108. package/src/strategies/tus/index.ts +211 -0
  109. package/dist/async-DxRWu4le.js +0 -1
  110. package/dist/core-BNxznRif.js +0 -2
  111. package/dist/index-BXg9WIce.d.ts +0 -987
  112. package/dist/strategy.types-gg9ZStRe.d.ts +0 -225
  113. package/src/core/client/config.types.ts +0 -77
  114. package/src/core/client/index.ts +0 -4
  115. package/src/core/client/plugins.types.ts +0 -46
  116. package/src/core/client/retry.types.ts +0 -6
  117. package/src/core/client/uploadClient.ts +0 -19
  118. package/src/core/contracts/backendApi.ts +0 -62
  119. package/src/core/contracts/cursor.types.ts +0 -23
  120. package/src/core/contracts/errors.types.ts +0 -67
  121. package/src/core/contracts/fingerprint.types.ts +0 -18
  122. package/src/core/contracts/intent.types.ts +0 -39
  123. package/src/core/contracts/result.types.ts +0 -11
  124. package/src/core/contracts/strategy.types.ts +0 -45
  125. package/src/core/engine/commands.types.ts +0 -17
  126. package/src/core/engine/event-map.types.ts +0 -45
  127. package/src/core/engine/internal-events.types.ts +0 -222
  128. package/src/core/engine/outcome.types.ts +0 -9
  129. package/src/core/engine/progress.types.ts +0 -15
@@ -1,225 +0,0 @@
1
- //#region src/core/contracts/intent.types.d.ts
2
- /**
3
- * Base shape for all upload intents.
4
- *
5
- * An "intent" is data provided by your backend that tells the client how to upload a file
6
- * (for example: a presigned POST, presigned PUT, multipart upload IDs, part URLs, headers, etc).
7
- *
8
- * Every intent must include:
9
- * - `strategy`: a discriminant used to select an upload strategy implementation
10
- * - `fileId`: a stable backend identifier for the file being uploaded
11
- *
12
- * @typeParam K - Strategy key / discriminant string (example: `"direct" | "multipart"`).
13
- */
14
- type IntentBase<K extends string = string> = {
15
- /** Strategy name (discriminant). */strategy: K; /** Unique backend file identifier. */
16
- fileId: string;
17
- };
18
- /**
19
- * Registry of intent types mapped by strategy name.
20
- *
21
- * Example:
22
- * ```ts
23
- * type MyIntents = {
24
- * direct: DirectIntent
25
- * multipart: MultipartIntent
26
- * }
27
- * ```
28
- *
29
- * `M[keyof M]` is used throughout the engine as the "any intent" union.
30
- */
31
- type IntentMap = Record<string, IntentBase<string>>;
32
- /** Union of strategy keys for an intent map. */
33
- type StrategyKey<M extends IntentMap> = keyof M & string;
34
- /** Union of all intent variants for an intent map. */
35
- type AnyIntent<M extends IntentMap> = M[StrategyKey<M>];
36
- //#endregion
37
- //#region src/core/contracts/result.types.d.ts
38
- /**
39
- * Base shape for upload completion results returned by your backend.
40
- *
41
- * Extend this with any additional metadata you return (e.g. width/height, variants).
42
- */
43
- type UploadResultBase = {
44
- /** Backend file identifier. */fileId: string; /** Storage key or path used for signed URLs. */
45
- key: string;
46
- };
47
- //#endregion
48
- //#region src/core/contracts/backendApi.d.ts
49
- type WithSignal = {
50
- signal?: AbortSignal;
51
- };
52
- interface UploadApi<M extends IntentMap, P extends string, R extends UploadResultBase = UploadResultBase> {
53
- createIntent(args: {
54
- purpose: P;
55
- contentType: string;
56
- size: number;
57
- filename: string;
58
- checksum?: string;
59
- }, opts?: WithSignal): Promise<M[keyof M]>;
60
- complete(args: {
61
- fileId: string;
62
- }, opts?: WithSignal): Promise<R>;
63
- getSignedPreviewUrl?(args: {
64
- fileId: string;
65
- key: string;
66
- purpose: P;
67
- }, opts?: WithSignal): Promise<string>;
68
- /**
69
- * Optional support for finding existing files by checksum for deduplication.
70
- * If implemented, the upload engine will check for existing files before uploading.
71
- * If a file with the same checksum exists, the upload will be skipped and the existing file will be linked.
72
- */
73
- findByChecksum?(args: {
74
- checksum: string;
75
- purpose: P;
76
- }, opts?: WithSignal): Promise<R | null>;
77
- multipart?: {
78
- signPart(args: {
79
- fileId: string;
80
- uploadId: string;
81
- partNumber: number;
82
- checksum?: string;
83
- }, opts?: WithSignal): Promise<{
84
- url: string;
85
- headers?: Record<string, string>;
86
- }>;
87
- completeMultipart(args: {
88
- fileId: string;
89
- uploadId: string;
90
- parts: Array<{
91
- partNumber: number;
92
- etag: string;
93
- }>;
94
- }, opts?: WithSignal): Promise<unknown>;
95
- listParts?(args: {
96
- fileId: string;
97
- uploadId: string;
98
- }, opts?: WithSignal): Promise<Array<{
99
- partNumber: number;
100
- etag?: string;
101
- size?: number;
102
- }>>;
103
- abort?(args: {
104
- fileId: string;
105
- uploadId: string;
106
- }, opts?: WithSignal): Promise<void>;
107
- };
108
- tus?: {
109
- create(args: {
110
- fileId: string;
111
- size: number;
112
- filename: string;
113
- contentType: string;
114
- }, opts?: WithSignal): Promise<{
115
- uploadUrl: string;
116
- }>;
117
- getOffset(args: {
118
- uploadUrl: string;
119
- }, opts?: WithSignal): Promise<{
120
- offset: number;
121
- }>;
122
- };
123
- }
124
- //#endregion
125
- //#region src/core/contracts/cursor.types.d.ts
126
- /**
127
- * Registry of cursor payload types mapped by strategy name.
128
- *
129
- * A "cursor" is strategy-defined resume state (ex: multipart uploaded parts, next byte offset).
130
- * Use `unknown` here and define concrete cursor payload types in your app.
131
- *
132
- * @typeParam M - The intent map, used to constrain strategy keys.
133
- */
134
- type CursorMap<M extends IntentMap> = Partial<Record<StrategyKey<M>, unknown>>;
135
- /**
136
- * Discriminated union of all cursor variants.
137
- *
138
- * The cursor is always tagged with a `strategy` so it can be routed back to the correct strategy
139
- * on resume/rebind.
140
- *
141
- * @typeParam C - Cursor registry (usually {@link CursorMap}).
142
- */
143
- type AnyCursor<C extends Record<string, unknown>> = { [K in keyof C & string]: {
144
- strategy: K;
145
- value?: C[K];
146
- } }[keyof C & string];
147
- //#endregion
148
- //#region src/core/contracts/transport/transport.types.d.ts
149
- /**
150
- * Abstraction layer for network operations.
151
- * Allows switching between XHR (browser) or other implementations (testing/Node).
152
- *
153
- * Designed to support:
154
- * - Deterministic progress tracking (requires XHR in browsers)
155
- * - Cancellation via AbortSignal
156
- * - Header parsing (ETag extraction)
157
- */
158
- interface UploadTransport {
159
- /**
160
- * Performs an HTTP PUT request (typically for S3 signed URLs).
161
- */
162
- put(args: {
163
- url: string;
164
- body: Blob;
165
- headers?: Record<string, string>;
166
- signal: AbortSignal;
167
- onProgress?: (u: number, t: number) => void;
168
- }): Promise<{
169
- etag?: string;
170
- headers?: Record<string, string>;
171
- }>;
172
- /**
173
- * Performs an HTTP POST multipart request.
174
- */
175
- postForm(args: {
176
- url: string;
177
- fields: Record<string, string>;
178
- file: File | Blob;
179
- filename?: string;
180
- signal: AbortSignal;
181
- onProgress?: (uploadedBytes: number, totalBytes: number) => void;
182
- }): Promise<{
183
- etag?: string;
184
- headers?: Record<string, string>;
185
- }>;
186
- /**
187
- * Performs an HTTP request.
188
- */
189
- patch(args: {
190
- url: string;
191
- body: Blob | ArrayBuffer;
192
- headers?: Record<string, string>;
193
- signal: AbortSignal;
194
- onProgress?: (u: number, t: number) => void;
195
- }): Promise<{
196
- headers?: Record<string, string>;
197
- }>;
198
- }
199
- //#endregion
200
- //#region src/core/contracts/strategy.types.d.ts
201
- type StrategyCtx<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase, K extends keyof M & string> = {
202
- file: File;
203
- intent: M[K];
204
- signal: AbortSignal;
205
- transport: UploadTransport;
206
- api: UploadApi<M, P, R>;
207
- reportProgress: (p: {
208
- uploadedBytes: number;
209
- totalBytes: number;
210
- }) => void;
211
- readCursor: () => C[K] | undefined;
212
- persistCursor: (cursor: C[K] | undefined) => void;
213
- };
214
- type UploadStrategy<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase, K extends keyof M & string> = {
215
- id: K;
216
- resumable: boolean;
217
- start(ctx: StrategyCtx<M, C, P, R, K>): Promise<void>;
218
- };
219
- interface StrategyRegistry<M extends IntentMap, C extends CursorMap<M>, P extends string = string, R extends UploadResultBase = UploadResultBase> {
220
- get<K extends keyof M & string>(id: K): UploadStrategy<M, C, P, R, K> | undefined;
221
- has(id: string): id is keyof M & string;
222
- set<K extends keyof M & string>(strategy: UploadStrategy<M, C, P, R, K>): void;
223
- }
224
- //#endregion
225
- export { AnyCursor as a, WithSignal as c, IntentBase as d, IntentMap as f, UploadTransport as i, UploadResultBase as l, StrategyRegistry as n, CursorMap as o, StrategyKey as p, UploadStrategy as r, UploadApi as s, StrategyCtx as t, AnyIntent as u };
@@ -1,77 +0,0 @@
1
- import type { UploadError, UploadValidationRules } from '../contracts'
2
- import {
3
- DEFAULT_MAX_ATTEMPTS,
4
- DEFAULT_MAX_CONCURRENT_UPLOADS,
5
- DEFAULT_MAX_ITEMS,
6
- DEFAULT_PROGRESS_THROTTLE_MS,
7
- } from '../utils/constants'
8
- import type { RetryDecision } from './retry.types'
9
-
10
- /**
11
- * Global upload engine configuration.
12
- *
13
- * @typeParam P - Union of allowed `purpose` strings for your app.
14
- */
15
- export type UploadConfig<P extends string> = {
16
- /** Maximum concurrent uploads. */
17
- maxConcurrentUploads: number
18
-
19
- /**
20
- * Auto-start uploads for specific purposes.
21
- * - Array form: only those purposes auto-start
22
- * - Function form: dynamic decision per purpose
23
- */
24
- autoStart?: readonly P[] | ((purpose: P) => boolean)
25
-
26
- /** Progress event throttle in milliseconds. */
27
- progressThrottleMs: number
28
-
29
- /** Validation rules keyed by purpose. */
30
- validation: Partial<Record<P, UploadValidationRules>>
31
-
32
- /** Maximum number of retry attempts (per item / per phase depending on implementation). */
33
- maxAttempts: number
34
-
35
- /**
36
- * Retry policy.
37
- * Called whenever a phase fails and the engine needs a decision.
38
- */
39
- retryPolicy?: (ctx: { phase: 'intent' | 'upload' | 'complete'; attempt: number; error: UploadError }) => RetryDecision
40
-
41
- /**
42
- * Maximum number of items to keep in state.
43
- * When exceeded, oldest completed/canceled items are removed automatically.
44
- *
45
- * Set to `undefined` for no limit (not recommended for long-running apps).
46
- * Default often: `100`.
47
- */
48
- maxItems: number
49
-
50
- /**
51
- * Automatically remove completed items after this many milliseconds.
52
- * Set to `undefined` to keep completed items indefinitely.
53
- */
54
- completedItemTTL?: number
55
- }
56
-
57
- /**
58
- * Config input shape accepted by store creation.
59
- * All fields are optional; defaults are applied by {@link resolveUploadConfig}.
60
- */
61
- export type UploadConfigInput<P extends string> = Partial<UploadConfig<P>>
62
-
63
- /**
64
- * Normalizes user config by applying sensible defaults.
65
- */
66
- export function resolveUploadConfig<P extends string>(input?: UploadConfigInput<P>): UploadConfig<P> {
67
- return {
68
- maxConcurrentUploads: Math.max(1, input?.maxConcurrentUploads ?? DEFAULT_MAX_CONCURRENT_UPLOADS),
69
- autoStart: input?.autoStart,
70
- progressThrottleMs: Math.max(0, input?.progressThrottleMs ?? DEFAULT_PROGRESS_THROTTLE_MS),
71
- validation: input?.validation ?? {},
72
- maxAttempts: Math.max(1, input?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS),
73
- retryPolicy: input?.retryPolicy,
74
- maxItems: Math.max(1, input?.maxItems ?? DEFAULT_MAX_ITEMS),
75
- completedItemTTL: input?.completedItemTTL,
76
- }
77
- }
@@ -1,4 +0,0 @@
1
- export * from './config.types'
2
- export * from './plugins.types'
3
- export * from './retry.types'
4
- export * from './uploadClient'
@@ -1,46 +0,0 @@
1
- import type { CursorMap, IntentMap, UploadResultBase } from '../contracts'
2
- import type { InternalEvent } from '../engine/internal-events.types'
3
- import type { UploadStore } from '../engine/store'
4
-
5
- /**
6
- * Optional hooks for observing internal behavior.
7
- *
8
- * Use these for diagnostics, analytics, logging, or devtools.
9
- */
10
- export type UploadHooks<
11
- M extends IntentMap,
12
- C extends CursorMap<M>,
13
- P extends string,
14
- R extends UploadResultBase = UploadResultBase,
15
- > = {
16
- /**
17
- * Called after internal events are processed (depending on your runtime wiring).
18
- * Prefer this over subscribing to public events if you need full fidelity.
19
- */
20
- onInternalEvent?: (
21
- event: InternalEvent<M, C, P, R>,
22
- state: import('../engine/reducer').UploadState<M, C, P, R>,
23
- ) => void
24
- }
25
-
26
- /**
27
- * Store plugins.
28
- *
29
- * Plugins can subscribe to store events and dispatch commands.
30
- * They should not reach into internal state mutably.
31
- */
32
- export type UploadPlugin<
33
- M extends IntentMap,
34
- C extends CursorMap<M>,
35
- P extends string,
36
- R extends UploadResultBase = UploadResultBase,
37
- > = {
38
- /** Plugin name (for debugging). */
39
- name: string
40
-
41
- /**
42
- * Setup callback called once when the store is created.
43
- * The plugin can register event listeners and drive behavior.
44
- */
45
- setup: (ctx: Pick<UploadStore<M, C, P, R>, 'on' | 'dispatch' | 'getSnapshot'>) => void
46
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Output of the retry policy.
3
- *
4
- * `delayMs` should be a backoff delay before re-attempting the failed step.
5
- */
6
- export type RetryDecision = { retryable: false } | { retryable: true; delayMs: number }
@@ -1,19 +0,0 @@
1
- import type { CursorMap, IntentMap, UploadResultBase } from '../contracts'
2
- import type { StoreOptions, UploadStore } from '../engine/store'
3
- import { createUploadStore } from '../engine/store'
4
-
5
- export type UploadClient<
6
- M extends IntentMap,
7
- C extends CursorMap<M>,
8
- P extends string,
9
- R extends UploadResultBase = UploadResultBase,
10
- > = UploadStore<M, C, P, R>
11
-
12
- export function createUploadClient<
13
- M extends IntentMap,
14
- C extends CursorMap<M>,
15
- P extends string,
16
- R extends UploadResultBase = UploadResultBase,
17
- >(opts: StoreOptions<M, C, P, R>): UploadClient<M, C, P, R> {
18
- return createUploadStore(opts)
19
- }
@@ -1,62 +0,0 @@
1
- import type { IntentMap } from './intent.types'
2
- import type { UploadResultBase } from './result.types'
3
-
4
- export type WithSignal = { signal?: AbortSignal }
5
-
6
- // Backend API contract (typed, injected)
7
- export interface UploadApi<M extends IntentMap, P extends string, R extends UploadResultBase = UploadResultBase> {
8
- // Create upload intent - backend decides strategy, bucket, key
9
- createIntent(
10
- args: {
11
- purpose: P
12
- contentType: string
13
- size: number
14
- filename: string
15
- checksum?: string
16
- },
17
- opts?: WithSignal,
18
- ): Promise<M[keyof M]>
19
-
20
- // Finalize upload (mark as complete in DB, return metadata)
21
- complete(args: { fileId: string }, opts?: WithSignal): Promise<R>
22
-
23
- // Get signed preview URL
24
- getSignedPreviewUrl?(args: { fileId: string; key: string; purpose: P }, opts?: WithSignal): Promise<string>
25
-
26
- /**
27
- * Optional support for finding existing files by checksum for deduplication.
28
- * If implemented, the upload engine will check for existing files before uploading.
29
- * If a file with the same checksum exists, the upload will be skipped and the existing file will be linked.
30
- */
31
- findByChecksum?(args: { checksum: string; purpose: P }, opts?: WithSignal): Promise<R | null>
32
-
33
- // Multipart-specific operations (optional, for multipart strategy)
34
- multipart?: {
35
- signPart(
36
- args: { fileId: string; uploadId: string; partNumber: number; checksum?: string },
37
- opts?: WithSignal,
38
- ): Promise<{ url: string; headers?: Record<string, string> }>
39
-
40
- completeMultipart(
41
- args: { fileId: string; uploadId: string; parts: Array<{ partNumber: number; etag: string }> },
42
- opts?: WithSignal,
43
- ): Promise<unknown>
44
-
45
- listParts?(
46
- args: { fileId: string; uploadId: string },
47
- opts?: WithSignal,
48
- ): Promise<Array<{ partNumber: number; etag?: string; size?: number }>>
49
-
50
- abort?(args: { fileId: string; uploadId: string }, opts?: WithSignal): Promise<void>
51
- }
52
-
53
- // TUS-specific operations (optional, for tus strategy)
54
- tus?: {
55
- create(
56
- args: { fileId: string; size: number; filename: string; contentType: string },
57
- opts?: WithSignal,
58
- ): Promise<{ uploadUrl: string }>
59
-
60
- getOffset(args: { uploadUrl: string }, opts?: WithSignal): Promise<{ offset: number }>
61
- }
62
- }
@@ -1,23 +0,0 @@
1
- import type { IntentMap, StrategyKey } from './intent.types'
2
-
3
- /**
4
- * Registry of cursor payload types mapped by strategy name.
5
- *
6
- * A "cursor" is strategy-defined resume state (ex: multipart uploaded parts, next byte offset).
7
- * Use `unknown` here and define concrete cursor payload types in your app.
8
- *
9
- * @typeParam M - The intent map, used to constrain strategy keys.
10
- */
11
- export type CursorMap<M extends IntentMap> = Partial<Record<StrategyKey<M>, unknown>>
12
-
13
- /**
14
- * Discriminated union of all cursor variants.
15
- *
16
- * The cursor is always tagged with a `strategy` so it can be routed back to the correct strategy
17
- * on resume/rebind.
18
- *
19
- * @typeParam C - Cursor registry (usually {@link CursorMap}).
20
- */
21
- export type AnyCursor<C extends Record<string, unknown>> = {
22
- [K in keyof C & string]: { strategy: K; value?: C[K] }
23
- }[keyof C & string]
@@ -1,67 +0,0 @@
1
- /**
2
- * File rejection reasons produced by client-side validation.
3
- *
4
- * These should be safe to show in UI.
5
- */
6
- export type RejectReason =
7
- | { code: 'empty_file' }
8
- | { code: 'file_too_large'; maxBytes: number; size: number }
9
- | { code: 'type_not_allowed'; allowed: string[]; got: string }
10
- | { code: 'too_many_files'; max: number }
11
-
12
- /**
13
- * Base interface for custom errors.
14
- *
15
- * If you want to add domain-specific fields, extend this and intersect in {@link UploadError}.
16
- */
17
- export interface UploadErrorBase {
18
- /** Stable error code. */
19
- code: string
20
- /** Human-friendly message (safe for UI). */
21
- message: string
22
- /** Optional cause (original error, response, etc). */
23
- cause?: unknown
24
- /** Whether the error should be retried. */
25
- retryable?: boolean
26
- }
27
-
28
- /**
29
- * Built-in engine errors.
30
- *
31
- * Keep these narrow and explicit so reducer logic can rely on `code` as a discriminant.
32
- */
33
- export type BuiltInUploadError =
34
- | { code: 'validation_failed'; message: string; reason: RejectReason; retryable?: false }
35
- | { code: 'strategy_missing'; message: string; strategy: string; retryable?: false }
36
- | { code: 'aborted'; message: string; reason: 'pause' | 'cancel' | 'unknown'; cause?: unknown; retryable?: false }
37
- | { code: 'network'; message: string; cause?: unknown; retryable?: boolean }
38
- | { code: 'http'; message: string; status: number; statusText?: string; cause?: unknown; retryable?: boolean }
39
- | { code: 'timeout'; message: string; cause?: unknown; retryable?: boolean }
40
- | { code: 'auth'; message: string; cause?: unknown; retryable?: false }
41
- | { code: 'rate_limit'; message: string; retryAfterMs?: number; cause?: unknown; retryable?: boolean }
42
- | { code: 'server'; message: string; serverCode?: string; cause?: unknown; retryable?: boolean }
43
- | { code: 'unknown'; message: string; cause?: unknown; retryable?: boolean }
44
-
45
- /**
46
- * The error type used everywhere in the engine.
47
- *
48
- * You can extend this by intersecting your own shape with {@link UploadErrorBase}
49
- * to add fields (example: `{ endpoint: string }`).
50
- */
51
- export type UploadError = BuiltInUploadError | (UploadErrorBase & Record<string, unknown>)
52
-
53
- /**
54
- * Validation rules applied on `addFiles` before an item enters the upload pipeline.
55
- */
56
- export type UploadValidationRules = {
57
- /** Maximum number of files allowed. */
58
- maxFiles?: number
59
- /** Maximum file size in bytes. */
60
- maxSizeBytes?: number
61
- /** Minimum file size in bytes. */
62
- minSizeBytes?: number
63
- /** Allowed MIME types. */
64
- allowedTypes?: string[]
65
- /** Allowed file extensions. */
66
- allowedExtensions?: string[]
67
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Deterministic client-side identity for a file.
3
- *
4
- * Used to match the same file across refreshes (for rebind) and to deduplicate.
5
- * If you include `checksum`, it should be stable and computed consistently.
6
- */
7
- export type FileFingerprint = {
8
- /** File name. */
9
- name: string
10
- /** File size in bytes. */
11
- size: number
12
- /** File MIME type. */
13
- type: string
14
- /** File lastModified timestamp (ms). */
15
- lastModified: number
16
- /** Optional checksum (example: SHA-256 hex). */
17
- checksum?: string
18
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Base shape for all upload intents.
3
- *
4
- * An "intent" is data provided by your backend that tells the client how to upload a file
5
- * (for example: a presigned POST, presigned PUT, multipart upload IDs, part URLs, headers, etc).
6
- *
7
- * Every intent must include:
8
- * - `strategy`: a discriminant used to select an upload strategy implementation
9
- * - `fileId`: a stable backend identifier for the file being uploaded
10
- *
11
- * @typeParam K - Strategy key / discriminant string (example: `"direct" | "multipart"`).
12
- */
13
- export type IntentBase<K extends string = string> = {
14
- /** Strategy name (discriminant). */
15
- strategy: K
16
- /** Unique backend file identifier. */
17
- fileId: string
18
- }
19
-
20
- /**
21
- * Registry of intent types mapped by strategy name.
22
- *
23
- * Example:
24
- * ```ts
25
- * type MyIntents = {
26
- * direct: DirectIntent
27
- * multipart: MultipartIntent
28
- * }
29
- * ```
30
- *
31
- * `M[keyof M]` is used throughout the engine as the "any intent" union.
32
- */
33
- export type IntentMap = Record<string, IntentBase<string>>
34
-
35
- /** Union of strategy keys for an intent map. */
36
- export type StrategyKey<M extends IntentMap> = keyof M & string
37
-
38
- /** Union of all intent variants for an intent map. */
39
- export type AnyIntent<M extends IntentMap> = M[StrategyKey<M>]
@@ -1,11 +0,0 @@
1
- /**
2
- * Base shape for upload completion results returned by your backend.
3
- *
4
- * Extend this with any additional metadata you return (e.g. width/height, variants).
5
- */
6
- export type UploadResultBase = {
7
- /** Backend file identifier. */
8
- fileId: string
9
- /** Storage key or path used for signed URLs. */
10
- key: string
11
- }
@@ -1,45 +0,0 @@
1
- import type { UploadApi } from './backendApi'
2
- import type { CursorMap } from './cursor.types'
3
- import type { IntentMap } from './intent.types'
4
- import type { UploadResultBase } from './result.types'
5
- import type { UploadTransport } from './transport/transport.types'
6
-
7
- export type StrategyCtx<
8
- M extends IntentMap,
9
- C extends CursorMap<M>,
10
- P extends string,
11
- R extends UploadResultBase,
12
- K extends keyof M & string,
13
- > = {
14
- file: File
15
- intent: M[K]
16
- signal: AbortSignal
17
- transport: UploadTransport
18
- api: UploadApi<M, P, R>
19
- reportProgress: (p: { uploadedBytes: number; totalBytes: number }) => void
20
- readCursor: () => C[K] | undefined
21
- persistCursor: (cursor: C[K] | undefined) => void
22
- }
23
-
24
- export type UploadStrategy<
25
- M extends IntentMap,
26
- C extends CursorMap<M>,
27
- P extends string,
28
- R extends UploadResultBase,
29
- K extends keyof M & string,
30
- > = {
31
- id: K
32
- resumable: boolean
33
- start(ctx: StrategyCtx<M, C, P, R, K>): Promise<void>
34
- }
35
-
36
- export interface StrategyRegistry<
37
- M extends IntentMap,
38
- C extends CursorMap<M>,
39
- P extends string = string,
40
- R extends UploadResultBase = UploadResultBase,
41
- > {
42
- get<K extends keyof M & string>(id: K): UploadStrategy<M, C, P, R, K> | undefined
43
- has(id: string): id is keyof M & string
44
- set<K extends keyof M & string>(strategy: UploadStrategy<M, C, P, R, K>): void
45
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Public commands accepted by the upload store.
3
- *
4
- * Reducer/effects should treat commands as "user intent".
5
- */
6
- export type UploadCommand<P extends string> =
7
- | { type: 'addFiles'; files: File[]; purpose: P }
8
- | { type: 'start'; localId: string }
9
- | { type: 'startAll'; purpose?: P }
10
- | { type: 'pause'; localId: string }
11
- | { type: 'pauseAll'; purpose?: P }
12
- | { type: 'resume'; localId: string }
13
- | { type: 'cancel'; localId: string }
14
- | { type: 'cancelAll'; purpose?: P }
15
- | { type: 'retry'; localId: string }
16
- | { type: 'rebind'; localId: string; file: File }
17
- | { type: 'remove'; localId: string }