@gentleduck/upload 0.2.4 → 0.2.6

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 (42) hide show
  1. package/README.md +320 -0
  2. package/dist/core/index.d.ts +1 -1
  3. package/dist/core/index.js +1 -1
  4. package/dist/{core-BNxznRif.js → core-CMkrP9DX.js} +2 -2
  5. package/dist/{index-BXg9WIce.d.ts → index-C38Jmizu.d.ts} +51 -33
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/react/index.d.ts +8 -47
  9. package/dist/react/index.js +1 -1
  10. package/dist/strategies/index.d.ts +5 -5
  11. package/package.json +1 -1
  12. package/src/__test__/effect-pool.test.ts +5 -0
  13. package/src/__test__/emitter.test.ts +85 -0
  14. package/src/__test__/fingerprint.test.ts +49 -0
  15. package/src/__test__/guards.test.ts +30 -0
  16. package/src/__test__/id.test.ts +14 -0
  17. package/src/__test__/persistence.test.ts +156 -0
  18. package/src/__test__/store-integration.test.ts +5 -0
  19. package/src/__test__/store-libs.test.ts +138 -0
  20. package/src/__test__/transport-libs.test.ts +42 -0
  21. package/src/__test__/transport.test.ts +182 -0
  22. package/src/core/client/client.ts +50 -0
  23. package/src/core/client/client.types.ts +184 -0
  24. package/src/core/contracts/contracts.types.ts +179 -0
  25. package/src/core/contracts/transport/transport.libs.ts +7 -32
  26. package/src/core/contracts/transport/transport.ts +5 -7
  27. package/src/core/engine/engine.types.ts +364 -0
  28. package/src/core/engine/internal-events.types.ts +3 -23
  29. package/src/core/engine/reducer.ts +5 -11
  30. package/src/core/engine/store/handlers/add-file.ts +6 -21
  31. package/src/core/engine/store/handlers/run-upload.ts +7 -7
  32. package/src/core/engine/store/handlers.ts +819 -0
  33. package/src/core/engine/store/store.libs.ts +17 -57
  34. package/src/core/engine/store/store.runtime.ts +10 -20
  35. package/src/core/engine/validation/file.ts +1 -1
  36. package/src/core/engine/validation.ts +216 -0
  37. package/src/core/utils/emitter/emitter.ts +143 -0
  38. package/src/react/upload-provider.tsx +7 -58
  39. package/src/react/uploader.types.ts +83 -0
  40. package/src/react/use-uploader.tsx +2 -5
  41. package/src/strategies/multipart/index.ts +9 -12
  42. package/src/strategies/post/index.ts +6 -46
package/README.md ADDED
@@ -0,0 +1,320 @@
1
+ <p align="center">
2
+ <img src="../../public/logo-dark.svg" alt="@gentleduck/upload" width="120"/>
3
+ </p>
4
+
5
+ <h1 align="center">@gentleduck/upload</h1>
6
+
7
+ <p align="center">
8
+ Headless, framework-agnostic file-upload engine with a typed state machine, pluggable strategies, and React bindings.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="../../LICENSE">MIT</a> -
13
+ <a href="../../CHANGELOG.md">Changelog</a> -
14
+ <a href="../../CONTRIBUTING.md">Contributing</a> -
15
+ <a href="https://gentleduck.org/duck-upload">Docs</a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://www.npmjs.com/package/@gentleduck/upload"><img src="https://img.shields.io/npm/v/@gentleduck/upload.svg" alt="npm"/></a>
20
+ <a href="https://www.npmjs.com/package/@gentleduck/upload"><img src="https://img.shields.io/npm/dm/@gentleduck/upload.svg" alt="downloads"/></a>
21
+ <a href="../../LICENSE"><img src="https://img.shields.io/npm/l/@gentleduck/upload.svg" alt="MIT"/></a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ Headless, framework-agnostic file-upload engine with a typed state machine, pluggable strategies, and React bindings.
27
+
28
+ Pure-reducer core, persistence-aware, resume-after-refresh, bounded effect concurrency, and zero coupling to a specific backend or UI library.
29
+
30
+ ## Quick Start
31
+
32
+ ```ts
33
+ import { createUploadStore } from '@gentleduck/upload/core'
34
+ import { createStrategyRegistry } from '@gentleduck/upload/strategies'
35
+
36
+ type Intents = { post: { strategy: 'post'; fileId: string; url: string; fields: Record<string, string> } }
37
+ type Cursors = { post: { offset: number } }
38
+ type Purpose = 'attachment'
39
+ type Result = { fileId: string; key: string }
40
+
41
+ const strategies = createStrategyRegistry<Intents, Cursors, Purpose, Result>()
42
+ strategies.set({
43
+ id: 'post',
44
+ resumable: false,
45
+ async start({ file, intent, transport, signal, reportProgress }) {
46
+ // your upload logic (PUT/POST/multipart/tus)
47
+ reportProgress({ uploadedBytes: file.size, totalBytes: file.size })
48
+ },
49
+ })
50
+
51
+ const store = createUploadStore<Intents, Cursors, Purpose, Result>({
52
+ strategies,
53
+ api: {
54
+ async createIntent({ filename }) {
55
+ return { strategy: 'post', fileId: filename, url: '/sign', fields: {} }
56
+ },
57
+ async complete({ fileId }) {
58
+ return { fileId, key: `attachments/${fileId}` }
59
+ },
60
+ },
61
+ config: { maxConcurrentUploads: 3, autoStart: ['attachment'] },
62
+ })
63
+
64
+ store.dispatch({ type: 'addFiles', files, purpose: 'attachment' })
65
+ ```
66
+
67
+ ## React
68
+
69
+ ```tsx
70
+ import { UploadProvider, useUploader, createUploadFactory } from '@gentleduck/upload/react'
71
+
72
+ const useUploads = createUploadFactory(store)
73
+
74
+ function FileList() {
75
+ const { items, uploading, dispatch } = useUploads()
76
+ return (
77
+ <UploadProvider store={store}>
78
+ {items.map(item => (
79
+ <Row key={item.localId} item={item} onCancel={() => dispatch({ type: 'cancel', localId: item.localId })} />
80
+ ))}
81
+ </UploadProvider>
82
+ )
83
+ }
84
+ ```
85
+
86
+ A full working demo lives at `packages/registry-examples/src/upload/upload-1.tsx`.
87
+
88
+ ## Features
89
+
90
+ - **Pure reducer** - command/event-driven state machine; effects are isolated
91
+ - **Typed end-to-end** - intents, cursors, purposes, and results are quad-generics threaded through the engine
92
+ - **Strategy registry** - ship POST/multipart out of the box, add TUS or your own
93
+ - **Resume after refresh** - IndexedDB / LocalStorage / Memory persistence adapters; cursor-aware re-binding
94
+ - **Retry policy** - exponential backoff with attempt escalation per phase (intent / upload / complete)
95
+ - **Bounded effect concurrency** - `effectConcurrency` separates side-effect parallelism from upload-byte parallelism
96
+ - **Batched bulk dispatch** - `startAll` / `pauseAll` / `cancelAll` collapse into a single reducer pass and notification
97
+ - **Rebind** - rebind a paused, persisted item to a fresh `File` after refresh, with fingerprint validation
98
+ - **Plugins + hooks** - `onInternalEvent`, `onPersistenceError`, custom plugins
99
+ - **No DOM coupling** - works in Workers, Node 22+, or the browser
100
+
101
+ ## Subpath Exports
102
+
103
+ ```ts
104
+ import { createUploadStore } from '@gentleduck/upload/core'
105
+ import { UploadProvider, useUploader } from '@gentleduck/upload/react'
106
+ import { createStrategyRegistry } from '@gentleduck/upload/strategies'
107
+ ```
108
+
109
+ ## Persistence
110
+
111
+ ```ts
112
+ import { createIndexedDBAdapter } from '@gentleduck/upload/core'
113
+
114
+ createUploadStore({
115
+ persistence: {
116
+ key: 'app:uploads',
117
+ version: 1,
118
+ adapter: createIndexedDBAdapter(),
119
+ isPurpose,
120
+ isIntent,
121
+ },
122
+ hooks: { onPersistenceError: (err) => toast.error(err.message) },
123
+ ...
124
+ })
125
+ ```
126
+
127
+ Use `createIndexedDBAdapter()` / `createMemoryAdapter()` factories so each store owns its own connection.
128
+
129
+ Adapters throw a typed `PersistenceError` on failure (`quota_exceeded`, `unavailable`, `serialization_failed`, `transaction_failed`, `unknown`) that is routed to the optional `onPersistenceError` hook.
130
+
131
+ ### Cross-tab IndexedDB contract
132
+
133
+ When a second tab opens the same database at a higher `persistence.version`, the existing tab's connection receives `onversionchange` and closes. The adapter resets its memoized handle so the next call reopens -- but at the OLD version. IndexedDB rejects that with `VersionError`, which the adapter surfaces as `PersistenceError('unavailable', operation, ...)`.
134
+
135
+ Application policy:
136
+
137
+ - Bump `persistence.version` in every tab/deployment in lockstep.
138
+ - Treat `onPersistenceError({ code: 'unavailable' })` after a known multi-tab scenario as a signal to reload the tab. The adapter is fixed at construction; the store does not support runtime adapter swap. If you want graceful fallback to in-memory persistence, the consumer must tear the store down and reconstruct with `createMemoryAdapter()`.
139
+ - Snapshot version mismatches at load time also surface as `PersistenceError('unavailable')` (via `deserializeSnapshot`'s `expectedVersion` fence).
140
+
141
+ ### Handling persistence errors
142
+
143
+ ```ts
144
+ import { createIndexedDBAdapter, type PersistenceError } from '@gentleduck/upload/core'
145
+
146
+ createUploadStore({
147
+ persistence: { key: 'app:uploads', version: 1, adapter: createIndexedDBAdapter(), isPurpose, isIntent },
148
+ hooks: {
149
+ onPersistenceError(err: PersistenceError) {
150
+ switch (err.code) {
151
+ case 'quota_exceeded':
152
+ toast.error('Storage full -- drop some uploads and try again.')
153
+ break
154
+ case 'unavailable':
155
+ // Private mode / SecurityError / no IndexedDB. Fall back to memory.
156
+ telemetry.warn('upload.persistence.unavailable', { op: err.operation })
157
+ break
158
+ case 'serialization_failed':
159
+ // Snapshot was tampered with; drop the bad key and start over.
160
+ adapter.clear('app:uploads').catch(() => undefined)
161
+ break
162
+ case 'transaction_failed':
163
+ case 'unknown':
164
+ telemetry.error('upload.persistence.error', { op: err.operation, message: err.message })
165
+ break
166
+ }
167
+ },
168
+ },
169
+ ...
170
+ })
171
+ ```
172
+
173
+ The engine never aborts uploads on a persistence error -- it continues operating in-memory. Use the hook to surface the failure to the user or to swap adapters at runtime.
174
+
175
+ The hook may be sync or `async`; the engine tracks the returned promise and keeps the re-entrancy guard active until it settles, so dispatches issued inside an async hook cannot loop the failure path.
176
+
177
+ ## Effect concurrency and watchdog
178
+
179
+ Side-effects (intent creation, finalize, checksum, retry sleep, multipart abort) run in a bounded worker pool:
180
+
181
+ ```ts
182
+ createUploadStore({
183
+ config: {
184
+ effectConcurrency: 8, // default: 8. Set to 1 for strict-sequential.
185
+ effectTimeoutMs: 60_000, // default: 60s per effect. 0 disables the watchdog.
186
+ },
187
+ ...
188
+ })
189
+ ```
190
+
191
+ Every effect receives an `AbortSignal`. When the watchdog fires the signal is aborted so well-behaved effects can exit cleanly. Effects that ignore the signal keep running in the background; the slot is released either way so the pool keeps draining. Retry-sleep effects that get cut short by the watchdog still dispatch the retry as long as the item is still in `error` -- the watchdog cannot strand an item permanently.
192
+
193
+ ## Writing a custom strategy
194
+
195
+ A strategy is the thin layer that knows how to actually move bytes from a `File` to your storage using the intent shape your backend returns. Every strategy implements one `start()` and shares the same `Strategy.ICtx` contract:
196
+
197
+ ```ts
198
+ import type { Strategy } from '@gentleduck/upload/core'
199
+
200
+ type Intents = { tus: { strategy: 'tus'; fileId: string; uploadUrl: string } }
201
+ type Cursors = { tus: { offset: number } }
202
+ type Purpose = 'attachment'
203
+ type Result = { fileId: string; key: string }
204
+
205
+ export const tusStrategy: Strategy.IStrategy<Intents, Cursors, Purpose, Result, 'tus'> = {
206
+ id: 'tus',
207
+ resumable: true,
208
+ async start({ file, intent, signal, transport, readCursor, persistCursor, reportProgress }) {
209
+ // 1. Resume from the last persisted cursor if there is one.
210
+ let offset = readCursor()?.offset ?? 0
211
+
212
+ while (offset < file.size) {
213
+ if (signal.aborted) throw { code: 'aborted', reason: signal.reason }
214
+
215
+ const chunk = file.slice(offset, offset + 1024 * 1024)
216
+ const res = await transport.send({
217
+ url: intent.uploadUrl,
218
+ method: 'PATCH',
219
+ headers: {
220
+ 'Upload-Offset': String(offset),
221
+ 'Content-Type': 'application/offset+octet-stream',
222
+ },
223
+ body: chunk,
224
+ signal,
225
+ onProgress: ({ uploadedBytes }) => {
226
+ reportProgress({ uploadedBytes: offset + uploadedBytes, totalBytes: file.size })
227
+ },
228
+ })
229
+
230
+ offset += chunk.size
231
+
232
+ // 2. Persist resumable progress so a refresh can rebind + resume.
233
+ persistCursor({ offset })
234
+
235
+ if (!res.ok) throw new Error(`TUS PATCH failed: ${res.status}`)
236
+ }
237
+ },
238
+ }
239
+ ```
240
+
241
+ Contract:
242
+
243
+ - **`id`** must match the `intent.strategy` your backend returns.
244
+ - **`resumable`** is metadata for UIs; the engine doesn't enforce it.
245
+ - **`start()`** is awaited; resolve normally → engine transitions to `completing`. Throw → engine routes the error through `errorNormalizer` + `retryPolicy`.
246
+ - Honor **`signal`** -- abort cleanly when it fires (throw `{ code: 'aborted', reason: signal.reason }`).
247
+ - Call **`reportProgress`** as often as makes sense; it is throttled by `config.progressThrottleMs`.
248
+ - Call **`persistCursor`** at every safe resume boundary; the value is restored on the next `rebind` after refresh.
249
+ - Use **`api`** for backend round-trips that depend on the intent (e.g. multipart `signPart` / `completeMultipart`).
250
+
251
+ Register the strategy and reference it from `createIntent`:
252
+
253
+ ```ts
254
+ const strategies = createStrategyRegistry<Intents, Cursors, Purpose, Result>()
255
+ strategies.set(tusStrategy)
256
+ ```
257
+
258
+ ## Architecture
259
+
260
+ ```
261
+ @gentleduck/upload
262
+ ├── core/
263
+ │ ├── client/ - Client.IUploadConfig, Client.IUploadHooks, Client.IUploadPlugin
264
+ │ ├── contracts/ - intents, cursors, transport, errors, strategy
265
+ │ ├── engine/ - reducer + commands + internal events
266
+ │ │ └── store/ - runtime, dispatch, scheduler, handlers
267
+ │ ├── persistence/ - Memory / LocalStorage / IndexedDB adapters + serializer
268
+ │ └── utils/ - emitter, id, fingerprint, guards, async
269
+ ├── strategies/
270
+ │ ├── post/ - single-PUT/POST strategy
271
+ │ ├── multipart/ - S3-style multipart with partSize + ETag tracking
272
+ │ └── registry/ - createStrategyRegistry
273
+ └── react/ - UploadProvider, useUploader, createUploadFactory
274
+ ```
275
+
276
+ ## Commands
277
+
278
+ | Command | Purpose |
279
+ |---|---|
280
+ | `addFiles` | Validate + insert; schedules checksum + intent |
281
+ | `start` / `startAll` | Move ready items to queued (batched) |
282
+ | `pause` / `pauseAll` | Abort inflight, persist cursor (batched) |
283
+ | `resume` | Re-queue a paused item |
284
+ | `cancel` / `cancelAll` | Abort everything and mark canceled (batched) |
285
+ | `retry` | Re-attempt the failed phase, bumping the attempt counter |
286
+ | `rebind` | Re-attach a fresh `File` after refresh (validated by fingerprint) |
287
+ | `remove` | Drop the item from state |
288
+
289
+ ## Events
290
+
291
+ Subscribe via `store.on(type, cb)`. Highlights:
292
+
293
+ - `file.added`, `file.rejected`
294
+ - `validation.ok`, `validation.failed`
295
+ - `intent.creating`, `intent.created`, `intent.failed`
296
+ - `upload.queued`, `upload.started`, `upload.progress`, `upload.cursor`
297
+ - `upload.paused`, `upload.canceled`, `upload.completing`, `upload.completed`, `upload.error`
298
+ - `rebind.ok`, `rebind.failed`
299
+
300
+ ## Bundler requirement
301
+
302
+ The engine references `process.env.NODE_ENV` to gate dev-only invariant
303
+ warnings (reducer no-op check, listener-throw fallback logging, strategy-
304
+ overwrite warn). Vite / Webpack / Rspack / esbuild / Rollup replace this at
305
+ build time and ship a single branch into production. Pure-ESM consumers that
306
+ load the source directly without a bundler must polyfill
307
+ `globalThis.process = { env: { NODE_ENV: 'production' } }` before importing
308
+ the package, otherwise `process` is undefined at module load.
309
+
310
+ ## Tests
311
+
312
+ ```bash
313
+ bun run test
314
+ ```
315
+
316
+ Vitest suite covers the reducer state machine, the persistence layer, all utility modules, the rebind handler, and a full smoke upload through the React-less store API.
317
+
318
+ ## License
319
+
320
+ MIT
@@ -1,3 +1,3 @@
1
1
  import { a as AnyCursor, c as WithSignal, d as IntentBase, f as IntentMap, i as UploadTransport, l as UploadResultBase, n as StrategyRegistry, o as CursorMap, p as StrategyKey, r as UploadStrategy, s as UploadApi, t as StrategyCtx, u as AnyIntent } from "../strategy.types-gg9ZStRe.js";
2
- import { $ as RejectReason, A as serializeSnapshot, B as UploadItem, C as StoreOptions, D as createTypedEmitter, E as UploadEventMap, F as PersistedUploadItem, G as UploadCommand, H as UploadProgress, I as PersistenceAdapter, J as resolveUploadConfig, K as UploadConfig, L as UploadState, M as LocalStorageAdapter, N as IndexedDBAdapter, O as TypedEmitter, P as PersistedSnapshot, Q as BuiltInUploadError, R as createReducer, S as PersistenceOptions, T as UploadStore, U as UploadCompletionKind, V as UploadPhase, W as UploadOutcome, X as createXHRTransport, Y as RetryDecision, Z as FileFingerprint, _ as UploadHooks, a as DEFAULT_MAX_ATTEMPTS, b as DeserializeContext, c as DEFAULT_PROGRESS_THROTTLE_MS, d as sleep, et as UploadError, f as validateFileList, g as createUploadClient, h as UploadClient, i as fingerprintMatches, j as MemoryAdapter, k as deserializeSnapshot, l as DEFAULT_RETRY_DELAY_BASE_MS, m as validateFile, n as isRecord, nt as UploadValidationRules, o as DEFAULT_MAX_CONCURRENT_UPLOADS, p as validateIntent, q as UploadConfigInput, r as computeFingerprint, s as DEFAULT_MAX_ITEMS, t as generateId, tt as UploadErrorBase, u as DEFAULT_RETRY_DELAY_MAX_MS, v as UploadPlugin, w as StoreRuntime, x as InflightUpload, y as createUploadStore, z as InternalEvent } from "../index-BXg9WIce.js";
2
+ import { $ as RejectReason, A as serializeSnapshot, B as UploadItem, C as StoreOptions, D as createTypedEmitter, E as UploadEventMap, F as PersistedUploadItem, G as UploadCommand, H as UploadProgress, I as PersistenceAdapter, J as resolveUploadConfig, K as UploadConfig, L as UploadState, M as LocalStorageAdapter, N as IndexedDBAdapter, O as TypedEmitter, P as PersistedSnapshot, Q as BuiltInUploadError, R as createReducer, S as PersistenceOptions, T as UploadStore, U as UploadCompletionKind, V as UploadPhase, W as UploadOutcome, X as createXHRTransport, Y as RetryDecision, Z as FileFingerprint, _ as UploadHooks, a as DEFAULT_MAX_ATTEMPTS, b as DeserializeContext, c as DEFAULT_PROGRESS_THROTTLE_MS, d as sleep, et as UploadError, f as validateFile, g as createUploadClient, h as UploadClient, i as fingerprintMatches, j as MemoryAdapter, k as deserializeSnapshot, l as DEFAULT_RETRY_DELAY_BASE_MS, m as validateIntent, n as isRecord, nt as UploadValidationRules, o as DEFAULT_MAX_CONCURRENT_UPLOADS, p as validateFileList, q as UploadConfigInput, r as computeFingerprint, s as DEFAULT_MAX_ITEMS, t as generateId, tt as UploadErrorBase, u as DEFAULT_RETRY_DELAY_MAX_MS, v as UploadPlugin, w as StoreRuntime, x as InflightUpload, y as createUploadStore, z as InternalEvent } from "../index-C38Jmizu.js";
3
3
  export { AnyCursor, AnyIntent, BuiltInUploadError, CursorMap, DEFAULT_MAX_ATTEMPTS, DEFAULT_MAX_CONCURRENT_UPLOADS, DEFAULT_MAX_ITEMS, DEFAULT_PROGRESS_THROTTLE_MS, DEFAULT_RETRY_DELAY_BASE_MS, DEFAULT_RETRY_DELAY_MAX_MS, DeserializeContext, FileFingerprint, IndexedDBAdapter, InflightUpload, IntentBase, IntentMap, InternalEvent, LocalStorageAdapter, MemoryAdapter, PersistedSnapshot, PersistedUploadItem, PersistenceAdapter, PersistenceOptions, RejectReason, RetryDecision, StoreOptions, StoreRuntime, StrategyCtx, StrategyKey, StrategyRegistry, TypedEmitter, UploadApi, UploadClient, UploadCommand, UploadCompletionKind, UploadConfig, UploadConfigInput, UploadError, UploadErrorBase, UploadEventMap, UploadHooks, UploadItem, UploadOutcome, UploadPhase, UploadPlugin, UploadProgress, UploadResultBase, UploadState, UploadStore, UploadStrategy, UploadTransport, UploadValidationRules, WithSignal, computeFingerprint, createReducer, createTypedEmitter, createUploadClient, createUploadStore, createXHRTransport, deserializeSnapshot, fingerprintMatches, generateId, isRecord, resolveUploadConfig, serializeSnapshot, sleep, validateFile, validateFileList, validateIntent };
@@ -1 +1 @@
1
- import{C as e,S as t,_ as n,a as r,b as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"../core-BNxznRif.js";import{t as w}from"../async-DxRWu4le.js";export{C as DEFAULT_MAX_ATTEMPTS,i as DEFAULT_MAX_CONCURRENT_UPLOADS,S as DEFAULT_MAX_ITEMS,t as DEFAULT_PROGRESS_THROTTLE_MS,e as DEFAULT_RETRY_DELAY_BASE_MS,x as DEFAULT_RETRY_DELAY_MAX_MS,d as IndexedDBAdapter,a as LocalStorageAdapter,_ as MemoryAdapter,o as computeFingerprint,g as createReducer,u as createTypedEmitter,v as createUploadClient,p as createUploadStore,y as createXHRTransport,r as deserializeSnapshot,s as fingerprintMatches,n as generateId,l as isRecord,b as resolveUploadConfig,m as serializeSnapshot,w as sleep,c as validateFile,h as validateFileList,f as validateIntent};
1
+ import{C as e,S as t,_ as n,a as r,b as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"../core-CMkrP9DX.js";import{t as w}from"../async-DxRWu4le.js";export{C as DEFAULT_MAX_ATTEMPTS,i as DEFAULT_MAX_CONCURRENT_UPLOADS,S as DEFAULT_MAX_ITEMS,t as DEFAULT_PROGRESS_THROTTLE_MS,e as DEFAULT_RETRY_DELAY_BASE_MS,x as DEFAULT_RETRY_DELAY_MAX_MS,d as IndexedDBAdapter,a as LocalStorageAdapter,_ as MemoryAdapter,o as computeFingerprint,g as createReducer,u as createTypedEmitter,v as createUploadClient,p as createUploadStore,y as createXHRTransport,r as deserializeSnapshot,s as fingerprintMatches,n as generateId,c as isRecord,b as resolveUploadConfig,m as serializeSnapshot,w as sleep,h as validateFile,f as validateFileList,l as validateIntent};
@@ -1,2 +1,2 @@
1
- import{t as e}from"./async-DxRWu4le.js";const t=500,n=1e4,r=100,i=3,a=3,o=100;function s(e){return{maxConcurrentUploads:Math.max(1,e?.maxConcurrentUploads??3),autoStart:e?.autoStart,progressThrottleMs:Math.max(0,e?.progressThrottleMs??100),validation:e?.validation??{},maxAttempts:Math.max(1,e?.maxAttempts??3),retryPolicy:e?.retryPolicy,maxItems:Math.max(1,e?.maxItems??100),completedItemTTL:e?.completedItemTTL}}function c(){return`upload_${Date.now()}_${Math.random().toString(36).slice(2)}`}function l(e,t,n){let r=n.validation?.[t];if(!r)return null;if(e.size===0)return{code:`empty_file`};if(r.maxSizeBytes!==void 0&&e.size>r.maxSizeBytes)return{code:`file_too_large`,maxBytes:r.maxSizeBytes,size:e.size};if(r.minSizeBytes!==void 0&&e.size<r.minSizeBytes)return{code:`empty_file`};if(r.allowedTypes||r.allowedExtensions){let t=r.allowedTypes||[],n=r.allowedExtensions||[],i=e.name.split(`.`).pop()?.toLowerCase(),a=(e.type||``).toLowerCase(),o=t.length===0?!1:t.some(e=>{if(e.endsWith(`/*`)){let t=e.slice(0,-2).toLowerCase();return a.startsWith(`${t}/`)}return a===e.toLowerCase()}),s=i&&n.length>0?n.some(e=>e.toLowerCase()===i):!1,c=t.length>0,l=n.length>0;if(!(c&&l?o||s:(!c||o)&&(!l||s)))return{code:`type_not_allowed`,allowed:[...t,...n],got:e.type||e.name}}return null}function u(e){return!!e&&typeof e==`object`}function d(e,t){if(!u(e))return Error(`Invalid intent: must be an object`);if(typeof e.strategy!=`string`)return Error(`Invalid intent: missing or invalid strategy field`);if(e.strategy!==t)return Error(`Invalid intent: strategy mismatch (expected ${t}, got ${e.strategy})`);if(typeof e.fileId!=`string`||!e.fileId)return Error(`Invalid intent: missing or invalid fileId`);if(t===`post`){if(typeof e.url!=`string`||!e.url)return Error(`Invalid post intent: missing or invalid url`);try{let t=new URL(e.url);if(![`http:`,`https:`].includes(t.protocol))return Error(`Invalid post intent: url must use http or https protocol`)}catch{return Error(`Invalid post intent: url is not a valid URL`)}if(!e.fields||typeof e.fields!=`object`)return Error(`Invalid post intent: missing or invalid fields`)}else if(t===`multipart`){if(typeof e.uploadId!=`string`||!e.uploadId)return Error(`Invalid multipart intent: missing or invalid uploadId`);if(typeof e.partSize!=`number`||e.partSize<=0)return Error(`Invalid multipart intent: missing or invalid partSize`);if(`parts`in e&&e.parts!==void 0&&!Array.isArray(e.parts))return Error(`Invalid multipart intent: parts must be an array if provided`)}return null}function f(e,t,n,r=0){let i=n.validation?.[t],a=[],o=[],s=i?.maxFiles,c=s===void 0?1/0:Math.max(0,s-r);for(let r of e){if(c<=0){let e=s??0;o.push({file:r,reason:{code:`too_many_files`,max:e}});continue}let e=l(r,t,n);if(e){o.push({file:r,reason:e});continue}a.push(r),--c}return{valid:a,rejected:o}}function p(e){return{name:e.name,size:e.size,type:e.type,lastModified:e.lastModified}}function m(e,t){return e.name===t.name&&e.size===t.size&&e.lastModified===t.lastModified}async function h(e){let t=await e.arrayBuffer(),n=await crypto.subtle.digest(`SHA-256`,t);return Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,`0`)).join(``)}function g(e,t){let n=e.config?.autoStart;return n===void 0?!1:Array.isArray(n)?n.includes(t):typeof n==`function`?n(t):!1}function _(e,t){if(t)return t(e);if(x(e))return{code:`aborted`,message:`Upload aborted`,reason:String(e.reason??`unknown`),retryable:!1};let n=u(e)&&typeof e.message==`string`?e.message:`Unknown error`;if(String(n).toLowerCase().includes(`network`)||String(n).toLowerCase().includes(`fetch`))return{code:`network`,message:String(n),cause:e,retryable:!0};if(u(e)&&(typeof e.status==`number`||typeof e.statusCode==`number`)){let t=typeof e.status==`number`?e.status:e.statusCode,r=t>=500||t===429;return{code:`http`,status:t,message:String(n),cause:e,retryable:r}}return{code:`unknown`,message:String(n),cause:e,retryable:!1}}function v(e){return`intent`in e&&!!e.intent}function y(e){return`file`in e&&!!e.file}function b(e){return`cursor`in e}function ee(e){return u(e)&&e.strategy===`multipart`&&typeof e.fileId==`string`&&typeof e.uploadId==`string`}function x(e){return u(e)&&e.code===`aborted`}function S(e,t){if(e.retryPolicy)return e.retryPolicy(t);if(t.error.code===`auth`||t.error.code===`validation_failed`||t.error.code===`strategy_missing`||t.error.code===`aborted`)return{retryable:!1};let r=e.maxAttempts;return t.attempt<r?{retryable:!0,delayMs:Math.min(n,500*2**(t.attempt-1))}:{retryable:!1}}function C(e,t,n){let r=Array.from(e.state.items.values()).filter(e=>e.purpose===n&&e.phase!==`canceled`).length,{valid:i,rejected:a}=f(t,n,e.opts.config,r);for(let{file:t,reason:n}of a)e.emitter.emit(`file.rejected`,{reason:n,file:t});let o=Date.now(),s=[];for(let t of i){let r=c(),i=(e.opts.fingerprint??p)(t);s.push({localId:r,purpose:n,file:t,fingerprint:i,createdAt:o}),e.enqueueEffect(async()=>{let a;try{a=await h(t);let n={...i,checksum:a};e.applyInternal({type:`fingerprint.updated`,localId:r,fingerprint:n})}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[UploadEngine] Failed to calculate checksum:`,e)}if(a&&e.opts.api.findByChecksum)try{let t=await e.opts.api.findByChecksum({checksum:a,purpose:n});if(t){let n=e.state.items.get(r);if(n&&n.phase===`validating`){e.applyInternal({type:`dedupe.ok`,localId:r,result:t});return}}}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[UploadEngine] Failed to check for existing file:`,e)}let o=e.opts.validateFile?.(t,n);if(o){e.applyInternal({type:`validation.failed`,localId:r,reason:o});return}let s=l(t,n,e.opts.config);if(s){e.applyInternal({type:`validation.failed`,localId:r,reason:s});return}e.applyInternal({type:`validation.ok`,localId:r})})}s.length>0&&e.applyInternal({type:`files.added`,items:s})}function w(e,t){let n=e.state.items.get(t),r=e.inflightIntents.get(t);r&&(r.abort({reason:`cancel`}),e.inflightIntents.delete(t));let i=e.inflightCompletes.get(t);i&&(i.abort({reason:`cancel`}),e.inflightCompletes.delete(t));let a=e.inflightUploads.get(t);if(a&&(a.mode=`cancel`,a.controller.abort({reason:`cancel`})),n&&v(n)&&n.intent.strategy===`multipart`&&e.opts.api.multipart?.abort){let t=n.intent;ee(t)&&e.enqueueEffect(async()=>{try{let n=e.opts.api.multipart?.abort;n&&await n({fileId:t.fileId,uploadId:t.uploadId})}catch{}})}}function T(e,t){let n=e.inflightUploads.get(t);if(n){n.mode=`pause`,n.controller.abort({reason:`pause`});return}}function E(e,t){if(t.type===`startAll`){let n=t.purpose;Array.from(e.state.items.values()).filter(e=>e.phase===`ready`&&(!n||e.purpose===n)).forEach(t=>{e.dispatch({type:`start`,localId:t.localId})});return}if(t.type===`pauseAll`){let n=t.purpose;Array.from(e.state.items.values()).filter(e=>e.phase===`uploading`&&(!n||e.purpose===n)).forEach(t=>{e.dispatch({type:`pause`,localId:t.localId})});return}if(t.type===`cancelAll`){let n=t.purpose;Array.from(e.state.items.values()).filter(e=>e.phase!==`completed`&&e.phase!==`canceled`&&(!n||e.purpose===n)).forEach(t=>{e.dispatch({type:`cancel`,localId:t.localId})});return}if(t.type===`addFiles`){C(e,t.files,t.purpose);return}if(t.type===`pause`){T(e,t.localId),e.applyCommand(t);return}if(t.type===`cancel`){w(e,t.localId),e.applyCommand(t);return}if(t.type===`start`){e.applyCommand(t);return}if(t.type===`resume`){e.applyCommand(t);return}e.applyCommand(t)}function D(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function O(e){return e?.reason}function te(e){if(!e)return`unknown`;if(typeof e==`string`)return e===`pause`||e===`cancel`?e:`unknown`;if(u(e)&&`reason`in e){let t=e.reason;if(t===`pause`||t===`cancel`)return t}if(u(e)&&`kind`in e){let t=e.kind;if(t===`pause`||t===`cancel`)return t}return`unknown`}var k=class extends Error{constructor(e){super(`Upload aborted`),D(this,`code`,`aborted`),D(this,`reason`,void 0),this.reason=te(e)}};function A(e){return new k(e)}function ne(e,t){return e.status===0?Error(`CORS error: The upload server does not allow requests from this origin. Please check CORS configuration.`):Error(`${t} (status: ${e.status})`)}function re(e){let t={};return e.getAllResponseHeaders().split(`\r
2
- `).map(e=>e.trim()).filter(Boolean).forEach(e=>{let n=e.indexOf(`:`);if(n<=0)return;let r=e.slice(0,n).trim().toLowerCase(),i=e.slice(n+1).trim();r&&(t[r]=i)}),t}function ie(e){return/^https?:\/\//i.test(e)}function ae(e){let t=e.getResponseHeader(`ETag`)??e.getResponseHeader(`etag`);if(t)return t.replace(/"/g,``)}function j(e){return new Promise((t,n)=>{if(!e.url||typeof e.url!=`string`){n(Error(`UploadTransport: missing url`));return}if(!ie(e.url)){n(Error(`UploadTransport: expected absolute URL, got: ${String(e.url)}`));return}let r=new XMLHttpRequest,i=!1,a=e=>{i||(i=!0,s(),e())},o=()=>{try{r.abort()}finally{a(()=>n(A(O(e.signal))))}},s=()=>{e.signal&&e.signal.removeEventListener(`abort`,o),r.upload.onprogress=null,r.onload=null,r.onerror=null,r.onabort=null};if(e.signal?.aborted){a(()=>n(A(O(e.signal))));return}if(e.signal&&e.signal.addEventListener(`abort`,o,{once:!0}),e.onProgress&&(r.upload.onprogress=t=>{t.lengthComputable&&e.onProgress?.(t.loaded,t.total)}),r.onload=()=>{let e=r.status;a(e>=200&&e<300?()=>t({headers:re(r),etag:ae(r)}):()=>n(Error(`Upload failed with status ${e}`)))},r.onerror=()=>a(()=>n(ne(r,`Network error during upload`))),r.onabort=()=>a(()=>n(A(e.signal?.aborted?O(e.signal):`unknown`))),r.open(e.method,e.url),e.headers)for(let[t,n]of Object.entries(e.headers))r.setRequestHeader(t,n);r.send(e.body)})}function M(){return{async postForm(e){let t=new FormData;for(let[n,r]of Object.entries(e.fields))t.append(n,r);return t.append(`file`,e.file,e.filename??`file`),{headers:(await j({method:`POST`,url:e.url,body:t,signal:e.signal,onProgress:e.onProgress})).headers}},async put(e){let t=await j({method:`PUT`,url:e.url,body:e.body,signal:e.signal,headers:e.headers,onProgress:e.onProgress});return{headers:t.headers,etag:t.etag}},async patch(e){return{headers:(await j({method:`PATCH`,url:e.url,body:e.body,signal:e.signal,headers:e.headers,onProgress:e.onProgress})).headers}}}}const N=`snapshots`;function P(){return new Promise((e,t)=>{let n=indexedDB.open(`upload-engine`,1);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(N)||e.createObjectStore(N)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}function F(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error)})}function I(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onabort=()=>n(e.error??Error(`IndexedDB transaction aborted`)),e.onerror=()=>n(e.error??Error(`IndexedDB transaction error`))})}const L={async load(e){if(typeof indexedDB>`u`)return null;let t=(await P()).transaction(N,`readonly`),n=t.objectStore(N);try{let r=await F(n.get(e));return await I(t),r??null}catch{try{await I(t)}catch{}return null}},async save(e,t){if(typeof indexedDB>`u`)return;let n=(await P()).transaction(N,`readwrite`),r=n.objectStore(N);try{r.put(t,e),await I(n)}catch{try{n.abort()}catch{}}},async clear(e){if(typeof indexedDB>`u`)return;let t=(await P()).transaction(N,`readwrite`),n=t.objectStore(N);try{n.delete(e),await I(t)}catch{try{t.abort()}catch{}}}},R={load(e){if(typeof localStorage>`u`)return null;let t=localStorage.getItem(e);if(!t)return null;try{return JSON.parse(t)}catch{return null}},save(e,t){typeof localStorage>`u`||localStorage.setItem(e,JSON.stringify(t))},clear(e){typeof localStorage>`u`||localStorage.removeItem(e)}},z=new Map,B={load(e){return z.get(e)??null},save(e,t){z.set(e,t)},clear(e){z.delete(e)}};function V(e,t){let n={};for(let t of e.items.values()){if(!v(t)||t.phase===`completed`||t.phase===`canceled`||t.phase===`error`)continue;let e=b(t)?t.cursor:void 0,r=`progress`in t&&t.progress?{uploadedBytes:t.progress.uploadedBytes,totalBytes:t.progress.totalBytes,pct:t.progress.pct}:void 0,i={id:t.localId,purpose:t.purpose,status:t.phase,file:{name:t.fingerprint.name,size:t.fingerprint.size,type:t.fingerprint.type,lastModified:t.fingerprint.lastModified,checksum:t.fingerprint.checksum},intent:t.intent,cursor:e,progress:r};n[i.id]=i}return{version:t,createdAt:Date.now(),items:n}}function H(e,t){if(!t.isPurpose||!t.isIntent||!U(e))return null;let n=new Map;for(let r of Object.values(e.items)){let i=W(r);if(!i||!t.isPurpose(i.purpose)||!t.isIntent(i.intent))continue;let a=i.intent.strategy;if(typeof a!=`string`||!t.hasStrategy(a)||!i.cursor||!K(i.cursor,t.hasStrategy)||i.cursor.strategy!==a)continue;let o=i.progress?.totalBytes??i.file.size,s=i.progress?.uploadedBytes??0,c=typeof i.progress?.pct==`number`?i.progress.pct:o>0?Math.min(100,Math.max(0,s/o*100)):0;n.set(i.id,{phase:`paused`,localId:i.id,purpose:i.purpose,fingerprint:{name:i.file.name,size:i.file.size,type:i.file.type,lastModified:i.file.lastModified,checksum:i.file.checksum},intent:i.intent,cursor:i.cursor,progress:{uploadedBytes:s,totalBytes:o,pct:c},pausedAt:Date.now(),createdAt:e.createdAt??Date.now(),file:void 0})}return{items:n}}function U(e){return!u(e)||typeof e.version!=`number`||typeof e.createdAt!=`number`?!1:u(e.items)}function W(e){if(!u(e))return null;let t=typeof e.id==`string`?e.id:null,n=typeof e.purpose==`string`?e.purpose:null,r=typeof e.status==`string`?e.status:null,i=e.intent;if(!t||!n||!r||!u(e.file))return null;let a=typeof e.file.name==`string`?e.file.name:null,o=typeof e.file.size==`number`?e.file.size:null,s=typeof e.file.type==`string`?e.file.type:null,c=typeof e.file.lastModified==`number`?e.file.lastModified:null,l=typeof e.file.checksum==`string`?e.file.checksum:void 0;if(!a||o===null||!s||c===null)return null;let d=G(e.progress);return{id:t,purpose:n,status:r,file:{name:a,size:o,type:s,lastModified:c,checksum:l},intent:i,cursor:e.cursor,progress:d}}function G(e){if(!u(e)||typeof e.uploadedBytes!=`number`||typeof e.totalBytes!=`number`)return;let t=typeof e.pct==`number`?e.pct:void 0;return{uploadedBytes:e.uploadedBytes,totalBytes:e.totalBytes,pct:t}}function K(e,t){return!(!u(e)||typeof e.strategy!=`string`||!t(e.strategy))}function q(){let e={};return{on(t,n){let r=e[t];return r||(r=new Set,e[t]=r),r.add(n),()=>{let r=e[t];r&&(r.delete(n),r.size===0&&delete e[t])}},off(t,n){let r=e[t];return r?(r.delete(n),r.size===0&&delete e[t],()=>{}):()=>{}},emit(t,n){let r=e[t];r&&r.forEach(e=>{try{e(n)}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.error(`[UploadEngine] Error in event listener for "${t}":`,e)}})}}}function J(){return function(e,t){let n=new Map(e.items),r=(e,t)=>n.set(e,t),i=Date.now();if((e=>`type`in e&&typeof e.type==`string`&&!e.type.includes(`.`))(t)){switch(t.type){case`addFiles`:break;case`start`:{let e=n.get(t.localId);if(!e||e.phase!==`ready`)break;r(t.localId,{...e,phase:`queued`,requestedAt:i});break}case`resume`:{let e=n.get(t.localId);if(!e||e.phase!==`paused`||!e.file)break;r(t.localId,{...e,file:e.file,phase:`queued`,requestedAt:i});break}case`pause`:{let e=n.get(t.localId);if(!e)break;e.phase===`queued`&&r(t.localId,{...e,phase:`ready`});break}case`cancel`:{let e=n.get(t.localId);if(!e||e.phase===`completed`||e.phase===`canceled`)break;r(t.localId,Y(e,i));break}case`retry`:{let e=n.get(t.localId);if(!e||e.phase!==`error`||!e.retryable||!e.file)break;e.intent?e.progress&&e.progress.pct===100?r(t.localId,{...e,progress:e.progress,intent:e.intent,file:e.file,phase:`completing`,completingAt:i}):r(t.localId,{...e,file:e.file,intent:e.intent,phase:`ready`}):r(t.localId,{...e,phase:`creating_intent`,file:e.file,attempt:(e.attempt??1)+1});break}case`rebind`:{let e=n.get(t.localId);if(!e)break;e.phase===`paused`&&!e.file&&m(p(t.file),e.fingerprint)&&r(t.localId,{...e,file:t.file});break}case`remove`:n.delete(t.localId);break;case`startAll`:case`pauseAll`:case`cancelAll`:break}return{items:n}}switch(t.type){case`files.added`:{let e=t;for(let t of e.items)n.has(t.localId)||r(t.localId,{phase:`validating`,localId:t.localId,purpose:t.purpose,file:t.file,fingerprint:t.fingerprint,createdAt:t.createdAt});break}case`fingerprint.updated`:{let e=t,i=n.get(e.localId);if(!i)break;r(e.localId,{...i,fingerprint:e.fingerprint});break}case`validation.ok`:{let e=n.get(t.localId);if(!e||e.phase!==`validating`)break;r(t.localId,{...e,phase:`creating_intent`,attempt:1});break}case`validation.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`validating`)break;r(e.localId,{...a,phase:`error`,error:{code:`validation_failed`,reason:e.reason,message:String(e.reason)},retryable:!1,attempt:1,failedAt:i});break}case`intent.ok`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`creating_intent`)break;r(e.localId,{...i,phase:`ready`,intent:e.intent});break}case`intent.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`creating_intent`)break;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,failedAt:i});break}case`upload.begin`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`queued`)break;let a=i.file.size,o=i.progress,s=o?{...o,totalBytes:a,pct:X(o.uploadedBytes,a)}:{uploadedBytes:0,totalBytes:a,pct:0};r(e.localId,{...i,phase:`uploading`,progress:s,startedAt:e.startedAt});break}case`upload.progress`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`uploading`)break;let a={uploadedBytes:e.uploadedBytes,totalBytes:e.totalBytes,pct:X(e.uploadedBytes,e.totalBytes)};r(e.localId,{...i,progress:a});break}case`cursor.updated`:{let e=t,i=n.get(e.localId);if(!i)break;(i.phase===`uploading`||i.phase===`queued`||i.phase===`paused`)&&r(e.localId,{...i,cursor:e.cursor});break}case`upload.ok`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`uploading`)break;r(e.localId,{...a,phase:`completing`,progress:{uploadedBytes:a.file.size,totalBytes:a.file.size,pct:100},completingAt:i});break}case`upload.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`uploading`)break;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,attempt:1,failedAt:i});break}case`paused`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`uploading`)break;r(e.localId,{...i,phase:`paused`,cursor:e.cursor,pausedAt:e.pausedAt});break}case`canceled`:{let e=t,i=n.get(e.localId);if(!i)break;r(e.localId,Y(i,e.canceledAt));break}case`complete.ok`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`completing`)break;r(e.localId,{...a,phase:`completed`,completedBy:`upload`,result:e.result,completedAt:i});break}case`dedupe.ok`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`validating`)break;r(e.localId,{phase:`completed`,localId:a.localId,file:a.file,purpose:a.purpose,fingerprint:a.fingerprint,result:e.result,completedBy:`dedupe`,completedAt:i,createdAt:a.createdAt});break}case`complete.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`completing`)break;let o=(a.attempt??1)+1;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,attempt:o,failedAt:i});break}}return{items:n}}}function Y(e,t){return{phase:`canceled`,localId:e.localId,purpose:e.purpose,fingerprint:e.fingerprint,canceledAt:t,createdAt:e.createdAt,file:`file`in e?e.file:void 0,intent:`intent`in e?e.intent:void 0,cursor:`cursor`in e?e.cursor:void 0,progress:`progress`in e?e.progress:void 0,attempt:`attempt`in e?e.attempt:void 0}}function X(e,t){return t<=0?0:Math.min(100,e/t*100)}function Z(e,t){let n=e.config?.maxItems,r=e.config?.completedItemTTL,i=Date.now(),a=[],o=Array.from(t.items.entries());if(r!==void 0)for(let[e,t]of o)((t.phase===`completed`||t.phase===`canceled`)&&`completedAt`in t&&typeof t.completedAt==`number`&&i-t.completedAt>r||t.phase===`canceled`&&`canceledAt`in t&&typeof t.canceledAt==`number`&&i-t.canceledAt>r)&&a.push(e);let s=o.filter(([e])=>!a.includes(e));if(n!==void 0&&s.length>n){let e=s.filter(([,e])=>e.phase===`completed`||e.phase===`canceled`).sort(([,e],[,t])=>(`completedAt`in e?e.completedAt:`canceledAt`in e?e.canceledAt:0)-(`completedAt`in t?t.completedAt:`canceledAt`in t?t.canceledAt:0)),t=s.length-n;for(let n=0;n<t&&n<e.length;n++)a.push(e[n][0])}if(a.length===0)return null;let c=new Map(t.items);for(let e of a)c.delete(e);return{...t,items:c}}async function oe(t,n){let r=t.state.items.get(n);if(!r||r.phase!==`creating_intent`||t.inflightIntents.has(n))return;let i=new AbortController;t.inflightIntents.set(n,i);try{let e=await t.opts.api.createIntent({purpose:r.purpose,contentType:r.file.type||`application/octet-stream`,size:r.file.size,filename:r.file.name,checksum:r.fingerprint.checksum},{signal:i.signal});t.inflightIntents.delete(n);let a=t.state.items.get(n);if(!a||a.phase!==`creating_intent`)return;let o=d(e,e.strategy);if(o){let e={code:`validation_failed`,message:`Invalid intent from backend: ${o.message}`,cause:o,retryable:!1};t.applyInternal({type:`intent.failed`,localId:n,error:e,retryable:!1});return}t.applyInternal({type:`intent.ok`,localId:n,intent:e})}catch(a){if(t.inflightIntents.delete(n),i.signal.aborted)return;let o=_(a,t.opts.errorNormalizer),s={...o,message:`${o.message} (file: ${r.fingerprint.name}, size: ${r.file.size} bytes, purpose: ${r.purpose})`},c=S(t.opts.config,{phase:`intent`,attempt:r.attempt,error:s});t.applyInternal({type:`intent.failed`,localId:n,error:s,retryable:c.retryable}),c.retryable&&c.delayMs!==void 0&&t.enqueueEffect(async()=>{await e(c.delayMs),t.dispatch({type:`retry`,localId:n})})}}async function se(t,n){let r=t.state.items.get(n);if(!r||r.phase!==`completing`||t.inflightCompletes.has(n))return;let i=new AbortController;t.inflightCompletes.set(n,i);try{let e=await t.opts.api.complete({fileId:r.intent.fileId},{signal:i.signal});t.inflightCompletes.delete(n);let a=t.state.items.get(n);if(!a||a.phase!==`completing`)return;t.applyInternal({type:`complete.ok`,localId:n,result:e})}catch(r){if(t.inflightCompletes.delete(n),i.signal.aborted)return;let a=_(r,t.opts.errorNormalizer),o=t.state.items.get(n),s=o?{...a,message:`${a.message} (file: ${o.fingerprint.name}, fileId: ${(v(o)&&o.intent?.fileId)??`unknown`})`}:a,c=o&&`attempt`in o?o.attempt??1:1,l=S(t.opts.config,{phase:`complete`,attempt:c,error:s});t.applyInternal({type:`complete.failed`,localId:n,error:s,retryable:l.retryable}),l.retryable&&l.delayMs!==void 0&&t.enqueueEffect(async()=>{await e(l.delayMs),t.dispatch({type:`retry`,localId:n})})}}async function ce(t,n){let r=t.state.items.get(n);if(!r||r.phase!==`uploading`||!v(r)){t.inflightUploads.delete(n);return}let i=t.inflightUploads.get(n);if(i||(i={controller:new AbortController,mode:`normal`,started:!1},t.inflightUploads.set(n,i)),i.started)return;i.started=!0;let a=i.controller,o=()=>{let e=t.state.items.get(n);!e||e.phase===`canceled`||t.applyInternal({type:`canceled`,localId:n,canceledAt:Date.now()})},s=e=>{let r=t.state.items.get(n);!r||r.phase===`paused`||t.applyInternal({type:`paused`,localId:n,cursor:e,pausedAt:Date.now()})};if(a.signal.aborted){if(t.inflightUploads.delete(n),i.mode===`pause`){let e=t.state.items.get(n),i;r&&v(r)&&(i=r.intent.strategy);let a=(e&&b(e)?e.cursor:void 0)||(i?{strategy:i,value:void 0}:void 0);a&&s(a);return}if(i.mode===`cancel`){o();return}return}let c=r.intent.strategy;if(!t.opts.strategies.has(c)){t.inflightUploads.delete(n);let e={code:`strategy_missing`,message:`Strategy not found: ${c}`,strategy:String(c),retryable:!1};t.applyInternal({type:`upload.failed`,localId:n,error:e,retryable:!1});return}let l=t.opts.strategies.get(c);if(!l){t.inflightUploads.delete(n);let e={code:`strategy_missing`,message:`Strategy not found: ${r.intent.strategy}`,strategy:String(r.intent.strategy),retryable:!1};t.applyInternal({type:`upload.failed`,localId:n,error:e,retryable:!1});return}let u=0,d=t.opts.config.progressThrottleMs;try{if(await l.start({file:r.file,intent:r.intent,signal:a.signal,transport:t.opts.transport,api:t.opts.api,readCursor:()=>{let e=t.state.items.get(n);if(!(!e||!b(e)||!e.cursor)&&e.cursor.strategy===c)return e.cursor.value},persistCursor:e=>{let r={strategy:c,value:e};t.applyInternal({type:`cursor.updated`,localId:n,cursor:r})},reportProgress:e=>{let r=Date.now();r-u<d||(t.applyInternal({type:`upload.progress`,localId:n,uploadedBytes:e.uploadedBytes,totalBytes:e.totalBytes}),u=r)}}),t.inflightUploads.delete(n),i?.mode===`cancel`){o();return}if(a.signal.aborted&&i?.mode===`pause`){let e=t.state.items.get(n);s((e&&b(e)?e.cursor:void 0)||{strategy:c,value:void 0});return}t.applyInternal({type:`upload.ok`,localId:n})}catch(r){if(t.inflightUploads.delete(n),a.signal.aborted){if(i?.mode===`pause`){let e=t.state.items.get(n);s((e&&b(e)?e.cursor:void 0)||{strategy:c,value:void 0});return}if(i?.mode===`cancel`){o();return}o();return}let l=_(r,t.opts.errorNormalizer),u=t.state.items.get(n),d=u?{...l,message:`${l.message} (file: ${u.fingerprint.name}, size: ${(y(u)&&u.file?.size)??0} bytes, phase: ${u.phase})`}:l,f=S(t.opts.config,{phase:`upload`,attempt:1,error:d});t.applyInternal({type:`upload.failed`,localId:n,error:d,retryable:f.retryable}),f.retryable&&f.delayMs!==void 0&&t.enqueueEffect(async()=>{await e(f.delayMs),t.dispatch({type:`retry`,localId:n})})}}function le(e){if(!e.scheduling){e.scheduling=!0;try{ue(e),fe(e),de(e)}finally{e.scheduling=!1}}}function ue(e){for(let t of e.state.items.values())t.phase===`creating_intent`&&(e.inflightIntents.has(t.localId)||e.enqueueEffect(()=>oe(e,t.localId)))}function de(e){for(let t of e.state.items.values())t.phase===`completing`&&(e.inflightCompletes.has(t.localId)||e.enqueueEffect(()=>se(e,t.localId)))}function fe(e){let t=Math.max(1,e.opts.config.maxConcurrentUploads),n=e.inflightUploads.size;for(let t of e.state.items.values())t.phase===`ready`&&g(e.opts,t.purpose)&&e.applyCommand({type:`start`,localId:t.localId});if(n>=t)return;let r=Array.from(e.state.items.values()).filter(pe);if(r.length===0)return;let i=Math.max(0,t-n),a=r.slice(0,i);for(let t of a){if(e.inflightUploads.has(t.localId))continue;let n=new AbortController;e.inflightUploads.set(t.localId,{controller:n,mode:`normal`,started:!1}),e.applyInternal({type:`upload.begin`,localId:t.localId,startedAt:Date.now()}),ce(e,t.localId)}}function pe(e){return e.phase===`queued`}function me(e){let t=Q(e),n=t.persistence,r=null,i=Math.max(0,n?.debounceMs??200),a=async()=>{if(n)try{let e=(n.serialize??V)(s.state,n.version);if(!e.items||Object.keys(e.items).length===0){await n.adapter.clear(n.key);return}await n.adapter.save(n.key,e)}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[UploadEngine] persistence flush failed:`,e)}},o=()=>{if(n){if(i===0){a();return}r&&clearTimeout(r),r=setTimeout(()=>void a(),i)}},s={opts:t,state:t.initialState??{items:new Map},listeners:new Set,emitter:q(),reduce:J(),inflightUploads:new Map,inflightIntents:new Map,inflightCompletes:new Map,effectQueue:[],processingEffects:!1,scheduling:!1,notify(){s.listeners.forEach(e=>{e()})},scheduleWork(){le(s)},applyInternal(e){let t=s.state;s.state=s.reduce(t,e),s.opts.hooks?.onInternalEvent?.(e,s.state),he(s,e);let n=Z(s.opts,s.state);n&&(s.state=n),o(),s.notify(),s.scheduleWork()},applyCommand(e){let t=s.state,n=s.reduce(t,e);s.state=n,ge(s,e,t,n);let r=Z(s.opts,s.state);r&&(s.state=r),o(),s.notify(),s.scheduleWork()},enqueueEffect(e){s.effectQueue.push(e),s.processingEffects||s.processEffects()},async processEffects(){for(s.processingEffects=!0;s.effectQueue.length>0;){let e=s.effectQueue.shift();if(e)try{await e()}catch(e){if(typeof window<`u`&&process.env.NODE_ENV===`development`){let t=e instanceof Error?e:Error(String(e)),n={queueLength:s.effectQueue.length,processing:s.processingEffects,timestamp:new Date().toISOString()};console.error(`[UploadEngine] Effect error:`,t,n)}}}s.processingEffects=!1},dispatch:()=>{throw Error(`[UploadEngine] dispatch not initialized`)}};if(n&&!t.initialState&&typeof window<`u`)try{let e=n.adapter.load(n.key);Promise.resolve(e).then(e=>{if(!e)return;let r=(n.deserialize??H)(e,{isPurpose:n.isPurpose,isIntent:n.isIntent,hasStrategy:t.strategies.has});if(r){if(s.state.items.size===0)s.state=r;else{let e=new Map(s.state.items);for(let[t,n]of r.items.entries())e.has(t)||e.set(t,n);s.state={...s.state,items:e}}s.notify(),s.scheduleWork()}}).catch(()=>void 0)}catch{}return s}function Q(e){return{...e,config:s(e.config),transport:e.transport??M()}}function he(e,t){switch(t.type){case`files.added`:for(let n of t.items)e.emitter.emit(`file.added`,{localId:n.localId,purpose:n.purpose,file:n.file,fingerprint:n.fingerprint});break;case`validation.ok`:e.emitter.emit(`validation.ok`,{localId:t.localId}),e.emitter.emit(`intent.creating`,{localId:t.localId});break;case`validation.failed`:e.emitter.emit(`validation.failed`,{localId:t.localId,reason:t.reason});break;case`intent.ok`:e.emitter.emit(`intent.created`,{localId:t.localId,intent:t.intent});break;case`intent.failed`:e.emitter.emit(`intent.failed`,{localId:t.localId,error:t.error,retryable:t.retryable});break;case`upload.begin`:e.emitter.emit(`upload.started`,{localId:t.localId});break;case`upload.progress`:{let n=t.totalBytes>0?Math.min(100,t.uploadedBytes/t.totalBytes*100):0;e.emitter.emit(`upload.progress`,{localId:t.localId,pct:n,uploadedBytes:t.uploadedBytes,totalBytes:t.totalBytes});break}case`cursor.updated`:e.emitter.emit(`upload.cursor`,{localId:t.localId,cursor:t.cursor});break;case`upload.ok`:e.emitter.emit(`upload.completing`,{localId:t.localId});break;case`upload.failed`:e.emitter.emit(`upload.error`,{localId:t.localId,error:t.error,retryable:t.retryable});break;case`dedupe.ok`:e.emitter.emit(`upload.completed`,{localId:t.localId,result:t.result,completedBy:`dedupe`});break;case`complete.ok`:e.emitter.emit(`upload.completed`,{localId:t.localId,result:t.result,completedBy:`upload`});break;case`complete.failed`:e.emitter.emit(`upload.error`,{localId:t.localId,error:t.error,retryable:t.retryable});break;case`paused`:e.emitter.emit(`upload.paused`,{localId:t.localId,cursor:t.cursor});break;case`canceled`:e.emitter.emit(`upload.canceled`,{localId:t.localId});break;case`fingerprint.updated`:break}}function ge(e,t,n,r){if(t.type===`start`||t.type===`resume`){let i=n.items.get(t.localId),a=r.items.get(t.localId);if(!i||!a||a.phase!==`queued`)return;i.phase===`paused`?e.emitter.emit(`upload.resumed`,{localId:t.localId}):e.emitter.emit(`upload.queued`,{localId:t.localId})}if(t.type===`cancel`){let i=n.items.get(t.localId),a=r.items.get(t.localId);if(!i||!a)return;a.phase===`canceled`&&i.phase!==`canceled`&&e.emitter.emit(`upload.canceled`,{localId:t.localId})}}function $(e){let t=me(e);t.dispatch=e=>E(t,e);let n={on:t.emitter.on.bind(t.emitter),off:t.emitter.off.bind(t.emitter),dispatch:t.dispatch,getSnapshot:()=>t.state};for(let t of e.plugins||[])try{t.setup(n)}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.error(`[UploadEngine] Plugin "${t.name}" failed to setup:`,e)}return{...n,subscribe:e=>(t.listeners.add(e),()=>t.listeners.delete(e)),waitFor(e){return _e(t,e)}}}function _e(e,t){if(t.length===0)return Promise.resolve([]);let n=new Set(t),r=new Map,i=()=>{for(let t of n){let i=e.state.items.get(t);if(!i){r.set(t,{localId:t,status:`missing`}),n.delete(t);continue}if(i.phase===`completed`){r.set(t,{localId:t,status:`completed`,completedBy:i.completedBy,result:i.result}),n.delete(t);continue}if(i.phase===`error`){r.set(t,{localId:t,status:`error`,error:i.error}),n.delete(t);continue}i.phase===`canceled`&&(r.set(t,{localId:t,status:`canceled`}),n.delete(t))}};return i(),n.size===0?Promise.resolve(t.map(e=>r.get(e)??{localId:e,status:`missing`})):new Promise(a=>{let o=()=>{i(),n.size===0&&(e.listeners.delete(o),a(t.map(e=>r.get(e)??{localId:e,status:`missing`})))};e.listeners.add(o)})}function ve(e){return $(e)}export{t as C,r as S,c as _,H as a,a as b,R as c,p as d,m as f,l as g,u as h,q as i,L as l,d as m,$ as n,V as o,f as p,J as r,B as s,ve as t,M as u,s as v,n as w,o as x,i as y};
1
+ import{t as e}from"./async-DxRWu4le.js";const t=500,n=1e4,r=100,i=3,a=3,o=100;function s(e){return{maxConcurrentUploads:Math.max(1,e?.maxConcurrentUploads??3),autoStart:e?.autoStart,progressThrottleMs:Math.max(0,e?.progressThrottleMs??100),validation:e?.validation??{},maxAttempts:Math.max(1,e?.maxAttempts??3),retryPolicy:e?.retryPolicy,maxItems:Math.max(1,e?.maxItems??100),completedItemTTL:e?.completedItemTTL}}function c(){return`upload_${Date.now()}_${Math.random().toString(36).slice(2)}`}function l(e){return!!e&&typeof e==`object`}function u(e,t,n){if(e.size===0)return{code:`empty_file`};let r=n.validation?.[t];if(!r)return null;if(r.maxSizeBytes!==void 0&&e.size>r.maxSizeBytes)return{code:`file_too_large`,maxBytes:r.maxSizeBytes,size:e.size};if(r.minSizeBytes!==void 0&&e.size<r.minSizeBytes)return{code:`empty_file`};if(r.allowedTypes||r.allowedExtensions){let t=r.allowedTypes||[],n=r.allowedExtensions||[],i=e.name.split(`.`).pop()?.toLowerCase(),a=(e.type||``).toLowerCase(),o=t.length===0?!1:t.some(e=>{if(e.endsWith(`/*`)){let t=e.slice(0,-2).toLowerCase();return a.startsWith(`${t}/`)}return a===e.toLowerCase()}),s=i&&n.length>0?n.some(e=>e.toLowerCase()===i):!1,c=t.length>0,l=n.length>0;if(!(c&&l?o||s:(!c||o)&&(!l||s)))return{code:`type_not_allowed`,allowed:[...t,...n],got:e.type||e.name}}return null}function d(e,t,n,r=0){let i=n.validation?.[t],a=[],o=[],s=i?.maxFiles,c=s===void 0?1/0:Math.max(0,s-r);for(let r of e){if(c<=0){let e=s??0;o.push({file:r,reason:{code:`too_many_files`,max:e}});continue}let e=u(r,t,n);if(e){o.push({file:r,reason:e});continue}a.push(r),--c}return{valid:a,rejected:o}}function f(e,t,n){if(!l(e))return Error(`Invalid intent: must be an object`);if(typeof e.strategy!=`string`)return Error(`Invalid intent: missing or invalid strategy field`);if(e.strategy!==t)return Error(`Invalid intent: strategy mismatch (expected ${t}, got ${e.strategy})`);if(typeof e.fileId!=`string`||!e.fileId)return Error(`Invalid intent: missing or invalid fileId`);if(t===`post`){if(typeof e.url!=`string`||!e.url)return Error(`Invalid post intent: missing or invalid url`);try{let t=new URL(e.url);if(![`http:`,`https:`].includes(t.protocol))return Error(`Invalid post intent: url must use http or https protocol`)}catch{return Error(`Invalid post intent: url is not a valid URL`)}if(!e.fields||typeof e.fields!=`object`)return Error(`Invalid post intent: missing or invalid fields`)}else if(t===`multipart`){if(typeof e.uploadId!=`string`||!e.uploadId)return Error(`Invalid multipart intent: missing or invalid uploadId`);if(typeof e.partSize!=`number`||e.partSize<=0)return Error(`Invalid multipart intent: missing or invalid partSize`);if(`partCount`in e&&e.partCount!==void 0&&(typeof e.partCount!=`number`||!Number.isFinite(e.partCount)||e.partCount<=0))return Error(`Invalid multipart intent: partCount must be a positive number`);if(typeof n==`number`&&typeof e.partCount==`number`&&e.partCount>0){let t=e.partSize,r=e.partCount,i=r*t,a=(r-1)*t;if(i<n)return Error(`Invalid multipart intent: partCount * partSize (${i}) is smaller than file size (${n})`);if(a>=n&&r>1)return Error(`Invalid multipart intent: partCount (${r}) exceeds what file size (${n}) needs at partSize ${t}`)}if(`parts`in e&&e.parts!==void 0&&!Array.isArray(e.parts))return Error(`Invalid multipart intent: parts must be an array if provided`)}return null}function p(e){return{name:e.name,size:e.size,type:e.type,lastModified:e.lastModified}}function m(e,t){return e.name===t.name&&e.size===t.size&&e.lastModified===t.lastModified}async function h(e){let t=await e.arrayBuffer(),n=await crypto.subtle.digest(`SHA-256`,t);return Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,`0`)).join(``)}function g(e,t){let n=e.config?.autoStart;return n===void 0?!1:Array.isArray(n)?n.includes(t):typeof n==`function`?n(t):!1}function _(e,t){if(t)return t(e);if(x(e))return{code:`aborted`,message:`Upload aborted`,reason:String(e.reason??`unknown`),retryable:!1};let n=l(e)&&typeof e.message==`string`?e.message:`Unknown error`;if(String(n).toLowerCase().includes(`network`)||String(n).toLowerCase().includes(`fetch`))return{code:`network`,message:String(n),cause:e,retryable:!0};if(l(e)&&(typeof e.status==`number`||typeof e.statusCode==`number`)){let t=typeof e.status==`number`?e.status:e.statusCode,r=t>=500||t===429;return{code:`http`,status:t,message:String(n),cause:e,retryable:r}}return{code:`unknown`,message:String(n),cause:e,retryable:!1}}function v(e){return`intent`in e&&!!e.intent}function y(e){return`file`in e&&!!e.file}function b(e){return`cursor`in e}function ee(e){return l(e)&&e.strategy===`multipart`&&typeof e.fileId==`string`&&typeof e.uploadId==`string`}function x(e){return l(e)&&e.code===`aborted`}function S(e,t){if(e.retryPolicy)return e.retryPolicy(t);if(t.error.code===`auth`||t.error.code===`validation_failed`||t.error.code===`strategy_missing`||t.error.code===`aborted`)return{retryable:!1};let r=e.maxAttempts;return t.attempt<r?{retryable:!0,delayMs:Math.min(n,500*2**(t.attempt-1))}:{retryable:!1}}function C(e,t,n){let r=Array.from(e.state.items.values()).filter(e=>e.purpose===n&&e.phase!==`canceled`).length,{valid:i,rejected:a}=d(t,n,e.opts.config,r);for(let{file:t,reason:n}of a)e.emitter.emit(`file.rejected`,{reason:n,file:t});let o=Date.now(),s=[];for(let t of i){let r=c(),i=(e.opts.fingerprint??p)(t);s.push({localId:r,purpose:n,file:t,fingerprint:i,createdAt:o}),e.enqueueEffect(async()=>{let a;try{a=await h(t);let n={...i,checksum:a};e.applyInternal({type:`fingerprint.updated`,localId:r,fingerprint:n})}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[UploadEngine] Failed to calculate checksum:`,e)}if(a&&e.opts.api.findByChecksum)try{let t=await e.opts.api.findByChecksum({checksum:a,purpose:n});if(t){let n=e.state.items.get(r);if(n&&n.phase===`validating`){e.applyInternal({type:`dedupe.ok`,localId:r,result:t});return}}}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[UploadEngine] Failed to check for existing file:`,e)}let o=e.opts.validateFile?.(t,n);if(o){e.applyInternal({type:`validation.failed`,localId:r,reason:o});return}let s=u(t,n,e.opts.config);if(s){e.applyInternal({type:`validation.failed`,localId:r,reason:s});return}e.applyInternal({type:`validation.ok`,localId:r})})}s.length>0&&e.applyInternal({type:`files.added`,items:s})}function w(e,t){let n=e.state.items.get(t),r=e.inflightIntents.get(t);r&&(r.abort({reason:`cancel`}),e.inflightIntents.delete(t));let i=e.inflightCompletes.get(t);i&&(i.abort({reason:`cancel`}),e.inflightCompletes.delete(t));let a=e.inflightUploads.get(t);if(a&&(a.mode=`cancel`,a.controller.abort({reason:`cancel`})),n&&v(n)&&n.intent.strategy===`multipart`&&e.opts.api.multipart?.abort){let t=n.intent;ee(t)&&e.enqueueEffect(async()=>{try{let n=e.opts.api.multipart?.abort;n&&await n({fileId:t.fileId,uploadId:t.uploadId})}catch{}})}}function T(e,t){let n=e.inflightUploads.get(t);if(n){n.mode=`pause`,n.controller.abort({reason:`pause`});return}}function E(e,t){if(t.type===`startAll`){let n=t.purpose;Array.from(e.state.items.values()).filter(e=>e.phase===`ready`&&(!n||e.purpose===n)).forEach(t=>{e.dispatch({type:`start`,localId:t.localId})});return}if(t.type===`pauseAll`){let n=t.purpose;Array.from(e.state.items.values()).filter(e=>e.phase===`uploading`&&(!n||e.purpose===n)).forEach(t=>{e.dispatch({type:`pause`,localId:t.localId})});return}if(t.type===`cancelAll`){let n=t.purpose;Array.from(e.state.items.values()).filter(e=>e.phase!==`completed`&&e.phase!==`canceled`&&(!n||e.purpose===n)).forEach(t=>{e.dispatch({type:`cancel`,localId:t.localId})});return}if(t.type===`addFiles`){C(e,t.files,t.purpose);return}if(t.type===`pause`){T(e,t.localId),e.applyCommand(t);return}if(t.type===`cancel`){w(e,t.localId),e.applyCommand(t);return}if(t.type===`start`){e.applyCommand(t);return}if(t.type===`resume`){e.applyCommand(t);return}e.applyCommand(t)}function D(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function O(e){return e?.reason}function te(e){if(!e)return`unknown`;if(typeof e==`string`)return e===`pause`||e===`cancel`?e:`unknown`;if(l(e)&&`reason`in e){let t=e.reason;if(t===`pause`||t===`cancel`)return t}if(l(e)&&`kind`in e){let t=e.kind;if(t===`pause`||t===`cancel`)return t}return`unknown`}var k=class extends Error{constructor(e){super(`Upload aborted`),D(this,`code`,`aborted`),D(this,`reason`,void 0),this.reason=te(e)}};function A(e){return new k(e)}function ne(e,t){return e.status===0?Error(`CORS error: The upload server does not allow requests from this origin. Please check CORS configuration.`):Error(`${t} (status: ${e.status})`)}function re(e){let t={};return e.getAllResponseHeaders().split(`\r
2
+ `).map(e=>e.trim()).filter(Boolean).forEach(e=>{let n=e.indexOf(`:`);if(n<=0)return;let r=e.slice(0,n).trim().toLowerCase(),i=e.slice(n+1).trim();r&&(t[r]=i)}),t}function ie(e){return/^https?:\/\//i.test(e)}function ae(e){let t=e.getResponseHeader(`ETag`)??e.getResponseHeader(`etag`);if(t)return t.replace(/"/g,``)}function j(e){return new Promise((t,n)=>{if(!e.url||typeof e.url!=`string`){n(Error(`UploadTransport: missing url`));return}if(!ie(e.url)){n(Error(`UploadTransport: expected absolute URL, got: ${String(e.url)}`));return}let r=new XMLHttpRequest,i=!1,a=e=>{i||(i=!0,s(),e())},o=()=>{try{r.abort()}finally{a(()=>n(A(O(e.signal))))}},s=()=>{e.signal&&e.signal.removeEventListener(`abort`,o),r.upload.onprogress=null,r.onload=null,r.onerror=null,r.onabort=null};if(e.signal?.aborted){a(()=>n(A(O(e.signal))));return}if(e.signal&&e.signal.addEventListener(`abort`,o,{once:!0}),e.onProgress&&(r.upload.onprogress=t=>{t.lengthComputable&&e.onProgress?.(t.loaded,t.total)}),r.onload=()=>{let e=r.status;a(e>=200&&e<300?()=>t({headers:re(r),etag:ae(r)}):()=>n(Error(`Upload failed with status ${e}`)))},r.onerror=()=>a(()=>n(ne(r,`Network error during upload`))),r.onabort=()=>a(()=>n(A(e.signal?.aborted?O(e.signal):`unknown`))),r.open(e.method,e.url),e.headers)for(let[t,n]of Object.entries(e.headers))r.setRequestHeader(t,n);r.send(e.body)})}function M(){return{async postForm(e){let t=new FormData;for(let[n,r]of Object.entries(e.fields))t.append(n,r);return t.append(`file`,e.file,e.filename??`file`),{headers:(await j({method:`POST`,url:e.url,body:t,signal:e.signal,onProgress:e.onProgress})).headers}},async put(e){let t=await j({method:`PUT`,url:e.url,body:e.body,signal:e.signal,headers:e.headers,onProgress:e.onProgress});return{headers:t.headers,etag:t.etag}},async patch(e){return{headers:(await j({method:`PATCH`,url:e.url,body:e.body,signal:e.signal,headers:e.headers,onProgress:e.onProgress})).headers}}}}const N=`snapshots`;function P(){return new Promise((e,t)=>{let n=indexedDB.open(`upload-engine`,1);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(N)||e.createObjectStore(N)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}function F(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error)})}function I(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onabort=()=>n(e.error??Error(`IndexedDB transaction aborted`)),e.onerror=()=>n(e.error??Error(`IndexedDB transaction error`))})}const L={async load(e){if(typeof indexedDB>`u`)return null;let t=(await P()).transaction(N,`readonly`),n=t.objectStore(N);try{let r=await F(n.get(e));return await I(t),r??null}catch{try{await I(t)}catch{}return null}},async save(e,t){if(typeof indexedDB>`u`)return;let n=(await P()).transaction(N,`readwrite`),r=n.objectStore(N);try{r.put(t,e),await I(n)}catch{try{n.abort()}catch{}}},async clear(e){if(typeof indexedDB>`u`)return;let t=(await P()).transaction(N,`readwrite`),n=t.objectStore(N);try{n.delete(e),await I(t)}catch{try{t.abort()}catch{}}}},R={load(e){if(typeof localStorage>`u`)return null;let t=localStorage.getItem(e);if(!t)return null;try{return JSON.parse(t)}catch{return null}},save(e,t){typeof localStorage>`u`||localStorage.setItem(e,JSON.stringify(t))},clear(e){typeof localStorage>`u`||localStorage.removeItem(e)}},z=new Map,B={load(e){return z.get(e)??null},save(e,t){z.set(e,t)},clear(e){z.delete(e)}};function V(e,t){let n={};for(let t of e.items.values()){if(!v(t)||t.phase===`completed`||t.phase===`canceled`||t.phase===`error`)continue;let e=b(t)?t.cursor:void 0,r=`progress`in t&&t.progress?{uploadedBytes:t.progress.uploadedBytes,totalBytes:t.progress.totalBytes,pct:t.progress.pct}:void 0,i={id:t.localId,purpose:t.purpose,status:t.phase,file:{name:t.fingerprint.name,size:t.fingerprint.size,type:t.fingerprint.type,lastModified:t.fingerprint.lastModified,checksum:t.fingerprint.checksum},intent:t.intent,cursor:e,progress:r};n[i.id]=i}return{version:t,createdAt:Date.now(),items:n}}function H(e,t){if(!t.isPurpose||!t.isIntent||!U(e))return null;let n=new Map;for(let r of Object.values(e.items)){let i=W(r);if(!i||!t.isPurpose(i.purpose)||!t.isIntent(i.intent))continue;let a=i.intent.strategy;if(typeof a!=`string`||!t.hasStrategy(a)||!i.cursor||!K(i.cursor,t.hasStrategy)||i.cursor.strategy!==a)continue;let o=i.progress?.totalBytes??i.file.size,s=i.progress?.uploadedBytes??0,c=typeof i.progress?.pct==`number`?i.progress.pct:o>0?Math.min(100,Math.max(0,s/o*100)):0;n.set(i.id,{phase:`paused`,localId:i.id,purpose:i.purpose,fingerprint:{name:i.file.name,size:i.file.size,type:i.file.type,lastModified:i.file.lastModified,checksum:i.file.checksum},intent:i.intent,cursor:i.cursor,progress:{uploadedBytes:s,totalBytes:o,pct:c},pausedAt:Date.now(),createdAt:e.createdAt??Date.now(),file:void 0})}return{items:n}}function U(e){return!l(e)||typeof e.version!=`number`||typeof e.createdAt!=`number`?!1:l(e.items)}function W(e){if(!l(e))return null;let t=typeof e.id==`string`?e.id:null,n=typeof e.purpose==`string`?e.purpose:null,r=typeof e.status==`string`?e.status:null,i=e.intent;if(!t||!n||!r||!l(e.file))return null;let a=typeof e.file.name==`string`?e.file.name:null,o=typeof e.file.size==`number`?e.file.size:null,s=typeof e.file.type==`string`?e.file.type:null,c=typeof e.file.lastModified==`number`?e.file.lastModified:null,u=typeof e.file.checksum==`string`?e.file.checksum:void 0;if(!a||o===null||!s||c===null)return null;let d=G(e.progress);return{id:t,purpose:n,status:r,file:{name:a,size:o,type:s,lastModified:c,checksum:u},intent:i,cursor:e.cursor,progress:d}}function G(e){if(!l(e)||typeof e.uploadedBytes!=`number`||typeof e.totalBytes!=`number`)return;let t=typeof e.pct==`number`?e.pct:void 0;return{uploadedBytes:e.uploadedBytes,totalBytes:e.totalBytes,pct:t}}function K(e,t){return!(!l(e)||typeof e.strategy!=`string`||!t(e.strategy))}function q(){let e={};return{on(t,n){let r=e[t];return r||(r=new Set,e[t]=r),r.add(n),()=>{let r=e[t];r&&(r.delete(n),r.size===0&&delete e[t])}},off(t,n){let r=e[t];return r?(r.delete(n),r.size===0&&delete e[t],()=>{}):()=>{}},emit(t,n){let r=e[t];r&&r.forEach(e=>{try{e(n)}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.error(`[UploadEngine] Error in event listener for "${t}":`,e)}})}}}function J(){return function(e,t){let n=new Map(e.items),r=(e,t)=>n.set(e,t),i=Date.now();if((e=>`type`in e&&typeof e.type==`string`&&!e.type.includes(`.`))(t)){switch(t.type){case`addFiles`:break;case`start`:{let e=n.get(t.localId);if(!e||e.phase!==`ready`)break;r(t.localId,{...e,phase:`queued`,requestedAt:i});break}case`resume`:{let e=n.get(t.localId);if(!e||e.phase!==`paused`||!e.file)break;r(t.localId,{...e,file:e.file,phase:`queued`,requestedAt:i});break}case`pause`:{let e=n.get(t.localId);if(!e)break;e.phase===`queued`&&r(t.localId,{...e,phase:`ready`});break}case`cancel`:{let e=n.get(t.localId);if(!e||e.phase===`completed`||e.phase===`canceled`)break;r(t.localId,Y(e,i));break}case`retry`:{let e=n.get(t.localId);if(!e||e.phase!==`error`||!e.retryable||!e.file)break;e.intent?e.progress&&e.progress.pct===100?r(t.localId,{...e,progress:e.progress,intent:e.intent,file:e.file,phase:`completing`,completingAt:i}):r(t.localId,{...e,file:e.file,intent:e.intent,phase:`ready`}):r(t.localId,{...e,phase:`creating_intent`,file:e.file,attempt:(e.attempt??1)+1});break}case`rebind`:{let e=n.get(t.localId);if(!e)break;e.phase===`paused`&&!e.file&&m(p(t.file),e.fingerprint)&&r(t.localId,{...e,file:t.file});break}case`remove`:n.delete(t.localId);break;case`startAll`:case`pauseAll`:case`cancelAll`:break}return{items:n}}switch(t.type){case`files.added`:{let e=t;for(let t of e.items)n.has(t.localId)||r(t.localId,{phase:`validating`,localId:t.localId,purpose:t.purpose,file:t.file,fingerprint:t.fingerprint,createdAt:t.createdAt});break}case`fingerprint.updated`:{let e=t,i=n.get(e.localId);if(!i)break;r(e.localId,{...i,fingerprint:e.fingerprint});break}case`validation.ok`:{let e=n.get(t.localId);if(!e||e.phase!==`validating`)break;r(t.localId,{...e,phase:`creating_intent`,attempt:1});break}case`validation.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`validating`)break;r(e.localId,{...a,phase:`error`,error:{code:`validation_failed`,reason:e.reason,message:String(e.reason)},retryable:!1,attempt:1,failedAt:i});break}case`intent.ok`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`creating_intent`)break;r(e.localId,{...i,phase:`ready`,intent:e.intent});break}case`intent.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`creating_intent`)break;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,failedAt:i});break}case`upload.begin`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`queued`)break;let a=i.file.size,o=i.progress,s=o?{...o,totalBytes:a,pct:X(o.uploadedBytes,a)}:{uploadedBytes:0,totalBytes:a,pct:0};r(e.localId,{...i,phase:`uploading`,progress:s,startedAt:e.startedAt});break}case`upload.progress`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`uploading`)break;let a={uploadedBytes:e.uploadedBytes,totalBytes:e.totalBytes,pct:X(e.uploadedBytes,e.totalBytes)};r(e.localId,{...i,progress:a});break}case`cursor.updated`:{let e=t,i=n.get(e.localId);if(!i)break;(i.phase===`uploading`||i.phase===`queued`||i.phase===`paused`)&&r(e.localId,{...i,cursor:e.cursor});break}case`upload.ok`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`uploading`)break;r(e.localId,{...a,phase:`completing`,progress:{uploadedBytes:a.file.size,totalBytes:a.file.size,pct:100},completingAt:i});break}case`upload.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`uploading`)break;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,attempt:1,failedAt:i});break}case`paused`:{let e=t,i=n.get(e.localId);if(!i||i.phase!==`uploading`)break;r(e.localId,{...i,phase:`paused`,cursor:e.cursor,pausedAt:e.pausedAt});break}case`canceled`:{let e=t,i=n.get(e.localId);if(!i)break;r(e.localId,Y(i,e.canceledAt));break}case`complete.ok`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`completing`)break;r(e.localId,{...a,phase:`completed`,completedBy:`upload`,result:e.result,completedAt:i});break}case`dedupe.ok`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`validating`)break;r(e.localId,{phase:`completed`,localId:a.localId,file:a.file,purpose:a.purpose,fingerprint:a.fingerprint,result:e.result,completedBy:`dedupe`,completedAt:i,createdAt:a.createdAt});break}case`complete.failed`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`completing`)break;let o=(a.attempt??1)+1;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,attempt:o,failedAt:i});break}}return{items:n}}}function Y(e,t){return{phase:`canceled`,localId:e.localId,purpose:e.purpose,fingerprint:e.fingerprint,canceledAt:t,createdAt:e.createdAt,file:`file`in e?e.file:void 0,intent:`intent`in e?e.intent:void 0,cursor:`cursor`in e?e.cursor:void 0,progress:`progress`in e?e.progress:void 0,attempt:`attempt`in e?e.attempt:void 0}}function X(e,t){return t<=0?0:Math.min(100,e/t*100)}function Z(e,t){let n=e.config?.maxItems,r=e.config?.completedItemTTL,i=Date.now(),a=[],o=Array.from(t.items.entries());if(r!==void 0)for(let[e,t]of o)((t.phase===`completed`||t.phase===`canceled`)&&`completedAt`in t&&typeof t.completedAt==`number`&&i-t.completedAt>r||t.phase===`canceled`&&`canceledAt`in t&&typeof t.canceledAt==`number`&&i-t.canceledAt>r)&&a.push(e);let s=o.filter(([e])=>!a.includes(e));if(n!==void 0&&s.length>n){let e=s.filter(([,e])=>e.phase===`completed`||e.phase===`canceled`).sort(([,e],[,t])=>(`completedAt`in e?e.completedAt:`canceledAt`in e?e.canceledAt:0)-(`completedAt`in t?t.completedAt:`canceledAt`in t?t.canceledAt:0)),t=s.length-n;for(let n=0;n<t&&n<e.length;n++)a.push(e[n][0])}if(a.length===0)return null;let c=new Map(t.items);for(let e of a)c.delete(e);return{...t,items:c}}async function oe(t,n){let r=t.state.items.get(n);if(!r||r.phase!==`creating_intent`||t.inflightIntents.has(n))return;let i=new AbortController;t.inflightIntents.set(n,i);try{let e=await t.opts.api.createIntent({purpose:r.purpose,contentType:r.file.type||`application/octet-stream`,size:r.file.size,filename:r.file.name,checksum:r.fingerprint.checksum},{signal:i.signal});t.inflightIntents.delete(n);let a=t.state.items.get(n);if(!a||a.phase!==`creating_intent`)return;let o=f(e,e.strategy);if(o){let e={code:`validation_failed`,message:`Invalid intent from backend: ${o.message}`,cause:o,retryable:!1};t.applyInternal({type:`intent.failed`,localId:n,error:e,retryable:!1});return}t.applyInternal({type:`intent.ok`,localId:n,intent:e})}catch(a){if(t.inflightIntents.delete(n),i.signal.aborted)return;let o=_(a,t.opts.errorNormalizer),s={...o,message:`${o.message} (file: ${r.fingerprint.name}, size: ${r.file.size} bytes, purpose: ${r.purpose})`},c=S(t.opts.config,{phase:`intent`,attempt:r.attempt,error:s});t.applyInternal({type:`intent.failed`,localId:n,error:s,retryable:c.retryable}),c.retryable&&c.delayMs!==void 0&&t.enqueueEffect(async()=>{await e(c.delayMs),t.dispatch({type:`retry`,localId:n})})}}async function se(t,n){let r=t.state.items.get(n);if(!r||r.phase!==`completing`||t.inflightCompletes.has(n))return;let i=new AbortController;t.inflightCompletes.set(n,i);try{let e=await t.opts.api.complete({fileId:r.intent.fileId},{signal:i.signal});t.inflightCompletes.delete(n);let a=t.state.items.get(n);if(!a||a.phase!==`completing`)return;t.applyInternal({type:`complete.ok`,localId:n,result:e})}catch(r){if(t.inflightCompletes.delete(n),i.signal.aborted)return;let a=_(r,t.opts.errorNormalizer),o=t.state.items.get(n),s=o?{...a,message:`${a.message} (file: ${o.fingerprint.name}, fileId: ${(v(o)&&o.intent?.fileId)??`unknown`})`}:a,c=o&&`attempt`in o?o.attempt??1:1,l=S(t.opts.config,{phase:`complete`,attempt:c,error:s});t.applyInternal({type:`complete.failed`,localId:n,error:s,retryable:l.retryable}),l.retryable&&l.delayMs!==void 0&&t.enqueueEffect(async()=>{await e(l.delayMs),t.dispatch({type:`retry`,localId:n})})}}async function ce(t,n){let r=t.state.items.get(n);if(!r||r.phase!==`uploading`||!v(r)){t.inflightUploads.delete(n);return}let i=t.inflightUploads.get(n);if(i||(i={controller:new AbortController,mode:`normal`,started:!1},t.inflightUploads.set(n,i)),i.started)return;i.started=!0;let a=i.controller,o=()=>{let e=t.state.items.get(n);!e||e.phase===`canceled`||t.applyInternal({type:`canceled`,localId:n,canceledAt:Date.now()})},s=e=>{let r=t.state.items.get(n);!r||r.phase===`paused`||t.applyInternal({type:`paused`,localId:n,cursor:e,pausedAt:Date.now()})};if(a.signal.aborted){if(t.inflightUploads.delete(n),i.mode===`pause`){let e=t.state.items.get(n),i;r&&v(r)&&(i=r.intent.strategy);let a=(e&&b(e)?e.cursor:void 0)||(i?{strategy:i,value:void 0}:void 0);a&&s(a);return}if(i.mode===`cancel`){o();return}return}let c=r.intent.strategy;if(!t.opts.strategies.has(c)){t.inflightUploads.delete(n);let e={code:`strategy_missing`,message:`Strategy not found: ${c}`,strategy:String(c),retryable:!1};t.applyInternal({type:`upload.failed`,localId:n,error:e,retryable:!1});return}let l=t.opts.strategies.get(c);if(!l){t.inflightUploads.delete(n);let e={code:`strategy_missing`,message:`Strategy not found: ${r.intent.strategy}`,strategy:String(r.intent.strategy),retryable:!1};t.applyInternal({type:`upload.failed`,localId:n,error:e,retryable:!1});return}let u=0,d=t.opts.config.progressThrottleMs;try{if(await l.start({file:r.file,intent:r.intent,signal:a.signal,transport:t.opts.transport,api:t.opts.api,readCursor:()=>{let e=t.state.items.get(n);if(!(!e||!b(e)||!e.cursor)&&e.cursor.strategy===c)return e.cursor.value},persistCursor:e=>{let r={strategy:c,value:e};t.applyInternal({type:`cursor.updated`,localId:n,cursor:r})},reportProgress:e=>{let r=Date.now();r-u<d||(t.applyInternal({type:`upload.progress`,localId:n,uploadedBytes:e.uploadedBytes,totalBytes:e.totalBytes}),u=r)}}),t.inflightUploads.delete(n),i?.mode===`cancel`){o();return}if(a.signal.aborted&&i?.mode===`pause`){let e=t.state.items.get(n);s((e&&b(e)?e.cursor:void 0)||{strategy:c,value:void 0});return}t.applyInternal({type:`upload.ok`,localId:n})}catch(r){if(t.inflightUploads.delete(n),a.signal.aborted){if(i?.mode===`pause`){let e=t.state.items.get(n);s((e&&b(e)?e.cursor:void 0)||{strategy:c,value:void 0});return}if(i?.mode===`cancel`){o();return}o();return}let l=_(r,t.opts.errorNormalizer),u=t.state.items.get(n),d=u?{...l,message:`${l.message} (file: ${u.fingerprint.name}, size: ${(y(u)&&u.file?.size)??0} bytes, phase: ${u.phase})`}:l,f=S(t.opts.config,{phase:`upload`,attempt:1,error:d});t.applyInternal({type:`upload.failed`,localId:n,error:d,retryable:f.retryable}),f.retryable&&f.delayMs!==void 0&&t.enqueueEffect(async()=>{await e(f.delayMs),t.dispatch({type:`retry`,localId:n})})}}function le(e){if(!e.scheduling){e.scheduling=!0;try{ue(e),fe(e),de(e)}finally{e.scheduling=!1}}}function ue(e){for(let t of e.state.items.values())t.phase===`creating_intent`&&(e.inflightIntents.has(t.localId)||e.enqueueEffect(()=>oe(e,t.localId)))}function de(e){for(let t of e.state.items.values())t.phase===`completing`&&(e.inflightCompletes.has(t.localId)||e.enqueueEffect(()=>se(e,t.localId)))}function fe(e){let t=Math.max(1,e.opts.config.maxConcurrentUploads),n=e.inflightUploads.size;for(let t of e.state.items.values())t.phase===`ready`&&g(e.opts,t.purpose)&&e.applyCommand({type:`start`,localId:t.localId});if(n>=t)return;let r=Array.from(e.state.items.values()).filter(pe);if(r.length===0)return;let i=Math.max(0,t-n),a=r.slice(0,i);for(let t of a){if(e.inflightUploads.has(t.localId))continue;let n=new AbortController;e.inflightUploads.set(t.localId,{controller:n,mode:`normal`,started:!1}),e.applyInternal({type:`upload.begin`,localId:t.localId,startedAt:Date.now()}),ce(e,t.localId)}}function pe(e){return e.phase===`queued`}function me(e){let t=Q(e),n=t.persistence,r=null,i=Math.max(0,n?.debounceMs??200),a=async()=>{if(n)try{let e=(n.serialize??V)(s.state,n.version);if(!e.items||Object.keys(e.items).length===0){await n.adapter.clear(n.key);return}await n.adapter.save(n.key,e)}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[UploadEngine] persistence flush failed:`,e)}},o=()=>{if(n){if(i===0){a();return}r&&clearTimeout(r),r=setTimeout(()=>void a(),i)}},s={opts:t,state:t.initialState??{items:new Map},listeners:new Set,emitter:q(),reduce:J(),inflightUploads:new Map,inflightIntents:new Map,inflightCompletes:new Map,effectQueue:[],processingEffects:!1,scheduling:!1,notify(){s.listeners.forEach(e=>{e()})},scheduleWork(){le(s)},applyInternal(e){let t=s.state;s.state=s.reduce(t,e),s.opts.hooks?.onInternalEvent?.(e,s.state),he(s,e);let n=Z(s.opts,s.state);n&&(s.state=n),o(),s.notify(),s.scheduleWork()},applyCommand(e){let t=s.state,n=s.reduce(t,e);s.state=n,ge(s,e,t,n);let r=Z(s.opts,s.state);r&&(s.state=r),o(),s.notify(),s.scheduleWork()},enqueueEffect(e){s.effectQueue.push(e),s.processingEffects||s.processEffects()},async processEffects(){for(s.processingEffects=!0;s.effectQueue.length>0;){let e=s.effectQueue.shift();if(e)try{await e()}catch(e){if(typeof window<`u`&&process.env.NODE_ENV===`development`){let t=e instanceof Error?e:Error(String(e)),n={queueLength:s.effectQueue.length,processing:s.processingEffects,timestamp:new Date().toISOString()};console.error(`[UploadEngine] Effect error:`,t,n)}}}s.processingEffects=!1},dispatch:()=>{throw Error(`[UploadEngine] dispatch not initialized`)}};if(n&&!t.initialState&&typeof window<`u`)try{let e=n.adapter.load(n.key);Promise.resolve(e).then(e=>{if(!e)return;let r=(n.deserialize??H)(e,{isPurpose:n.isPurpose,isIntent:n.isIntent,hasStrategy:t.strategies.has});if(r){if(s.state.items.size===0)s.state=r;else{let e=new Map(s.state.items);for(let[t,n]of r.items.entries())e.has(t)||e.set(t,n);s.state={...s.state,items:e}}s.notify(),s.scheduleWork()}}).catch(()=>void 0)}catch{}return s}function Q(e){return{...e,config:s(e.config),transport:e.transport??M()}}function he(e,t){switch(t.type){case`files.added`:for(let n of t.items)e.emitter.emit(`file.added`,{localId:n.localId,purpose:n.purpose,file:n.file,fingerprint:n.fingerprint});break;case`validation.ok`:e.emitter.emit(`validation.ok`,{localId:t.localId}),e.emitter.emit(`intent.creating`,{localId:t.localId});break;case`validation.failed`:e.emitter.emit(`validation.failed`,{localId:t.localId,reason:t.reason});break;case`intent.ok`:e.emitter.emit(`intent.created`,{localId:t.localId,intent:t.intent});break;case`intent.failed`:e.emitter.emit(`intent.failed`,{localId:t.localId,error:t.error,retryable:t.retryable});break;case`upload.begin`:e.emitter.emit(`upload.started`,{localId:t.localId});break;case`upload.progress`:{let n=t.totalBytes>0?Math.min(100,t.uploadedBytes/t.totalBytes*100):0;e.emitter.emit(`upload.progress`,{localId:t.localId,pct:n,uploadedBytes:t.uploadedBytes,totalBytes:t.totalBytes});break}case`cursor.updated`:e.emitter.emit(`upload.cursor`,{localId:t.localId,cursor:t.cursor});break;case`upload.ok`:e.emitter.emit(`upload.completing`,{localId:t.localId});break;case`upload.failed`:e.emitter.emit(`upload.error`,{localId:t.localId,error:t.error,retryable:t.retryable});break;case`dedupe.ok`:e.emitter.emit(`upload.completed`,{localId:t.localId,result:t.result,completedBy:`dedupe`});break;case`complete.ok`:e.emitter.emit(`upload.completed`,{localId:t.localId,result:t.result,completedBy:`upload`});break;case`complete.failed`:e.emitter.emit(`upload.error`,{localId:t.localId,error:t.error,retryable:t.retryable});break;case`paused`:e.emitter.emit(`upload.paused`,{localId:t.localId,cursor:t.cursor});break;case`canceled`:e.emitter.emit(`upload.canceled`,{localId:t.localId});break;case`fingerprint.updated`:break}}function ge(e,t,n,r){if(t.type===`start`||t.type===`resume`){let i=n.items.get(t.localId),a=r.items.get(t.localId);if(!i||!a||a.phase!==`queued`)return;i.phase===`paused`?e.emitter.emit(`upload.resumed`,{localId:t.localId}):e.emitter.emit(`upload.queued`,{localId:t.localId})}if(t.type===`cancel`){let i=n.items.get(t.localId),a=r.items.get(t.localId);if(!i||!a)return;a.phase===`canceled`&&i.phase!==`canceled`&&e.emitter.emit(`upload.canceled`,{localId:t.localId})}}function $(e){let t=me(e);t.dispatch=e=>E(t,e);let n={on:t.emitter.on.bind(t.emitter),off:t.emitter.off.bind(t.emitter),dispatch:t.dispatch,getSnapshot:()=>t.state};for(let t of e.plugins||[])try{t.setup(n)}catch(e){typeof window<`u`&&process.env.NODE_ENV===`development`&&console.error(`[UploadEngine] Plugin "${t.name}" failed to setup:`,e)}return{...n,subscribe:e=>(t.listeners.add(e),()=>t.listeners.delete(e)),waitFor(e){return _e(t,e)}}}function _e(e,t){if(t.length===0)return Promise.resolve([]);let n=new Set(t),r=new Map,i=()=>{for(let t of n){let i=e.state.items.get(t);if(!i){r.set(t,{localId:t,status:`missing`}),n.delete(t);continue}if(i.phase===`completed`){r.set(t,{localId:t,status:`completed`,completedBy:i.completedBy,result:i.result}),n.delete(t);continue}if(i.phase===`error`){r.set(t,{localId:t,status:`error`,error:i.error}),n.delete(t);continue}i.phase===`canceled`&&(r.set(t,{localId:t,status:`canceled`}),n.delete(t))}};return i(),n.size===0?Promise.resolve(t.map(e=>r.get(e)??{localId:e,status:`missing`})):new Promise(a=>{let o=()=>{i(),n.size===0&&(e.listeners.delete(o),a(t.map(e=>r.get(e)??{localId:e,status:`missing`})))};e.listeners.add(o)})}function ve(e){return $(e)}export{t as C,r as S,c as _,H as a,a as b,R as c,p as d,m as f,l as g,f as h,q as i,L as l,d as m,$ as n,V as o,u as p,J as r,B as s,ve as t,M as u,s as v,n as w,o as x,i as y};
@@ -137,12 +137,11 @@ type XhrCommonArgs = {
137
137
  onProgress?: (loaded: number, total: number) => void;
138
138
  };
139
139
  /**
140
- * Creates a browser-native XHR transport.
141
- * We use XMLHttpRequest instead of fetch because fetch does not yet support
142
- * upload progress events in a standard cross-browser way.
140
+ * Browser-native XHR transport. Uses `XMLHttpRequest` over `fetch` because
141
+ * `fetch` lacks cross-browser upload-progress events.
143
142
  */
144
143
  declare function createXHRTransport(): UploadTransport & {
145
- patch?: (args: XhrCommonArgs & {
144
+ /** Optional PATCH for tus-style strategies. */patch?: (args: XhrCommonArgs & {
146
145
  body: Blob | ArrayBuffer;
147
146
  }) => Promise<{
148
147
  headers: Record<string, string>;
@@ -291,11 +290,7 @@ type UploadProgress = {
291
290
  //#region src/core/engine/internal-events.types.d.ts
292
291
  /**
293
292
  * Internal events emitted by effects and consumed by the reducer.
294
- *
295
- * These are not the same as public events. They are designed for correctness and state transitions.
296
- *
297
- * @typeParam M - Intent map
298
- * @typeParam C - Cursor map
293
+ * Distinct from the public event surface — these drive state transitions.
299
294
  */
300
295
  type InternalEvent<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase> = {
301
296
  type: 'files.added';
@@ -370,21 +365,9 @@ type InternalEvent<M extends IntentMap, C extends CursorMap<M>, P extends string
370
365
  localId: string;
371
366
  canceledAt: number;
372
367
  };
373
- /**
374
- * All possible upload phases.
375
- *
376
- * Useful for constraints and UI filtering.
377
- */
368
+ /** Every upload phase; the `phase` discriminator of {@link UploadItem}. */
378
369
  type UploadPhase = 'validating' | 'creating_intent' | 'ready' | 'queued' | 'uploading' | 'paused' | 'completing' | 'completed' | 'error' | 'canceled';
379
- /**
380
- * State of a single upload item.
381
- *
382
- * This is a discriminated union on `phase`.
383
- *
384
- * @typeParam M - Intent map
385
- * @typeParam C - Cursor map
386
- * @typeParam P - Purpose union
387
- */
370
+ /** Discriminated union on `phase` describing a single upload's state. */
388
371
  type UploadItem<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase> = {
389
372
  /** Validating file against rules. */phase: 'validating';
390
373
  localId: string;
@@ -921,20 +904,55 @@ type UploadPlugin<M extends IntentMap, C extends CursorMap<M>, P extends string,
921
904
  type UploadClient<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase> = UploadStore<M, C, P, R>;
922
905
  declare function createUploadClient<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>(opts: StoreOptions<M, C, P, R>): UploadClient<M, C, P, R>;
923
906
  //#endregion
924
- //#region src/core/engine/validation/file.d.ts
925
- declare function validateFile<P extends string>(file: File, purpose: P, config: UploadConfig<P>): RejectReason | null;
926
- //#endregion
927
- //#region src/core/engine/validation/intent.d.ts
928
- declare function validateIntent(intent: unknown, strategy: string): Error | null;
929
- //#endregion
930
- //#region src/core/engine/validation/list.d.ts
931
- declare function validateFileList<P extends string>(files: File[], purpose: P, config: UploadConfig<P>, existingCount?: number): {
907
+ //#region src/core/engine/validation.d.ts
908
+ /**
909
+ * Validate one file against `config.validation[purpose]` rules.
910
+ *
911
+ * Empty files are always rejected.
912
+ *
913
+ * @param file File to validate.
914
+ * @param purpose Purpose key used to look up rules.
915
+ * @param config Resolved upload config.
916
+ * @template P Purpose discriminator string.
917
+ * @returns `null` when valid, otherwise a {@link Contracts.RejectReason}.
918
+ * @author wildduck2 <https://github.com/wildduck2>
919
+ */
920
+ declare function validateFile<P extends string>(file: File, purpose: P, config: Client.IUploadConfig<P>): Contracts.RejectReason | null;
921
+ /**
922
+ * Validate a batch of files and split into accepted vs rejected.
923
+ *
924
+ * Enforces `maxFiles` against the existing count plus the running additions
925
+ * so a single `addFiles` call cannot overshoot.
926
+ *
927
+ * @param files Files to validate.
928
+ * @param purpose Purpose key used to look up rules.
929
+ * @param config Resolved upload config.
930
+ * @param existingCount Items already in the store for this purpose.
931
+ * @template P Purpose discriminator string.
932
+ * @returns Object with `valid` files and `rejected` files plus reasons.
933
+ * @author wildduck2 <https://github.com/wildduck2>
934
+ */
935
+ declare function validateFileList<P extends string>(files: File[], purpose: P, config: Client.IUploadConfig<P>, existingCount?: number): {
932
936
  valid: File[];
933
937
  rejected: Array<{
934
938
  file: File;
935
- reason: RejectReason;
939
+ reason: Contracts.RejectReason;
936
940
  }>;
937
941
  };
942
+ /**
943
+ * Validate an intent payload returned by the backend before the engine
944
+ * commits to it.
945
+ *
946
+ * Rejects malformed shapes, protocol-foreign URLs, and cross-field
947
+ * inconsistencies (e.g. `partCount * partSize` cannot cover the file).
948
+ *
949
+ * @param intent Raw intent value from `createIntent`.
950
+ * @param strategy Expected `strategy` discriminator.
951
+ * @param fileSize File byte length; enables multipart cross-checks.
952
+ * @returns `Error` describing the failure, or `null` if valid.
953
+ * @author wildduck2 <https://github.com/wildduck2>
954
+ */
955
+ declare function validateIntent(intent: unknown, strategy: string, fileSize?: number): Error | null;
938
956
  //#endregion
939
957
  //#region src/core/utils/async.d.ts
940
958
  declare function sleep(ms: number): Promise<void>;
@@ -984,4 +1002,4 @@ declare function isRecord(value: unknown): value is Record<string, unknown>;
984
1002
  //#region src/core/utils/id.d.ts
985
1003
  declare function generateId(): string;
986
1004
  //#endregion
987
- export { RejectReason as $, serializeSnapshot as A, UploadItem as B, StoreOptions as C, createTypedEmitter as D, UploadEventMap as E, PersistedUploadItem as F, UploadCommand as G, UploadProgress as H, PersistenceAdapter as I, resolveUploadConfig as J, UploadConfig as K, UploadState as L, LocalStorageAdapter as M, IndexedDBAdapter as N, TypedEmitter as O, PersistedSnapshot as P, BuiltInUploadError as Q, createReducer as R, PersistenceOptions as S, UploadStore as T, UploadCompletionKind as U, UploadPhase as V, UploadOutcome as W, createXHRTransport as X, RetryDecision as Y, FileFingerprint as Z, UploadHooks as _, DEFAULT_MAX_ATTEMPTS as a, DeserializeContext as b, DEFAULT_PROGRESS_THROTTLE_MS as c, sleep as d, UploadError as et, validateFileList as f, createUploadClient as g, UploadClient as h, fingerprintMatches as i, MemoryAdapter as j, deserializeSnapshot as k, DEFAULT_RETRY_DELAY_BASE_MS as l, validateFile as m, isRecord as n, UploadValidationRules as nt, DEFAULT_MAX_CONCURRENT_UPLOADS as o, validateIntent as p, UploadConfigInput as q, computeFingerprint as r, DEFAULT_MAX_ITEMS as s, generateId as t, UploadErrorBase as tt, DEFAULT_RETRY_DELAY_MAX_MS as u, UploadPlugin as v, StoreRuntime as w, InflightUpload as x, createUploadStore as y, InternalEvent as z };
1005
+ export { RejectReason as $, serializeSnapshot as A, UploadItem as B, StoreOptions as C, createTypedEmitter as D, UploadEventMap as E, PersistedUploadItem as F, UploadCommand as G, UploadProgress as H, PersistenceAdapter as I, resolveUploadConfig as J, UploadConfig as K, UploadState as L, LocalStorageAdapter as M, IndexedDBAdapter as N, TypedEmitter as O, PersistedSnapshot as P, BuiltInUploadError as Q, createReducer as R, PersistenceOptions as S, UploadStore as T, UploadCompletionKind as U, UploadPhase as V, UploadOutcome as W, createXHRTransport as X, RetryDecision as Y, FileFingerprint as Z, UploadHooks as _, DEFAULT_MAX_ATTEMPTS as a, DeserializeContext as b, DEFAULT_PROGRESS_THROTTLE_MS as c, sleep as d, UploadError as et, validateFile as f, createUploadClient as g, UploadClient as h, fingerprintMatches as i, MemoryAdapter as j, deserializeSnapshot as k, DEFAULT_RETRY_DELAY_BASE_MS as l, validateIntent as m, isRecord as n, UploadValidationRules as nt, DEFAULT_MAX_CONCURRENT_UPLOADS as o, validateFileList as p, UploadConfigInput as q, computeFingerprint as r, DEFAULT_MAX_ITEMS as s, generateId as t, UploadErrorBase as tt, DEFAULT_RETRY_DELAY_MAX_MS as u, UploadPlugin as v, StoreRuntime as w, InflightUpload as x, createUploadStore as y, InternalEvent as z };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as AnyCursor, c as WithSignal, d as IntentBase, f as IntentMap, i as UploadTransport, l as UploadResultBase, n as StrategyRegistry, o as CursorMap, p as StrategyKey, r as UploadStrategy, s as UploadApi, t as StrategyCtx, u as AnyIntent } from "./strategy.types-gg9ZStRe.js";
2
- import { $ as RejectReason, A as serializeSnapshot, B as UploadItem, C as StoreOptions, D as createTypedEmitter, E as UploadEventMap, F as PersistedUploadItem, G as UploadCommand, H as UploadProgress, I as PersistenceAdapter, J as resolveUploadConfig, K as UploadConfig, L as UploadState, M as LocalStorageAdapter, N as IndexedDBAdapter, O as TypedEmitter, P as PersistedSnapshot, Q as BuiltInUploadError, R as createReducer, S as PersistenceOptions, T as UploadStore, U as UploadCompletionKind, V as UploadPhase, W as UploadOutcome, X as createXHRTransport, Y as RetryDecision, Z as FileFingerprint, _ as UploadHooks, a as DEFAULT_MAX_ATTEMPTS, b as DeserializeContext, c as DEFAULT_PROGRESS_THROTTLE_MS, d as sleep, et as UploadError, f as validateFileList, g as createUploadClient, h as UploadClient, i as fingerprintMatches, j as MemoryAdapter, k as deserializeSnapshot, l as DEFAULT_RETRY_DELAY_BASE_MS, m as validateFile, n as isRecord, nt as UploadValidationRules, o as DEFAULT_MAX_CONCURRENT_UPLOADS, p as validateIntent, q as UploadConfigInput, r as computeFingerprint, s as DEFAULT_MAX_ITEMS, t as generateId, tt as UploadErrorBase, u as DEFAULT_RETRY_DELAY_MAX_MS, v as UploadPlugin, w as StoreRuntime, x as InflightUpload, y as createUploadStore, z as InternalEvent } from "./index-BXg9WIce.js";
2
+ import { $ as RejectReason, A as serializeSnapshot, B as UploadItem, C as StoreOptions, D as createTypedEmitter, E as UploadEventMap, F as PersistedUploadItem, G as UploadCommand, H as UploadProgress, I as PersistenceAdapter, J as resolveUploadConfig, K as UploadConfig, L as UploadState, M as LocalStorageAdapter, N as IndexedDBAdapter, O as TypedEmitter, P as PersistedSnapshot, Q as BuiltInUploadError, R as createReducer, S as PersistenceOptions, T as UploadStore, U as UploadCompletionKind, V as UploadPhase, W as UploadOutcome, X as createXHRTransport, Y as RetryDecision, Z as FileFingerprint, _ as UploadHooks, a as DEFAULT_MAX_ATTEMPTS, b as DeserializeContext, c as DEFAULT_PROGRESS_THROTTLE_MS, d as sleep, et as UploadError, f as validateFile, g as createUploadClient, h as UploadClient, i as fingerprintMatches, j as MemoryAdapter, k as deserializeSnapshot, l as DEFAULT_RETRY_DELAY_BASE_MS, m as validateIntent, n as isRecord, nt as UploadValidationRules, o as DEFAULT_MAX_CONCURRENT_UPLOADS, p as validateFileList, q as UploadConfigInput, r as computeFingerprint, s as DEFAULT_MAX_ITEMS, t as generateId, tt as UploadErrorBase, u as DEFAULT_RETRY_DELAY_MAX_MS, v as UploadPlugin, w as StoreRuntime, x as InflightUpload, y as createUploadStore, z as InternalEvent } from "./index-C38Jmizu.js";
3
3
  import { PickItemsByPhase, UploadProvider, UploaderActions, createUploadFactory, useOptionalUploadStore, useUploadStore, useUploader, useUploaderActions } from "./react/index.js";
4
4
  import { MultipartCursor, MultipartIntent, PostCursor, PostIntent, PostStrategy, createStrategyRegistry, multipartStrategy } from "./strategies/index.js";
5
5
  export { AnyCursor, AnyIntent, BuiltInUploadError, CursorMap, DEFAULT_MAX_ATTEMPTS, DEFAULT_MAX_CONCURRENT_UPLOADS, DEFAULT_MAX_ITEMS, DEFAULT_PROGRESS_THROTTLE_MS, DEFAULT_RETRY_DELAY_BASE_MS, DEFAULT_RETRY_DELAY_MAX_MS, DeserializeContext, FileFingerprint, IndexedDBAdapter, InflightUpload, IntentBase, IntentMap, InternalEvent, LocalStorageAdapter, MemoryAdapter, MultipartCursor, MultipartIntent, PersistedSnapshot, PersistedUploadItem, PersistenceAdapter, PersistenceOptions, PickItemsByPhase, PostCursor, PostIntent, PostStrategy, RejectReason, RetryDecision, StoreOptions, StoreRuntime, StrategyCtx, StrategyKey, StrategyRegistry, TypedEmitter, UploadApi, UploadClient, UploadCommand, UploadCompletionKind, UploadConfig, UploadConfigInput, UploadError, UploadErrorBase, UploadEventMap, UploadHooks, UploadItem, UploadOutcome, UploadPhase, UploadPlugin, UploadProgress, UploadProvider, UploadResultBase, UploadState, UploadStore, UploadStrategy, UploadTransport, UploadValidationRules, UploaderActions, WithSignal, computeFingerprint, createReducer, createStrategyRegistry, createTypedEmitter, createUploadClient, createUploadFactory, createUploadStore, createXHRTransport, deserializeSnapshot, fingerprintMatches, generateId, isRecord, multipartStrategy, resolveUploadConfig, serializeSnapshot, sleep, useOptionalUploadStore, useUploadStore, useUploader, useUploaderActions, validateFile, validateFileList, validateIntent };