@lunora/storage 1.0.0-alpha.159 → 1.0.0-alpha.160

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.mjs CHANGED
@@ -1 +1 @@
1
- import{createBucketStorage as o}from"./packem_shared/createBucketStorage-AqIMq050.mjs";import{createStorage as i,scopeKey as d}from"./packem_shared/createStorage-YPzdUgGl.mjs";import{buildPresignedUrl as g}from"./packem_shared/buildPresignedUrl-U55ktL8x.mjs";import{buildSignedUrl as p,verifySignedUrl as a}from"./packem_shared/buildSignedUrl-czz1xjLU.mjs";export{g as buildPresignedUrl,p as buildSignedUrl,o as createBucketStorage,i as createStorage,d as scopeKey,a as verifySignedUrl};
1
+ import{createBucketStorage as o}from"./packem_shared/createBucketStorage-AqIMq050.mjs";import{createStorage as i,scopeKey as d}from"./packem_shared/createStorage-DzyBEGJr.mjs";import{buildPresignedUrl as g}from"./packem_shared/buildPresignedUrl-GEo1eFpq.mjs";import{buildSignedUrl as p,verifySignedUrl as a}from"./packem_shared/buildSignedUrl-Bg2beHZA.mjs";export{g as buildPresignedUrl,p as buildSignedUrl,o as createBucketStorage,i as createStorage,d as scopeKey,a as verifySignedUrl};
@@ -1 +1 @@
1
- import{N as t,a as o,R as r,o as e,p}from"./r2-upload-part-writer-SdQlWaAu.mjs";export{t as NAME_TAKEN,o as R2UploadPartWriter,r as R2_PART_SIZE,e as objectExists,p as putObject};
1
+ import{N as t,a as o,R as r,o as e,p}from"./r2-upload-part-writer-M7dEI00-.mjs";export{t as NAME_TAKEN,o as R2UploadPartWriter,r as R2_PART_SIZE,e as objectExists,p as putObject};
@@ -0,0 +1,5 @@
1
+ import{LunoraError as f}from"@lunora/errors";import{b as g,v as D,m as K}from"./internal-B047mSU9.mjs";const $="auto",w="s3",p="AWS4-HMAC-SHA256",R=10080*60,x=900,r=new TextEncoder,z=(t,o)=>t[0]<o[0]?-1:t[0]>o[0]?1:0,i=t=>encodeURIComponent(t).replaceAll(/[!'()*]/gu,o=>`%${o.codePointAt(0)?.toString(16).toUpperCase()??""}`),M=t=>t.split("/").map(o=>i(o)).join("/"),X=async t=>g(await crypto.subtle.digest("SHA-256",r.encode(t))),a=async(t,o)=>{const n=await crypto.subtle.importKey("raw",t,{hash:"SHA-256",name:"HMAC"},!1,["sign"]);return crypto.subtle.sign("HMAC",n,r.encode(o))},_=64,b=new Map,N=async(t,o)=>K(b,`${o}
2
+ ${t}`,async()=>{const n=await a(r.encode(`AWS4${t}`),o),d=await a(n,$),s=await a(d,w),c=await a(s,"aws4_request");return crypto.subtle.importKey("raw",c,{hash:"SHA-256",name:"HMAC"},!1,["sign"])},_),O=t=>{const o=t.jurisdiction===void 0?"":`${t.jurisdiction}.`;return`${t.accountId}.${o}r2.cloudflarestorage.com`},P=t=>{const o=`${t.toISOString().replaceAll(/[:-]/gu,"").slice(0,15)}Z`;return{amzDate:o,dateStamp:o.slice(0,8)}},G=async t=>{const{credentials:o,key:n}=t,d=t.method??"GET";let s=x;if(t.expiresInSeconds!==void 0){s=Math.floor(t.expiresInSeconds);const e=D(s,R);if(e!==void 0)throw new f("VALIDATION_ERROR",`@lunora/storage: ${e}`)}const c=O(o),E=new Date(t.now?.()??Date.now()),{amzDate:u,dateStamp:A}=P(E),m=`${A}/${$}/${w}/aws4_request`,y=`/${i(o.bucket)}/${M(n)}`,S=[["X-Amz-Algorithm",p],["X-Amz-Credential",`${o.accessKeyId}/${m}`],["X-Amz-Date",u],["X-Amz-Expires",s.toString()],["X-Amz-SignedHeaders","host"]].map(([e,l])=>[i(e),i(l)]).toSorted(z).map(([e,l])=>`${e}=${l}`).join("&"),h=[d,y,S,`host:${c}
3
+ `,"host","UNSIGNED-PAYLOAD"].join(`
4
+ `),I=[p,u,m,await X(h)].join(`
5
+ `),H=await N(o.secretAccessKey,A),C=g(await crypto.subtle.sign("HMAC",H,r.encode(I)));return`https://${c}${y}?${S}&X-Amz-Signature=${C}`};export{G as buildPresignedUrl};
@@ -1,4 +1,4 @@
1
- import{LunoraError as f}from"@lunora/errors";import{i as y,c as u,e as p,s as v,t as U,f as N,d as w,v as I,M as $}from"./internal-B5yyxF10.mjs";const k=/^\//,b=(e,s,r,t,o,a)=>{const n=`${e}
1
+ import{LunoraError as f}from"@lunora/errors";import{i as y,c as u,e as p,s as v,t as U,f as N,d as w,v as I,M as $}from"./internal-B047mSU9.mjs";const k=/^\//,b=(e,s,r,t,o,a)=>{const n=`${e}
2
2
  ${s.toLowerCase()}
3
3
  ${r}
4
4
  ${t}
@@ -1 +1 @@
1
- import{Readable as L}from"node:stream";import{MetaStorage as N,throwErrorCode as c,ERRORS as n,AbstractBaseStorage as g,File as y,UploadError as _}from"@visulima/storage";import{o as A,p as x,r as E,a as I,N as T}from"./r2-upload-part-writer-SdQlWaAu.mjs";import{R as G}from"./r2-upload-part-writer-SdQlWaAu.mjs";const k=5,S=["bytesWritten","ETag","id","name","size","status"],D=(d,t)=>JSON.stringify({...d,modifiedAt:void 0})===JSON.stringify({...t,modifiedAt:void 0});class U extends N{bucket;scope;constructor(t,e,a=()=>""){super({prefix:e,suffix:".json"}),this.bucket=t,this.scope=a}currentPrefix(){const t=this.scope();if(t!==""&&!t.endsWith("/"))throw new TypeError(`@lunora/storage: stateScope must end in "/" (got "${t}")`);return`${this.prefix}${t}`}getMetaName(t){return`${this.currentPrefix()}${t}${this.suffix}`}segmentPrefix(t){return`${this.currentPrefix()}${t}/`}async read(t){const e=await this.bucket.get(this.getMetaName(t));if(e===null)return;const a=await e.text();return{etag:e.etag,state:JSON.parse(a)}}async create(t,e){return await this.bucket.put(this.getMetaName(t),JSON.stringify(e),{onlyIf:{etagDoesNotMatch:"*"}})!==null}async write(t,e,a){return(await this.bucket.put(this.getMetaName(t),JSON.stringify(e),{onlyIf:{etagMatches:a}}))?.etag}async swap(t,e){for(let a=0;a<k;a+=1){const r=await this.read(t);if(r===void 0)return c(n.FILE_NOT_FOUND);const s=e(r.state);if(s===r.state||await this.write(t,s,r.etag)!==void 0)return s}return c(n.FILE_CONFLICT,"The upload kept changing while it was being written; resume from the current offset")}async get(t){const e=await this.read(t);return e===void 0?c(n.FILE_NOT_FOUND):e.state.file}async save(t,e){return(await this.swap(t,r=>{const i={...e};for(const o of S)i[o]=r.file[o];return D(i,r.file)?r:{...r,file:i}})).file}async delete(t){await this.bucket.delete(this.getMetaName(t))}}const F="_lunora/uploads/",m=6e4,R=new Set(["checksum","concatenation","creation-defer-length"]),M=new Set([...S,"expiredAt"]),p=d=>c(n.FILE_CONFLICT,d),P=d=>d!==void 0&&d.expiresAt>Date.now(),b=32*1024*1024;class j extends g{sequentialWrites=!0;meta;bucket;constructor(t,e={}){super(e),this.bucket=t,this.meta=new U(t,e.statePrefix??F,e.stateScope)}get tusExtension(){return super.tusExtension.filter(t=>!R.has(t))}async create(t){return this.instrumentOperation("create",async()=>{const e=t.size===void 0||t.size===""?Number.NaN:Number(t.size);if(!Number.isSafeInteger(e)||e<0)return c(n.INVALID_FILE_SIZE,"An upload must declare its size as a non-negative integer");const a=new y(t);if(a.size=e,a.name=this.namingFunction(a),g.assertSafeId(a.id),a.name.includes(this.meta.prefix))return c(n.INVALID_FILE_NAME,"An upload cannot be stored under the upload state prefix");await this.validate(a);const r=t.id!==void 0;if(r&&(await this.meta.read(a.id)!==void 0||await A(this.bucket,a.name)))return p(T);if(a.bytesWritten=0,a.status=e===0?"completed":"created",this.updateTimestamps(a),!await this.meta.create(a.id,{file:a,upload:{parts:[],segments:[],...r?{createOnly:r}:{}}}))return p(T);if(e===0)try{await x(this.bucket,a.name,new Uint8Array(0),a.contentType,r)}catch(s){throw await this.deleteMeta(a.id).catch(()=>{}),s}return await this.onCreate(a),a})}async write(t){return this.instrumentOperation("write",async()=>{const e=await this.meta.read(t.id);if(e===void 0)return c(n.FILE_NOT_FOUND);const a=await this.checkIfExpired(e.state.file);if(a.status==="completed"||!("body"in t)||!("start"in t))return a;this.checkPart(a,t);const r=await this.acquireLease(a.id,e.state,e.etag);try{return await this.writeLeased(a,e.state.upload,t.body,r)}catch(s){throw await this.releaseLease(a.id,r).catch(()=>{}),s}})}async get({id:t}){return this.instrumentOperation("get",async()=>{const{file:e,object:a}=await this.completedObject(t);if(a.size>b)return await a.body?.cancel(),c(n.REQUEST_ENTITY_TOO_LARGE,`get() buffers the whole file and refuses one over ${String(b)} bytes; use getStream(), ctx.storage.download() or a signed URL`);const r=Buffer.from(await a.arrayBuffer());return{content:r,contentType:e.contentType,ETag:a.etag,expiredAt:e.expiredAt,id:t,metadata:e.metadata,modifiedAt:e.modifiedAt,name:e.name,originalName:e.originalName,size:r.byteLength}})}async getStream({id:t}){return this.instrumentOperation("getStream",async()=>{const{file:e,object:a}=await this.completedObject(t),r=L.from(E(a.body));return{headers:{"Content-Length":String(a.size),"Content-Type":e.contentType,ETag:a.etag},size:a.size,stream:r}})}async delete({id:t}){return this.instrumentOperation("delete",async()=>{const e=await this.meta.read(t);if(e===void 0)return c(n.FILE_NOT_FOUND);const{file:a,upload:r}=e.state;a.status!=="completed"&&(await this.acquireLease(t,e.state,e.etag),r.uploadId!==void 0&&await this.bucket.resumeMultipartUpload(a.name,r.uploadId).abort().catch(()=>{}),await this.deleteSegments(t)),await this.deleteMeta(t);const s={...a,status:"deleted"};return await this.onDelete(s),s})}async update(t,e){const a=Object.fromEntries(Object.entries(e).filter(([r])=>!M.has(r)));return super.update(t,a)}async list(){return this.instrumentOperation("list",async()=>{const{prefix:t,suffix:e}=this.meta,a=[];let r;do{const s=await this.bucket.list({delimiter:"/",prefix:t,...r===void 0?{}:{cursor:r}});for(const i of s.objects)if(i.key.endsWith(e)){const o=new y({id:this.meta.getIdFromMetaName(i.key),metadata:{}});o.createdAt=(i.uploaded??new Date).toISOString(),o.modifiedAt=o.createdAt,a.push(o)}r=s.truncated===!0?s.cursor:void 0}while(r!==void 0);return a})}copy(){return this.unsupported("copy")}move(){return this.unsupported("move")}async finish(t,e,a){try{return await t.finish()}catch(r){throw a&&r.UploadErrorCode===n.FILE_CONFLICT&&(await this.deleteSegments(e).catch(()=>{}),await this.deleteMeta(e).catch(()=>{})),r}}async completedObject(t){const e=await this.checkIfExpired(await this.getMeta(t)),a=e.status==="completed"?await this.bucket.get(e.name):void 0;return a==null?c(n.FILE_NOT_FOUND):{file:e,object:a}}unsupported(t){return Promise.reject(new _(n.METHOD_NOT_ALLOWED,`${this.constructor.name} does not implement ${t}()`))}checkPart(t,e){e.start!==t.bytesWritten&&p(`Upload-Offset ${String(e.start)} does not match the upload's offset ${String(t.bytesWritten)}`),e.checksumAlgorithm!==void 0&&e.checksumAlgorithm!==""&&c(n.UNSUPPORTED_CHECKSUM_ALGORITHM),e.contentLength!==void 0&&e.start+e.contentLength>(t.size??0)&&c(n.REQUEST_ENTITY_TOO_LARGE,"The chunk runs past the declared upload length")}async acquireLease(t,e,a){if(P(e.upload.lock))return p("Another request is writing to this upload");const r=crypto.randomUUID(),s={...e.upload,lock:{expiresAt:Date.now()+m,token:r}};return await this.meta.write(t,{...e,upload:s},a)===void 0?p("Another request is writing to this upload"):r}async changeLeased(t,e,a){return this.meta.swap(t,r=>r.upload.lock?.token!==e?p("This request's lease on the upload expired; resume from the current offset"):a(r))}async releaseLease(t,e){await this.changeLeased(t,e,a=>{const r={...a.upload};return delete r.lock,{...a,upload:r}})}async renewLease(t,e){await this.changeLeased(t,e,a=>({...a,upload:{...a.upload,lock:{expiresAt:Date.now()+m,token:e}}}))}async deleteSegments(t){const e=[];let a;do{const r=await this.bucket.list({prefix:this.meta.segmentPrefix(t),...a===void 0?{}:{cursor:a}});e.push(...r.objects.map(s=>s.key)),a=r.truncated===!0?r.cursor:void 0}while(a!==void 0);for(let r=0;r<e.length;r+=1e3)await this.bucket.delete(e.slice(r,r+1e3))}async pump(t,e,a,r,s){const i=E(e);let o=0,u=Date.now();try{for(;;){let h;try{h=await i.next()}catch(l){return l instanceof Error?l:new Error(String(l))}if(h.done===!0)return;if(o+=h.value.byteLength,o>a)return c(n.REQUEST_ENTITY_TOO_LARGE,"The chunk runs past the declared upload length");Date.now()-u>=m/3&&(u=Date.now(),await this.renewLease(r,s)),await t.push(h.value)}}finally{await i.return(void 0).catch(()=>{})}}async writeLeased(t,e,a,r){const s=t.size??0,i=new I(this.bucket,t,e,this.meta.segmentPrefix(t.id),{confirm:async()=>this.renewLease(t.id,r),save:async(f,v)=>{await this.changeLeased(t.id,r,w=>({file:{...w.file,bytesWritten:v,status:"part"},upload:{...f,lock:w.upload.lock}}))},token:r}),o=await this.pump(i,a,s-t.bytesWritten,t.id,r),u=i.offset===s,h=u?await this.finish(i,t.id,e.createOnly===!0):void 0;if(!u)try{await i.keepTail()}catch(f){if(o===void 0)throw f}const l={bytesWritten:u?s:i.stored,modifiedAt:new Date().toISOString(),status:u?"completed":"part",...h===void 0?{}:{ETag:h}},O=u?{parts:[],segments:[]}:i.progress();if(await this.changeLeased(t.id,r,f=>({file:{...f.file,...l},upload:O})),await(u?this.deleteSegments(t.id).catch(()=>{}):i.dropConsumed()),o!==void 0)throw o;return{...t,...l}}}const $=(d,t={})=>new j(d,t);export{G as R2_PART_SIZE,$ as createR2BindingUploadStorage};
1
+ import{Readable as L}from"node:stream";import{MetaStorage as N,throwErrorCode as c,ERRORS as n,AbstractBaseStorage as g,File as y,UploadError as _}from"@visulima/storage";import{o as A,p as x,r as E,a as I,N as T}from"./r2-upload-part-writer-M7dEI00-.mjs";import{R as G}from"./r2-upload-part-writer-M7dEI00-.mjs";const k=5,S=["bytesWritten","ETag","id","name","size","status"],D=(d,t)=>JSON.stringify({...d,modifiedAt:void 0})===JSON.stringify({...t,modifiedAt:void 0});class U extends N{bucket;scope;constructor(t,e,a=()=>""){super({prefix:e,suffix:".json"}),this.bucket=t,this.scope=a}currentPrefix(){const t=this.scope();if(t!==""&&!t.endsWith("/"))throw new TypeError(`@lunora/storage: stateScope must end in "/" (got "${t}")`);return`${this.prefix}${t}`}getMetaName(t){return`${this.currentPrefix()}${t}${this.suffix}`}segmentPrefix(t){return`${this.currentPrefix()}${t}/`}async read(t){const e=await this.bucket.get(this.getMetaName(t));if(e===null)return;const a=await e.text();return{etag:e.etag,state:JSON.parse(a)}}async create(t,e){return await this.bucket.put(this.getMetaName(t),JSON.stringify(e),{onlyIf:{etagDoesNotMatch:"*"}})!==null}async write(t,e,a){return(await this.bucket.put(this.getMetaName(t),JSON.stringify(e),{onlyIf:{etagMatches:a}}))?.etag}async swap(t,e){for(let a=0;a<k;a+=1){const r=await this.read(t);if(r===void 0)return c(n.FILE_NOT_FOUND);const s=e(r.state);if(s===r.state||await this.write(t,s,r.etag)!==void 0)return s}return c(n.FILE_CONFLICT,"The upload kept changing while it was being written; resume from the current offset")}async get(t){const e=await this.read(t);return e===void 0?c(n.FILE_NOT_FOUND):e.state.file}async save(t,e){return(await this.swap(t,r=>{const i={...e};for(const o of S)i[o]=r.file[o];return D(i,r.file)?r:{...r,file:i}})).file}async delete(t){await this.bucket.delete(this.getMetaName(t))}}const F="_lunora/uploads/",m=6e4,R=new Set(["checksum","concatenation","creation-defer-length"]),M=new Set([...S,"expiredAt"]),p=d=>c(n.FILE_CONFLICT,d),P=d=>d!==void 0&&d.expiresAt>Date.now(),b=32*1024*1024;class j extends g{sequentialWrites=!0;meta;bucket;constructor(t,e={}){super(e),this.bucket=t,this.meta=new U(t,e.statePrefix??F,e.stateScope)}get tusExtension(){return super.tusExtension.filter(t=>!R.has(t))}async create(t){return this.instrumentOperation("create",async()=>{const e=t.size===void 0||t.size===""?Number.NaN:Number(t.size);if(!Number.isSafeInteger(e)||e<0)return c(n.INVALID_FILE_SIZE,"An upload must declare its size as a non-negative integer");const a=new y(t);if(a.size=e,a.name=this.namingFunction(a),g.assertSafeId(a.id),a.name.includes(this.meta.prefix))return c(n.INVALID_FILE_NAME,"An upload cannot be stored under the upload state prefix");await this.validate(a);const r=t.id!==void 0;if(r&&(await this.meta.read(a.id)!==void 0||await A(this.bucket,a.name)))return p(T);if(a.bytesWritten=0,a.status=e===0?"completed":"created",this.updateTimestamps(a),!await this.meta.create(a.id,{file:a,upload:{parts:[],segments:[],...r?{createOnly:r}:{}}}))return p(T);if(e===0)try{await x(this.bucket,a.name,new Uint8Array(0),a.contentType,r)}catch(s){throw await this.deleteMeta(a.id).catch(()=>{}),s}return await this.onCreate(a),a})}async write(t){return this.instrumentOperation("write",async()=>{const e=await this.meta.read(t.id);if(e===void 0)return c(n.FILE_NOT_FOUND);const a=await this.checkIfExpired(e.state.file);if(a.status==="completed"||!("body"in t)||!("start"in t))return a;this.checkPart(a,t);const r=await this.acquireLease(a.id,e.state,e.etag);try{return await this.writeLeased(a,e.state.upload,t.body,r)}catch(s){throw await this.releaseLease(a.id,r).catch(()=>{}),s}})}async get({id:t}){return this.instrumentOperation("get",async()=>{const{file:e,object:a}=await this.completedObject(t);if(a.size>b)return await a.body?.cancel(),c(n.REQUEST_ENTITY_TOO_LARGE,`get() buffers the whole file and refuses one over ${String(b)} bytes; use getStream(), ctx.storage.download() or a signed URL`);const r=Buffer.from(await a.arrayBuffer());return{content:r,contentType:e.contentType,ETag:a.etag,expiredAt:e.expiredAt,id:t,metadata:e.metadata,modifiedAt:e.modifiedAt,name:e.name,originalName:e.originalName,size:r.byteLength}})}async getStream({id:t}){return this.instrumentOperation("getStream",async()=>{const{file:e,object:a}=await this.completedObject(t),r=L.from(E(a.body));return{headers:{"Content-Length":String(a.size),"Content-Type":e.contentType,ETag:a.etag},size:a.size,stream:r}})}async delete({id:t}){return this.instrumentOperation("delete",async()=>{const e=await this.meta.read(t);if(e===void 0)return c(n.FILE_NOT_FOUND);const{file:a,upload:r}=e.state;a.status!=="completed"&&(await this.acquireLease(t,e.state,e.etag),r.uploadId!==void 0&&await this.bucket.resumeMultipartUpload(a.name,r.uploadId).abort().catch(()=>{}),await this.deleteSegments(t)),await this.deleteMeta(t);const s={...a,status:"deleted"};return await this.onDelete(s),s})}async update(t,e){const a=Object.fromEntries(Object.entries(e).filter(([r])=>!M.has(r)));return super.update(t,a)}async list(){return this.instrumentOperation("list",async()=>{const{prefix:t,suffix:e}=this.meta,a=[];let r;do{const s=await this.bucket.list({delimiter:"/",prefix:t,...r===void 0?{}:{cursor:r}});for(const i of s.objects)if(i.key.endsWith(e)){const o=new y({id:this.meta.getIdFromMetaName(i.key),metadata:{}});o.createdAt=(i.uploaded??new Date).toISOString(),o.modifiedAt=o.createdAt,a.push(o)}r=s.truncated===!0?s.cursor:void 0}while(r!==void 0);return a})}copy(){return this.unsupported("copy")}move(){return this.unsupported("move")}async finish(t,e,a){try{return await t.finish()}catch(r){throw a&&r.UploadErrorCode===n.FILE_CONFLICT&&(await this.deleteSegments(e).catch(()=>{}),await this.deleteMeta(e).catch(()=>{})),r}}async completedObject(t){const e=await this.checkIfExpired(await this.getMeta(t)),a=e.status==="completed"?await this.bucket.get(e.name):void 0;return a==null?c(n.FILE_NOT_FOUND):{file:e,object:a}}unsupported(t){return Promise.reject(new _(n.METHOD_NOT_ALLOWED,`${this.constructor.name} does not implement ${t}()`))}checkPart(t,e){e.start!==t.bytesWritten&&p(`Upload-Offset ${String(e.start)} does not match the upload's offset ${String(t.bytesWritten)}`),e.checksumAlgorithm!==void 0&&e.checksumAlgorithm!==""&&c(n.UNSUPPORTED_CHECKSUM_ALGORITHM),e.contentLength!==void 0&&e.start+e.contentLength>(t.size??0)&&c(n.REQUEST_ENTITY_TOO_LARGE,"The chunk runs past the declared upload length")}async acquireLease(t,e,a){if(P(e.upload.lock))return p("Another request is writing to this upload");const r=crypto.randomUUID(),s={...e.upload,lock:{expiresAt:Date.now()+m,token:r}};return await this.meta.write(t,{...e,upload:s},a)===void 0?p("Another request is writing to this upload"):r}async changeLeased(t,e,a){return this.meta.swap(t,r=>r.upload.lock?.token!==e?p("This request's lease on the upload expired; resume from the current offset"):a(r))}async releaseLease(t,e){await this.changeLeased(t,e,a=>{const r={...a.upload};return delete r.lock,{...a,upload:r}})}async renewLease(t,e){await this.changeLeased(t,e,a=>({...a,upload:{...a.upload,lock:{expiresAt:Date.now()+m,token:e}}}))}async deleteSegments(t){const e=[];let a;do{const r=await this.bucket.list({prefix:this.meta.segmentPrefix(t),...a===void 0?{}:{cursor:a}});e.push(...r.objects.map(s=>s.key)),a=r.truncated===!0?r.cursor:void 0}while(a!==void 0);for(let r=0;r<e.length;r+=1e3)await this.bucket.delete(e.slice(r,r+1e3))}async pump(t,e,a,r,s){const i=E(e);let o=0,u=Date.now();try{for(;;){let h;try{h=await i.next()}catch(l){return l instanceof Error?l:new Error(String(l))}if(h.done===!0)return;if(o+=h.value.byteLength,o>a)return c(n.REQUEST_ENTITY_TOO_LARGE,"The chunk runs past the declared upload length");Date.now()-u>=m/3&&(u=Date.now(),await this.renewLease(r,s)),await t.push(h.value)}}finally{await i.return(void 0).catch(()=>{})}}async writeLeased(t,e,a,r){const s=t.size??0,i=new I(this.bucket,t,e,this.meta.segmentPrefix(t.id),{confirm:async()=>this.renewLease(t.id,r),save:async(f,v)=>{await this.changeLeased(t.id,r,w=>({file:{...w.file,bytesWritten:v,status:"part"},upload:{...f,lock:w.upload.lock}}))},token:r}),o=await this.pump(i,a,s-t.bytesWritten,t.id,r),u=i.offset===s,h=u?await this.finish(i,t.id,e.createOnly===!0):void 0;if(!u)try{await i.keepTail()}catch(f){if(o===void 0)throw f}const l={bytesWritten:u?s:i.stored,modifiedAt:new Date().toISOString(),status:u?"completed":"part",...h===void 0?{}:{ETag:h}},O=u?{parts:[],segments:[]}:i.progress();if(await this.changeLeased(t.id,r,f=>({file:{...f.file,...l},upload:O})),await(u?this.deleteSegments(t.id).catch(()=>{}):i.dropConsumed()),o!==void 0)throw o;return{...t,...l}}}const $=(d,t={})=>new j(d,t);export{G as R2_PART_SIZE,$ as createR2BindingUploadStorage};
@@ -1 +1 @@
1
- import{LunoraError as n}from"@lunora/errors";import{t as U,h as M,a as I,b as f}from"./internal-B5yyxF10.mjs";import{buildPresignedUrl as S}from"./buildPresignedUrl-U55ktL8x.mjs";import{buildSignedUrl as _}from"./buildSignedUrl-czz1xjLU.mjs";const w=1024,v=1e3,k=100,A=16*1024*1024,D=e=>{const a=e.checksums?.sha256;if(a===void 0)return e;const c=f(a),l=I(new Uint8Array(a));return new Proxy(e,{get(u,s){if(s==="sha256")return c;if(s==="sha256Base64")return l;const d=Reflect.get(u,s,u);return typeof d=="function"?d.bind(u):d},has(u,s){return s==="sha256"||s==="sha256Base64"||Reflect.has(u,s)}})},B=e=>{const a=e.checksums?.sha256;return{contentType:e.httpMetadata?.contentType,customMetadata:e.customMetadata,key:e.key,sha256:a===void 0?void 0:f(a),size:e.size,uploaded:e.uploaded===void 0?void 0:e.uploaded.getTime()}},y=e=>{const a=e.checksums?.sha256;return{checksums:e.checksums,customMetadata:e.customMetadata,etag:e.etag,httpEtag:e.httpEtag,httpMetadata:e.httpMetadata,key:e.key,sha256:a===void 0?void 0:f(a),sha256Base64:a===void 0?void 0:I(new Uint8Array(a)),size:e.size,uploaded:e.uploaded}},O=async(e,a)=>{let c=0;const l=s=>s instanceof ArrayBuffer?new Uint8Array(s):ArrayBuffer.isView(s)?new Uint8Array(s.buffer,s.byteOffset,s.byteLength):void 0,u=new TransformStream({transform(s,d){const m=l(s);if(m===void 0){d.error(new n("VALIDATION_ERROR","@lunora/storage: stream chunk is not a byte chunk; cannot enforce maxSize"));return}if(c+=m.byteLength,c>a){d.error(new n("PAYLOAD_TOO_LARGE",`@lunora/storage: stream body exceeds maxSize (> ${String(a)} bytes)`));return}d.enqueue(m)}});return await new Response(e.pipeThrough(u)).blob()},V=new TextEncoder,R=e=>V.encode(e).length,x=e=>{if(e instanceof ArrayBuffer||ArrayBuffer.isView(e))return e.byteLength;if(typeof e=="string")return R(e);if(typeof e!="object"||e===null)return;const{size:a}=e;return typeof a=="number"?a:void 0},$=async(e,a)=>{if(!Number.isFinite(a)||a<0)throw new n("VALIDATION_ERROR",`@lunora/storage: maxSize must be a finite, non-negative number (received ${String(a)})`);if(e instanceof ReadableStream){if(a>A)throw new n("VALIDATION_ERROR",`@lunora/storage: maxSize ${String(a)} exceeds the ${String(A)}-byte ceiling for a streamed body, which must be buffered to be capped — use createMultipartUpload() or createUploadHandler() for objects this large`);return await O(e,a)}const c=x(e);if(c===void 0)throw new n("VALIDATION_ERROR","@lunora/storage: cannot measure body length, so maxSize cannot be enforced");if(c>a)throw new n("PAYLOAD_TOO_LARGE",`@lunora/storage: body exceeds maxSize (${String(c)} > ${String(a)})`);return e},i=e=>{if(typeof e!="string"||e.length===0)throw new n("VALIDATION_ERROR","@lunora/storage: key must be a non-empty string");if(R(e)>w)throw new n("VALIDATION_ERROR",`@lunora/storage: key exceeds ${String(w)}-byte limit`);if(e.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: key contains NUL byte");if(M(e))throw new n("VALIDATION_ERROR","@lunora/storage: key contains a control character (including CR/LF)");if(e.startsWith("/"))throw new n("VALIDATION_ERROR","@lunora/storage: key must not start with `/`");if(e.split("/").includes(".."))throw new n("VALIDATION_ERROR","@lunora/storage: key contains a `..` path component")},C=e=>{if(e.allowedContentTypes!==void 0){if(e.contentType===void 0)throw new n("VALIDATION_ERROR","@lunora/storage: contentType is required when allowedContentTypes is set");if(!e.allowedContentTypes.includes(e.contentType))throw new n("VALIDATION_ERROR",`@lunora/storage: contentType "${e.contentType}" not in allowedContentTypes`)}},F=(e,a)=>{i(e),i(a);const l=`${e.endsWith("/")?e.slice(0,-1):e}/${a}`;if(R(l)>w)throw new n("VALIDATION_ERROR",`@lunora/storage: scoped key exceeds ${String(w)}-byte limit`);return l},X=e=>{if(!e.bucket)throw new n("INTERNAL","@lunora/storage: `bucket` is required");if(typeof e.bucketName!="string"||e.bucketName==="")throw new n("INTERNAL",'@lunora/storage: `bucketName` is required — pass the name this bucket is registered under (`"default"` for a single-bucket app)');const a=async(r,t,o={})=>{i(r),C(o);const g=o.maxSize===void 0?t:await $(t,o.maxSize),h=await e.bucket.put(r,g,{customMetadata:o.customMetadata,httpMetadata:o.contentType?{contentType:o.contentType}:void 0,...o.sha256===void 0?{}:{sha256:o.sha256}});return{etag:h.etag,httpEtag:h.httpEtag??`"${h.etag}"`,key:h.key}},c=async(r,t={})=>{i(r);const o=await(t.range?e.bucket.get(r,{range:t.range}):e.bucket.get(r));return o&&D(o)},l=async r=>{i(r),await e.bucket.delete(r)},u=async r=>{i(r);let t;if(e.bucket.head)t=await e.bucket.head(r);else{const o=await e.bucket.get(r);await o?.body?.cancel(),t=o}return t&&y(t)},s=async r=>{const t=await u(r);return t&&B(t)},d=async(r,t={})=>{if(r?.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: prefix contains NUL byte");if(t.limit!==void 0&&(!Number.isInteger(t.limit)||t.limit<=0))throw new n("VALIDATION_ERROR",`@lunora/storage: list limit must be a positive integer (received ${String(t.limit)})`);const o=Math.min(t.limit??k,v),g=await e.bucket.list({cursor:t.cursor,delimiter:t.delimiter,include:["customMetadata","httpMetadata"],limit:o,prefix:r}),{delimitedPrefixes:h}=g;return{cursor:g.cursor,delimitedPrefixes:h,objects:g.objects.map(E=>y(E)),truncated:g.truncated}},m=r=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getUrl()");i(r);const t=r.split("/").map(o=>encodeURIComponent(o)).join("/");return`${U(e.publicBaseUrl)}/${t}`},T=async(r,t={})=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getSignedUrl()");if(!e.signingSecret)throw new n("INTERNAL","@lunora/storage: `signingSecret` is required for getSignedUrl()");return i(r),_({baseUrl:e.publicBaseUrl,bucketName:e.bucketName,contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method,secret:e.signingSecret})},b=async(r,t={})=>{if(i(r),!e.bucket.createMultipartUpload)throw new n("INTERNAL","@lunora/storage: bucket binding does not support multipart uploads (createMultipartUpload)");return e.bucket.createMultipartUpload(r,{customMetadata:t.customMetadata,httpMetadata:t.contentType?{contentType:t.contentType}:void 0})},N=(r,t)=>{if(i(r),typeof t!="string"||t.length===0)throw new n("VALIDATION_ERROR","@lunora/storage: resumeMultipartUpload requires a non-empty uploadId");if(!e.bucket.resumeMultipartUpload)throw new n("INTERNAL","@lunora/storage: bucket binding does not support multipart uploads (resumeMultipartUpload)");return e.bucket.resumeMultipartUpload(r,t)},L=async(r,t={})=>{if(!e.s3)throw new n("INTERNAL","@lunora/storage: `s3` credentials are required for getPresignedUrl() — pass { accountId, accessKeyId, secretAccessKey, bucket }");return i(r),S({credentials:e.s3,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method})},p=async(r,t={})=>T(r,{contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,method:"PUT"});return{bucketName:e.bucketName,createMultipartUpload:b,delete:l,download:c,generateUploadUrl:p,getMetadata:s,getPresignedUrl:L,getSignedUrl:T,getUrl:m,head:u,list:d,resumeMultipartUpload:N,store:a,upload:a}};export{X as createStorage,F as scopeKey};
1
+ import{LunoraError as n}from"@lunora/errors";import{t as U,h as M,a as I,b as f}from"./internal-B047mSU9.mjs";import{buildPresignedUrl as S}from"./buildPresignedUrl-GEo1eFpq.mjs";import{buildSignedUrl as _}from"./buildSignedUrl-Bg2beHZA.mjs";const w=1024,v=1e3,k=100,A=16*1024*1024,D=e=>{const a=e.checksums?.sha256;if(a===void 0)return e;const c=f(a),l=I(new Uint8Array(a));return new Proxy(e,{get(u,s){if(s==="sha256")return c;if(s==="sha256Base64")return l;const d=Reflect.get(u,s,u);return typeof d=="function"?d.bind(u):d},has(u,s){return s==="sha256"||s==="sha256Base64"||Reflect.has(u,s)}})},B=e=>{const a=e.checksums?.sha256;return{contentType:e.httpMetadata?.contentType,customMetadata:e.customMetadata,key:e.key,sha256:a===void 0?void 0:f(a),size:e.size,uploaded:e.uploaded===void 0?void 0:e.uploaded.getTime()}},y=e=>{const a=e.checksums?.sha256;return{checksums:e.checksums,customMetadata:e.customMetadata,etag:e.etag,httpEtag:e.httpEtag,httpMetadata:e.httpMetadata,key:e.key,sha256:a===void 0?void 0:f(a),sha256Base64:a===void 0?void 0:I(new Uint8Array(a)),size:e.size,uploaded:e.uploaded}},O=async(e,a)=>{let c=0;const l=s=>s instanceof ArrayBuffer?new Uint8Array(s):ArrayBuffer.isView(s)?new Uint8Array(s.buffer,s.byteOffset,s.byteLength):void 0,u=new TransformStream({transform(s,d){const m=l(s);if(m===void 0){d.error(new n("VALIDATION_ERROR","@lunora/storage: stream chunk is not a byte chunk; cannot enforce maxSize"));return}if(c+=m.byteLength,c>a){d.error(new n("PAYLOAD_TOO_LARGE",`@lunora/storage: stream body exceeds maxSize (> ${String(a)} bytes)`));return}d.enqueue(m)}});return await new Response(e.pipeThrough(u)).blob()},V=new TextEncoder,R=e=>V.encode(e).length,x=e=>{if(e instanceof ArrayBuffer||ArrayBuffer.isView(e))return e.byteLength;if(typeof e=="string")return R(e);if(typeof e!="object"||e===null)return;const{size:a}=e;return typeof a=="number"?a:void 0},$=async(e,a)=>{if(!Number.isFinite(a)||a<0)throw new n("VALIDATION_ERROR",`@lunora/storage: maxSize must be a finite, non-negative number (received ${String(a)})`);if(e instanceof ReadableStream){if(a>A)throw new n("VALIDATION_ERROR",`@lunora/storage: maxSize ${String(a)} exceeds the ${String(A)}-byte ceiling for a streamed body, which must be buffered to be capped — use createMultipartUpload() or createUploadHandler() for objects this large`);return await O(e,a)}const c=x(e);if(c===void 0)throw new n("VALIDATION_ERROR","@lunora/storage: cannot measure body length, so maxSize cannot be enforced");if(c>a)throw new n("PAYLOAD_TOO_LARGE",`@lunora/storage: body exceeds maxSize (${String(c)} > ${String(a)})`);return e},i=e=>{if(typeof e!="string"||e.length===0)throw new n("VALIDATION_ERROR","@lunora/storage: key must be a non-empty string");if(R(e)>w)throw new n("VALIDATION_ERROR",`@lunora/storage: key exceeds ${String(w)}-byte limit`);if(e.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: key contains NUL byte");if(M(e))throw new n("VALIDATION_ERROR","@lunora/storage: key contains a control character (including CR/LF)");if(e.startsWith("/"))throw new n("VALIDATION_ERROR","@lunora/storage: key must not start with `/`");if(e.split("/").includes(".."))throw new n("VALIDATION_ERROR","@lunora/storage: key contains a `..` path component")},C=e=>{if(e.allowedContentTypes!==void 0){if(e.contentType===void 0)throw new n("VALIDATION_ERROR","@lunora/storage: contentType is required when allowedContentTypes is set");if(!e.allowedContentTypes.includes(e.contentType))throw new n("VALIDATION_ERROR",`@lunora/storage: contentType "${e.contentType}" not in allowedContentTypes`)}},F=(e,a)=>{i(e),i(a);const l=`${e.endsWith("/")?e.slice(0,-1):e}/${a}`;if(R(l)>w)throw new n("VALIDATION_ERROR",`@lunora/storage: scoped key exceeds ${String(w)}-byte limit`);return l},X=e=>{if(!e.bucket)throw new n("INTERNAL","@lunora/storage: `bucket` is required");if(typeof e.bucketName!="string"||e.bucketName==="")throw new n("INTERNAL",'@lunora/storage: `bucketName` is required — pass the name this bucket is registered under (`"default"` for a single-bucket app)');const a=async(r,t,o={})=>{i(r),C(o);const g=o.maxSize===void 0?t:await $(t,o.maxSize),h=await e.bucket.put(r,g,{customMetadata:o.customMetadata,httpMetadata:o.contentType?{contentType:o.contentType}:void 0,...o.sha256===void 0?{}:{sha256:o.sha256}});return{etag:h.etag,httpEtag:h.httpEtag??`"${h.etag}"`,key:h.key}},c=async(r,t={})=>{i(r);const o=await(t.range?e.bucket.get(r,{range:t.range}):e.bucket.get(r));return o&&D(o)},l=async r=>{i(r),await e.bucket.delete(r)},u=async r=>{i(r);let t;if(e.bucket.head)t=await e.bucket.head(r);else{const o=await e.bucket.get(r);await o?.body?.cancel(),t=o}return t&&y(t)},s=async r=>{const t=await u(r);return t&&B(t)},d=async(r,t={})=>{if(r?.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: prefix contains NUL byte");if(t.limit!==void 0&&(!Number.isInteger(t.limit)||t.limit<=0))throw new n("VALIDATION_ERROR",`@lunora/storage: list limit must be a positive integer (received ${String(t.limit)})`);const o=Math.min(t.limit??k,v),g=await e.bucket.list({cursor:t.cursor,delimiter:t.delimiter,include:["customMetadata","httpMetadata"],limit:o,prefix:r}),{delimitedPrefixes:h}=g;return{cursor:g.cursor,delimitedPrefixes:h,objects:g.objects.map(E=>y(E)),truncated:g.truncated}},m=r=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getUrl()");i(r);const t=r.split("/").map(o=>encodeURIComponent(o)).join("/");return`${U(e.publicBaseUrl)}/${t}`},T=async(r,t={})=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getSignedUrl()");if(!e.signingSecret)throw new n("INTERNAL","@lunora/storage: `signingSecret` is required for getSignedUrl()");return i(r),_({baseUrl:e.publicBaseUrl,bucketName:e.bucketName,contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method,secret:e.signingSecret})},b=async(r,t={})=>{if(i(r),!e.bucket.createMultipartUpload)throw new n("INTERNAL","@lunora/storage: bucket binding does not support multipart uploads (createMultipartUpload)");return e.bucket.createMultipartUpload(r,{customMetadata:t.customMetadata,httpMetadata:t.contentType?{contentType:t.contentType}:void 0})},N=(r,t)=>{if(i(r),typeof t!="string"||t.length===0)throw new n("VALIDATION_ERROR","@lunora/storage: resumeMultipartUpload requires a non-empty uploadId");if(!e.bucket.resumeMultipartUpload)throw new n("INTERNAL","@lunora/storage: bucket binding does not support multipart uploads (resumeMultipartUpload)");return e.bucket.resumeMultipartUpload(r,t)},L=async(r,t={})=>{if(!e.s3)throw new n("INTERNAL","@lunora/storage: `s3` credentials are required for getPresignedUrl() — pass { accountId, accessKeyId, secretAccessKey, bucket }");return i(r),S({credentials:e.s3,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method})},p=async(r,t={})=>T(r,{contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,method:"PUT"});return{bucketName:e.bucketName,createMultipartUpload:b,delete:l,download:c,generateUploadUrl:p,getMetadata:s,getPresignedUrl:L,getSignedUrl:T,getUrl:m,head:u,list:d,resumeMultipartUpload:N,store:a,upload:a}};export{X as createStorage,F as scopeKey};
@@ -0,0 +1 @@
1
+ const w=t=>{let n="";for(let r=0;r<t.length;r+=32768)n+=String.fromCharCode(...t.subarray(r,r+32768));return btoa(n)},d=t=>{const n=atob(t),e=new Uint8Array(n.length);for(let r=0;r<n.length;r+=1)e[r]=n.codePointAt(r)??0;return e},s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",u=t=>{let n="",e=0;const r=t.length-2;for(;e<r;e+=3){const o=t[e]<<16|t[e+1]<<8|t[e+2];n+=s.charAt(o>>18&63)+s.charAt(o>>12&63)+s.charAt(o>>6&63)+s.charAt(o&63)}const c=t.length-e;if(c===1){const o=t[e]<<16;n+=s.charAt(o>>18&63)+s.charAt(o>>12&63)}else if(c===2){const o=t[e]<<16|t[e+1]<<8;n+=s.charAt(o>>18&63)+s.charAt(o>>12&63)+s.charAt(o>>6&63)}return n},_=t=>{const n=t.replaceAll("-","+").replaceAll("_","/"),e=n+"=".repeat((4-n.length%4)%4);return d(e)},A=(t,n)=>{if(t.size<n)return;const e=t.keys().next().value;e!==void 0&&t.delete(e)},f=(t,n,e,r)=>{const c=t.get(n);if(c!==void 0)return c;A(t,r);const o=e().catch(h=>{throw t.get(n)===o&&t.delete(n),h});return t.set(n,o),o},g=new TextEncoder,y=64,C=new Map,a=async t=>f(C,t,async()=>crypto.subtle.importKey("raw",g.encode(t),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]),y),i=new TextEncoder,m=10080*60,S=/^[a-z][a-z0-9+\-.]*:\/\//i,E=Array.from({length:32},(t,n)=>n),l=new RegExp(`[${E.map(t=>String.fromCodePoint(t)).join("")}]`,"u"),x=t=>{try{return new URL(t).host}catch{return t.replace(S,"").split("/")[0]??""}},H=t=>{if(l.test(t))throw new TypeError("hmac-url: value must not contain control characters (including CR/LF)")},R=t=>l.test(t),p=/^\/+$/u,T=t=>p.test(t),L=(t,n)=>{if(!Number.isFinite(t)||t<=0)return"expiresInSeconds must be a positive finite number";if(t>n){const e=` (${String(n/86400)} days)`;return`expiresInSeconds must not exceed ${String(n)} seconds${e}`}},O=async(t,n)=>{const e=await a(t),r=await crypto.subtle.sign("HMAC",e,i.encode(n));return u(new Uint8Array(r))},v=async(t,n,e)=>{const r=await a(t);return crypto.subtle.verify("HMAC",r,e,i.encode(n))},M=t=>{const n=new Uint8Array(t);let e="";for(const r of n)e+=r.toString(16).padStart(2,"0");return e},U=t=>{let n=t.length;for(;n>0&&t[n-1]==="/";)n-=1;return t.slice(0,n)};export{m as M,w as a,M as b,H as c,v as d,x as e,_ as f,R as h,T as i,f as m,O as s,U as t,L as v};
@@ -1 +1 @@
1
- import{throwErrorCode as c,ERRORS as u}from"@visulima/storage";const d=i=>{if(i instanceof Uint8Array)return i;if(typeof i=="string")return new TextEncoder().encode(i);if(ArrayBuffer.isView(i))return new Uint8Array(i.buffer,i.byteOffset,i.byteLength);if(i instanceof ArrayBuffer)return new Uint8Array(i);throw new TypeError("Unsupported upload body chunk")},l=i=>typeof i.getReader=="function",b=async function*(t){if(t!=null){if(t instanceof Uint8Array){yield t;return}if(typeof t=="object"&&l(t)){const e=t.getReader();try{for(;;){const{done:s,value:n}=await e.read();if(s)return;yield d(n)}}finally{e.releaseLock()}}if(typeof t=="object"&&Symbol.asyncIterator in t){for await(const e of t)yield d(e);return}throw new TypeError("Unsupported upload body")}};class f{size=0;chunks=[];push(t){t.byteLength>0&&(this.chunks.push(t),this.size+=t.byteLength)}copyFront(t,e,s){let n=0;for(const a of this.chunks){if(n>=s)break;const r=Math.min(a.byteLength,s-n);t.set(a.subarray(0,r),e+n),n+=r}}drop(t){let e=t;for(;e>0;){const s=this.chunks[0];if(s===void 0)break;s.byteLength<=e?(this.chunks.shift(),e-=s.byteLength):(this.chunks[0]=s.subarray(e),e=0)}this.size-=t-e}}const o=5*1024*1024,g=8,h=i=>i.reduce((t,e)=>t+e.size,0),p="A file already exists under this name",m=async(i,t,e,s,n)=>n?await i.put(t,e,{httpMetadata:{contentType:s},onlyIf:{etagDoesNotMatch:"*"}})??c(u.FILE_CONFLICT,p):i.put(t,e,{httpMetadata:{contentType:s}}),y=async(i,t)=>{if(i.head!==void 0)return await i.head(t)!==null;const e=await i.get(t);return await e?.body?.cancel(),e!==null};class I{parts;segments;uploadId;bucket;createOnly;consumed=[];file;lease;pending=new f;segmentPrefix;constructor(t,e,s,n,a){this.bucket=t,this.file=e,this.parts=[...s.parts],this.segments=[...s.segments],this.uploadId=s.uploadId,this.createOnly=s.createOnly===!0,this.segmentPrefix=n,this.lease=a}get stored(){return this.parts.length*o+h(this.segments)}get offset(){return this.stored+this.pending.size}progress(){return{parts:[...this.parts],segments:[...this.segments],...this.uploadId===void 0?{}:{uploadId:this.uploadId},...this.createOnly?{createOnly:!0}:{}}}async push(t){for(this.pending.push(t);h(this.segments)+this.pending.size>=o;)await this.flushPart()}async keepTail(){if(this.pending.size===0)return;const t=this.segments.length+1>g,e=t?this.parts.length*o:this.stored,s=t?h(this.segments)+this.pending.size:this.pending.size,n=t?await this.assemble(s):this.front(s),a=`${this.segmentPrefix}${String(e).padStart(16,"0")}-${this.lease.token}`;await this.bucket.put(a,n),t&&this.consumeSegments(),this.segments=[...this.segments,{key:a,size:s}],this.pending.drop(this.pending.size)}async finish(){const t=await this.assemble(h(this.segments)+this.pending.size);await this.lease.confirm();let e;if(this.uploadId===void 0)e=(await m(this.bucket,this.file.name,t,this.file.contentType,this.createOnly)).etag;else{if(this.createOnly&&await y(this.bucket,this.file.name))return await this.multipart(this.uploadId).abort().catch(()=>{}),c(u.FILE_CONFLICT,p);const s=this.multipart(this.uploadId);if(t.byteLength>0){const a=this.parts.length+1,r=await s.uploadPart(a,t);this.parts.push({etag:r.etag,partNumber:a})}e=(await s.complete(this.parts)).etag}return this.consumeSegments(),this.pending.drop(this.pending.size),e}async dropConsumed(){const t=this.consumed;this.consumed=[],t.length>0&&await this.bucket.delete(t).catch(()=>{})}async assemble(t){const e=new Uint8Array(t);let s=0;for(const n of this.segments){const a=await this.bucket.get(n.key);if(a===null)return c(u.STORAGE_ERROR,`Buffered upload segment ${n.key} is missing`);const r=new Uint8Array(await a.arrayBuffer());e.set(r,s),s+=r.byteLength}return this.pending.copyFront(e,s,t-s),e}front(t){const e=new Uint8Array(t);return this.pending.copyFront(e,0,t),e}consumeSegments(){this.consumed.push(...this.segments.map(t=>t.key)),this.segments=[]}multipart(t){return this.bucket.resumeMultipartUpload(this.file.name,t)}async flushPart(){const t=h(this.segments),e=await this.assemble(o);if(await this.lease.confirm(),this.uploadId===void 0){const a=await this.bucket.createMultipartUpload(this.file.name,{httpMetadata:{contentType:this.file.contentType}});this.uploadId=a.uploadId}const s=this.parts.length+1,n=await this.multipart(this.uploadId).uploadPart(s,e);this.parts.push({etag:n.etag,partNumber:s}),this.pending.drop(o-t),this.consumeSegments(),await this.lease.save(this.progress(),this.stored),await this.dropConsumed()}}export{p as N,o as R,I as a,y as o,m as p,b as r};
1
+ import{throwErrorCode as c,ERRORS as u}from"@visulima/storage";const d=i=>{if(i instanceof Uint8Array)return i;if(typeof i=="string")return new TextEncoder().encode(i);if(ArrayBuffer.isView(i))return new Uint8Array(i.buffer,i.byteOffset,i.byteLength);if(i instanceof ArrayBuffer)return new Uint8Array(i);throw new TypeError("Unsupported upload body chunk")},l=i=>typeof i.getReader=="function",b=async function*(t){if(t!=null){if(t instanceof Uint8Array){yield t;return}if(typeof t=="object"&&l(t)){const e=t.getReader();try{for(;;){const{done:s,value:a}=await e.read();if(s)return;yield d(a)}}finally{e.releaseLock()}}if(typeof t=="object"&&Symbol.asyncIterator in t){for await(const e of t)yield d(e);return}throw new TypeError("Unsupported upload body")}};class f{size=0;chunks=[];push(t){t.byteLength>0&&(this.chunks.push(t),this.size+=t.byteLength)}copyFront(t,e,s){let a=0;for(const n of this.chunks){if(a>=s)break;const r=Math.min(n.byteLength,s-a);t.set(n.subarray(0,r),e+a),a+=r}}drop(t){let e=t;for(;e>0;){const s=this.chunks[0];if(s===void 0)break;s.byteLength<=e?(this.chunks.shift(),e-=s.byteLength):(this.chunks[0]=s.subarray(e),e=0)}this.size-=t-e}}const o=5*1024*1024,g=8,h=i=>i.reduce((t,e)=>t+e.size,0),p="A file already exists under this name",m=async(i,t,e,s,a)=>a?await i.put(t,e,{httpMetadata:{contentType:s},onlyIf:{etagDoesNotMatch:"*"}})??c(u.FILE_CONFLICT,p):i.put(t,e,{httpMetadata:{contentType:s}}),y=async(i,t)=>{if(i.head!==void 0)return await i.head(t)!==null;const e=await i.get(t);return await e?.body?.cancel(),e!==null};class I{parts;segments;uploadId;bucket;createOnly;consumed=[];file;lease;pending=new f;segmentPrefix;constructor(t,e,s,a,n){this.bucket=t,this.file=e,this.parts=[...s.parts],this.segments=[...s.segments],this.uploadId=s.uploadId,this.createOnly=s.createOnly===!0,this.segmentPrefix=a,this.lease=n}get stored(){return this.parts.length*o+h(this.segments)}get offset(){return this.stored+this.pending.size}progress(){return{parts:[...this.parts],segments:[...this.segments],...this.uploadId===void 0?{}:{uploadId:this.uploadId},...this.createOnly?{createOnly:!0}:{}}}async push(t){for(this.pending.push(t);h(this.segments)+this.pending.size>=o;)await this.flushPart()}async keepTail(){if(this.pending.size===0)return;const t=this.segments.length+1>g,e=t?this.parts.length*o:this.stored,s=t?h(this.segments)+this.pending.size:this.pending.size,a=t?await this.assemble(s):this.front(s),n=`${this.segmentPrefix}${String(e).padStart(16,"0")}-${this.lease.token}`;await this.bucket.put(n,a),t&&this.consumeSegments(),this.segments=[...this.segments,{key:n,size:s}],this.pending.drop(this.pending.size)}async finish(){const t=await this.assemble(h(this.segments)+this.pending.size);await this.lease.confirm();let e;if(this.uploadId===void 0)e=(await m(this.bucket,this.file.name,t,this.file.contentType,this.createOnly)).etag;else{if(this.createOnly&&await y(this.bucket,this.file.name))return await this.multipart(this.uploadId).abort().catch(()=>{}),c(u.FILE_CONFLICT,p);const s=this.multipart(this.uploadId);if(t.byteLength>0){const n=this.parts.length+1,r=await s.uploadPart(n,t);this.parts.push({etag:r.etag,partNumber:n})}e=(await s.complete(this.parts)).etag}return this.consumeSegments(),this.pending.drop(this.pending.size),e}async dropConsumed(){const t=this.consumed;this.consumed=[],t.length>0&&await this.bucket.delete(t).catch(()=>{})}async assemble(t){const e=new Uint8Array(t);let s=0;const a=await Promise.all(this.segments.map(async n=>{const r=await this.bucket.get(n.key);return r===null?c(u.STORAGE_ERROR,`Buffered upload segment ${n.key} is missing`):new Uint8Array(await r.arrayBuffer())}));for(const n of a)e.set(n,s),s+=n.byteLength;return this.pending.copyFront(e,s,t-s),e}front(t){const e=new Uint8Array(t);return this.pending.copyFront(e,0,t),e}consumeSegments(){this.consumed.push(...this.segments.map(t=>t.key)),this.segments=[]}multipart(t){return this.bucket.resumeMultipartUpload(this.file.name,t)}async flushPart(){const t=h(this.segments),e=await this.assemble(o);if(await this.lease.confirm(),this.uploadId===void 0){const n=await this.bucket.createMultipartUpload(this.file.name,{httpMetadata:{contentType:this.file.contentType}});this.uploadId=n.uploadId}const s=this.parts.length+1,a=await this.multipart(this.uploadId).uploadPart(s,e);this.parts.push({etag:a.etag,partNumber:s}),this.pending.drop(o-t),this.consumeSegments(),await this.lease.save(this.progress(),this.stored),await this.dropConsumed()}}export{p as N,o as R,I as a,y as o,m as p,b as r};
package/dist/upload.mjs CHANGED
@@ -1 +1 @@
1
- import{createR2BindingUploadStorage as r}from"./packem_shared/createR2BindingUploadStorage-Vjs0vyJJ.mjs";import{createR2UploadStorage as t}from"./packem_shared/createR2UploadStorage-DGB1xJiD.mjs";import{DEFAULT_MAX_UPLOAD_BYTES as d,createUploadContext as U,createUploadHandler as l}from"./packem_shared/DEFAULT_MAX_UPLOAD_BYTES-BOaXqwP5.mjs";import{R}from"./packem_shared/r2-upload-part-writer-SdQlWaAu.mjs";export{d as DEFAULT_MAX_UPLOAD_BYTES,R as R2_PART_SIZE,r as createR2BindingUploadStorage,t as createR2UploadStorage,U as createUploadContext,l as createUploadHandler};
1
+ import{createR2BindingUploadStorage as r}from"./packem_shared/createR2BindingUploadStorage-A9OJQ4j_.mjs";import{createR2UploadStorage as t}from"./packem_shared/createR2UploadStorage-DGB1xJiD.mjs";import{DEFAULT_MAX_UPLOAD_BYTES as d,createUploadContext as U,createUploadHandler as l}from"./packem_shared/DEFAULT_MAX_UPLOAD_BYTES-BOaXqwP5.mjs";import{R}from"./packem_shared/r2-upload-part-writer-M7dEI00-.mjs";export{d as DEFAULT_MAX_UPLOAD_BYTES,R as R2_PART_SIZE,r as createR2BindingUploadStorage,t as createR2UploadStorage,U as createUploadContext,l as createUploadHandler};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/storage",
3
- "version": "1.0.0-alpha.159",
3
+ "version": "1.0.0-alpha.160",
4
4
  "description": "R2-backed storage for Lunora: typed buckets and signed URLs",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1,4 +0,0 @@
1
- import{LunoraError as H}from"@lunora/errors";import{b as y,v as R}from"./internal-B5yyxF10.mjs";const $="auto",E="s3",g="AWS4-HMAC-SHA256",x=10080*60,K=900,d=new TextEncoder,z=(t,e)=>t[0]<e[0]?-1:t[0]>e[0]?1:0,i=t=>encodeURIComponent(t).replaceAll(/[!'()*]/gu,e=>`%${e.codePointAt(0)?.toString(16).toUpperCase()??""}`),C=t=>t.split("/").map(e=>i(e)).join("/"),X=async t=>y(await crypto.subtle.digest("SHA-256",d.encode(t))),c=async(t,e)=>{const n=await crypto.subtle.importKey("raw",t,{hash:"SHA-256",name:"HMAC"},!1,["sign"]);return crypto.subtle.sign("HMAC",n,d.encode(e))},O=async(t,e)=>{const n=await c(d.encode(`AWS4${t}`),e),r=await c(n,$),s=await c(r,E);return c(s,"aws4_request")},v=t=>{const e=t.jurisdiction===void 0?"":`${t.jurisdiction}.`;return`${t.accountId}.${e}r2.cloudflarestorage.com`},T=t=>{const e=`${t.toISOString().replaceAll(/[:-]/gu,"").slice(0,15)}Z`;return{amzDate:e,dateStamp:e.slice(0,8)}},M=async t=>{const{credentials:e,key:n}=t,r=t.method??"GET";let s=K;if(t.expiresInSeconds!==void 0){s=Math.floor(t.expiresInSeconds);const o=R(s,x);if(o!==void 0)throw new H("VALIDATION_ERROR",`@lunora/storage: ${o}`)}const l=v(e),w=new Date(t.now?.()??Date.now()),{amzDate:u,dateStamp:S}=T(w),A=`${S}/${$}/${E}/aws4_request`,m=`/${i(e.bucket)}/${C(n)}`,p=[["X-Amz-Algorithm",g],["X-Amz-Credential",`${e.accessKeyId}/${A}`],["X-Amz-Date",u],["X-Amz-Expires",s.toString()],["X-Amz-SignedHeaders","host"]].map(([o,a])=>[i(o),i(a)]).toSorted(z).map(([o,a])=>`${o}=${a}`).join("&"),h=[r,m,p,`host:${l}
2
- `,"host","UNSIGNED-PAYLOAD"].join(`
3
- `),I=[g,u,A,await X(h)].join(`
4
- `),D=await O(e.secretAccessKey,S),f=y(await c(D,I));return`https://${l}${m}?${p}&X-Amz-Signature=${f}`};export{M as buildPresignedUrl};
@@ -1 +0,0 @@
1
- const p=t=>{let n="";for(let r=0;r<t.length;r+=32768)n+=String.fromCharCode(...t.subarray(r,r+32768));return btoa(n)},d=t=>{const n=atob(t),e=new Uint8Array(n.length);for(let r=0;r<n.length;r+=1)e[r]=n.codePointAt(r)??0;return e},s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",u=t=>{let n="",e=0;const r=t.length-2;for(;e<r;e+=3){const o=t[e]<<16|t[e+1]<<8|t[e+2];n+=s.charAt(o>>18&63)+s.charAt(o>>12&63)+s.charAt(o>>6&63)+s.charAt(o&63)}const c=t.length-e;if(c===1){const o=t[e]<<16;n+=s.charAt(o>>18&63)+s.charAt(o>>12&63)}else if(c===2){const o=t[e]<<16|t[e+1]<<8;n+=s.charAt(o>>18&63)+s.charAt(o>>12&63)+s.charAt(o>>6&63)}return n},_=t=>{const n=t.replaceAll("-","+").replaceAll("_","/"),e=n+"=".repeat((4-n.length%4)%4);return d(e)},A=(t,n)=>{if(t.size<n)return;const e=t.keys().next().value;e!==void 0&&t.delete(e)},f=(t,n,e,r)=>{const c=t.get(n);if(c!==void 0)return c;A(t,r);const o=e().catch(h=>{throw t.get(n)===o&&t.delete(n),h});return t.set(n,o),o},a=new TextEncoder,w=10080*60,g=/^[a-z][a-z0-9+\-.]*:\/\//i,y=Array.from({length:32},(t,n)=>n),i=new RegExp(`[${y.map(t=>String.fromCodePoint(t)).join("")}]`,"u"),C=64,S=new Map,l=async t=>f(S,t,async()=>crypto.subtle.importKey("raw",a.encode(t),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]),C),m=t=>{try{return new URL(t).host}catch{return t.replace(g,"").split("/")[0]??""}},H=t=>{if(i.test(t))throw new TypeError("hmac-url: value must not contain control characters (including CR/LF)")},R=t=>i.test(t),E=/^\/+$/u,x=t=>E.test(t),L=(t,n)=>{if(!Number.isFinite(t)||t<=0)return"expiresInSeconds must be a positive finite number";if(t>n){const e=` (${String(n/86400)} days)`;return`expiresInSeconds must not exceed ${String(n)} seconds${e}`}},T=async(t,n)=>{const e=await l(t),r=await crypto.subtle.sign("HMAC",e,a.encode(n));return u(new Uint8Array(r))},O=async(t,n,e)=>{const r=await l(t);return crypto.subtle.verify("HMAC",r,e,a.encode(n))},v=t=>{const n=new Uint8Array(t);let e="";for(const r of n)e+=r.toString(16).padStart(2,"0");return e},M=t=>{let n=t.length;for(;n>0&&t[n-1]==="/";)n-=1;return t.slice(0,n)};export{w as M,p as a,v as b,H as c,O as d,m as e,_ as f,R as h,x as i,T as s,M as t,L as v};