@gentleduck/upload 0.2.6 → 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 (136) hide show
  1. package/README.md +170 -193
  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 -51
  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/{__test__ → __tests__}/emitter.test.ts +4 -4
  18. package/src/{__test__ → __tests__}/fingerprint.test.ts +1 -1
  19. package/src/{__test__ → __tests__}/guards.test.ts +15 -5
  20. package/src/__tests__/id.test.ts +67 -0
  21. package/src/__tests__/mime-sniff.test.ts +142 -0
  22. package/src/{__test__ → __tests__}/persistence.test.ts +43 -8
  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/{__test__ → __tests__}/store-libs.test.ts +57 -3
  27. package/src/__tests__/transport-fetch.test.ts +108 -0
  28. package/src/{__test__ → __tests__}/transport.test.ts +5 -5
  29. package/src/core/__tests__/errors.test.ts +152 -0
  30. package/src/core/client.ts +3 -0
  31. package/src/core/contracts/contracts.types.ts +335 -141
  32. package/src/core/contracts/index.ts +3 -8
  33. package/src/core/contracts/transport/index.ts +3 -2
  34. package/src/core/contracts/transport/transport.fetch.ts +203 -0
  35. package/src/core/contracts/transport/transport.libs.ts +7 -8
  36. package/src/core/contracts/transport/transport.ts +91 -83
  37. package/src/core/contracts/transport/transport.types.ts +71 -35
  38. package/src/core/engine/__tests__/config-nan-bypass.test.ts +40 -0
  39. package/src/core/engine/engine.libs.ts +35 -0
  40. package/src/core/engine/engine.types.ts +367 -241
  41. package/src/core/engine/index.ts +3 -8
  42. package/src/core/engine/reducer.ts +89 -43
  43. package/src/core/engine/store/handlers/add-file.ts +39 -29
  44. package/src/core/engine/store/handlers/cancel.ts +13 -8
  45. package/src/core/engine/store/handlers/clean-up.ts +11 -8
  46. package/src/core/engine/store/handlers/create-intent.ts +82 -27
  47. package/src/core/engine/store/handlers/finalize.ts +30 -13
  48. package/src/core/engine/store/handlers/index.ts +7 -7
  49. package/src/core/engine/store/handlers/pause.ts +8 -6
  50. package/src/core/engine/store/handlers/run-upload.ts +49 -34
  51. package/src/core/engine/store/index.ts +27 -21
  52. package/src/core/engine/store/store.dispatch.ts +10 -8
  53. package/src/core/engine/store/store.libs.ts +184 -41
  54. package/src/core/engine/store/store.runtime.ts +45 -36
  55. package/src/core/engine/store/store.schedule.ts +27 -21
  56. package/src/core/engine/store/store.types.ts +177 -227
  57. package/src/core/engine/validation/__tests__/intent.test.ts +70 -0
  58. package/src/core/engine/validation/file.ts +7 -3
  59. package/src/core/engine/validation/index.ts +1 -0
  60. package/src/core/engine/validation/intent.ts +49 -27
  61. package/src/core/engine/validation/list.ts +14 -15
  62. package/src/core/engine/validation/mime.ts +39 -0
  63. package/src/core/errors.ts +271 -0
  64. package/src/core/index.ts +28 -5
  65. package/src/core/persistence/__tests__/deserialize-nan-bypass.test.ts +100 -0
  66. package/src/core/persistence/__tests__/persistence.test.ts +85 -0
  67. package/src/core/persistence/adapters.indexeddb.ts +9 -3
  68. package/src/core/persistence/adapters.local.ts +9 -3
  69. package/src/core/persistence/adapters.memory.ts +35 -13
  70. package/src/core/persistence/index.ts +5 -5
  71. package/src/core/persistence/persistence.ts +73 -60
  72. package/src/core/persistence/persistence.types.ts +140 -60
  73. package/src/core/utils/__tests__/guards.test.ts +93 -0
  74. package/src/core/utils/__tests__/url-safety.test.ts +112 -0
  75. package/src/core/utils/async.ts +7 -2
  76. package/src/core/utils/constants.ts +3 -9
  77. package/src/core/utils/emitter/emitter.ts +7 -24
  78. package/src/core/utils/emitter/emitter.types.ts +33 -16
  79. package/src/core/utils/emitter/index.ts +2 -74
  80. package/src/core/utils/fingerprint.ts +16 -5
  81. package/src/core/utils/guards.ts +91 -1
  82. package/src/core/utils/hash/__tests__/sha256.test.ts +61 -0
  83. package/src/core/utils/hash/index.ts +126 -0
  84. package/src/core/utils/hash/sha256.ts +179 -0
  85. package/src/core/utils/id.ts +33 -1
  86. package/src/core/utils/index.ts +15 -6
  87. package/src/core/utils/mime-sniff.ts +119 -0
  88. package/src/core/utils/sanitize-filename.ts +85 -0
  89. package/src/core/utils/url-safety.ts +156 -0
  90. package/src/react/__tests__/use-dropzone.test.ts +64 -0
  91. package/src/react/index.ts +5 -2
  92. package/src/react/upload-provider.tsx +68 -53
  93. package/src/react/uploader.types.ts +58 -61
  94. package/src/react/use-dropzone.tsx +226 -0
  95. package/src/react/use-uploader.tsx +98 -91
  96. package/src/strategies/_shared/__tests__/retry.test.ts +120 -0
  97. package/src/strategies/_shared/retry.ts +79 -0
  98. package/src/strategies/index.ts +9 -15
  99. package/src/strategies/multipart/__tests__/multipart.test.ts +229 -0
  100. package/src/strategies/multipart/index.ts +206 -108
  101. package/src/strategies/post/__tests__/post-url-safety.test.ts +119 -0
  102. package/src/strategies/post/index.ts +115 -33
  103. package/src/strategies/put/__tests__/put.test.ts +132 -0
  104. package/src/strategies/put/index.ts +139 -0
  105. package/src/strategies/registry/index.ts +12 -9
  106. package/src/strategies/tus/__tests__/tus.test.ts +159 -0
  107. package/src/strategies/tus/index.ts +211 -0
  108. package/dist/async-DxRWu4le.js +0 -1
  109. package/dist/core-CMkrP9DX.js +0 -2
  110. package/dist/index-C38Jmizu.d.ts +0 -1005
  111. package/dist/strategy.types-gg9ZStRe.d.ts +0 -225
  112. package/src/__test__/effect-pool.test.ts +0 -5
  113. package/src/__test__/id.test.ts +0 -14
  114. package/src/__test__/store-integration.test.ts +0 -5
  115. package/src/core/client/client.ts +0 -50
  116. package/src/core/client/client.types.ts +0 -184
  117. package/src/core/client/config.types.ts +0 -77
  118. package/src/core/client/index.ts +0 -4
  119. package/src/core/client/plugins.types.ts +0 -46
  120. package/src/core/client/retry.types.ts +0 -6
  121. package/src/core/client/uploadClient.ts +0 -19
  122. package/src/core/contracts/backendApi.ts +0 -62
  123. package/src/core/contracts/cursor.types.ts +0 -23
  124. package/src/core/contracts/errors.types.ts +0 -67
  125. package/src/core/contracts/fingerprint.types.ts +0 -18
  126. package/src/core/contracts/intent.types.ts +0 -39
  127. package/src/core/contracts/result.types.ts +0 -11
  128. package/src/core/contracts/strategy.types.ts +0 -45
  129. package/src/core/engine/commands.types.ts +0 -17
  130. package/src/core/engine/event-map.types.ts +0 -45
  131. package/src/core/engine/internal-events.types.ts +0 -202
  132. package/src/core/engine/outcome.types.ts +0 -9
  133. package/src/core/engine/progress.types.ts +0 -15
  134. package/src/core/engine/store/handlers.ts +0 -819
  135. package/src/core/engine/validation.ts +0 -216
  136. /package/src/{__test__ → __tests__}/transport-libs.test.ts +0 -0
@@ -1,179 +1,373 @@
1
1
  /**
2
2
  * Public contract types grouped under the `Contracts` namespace.
3
3
  *
4
- * Each sub-namespace owns one concern (Intent, Cursor, Result, Fingerprint,
5
- * Errors, Validation, Strategy, BackendApi).
6
- *
4
+ * Each sub-namespace owns one concern (Intent, Cursor, Result, Validation,
5
+ * Fingerprint, Errors, Strategy, BackendApi).
7
6
  * @author wildduck2 <https://github.com/wildduck2>
8
7
  */
9
8
  export namespace Contracts {
10
- // -------------------- Intent --------------------
11
-
12
- /** Base shape for all upload intents. */
13
- export interface IIntentBase<K extends string = string> {
14
- /** Strategy name (discriminant). */
15
- strategy: K
16
- /** Unique backend file identifier. */
17
- fileId: string
18
- }
19
-
20
- /** Registry of intent types mapped by strategy name. */
21
- export type IntentMap = Record<string, IIntentBase<string>>
22
-
23
- /** Union of strategy keys for an intent map. */
24
- export type StrategyKey<M extends IntentMap> = keyof M & string
9
+ export namespace Intent {
10
+ /** Base shape for all upload intents. */
11
+ export type Base<K extends string = string> = {
12
+ /** Strategy name (discriminant). */
13
+ strategy: K
14
+ /** Unique backend file identifier. */
15
+ fileId: string
16
+ }
25
17
 
26
- /** Union of all intent variants for an intent map. */
27
- export type AnyIntent<M extends IntentMap> = M[StrategyKey<M>]
18
+ /** Registry of intent types mapped by strategy name. */
19
+ export type Map = Record<string, Base<string>>
28
20
 
29
- // -------------------- Cursor --------------------
21
+ /** Union of strategy keys for an intent map. */
22
+ export type StrategyKey<M extends Map> = keyof M & string
30
23
 
31
- /** Registry of cursor payload types mapped by strategy name. */
32
- export type CursorMap<M extends IntentMap> = Partial<Record<StrategyKey<M>, unknown>>
24
+ /** Union of all intent variants for an intent map. */
25
+ export type Any<M extends Map> = M[StrategyKey<M>]
26
+ }
33
27
 
34
- /** Discriminated union of all cursor variants. */
35
- export type AnyCursor<C extends Record<string, unknown>> = {
36
- [K in keyof C & string]: { strategy: K; value?: C[K] }
37
- }[keyof C & string]
28
+ export namespace Cursor {
29
+ /** Registry of cursor payload types mapped by strategy name. */
30
+ export type Map<M extends Intent.Map> = Partial<Record<Intent.StrategyKey<M>, unknown>>
38
31
 
39
- // -------------------- Result --------------------
32
+ /** Discriminated union of all cursor variants. */
33
+ export type Any<C extends Record<string, unknown>> = {
34
+ [K in keyof C & string]: {
35
+ /** Strategy name associated with this cursor. */
36
+ strategy: K
37
+ /** The custom strategy-defined state needed to resume the upload. */
38
+ value?: C[K] | undefined
39
+ }
40
+ }[keyof C & string]
41
+ }
40
42
 
41
- /** Base shape for upload completion results returned by your backend. */
42
- export interface IResultBase {
43
- /** Backend file identifier. */
44
- fileId: string
45
- /** Storage key or path used for signed URLs. */
46
- key: string
43
+ export namespace Result {
44
+ /** Successful upload payload returned by your backend API's complete endpoint. */
45
+ export type Base = {
46
+ /** Unique backend file identifier. */
47
+ fileId: string
48
+ /** Storage key/path or public address of the uploaded file. */
49
+ key: string
50
+ }
47
51
  }
48
52
 
49
- // -------------------- Fingerprint --------------------
53
+ export namespace Validation {
54
+ /** Why a file was rejected during client-side validation. */
55
+ export type Rejection =
56
+ | { code: 'empty_file' }
57
+ | { code: 'file_too_large'; maxBytes: number; size: number }
58
+ | { code: 'type_not_allowed'; allowed: string[]; got: string }
59
+ | { code: 'too_many_files'; max: number }
60
+ | { code: 'mime_mismatch'; claimed: string; sniffed: string }
61
+ | { code: 'filename_rejected'; reason: 'reserved' | 'too-long' | 'empty' | 'path-sep' }
62
+ }
50
63
 
51
- /** Deterministic client-side identity for a file. */
52
- export interface IFileFingerprint {
53
- /** File name. */
64
+ /** Deterministic identity signature of a file on the client. */
65
+ export type FingerprintFile = {
66
+ /** Base name of the file (excluding directory path). */
54
67
  name: string
55
- /** File size in bytes. */
68
+ /** Size of the file in bytes. */
56
69
  size: number
57
- /** File MIME type. */
70
+ /** MIME type of the file. */
58
71
  type: string
59
- /** File lastModified timestamp (ms). */
72
+ /** Timestamp when the file was last modified on disk (milliseconds since epoch). */
60
73
  lastModified: number
61
- /** Optional checksum (e.g. SHA-256 hex). */
62
- checksum?: string
74
+ /** Optional pre-computed checksum of the file (e.g. SHA-256 hex). */
75
+ checksum?: string | undefined
63
76
  }
64
77
 
65
- // -------------------- Errors --------------------
78
+ export namespace Errors {
79
+ /** Base schema for upload-related runtime errors. */
80
+ export type Base = {
81
+ /** Stable machine-readable error classification. */
82
+ code: string
83
+ /** Human-friendly message suitable for presentation in standard warning elements. */
84
+ message: string
85
+ /** Original thrown error or reason context if available. */
86
+ cause?: unknown
87
+ /** Flag specifying whether the error recovery system should schedule retries. */
88
+ retryable?: boolean
89
+ }
66
90
 
67
- /** File rejection reasons produced by client-side validation. */
68
- export type RejectReason =
69
- | { code: 'empty_file' }
70
- | { code: 'file_too_large'; maxBytes: number; size: number }
71
- | { code: 'type_not_allowed'; allowed: string[]; got: string }
72
- | { code: 'too_many_files'; max: number }
91
+ /** Discriminant union of built-in engine failure structures. */
92
+ export type BuiltIn =
93
+ | { code: 'validation_failed'; message: string; reason: Validation.Rejection; retryable?: false }
94
+ | { code: 'strategy_missing'; message: string; strategy: string; retryable?: false }
95
+ | { code: 'aborted'; message: string; reason: 'pause' | 'cancel' | 'unknown'; cause?: unknown; retryable?: false }
96
+ | { code: 'network'; message: string; cause?: unknown; retryable?: boolean }
97
+ | { code: 'http'; message: string; status: number; statusText?: string; cause?: unknown; retryable?: boolean }
98
+ | { code: 'timeout'; message: string; cause?: unknown; retryable?: boolean }
99
+ | { code: 'auth'; message: string; cause?: unknown; retryable?: false }
100
+ | { code: 'rate_limit'; message: string; retryAfterMs?: number; cause?: unknown; retryable?: boolean }
101
+ | { code: 'server'; message: string; serverCode?: string; cause?: unknown; retryable?: boolean }
102
+ | { code: 'unknown'; message: string; cause?: unknown; retryable?: boolean }
73
103
 
74
- /** Base shape for custom errors. */
75
- export interface IErrorBase {
76
- /** Stable error code. */
77
- code: string
78
- /** Human-friendly message (safe for UI). */
79
- message: string
80
- /** Optional cause (original error, response, etc.). */
81
- cause?: unknown
82
- /** Whether the error should be retried. */
83
- retryable?: boolean
104
+ /** The canonical error shape handled by the engine, plugins, and handlers. */
105
+ export type Error = BuiltIn | (Base & Record<string, unknown>)
84
106
  }
85
107
 
86
- /** Built-in engine errors. */
87
- export type BuiltInError =
88
- | { code: 'validation_failed'; message: string; reason: RejectReason; retryable?: false }
89
- | { code: 'strategy_missing'; message: string; strategy: string; retryable?: false }
90
- | { code: 'aborted'; message: string; reason: 'pause' | 'cancel' | 'unknown'; cause?: unknown; retryable?: false }
91
- | { code: 'network'; message: string; cause?: unknown; retryable?: boolean }
92
- | { code: 'http'; message: string; status: number; statusText?: string; cause?: unknown; retryable?: boolean }
93
- | { code: 'timeout'; message: string; cause?: unknown; retryable?: boolean }
94
- | { code: 'auth'; message: string; cause?: unknown; retryable?: false }
95
- | { code: 'rate_limit'; message: string; retryAfterMs?: number; cause?: unknown; retryable?: boolean }
96
- | { code: 'server'; message: string; serverCode?: string; cause?: unknown; retryable?: boolean }
97
- | { code: 'unknown'; message: string; cause?: unknown; retryable?: boolean }
98
-
99
- /** The error type used everywhere in the engine. */
100
- export type Error = BuiltInError | (IErrorBase & Record<string, unknown>)
101
-
102
- // -------------------- Validation --------------------
103
-
104
- /** Validation rules applied on `addFiles`. */
105
- export interface IValidationRules {
106
- /** Maximum number of files allowed. */
108
+ /** Validation rules applied to files on registration before intents are initiated. */
109
+ export type ValidationRules = {
110
+ /** Maximum number of files permitted in a single upload batch. */
107
111
  maxFiles?: number
108
- /** Maximum file size in bytes. */
112
+ /** Maximum size in bytes allowed for a single file. */
109
113
  maxSizeBytes?: number
110
- /** Minimum file size in bytes. */
114
+ /** Minimum size in bytes allowed for a single file. */
111
115
  minSizeBytes?: number
112
- /** Allowed MIME types. */
116
+ /** White-list of allowed MIME types (e.g. `["image/png", "application/pdf"]`). */
113
117
  allowedTypes?: string[]
114
- /** Allowed file extensions. */
118
+ /** White-list of allowed file extension strings (e.g. `["png", "pdf"]`). */
115
119
  allowedExtensions?: string[]
116
120
  }
117
121
 
118
- // -------------------- Backend API --------------------
119
-
120
- /** Common options object for backend calls. */
121
- export type WithSignal = { signal?: AbortSignal }
122
-
123
- /** Backend adapter the engine calls into. */
124
- export interface IUploadApi<M extends IntentMap, P extends string, R extends IResultBase = IResultBase> {
125
- /**
126
- * Create an upload intent. The backend decides the strategy and any
127
- * presigned URLs / form fields.
128
- */
129
- createIntent(
130
- args: { purpose: P; contentType: string; size: number; filename: string; checksum?: string },
131
- opts?: WithSignal,
132
- ): Promise<M[keyof M]>
133
-
134
- /** Finalize an upload after all bytes are on the server. */
135
- complete(args: { fileId: string }, opts?: WithSignal): Promise<R>
136
-
137
- /** Optional helper to mint a signed preview URL for a finished upload. */
138
- getSignedPreviewUrl?(args: { fileId: string; key: string; purpose: P }, opts?: WithSignal): Promise<string>
139
-
140
- /** Optional checksum-based dedupe lookup. */
141
- findByChecksum?(args: { checksum: string; purpose: P }, opts?: WithSignal): Promise<R | null>
142
-
143
- /** Multipart-strategy operations. Required when using the multipart strategy. */
144
- multipart?: {
145
- /** Sign one part for upload. */
146
- signPart(
147
- args: { fileId: string; uploadId: string; partNumber: number; checksum?: string },
148
- opts?: WithSignal,
149
- ): Promise<{ url: string; headers?: Record<string, string> }>
150
-
151
- /** Tell the backend to assemble the parts into the final object. */
152
- completeMultipart(
153
- args: { fileId: string; uploadId: string; parts: Array<{ partNumber: number; etag: string }> },
154
- opts?: WithSignal,
155
- ): Promise<unknown>
156
-
157
- /** Optional: list already-uploaded parts (used by resume diagnostics). */
158
- listParts?(
159
- args: { fileId: string; uploadId: string },
160
- opts?: WithSignal,
161
- ): Promise<Array<{ partNumber: number; etag?: string; size?: number }>>
162
-
163
- /** Optional: abort an in-progress multipart session on the backend. */
164
- abort?(args: { fileId: string; uploadId: string }, opts?: WithSignal): Promise<void>
122
+ /**
123
+ * Safe, cause-stripped error summary stored in step history and forwarded in API context.
124
+ * The raw `cause` field is omitted because it may contain sensitive server response bodies.
125
+ */
126
+ export type StepError = {
127
+ code: string
128
+ message: string
129
+ retryable?: boolean
130
+ }
131
+
132
+ /** A single recorded phase transition on an upload item. */
133
+ export type UploadStep = {
134
+ /** Phase name when this step was entered. */
135
+ phase: string
136
+ /** Epoch ms when this phase was entered. */
137
+ enteredAt: number
138
+ /** Epoch ms when this phase was left. Undefined while still in this phase. */
139
+ leftAt?: number
140
+ /** Attempt number at the time of entering this phase. */
141
+ attempt: number
142
+ /** Safe error summary if this phase ended in failure. Cause is stripped. */
143
+ error?: StepError | undefined
144
+ }
145
+
146
+ export namespace Api {
147
+ /** Context passed to every backend API method alongside the call-specific args. */
148
+ export type Context<P extends string = string, M extends Intent.Map = Intent.Map> = {
149
+ /** Client-side identifier for this file item. */
150
+ localId: string
151
+ /** Upload purpose. */
152
+ purpose: P
153
+ /** File identity: name, size, MIME type, checksum. Always present. */
154
+ fingerprint: FingerprintFile
155
+ /** Which attempt this is, starting at 1. Increments on retry. */
156
+ attempt: number
157
+ /** Epoch ms when this item was registered. */
158
+ createdAt: number
159
+ /** Phase history up to this call. Errors have cause stripped. */
160
+ steps: readonly UploadStep[]
161
+ /** Caller-supplied metadata from the addFiles command. Never persisted. */
162
+ meta: Record<string, unknown>
163
+ /** Safe error from the most recent failed step, if any. */
164
+ lastError?: StepError | undefined
165
+ /** Browser File object. Present in most phases; absent when file is not loaded. */
166
+ file?: File | undefined
167
+ /** Upload intent from the backend. Present after createIntent succeeds. */
168
+ intent?: Intent.Any<M> | undefined
169
+ /** Cancellation signal. */
170
+ signal?: AbortSignal | undefined
165
171
  }
166
172
 
167
- /** TUS-strategy operations. */
168
- tus?: {
169
- /** Create a TUS upload session, returning its URL. */
170
- create(
171
- args: { fileId: string; size: number; filename: string; contentType: string },
172
- opts?: WithSignal,
173
- ): Promise<{ uploadUrl: string }>
173
+ /** Context for API calls where intent is not yet created but file is loaded (e.g. createIntent) */
174
+ export type CreateIntentContext<P extends string, M extends Intent.Map> = Context<P, M> &
175
+ Required<Pick<Context<P, M>, 'file' | 'signal'>>
176
+
177
+ /** Context for API calls verifying file checksum (e.g. findByChecksum) */
178
+ export type FindByChecksumContext<P extends string, M extends Intent.Map> = Context<P, M> &
179
+ Required<Pick<Context<P, M>, 'file'>>
180
+
181
+ /** Context for API calls executing during/after upload where intent is active (e.g. complete, signPart, completeMultipart) */
182
+ export type CompleteContext<P extends string, M extends Intent.Map> = Context<P, M> &
183
+ Required<Pick<Context<P, M>, 'file' | 'intent' | 'signal'>>
184
+
185
+ /** Context for API abort calls where intent is active but file/signal might be absent (e.g. abort) */
186
+ export type AbortContext<P extends string, M extends Intent.Map> = Context<P, M> &
187
+ Required<Pick<Context<P, M>, 'intent'>>
188
+
189
+ /** Contract definition for your backend API endpoints. */
190
+ export type Me<M extends Intent.Map, P extends string, R extends Result.Base = Result.Base> = {
191
+ /** Requests an upload intent from the server. */
192
+ createIntent(
193
+ args: {
194
+ purpose: P
195
+ contentType: string
196
+ size: number
197
+ filename: string
198
+ checksum?: string
199
+ attempt: number
200
+ },
201
+ ctx: CreateIntentContext<P, M>,
202
+ ...extra: any[]
203
+ ): Promise<M[keyof M]>
204
+
205
+ /** Finalizes the upload on the server after bytes have been transferred. */
206
+ complete(
207
+ args: {
208
+ fileId: string
209
+ /** Sanitized filename. Do not use the raw File.name from the browser. */
210
+ filename: string
211
+ contentType: string
212
+ size: number
213
+ checksum?: string
214
+ attempt: number
215
+ },
216
+ ctx: CompleteContext<P, M>,
217
+ ...extra: any[]
218
+ ): Promise<R>
219
+
220
+ /** Optional endpoint to fetch a signed preview/access URL for completed resources. */
221
+ getSignedPreviewUrl?(
222
+ args: { fileId: string; key: string; purpose: P },
223
+ ctx: Context<P, M>,
224
+ ...extra: any[]
225
+ ): Promise<string>
226
+
227
+ /** Optional endpoint to verify if the file has already been uploaded previously. */
228
+ findByChecksum?(
229
+ args: { checksum: string; purpose: P },
230
+ ctx: FindByChecksumContext<P, M>,
231
+ ...extra: any[]
232
+ ): Promise<R | null>
233
+
234
+ /** Multipart-specific backend commands. Required if using the multipart strategy. */
235
+ multipart?: {
236
+ signPart(
237
+ args: {
238
+ fileId: string
239
+ uploadId: string
240
+ partNumber: number
241
+ checksum?: string
242
+ attempt: number
243
+ },
244
+ ctx: CompleteContext<P, M>,
245
+ ...extra: any[]
246
+ ): Promise<{
247
+ url: string
248
+ headers?: Record<string, string>
249
+ }>
250
+
251
+ completeMultipart(
252
+ args: {
253
+ fileId: string
254
+ uploadId: string
255
+ parts: Array<{ partNumber: number; etag: string }>
256
+ },
257
+ ctx: CompleteContext<P, M>,
258
+ ...extra: any[]
259
+ ): Promise<unknown>
260
+
261
+ listParts?(
262
+ args: { fileId: string; uploadId: string },
263
+ ctx: CompleteContext<P, M>,
264
+ ...extra: any[]
265
+ ): Promise<Array<{ partNumber: number; etag?: string; size?: number }>>
266
+
267
+ abort?(args: { fileId: string; uploadId: string }, ctx: AbortContext<P, M>, ...extra: any[]): Promise<void>
268
+ }
269
+
270
+ /** TUS resumable protocol operations. */
271
+ tus?: {
272
+ create(
273
+ args: { fileId: string; size: number; filename: string; contentType: string; attempt: number },
274
+ ctx: CompleteContext<P, M>,
275
+ ...extra: any[]
276
+ ): Promise<{ uploadUrl: string }>
277
+
278
+ getOffset(args: { uploadUrl: string }, ctx: CompleteContext<P, M>, ...extra: any[]): Promise<{ offset: number }>
279
+ }
280
+ }
281
+ }
282
+
283
+ export namespace Strategy {
284
+ /** Per-call context handed to a strategy's `start()` method. */
285
+ export type Ctx<
286
+ M extends Intent.Map,
287
+ C extends Cursor.Map<M>,
288
+ P extends string,
289
+ R extends Result.Base,
290
+ K extends keyof M & string,
291
+ > = {
292
+ localId: string
293
+ purpose: P
294
+ file: File
295
+ fingerprint: FingerprintFile
296
+ intent: M[K]
297
+ signal: AbortSignal
298
+ attempt: number
299
+ createdAt: number
300
+ steps: readonly UploadStep[]
301
+ lastError?: StepError | undefined
302
+ meta: Record<string, unknown>
303
+ transport: {
304
+ put(args: {
305
+ url: string
306
+ body: Blob
307
+ headers?: Record<string, string>
308
+ signal: AbortSignal
309
+ onProgress?: (u: number, t: number) => void
310
+ }): Promise<{ etag?: string | undefined; headers?: Record<string, string> }>
311
+
312
+ postForm(args: {
313
+ url: string
314
+ fields: Record<string, string>
315
+ file: File | Blob
316
+ filename?: string
317
+ signal: AbortSignal
318
+ onProgress?: (uploadedBytes: number, totalBytes: number) => void
319
+ }): Promise<{ etag?: string; headers?: Record<string, string> }>
320
+
321
+ patch(args: {
322
+ url: string
323
+ body: Blob | ArrayBuffer
324
+ headers?: Record<string, string>
325
+ signal: AbortSignal
326
+ onProgress?: (u: number, t: number) => void
327
+ }): Promise<{ headers?: Record<string, string> }>
328
+
329
+ get?(args: {
330
+ url: string
331
+ headers?: Record<string, string>
332
+ signal: AbortSignal
333
+ onProgress?: (loaded: number, total: number) => void
334
+ }): Promise<{ blob: Blob; status: number; headers?: Record<string, string> }>
335
+
336
+ head?(args: {
337
+ url: string
338
+ headers?: Record<string, string>
339
+ signal: AbortSignal
340
+ }): Promise<{ status: number; headers: Record<string, string> }>
341
+ }
342
+ api: Api.Me<M, P, R>
343
+ reportProgress: (p: { uploadedBytes: number; totalBytes: number }) => void
344
+ readCursor: () => C[K] | undefined
345
+ persistCursor: (cursor: C[K] | undefined) => void
346
+ }
347
+
348
+ /** A single upload strategy implementation. */
349
+ export type Me<
350
+ M extends Intent.Map,
351
+ C extends Cursor.Map<M>,
352
+ P extends string,
353
+ R extends Result.Base,
354
+ K extends keyof M & string,
355
+ > = {
356
+ id: K
357
+ resumable: boolean
358
+ start(ctx: Ctx<M, C, P, R, K>): Promise<void>
359
+ }
174
360
 
175
- /** Read the current TUS upload offset (used for resume). */
176
- getOffset(args: { uploadUrl: string }, opts?: WithSignal): Promise<{ offset: number }>
361
+ /** Registry of available upload strategies, keyed by strategy id. */
362
+ export type Registry<
363
+ M extends Intent.Map,
364
+ C extends Cursor.Map<M>,
365
+ P extends string = string,
366
+ R extends Result.Base = Result.Base,
367
+ > = {
368
+ get<K extends keyof M & string>(id: K): Me<M, C, P, R, K> | undefined
369
+ has(id: string): id is keyof M & string
370
+ set<K extends keyof M & string>(strategy: Me<M, C, P, R, K>): void
177
371
  }
178
372
  }
179
373
  }
@@ -1,8 +1,3 @@
1
- export * from './backendApi'
2
- export * from './cursor.types'
3
- export * from './errors.types'
4
- export * from './fingerprint.types'
5
- export * from './intent.types'
6
- export * from './result.types'
7
- export * from './strategy.types'
8
- export * from './transport'
1
+ export type { Contracts } from './contracts.types'
2
+ export type { Transport } from './transport'
3
+ export { createFetchTransport, createXHRTransport } from './transport'
@@ -1,2 +1,3 @@
1
- export * from './transport'
2
- export * from './transport.types'
1
+ export { createXHRTransport } from './transport'
2
+ export { createFetchTransport } from './transport.fetch'
3
+ export type { Transport } from './transport.types'