@lunora/storage 1.0.0-alpha.48 → 1.0.0-alpha.49

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-D9fBoaEi.mjs";import{createStorage as i,scopeKey as d}from"./packem_shared/createStorage-Ck4vcbE_.mjs";import{buildPresignedUrl as g}from"./packem_shared/buildPresignedUrl-U55ktL8x.mjs";import{buildSignedUrl as p,verifySignedUrl as a}from"./packem_shared/buildSignedUrl-BnUO-9Fl.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-D9fBoaEi.mjs";import{createStorage as i,scopeKey as d}from"./packem_shared/createStorage-B-E6dH3Q.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};
@@ -0,0 +1,6 @@
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}
2
+ ${s.toLowerCase()}
3
+ ${r}
4
+ ${t}
5
+ ${String(o)}`;return a===void 0?n:`${n}
6
+ ${a}`},x=async e=>{const s=e.method??"GET",r=e.expiresInSeconds??3600,t=I(r,$);if(t!==void 0)throw new f("VALIDATION_ERROR",`@lunora/storage: ${t}`);const o=s==="PUT"?e.contentType:void 0;let a="";try{a=new URL(e.baseUrl).pathname}catch{}if(a!==""&&!y(a))throw new f("VALIDATION_ERROR",`@lunora/storage: baseUrl must not carry a path ("${a}") — the key is verified from the full URL pathname, so a subpath base would make every signed URL fail verification`);if(u(e.key),u(e.bucketName),e.bucketName==="")throw new f("VALIDATION_ERROR","@lunora/storage: bucketName must not be empty");if(e.key==="")throw new f("VALIDATION_ERROR","@lunora/storage: key must not be empty");const n=Math.floor(Date.now()/1e3)+r,c=p(e.baseUrl),i=await v(e.secret,b(s,c,e.bucketName,e.key,n,o)),m=U(e.baseUrl),l=e.key.split("/").map(h=>encodeURIComponent(h)).join("/"),d=o===void 0?"":`&ct=${encodeURIComponent(o)}`;return`${m}/${l}?exp=${String(n)}&method=${s}&bucket=${encodeURIComponent(e.bucketName)}&sig=${i}${d}`},E=async(e,s,r)=>{let t;try{t=e instanceof URL?e:new URL(e)}catch{return{reason:"malformed",valid:!1}}const o=t.searchParams.get("exp"),a=o===null?Number.NaN:Number(o),n=t.searchParams.get("sig"),c=t.searchParams.get("method")??"GET",i=t.searchParams.get("bucket"),m=t.searchParams.get("ct")??void 0;if(!n||!i||!Number.isInteger(a))return{reason:"malformed",valid:!1};if(a<Math.floor(Date.now()/1e3))return{reason:"expired",valid:!1};if(c!=="GET"&&c!=="PUT")return{reason:"malformed",valid:!1};let l,d;try{l=t.pathname.replace(k,"").split("/").map(R=>decodeURIComponent(R)).join("/"),u(l),u(i),d=N(n)}catch{return{reason:"malformed",valid:!1}}const h=r?.expectedHost===void 0?t.host:p(r.expectedHost);return await w(s,b(c,h,i,l,a,m),d)?{bucketName:i,contentType:m,key:l,method:c,valid:!0}:{reason:"bad_signature",valid:!1}};export{x as buildSignedUrl,E as verifySignedUrl};
@@ -1 +1 @@
1
- import{LunoraError as n}from"@lunora/errors";import{t as U,h as M,a as I,b as T}from"./internal-B5yyxF10.mjs";import{buildPresignedUrl as p}from"./buildPresignedUrl-U55ktL8x.mjs";import{buildSignedUrl as k}from"./buildSignedUrl-BnUO-9Fl.mjs";const w=1024,x=1e3,_=100,v=e=>{const a=e.checksums?.sha256;if(a===void 0)return e;const h=T(a),l=I(new Uint8Array(a));return new Proxy(e,{get(i,s){if(s==="sha256")return h;if(s==="sha256Base64")return l;const u=Reflect.get(i,s,i);return typeof u=="function"?u.bind(i):u},has(i,s){return s==="sha256"||s==="sha256Base64"||Reflect.has(i,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:T(a),size:e.size,uploaded:e.uploaded===void 0?void 0:e.uploaded.getTime()}},R=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:T(a),sha256Base64:a===void 0?void 0:I(new Uint8Array(a)),size:e.size,uploaded:e.uploaded}},D=(e,a)=>{let h=0;const l=s=>s instanceof ArrayBuffer||ArrayBuffer.isView(s)?s.byteLength:void 0,i=new TransformStream({transform(s,u){const m=l(s);if(m===void 0){u.error(new Error("@lunora/storage: stream chunk is not a byte chunk; cannot enforce maxSize"));return}if(h+=m,h>a){u.error(new Error(`@lunora/storage: stream body exceeds maxSize (> ${String(a)} bytes)`));return}u.enqueue(s)}});return e.pipeThrough(i)},z=new TextEncoder,A=e=>z.encode(e).length,o=e=>{if(typeof e!="string"||e.length===0)throw new n("VALIDATION_ERROR","@lunora/storage: key must be a non-empty string");if(A(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")},V=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`)}},O=(e,a)=>{o(e),o(a);const l=`${e.endsWith("/")?e.slice(0,-1):e}/${a}`;if(A(l)>w)throw new n("VALIDATION_ERROR",`@lunora/storage: scoped key exceeds ${String(w)}-byte limit`);return l},K=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,c={})=>{o(r),V(c);let f=t;if(typeof c.maxSize=="number"){let g;if(t instanceof ArrayBuffer?g=t.byteLength:t instanceof Blob&&(g=t.size),g!==void 0&&g>c.maxSize)throw new n("PAYLOAD_TOO_LARGE",`@lunora/storage: body exceeds maxSize (${String(g)} > ${String(c.maxSize)})`);t instanceof ReadableStream&&(f=D(t,c.maxSize))}const d=await e.bucket.put(r,f,{customMetadata:c.customMetadata,httpMetadata:c.contentType?{contentType:c.contentType}:void 0,...c.sha256===void 0?{}:{sha256:c.sha256}});return{etag:d.etag,httpEtag:d.httpEtag??`"${d.etag}"`,key:d.key}},h=async(r,t={})=>{o(r);const c=await(t.range?e.bucket.get(r,{range:t.range}):e.bucket.get(r));return c&&v(c)},l=async r=>{o(r),await e.bucket.delete(r)},i=async r=>{o(r);const t=e.bucket.head?await e.bucket.head(r):await e.bucket.get(r,{range:{length:0}});return t&&R(t)},s=async r=>{const t=await i(r);return t&&B(t)},u=async(r,t={})=>{if(r?.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: prefix contains NUL byte");const c=t.limit??_,f=Math.min(Math.max(1,Math.floor(c)),x),d=await e.bucket.list({cursor:t.cursor,delimiter:t.delimiter,limit:f,prefix:r}),{delimitedPrefixes:g}=d;return{cursor:d.cursor,delimitedPrefixes:g,objects:d.objects.map(S=>R(S)),truncated:d.truncated}},m=r=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getUrl()");o(r);const t=r.split("/").map(c=>encodeURIComponent(c)).join("/");return`${U(e.publicBaseUrl)}/${t}`},y=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 o(r),k({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(o(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(o(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 o(r),p({credentials:e.s3,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method})},E=async(r,t={})=>y(r,{contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,method:"PUT"});return{bucketName:e.bucketName,createMultipartUpload:b,delete:l,download:h,generateUploadUrl:E,getMetadata:s,getPresignedUrl:L,getSignedUrl:y,getUrl:m,head:i,list:u,resumeMultipartUpload:N,store:a,upload:a}};export{K as createStorage,O as scopeKey};
1
+ import{LunoraError as n}from"@lunora/errors";import{t as U,h as M,a as I,b as T}from"./internal-B5yyxF10.mjs";import{buildPresignedUrl as p}from"./buildPresignedUrl-U55ktL8x.mjs";import{buildSignedUrl as k}from"./buildSignedUrl-czz1xjLU.mjs";const w=1024,x=1e3,_=100,v=e=>{const a=e.checksums?.sha256;if(a===void 0)return e;const h=T(a),l=I(new Uint8Array(a));return new Proxy(e,{get(i,s){if(s==="sha256")return h;if(s==="sha256Base64")return l;const u=Reflect.get(i,s,i);return typeof u=="function"?u.bind(i):u},has(i,s){return s==="sha256"||s==="sha256Base64"||Reflect.has(i,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:T(a),size:e.size,uploaded:e.uploaded===void 0?void 0:e.uploaded.getTime()}},R=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:T(a),sha256Base64:a===void 0?void 0:I(new Uint8Array(a)),size:e.size,uploaded:e.uploaded}},D=(e,a)=>{let h=0;const l=s=>s instanceof ArrayBuffer||ArrayBuffer.isView(s)?s.byteLength:void 0,i=new TransformStream({transform(s,u){const m=l(s);if(m===void 0){u.error(new Error("@lunora/storage: stream chunk is not a byte chunk; cannot enforce maxSize"));return}if(h+=m,h>a){u.error(new Error(`@lunora/storage: stream body exceeds maxSize (> ${String(a)} bytes)`));return}u.enqueue(s)}});return e.pipeThrough(i)},z=new TextEncoder,A=e=>z.encode(e).length,o=e=>{if(typeof e!="string"||e.length===0)throw new n("VALIDATION_ERROR","@lunora/storage: key must be a non-empty string");if(A(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")},V=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`)}},O=(e,a)=>{o(e),o(a);const l=`${e.endsWith("/")?e.slice(0,-1):e}/${a}`;if(A(l)>w)throw new n("VALIDATION_ERROR",`@lunora/storage: scoped key exceeds ${String(w)}-byte limit`);return l},K=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,c={})=>{o(r),V(c);let f=t;if(typeof c.maxSize=="number"){let g;if(t instanceof ArrayBuffer?g=t.byteLength:t instanceof Blob&&(g=t.size),g!==void 0&&g>c.maxSize)throw new n("PAYLOAD_TOO_LARGE",`@lunora/storage: body exceeds maxSize (${String(g)} > ${String(c.maxSize)})`);t instanceof ReadableStream&&(f=D(t,c.maxSize))}const d=await e.bucket.put(r,f,{customMetadata:c.customMetadata,httpMetadata:c.contentType?{contentType:c.contentType}:void 0,...c.sha256===void 0?{}:{sha256:c.sha256}});return{etag:d.etag,httpEtag:d.httpEtag??`"${d.etag}"`,key:d.key}},h=async(r,t={})=>{o(r);const c=await(t.range?e.bucket.get(r,{range:t.range}):e.bucket.get(r));return c&&v(c)},l=async r=>{o(r),await e.bucket.delete(r)},i=async r=>{o(r);const t=e.bucket.head?await e.bucket.head(r):await e.bucket.get(r,{range:{length:0}});return t&&R(t)},s=async r=>{const t=await i(r);return t&&B(t)},u=async(r,t={})=>{if(r?.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: prefix contains NUL byte");const c=t.limit??_,f=Math.min(Math.max(1,Math.floor(c)),x),d=await e.bucket.list({cursor:t.cursor,delimiter:t.delimiter,limit:f,prefix:r}),{delimitedPrefixes:g}=d;return{cursor:d.cursor,delimitedPrefixes:g,objects:d.objects.map(S=>R(S)),truncated:d.truncated}},m=r=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getUrl()");o(r);const t=r.split("/").map(c=>encodeURIComponent(c)).join("/");return`${U(e.publicBaseUrl)}/${t}`},y=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 o(r),k({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(o(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(o(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 o(r),p({credentials:e.s3,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method})},E=async(r,t={})=>y(r,{contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,method:"PUT"});return{bucketName:e.bucketName,createMultipartUpload:b,delete:l,download:h,generateUploadUrl:E,getMetadata:s,getPresignedUrl:L,getSignedUrl:y,getUrl:m,head:i,list:u,resumeMultipartUpload:N,store:a,upload:a}};export{K as createStorage,O as scopeKey};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/storage",
3
- "version": "1.0.0-alpha.48",
3
+ "version": "1.0.0-alpha.49",
4
4
  "description": "R2-backed storage for Lunora: typed buckets and signed URLs",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1,6 +0,0 @@
1
- import{LunoraError as u}from"@lunora/errors";import{i as R,c as f,e as p,s as y,t as U,f as N,d as $,v as w,M as I}from"./internal-B5yyxF10.mjs";const S=/^\//,b=(e,s,r,t,o,a)=>{const n=`${e}
2
- ${s.toLowerCase()}
3
- ${r}
4
- ${t}
5
- ${String(o)}`;return a===void 0?n:`${n}
6
- ${a}`},x=async e=>{const s=e.method??"GET",r=e.expiresInSeconds??3600,t=w(r,I);if(t!==void 0)throw new u("VALIDATION_ERROR",`@lunora/storage: ${t}`);const o=s==="PUT"?e.contentType:void 0;let a="";try{a=new URL(e.baseUrl).pathname}catch{}if(a!==""&&!R(a))throw new u("VALIDATION_ERROR",`@lunora/storage: baseUrl must not carry a path ("${a}") — the key is verified from the full URL pathname, so a subpath base would make every signed URL fail verification`);if(f(e.key),f(e.bucketName),e.bucketName==="")throw new u("VALIDATION_ERROR","@lunora/storage: bucketName must not be empty");const n=Math.floor(Date.now()/1e3)+r,c=p(e.baseUrl),i=await y(e.secret,b(s,c,e.bucketName,e.key,n,o)),m=U(e.baseUrl),l=e.key.split("/").map(h=>encodeURIComponent(h)).join("/"),d=o===void 0?"":`&ct=${encodeURIComponent(o)}`;return`${m}/${l}?exp=${String(n)}&method=${s}&bucket=${encodeURIComponent(e.bucketName)}&sig=${i}${d}`},E=async(e,s,r)=>{let t;try{t=e instanceof URL?e:new URL(e)}catch{return{reason:"malformed",valid:!1}}const o=t.searchParams.get("exp"),a=o===null?Number.NaN:Number(o),n=t.searchParams.get("sig"),c=t.searchParams.get("method")??"GET",i=t.searchParams.get("bucket"),m=t.searchParams.get("ct")??void 0;if(!n||!i||!Number.isInteger(a))return{reason:"malformed",valid:!1};if(a<Math.floor(Date.now()/1e3))return{reason:"expired",valid:!1};if(c!=="GET"&&c!=="PUT")return{reason:"malformed",valid:!1};let l,d;try{l=t.pathname.replace(S,"").split("/").map(v=>decodeURIComponent(v)).join("/"),f(l),f(i),d=N(n)}catch{return{reason:"malformed",valid:!1}}const h=r?.expectedHost===void 0?t.host:p(r.expectedHost);return await $(s,b(c,h,i,l,a,m),d)?{bucketName:i,contentType:m,key:l,method:c,valid:!0}:{reason:"bad_signature",valid:!1}};export{x as buildSignedUrl,E as verifySignedUrl};