@gentleduck/upload 0.2.2 → 0.2.4

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.js CHANGED
@@ -1,6 +1 @@
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
- /*! Copyright (c) Meta Platforms, Inc. and affiliates. **/
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
- ---
6
- `+o}return e.fullKey=t,t}function Je(e,t){let n=L.get(e),r=L.get(t);return n===void 0&&r===void 0?!0:!(n===void 0||r===void 0||H(n)!==H(r)||r.forceReset)}function Ye(e){return e.prototype&&e.prototype.isReactComponent}function Xe(e,t){return Ye(e)||Ye(t)?!1:!!Je(e,t)}function Ze(e){return R.get(e)}function U(e,t){try{return e[t]}catch{return}}function Qe(){if(z.length===0||V)return null;V=!0;try{let e=new Set,t=new Set,n=z;z=[],n.forEach(([n,r])=>{let i=n.current;R.set(i,n),R.set(r,n),n.current=r,Xe(i,r)?t.add(n):e.add(n)});let r={updatedFamilies:t,staleFamilies:e};We.forEach(e=>{e.setRefreshHandler(Ze)});let i=!1,a=null,o=new Set(qe),s=new Set(Ke),c=new Map(Ge);if(o.forEach(e=>{let t=c.get(e);if(t===void 0)throw Error(`Could not find helpers for a root. This is a bug in React Refresh.`);if(qe.has(e),B===null||!B.has(e))return;let n=B.get(e);try{t.scheduleRoot(e,n)}catch(e){i||(i=!0,a=e)}}),s.forEach(e=>{let t=c.get(e);if(t===void 0)throw Error(`Could not find helpers for a root. This is a bug in React Refresh.`);Ke.has(e);try{t.scheduleRefresh(e,r)}catch(e){i||(i=!0,a=e)}}),i)throw a;return r}finally{V=!1}}function W(e,t){if(e===null||typeof e!=`function`&&typeof e!=`object`||Ue.has(e))return;let n=He.get(t);if(n===void 0?(n={current:e},He.set(t,n)):z.push([n,e]),Ue.set(e,n),typeof e==`object`&&e)switch(U(e,`$$typeof`)){case F:W(e.render,t+`$render`);break;case I:W(e.type,t+`$type`);break}}function G(e,t,n,r){if(L.has(e)||L.set(e,{forceReset:n,ownKey:t,fullKey:null,getCustomHooks:r||(()=>[])}),typeof e==`object`&&e)switch(U(e,`$$typeof`)){case F:G(e.render,t,n,r);break;case I:G(e.type,t,n,r);break}}function $e(e){let t=L.get(e);t!==void 0&&H(t)}function et(){let e,t,n=!1;return function(r,i,a,o){if(typeof i==`string`)return e||(e=r,t=typeof o==`function`),r!=null&&(typeof r==`function`||typeof r==`object`)&&G(r,i,a,o),r;!n&&t&&(n=!0,$e(e))}}function K(e){switch(typeof e){case`function`:{if(e.prototype!=null){if(e.prototype.isReactComponent)return!0;let t=Object.getOwnPropertyNames(e.prototype);if(t.length>1||t[0]!==`constructor`||e.prototype.__proto__!==Object.prototype)return!1}let t=e.name||e.displayName;return typeof t==`string`&&/^[A-Z]/.test(t)}case`object`:if(e!=null)switch(U(e,`$$typeof`)){case F:case I:return!0;default:return!1}return!1;default:return!1}}function tt(e){if(!nt(e))return!1;for(let t in e)if(!K(e[t]))return!1;return!0}function nt(e){return Object.prototype.toString.call(e)===`[object Object]`&&(e.constructor===Object||e.constructor===void 0)}function rt(e){return(t,n)=>W(t,e+` `+n)}function it(e,t){for(let n in t){if(n===`__esModule`)continue;let r=t[n];if(K(r))W(r,e+` export `+n);else if(tt(r))for(let t in r)W(r[t],e+` export `+n+`-`+t)}}function at(e,t){let n;return()=>{clearTimeout(n),n=setTimeout(e,t)}}const q=[];window.__registerBeforePerformReactRefresh=e=>{q.push(e)};const ot=at(async()=>{q.length&&await Promise.all(q.map(e=>e())),Qe()},16);function st(e,t,n){let r=window.__getReactRefreshIgnoredExports?.({id:e})??[];if(J(r,t,e=>e in n)!==!0)return`Could not Fast Refresh (export removed)`;if(J(r,n,e=>e in t)!==!0)return`Could not Fast Refresh (new export)`;let i=!1,a=J(r,n,(e,r)=>(i=!0,K(r)||tt(r)?!0:t[e]===n[e]));if(i&&a===!0)ot();else return`Could not Fast Refresh ("${a}" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc#consistent-components-exports`}function J(e,t,n){for(let r in t){if(r===`__esModule`||e.includes(r))continue;let i=Object.getOwnPropertyDescriptor(t,r);if(i&&i.get||!n(r,t[r]))return r}return!0}const ct=e=>import(e),Y=typeof WorkerGlobalScope<`u`&&self instanceof WorkerGlobalScope;let lt,ut;if(import.meta.hot&&!Y){if(!window.$RefreshReg$)throw Error(`@vitejs/plugin-react-swc can't detect preamble. Something is wrong.`);lt=window.$RefreshReg$,ut=window.$RefreshSig$,window.$RefreshReg$=rt(`/home/runner/work/duck-upload/duck-upload/packages/duck-upload/src/react/upload-provider.tsx`),window.$RefreshSig$=et}var dt=$RefreshSig$();const ft=n(null);function pt({store:t,children:n}){return e(ft.Provider,{value:t,children:n},void 0,!1,{fileName:`/home/runner/work/duck-upload/duck-upload/packages/duck-upload/src/react/upload-provider.tsx`,lineNumber:52,columnNumber:10},this)}Z=pt;function X(){dt();let e=r(ft);if(!e||!mt(e))throw Error(`useUploadStore must be used within UploadProvider`);return e}dt(X,`Wl1jJrZzTCxjtMcqth/dX4wg3Uo=`);function mt(e){return p(e)?typeof e.getSnapshot==`function`&&typeof e.subscribe==`function`&&typeof e.dispatch==`function`&&typeof e.on==`function`&&typeof e.off==`function`&&typeof e.waitFor==`function`:!1}var Z;$RefreshReg$(Z,`UploadProvider`),import.meta.hot&&!Y&&(window.$RefreshReg$=lt,window.$RefreshSig$=ut),import.meta.hot&&!Y&&ct(import.meta.url).then(e=>{it(`/home/runner/work/duck-upload/duck-upload/packages/duck-upload/src/react/upload-provider.tsx`,e),import.meta.hot.accept(t=>{if(!t)return;let n=st(`/home/runner/work/duck-upload/duck-upload/packages/duck-upload/src/react/upload-provider.tsx`,e,t);n&&import.meta.hot.invalidate(n)})});var ht=$RefreshSig$(),gt=$RefreshSig$();function _t(e){var t=$RefreshSig$();return t(()=>(t(),Q(e)),`koxc0gANW1Ok/JXE6Hee4XqLL8g=`,!1,function(){return[Q]})}function Q(e){ht();let n=e??X(),r=t.useSyncExternalStore(n.subscribe.bind(n),n.getSnapshot.bind(n),n.getSnapshot.bind(n)),i=t.useMemo(()=>Array.from(r.items.values()),[r.items]),a=t.useMemo(()=>{let e={};return i.forEach(t=>{e[t.phase]||(e[t.phase]=[]),e[t.phase].push(t)}),e},[i]);return{items:i,byPhase:a,dispatch:n.dispatch.bind(n),on:n.on.bind(n),off:n.off.bind(n),uploading:a.uploading||[],paused:a.paused||[],completed:a.completed||[],failed:a.error||[],ready:a.ready||[]}}ht(Q,`2QiAcG9/Y3tKduRxVnmpacwTbRk=`,!1,function(){return[X]});function $(){gt();let e=X();return{dispatch:t.useMemo(()=>e.dispatch.bind(e),[e]),on:t.useMemo(()=>e.on.bind(e),[e]),store:e}}gt($,`mmC0pzXKvfZAl3ye5rFLChFGvco=`,!1,function(){return[X]});function vt(e){return e instanceof Error&&(e.name===`AbortError`||/abort/i.test(e.message))}function yt(e){let t=Math.max(1,e?.maxPartConcurrency??4);return{id:`multipart`,resumable:!0,async start(e){let n=e.intent,r=e.file.size,i=Math.max(1,n.partSize),a=Math.max(1,n.partCount??Math.ceil(r/i)),o=e.readCursor(),s=new Map;if(o?.done)for(let e of o.done)s.set(e.partNumber,{etag:e.etag,size:e.size});let c=o?.completed===!0,l=new Map,u=0;for(let e of s.values())u+=e.size;let d=()=>{let t=0;for(let e of l.values())t+=e;e.reportProgress({uploadedBytes:u+t,totalBytes:r})},f=[];for(let e=1;e<=a;e++){if(s.has(e))continue;let t=(e-1)*i,n=Math.min(r,t+i);f.push({partNumber:e,start:t,end:n})}if(f.length===0){c||await _(),d();return}let p=f.slice(),m=new Set,h=async t=>{let r=n.parts?.find(e=>e.partNumber===t);if(r)return r;if(!e.api.multipart?.signPart)throw Error(`multipart.signPart is missing in UploadApi. Implement it to call your backend sign-part endpoint.`);let i=await e.api.multipart.signPart({fileId:n.fileId,uploadId:n.uploadId,partNumber:t},{signal:e.signal});return{partNumber:t,url:i.url,headers:i.headers}},g=async(t,n=0)=>{try{let n=e.file.slice(t.start,t.end),r=n.size,i=await h(t.partNumber),a=await e.transport.put({url:i.url,body:n,headers:i.headers,signal:e.signal,onProgress:e=>{l.set(t.partNumber,e),d()}});l.delete(t.partNumber);let o=a.etag;if(!o)throw Error(`Missing ETag from upload part response. Ensure MinIO/S3 CORS exposes ETag (Access-Control-Expose-Headers: ETag).`);u+=r,s.set(t.partNumber,{etag:o,size:r});let c={done:Array.from(s.entries()).map(([e,t])=>({partNumber:e,etag:t.etag,size:t.size})).sort((e,t)=>e.partNumber-t.partNumber)};e.persistCursor(c),d()}catch(r){if(l.delete(t.partNumber),e.signal?.aborted||vt(r))throw r;if(n<3){let e=r instanceof Error?r.message:String(r);if(/network/i.test(e)||/timeout/i.test(e)||/5\d\d/.test(e)||/ECONNRESET/i.test(e)||/EHOSTUNREACH/i.test(e))return await v(2**n*500),g(t,n+1)}throw r}};for(;p.length>0||m.size>0;){for(;p.length>0&&m.size<t;){let e=p.shift();if(!e)break;let t,n=g(e).finally(()=>{t&&m.delete(t)});t=n,m.add(n)}m.size>0&&await Promise.race(m)}await _(),d();async function _(){if(c)return;if(!e.api.multipart?.completeMultipart)throw Error(`multipart.completeMultipart is missing in UploadApi. Implement it to call your backend complete endpoint.`);let t=Array.from(s.entries()).map(([e,t])=>({partNumber:e,etag:t.etag})).sort((e,t)=>e.partNumber-t.partNumber);if(t.length!==a)throw Error(`Cannot complete multipart: expected ${a} parts, got ${t.length}`);await e.api.multipart.completeMultipart({fileId:n.fileId,uploadId:n.uploadId,parts:t},{signal:e.signal});let r={done:Array.from(s.entries()).map(([e,t])=>({partNumber:e,etag:t.etag,size:t.size})).sort((e,t)=>e.partNumber-t.partNumber),completed:!0};e.persistCursor(r)}}}}function bt(){return{id:`post`,resumable:!1,async start(e){let t=e.intent;if(!t.url)throw Error(`Direct strategy: intent missing url/uploadUrl`);await e.transport.postForm({url:t.url,file:e.file,fields:t.fields,filename:e.file.name,signal:e.signal,onProgress(t,n){e.reportProgress({uploadedBytes:t,totalBytes:n})}})}}}function xt(){let e={};return{get(t){return e[t]},has(t){return Object.hasOwn(e,t)},set(t){e[t.id]=t}}}export{s as DEFAULT_MAX_ATTEMPTS,c as DEFAULT_MAX_CONCURRENT_UPLOADS,l as DEFAULT_MAX_ITEMS,o as DEFAULT_PROGRESS_THROTTLE_MS,i as DEFAULT_RETRY_DELAY_BASE_MS,a as DEFAULT_RETRY_DELAY_MAX_MS,ve as IndexedDBAdapter,ye as LocalStorageAdapter,be as MemoryAdapter,bt as PostStrategy,pt as UploadProvider,g as computeFingerprint,N as createReducer,xt as createStrategyRegistry,M as createTypedEmitter,Ve as createUploadClient,_t as createUploadFactory,ze as createUploadStore,ge as createXHRTransport,j as deserializeSnapshot,_ as fingerprintMatches,d as generateId,p as isRecord,yt as multipartStrategy,u as resolveUploadConfig,A as serializeSnapshot,v as sleep,X as useUploadStore,Q as useUploader,$ as useUploaderActions,f as validateFile,h as validateFileList,m as validateIntent};
1
+ import{C as e,S as t,_ as n,a as r,b as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"./core-BNxznRif.js";import{t as w}from"./async-DxRWu4le.js";import{UploadProvider as T,createUploadFactory as E,useOptionalUploadStore as D,useUploadStore as O,useUploader as k,useUploaderActions as A}from"./react/index.js";import{PostStrategy as j,createStrategyRegistry as M,multipartStrategy as N}from"./strategies/index.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,j as PostStrategy,T as UploadProvider,o as computeFingerprint,g as createReducer,M as createStrategyRegistry,u as createTypedEmitter,v as createUploadClient,E as createUploadFactory,p as createUploadStore,y as createXHRTransport,r as deserializeSnapshot,s as fingerprintMatches,n as generateId,l as isRecord,N as multipartStrategy,b as resolveUploadConfig,m as serializeSnapshot,w as sleep,D as useOptionalUploadStore,O as useUploadStore,k as useUploader,A as useUploaderActions,c as validateFile,h as validateFileList,f as validateIntent};
@@ -0,0 +1,90 @@
1
+ import { f as IntentMap, l as UploadResultBase, o as CursorMap } from "../strategy.types-gg9ZStRe.js";
2
+ import { B as UploadItem, E as UploadEventMap, G as UploadCommand, T as UploadStore, V as UploadPhase } from "../index-BXg9WIce.js";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/react/upload-provider.d.ts
6
+ /**
7
+ * Provider component that makes the upload store available to child components.
8
+ *
9
+ * @template M - Intent map type
10
+ * @template C - Cursor map type
11
+ * @template P - Purpose type
12
+ *
13
+ * @param props - Provider props
14
+ * @param props.store - Upload store instance to provide
15
+ * @param props.children - Child components that can access the store
16
+ *
17
+ * @returns {JSX.Element} Context provider wrapping children
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <UploadProvider store={store}>
22
+ * <UploadDropzone purpose="avatar" />
23
+ * </UploadProvider>
24
+ * ```
25
+ */
26
+ declare function UploadProvider<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>({
27
+ store,
28
+ children
29
+ }: {
30
+ store: UploadStore<M, C, P, R>;
31
+ children: ReactNode;
32
+ }): React.JSX.Element;
33
+ /**
34
+ * Hook to access the upload store from React context when present.
35
+ *
36
+ * Some higher-level hooks support either an explicit store argument or context.
37
+ * They use this helper so the context read stays unconditional without throwing
38
+ * when a caller intentionally passes a store instance.
39
+ */
40
+ declare function useOptionalUploadStore<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>(): UploadStore<M, C, P, R> | null;
41
+ /**
42
+ * Hook to access the upload store from React context.
43
+ *
44
+ * @template M - Intent map type
45
+ * @template C - Cursor map type
46
+ * @template P - Purpose type
47
+ *
48
+ * @returns {UploadStore<M, C, P>} The upload store instance
49
+ * @throws {Error} If called outside of UploadProvider
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * const store = useUploadStore()
54
+ * const snapshot = store.getSnapshot()
55
+ * ```
56
+ */
57
+ declare function useUploadStore<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>(): UploadStore<M, C, P, R>;
58
+ //#endregion
59
+ //#region src/react/use-uploader.d.ts
60
+ declare function createUploadFactory<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>(store: UploadStore<M, C, P, R>): () => Uploader<M, C, P, R>;
61
+ type PickItemsByPhase<Phase extends UploadPhase> = Extract<UploadItem<IntentMap, CursorMap<IntentMap>, string, UploadResultBase>, {
62
+ phase: Phase;
63
+ }>;
64
+ type Uploader<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase> = {
65
+ items: UploadItem<M, C, P, R>[];
66
+ byPhase: Record<string, UploadItem<M, C, P, R>[]>;
67
+ dispatch: (cmd: UploadCommand<P>) => void;
68
+ on: <K extends keyof UploadEventMap<M, C, P, R> & string>(type: K, cb: (payload: UploadEventMap<M, C, P, R>[K]) => void) => () => void;
69
+ off: <K extends keyof UploadEventMap<M, C, P, R> & string>(type: K, cb: (payload: UploadEventMap<M, C, P, R>[K]) => void) => () => void;
70
+ uploading: UploadItem<M, C, P, R>[];
71
+ paused: UploadItem<M, C, P, R>[];
72
+ completed: UploadItem<M, C, P, R>[];
73
+ failed: UploadItem<M, C, P, R>[];
74
+ ready: UploadItem<M, C, P, R>[];
75
+ };
76
+ /**
77
+ * Stable action surface for React consumers that need imperative store access.
78
+ *
79
+ * This is declared explicitly instead of relying on an inferred object return
80
+ * type so the generated package declarations do not leak the internal generic
81
+ * names created by `store.on.bind(store)`.
82
+ */
83
+ type UploaderActions<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase> = Pick<UploadStore<M, C, P, R>, 'dispatch' | 'on'> & {
84
+ store: UploadStore<M, C, P, R>;
85
+ };
86
+ declare function useUploader<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>(): Uploader<M, C, P, R>;
87
+ declare function useUploader<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>(store: UploadStore<M, C, P, R>): Uploader<M, C, P, R>;
88
+ declare function useUploaderActions<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase = UploadResultBase>(): UploaderActions<M, C, P, R>;
89
+ //#endregion
90
+ export { PickItemsByPhase, UploadProvider, UploaderActions, createUploadFactory, useOptionalUploadStore, useUploadStore, useUploader, useUploaderActions };
@@ -0,0 +1 @@
1
+ import{h as e}from"../core-BNxznRif.js";import{jsxDEV as t}from"react/jsx-dev-runtime";import*as n from"react";import{createContext as r,useContext as i}from"react";const a=r(null);function o({store:e,children:n}){return t(a.Provider,{value:e,children:n},void 0,!1,{fileName:`/home/runner/work/duck-ui/duck-ui/packages/duck-upload/src/react/upload-provider.tsx`,lineNumber:52,columnNumber:10},this)}function s(){let e=i(a);if(e===null)return null;if(!l(e))throw Error(`UploadProvider received an invalid store value`);return e}function c(){let e=s();if(!e)throw Error(`useUploadStore must be used within UploadProvider`);return e}function l(t){return e(t)?typeof t.getSnapshot==`function`&&typeof t.subscribe==`function`&&typeof t.dispatch==`function`&&typeof t.on==`function`&&typeof t.off==`function`&&typeof t.waitFor==`function`:!1}function u(e){return()=>d(e)}function d(e){let t=s(),r=e??t;if(!r)throw Error(`useUploader must be used within UploadProvider when no store argument is provided`);let i=n.useSyncExternalStore(r.subscribe.bind(r),r.getSnapshot.bind(r),r.getSnapshot.bind(r)),a=n.useMemo(()=>Array.from(i.items.values()),[i.items]),o=n.useMemo(()=>{let e={};return a.forEach(t=>{e[t.phase]||(e[t.phase]=[]),e[t.phase].push(t)}),e},[a]);return{items:a,byPhase:o,dispatch:r.dispatch.bind(r),on:r.on.bind(r),off:r.off.bind(r),uploading:o.uploading||[],paused:o.paused||[],completed:o.completed||[],failed:o.error||[],ready:o.ready||[]}}function f(){let e=c();return{dispatch:n.useMemo(()=>e.dispatch.bind(e),[e]),on:n.useMemo(()=>e.on.bind(e),[e]),store:e}}export{o as UploadProvider,u as createUploadFactory,s as useOptionalUploadStore,c as useUploadStore,d as useUploader,f as useUploaderActions};
@@ -0,0 +1,56 @@
1
+ import { f as IntentMap, l as UploadResultBase, n as StrategyRegistry, o as CursorMap, r as UploadStrategy, t as StrategyCtx } from "../strategy.types-gg9ZStRe.js";
2
+
3
+ //#region src/strategies/multipart/index.d.ts
4
+ type MultipartIntent = {
5
+ strategy: 'multipart';
6
+ fileId: string;
7
+ uploadId: string;
8
+ partSize: number;
9
+ partCount: number;
10
+ parts?: Array<{
11
+ partNumber: number;
12
+ url: string;
13
+ headers?: Record<string, string>;
14
+ }>;
15
+ };
16
+ type MultipartCursor = {
17
+ done: Array<{
18
+ partNumber: number;
19
+ etag: string;
20
+ size: number;
21
+ }>;
22
+ /**
23
+ * Marks that the multipart session was completed on the backend (parts assembled).
24
+ * This prevents re-sending completeMultipart on resume.
25
+ */
26
+ completed?: true;
27
+ };
28
+ declare function multipartStrategy<M extends IntentMap & {
29
+ multipart: MultipartIntent;
30
+ }, C extends CursorMap<M> & {
31
+ multipart?: MultipartCursor;
32
+ }, P extends string = string, R extends UploadResultBase = UploadResultBase>(opts?: {
33
+ maxPartConcurrency?: number;
34
+ }): UploadStrategy<M, C, P, R, 'multipart'>;
35
+ //#endregion
36
+ //#region src/strategies/post/index.d.ts
37
+ type PostIntent = {
38
+ /** Strategy identifier - must be 'post' */strategy: 'post'; /** Backend file identifier */
39
+ fileId: string; /** Presigned POST URL (form action) */
40
+ url: string; /** Form fields to include before the file */
41
+ fields: Record<string, string>; /** Optional expiration timestamp for the presigned URL */
42
+ expiresAt?: string;
43
+ };
44
+ type PostCursor = {
45
+ /** Bytes uploaded (for progress display only) */uploadedBytes: number;
46
+ };
47
+ declare function PostStrategy<M extends IntentMap & {
48
+ post: PostIntent;
49
+ }, C extends CursorMap<M> & {
50
+ post?: PostCursor;
51
+ }, P extends string = string, R extends UploadResultBase = UploadResultBase>(): UploadStrategy<M, C, P, R, 'post'>;
52
+ //#endregion
53
+ //#region src/strategies/registry/index.d.ts
54
+ declare function createStrategyRegistry<M extends IntentMap, C extends CursorMap<M>, P extends string = string, R extends UploadResultBase = UploadResultBase>(): StrategyRegistry<M, C, P, R>;
55
+ //#endregion
56
+ export { MultipartCursor, MultipartIntent, PostCursor, PostIntent, PostStrategy, type StrategyCtx, type StrategyRegistry, type UploadStrategy, createStrategyRegistry, multipartStrategy };
@@ -0,0 +1 @@
1
+ import{t as e}from"../async-DxRWu4le.js";function t(e){return e instanceof Error&&(e.name===`AbortError`||/abort/i.test(e.message))}function n(n){let r=Math.max(1,n?.maxPartConcurrency??4);return{id:`multipart`,resumable:!0,async start(n){let i=n.intent,a=n.file.size,o=Math.max(1,i.partSize),s=Math.max(1,i.partCount??Math.ceil(a/o)),c=n.readCursor(),l=new Map;if(c?.done)for(let e of c.done)l.set(e.partNumber,{etag:e.etag,size:e.size});let u=c?.completed===!0,d=new Map,f=0;for(let e of l.values())f+=e.size;let p=()=>{let e=0;for(let t of d.values())e+=t;n.reportProgress({uploadedBytes:f+e,totalBytes:a})},m=[];for(let e=1;e<=s;e++){if(l.has(e))continue;let t=(e-1)*o,n=Math.min(a,t+o);m.push({partNumber:e,start:t,end:n})}if(m.length===0){u||await y(),p();return}let h=m.slice(),g=new Set,_=async e=>{let t=i.parts?.find(t=>t.partNumber===e);if(t)return t;if(!n.api.multipart?.signPart)throw Error(`multipart.signPart is missing in UploadApi. Implement it to call your backend sign-part endpoint.`);let r=await n.api.multipart.signPart({fileId:i.fileId,uploadId:i.uploadId,partNumber:e},{signal:n.signal});return{partNumber:e,url:r.url,headers:r.headers}},v=async(r,i=0)=>{try{let e=n.file.slice(r.start,r.end),t=e.size,i=await _(r.partNumber),a=await n.transport.put({url:i.url,body:e,headers:i.headers,signal:n.signal,onProgress:e=>{d.set(r.partNumber,e),p()}});d.delete(r.partNumber);let o=a.etag;if(!o)throw Error(`Missing ETag from upload part response. Ensure MinIO/S3 CORS exposes ETag (Access-Control-Expose-Headers: ETag).`);f+=t,l.set(r.partNumber,{etag:o,size:t});let s={done:Array.from(l.entries()).map(([e,t])=>({partNumber:e,etag:t.etag,size:t.size})).sort((e,t)=>e.partNumber-t.partNumber)};n.persistCursor(s),p()}catch(a){if(d.delete(r.partNumber),n.signal?.aborted||t(a))throw a;if(i<3){let t=a instanceof Error?a.message:String(a);if(/network/i.test(t)||/timeout/i.test(t)||/5\d\d/.test(t)||/ECONNRESET/i.test(t)||/EHOSTUNREACH/i.test(t))return await e(2**i*500),v(r,i+1)}throw a}};for(;h.length>0||g.size>0;){for(;h.length>0&&g.size<r;){let e=h.shift();if(!e)break;let t,n=v(e).finally(()=>{t&&g.delete(t)});t=n,g.add(n)}g.size>0&&await Promise.race(g)}await y(),p();async function y(){if(u)return;if(!n.api.multipart?.completeMultipart)throw Error(`multipart.completeMultipart is missing in UploadApi. Implement it to call your backend complete endpoint.`);let e=Array.from(l.entries()).map(([e,t])=>({partNumber:e,etag:t.etag})).sort((e,t)=>e.partNumber-t.partNumber);if(e.length!==s)throw Error(`Cannot complete multipart: expected ${s} parts, got ${e.length}`);await n.api.multipart.completeMultipart({fileId:i.fileId,uploadId:i.uploadId,parts:e},{signal:n.signal});let t={done:Array.from(l.entries()).map(([e,t])=>({partNumber:e,etag:t.etag,size:t.size})).sort((e,t)=>e.partNumber-t.partNumber),completed:!0};n.persistCursor(t)}}}}function r(){return{id:`post`,resumable:!1,async start(e){let t=e.intent;if(!t.url)throw Error(`Direct strategy: intent missing url/uploadUrl`);await e.transport.postForm({url:t.url,file:e.file,fields:t.fields,filename:e.file.name,signal:e.signal,onProgress(t,n){e.reportProgress({uploadedBytes:t,totalBytes:n})}})}}}function i(){let e={};return{get(t){return e[t]},has(t){return Object.hasOwn(e,t)},set(t){e[t.id]=t}}}export{r as PostStrategy,i as createStrategyRegistry,n as multipartStrategy};
@@ -0,0 +1,225 @@
1
+ //#region src/core/contracts/intent.types.d.ts
2
+ /**
3
+ * Base shape for all upload intents.
4
+ *
5
+ * An "intent" is data provided by your backend that tells the client how to upload a file
6
+ * (for example: a presigned POST, presigned PUT, multipart upload IDs, part URLs, headers, etc).
7
+ *
8
+ * Every intent must include:
9
+ * - `strategy`: a discriminant used to select an upload strategy implementation
10
+ * - `fileId`: a stable backend identifier for the file being uploaded
11
+ *
12
+ * @typeParam K - Strategy key / discriminant string (example: `"direct" | "multipart"`).
13
+ */
14
+ type IntentBase<K extends string = string> = {
15
+ /** Strategy name (discriminant). */strategy: K; /** Unique backend file identifier. */
16
+ fileId: string;
17
+ };
18
+ /**
19
+ * Registry of intent types mapped by strategy name.
20
+ *
21
+ * Example:
22
+ * ```ts
23
+ * type MyIntents = {
24
+ * direct: DirectIntent
25
+ * multipart: MultipartIntent
26
+ * }
27
+ * ```
28
+ *
29
+ * `M[keyof M]` is used throughout the engine as the "any intent" union.
30
+ */
31
+ type IntentMap = Record<string, IntentBase<string>>;
32
+ /** Union of strategy keys for an intent map. */
33
+ type StrategyKey<M extends IntentMap> = keyof M & string;
34
+ /** Union of all intent variants for an intent map. */
35
+ type AnyIntent<M extends IntentMap> = M[StrategyKey<M>];
36
+ //#endregion
37
+ //#region src/core/contracts/result.types.d.ts
38
+ /**
39
+ * Base shape for upload completion results returned by your backend.
40
+ *
41
+ * Extend this with any additional metadata you return (e.g. width/height, variants).
42
+ */
43
+ type UploadResultBase = {
44
+ /** Backend file identifier. */fileId: string; /** Storage key or path used for signed URLs. */
45
+ key: string;
46
+ };
47
+ //#endregion
48
+ //#region src/core/contracts/backendApi.d.ts
49
+ type WithSignal = {
50
+ signal?: AbortSignal;
51
+ };
52
+ interface UploadApi<M extends IntentMap, P extends string, R extends UploadResultBase = UploadResultBase> {
53
+ createIntent(args: {
54
+ purpose: P;
55
+ contentType: string;
56
+ size: number;
57
+ filename: string;
58
+ checksum?: string;
59
+ }, opts?: WithSignal): Promise<M[keyof M]>;
60
+ complete(args: {
61
+ fileId: string;
62
+ }, opts?: WithSignal): Promise<R>;
63
+ getSignedPreviewUrl?(args: {
64
+ fileId: string;
65
+ key: string;
66
+ purpose: P;
67
+ }, opts?: WithSignal): Promise<string>;
68
+ /**
69
+ * Optional support for finding existing files by checksum for deduplication.
70
+ * If implemented, the upload engine will check for existing files before uploading.
71
+ * If a file with the same checksum exists, the upload will be skipped and the existing file will be linked.
72
+ */
73
+ findByChecksum?(args: {
74
+ checksum: string;
75
+ purpose: P;
76
+ }, opts?: WithSignal): Promise<R | null>;
77
+ multipart?: {
78
+ signPart(args: {
79
+ fileId: string;
80
+ uploadId: string;
81
+ partNumber: number;
82
+ checksum?: string;
83
+ }, opts?: WithSignal): Promise<{
84
+ url: string;
85
+ headers?: Record<string, string>;
86
+ }>;
87
+ completeMultipart(args: {
88
+ fileId: string;
89
+ uploadId: string;
90
+ parts: Array<{
91
+ partNumber: number;
92
+ etag: string;
93
+ }>;
94
+ }, opts?: WithSignal): Promise<unknown>;
95
+ listParts?(args: {
96
+ fileId: string;
97
+ uploadId: string;
98
+ }, opts?: WithSignal): Promise<Array<{
99
+ partNumber: number;
100
+ etag?: string;
101
+ size?: number;
102
+ }>>;
103
+ abort?(args: {
104
+ fileId: string;
105
+ uploadId: string;
106
+ }, opts?: WithSignal): Promise<void>;
107
+ };
108
+ tus?: {
109
+ create(args: {
110
+ fileId: string;
111
+ size: number;
112
+ filename: string;
113
+ contentType: string;
114
+ }, opts?: WithSignal): Promise<{
115
+ uploadUrl: string;
116
+ }>;
117
+ getOffset(args: {
118
+ uploadUrl: string;
119
+ }, opts?: WithSignal): Promise<{
120
+ offset: number;
121
+ }>;
122
+ };
123
+ }
124
+ //#endregion
125
+ //#region src/core/contracts/cursor.types.d.ts
126
+ /**
127
+ * Registry of cursor payload types mapped by strategy name.
128
+ *
129
+ * A "cursor" is strategy-defined resume state (ex: multipart uploaded parts, next byte offset).
130
+ * Use `unknown` here and define concrete cursor payload types in your app.
131
+ *
132
+ * @typeParam M - The intent map, used to constrain strategy keys.
133
+ */
134
+ type CursorMap<M extends IntentMap> = Partial<Record<StrategyKey<M>, unknown>>;
135
+ /**
136
+ * Discriminated union of all cursor variants.
137
+ *
138
+ * The cursor is always tagged with a `strategy` so it can be routed back to the correct strategy
139
+ * on resume/rebind.
140
+ *
141
+ * @typeParam C - Cursor registry (usually {@link CursorMap}).
142
+ */
143
+ type AnyCursor<C extends Record<string, unknown>> = { [K in keyof C & string]: {
144
+ strategy: K;
145
+ value?: C[K];
146
+ } }[keyof C & string];
147
+ //#endregion
148
+ //#region src/core/contracts/transport/transport.types.d.ts
149
+ /**
150
+ * Abstraction layer for network operations.
151
+ * Allows switching between XHR (browser) or other implementations (testing/Node).
152
+ *
153
+ * Designed to support:
154
+ * - Deterministic progress tracking (requires XHR in browsers)
155
+ * - Cancellation via AbortSignal
156
+ * - Header parsing (ETag extraction)
157
+ */
158
+ interface UploadTransport {
159
+ /**
160
+ * Performs an HTTP PUT request (typically for S3 signed URLs).
161
+ */
162
+ put(args: {
163
+ url: string;
164
+ body: Blob;
165
+ headers?: Record<string, string>;
166
+ signal: AbortSignal;
167
+ onProgress?: (u: number, t: number) => void;
168
+ }): Promise<{
169
+ etag?: string;
170
+ headers?: Record<string, string>;
171
+ }>;
172
+ /**
173
+ * Performs an HTTP POST multipart request.
174
+ */
175
+ postForm(args: {
176
+ url: string;
177
+ fields: Record<string, string>;
178
+ file: File | Blob;
179
+ filename?: string;
180
+ signal: AbortSignal;
181
+ onProgress?: (uploadedBytes: number, totalBytes: number) => void;
182
+ }): Promise<{
183
+ etag?: string;
184
+ headers?: Record<string, string>;
185
+ }>;
186
+ /**
187
+ * Performs an HTTP request.
188
+ */
189
+ patch(args: {
190
+ url: string;
191
+ body: Blob | ArrayBuffer;
192
+ headers?: Record<string, string>;
193
+ signal: AbortSignal;
194
+ onProgress?: (u: number, t: number) => void;
195
+ }): Promise<{
196
+ headers?: Record<string, string>;
197
+ }>;
198
+ }
199
+ //#endregion
200
+ //#region src/core/contracts/strategy.types.d.ts
201
+ type StrategyCtx<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase, K extends keyof M & string> = {
202
+ file: File;
203
+ intent: M[K];
204
+ signal: AbortSignal;
205
+ transport: UploadTransport;
206
+ api: UploadApi<M, P, R>;
207
+ reportProgress: (p: {
208
+ uploadedBytes: number;
209
+ totalBytes: number;
210
+ }) => void;
211
+ readCursor: () => C[K] | undefined;
212
+ persistCursor: (cursor: C[K] | undefined) => void;
213
+ };
214
+ type UploadStrategy<M extends IntentMap, C extends CursorMap<M>, P extends string, R extends UploadResultBase, K extends keyof M & string> = {
215
+ id: K;
216
+ resumable: boolean;
217
+ start(ctx: StrategyCtx<M, C, P, R, K>): Promise<void>;
218
+ };
219
+ interface StrategyRegistry<M extends IntentMap, C extends CursorMap<M>, P extends string = string, R extends UploadResultBase = UploadResultBase> {
220
+ get<K extends keyof M & string>(id: K): UploadStrategy<M, C, P, R, K> | undefined;
221
+ has(id: string): id is keyof M & string;
222
+ set<K extends keyof M & string>(strategy: UploadStrategy<M, C, P, R, K>): void;
223
+ }
224
+ //#endregion
225
+ export { AnyCursor as a, WithSignal as c, IntentBase as d, IntentMap as f, UploadTransport as i, UploadResultBase as l, StrategyRegistry as n, CursorMap as o, StrategyKey as p, UploadStrategy as r, UploadApi as s, StrategyCtx as t, AnyIntent as u };
package/package.json CHANGED
@@ -1,28 +1,41 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@gentleduck/upload",
4
- "version": "0.2.2",
4
+ "version": "0.2.4",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "type": "module",
9
9
  "exports": {
10
- ".": "./src/index.ts",
11
- "./core": "./src/core/index.ts",
12
- "./react": "./src/react/index.ts",
13
- "./strategies": "./src/strategies/index.ts"
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./core": {
15
+ "types": "./dist/core/index.d.ts",
16
+ "default": "./dist/core/index.js"
17
+ },
18
+ "./react": {
19
+ "types": "./dist/react/index.d.ts",
20
+ "default": "./dist/react/index.js"
21
+ },
22
+ "./strategies": {
23
+ "types": "./dist/strategies/index.d.ts",
24
+ "default": "./dist/strategies/index.js"
25
+ }
14
26
  },
15
27
  "files": [
16
28
  "dist",
17
29
  "src"
18
30
  ],
19
- "types": "./dist/index.d.ts",
31
+ "main": "./dist/index.js",
20
32
  "module": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
21
34
  "scripts": {
22
35
  "build": "tsdown",
23
36
  "dev": "tsdown --watch",
24
37
  "check-types": "tsc --noEmit",
25
- "test": "vitest --passWithNoTests",
38
+ "test": "vitest run --passWithNoTests",
26
39
  "lint": "biome lint .",
27
40
  "format": "biome format --write ."
28
41
  },
@@ -33,12 +46,12 @@
33
46
  "devDependencies": {
34
47
  "@gentleduck/tsdown-config": "workspace:*",
35
48
  "@gentleduck/typescript-config": "workspace:*",
36
- "@types/node": "^25.4.0",
37
- "@types/react": "^19.2.14",
38
- "@types/react-dom": "^19.2.3",
39
- "tsdown": "0.18.3",
40
- "typescript": "5.9.3",
41
- "vite": "^7.3.1",
49
+ "@types/node": "catalog:",
50
+ "@types/react": "catalog:react19",
51
+ "@types/react-dom": "catalog:react19",
52
+ "tsdown": "catalog:",
53
+ "typescript": "catalog:",
54
+ "vite": "catalog:",
42
55
  "vitest": "^4.0.18"
43
56
  },
44
57
  "engines": {
@@ -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
  }
@@ -25,7 +25,9 @@ export function dispatch<M extends IntentMap, C extends CursorMap<M>, P extends
25
25
  const purpose = cmd.purpose
26
26
  Array.from(rt.state.items.values())
27
27
  .filter((item) => item.phase === 'ready' && (!purpose || item.purpose === purpose))
28
- .forEach((item) => rt.dispatch({ type: 'start', localId: item.localId }))
28
+ .forEach((item) => {
29
+ rt.dispatch({ type: 'start', localId: item.localId })
30
+ })
29
31
  return
30
32
  }
31
33
 
@@ -33,7 +35,9 @@ export function dispatch<M extends IntentMap, C extends CursorMap<M>, P extends
33
35
  const purpose = cmd.purpose
34
36
  Array.from(rt.state.items.values())
35
37
  .filter((item) => item.phase === 'uploading' && (!purpose || item.purpose === purpose))
36
- .forEach((item) => rt.dispatch({ type: 'pause', localId: item.localId }))
38
+ .forEach((item) => {
39
+ rt.dispatch({ type: 'pause', localId: item.localId })
40
+ })
37
41
  return
38
42
  }
39
43
 
@@ -43,7 +47,9 @@ export function dispatch<M extends IntentMap, C extends CursorMap<M>, P extends
43
47
  .filter(
44
48
  (item) => item.phase !== 'completed' && item.phase !== 'canceled' && (!purpose || item.purpose === purpose),
45
49
  )
46
- .forEach((item) => rt.dispatch({ type: 'cancel', localId: item.localId }))
50
+ .forEach((item) => {
51
+ rt.dispatch({ type: 'cancel', localId: item.localId })
52
+ })
47
53
  return
48
54
  }
49
55
 
@@ -91,7 +91,9 @@ export function createStoreRuntime<
91
91
  scheduling: false,
92
92
 
93
93
  notify() {
94
- rt.listeners.forEach((l) => l())
94
+ rt.listeners.forEach((listener) => {
95
+ listener()
96
+ })
95
97
  },
96
98
 
97
99
  scheduleWork() {
@@ -138,7 +138,7 @@ export interface StoreOptions<
138
138
  *
139
139
  * Used to configure the upload store persistence layer.
140
140
  */
141
- export type DeserializeContext<M extends IntentMap, C extends CursorMap<M>, P extends string> = {
141
+ export type DeserializeContext<M extends IntentMap, _C extends CursorMap<M>, P extends string> = {
142
142
  isPurpose?: (value: string) => value is P
143
143
  isIntent?: (value: unknown) => value is M[keyof M]
144
144
  hasStrategy: (value: string) => boolean
@@ -36,7 +36,7 @@ export function validateFile<P extends string>(file: File, purpose: P, config: U
36
36
  if (type.endsWith('/*')) {
37
37
  // Wildcard match: 'image/*' matches 'image/jpeg', 'image/png', etc.
38
38
  const prefix = type.slice(0, -2).toLowerCase()
39
- return mimeType.startsWith(prefix + '/')
39
+ return mimeType.startsWith(`${prefix}/`)
40
40
  }
41
41
  // Exact match
42
42
  return mimeType === type.toLowerCase()