@gentleduck/upload 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +297 -0
  2. package/dist/core/index.d.ts +2 -3
  3. package/dist/core/index.js +1 -1
  4. package/dist/core-BVgloTiP.js +10 -0
  5. package/dist/index-DCqyBVcx.d.ts +1441 -0
  6. package/dist/index-DsZHPmJm.d.ts +225 -0
  7. package/dist/index-KROXtLQh.d.ts +281 -0
  8. package/dist/index.d.ts +4 -5
  9. package/dist/index.js +1 -1
  10. package/dist/react/index.d.ts +2 -90
  11. package/dist/react/index.js +1 -1
  12. package/dist/react-euPrDv24.js +1 -0
  13. package/dist/strategies/index.d.ts +2 -56
  14. package/dist/strategies/index.js +1 -1
  15. package/dist/strategies-CUF0s68j.js +1 -0
  16. package/package.json +3 -2
  17. package/src/__tests__/emitter.test.ts +85 -0
  18. package/src/__tests__/fingerprint.test.ts +49 -0
  19. package/src/__tests__/guards.test.ts +40 -0
  20. package/src/__tests__/id.test.ts +67 -0
  21. package/src/__tests__/mime-sniff.test.ts +142 -0
  22. package/src/__tests__/persistence.test.ts +191 -0
  23. package/src/__tests__/sanitize-filename.test.ts +109 -0
  24. package/src/__tests__/sleep.test.ts +34 -0
  25. package/src/__tests__/static-analysis.test.ts +145 -0
  26. package/src/__tests__/store-libs.test.ts +192 -0
  27. package/src/__tests__/transport-fetch.test.ts +108 -0
  28. package/src/__tests__/transport-libs.test.ts +42 -0
  29. package/src/__tests__/transport.test.ts +182 -0
  30. package/src/core/__tests__/errors.test.ts +152 -0
  31. package/src/core/client.ts +3 -0
  32. package/src/core/contracts/contracts.types.ts +373 -0
  33. package/src/core/contracts/index.ts +3 -8
  34. package/src/core/contracts/transport/index.ts +3 -2
  35. package/src/core/contracts/transport/transport.fetch.ts +203 -0
  36. package/src/core/contracts/transport/transport.libs.ts +13 -39
  37. package/src/core/contracts/transport/transport.ts +96 -90
  38. package/src/core/contracts/transport/transport.types.ts +71 -35
  39. package/src/core/engine/__tests__/config-nan-bypass.test.ts +40 -0
  40. package/src/core/engine/engine.libs.ts +35 -0
  41. package/src/core/engine/engine.types.ts +490 -0
  42. package/src/core/engine/index.ts +3 -8
  43. package/src/core/engine/reducer.ts +93 -53
  44. package/src/core/engine/store/handlers/add-file.ts +43 -48
  45. package/src/core/engine/store/handlers/cancel.ts +13 -8
  46. package/src/core/engine/store/handlers/clean-up.ts +11 -8
  47. package/src/core/engine/store/handlers/create-intent.ts +82 -27
  48. package/src/core/engine/store/handlers/finalize.ts +30 -13
  49. package/src/core/engine/store/handlers/index.ts +7 -7
  50. package/src/core/engine/store/handlers/pause.ts +8 -6
  51. package/src/core/engine/store/handlers/run-upload.ts +54 -39
  52. package/src/core/engine/store/index.ts +27 -21
  53. package/src/core/engine/store/store.dispatch.ts +10 -8
  54. package/src/core/engine/store/store.libs.ts +197 -94
  55. package/src/core/engine/store/store.runtime.ts +52 -53
  56. package/src/core/engine/store/store.schedule.ts +27 -21
  57. package/src/core/engine/store/store.types.ts +177 -227
  58. package/src/core/engine/validation/__tests__/intent.test.ts +70 -0
  59. package/src/core/engine/validation/file.ts +8 -4
  60. package/src/core/engine/validation/index.ts +1 -0
  61. package/src/core/engine/validation/intent.ts +49 -27
  62. package/src/core/engine/validation/list.ts +14 -15
  63. package/src/core/engine/validation/mime.ts +39 -0
  64. package/src/core/errors.ts +271 -0
  65. package/src/core/index.ts +28 -5
  66. package/src/core/persistence/__tests__/deserialize-nan-bypass.test.ts +100 -0
  67. package/src/core/persistence/__tests__/persistence.test.ts +85 -0
  68. package/src/core/persistence/adapters.indexeddb.ts +9 -3
  69. package/src/core/persistence/adapters.local.ts +9 -3
  70. package/src/core/persistence/adapters.memory.ts +35 -13
  71. package/src/core/persistence/index.ts +5 -5
  72. package/src/core/persistence/persistence.ts +73 -60
  73. package/src/core/persistence/persistence.types.ts +140 -60
  74. package/src/core/utils/__tests__/guards.test.ts +93 -0
  75. package/src/core/utils/__tests__/url-safety.test.ts +112 -0
  76. package/src/core/utils/async.ts +7 -2
  77. package/src/core/utils/constants.ts +3 -9
  78. package/src/core/utils/emitter/emitter.ts +126 -0
  79. package/src/core/utils/emitter/emitter.types.ts +33 -16
  80. package/src/core/utils/emitter/index.ts +2 -74
  81. package/src/core/utils/fingerprint.ts +16 -5
  82. package/src/core/utils/guards.ts +91 -1
  83. package/src/core/utils/hash/__tests__/sha256.test.ts +61 -0
  84. package/src/core/utils/hash/index.ts +126 -0
  85. package/src/core/utils/hash/sha256.ts +179 -0
  86. package/src/core/utils/id.ts +33 -1
  87. package/src/core/utils/index.ts +15 -6
  88. package/src/core/utils/mime-sniff.ts +119 -0
  89. package/src/core/utils/sanitize-filename.ts +85 -0
  90. package/src/core/utils/url-safety.ts +156 -0
  91. package/src/react/__tests__/use-dropzone.test.ts +64 -0
  92. package/src/react/index.ts +5 -2
  93. package/src/react/upload-provider.tsx +59 -95
  94. package/src/react/uploader.types.ts +80 -0
  95. package/src/react/use-dropzone.tsx +226 -0
  96. package/src/react/use-uploader.tsx +97 -93
  97. package/src/strategies/_shared/__tests__/retry.test.ts +120 -0
  98. package/src/strategies/_shared/retry.ts +79 -0
  99. package/src/strategies/index.ts +9 -15
  100. package/src/strategies/multipart/__tests__/multipart.test.ts +229 -0
  101. package/src/strategies/multipart/index.ts +211 -116
  102. package/src/strategies/post/__tests__/post-url-safety.test.ts +119 -0
  103. package/src/strategies/post/index.ts +113 -71
  104. package/src/strategies/put/__tests__/put.test.ts +132 -0
  105. package/src/strategies/put/index.ts +139 -0
  106. package/src/strategies/registry/index.ts +12 -9
  107. package/src/strategies/tus/__tests__/tus.test.ts +159 -0
  108. package/src/strategies/tus/index.ts +211 -0
  109. package/dist/async-DxRWu4le.js +0 -1
  110. package/dist/core-BNxznRif.js +0 -2
  111. package/dist/index-BXg9WIce.d.ts +0 -987
  112. package/dist/strategy.types-gg9ZStRe.d.ts +0 -225
  113. package/src/core/client/config.types.ts +0 -77
  114. package/src/core/client/index.ts +0 -4
  115. package/src/core/client/plugins.types.ts +0 -46
  116. package/src/core/client/retry.types.ts +0 -6
  117. package/src/core/client/uploadClient.ts +0 -19
  118. package/src/core/contracts/backendApi.ts +0 -62
  119. package/src/core/contracts/cursor.types.ts +0 -23
  120. package/src/core/contracts/errors.types.ts +0 -67
  121. package/src/core/contracts/fingerprint.types.ts +0 -18
  122. package/src/core/contracts/intent.types.ts +0 -39
  123. package/src/core/contracts/result.types.ts +0 -11
  124. package/src/core/contracts/strategy.types.ts +0 -45
  125. package/src/core/engine/commands.types.ts +0 -17
  126. package/src/core/engine/event-map.types.ts +0 -45
  127. package/src/core/engine/internal-events.types.ts +0 -222
  128. package/src/core/engine/outcome.types.ts +0 -9
  129. package/src/core/engine/progress.types.ts +0 -15
package/README.md ADDED
@@ -0,0 +1,297 @@
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
+ A pure-reducer core, persistence-aware and resume-after-refresh, with bounded upload
27
+ concurrency and zero coupling to a specific backend or UI library. The engine stays
28
+ protocol-agnostic: your backend returns an intent, the engine dispatches on its `strategy` field
29
+ to a registered strategy that moves the bytes.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ bun add @gentleduck/upload
35
+ # peer deps for the React bindings
36
+ bun add react react-dom
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```ts
42
+ import { createUploadStore } from '@gentleduck/upload/core'
43
+ import { PutStrategy, createStrategyRegistry } from '@gentleduck/upload/strategies'
44
+
45
+ type Intents = { put: PutStrategy.Intent }
46
+ type Cursors = { put?: PutStrategy.Cursor }
47
+ type Purpose = 'attachment'
48
+ type Result = { fileId: string; key: string }
49
+
50
+ const strategies = createStrategyRegistry<Intents, Cursors, Purpose, Result>()
51
+ strategies.set(PutStrategy({ allowedHosts: ['uploads.example.com'] }))
52
+
53
+ const store = createUploadStore<Intents, Cursors, Purpose, Result>({
54
+ strategies,
55
+ api: {
56
+ // Ask your backend to presign the upload.
57
+ async createIntent({ filename, contentType, size }) {
58
+ const res = await fetch('/api/sign', {
59
+ method: 'POST',
60
+ body: JSON.stringify({ filename, contentType, size }),
61
+ })
62
+ return res.json() // -> { strategy: 'put', fileId, url, headers? }
63
+ },
64
+ // Finalize once the bytes are transferred.
65
+ async complete({ fileId }) {
66
+ return { fileId, key: `attachments/${fileId}` }
67
+ },
68
+ },
69
+ config: { maxConcurrentUploads: 3, autoStart: ['attachment'] },
70
+ })
71
+
72
+ store.dispatch({ type: 'addFiles', files, purpose: 'attachment' })
73
+ ```
74
+
75
+ The `createXHRTransport()` browser transport is installed automatically. Pass
76
+ `transport: createFetchTransport()` to run in Node / SSR / edge / workers.
77
+
78
+ ## Strategies
79
+
80
+ A strategy is the thin layer that turns an intent into network calls. Four ship built in;
81
+ register the ones you need and let `createIntent` pick per file.
82
+
83
+ | Strategy | Factory | Use case | Resumable |
84
+ | --- | --- | --- | --- |
85
+ | PUT | `PutStrategy(opts?)` | Single presigned PUT (S3 `putObject`, signed PUT) | No |
86
+ | POST | `PostStrategy(opts?)` | Presigned `multipart/form-data` (S3 POST policy) | No |
87
+ | Multipart | `multipartStrategy(opts?)` | Large files, concurrent parts, per-part resume | Yes |
88
+ | tus | `TusStrategy(opts?)` | Resumable uploads over the tus protocol | Yes |
89
+
90
+ ```ts
91
+ import {
92
+ PutStrategy,
93
+ PostStrategy,
94
+ multipartStrategy,
95
+ TusStrategy,
96
+ createStrategyRegistry,
97
+ } from '@gentleduck/upload/strategies'
98
+
99
+ const strategies = createStrategyRegistry<Intents, Cursors, Purpose, Result>()
100
+ strategies.set(PutStrategy({ allowedHosts: ['uploads.example.com'] }))
101
+ strategies.set(PostStrategy())
102
+ strategies.set(multipartStrategy({ maxPartConcurrency: 4 }))
103
+ strategies.set(TusStrategy({ allowedHosts: ['tus.example.com'] }))
104
+ ```
105
+
106
+ Every backend-supplied URL (PUT / POST / multipart part / tus) runs through the `validateUploadUrl`
107
+ SSRF guard before any byte leaves the client. Set `allowedHosts` to lock the host; keep
108
+ `allowPrivateHosts` at its default `false` to block loopback / RFC1918 / cloud-metadata targets.
109
+ `PutStrategy`, `PostStrategy`, and `TusStrategy` retry transient network failures
110
+ (timeouts / 5xx / `ECONNRESET`) with exponential backoff.
111
+
112
+ ## React
113
+
114
+ ```tsx
115
+ import {
116
+ UploadProvider,
117
+ useUploader,
118
+ useDropzone,
119
+ } from '@gentleduck/upload/react'
120
+
121
+ function Uploader() {
122
+ const { getRootProps, getInputProps, isDragging } = useDropzone({
123
+ purpose: 'attachment',
124
+ accept: 'image/*,.pdf',
125
+ })
126
+ const { items, dispatch } = useUploader<Intents, Cursors, Purpose, Result>()
127
+
128
+ return (
129
+ <div {...getRootProps()} data-active={isDragging}>
130
+ <input {...getInputProps()} />
131
+ {items.map((item) => (
132
+ <Row
133
+ key={item.localId}
134
+ item={item}
135
+ onCancel={() => dispatch({ type: 'cancel', localId: item.localId })}
136
+ />
137
+ ))}
138
+ </div>
139
+ )
140
+ }
141
+
142
+ // Wrap once so descendants can reach the store.
143
+ <UploadProvider store={store}>
144
+ <Uploader />
145
+ </UploadProvider>
146
+ ```
147
+
148
+ React exports: `UploadProvider`, `useUploader`, `useUploaderActions`, `useDropzone`,
149
+ `createUploadFactory`, `useUploadStore`, `isUploadStore` (plus the pure helpers
150
+ `fileMatchesAccept`, `matchesAcceptToken`, `selectFiles`).
151
+
152
+ ## Subpath Exports
153
+
154
+ ```ts
155
+ import { createUploadStore, createFetchTransport } from '@gentleduck/upload/core'
156
+ import { UploadProvider, useUploader, useDropzone } from '@gentleduck/upload/react'
157
+ import { PutStrategy, TusStrategy, createStrategyRegistry } from '@gentleduck/upload/strategies'
158
+ ```
159
+
160
+ ## Features
161
+
162
+ - **Pure reducer** — command/event-driven state machine; effects are isolated.
163
+ - **Typed end-to-end** — intents, cursors, purposes, and results are quad-generics threaded
164
+ through the engine (`M`, `C`, `P`, `R`).
165
+ - **Four built-in strategies** — PUT, POST, multipart, tus — plus a registry for your own.
166
+ - **Two transports** — `createXHRTransport()` (browser, real upload progress) and
167
+ `createFetchTransport()` (Node / SSR / edge / workers), with optional `get()` streaming
168
+ download and `head()`.
169
+ - **Resume after refresh** — Memory / LocalStorage / IndexedDB persistence adapters; cursor-aware
170
+ re-binding via the `rebind` command.
171
+ - **Retry policy** — exponential backoff with per-phase attempt escalation (intent / upload /
172
+ complete), overridable via `config.retryPolicy`.
173
+ - **Bounded concurrency** — `config.maxConcurrentUploads` caps parallel uploads; queued items
174
+ wait for a free slot.
175
+ - **Content dedupe, off-thread** — SHA-256 checksums hash inline for small files and
176
+ incrementally in a Web Worker for large ones, so `checksumMaxSize` is a boundary, not a skip —
177
+ big files stay deduplicated without freezing the UI.
178
+ - **Headless React** — `useDropzone` gives drag-and-drop + file-picker prop getters with zero
179
+ markup or styling.
180
+ - **SSRF guard built in** — allowlist + protocol + IPv4/IPv6 private + cloud-metadata + NAT64 +
181
+ 6to4 + IPv4-mapped checks on every upload URL.
182
+ - **Filename + MIME hardening** — NFKC normalize, control-char strip, Windows reserved-name +
183
+ 255-char + path-sep rejection; magic-byte sniff cross-checks the claimed `file.type`.
184
+ - **NaN-safe configs** — every numeric option clamps `NaN` / `Infinity` / negative to its
185
+ default; persisted snapshots reject `NaN` fields.
186
+ - **No DOM coupling** — runs in Workers, Node 22+, or the browser.
187
+ - **Threat model + audit** — STRIDE-mapped `THREAT-MODEL.md` and re-runnable `AUDIT-RESULTS.md`
188
+ checked into the package.
189
+
190
+ ## Persistence
191
+
192
+ Persistence lets a paused or in-flight upload survive a refresh: the snapshot is stored, and a
193
+ `rebind` re-attaches a fresh `File` (validated by fingerprint) so a resumable strategy continues
194
+ from its cursor.
195
+
196
+ ```ts
197
+ import { createUploadStore, IndexedDBAdapter } from '@gentleduck/upload/core'
198
+
199
+ createUploadStore({
200
+ strategies,
201
+ api,
202
+ persistence: {
203
+ key: 'app:uploads',
204
+ version: 1,
205
+ adapter: IndexedDBAdapter, // or LocalStorageAdapter / createMemoryAdapter()
206
+ isPurpose,
207
+ isIntent,
208
+ },
209
+ })
210
+ ```
211
+
212
+ Adapters: `IndexedDBAdapter` and `LocalStorageAdapter` (shared singletons), `MemoryAdapter`
213
+ (shared) or `createMemoryAdapter()` (isolated per store). Bump `persistence.version` in lockstep
214
+ across tabs/deployments; a snapshot at a different version is rejected by `deserializeSnapshot`'s
215
+ version fence rather than loaded.
216
+
217
+ ## Writing a custom strategy
218
+
219
+ A strategy implements one `start()` against the `Contracts.Strategy.Me` contract. `id` must equal
220
+ the intent's `strategy` field; `resumable` is metadata for your UI. Honor `signal`, call
221
+ `reportProgress`, and — if resumable — `persistCursor` at each safe boundary.
222
+
223
+ ```ts
224
+ import type { Contracts } from '@gentleduck/upload/core'
225
+
226
+ type Intents = { myput: { strategy: 'myput'; fileId: string; url: string } }
227
+ type Cursors = { myput?: Record<string, never> }
228
+ type Purpose = 'attachment'
229
+ type Result = { fileId: string; key: string }
230
+
231
+ const myPut: Contracts.Strategy.Me<Intents, Cursors, Purpose, Result, 'myput'> = {
232
+ id: 'myput',
233
+ resumable: false,
234
+ async start(ctx) {
235
+ await ctx.transport.put({
236
+ url: ctx.intent.url,
237
+ body: ctx.file,
238
+ signal: ctx.signal,
239
+ onProgress: (uploadedBytes, totalBytes) => ctx.reportProgress({ uploadedBytes, totalBytes }),
240
+ })
241
+ },
242
+ }
243
+
244
+ strategies.set(myPut)
245
+ ```
246
+
247
+ `start()` is awaited: resolve → the engine transitions to `completing`; throw → the error routes
248
+ through `errorNormalizer` + `retryPolicy`. The `ctx` also carries `intent`, `api`, `readCursor`,
249
+ `persistCursor`, `attempt`, and the injected `transport` (`put` / `postForm` / `patch` and the
250
+ optional `get` / `head`). Use `api.multipart.signPart` / `completeMultipart` for backend
251
+ round-trips a strategy needs mid-flight.
252
+
253
+ ## Commands
254
+
255
+ Dispatch via `store.dispatch(cmd)`.
256
+
257
+ | Command | Purpose |
258
+ | --- | --- |
259
+ | `addFiles` | Validate + insert; schedules checksum + intent |
260
+ | `start` / `startAll` | Move ready items to queued (batched) |
261
+ | `pause` / `pauseAll` | Abort inflight, persist cursor (batched) |
262
+ | `resume` | Re-queue a paused item |
263
+ | `cancel` / `cancelAll` | Abort and mark canceled (batched) |
264
+ | `retry` | Re-attempt the failed phase, bumping the attempt counter |
265
+ | `rebind` | Re-attach a fresh `File` after refresh (fingerprint-validated) |
266
+ | `remove` | Drop the item from state |
267
+
268
+ ## Events
269
+
270
+ Subscribe via `store.on(type, cb)`; payloads are typed from the engine's event map.
271
+
272
+ - `file.added`, `file.rejected`
273
+ - `validation.ok`, `validation.failed`
274
+ - `intent.creating`, `intent.created`, `intent.failed`
275
+ - `upload.queued`, `upload.started`, `upload.progress`, `upload.cursor`
276
+ - `upload.paused`, `upload.resumed`, `upload.canceled`
277
+ - `upload.completing`, `upload.completed`, `upload.error`
278
+
279
+ ## Bundler requirement
280
+
281
+ The engine references `process.env.NODE_ENV` to gate dev-only invariant warnings. Vite / Webpack /
282
+ Rspack / esbuild / Rollup replace this at build time and ship a single branch to production.
283
+ Pure-ESM consumers loading the source without a bundler must set
284
+ `globalThis.process = { env: { NODE_ENV: 'production' } }` before importing the package.
285
+
286
+ ## Tests
287
+
288
+ ```bash
289
+ bun run test
290
+ ```
291
+
292
+ The Vitest suite covers the reducer state machine, persistence, every strategy and transport, the
293
+ incremental hasher, the SSRF/filename/MIME guards, and the React helpers.
294
+
295
+ ## License
296
+
297
+ MIT
@@ -1,3 +1,2 @@
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";
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
+ import { C as UploadUnknownError, D as Transport, E as createXHRTransport, O as Contracts, S as UploadTimeoutError, T as createFetchTransport, _ as UploadHttpError, a as deserializeSnapshot, b as UploadServerError, c as createMemoryAdapter, d as UploadPersistence, f as UploadAbortError, g as UploadErrorContext, h as UploadError, i as Store, l as LocalStorageAdapter, m as UploadEngineError, n as Engine, o as serializeSnapshot, p as UploadAuthError, r as createUploadStore, s as MemoryAdapter, t as resolveUploadConfig, u as IndexedDBAdapter, v as UploadNetworkError, w as UploadValidationError, x as UploadStrategyMissingError, y as UploadRateLimitError } from "../index-DCqyBVcx.js";
2
+ export { Contracts, Engine, IndexedDBAdapter, LocalStorageAdapter, MemoryAdapter, Store, Transport, UploadAbortError, UploadAuthError, UploadEngineError, UploadError, UploadErrorContext, UploadHttpError, UploadNetworkError, UploadPersistence, UploadRateLimitError, UploadServerError, UploadStrategyMissingError, UploadTimeoutError, UploadUnknownError, UploadValidationError, createFetchTransport, createMemoryAdapter, createUploadStore, createXHRTransport, deserializeSnapshot, resolveUploadConfig, serializeSnapshot };
@@ -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,f as o,g as s,h as c,i as l,l as u,m as d,n as f,o as p,p as m,r as h,s as g,t as _,u as v,v as y,x as b,y as x}from"../core-BVgloTiP.js";export{_ as Engine,a as IndexedDBAdapter,g as LocalStorageAdapter,r as MemoryAdapter,o as UploadAbortError,m as UploadAuthError,d as UploadEngineError,c as UploadHttpError,s as UploadNetworkError,n as UploadRateLimitError,y as UploadServerError,x as UploadStrategyMissingError,i as UploadTimeoutError,b as UploadUnknownError,t as UploadValidationError,u as createFetchTransport,p as createMemoryAdapter,f as createUploadStore,v as createXHRTransport,h as deserializeSnapshot,e as resolveUploadConfig,l as serializeSnapshot};
@@ -0,0 +1,10 @@
1
+ function e(e,t,n=1){return typeof e!=`number`||!Number.isFinite(e)?t:Math.max(n,e)}function t(t){return{maxConcurrentUploads:e(t?.maxConcurrentUploads,3),autoStart:t?.autoStart,progressThrottleMs:e(t?.progressThrottleMs,100,0),validation:t?.validation??{},maxAttempts:e(t?.maxAttempts,3),retryPolicy:t?.retryPolicy,maxItems:e(t?.maxItems,100),completedItemTTL:t?.completedItemTTL,strictMimeMatch:t?.strictMimeMatch??!1,checksumMaxSize:t?.checksumMaxSize??null}}const n=new Set([`__proto__`,`constructor`,`prototype`]);function r(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===null||t===Object.prototype}function i(){return typeof window<`u`&&typeof process<`u`&&process.env?.NODE_ENV===`development`}function a(e){if(typeof e!=`object`||!e)return e;let t=[{value:e,depth:0}],r=new WeakSet;for(;t.length>0;){let e=t.pop();if(!e)break;let{value:i,depth:a}=e;if(r.has(i)||(r.add(i),a>=16))continue;if(Array.isArray(i)){for(let e of i)typeof e==`object`&&e&&t.push({value:e,depth:a+1});continue}let o=Object.getPrototypeOf(i);if(o!==null&&o!==Object.prototype)continue;let s=i;for(let e of Object.keys(s)){if(n.has(e)){delete s[e];continue}let r=s[e];typeof r==`object`&&r&&t.push({value:r,depth:a+1})}}return e}function o(){return`upload_${Date.now()}_${s()}`}function s(){let e=globalThis.crypto;if(e&&typeof e.randomUUID==`function`)return e.randomUUID();if(e&&typeof e.getRandomValues==`function`){let t=new Uint8Array(16);e.getRandomValues(t);let n=t[6]??0,r=t[8]??0;t[6]=n&15|64,t[8]=r&63|128;let i=[];for(let e=0;e<t.length;e++)i.push((t[e]??0).toString(16).padStart(2,`0`));return`${i.slice(0,4).join(``)}-${i.slice(4,6).join(``)}-${i.slice(6,8).join(``)}-${i.slice(8,10).join(``)}-${i.slice(10,16).join(``)}`}throw Error(`[duck-upload] No CSPRNG available: crypto.randomUUID and crypto.getRandomValues both missing`)}function c(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 l(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const u={intent_failed:`upload intent request failed`,upload_failed:`upload transfer failed`,complete_failed:`upload finalize failed`,validation_failed:`file validation failed`,strategy_missing:`upload strategy not found`,aborted:`upload aborted`,network:`network error occurred`,http:`http request failed`,timeout:`request timed out`,auth:`authentication failed`,rate_limit:`rate limit exceeded`,server:`server error occurred`,unknown:`unknown upload error`};var d=class extends Error{constructor(e,t){let n=t?.message??u[e]??`upload error (${e})`;super(n),l(this,`code`,void 0),l(this,`context`,void 0),l(this,`cause`,void 0),l(this,`retryable`,void 0),this.name=`UploadEngineError`,this.code=e,this.retryable=t?.retryable??!1,t?.context&&(this.context=t.context),t?.cause!==void 0&&(this.cause=t.cause)}},f=class extends d{constructor(e,t,n){super(`validation_failed`,{message:t??`File validation failed: ${e.code}`,retryable:!1,cause:n?.cause,context:n?.context}),l(this,`reason`,void 0),this.name=`UploadValidationError`,this.reason=e}},p=class extends d{constructor(e,t,n){super(`strategy_missing`,{message:t??`Upload strategy "${e}" is not registered`,retryable:!1,cause:n?.cause,context:n?.context}),l(this,`strategy`,void 0),this.name=`UploadStrategyMissingError`,this.strategy=e}},m=class extends d{constructor(e,t,n){super(`aborted`,{message:t??`Upload aborted`,retryable:!1,cause:n?.cause,context:n?.context}),l(this,`reason`,void 0),this.name=`UploadAbortError`,this.reason=e}},h=class extends d{constructor(e,t){super(`network`,{message:e??`Network error occurred`,retryable:!0,cause:t?.cause,context:t?.context}),this.name=`UploadNetworkError`}},g=class extends d{constructor(e,t,n){let r=n?.retryable??(e>=500||e===429);super(`http`,{message:t??`HTTP request failed with status ${e}`,retryable:r,cause:n?.cause,context:n?.context}),l(this,`status`,void 0),l(this,`statusText`,void 0),this.name=`UploadHttpError`,this.status=e,n?.statusText&&(this.statusText=n.statusText)}},_=class extends d{constructor(e,t){super(`timeout`,{message:e??`Request timed out`,retryable:!0,cause:t?.cause,context:t?.context}),this.name=`UploadTimeoutError`}},v=class extends d{constructor(e,t){super(`auth`,{message:e??`Authentication failed`,retryable:!1,cause:t?.cause,context:t?.context}),this.name=`UploadAuthError`}},y=class extends d{constructor(e,t){super(`rate_limit`,{message:e??`Rate limit exceeded`,retryable:!0,cause:t?.cause,context:t?.context}),l(this,`retryAfterMs`,void 0),this.name=`UploadRateLimitError`,t?.retryAfterMs!==void 0&&(this.retryAfterMs=t.retryAfterMs)}},b=class extends d{constructor(e,t){super(`server`,{message:e??`Server error occurred`,retryable:!0,cause:t?.cause,context:t?.context}),l(this,`serverCode`,void 0),this.name=`UploadServerError`,t?.serverCode&&(this.serverCode=t.serverCode)}},x=class extends d{constructor(e,t){super(`unknown`,{message:e??`Unknown upload error`,retryable:!1,cause:t?.cause,context:t?.context}),this.name=`UploadUnknownError`}};function S(e){return typeof e==`number`&&Number.isFinite(e)&&e>0}function C(e,t){if(!r(e))return new d(`validation_failed`,{message:`Invalid intent: must be an object`});if(typeof e.strategy!=`string`)return new d(`validation_failed`,{message:`Invalid intent: missing or invalid strategy field`});if(e.strategy!==t)return new d(`validation_failed`,{message:`Invalid intent: strategy mismatch (expected ${t}, got ${e.strategy})`});if(typeof e.fileId!=`string`||!e.fileId)return new d(`validation_failed`,{message:`Invalid intent: missing or invalid fileId`});if(t===`post`){if(typeof e.url!=`string`||!e.url)return new d(`validation_failed`,{message:`Invalid post intent: missing or invalid url`});try{let t=new URL(e.url);if(![`http:`,`https:`].includes(t.protocol))return new d(`validation_failed`,{message:`Invalid post intent: url must use http or https protocol`})}catch{return new d(`validation_failed`,{message:`Invalid post intent: url is not a valid URL`})}if(!e.fields||typeof e.fields!=`object`)return new d(`validation_failed`,{message:`Invalid post intent: missing or invalid fields`})}else if(t===`multipart`){if(typeof e.uploadId!=`string`||!e.uploadId)return new d(`validation_failed`,{message:`Invalid multipart intent: missing or invalid uploadId`});if(!S(e.partSize))return new d(`validation_failed`,{message:`Invalid multipart intent: missing or invalid partSize`});if(`parts`in e&&e.parts!==void 0&&!Array.isArray(e.parts))return new d(`validation_failed`,{message:`Invalid multipart intent: parts must be an array if provided`})}return null}function w(e,t,n,r=0){let i=n.validation?.[t],a=[],o=[],s=i?.maxFiles,l=s===void 0?1/0:Math.max(0,s-r);for(let r of e){if(l<=0){let e=s??0;o.push({file:r,reason:{code:`too_many_files`,max:e}});continue}let e=c(r,t,n);if(e){o.push({file:r,reason:e});continue}a.push(r),--l}return{valid:a,rejected:o}}function ee(e){let t=e.length;return t>=3&&e[0]===255&&e[1]===216&&e[2]===255?`image/jpeg`:t>=8&&e[0]===137&&e[1]===80&&e[2]===78&&e[3]===71&&e[4]===13&&e[5]===10&&e[6]===26&&e[7]===10?`image/png`:t>=4&&e[0]===71&&e[1]===73&&e[2]===70&&e[3]===56?`image/gif`:t>=4&&e[0]===37&&e[1]===80&&e[2]===68&&e[3]===70?`application/pdf`:t>=12&&e[0]===82&&e[1]===73&&e[2]===70&&e[3]===70&&e[8]===87&&e[9]===69&&e[10]===66&&e[11]===80?`image/webp`:t>=8&&e[4]===102&&e[5]===116&&e[6]===121&&e[7]===112?`video/mp4`:t>=4&&e[0]===26&&e[1]===69&&e[2]===223&&e[3]===163?`video/webm`:t>=4&&e[0]===80&&e[1]===75&&e[2]===3&&e[3]===4?`application/zip`:t>=3&&e[0]===73&&e[1]===68&&e[2]===51||t>=2&&e[0]===255&&(e[1]===251||e[1]===243||e[1]===242)?`audio/mpeg`:null}function te(e,t){if(!t)return!0;let n=e.toLowerCase().trim(),r=t.toLowerCase().trim();return!!(n===r||n===``||n===`application/octet-stream`||r===`application/zip`&&(n.startsWith(`application/vnd.openxmlformats-officedocument.`)||n.startsWith(`application/vnd.oasis.opendocument.`)||n===`application/epub+zip`||n===`application/java-archive`||n===`application/vnd.android.package-archive`)||r===`image/jpeg`&&(n===`image/jpg`||n===`image/pjpeg`))}const T=new Set;async function ne(e,t){if(e.size===0)return null;let n=await e.slice(0,16).arrayBuffer(),r=ee(new Uint8Array(n));if(r===null)return null;let i=e.type||``;if(te(i,r))return null;if(t)return{code:`mime_mismatch`,claimed:i||`<empty>`,sniffed:r};let a=`${i||`<empty>`}::${r}`;return T.has(a)||(T.add(a),console.warn(`[duck-upload] MIME mismatch: claimed "${i||`<empty>`}" but bytes look like "${r}". Set \`strictMimeMatch: true\` to reject these files.`)),null}function E(e){return{name:e.name,size:e.size,type:re(e.type),lastModified:e.lastModified}}function re(e){let t=e.indexOf(`;`);return t===-1?e.trim():e.slice(0,t).trim()}function ie(e,t){return e.name===t.name&&e.size===t.size&&e.lastModified===t.lastModified}async function D(e,t){let n=t?.chunkSize&&t.chunkSize>0?t.chunkSize:8*1024*1024,r=t?.signal,i=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),a=1779033703,o=3144134277,s=1013904242,c=2773480762,l=1359893119,u=2600822924,d=528734635,f=1541459225,p=new Uint32Array(64),m=new Uint8Array(64),h=0,g=0,_=(e,t)=>e>>>t|e<<32-t,v=()=>{for(let e=0;e<16;e++){let t=e*4;p[e]=(m[t]<<24|m[t+1]<<16|m[t+2]<<8|m[t+3])>>>0}for(let e=16;e<64;e++){let t=p[e-15],n=p[e-2],r=_(t,7)^_(t,18)^t>>>3,i=_(n,17)^_(n,19)^n>>>10;p[e]=p[e-16]+r+p[e-7]+i>>>0}let e=a,t=o,n=s,r=c,h=l,g=u,v=d,y=f;for(let a=0;a<64;a++){let o=_(h,6)^_(h,11)^_(h,25),s=h&g^~h&v,c=y+o+s+i[a]+p[a]>>>0,l=(_(e,2)^_(e,13)^_(e,22))+(e&t^e&n^t&n)>>>0;y=v,v=g,g=h,h=r+c>>>0,r=n,n=t,t=e,e=c+l>>>0}a=a+e>>>0,o=o+t>>>0,s=s+n>>>0,c=c+r>>>0,l=l+h>>>0,u=u+g>>>0,d=d+v>>>0,f=f+y>>>0},y=e=>{g+=e.length;let t=0;if(h>0){let n=Math.min(64-h,e.length);m.set(e.subarray(0,n),h),h+=n,t=n,h===64&&(v(),h=0)}for(;e.length-t>=64;)m.set(e.subarray(t,t+64),0),v(),t+=64;if(t<e.length){let n=e.subarray(t);m.set(n,0),h=n.length}},b=e.size;for(let t=0;t<b;t+=n){if(r?.aborted)throw new DOMException(`The operation was aborted.`,`AbortError`);let i=Math.min(b,t+n),a=await e.slice(t,i).arrayBuffer();y(new Uint8Array(a))}let x=g*8,S=Math.floor(x/4294967296)>>>0,C=x>>>0;if(m[h++]=128,h>56){for(;h<64;)m[h++]=0;v(),h=0}for(;h<56;)m[h++]=0;m[56]=S>>>24&255,m[57]=S>>>16&255,m[58]=S>>>8&255,m[59]=S&255,m[60]=C>>>24&255,m[61]=C>>>16&255,m[62]=C>>>8&255,m[63]=C&255,v();let w=e=>(e>>>0).toString(16).padStart(8,`0`);return w(a)+w(o)+w(s)+w(c)+w(l)+w(u)+w(d)+w(f)}async function ae(e){let t=await e.arrayBuffer(),n=await crypto.subtle.digest(`SHA-256`,t),r=new Uint8Array(n),i=``;for(let e of r)i+=e.toString(16).padStart(2,`0`);return i}function oe(){return typeof Worker<`u`&&typeof Blob<`u`&&typeof URL<`u`&&typeof URL.createObjectURL==`function`}let O=null;function se(){if(O)return O;let e=`const sha256HexOfBlob = ${D.toString()};
2
+ self.onmessage = async (e) => {
3
+ try {
4
+ const hex = await sha256HexOfBlob(e.data.blob, { chunkSize: e.data.chunkSize });
5
+ self.postMessage({ ok: true, hex });
6
+ } catch (err) {
7
+ self.postMessage({ ok: false, error: String((err && err.message) || err) });
8
+ }
9
+ };`,t=new Blob([e],{type:`application/javascript`});return O=URL.createObjectURL(t),O}function ce(e,t){return new Promise((n,r)=>{let i;try{i=new Worker(se())}catch(e){r(e instanceof Error?e:Error(String(e)));return}let a=e=>{i.onmessage=null,i.onerror=null,i.terminate(),e()};i.onmessage=e=>{let t=e.data;t.ok&&typeof t.hex==`string`?a(()=>n(t.hex)):a(()=>r(Error(t.error??`hash worker failed`)))},i.onerror=e=>{a(()=>r(Error(e.message||`hash worker error`)))},i.postMessage({blob:e,chunkSize:t})})}async function le(e,t){let n=typeof t?.inlineMaxSize==`number`&&Number.isFinite(t.inlineMaxSize)&&t.inlineMaxSize>0?t.inlineMaxSize:8388608,r=t?.chunkSize??8388608;if(e.size<=n&&typeof crypto<`u`&&crypto.subtle)return ae(e);if(oe()&&!t?.signal)try{return await ce(e,r)}catch{}return t?.signal?D(e,{chunkSize:r,signal:t.signal}):D(e,{chunkSize:r})}function k(e){let t=Number.isFinite(e)&&e>0?Math.min(e,864e5):0;return new Promise(e=>setTimeout(e,t))}function A(e,t={}){let n=[...e.steps].reverse().find(e=>e.error!==void 0);return{localId:e.localId,purpose:e.purpose,fingerprint:e.fingerprint,attempt:e.attempt??1,createdAt:e.createdAt,steps:e.steps,meta:e.meta,lastError:n?.error,file:`file`in e?e.file:void 0,intent:`intent`in e?e.intent:void 0,...t}}async function ue(e,t=null){return le(e,{inlineMaxSize:typeof t==`number`&&Number.isFinite(t)&&t>0?t:67108864})}function de(e,t){let n=e.config?.autoStart;if(n===void 0)return!1;if(Array.isArray(n))return n.includes(t);if(typeof n==`function`)try{return n(t)===!0}catch{return!1}return!1}async function j(e,t,n,r,a){if(!n||!e.opts.api.findByChecksum)return!1;let o=e.state.items.get(t);if(!o||!a.includes(o.phase)||!o.file)return!1;try{let i=await e.opts.api.findByChecksum({checksum:n,purpose:r},{...A(o),file:o.file});if(!i)return!1;let s=e.state.items.get(t);return!s||!a.includes(s.phase)?!1:(e.applyInternal({type:`dedupe.ok`,localId:t,result:i}),!0)}catch(e){return i()&&console.warn(`[UploadEngine] Failed to check for existing file:`,e),!1}}function fe(e){if(!r(e))return null;let t=e.strategy;if(!(e.dedupe===!0||e.existing===!0||e.skipUpload===!0||t===`dedupe`))return null;let n=typeof e.fileId==`string`?e.fileId:typeof e.id==`string`?e.id:null,i=typeof e.key==`string`?e.key:null;return!n||!i?null:{fileId:n,key:i}}function M(e,t){if(t)return t(e);if(e instanceof d)return e;if(he(e))return new m(e.reason===`pause`||e.reason===`cancel`?e.reason:`unknown`,void 0,{cause:e});let n=r(e)&&typeof e.message==`string`?e.message:`Unknown error`;return n.toLowerCase().includes(`network`)||n.toLowerCase().includes(`fetch`)?new h(n,{cause:e}):r(e)&&(typeof e.status==`number`||typeof e.statusCode==`number`)?new g(typeof e.status==`number`?e.status:e.statusCode,n,{cause:e,statusText:typeof e.statusText==`string`?e.statusText:void 0}):new x(n,{cause:e})}function N(e){return`intent`in e&&!!e.intent}function pe(e){return`file`in e&&!!e.file}function P(e){return`cursor`in e}function me(e){return r(e)&&e.strategy===`multipart`&&typeof e.fileId==`string`&&typeof e.uploadId==`string`}function he(e){return r(e)&&e.code===`aborted`}function F(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 n=e.maxAttempts;return t.attempt<n?{retryable:!0,delayMs:Math.min(1e4,500*2**(t.attempt-1))}:{retryable:!1}}function ge(e,t,n,r={}){let a=Array.from(e.state.items.values()).filter(e=>e.purpose===n&&e.phase!==`canceled`).length,{valid:s,rejected:l}=w(t,n,e.opts.config,a);for(let{file:t,reason:n}of l)e.emitter.emit(`file.rejected`,{reason:n,file:t});let u=Date.now(),d=[];for(let t of s){let a=o(),s=(e.opts.fingerprint??E)(t);d.push({localId:a,purpose:n,file:t,fingerprint:s,createdAt:u,meta:r}),e.enqueueEffect(async()=>{let r;try{r=await ue(t,e.opts.config.checksumMaxSize);let n={...s,checksum:r};e.applyInternal({type:`fingerprint.updated`,localId:a,fingerprint:n})}catch(e){i()&&console.warn(`[UploadEngine] Failed to calculate checksum:`,e)}if(await j(e,a,r,n,[`validating`]))return;let o=e.opts.validateFile?.(t,n);if(o){e.applyInternal({type:`validation.failed`,localId:a,reason:o});return}let l=c(t,n,e.opts.config);if(l){e.applyInternal({type:`validation.failed`,localId:a,reason:l});return}try{let n=await ne(t,e.opts.config.strictMimeMatch===!0);if(n){e.applyInternal({type:`validation.failed`,localId:a,reason:n});return}}catch(e){i()&&console.warn(`[UploadEngine] MIME sniff failed:`,e)}e.applyInternal({type:`validation.ok`,localId:a})})}d.length>0&&e.applyInternal({type:`files.added`,items:d})}function _e(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&&N(n)&&n.intent.strategy===`multipart`&&e.opts.api.multipart?.abort){let t=n.intent;me(t)&&e.enqueueEffect(async()=>{try{let r=e.opts.api.multipart?.abort;r&&await r({fileId:t.fileId,uploadId:t.uploadId},A(n))}catch{}})}}function ve(e,t){let n=e.inflightUploads.get(t);if(n){n.mode=`pause`,n.controller.abort({reason:`pause`});return}}function ye(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`){ge(e,t.files,t.purpose,t.meta??{});return}if(t.type===`pause`){ve(e,t.localId),e.applyCommand(t);return}if(t.type===`cancel`){_e(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 I(e){return e?.reason}function be(e){if(!e)return`unknown`;if(typeof e==`string`)return e===`pause`||e===`cancel`?e:`unknown`;if(r(e)&&`reason`in e){let t=e.reason;if(t===`pause`||t===`cancel`)return t}if(r(e)&&`kind`in e){let t=e.kind;if(t===`pause`||t===`cancel`)return t}return`unknown`}var xe=class extends m{constructor(e){let t=be(e);super(t)}};function L(e){return new xe(e)}function Se(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 Ce(e){let t={};return e.getAllResponseHeaders().split(`\r
10
+ `).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 we(e){return/^https?:\/\//i.test(e)}function Te(e){let t=e.getResponseHeader(`ETag`)??e.getResponseHeader(`etag`);if(t)return t.replace(/"/g,``)}function R(e){return new Promise((t,n)=>{if(!e.url||typeof e.url!=`string`){n(Error(`UploadTransport: missing url`));return}if(!we(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(L(I(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(L(I(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({status:e,headers:Ce(r),etag:Te(r)}):()=>n(Error(`Upload failed with status ${e}`)))},r.onerror=()=>a(()=>n(Se(r,`Network error during upload`))),r.onabort=()=>a(()=>n(L(e.signal?.aborted?I(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 z(){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 R({method:`POST`,url:e.url,body:t,signal:e.signal,onProgress:e.onProgress??(()=>{})})).headers}},async put(e){let t=await R({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 R({method:`PATCH`,url:e.url,body:e.body,signal:e.signal??new AbortController().signal,headers:e.headers??{},onProgress:e.onProgress??(()=>{})})).headers}},async head(e){let t=await R({method:`HEAD`,url:e.url,body:null,signal:e.signal,headers:e.headers??{}});return{status:t.status,headers:t.headers}}}}function B(e){return/^https?:\/\//i.test(e)}function Ee(e){let t=e.get(`etag`)??e.get(`ETag`);if(t)return t.replace(/"/g,``)}function V(e){let t={};return e.forEach((e,n)=>{t[n.toLowerCase()]=e}),t}async function H(e){if(!e.url||!B(e.url))throw Error(`UploadTransport(fetch): expected absolute URL, got: ${String(e.url)}`);if(e.signal.aborted)throw L(I(e.signal));let t={method:e.method,body:e.body,signal:e.signal};e.headers&&(t.headers=e.headers);let n;try{n=await fetch(e.url,t)}catch(t){throw e.signal.aborted||t instanceof Error&&t.name===`AbortError`?L(I(e.signal)):t instanceof Error?t:Error(String(t))}if(n.status<200||n.status>=300)throw Error(`Upload failed with status ${n.status}`);return e.onProgress?.(e.byteLength,e.byteLength),{headers:V(n.headers),etag:Ee(n.headers)}}function De(){return{async postForm(e){let t=new FormData;for(let[n,r]of Object.entries(e.fields))t.append(n,r);t.append(`file`,e.file,e.filename??`file`);let n=await H({method:`POST`,url:e.url,body:t,signal:e.signal,byteLength:e.file.size,onProgress:e.onProgress});return n.etag===void 0?{headers:n.headers}:{headers:n.headers,etag:n.etag}},async put(e){let t=await H({method:`PUT`,url:e.url,body:e.body,signal:e.signal,headers:e.headers??{},byteLength:e.body.size,onProgress:e.onProgress});return{headers:t.headers,etag:t.etag}},async patch(e){let t=e.body instanceof Blob?e.body:new Blob([e.body]);return{headers:(await H({method:`PATCH`,url:e.url,body:t,signal:e.signal,headers:e.headers??{},byteLength:t.size,onProgress:e.onProgress})).headers}},async get(e){if(!e.url||!B(e.url))throw Error(`UploadTransport(fetch): expected absolute URL, got: ${String(e.url)}`);if(e.signal.aborted)throw L(I(e.signal));let t={method:`GET`,signal:e.signal};e.headers&&(t.headers=e.headers);let n;try{n=await fetch(e.url,t)}catch(t){throw e.signal.aborted||t instanceof Error&&t.name===`AbortError`?L(I(e.signal)):t instanceof Error?t:Error(String(t))}if(n.status<200||n.status>=300)throw Error(`Download failed with status ${n.status}`);let r=V(n.headers),i=Number(n.headers.get(`content-length`))||0;if(n.body&&typeof n.body.getReader==`function`){let t=n.body.getReader(),a=[],o=0;for(;;){let{done:n,value:r}=await t.read();if(n)break;r&&(a.push(r),o+=r.byteLength,e.onProgress?.(o,i||o))}return{blob:new Blob(a,{type:n.headers.get(`content-type`)??`application/octet-stream`}),status:n.status,headers:r}}let a=await n.blob();return e.onProgress?.(a.size,i||a.size),{blob:a,status:n.status,headers:r}},async head(e){if(!e.url||!B(e.url))throw Error(`UploadTransport(fetch): expected absolute URL, got: ${String(e.url)}`);if(e.signal.aborted)throw L(I(e.signal));let t={method:`HEAD`,signal:e.signal};e.headers&&(t.headers=e.headers);let n;try{n=await fetch(e.url,t)}catch(t){throw e.signal.aborted||t instanceof Error&&t.name===`AbortError`?L(I(e.signal)):t instanceof Error?t:Error(String(t))}return{status:n.status,headers:V(n.headers)}}}}const U=`snapshots`;function W(){return new Promise((e,t)=>{let n=indexedDB.open(`upload-engine`,1);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(U)||e.createObjectStore(U)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}function Oe(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error)})}function G(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 ke={async load(e){if(typeof indexedDB>`u`)return null;let t=(await W()).transaction(U,`readonly`),n=t.objectStore(U);try{let r=await Oe(n.get(e));return await G(t),r==null?null:a(r)}catch{try{await G(t)}catch{}return null}},async save(e,t){if(typeof indexedDB>`u`)return;let n=(await W()).transaction(U,`readwrite`),r=n.objectStore(U);try{r.put(t,e),await G(n)}catch{try{n.abort()}catch{}}},async clear(e){if(typeof indexedDB>`u`)return;let t=(await W()).transaction(U,`readwrite`),n=t.objectStore(U);try{n.delete(e),await G(t)}catch{try{t.abort()}catch{}}}},Ae={load(e){if(typeof localStorage>`u`)return null;let t=localStorage.getItem(e);if(!t)return null;try{return a(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)}};function K(){let e=new Map;return{load(t){return e.get(t)??null},save(t,n){e.set(t,n)},clear(t){e.delete(t)}}}const je=K();function q(e,t){let n={};for(let t of e.items.values()){if(!N(t))continue;let e=t.phase;if(e===`completed`||e===`canceled`||e===`error`)continue;let r=P(t)?t.cursor:void 0,i=`progress`in t&&t.progress?{uploadedBytes:t.progress.uploadedBytes,totalBytes:t.progress.totalBytes,pct:t.progress.pct}:void 0,a={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:r,progress:i};n[a.id]=a}return{version:t,createdAt:Date.now(),items:n}}function J(e,t){if(!t.isPurpose||!t.isIntent||!t.hasStrategy||!Me(e))return null;let n=new Map;for(let i of Object.values(e.items)){let a=Ne(i);if(!a||!t.isPurpose(a.purpose)||!t.isIntent(a.intent))continue;let o=a.intent;if(!r(o))continue;let s=o.strategy;if(typeof s!=`string`||!t.hasStrategy(s)||!a.cursor||!Fe(a.cursor,t.hasStrategy)||a.cursor.strategy!==s)continue;let c=a.progress?.totalBytes??a.file.size,l=a.progress?.uploadedBytes??0,u=typeof a.progress?.pct==`number`?a.progress.pct:c>0?Math.min(100,Math.max(0,l/c*100)):0;n.set(a.id,{phase:`paused`,localId:a.id,purpose:a.purpose,fingerprint:{name:a.file.name,size:a.file.size,type:a.file.type,lastModified:a.file.lastModified,checksum:a.file.checksum},intent:a.intent,cursor:a.cursor,progress:{uploadedBytes:l,totalBytes:c,pct:u},pausedAt:Date.now(),createdAt:e.createdAt??Date.now(),file:void 0,steps:[],meta:{}})}return{items:n}}function Y(e){return typeof e==`number`&&Number.isFinite(e)}function Me(e){return!r(e)||!Y(e.version)||!Y(e.createdAt)?!1:r(e.items)}function Ne(e){if(!r(e))return null;let t=typeof e.id==`string`?e.id:null,n=typeof e.purpose==`string`?e.purpose:null,i=typeof e.status==`string`?e.status:null,a=e.intent;if(!t||!n||!i)return null;let o=e.file;if(!r(o))return null;let s=typeof o.name==`string`?o.name:null,c=o.size,l=Y(c)&&c>=0?c:null,u=typeof o.type==`string`?o.type:null,d=o.lastModified,f=Y(d)?d:null,p=typeof o.checksum==`string`?o.checksum:void 0;if(!s||l===null||u===null||f===null)return null;let m=Pe(e.progress),h=e.cursor;return{id:t,purpose:n,status:i,file:{name:s,size:l,type:u,lastModified:f,checksum:p},intent:a,cursor:h,progress:m}}function Pe(e){if(!r(e))return;let t=e.uploadedBytes,n=e.totalBytes;if(!Y(t)||!Y(n)||t<0||n<0)return;let i=e.pct;return{uploadedBytes:t,totalBytes:n,pct:Y(i)?i:void 0}}function Fe(e,t){if(!r(e))return!1;let n=e.strategy;return!(typeof n!=`string`||!t(n))}function Ie(e){let t={},n=new Map,r=0,a=!1,o=e=>{n.size>0&&n.delete(e)};return{on(e,n){let r=t[e];return r||(r=new Set,t[e]=r),r.add(n),o(e),()=>{let r=t[e];r&&(r.delete(n),o(e),r.size===0&&delete t[e])}},off(e,n){let r=t[e];r&&(r.delete(n),o(e),r.size===0&&delete t[e])},emit(o,s){let c=t[o];if(!c||c.size===0)return;let l=t=>{try{t(s)}catch(n){if(e){try{e(o,n,t)}catch{}return}i()&&console.error(`[UploadEngine] Error in event listener for "${o}":`,n)}};r++;try{if(r>50&&!a&&i()&&(a=!0,console.warn(`[UploadEngine] emit depth exceeded 50 for "${o}". A listener is likely dispatching a command that re-emits, forming a feedback loop. Defer the dispatch with \`queueMicrotask\` to break the chain.`)),c.size===1){let[e]=c;e&&l(e);return}let e=n.get(o);e||(e=Array.from(c),n.set(o,e));for(let t of e)l(t)}finally{r--,r===0&&(a=!1)}}}}function X(e){let t={code:e.code,message:e.message};return e.retryable!==void 0&&(t.retryable=e.retryable),t}function Le(e,t){if(!e.length)return e;let n=e[e.length-1];if(!n||n.leftAt!==void 0)return e;let r={phase:n.phase,enteredAt:n.enteredAt,leftAt:t,attempt:n.attempt,...n.error===void 0?{}:{error:n.error}};return[...e.slice(0,-1),r]}function Z(e,t,n,r,i){return[...Le(e,r),{phase:t,enteredAt:r,attempt:n,error:i}]}function Re(){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,steps:Z(e.steps,`queued`,e.attempt??1,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,steps:Z(e.steps,`queued`,e.attempt??1,i)});break}case`pause`:{let e=n.get(t.localId);if(!e)break;e.phase===`queued`&&r(t.localId,{...e,phase:`ready`,steps:Z(e.steps,`ready`,e.attempt??1,i)});break}case`cancel`:{let e=n.get(t.localId);if(!e||e.phase===`completed`||e.phase===`canceled`)break;r(t.localId,Q(e,i));break}case`retry`:{let e=n.get(t.localId);if(!e||e.phase!==`error`||!e.retryable||!e.file)break;if(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,steps:Z(e.steps,`completing`,e.attempt??1,i)}):r(t.localId,{...e,file:e.file,intent:e.intent,phase:`ready`,steps:Z(e.steps,`ready`,e.attempt??1,i)});else{let n=(e.attempt??1)+1;r(t.localId,{...e,phase:`creating_intent`,file:e.file,attempt:n,steps:Z(e.steps,`creating_intent`,n,i)})}break}case`rebind`:{let e=n.get(t.localId);if(!e)break;e.phase===`paused`&&!e.file&&ie(E(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,meta:t.meta,steps:[{phase:`validating`,enteredAt:i,attempt:1}]});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?.phase!==`validating`)break;r(t.localId,{...e,phase:`creating_intent`,attempt:1,steps:Z(e.steps,`creating_intent`,1,i)});break}case`validation.failed`:{let e=t,a=n.get(e.localId);if(a?.phase!==`validating`)break;let o=new f(e.reason);r(e.localId,{...a,phase:`error`,error:o,retryable:!1,attempt:1,failedAt:i,steps:Z(a.steps,`error`,1,i,X(o))});break}case`intent.ok`:{let e=t,a=n.get(e.localId);if(a?.phase!==`creating_intent`)break;r(e.localId,{...a,phase:`ready`,intent:e.intent,steps:Z(a.steps,`ready`,a.attempt??1,i)});break}case`intent.failed`:{let e=t,a=n.get(e.localId);if(a?.phase!==`creating_intent`)break;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,failedAt:i,steps:Z(a.steps,`error`,a.attempt??1,i,X(e.error))});break}case`upload.begin`:{let e=t,i=n.get(e.localId);if(i?.phase!==`queued`)break;let a=i.file.size,o=i.progress,s=o?{...o,totalBytes:a,pct:$(o.uploadedBytes,a)}:{uploadedBytes:0,totalBytes:a,pct:0};r(e.localId,{...i,phase:`uploading`,progress:s,startedAt:e.startedAt,steps:Z(i.steps,`uploading`,i.attempt??1,e.startedAt)});break}case`upload.progress`:{let e=t,i=n.get(e.localId);if(i?.phase!==`uploading`)break;let a={uploadedBytes:e.uploadedBytes,totalBytes:e.totalBytes,pct:$(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?.phase!==`uploading`)break;r(e.localId,{...a,phase:`completing`,progress:{uploadedBytes:a.file.size,totalBytes:a.file.size,pct:100},completingAt:i,steps:Z(a.steps,`completing`,a.attempt??1,i)});break}case`upload.failed`:{let e=t,a=n.get(e.localId);if(a?.phase!==`uploading`)break;r(e.localId,{...a,phase:`error`,error:e.error,retryable:e.retryable,attempt:1,failedAt:i,steps:Z(a.steps,`error`,a.attempt??1,i,X(e.error))});break}case`paused`:{let e=t,i=n.get(e.localId);if(i?.phase!==`uploading`)break;r(e.localId,{...i,phase:`paused`,cursor:e.cursor,pausedAt:e.pausedAt,steps:Z(i.steps,`paused`,i.attempt??1,e.pausedAt)});break}case`canceled`:{let e=t,i=n.get(e.localId);if(!i)break;r(e.localId,Q(i,e.canceledAt));break}case`complete.ok`:{let e=t,a=n.get(e.localId);if(a?.phase!==`completing`)break;r(e.localId,{...a,phase:`completed`,completedBy:`upload`,result:e.result,completedAt:i,steps:Z(a.steps,`completed`,a.attempt??1,i)});break}case`dedupe.ok`:{let e=t,a=n.get(e.localId);if(!a||a.phase!==`validating`&&a.phase!==`creating_intent`)break;r(e.localId,{phase:`completed`,localId:a.localId,file:`file`in a?a.file:void 0,purpose:a.purpose,fingerprint:a.fingerprint,result:e.result,completedBy:`dedupe`,completedAt:i,createdAt:a.createdAt,meta:a.meta,steps:Z(a.steps,`completed`,a.attempt??1,i)});break}case`complete.failed`:{let e=t,a=n.get(e.localId);if(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,steps:Z(a.steps,`error`,o,i,X(e.error))});break}}return{items:n}}}function Q(e,t){let n=(`attempt`in e?e.attempt:void 0)??1;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,meta:e.meta,steps:Z(e.steps,`canceled`,n,t)}}function $(e,t){return t<=0?0:Math.min(100,e/t*100)}function ze(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++){let t=e[n];t&&a.push(t[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}}const Be=new Set([`CON`,`PRN`,`AUX`,`NUL`,`COM1`,`COM2`,`COM3`,`COM4`,`COM5`,`COM6`,`COM7`,`COM8`,`COM9`,`LPT1`,`LPT2`,`LPT3`,`LPT4`,`LPT5`,`LPT6`,`LPT7`,`LPT8`,`LPT9`]);function Ve(e){let t=``;for(let n=0;n<e.length;n++){let r=e.charCodeAt(n);r>=32&&r!==127&&(t+=e[n])}return t}function He(e){let t=e.normalize(`NFKC`);for(t=Ve(t);t.startsWith(`-`);)t=t.slice(1);for(;t.length>0&&(t.endsWith(`.`)||t.endsWith(` `));)t=t.slice(0,-1);if(t.length===0)return{safe:!1,normalised:t,reason:`empty`};if(t.includes(`/`)||t.includes(`\\`))return{safe:!1,normalised:t,reason:`path-sep`};if(t.length>255)return{safe:!1,normalised:t,reason:`too-long`};let n=t.indexOf(`.`),r=(n===-1?t:t.slice(0,n)).toUpperCase();return Be.has(r)?{safe:!1,normalised:t,reason:`reserved`}:{safe:!0,normalised:t}}async function Ue(e,t){let n=e.state.items.get(t);if(n?.phase!==`creating_intent`||e.inflightIntents.has(t))return;let i=He(n.file.name);if(!i.safe){let r=new f({code:`filename_rejected`,reason:i.reason},`filename rejected`,{context:{original:n.file.name,reason:i.reason}});e.applyInternal({type:`intent.failed`,localId:t,error:r,retryable:!1});return}let a=new AbortController;e.inflightIntents.set(t,a);try{if(await j(e,t,n.fingerprint.checksum,n.purpose,[`creating_intent`]))return;let o={purpose:n.purpose,contentType:n.file.type||`application/octet-stream`,size:n.file.size,filename:i.normalised,attempt:n.attempt??1};n.fingerprint.checksum!==void 0&&(o.checksum=n.fingerprint.checksum);let s=await e.opts.api.createIntent(o,A(n,{signal:a.signal}));if(e.inflightIntents.delete(t),e.state.items.get(t)?.phase!==`creating_intent`)return;let c=fe(s);if(c){e.applyInternal({type:`dedupe.ok`,localId:t,result:c});return}let l=C(s,r(s)&&typeof s.strategy==`string`?s.strategy:``);if(l){e.applyInternal({type:`intent.failed`,localId:t,error:l,retryable:!1});return}e.applyInternal({type:`intent.ok`,localId:t,intent:s})}catch(r){if(e.inflightIntents.delete(t),a.signal.aborted)return;let i=M(r,e.opts.errorNormalizer),o={...i,context:{...i.context??{},filename:n.fingerprint.name,size:n.file.size,purpose:n.purpose}},s=n.attempt??1,c=F(e.opts.config,{phase:`intent`,attempt:s,error:o});e.applyInternal({type:`intent.failed`,localId:t,error:o,retryable:c.retryable}),c.retryable&&c.delayMs!==void 0&&e.enqueueEffect(async()=>{await k(c.delayMs),e.dispatch({type:`retry`,localId:t})})}}function We(e){return typeof e==`object`&&!!e&&`context`in e&&typeof e.context==`object`&&e.context!==null}async function Ge(e,t){let n=e.state.items.get(t);if(n?.phase!==`completing`||e.inflightCompletes.has(t))return;let r=new AbortController;e.inflightCompletes.set(t,r);try{let i=await e.opts.api.complete({fileId:n.intent.fileId,filename:n.fingerprint.name,contentType:n.file.type||`application/octet-stream`,size:n.fingerprint.size,...n.fingerprint.checksum===void 0?{}:{checksum:n.fingerprint.checksum},attempt:n.attempt??1},A(n,{signal:r.signal}));if(e.inflightCompletes.delete(t),e.state.items.get(t)?.phase!==`completing`)return;e.applyInternal({type:`complete.ok`,localId:t,result:i})}catch(n){if(e.inflightCompletes.delete(t),r.signal.aborted)return;let i=M(n,e.opts.errorNormalizer),a=e.state.items.get(t),o=a?{...i,context:{...We(i)?i.context:{},filename:a.fingerprint.name,fileId:(N(a)&&a.intent?.fileId)??`unknown`}}:i,s=a&&`attempt`in a?a.attempt??1:1,c=F(e.opts.config,{phase:`complete`,attempt:s,error:o});e.applyInternal({type:`complete.failed`,localId:t,error:o,retryable:c.retryable}),c.retryable&&c.delayMs!==void 0&&e.enqueueEffect(async()=>{await k(c.delayMs),e.dispatch({type:`retry`,localId:t})})}}async function Ke(e,t){let n=e.state.items.get(t);if(n?.phase!==`uploading`||!N(n)){e.inflightUploads.delete(t);return}let r=e.inflightUploads.get(t);if(r||(r={controller:new AbortController,mode:`normal`,started:!1},e.inflightUploads.set(t,r)),r.started)return;r.started=!0;let i=r.controller,a=()=>{let n=e.state.items.get(t);!n||n.phase===`canceled`||e.applyInternal({type:`canceled`,localId:t,canceledAt:Date.now()})},o=n=>{let r=e.state.items.get(t);!r||r.phase===`paused`||e.applyInternal({type:`paused`,localId:t,cursor:n,pausedAt:Date.now()})},s=n.intent.strategy;if(i.signal.aborted){if(e.inflightUploads.delete(t),r.mode===`pause`){let n=e.state.items.get(t),r=(n&&P(n)?n.cursor:void 0)||{strategy:s,value:void 0};r&&o(r);return}if(r.mode===`cancel`){a();return}return}if(!e.opts.strategies.has(s)){e.inflightUploads.delete(t);let n=new p(String(s));e.applyInternal({type:`upload.failed`,localId:t,error:n,retryable:!1});return}let c=e.opts.strategies.get(s);if(!c){e.inflightUploads.delete(t);let r=new p(String(n.intent.strategy));e.applyInternal({type:`upload.failed`,localId:t,error:r,retryable:!1});return}let l=0,u=e.opts.config.progressThrottleMs;try{if(await c.start({localId:t,purpose:n.purpose,file:n.file,intent:n.intent,signal:i.signal,transport:e.opts.transport,api:e.opts.api,fingerprint:n.fingerprint,attempt:n.attempt??1,createdAt:n.createdAt,steps:n.steps,meta:n.meta,lastError:[...n.steps].reverse().find(e=>e.error)?.error,readCursor:()=>{let n=e.state.items.get(t);if(!(!n||!P(n)||!n.cursor)&&n.cursor.strategy===s)return n.cursor.value},persistCursor:n=>{let r={strategy:s,value:n};e.applyInternal({type:`cursor.updated`,localId:t,cursor:r})},reportProgress:n=>{let r=Date.now();r-l<u||(e.applyInternal({type:`upload.progress`,localId:t,uploadedBytes:n.uploadedBytes,totalBytes:n.totalBytes}),l=r)}}),e.inflightUploads.delete(t),r?.mode===`cancel`){a();return}if(i.signal.aborted&&r?.mode===`pause`){let n=e.state.items.get(t);o((n&&P(n)?n.cursor:void 0)||{strategy:s,value:void 0});return}e.applyInternal({type:`upload.ok`,localId:t})}catch(n){if(e.inflightUploads.delete(t),i.signal.aborted){if(r?.mode===`pause`){let n=e.state.items.get(t);o((n&&P(n)?n.cursor:void 0)||{strategy:s,value:void 0});return}if(r?.mode===`cancel`){a();return}a();return}let c=M(n,e.opts.errorNormalizer),l=e.state.items.get(t),u=l?{...c,context:{...c.context??{},filename:l.fingerprint.name,size:(pe(l)&&l.file?.size)??0,phase:l.phase}}:c,d=F(e.opts.config,{phase:`upload`,attempt:1,error:u});e.applyInternal({type:`upload.failed`,localId:t,error:u,retryable:d.retryable}),d.retryable&&d.delayMs!==void 0&&e.enqueueEffect(async()=>{await k(d.delayMs),e.dispatch({type:`retry`,localId:t})})}}function qe(e){if(!e.scheduling){e.scheduling=!0;try{Je(e),Xe(e),Ye(e)}finally{e.scheduling=!1}}}function Je(e){for(let t of e.state.items.values())t.phase===`creating_intent`&&(e.inflightIntents.has(t.localId)||e.enqueueEffect(()=>Ue(e,t.localId)))}function Ye(e){for(let t of e.state.items.values())t.phase===`completing`&&(e.inflightCompletes.has(t.localId)||e.enqueueEffect(()=>Ge(e,t.localId)))}function Xe(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`&&de(e.opts,t.purpose)&&e.applyCommand({type:`start`,localId:t.localId});if(n>=t)return;let r=Array.from(e.state.items.values()).filter(Ze);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()}),Ke(e,t.localId)}}function Ze(e){return e.phase===`queued`}function Qe(e){let t=$e(e),n=t.persistence,r=null,a=Math.max(0,n?.debounceMs??200),o=async()=>{if(n)try{let e=(n.serialize??q)(c.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){i()&&console.warn(`[UploadEngine] persistence flush failed:`,e)}},s=()=>{if(n){if(a===0){o();return}r&&clearTimeout(r),r=setTimeout(()=>void o(),a)}},c={opts:t,state:t.initialState??{items:new Map},listeners:new Set,emitter:Ie(),reduce:Re(),inflightUploads:new Map,inflightIntents:new Map,inflightCompletes:new Map,effectQueue:[],processingEffects:!1,scheduling:!1,notify(){c.listeners.forEach(e=>{e()})},scheduleWork(){qe(c)},applyInternal(e){let t=c.state;c.state=c.reduce(t,e),c.opts.hooks?.onInternalEvent?.(e,c.state),et(c,e);let n=ze(c.opts,c.state);n&&(c.state=n),s(),c.notify(),c.scheduleWork()},applyCommand(e){let t=c.state,n=c.reduce(t,e);c.state=n,tt(c,e,t,n);let r=ze(c.opts,c.state);r&&(c.state=r),s(),c.notify(),c.scheduleWork()},enqueueEffect(e){c.effectQueue.push(e),c.processingEffects||c.processEffects()},async processEffects(){for(c.processingEffects=!0;c.effectQueue.length>0;){let e=c.effectQueue.shift();if(e)try{await e()}catch(e){if(i()){let t=e instanceof Error?e:Error(String(e)),n={queueLength:c.effectQueue.length,processing:c.processingEffects,timestamp:new Date().toISOString()};console.error(`[UploadEngine] Effect error:`,t,n)}}}c.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??J)(e,{isPurpose:n.isPurpose,isIntent:n.isIntent,hasStrategy:t.strategies.has});if(r){if(c.state.items.size===0)c.state=r;else{let e=new Map(c.state.items);for(let[t,n]of r.items.entries())e.has(t)||e.set(t,n);c.state={...c.state,items:e}}c.notify(),c.scheduleWork()}}).catch(()=>void 0)}catch{}return c}function $e(e){return{...e,config:t(e.config),transport:e.transport??z()}}function et(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 tt(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 nt(e){let t=Qe(e);t.dispatch=e=>ye(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){i()&&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 rt(t,e)}}}function rt(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`,reason:`never-existed`}),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`,reason:`never-existed`})):new Promise(a=>{let o=()=>{i(),n.size===0&&(e.listeners.delete(o),a(t.map(e=>r.get(e)??{localId:e,status:`missing`,reason:`never-existed`})))};e.listeners.add(o)})}(function(e){e.Phases={validating:`validating`,creating_intent:`creating_intent`,ready:`ready`,queued:`queued`,uploading:`uploading`,paused:`paused`,completing:`completing`,completed:`completed`,error:`error`,canceled:`canceled`}})(it||={});var it;export{t as C,f as S,y as _,je as a,_ as b,ke as c,k as d,m as f,h as g,g as h,q as i,De as l,d as m,nt as n,K as o,v as p,J as r,Ae as s,it as t,z as u,b as v,x,p as y};