@gentleduck/upload 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/package.json +5 -1
- package/src/core/contracts/transport/transport.ts +1 -1
- package/src/core/engine/store/handlers/clean-up.ts +4 -4
- package/src/core/engine/store/handlers/create-intent.ts +2 -2
- package/src/core/engine/store/store.libs.ts +2 -2
- package/src/core/persistence/persistence.ts +4 -4
- package/src/core/persistence/persistence.types.ts +1 -1
- package/src/core/utils/emitter/index.ts +1 -0
- package/src/strategies/multipart/index.ts +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -812,7 +812,7 @@ type PersistedUploadItem<M, C, P extends string> = {
|
|
|
812
812
|
/** Backend-provided "intent" that describes how to upload (strategy, URLs, fields, etc). */
|
|
813
813
|
intent: M[keyof M];
|
|
814
814
|
/** Strategy cursor used to resume uploads that support continuation. */
|
|
815
|
-
cursor?: AnyCursor<C
|
|
815
|
+
cursor?: AnyCursor<C & Record<string, unknown>>;
|
|
816
816
|
/** Last-known progress for UX continuity after restoring from persistence. */
|
|
817
817
|
progress?: {
|
|
818
818
|
uploadedBytes: number;
|
|
@@ -877,7 +877,7 @@ declare function deserializeSnapshot<M extends IntentMap, C extends CursorMap<M>
|
|
|
877
877
|
*
|
|
878
878
|
* @typeParam E - Event map (`eventName -> payload`).
|
|
879
879
|
*/
|
|
880
|
-
type TypedEmitter
|
|
880
|
+
type TypedEmitter<E extends Record<string, unknown>> = {
|
|
881
881
|
/**
|
|
882
882
|
* Subscribe to an event.
|
|
883
883
|
* @returns Unsubscribe function.
|
|
@@ -893,7 +893,7 @@ type TypedEmitter$1<E extends Record<string, unknown>> = {
|
|
|
893
893
|
};
|
|
894
894
|
//#endregion
|
|
895
895
|
//#region src/core/utils/emitter/index.d.ts
|
|
896
|
-
declare function createTypedEmitter<E extends Record<string, unknown>>(): TypedEmitter
|
|
896
|
+
declare function createTypedEmitter<E extends Record<string, unknown>>(): TypedEmitter<E>;
|
|
897
897
|
//#endregion
|
|
898
898
|
//#region src/core/engine/event-map.types.d.ts
|
|
899
899
|
/**
|
|
@@ -1414,4 +1414,4 @@ declare function PostStrategy<M extends IntentMap & {
|
|
|
1414
1414
|
//#region src/strategies/registry/index.d.ts
|
|
1415
1415
|
declare function createStrategyRegistry<M extends IntentMap, C extends CursorMap<M>, P extends string = string, R extends UploadResultBase = UploadResultBase>(): StrategyRegistry<M, C, P, R>;
|
|
1416
1416
|
//#endregion
|
|
1417
|
-
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, type StrategyCtx, StrategyKey, type StrategyRegistry, UploadApi, UploadClient, UploadCommand, UploadCompletionKind, UploadConfig, UploadConfigInput, UploadError, UploadErrorBase, UploadEventMap, UploadHooks, UploadItem, UploadOutcome, UploadPhase, UploadPlugin, UploadProgress, UploadProvider, UploadResultBase, UploadState, UploadStore, type UploadStrategy, UploadTransport, UploadValidationRules, WithSignal, computeFingerprint, createReducer, createStrategyRegistry, createTypedEmitter, createUploadClient, createUploadFactory, createUploadStore, createXHRTransport, deserializeSnapshot, fingerprintMatches, generateId, isRecord, multipartStrategy, resolveUploadConfig, serializeSnapshot, sleep, useUploadStore, useUploader, useUploaderActions, validateFile, validateFileList, validateIntent };
|
|
1417
|
+
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, type StrategyCtx, StrategyKey, type StrategyRegistry, type TypedEmitter, UploadApi, UploadClient, UploadCommand, UploadCompletionKind, UploadConfig, UploadConfigInput, UploadError, UploadErrorBase, UploadEventMap, UploadHooks, UploadItem, UploadOutcome, UploadPhase, UploadPlugin, UploadProgress, UploadProvider, UploadResultBase, UploadState, UploadStore, type UploadStrategy, UploadTransport, UploadValidationRules, WithSignal, computeFingerprint, createReducer, createStrategyRegistry, createTypedEmitter, createUploadClient, createUploadFactory, createUploadStore, createXHRTransport, deserializeSnapshot, fingerprintMatches, generateId, isRecord, multipartStrategy, resolveUploadConfig, serializeSnapshot, sleep, useUploadStore, useUploader, useUploaderActions, validateFile, validateFileList, validateIntent };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{jsxDEV as e}from"react/jsx-dev-runtime";import*as t from"react";import{createContext as n,useContext as r}from"react";const i=500,a=1e4,o=100,s=3,c=3,l=100;function u(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 d(){return`upload_${Date.now()}_${Math.random().toString(36).slice(2)}`}function f(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 p(e){return!!e&&typeof e==`object`}function m(e,t){if(!p(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 h(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=f(r,t,n);if(e){o.push({file:r,reason:e});continue}a.push(r),--c}return{valid:a,rejected:o}}function g(e){return{name:e.name,size:e.size,type:e.type,lastModified:e.lastModified}}function _(e,t){return e.name===t.name&&e.size===t.size&&e.lastModified===t.lastModified}function v(e){return new Promise(t=>setTimeout(t,e))}async function ee(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 te(e,t){let n=e.config
|
|
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 me(e){return/^https?:\/\//i.test(e)}function he(e){let t=e.getResponseHeader(`ETag`)??e.getResponseHeader(`etag`);if(t)return t.replace(/"/g,``)}function T(e){return new Promise((t,n)=>{if(!e.url||typeof e.url!=`string`){n(Error(`UploadTransport: missing url`));return}if(!me(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(w(C(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(w(C(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:pe(r),etag:he(r)}):()=>n(Error(`Upload failed with status ${e}`)))},r.onerror=()=>a(()=>n(fe(r,`Network error during upload`))),r.onabort=()=>a(()=>n(w(e.signal?.aborted?C(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 ge(){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 T({method:`POST`,url:e.url,body:t,signal:e.signal,onProgress:e.onProgress})).headers}},async put(e){let t=await T({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 T({method:`PATCH`,url:e.url,body:e.body,signal:e.signal,headers:e.headers,onProgress:e.onProgress})).headers}}}}const E=`snapshots`;function D(){return new Promise((e,t)=>{let n=indexedDB.open(`upload-engine`,1);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(E)||e.createObjectStore(E)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}function _e(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error)})}function O(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 ve={async load(e){if(typeof indexedDB>`u`)return null;let t=(await D()).transaction(E,`readonly`),n=t.objectStore(E);try{let r=await _e(n.get(e));return await O(t),r??null}catch{try{await O(t)}catch{}return null}},async save(e,t){if(typeof indexedDB>`u`)return;let n=(await D()).transaction(E,`readwrite`),r=n.objectStore(E);try{r.put(t,e),await O(n)}catch{try{n.abort()}catch{}}},async clear(e){if(typeof indexedDB>`u`)return;let t=(await D()).transaction(E,`readwrite`),n=t.objectStore(E);try{n.delete(e),await O(t)}catch{try{t.abort()}catch{}}}},ye={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)}},k=new Map,be={load(e){return k.get(e)??null},save(e,t){k.set(e,t)},clear(e){k.delete(e)}};function A(e,t){let n={};for(let t of e.items.values()){if(!b(t)||t.phase===`completed`||t.phase===`canceled`||t.phase===`error`)continue;let e=x(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 j(e,t){if(!t.isPurpose||!t.isIntent||!xe(e))return null;let n=new Map;for(let r of Object.values(e.items)){let i=Se(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||!we(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 xe(e){return!p(e)||typeof e.version!=`number`||typeof e.createdAt!=`number`?!1:p(e.items)}function Se(e){if(!p(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||!p(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 u=Ce(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:u}}function Ce(e){if(!p(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 we(e,t){return!(!p(e)||typeof e.strategy!=`string`||!t(e.strategy))}function M(){let e={};return{on(t,n){return(e[t]??(e[t]=new Set)).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 N(){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,P(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&&_(g(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:Te(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:Te(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,P(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 P(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 Te(e,t){return t<=0?0:Math.min(100,e/t*100)}function Ee(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(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 De(e,t){let n=e.state.items.get(t);if(!n||n.phase!==`creating_intent`||e.inflightIntents.has(t))return;let r=new AbortController;e.inflightIntents.set(t,r);try{let i=await e.opts.api.createIntent({purpose:n.purpose,contentType:n.file.type||`application/octet-stream`,size:n.file.size,filename:n.file.name,checksum:n.fingerprint.checksum},{signal:r.signal});e.inflightIntents.delete(t);let a=e.state.items.get(t);if(!a||a.phase!==`creating_intent`)return;let o=m(i,i.strategy);if(o){let n={code:`validation_failed`,message:`Invalid intent from backend: ${o.message}`,cause:o,retryable:!1};e.applyInternal({type:`intent.failed`,localId:t,error:n,retryable:!1});return}e.applyInternal({type:`intent.ok`,localId:t,intent:i})}catch(i){if(e.inflightIntents.delete(t),r.signal.aborted)return;let a=y(i,e.opts.errorNormalizer),o={...a,message:`${a.message} (file: ${n.fingerprint.name}, size: ${n.file.size} bytes, purpose: ${n.purpose})`},s=S(e.opts.config,{phase:`intent`,attempt:n.attempt,error:o});e.applyInternal({type:`intent.failed`,localId:t,error:o,retryable:s.retryable}),s.retryable&&s.delayMs!==void 0&&e.enqueueEffect(async()=>{await v(s.delayMs),e.dispatch({type:`retry`,localId:t})})}}async function Oe(e,t){let n=e.state.items.get(t);if(!n||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},{signal:r.signal});e.inflightCompletes.delete(t);let a=e.state.items.get(t);if(!a||a.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=y(n,e.opts.errorNormalizer),a=e.state.items.get(t),o=a?{...i,message:`${i.message} (file: ${a.fingerprint.name}, fileId: ${(b(a)&&a.intent?.fileId)??`unknown`})`}:i,s=a&&`attempt`in a?a.attempt??1:1,c=S(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 v(c.delayMs),e.dispatch({type:`retry`,localId:t})})}}async function ke(e,t){let n=e.state.items.get(t);if(!n||n.phase!==`uploading`||!b(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()})};if(i.signal.aborted){if(e.inflightUploads.delete(t),r.mode===`pause`){let r=e.state.items.get(t),i;n&&b(n)&&(i=n.intent.strategy);let a=(r&&x(r)?r.cursor:void 0)||(i?{strategy:i,value:void 0}:void 0);a&&o(a);return}if(r.mode===`cancel`){a();return}return}let s=n.intent.strategy;if(!e.opts.strategies.has(s)){e.inflightUploads.delete(t);let n={code:`strategy_missing`,message:`Strategy not found: ${s}`,strategy:String(s),retryable:!1};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={code:`strategy_missing`,message:`Strategy not found: ${n.intent.strategy}`,strategy:String(n.intent.strategy),retryable:!1};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({file:n.file,intent:n.intent,signal:i.signal,transport:e.opts.transport,api:e.opts.api,readCursor:()=>{let n=e.state.items.get(t);if(!(!n||!x(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&&x(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&&x(n)?n.cursor:void 0)||{strategy:s,value:void 0});return}if(r?.mode===`cancel`){a();return}a();return}let c=y(n,e.opts.errorNormalizer),l=e.state.items.get(t),u=l?{...c,message:`${c.message} (file: ${l.fingerprint.name}, size: ${(ne(l)&&l.file?.size)??0} bytes, phase: ${l.phase})`}:c,d=S(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 v(d.delayMs),e.dispatch({type:`retry`,localId:t})})}}function Ae(e){if(!e.scheduling){e.scheduling=!0;try{je(e),Ne(e),Me(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(()=>De(e,t.localId)))}function Me(e){for(let t of e.state.items.values())t.phase===`completing`&&(e.inflightCompletes.has(t.localId)||e.enqueueEffect(()=>Oe(e,t.localId)))}function Ne(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`&&te(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()}),ke(e,t.localId)}}function Pe(e){return e.phase===`queued`}function Fe(e){let t=Ie(e),n=t.persistence,r=null,i=Math.max(0,n?.debounceMs??200),a=async()=>{if(n)try{let e=(n.serialize??A)(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:M(),reduce:N(),inflightUploads:new Map,inflightIntents:new Map,inflightCompletes:new Map,effectQueue:[],processingEffects:!1,scheduling:!1,notify(){s.listeners.forEach(e=>e())},scheduleWork(){Ae(s)},applyInternal(e){let t=s.state;s.state=s.reduce(t,e),s.opts.hooks?.onInternalEvent?.(e,s.state),Le(s,e);let n=Ee(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,Re(s,e,t,n);let r=Ee(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??j)(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 Ie(e){return{...e,config:u(e.config),transport:e.transport??ge()}}function Le(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 Re(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 ze(e){let t=Fe(e);t.dispatch=e=>ce(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 Be(t,e)}}}function Be(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 ze(e)}
|
|
1
|
+
import{jsxDEV as e}from"react/jsx-dev-runtime";import*as t from"react";import{createContext as n,useContext as r}from"react";const i=500,a=1e4,o=100,s=3,c=3,l=100;function u(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 d(){return`upload_${Date.now()}_${Math.random().toString(36).slice(2)}`}function f(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 p(e){return!!e&&typeof e==`object`}function m(e,t){if(!p(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 h(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=f(r,t,n);if(e){o.push({file:r,reason:e});continue}a.push(r),--c}return{valid:a,rejected:o}}function g(e){return{name:e.name,size:e.size,type:e.type,lastModified:e.lastModified}}function _(e,t){return e.name===t.name&&e.size===t.size&&e.lastModified===t.lastModified}function v(e){return new Promise(t=>setTimeout(t,e))}async function ee(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 te(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 y(e,t){if(t)return t(e);if(ie(e))return{code:`aborted`,message:`Upload aborted`,reason:String(e.reason??`unknown`),retryable:!1};let n=p(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(p(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 b(e){return`intent`in e&&!!e.intent}function ne(e){return`file`in e&&!!e.file}function x(e){return`cursor`in e}function re(e){return p(e)&&e.strategy===`multipart`&&typeof e.fileId==`string`&&typeof e.uploadId==`string`}function ie(e){return p(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 n=e.maxAttempts;return t.attempt<n?{retryable:!0,delayMs:Math.min(a,500*2**(t.attempt-1))}:{retryable:!1}}function ae(e,t,n){let r=Array.from(e.state.items.values()).filter(e=>e.purpose===n&&e.phase!==`canceled`).length,{valid:i,rejected:a}=h(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=d(),i=(e.opts.fingerprint??g)(t);s.push({localId:r,purpose:n,file:t,fingerprint:i,createdAt:o}),e.enqueueEffect(async()=>{let a;try{a=await ee(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=f(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 oe(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&&b(n)&&n.intent.strategy===`multipart`&&e.opts.api.multipart?.abort){let t=n.intent;re(t)&&e.enqueueEffect(async()=>{try{let n=e.opts.api.multipart?.abort;n&&await n({fileId:t.fileId,uploadId:t.uploadId})}catch{}})}}function se(e,t){let n=e.inflightUploads.get(t);if(n){n.mode=`pause`,n.controller.abort({reason:`pause`});return}}function ce(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`){ae(e,t.files,t.purpose);return}if(t.type===`pause`){se(e,t.localId),e.applyCommand(t);return}if(t.type===`cancel`){oe(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 le(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function C(e){return e?.reason}function ue(e){if(!e)return`unknown`;if(typeof e==`string`)return e===`pause`||e===`cancel`?e:`unknown`;if(p(e)&&`reason`in e){let t=e.reason;if(t===`pause`||t===`cancel`)return t}if(p(e)&&`kind`in e){let t=e.kind;if(t===`pause`||t===`cancel`)return t}return`unknown`}var de=class extends Error{constructor(e){super(`Upload aborted`),le(this,`code`,`aborted`),le(this,`reason`,void 0),this.reason=ue(e)}};function w(e){return new de(e)}function fe(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 pe(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 me(e){return/^https?:\/\//i.test(e)}function he(e){let t=e.getResponseHeader(`ETag`)??e.getResponseHeader(`etag`);if(t)return t.replace(/"/g,``)}function T(e){return new Promise((t,n)=>{if(!e.url||typeof e.url!=`string`){n(Error(`UploadTransport: missing url`));return}if(!me(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(w(C(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(w(C(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:pe(r),etag:he(r)}):()=>n(Error(`Upload failed with status ${e}`)))},r.onerror=()=>a(()=>n(fe(r,`Network error during upload`))),r.onabort=()=>a(()=>n(w(e.signal?.aborted?C(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 ge(){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 T({method:`POST`,url:e.url,body:t,signal:e.signal,onProgress:e.onProgress})).headers}},async put(e){let t=await T({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 T({method:`PATCH`,url:e.url,body:e.body,signal:e.signal,headers:e.headers,onProgress:e.onProgress})).headers}}}}const E=`snapshots`;function D(){return new Promise((e,t)=>{let n=indexedDB.open(`upload-engine`,1);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(E)||e.createObjectStore(E)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}function _e(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error)})}function O(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 ve={async load(e){if(typeof indexedDB>`u`)return null;let t=(await D()).transaction(E,`readonly`),n=t.objectStore(E);try{let r=await _e(n.get(e));return await O(t),r??null}catch{try{await O(t)}catch{}return null}},async save(e,t){if(typeof indexedDB>`u`)return;let n=(await D()).transaction(E,`readwrite`),r=n.objectStore(E);try{r.put(t,e),await O(n)}catch{try{n.abort()}catch{}}},async clear(e){if(typeof indexedDB>`u`)return;let t=(await D()).transaction(E,`readwrite`),n=t.objectStore(E);try{n.delete(e),await O(t)}catch{try{t.abort()}catch{}}}},ye={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)}},k=new Map,be={load(e){return k.get(e)??null},save(e,t){k.set(e,t)},clear(e){k.delete(e)}};function A(e,t){let n={};for(let t of e.items.values()){if(!b(t)||t.phase===`completed`||t.phase===`canceled`||t.phase===`error`)continue;let e=x(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 j(e,t){if(!t.isPurpose||!t.isIntent||!xe(e))return null;let n=new Map;for(let r of Object.values(e.items)){let i=Se(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||!we(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 xe(e){return!p(e)||typeof e.version!=`number`||typeof e.createdAt!=`number`?!1:p(e.items)}function Se(e){if(!p(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||!p(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 u=Ce(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:u}}function Ce(e){if(!p(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 we(e,t){return!(!p(e)||typeof e.strategy!=`string`||!t(e.strategy))}function M(){let e={};return{on(t,n){return(e[t]??(e[t]=new Set)).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 N(){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,P(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&&_(g(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:Te(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:Te(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,P(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 P(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 Te(e,t){return t<=0?0:Math.min(100,e/t*100)}function Ee(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 De(e,t){let n=e.state.items.get(t);if(!n||n.phase!==`creating_intent`||e.inflightIntents.has(t))return;let r=new AbortController;e.inflightIntents.set(t,r);try{let i=await e.opts.api.createIntent({purpose:n.purpose,contentType:n.file.type||`application/octet-stream`,size:n.file.size,filename:n.file.name,checksum:n.fingerprint.checksum},{signal:r.signal});e.inflightIntents.delete(t);let a=e.state.items.get(t);if(!a||a.phase!==`creating_intent`)return;let o=m(i,i.strategy);if(o){let n={code:`validation_failed`,message:`Invalid intent from backend: ${o.message}`,cause:o,retryable:!1};e.applyInternal({type:`intent.failed`,localId:t,error:n,retryable:!1});return}e.applyInternal({type:`intent.ok`,localId:t,intent:i})}catch(i){if(e.inflightIntents.delete(t),r.signal.aborted)return;let a=y(i,e.opts.errorNormalizer),o={...a,message:`${a.message} (file: ${n.fingerprint.name}, size: ${n.file.size} bytes, purpose: ${n.purpose})`},s=S(e.opts.config,{phase:`intent`,attempt:n.attempt,error:o});e.applyInternal({type:`intent.failed`,localId:t,error:o,retryable:s.retryable}),s.retryable&&s.delayMs!==void 0&&e.enqueueEffect(async()=>{await v(s.delayMs),e.dispatch({type:`retry`,localId:t})})}}async function Oe(e,t){let n=e.state.items.get(t);if(!n||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},{signal:r.signal});e.inflightCompletes.delete(t);let a=e.state.items.get(t);if(!a||a.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=y(n,e.opts.errorNormalizer),a=e.state.items.get(t),o=a?{...i,message:`${i.message} (file: ${a.fingerprint.name}, fileId: ${(b(a)&&a.intent?.fileId)??`unknown`})`}:i,s=a&&`attempt`in a?a.attempt??1:1,c=S(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 v(c.delayMs),e.dispatch({type:`retry`,localId:t})})}}async function ke(e,t){let n=e.state.items.get(t);if(!n||n.phase!==`uploading`||!b(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()})};if(i.signal.aborted){if(e.inflightUploads.delete(t),r.mode===`pause`){let r=e.state.items.get(t),i;n&&b(n)&&(i=n.intent.strategy);let a=(r&&x(r)?r.cursor:void 0)||(i?{strategy:i,value:void 0}:void 0);a&&o(a);return}if(r.mode===`cancel`){a();return}return}let s=n.intent.strategy;if(!e.opts.strategies.has(s)){e.inflightUploads.delete(t);let n={code:`strategy_missing`,message:`Strategy not found: ${s}`,strategy:String(s),retryable:!1};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={code:`strategy_missing`,message:`Strategy not found: ${n.intent.strategy}`,strategy:String(n.intent.strategy),retryable:!1};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({file:n.file,intent:n.intent,signal:i.signal,transport:e.opts.transport,api:e.opts.api,readCursor:()=>{let n=e.state.items.get(t);if(!(!n||!x(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&&x(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&&x(n)?n.cursor:void 0)||{strategy:s,value:void 0});return}if(r?.mode===`cancel`){a();return}a();return}let c=y(n,e.opts.errorNormalizer),l=e.state.items.get(t),u=l?{...c,message:`${c.message} (file: ${l.fingerprint.name}, size: ${(ne(l)&&l.file?.size)??0} bytes, phase: ${l.phase})`}:c,d=S(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 v(d.delayMs),e.dispatch({type:`retry`,localId:t})})}}function Ae(e){if(!e.scheduling){e.scheduling=!0;try{je(e),Ne(e),Me(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(()=>De(e,t.localId)))}function Me(e){for(let t of e.state.items.values())t.phase===`completing`&&(e.inflightCompletes.has(t.localId)||e.enqueueEffect(()=>Oe(e,t.localId)))}function Ne(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`&&te(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()}),ke(e,t.localId)}}function Pe(e){return e.phase===`queued`}function Fe(e){let t=Ie(e),n=t.persistence,r=null,i=Math.max(0,n?.debounceMs??200),a=async()=>{if(n)try{let e=(n.serialize??A)(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:M(),reduce:N(),inflightUploads:new Map,inflightIntents:new Map,inflightCompletes:new Map,effectQueue:[],processingEffects:!1,scheduling:!1,notify(){s.listeners.forEach(e=>e())},scheduleWork(){Ae(s)},applyInternal(e){let t=s.state;s.state=s.reduce(t,e),s.opts.hooks?.onInternalEvent?.(e,s.state),Le(s,e);let n=Ee(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,Re(s,e,t,n);let r=Ee(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??j)(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 Ie(e){return{...e,config:u(e.config),transport:e.transport??ge()}}function Le(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 Re(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 ze(e){let t=Fe(e);t.dispatch=e=>ce(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 Be(t,e)}}}function Be(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 ze(e)}
|
|
3
3
|
/*! Copyright (c) Meta Platforms, Inc. and affiliates. **/
|
|
4
4
|
const F=Symbol.for(`react.forward_ref`),I=Symbol.for(`react.memo`);let He=new Map,Ue=new WeakMap,L=new WeakMap;const R=new WeakMap;let z=[];const We=new Map,Ge=new Map,Ke=new Set,qe=new Set;let B=new WeakMap,V=!1;function H(e){if(e.fullKey!==null)return e.fullKey;let t=e.ownKey,n;try{n=e.getCustomHooks()}catch{return e.forceReset=!0,e.fullKey=t,t}for(let r=0;r<n.length;r++){let i=n[r];if(typeof i!=`function`)return e.forceReset=!0,e.fullKey=t,t;let a=L.get(i);if(a===void 0)continue;let o=H(a);a.forceReset&&(e.forceReset=!0),t+=`
|
|
5
5
|
---
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@gentleduck/upload",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
5
8
|
"type": "module",
|
|
6
9
|
"exports": {
|
|
7
10
|
".": "./src/index.ts",
|
|
@@ -30,6 +33,7 @@
|
|
|
30
33
|
"devDependencies": {
|
|
31
34
|
"@gentleduck/tsdown-config": "workspace:*",
|
|
32
35
|
"@gentleduck/typescript-config": "workspace:*",
|
|
36
|
+
"@types/node": "^25.4.0",
|
|
33
37
|
"@types/react": "^19.2.14",
|
|
34
38
|
"@types/react-dom": "^19.2.3",
|
|
35
39
|
"tsdown": "0.18.3",
|
|
@@ -8,8 +8,8 @@ export function cleanupOldItems<
|
|
|
8
8
|
P extends string,
|
|
9
9
|
R extends UploadResultBase,
|
|
10
10
|
>(opts: StoreOptions<M, C, P, R>, state: UploadState<M, C, P, R>): UploadState<M, C, P, R> | null {
|
|
11
|
-
const maxItems = opts.config
|
|
12
|
-
const completedTTL = opts.config
|
|
11
|
+
const maxItems = opts.config?.maxItems
|
|
12
|
+
const completedTTL = opts.config?.completedItemTTL
|
|
13
13
|
const now = Date.now()
|
|
14
14
|
|
|
15
15
|
// Collect items to remove
|
|
@@ -39,7 +39,7 @@ export function cleanupOldItems<
|
|
|
39
39
|
|
|
40
40
|
// If still over limit, remove oldest completed/canceled items
|
|
41
41
|
const remainingItems = items.filter(([id]) => !toRemove.includes(id))
|
|
42
|
-
if (remainingItems.length > maxItems) {
|
|
42
|
+
if (maxItems !== undefined && remainingItems.length > maxItems) {
|
|
43
43
|
const completedOrCanceled = remainingItems
|
|
44
44
|
.filter(([, item]) => item.phase === 'completed' || item.phase === 'canceled')
|
|
45
45
|
.sort(([, a], [, b]) => {
|
|
@@ -48,7 +48,7 @@ export function cleanupOldItems<
|
|
|
48
48
|
return aTime - bTime
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
const excess = remainingItems.length - maxItems
|
|
51
|
+
const excess = remainingItems.length - maxItems!
|
|
52
52
|
for (let i = 0; i < excess && i < completedOrCanceled.length; i++) {
|
|
53
53
|
toRemove.push(completedOrCanceled[i][0])
|
|
54
54
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CursorMap, IntentMap, UploadError, UploadResultBase } from '../../../contracts'
|
|
1
|
+
import type { AnyIntent, CursorMap, IntentMap, UploadError, UploadResultBase } from '../../../contracts'
|
|
2
2
|
import { validateIntent } from '../../validation'
|
|
3
3
|
import { normalizeError, retryDecision, sleep } from '../store.libs'
|
|
4
4
|
import type { StoreRuntime } from '../store.types'
|
|
@@ -47,7 +47,7 @@ export async function createIntent<
|
|
|
47
47
|
return
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
rt.applyInternal({ type: 'intent.ok', localId, intent })
|
|
50
|
+
rt.applyInternal({ type: 'intent.ok', localId, intent: intent as AnyIntent<M> })
|
|
51
51
|
} catch (err: unknown) {
|
|
52
52
|
rt.inflightIntents.delete(localId)
|
|
53
53
|
|
|
@@ -32,7 +32,7 @@ export function isAutoStart<M extends IntentMap, C extends CursorMap<M>, P exten
|
|
|
32
32
|
opts: StoreOptions<M, C, P, R>,
|
|
33
33
|
purpose: P,
|
|
34
34
|
): boolean {
|
|
35
|
-
const v = opts.config
|
|
35
|
+
const v = opts.config?.autoStart
|
|
36
36
|
if (v === undefined) return false
|
|
37
37
|
if (Array.isArray(v)) return v.includes(purpose)
|
|
38
38
|
if (typeof v === 'function') return v(purpose)
|
|
@@ -73,7 +73,7 @@ export function normalizeError(err: unknown, customNormalizer?: (err: unknown) =
|
|
|
73
73
|
|
|
74
74
|
// HTTP errors
|
|
75
75
|
if (isRecord(err) && (typeof err.status === 'number' || typeof err.statusCode === 'number')) {
|
|
76
|
-
const status = typeof err.status === 'number' ? err.status : err.statusCode
|
|
76
|
+
const status = (typeof err.status === 'number' ? err.status : err.statusCode) as number
|
|
77
77
|
const retryable = status >= 500 || status === 429
|
|
78
78
|
return { code: 'http', status, message: String(msg), cause: err, retryable }
|
|
79
79
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyCursor, CursorMap, IntentMap, UploadResultBase } from '../contracts'
|
|
1
|
+
import type { AnyCursor, AnyIntent, CursorMap, IntentMap, UploadResultBase } from '../contracts'
|
|
2
2
|
import type { UploadItem } from '../engine/internal-events.types'
|
|
3
3
|
import type { UploadState } from '../engine/reducer'
|
|
4
4
|
import { hasCursor, hasIntent } from '../engine/store/store.libs'
|
|
@@ -22,7 +22,7 @@ export function serializeSnapshot<
|
|
|
22
22
|
if (!hasIntent(item)) continue
|
|
23
23
|
|
|
24
24
|
// Do not persist terminal items. Persistence is for resuming and recovery, not history.
|
|
25
|
-
if (item.phase === 'completed' || item.phase === 'canceled' || item.phase === 'error') continue
|
|
25
|
+
if ((item.phase as string) === 'completed' || (item.phase as string) === 'canceled' || (item.phase as string) === 'error') continue
|
|
26
26
|
|
|
27
27
|
const cursor = hasCursor(item) ? item.cursor : undefined
|
|
28
28
|
|
|
@@ -116,13 +116,13 @@ export function deserializeSnapshot<
|
|
|
116
116
|
lastModified: parsed.file.lastModified,
|
|
117
117
|
checksum: parsed.file.checksum,
|
|
118
118
|
},
|
|
119
|
-
intent: parsed.intent
|
|
119
|
+
intent: parsed.intent as AnyIntent<M>,
|
|
120
120
|
cursor: parsed.cursor,
|
|
121
121
|
progress: { uploadedBytes, totalBytes, pct },
|
|
122
122
|
pausedAt: Date.now(),
|
|
123
123
|
createdAt: raw.createdAt ?? Date.now(),
|
|
124
124
|
file: undefined,
|
|
125
|
-
})
|
|
125
|
+
} as UploadItem<M, C, P, R>)
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
return { items }
|
|
@@ -41,7 +41,7 @@ export type PersistedUploadItem<M, C, P extends string> = {
|
|
|
41
41
|
/** Backend-provided "intent" that describes how to upload (strategy, URLs, fields, etc). */
|
|
42
42
|
intent: M[keyof M]
|
|
43
43
|
/** Strategy cursor used to resume uploads that support continuation. */
|
|
44
|
-
cursor?: import('../contracts').AnyCursor<C
|
|
44
|
+
cursor?: import('../contracts').AnyCursor<C & Record<string, unknown>>
|
|
45
45
|
/** Last-known progress for UX continuity after restoring from persistence. */
|
|
46
46
|
progress?: { uploadedBytes: number; totalBytes: number; pct?: number }
|
|
47
47
|
/** The last known state-machine phase at the time of persistence. */
|
|
@@ -160,7 +160,7 @@ export function multipartStrategy<
|
|
|
160
160
|
.map(([partNumber, v]) => ({ partNumber, etag: v.etag, size: v.size }))
|
|
161
161
|
.sort((a, b) => a.partNumber - b.partNumber),
|
|
162
162
|
}
|
|
163
|
-
ctx.persistCursor(snapshot)
|
|
163
|
+
ctx.persistCursor(snapshot as C["multipart"])
|
|
164
164
|
report()
|
|
165
165
|
} catch (err) {
|
|
166
166
|
inflightBytes.delete(p.partNumber)
|
|
@@ -236,7 +236,7 @@ export function multipartStrategy<
|
|
|
236
236
|
.sort((a, b) => a.partNumber - b.partNumber),
|
|
237
237
|
completed: true,
|
|
238
238
|
}
|
|
239
|
-
ctx.persistCursor(snapshot)
|
|
239
|
+
ctx.persistCursor(snapshot as C["multipart"])
|
|
240
240
|
}
|
|
241
241
|
},
|
|
242
242
|
}
|