@gentleduck/upload 0.2.2 → 0.2.5
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/async-DxRWu4le.js +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +1 -0
- package/dist/core-BNxznRif.js +2 -0
- package/dist/index-BXg9WIce.d.ts +987 -0
- package/dist/index.d.ts +5 -1417
- package/dist/index.js +1 -6
- package/dist/react/index.d.ts +90 -0
- package/dist/react/index.js +1 -0
- package/dist/strategies/index.d.ts +56 -0
- package/dist/strategies/index.js +1 -0
- package/dist/strategy.types-gg9ZStRe.d.ts +225 -0
- package/package.json +26 -13
- package/src/core/engine/store/handlers/clean-up.ts +1 -1
- package/src/core/engine/store/store.dispatch.ts +9 -3
- package/src/core/engine/store/store.runtime.ts +3 -1
- package/src/core/engine/store/store.types.ts +1 -1
- package/src/core/engine/validation/file.ts +1 -1
- package/src/core/persistence/persistence.ts +6 -1
- package/src/core/utils/emitter/index.ts +6 -1
- package/src/react/upload-provider.tsx +27 -2
- package/src/react/use-uploader.tsx +25 -4
- package/src/strategies/multipart/index.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
import{
|
|
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.
|
|
4
|
+
"version": "0.2.5",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
10
|
-
".":
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
"
|
|
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": "
|
|
37
|
-
"@types/react": "
|
|
38
|
-
"@types/react-dom": "
|
|
39
|
-
"tsdown": "
|
|
40
|
-
"typescript": "
|
|
41
|
-
"vite": "
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
50
|
+
.forEach((item) => {
|
|
51
|
+
rt.dispatch({ type: 'cancel', localId: item.localId })
|
|
52
|
+
})
|
|
47
53
|
return
|
|
48
54
|
}
|
|
49
55
|
|
|
@@ -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,
|
|
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()
|