@lunora/storage 1.0.0-alpha.16 → 1.0.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/buildSignedUrl-DNIwBOK4.mjs +1 -0
- package/dist/packem_shared/{createStorage-BDKKnFcV.mjs → createStorage-LbyGLi4F.mjs} +1 -1
- package/dist/packem_shared/signed-url-Dm2EFS18.mjs +5 -0
- package/package.json +3 -3
- package/dist/packem_shared/buildSignedUrl-CT6IDyA5.mjs +0 -1
- package/dist/packem_shared/signed-url-4m6WhCR1.mjs +0 -5
package/dist/index.d.mts
CHANGED
|
@@ -240,8 +240,11 @@ declare const buildPresignedUrl: (parameters: PresignedUrlParams) => Promise<str
|
|
|
240
240
|
* the signing secret MUST NOT be shared across buckets/tenants — host binding
|
|
241
241
|
* narrows replay surface but is not a substitute for per-tenant key isolation.
|
|
242
242
|
*
|
|
243
|
-
* The Worker handling
|
|
244
|
-
*
|
|
243
|
+
* The Worker route handling the signed download/upload (mounted at
|
|
244
|
+
* `publicBaseUrl`'s origin, e.g. `GET /:key`) should call
|
|
245
|
+
* {@link verifySignedUrl} to validate the signature + expiry before streaming
|
|
246
|
+
* the R2 body. `baseUrl` must be a bare origin (no path) — see the module
|
|
247
|
+
* docstring.
|
|
245
248
|
*/
|
|
246
249
|
declare const buildSignedUrl: (args: SignedUrlOptions & {
|
|
247
250
|
baseUrl: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -240,8 +240,11 @@ declare const buildPresignedUrl: (parameters: PresignedUrlParams) => Promise<str
|
|
|
240
240
|
* the signing secret MUST NOT be shared across buckets/tenants — host binding
|
|
241
241
|
* narrows replay surface but is not a substitute for per-tenant key isolation.
|
|
242
242
|
*
|
|
243
|
-
* The Worker handling
|
|
244
|
-
*
|
|
243
|
+
* The Worker route handling the signed download/upload (mounted at
|
|
244
|
+
* `publicBaseUrl`'s origin, e.g. `GET /:key`) should call
|
|
245
|
+
* {@link verifySignedUrl} to validate the signature + expiry before streaming
|
|
246
|
+
* the R2 body. `baseUrl` must be a bare origin (no path) — see the module
|
|
247
|
+
* docstring.
|
|
245
248
|
*/
|
|
246
249
|
declare const buildSignedUrl: (args: SignedUrlOptions & {
|
|
247
250
|
baseUrl: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createBucketStorage as o}from"./packem_shared/createBucketStorage-DAip6cEw.mjs";import{createStorage as a,scopeKey as i}from"./packem_shared/createStorage-
|
|
1
|
+
import{createBucketStorage as o}from"./packem_shared/createBucketStorage-DAip6cEw.mjs";import{createStorage as a,scopeKey as i}from"./packem_shared/createStorage-LbyGLi4F.mjs";import{buildPresignedUrl as f}from"./packem_shared/buildPresignedUrl-TCv-5TAT.mjs";import{b as l,v as p}from"./packem_shared/signed-url-Dm2EFS18.mjs";export{f as buildPresignedUrl,l as buildSignedUrl,o as createBucketStorage,a as createStorage,i as scopeKey,p as verifySignedUrl};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"@lunora/errors";import{b as d,v as l}from"./signed-url-Dm2EFS18.mjs";import"./internal-DpjlEJPE.mjs";export{d as buildSignedUrl,l as verifySignedUrl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as a}from"@lunora/errors";import{b as R,d as T}from"./signed-url-
|
|
1
|
+
import{LunoraError as a}from"@lunora/errors";import{b as R,d as T}from"./signed-url-Dm2EFS18.mjs";import{t as k,a as f}from"./internal-DpjlEJPE.mjs";import{buildPresignedUrl as I}from"./buildPresignedUrl-TCv-5TAT.mjs";const m=1024,A=1e3,U=100,b=e=>{const n=new Uint8Array(e);let i="";for(const p of n)i+=String.fromCodePoint(p);return btoa(i)},S=e=>{const n=e.checksums?.sha256;if(n===void 0)return e;const i=f(n),p=b(n);return new Proxy(e,{get(u,s){if(s==="sha256")return i;if(s==="sha256Base64")return p;const l=Reflect.get(u,s,u);return typeof l=="function"?l.bind(u):l},has(u,s){return s==="sha256"||s==="sha256Base64"||Reflect.has(u,s)}})},w=e=>{const n=e.checksums?.sha256;return{contentType:e.httpMetadata?.contentType,customMetadata:e.customMetadata,key:e.key,sha256:n===void 0?void 0:f(n),size:e.size,uploaded:e.uploaded===void 0?void 0:e.uploaded.getTime()}},M=e=>{const n=e.checksums?.sha256;return{checksums:e.checksums,customMetadata:e.customMetadata,etag:e.etag,httpEtag:e.httpEtag,httpMetadata:e.httpMetadata,key:e.key,sha256:n===void 0?void 0:f(n),sha256Base64:n===void 0?void 0:b(n),size:e.size,uploaded:e.uploaded}},N=(e,n)=>{let i=0;const p=s=>s instanceof ArrayBuffer||ArrayBuffer.isView(s)?s.byteLength:void 0,u=new TransformStream({transform(s,l){const h=p(s);if(h===void 0){l.error(new Error("@lunora/storage: stream chunk is not a byte chunk; cannot enforce maxSize"));return}if(i+=h,i>n){l.error(new Error(`@lunora/storage: stream body exceeds maxSize (> ${String(n)} bytes)`));return}l.enqueue(s)}});return e.pipeThrough(u)},c=e=>{if(typeof e!="string"||e.length===0)throw new a("VALIDATION_ERROR","@lunora/storage: key must be a non-empty string");if(e.length>m)throw new a("VALIDATION_ERROR",`@lunora/storage: key exceeds ${String(m)}-byte limit`);if(e.includes("\0"))throw new a("VALIDATION_ERROR","@lunora/storage: key contains NUL byte");if(T(e))throw new a("VALIDATION_ERROR","@lunora/storage: key contains a control character (including CR/LF)");if(e.startsWith("/"))throw new a("VALIDATION_ERROR","@lunora/storage: key must not start with `/`");const n=e.split("/");for(const i of n)if(i==="..")throw new a("VALIDATION_ERROR","@lunora/storage: key contains a `..` path component")},v=(e,n)=>{c(e),c(n);const i=`${e.endsWith("/")?e.slice(0,-1):e}/${n}`;if(i.length>m)throw new a("VALIDATION_ERROR",`@lunora/storage: scoped key exceeds ${String(m)}-byte limit`);return i},B=e=>{if(!e.bucket)throw new a("INTERNAL","@lunora/storage: `bucket` is required");const n=async(r,t,o={})=>{if(c(r),o.allowedContentTypes!==void 0){if(o.contentType===void 0)throw new a("VALIDATION_ERROR","@lunora/storage: contentType is required when allowedContentTypes is set");if(!o.allowedContentTypes.includes(o.contentType))throw new a("VALIDATION_ERROR",`@lunora/storage: contentType "${o.contentType}" not in allowedContentTypes`)}let y=t;if(typeof o.maxSize=="number"){let g;if(t instanceof ArrayBuffer?g=t.byteLength:t instanceof Blob&&(g=t.size),g!==void 0&&g>o.maxSize)throw new a("PAYLOAD_TOO_LARGE",`@lunora/storage: body exceeds maxSize (${String(g)} > ${String(o.maxSize)})`);t instanceof ReadableStream&&(y=N(t,o.maxSize))}const d=await e.bucket.put(r,y,{customMetadata:o.customMetadata,httpMetadata:o.contentType?{contentType:o.contentType}:void 0});return{etag:d.etag,httpEtag:d.httpEtag??`"${d.etag}"`,key:d.key}},i=async(r,t={})=>{c(r);const o=await(t.range?e.bucket.get(r,{range:t.range}):e.bucket.get(r));return o&&S(o)},p=async r=>{c(r),await e.bucket.delete(r)},u=async r=>{if(c(r),e.bucket.head){const o=await e.bucket.head(r);return o&&w(o)}const t=await e.bucket.get(r,{range:{length:0}});return t&&w(t)},s=async(r,t={})=>{if(r?.includes("\0"))throw new a("VALIDATION_ERROR","@lunora/storage: prefix contains NUL byte");const o=t.limit??U,y=Math.min(Math.max(1,Math.floor(o)),A),d=await e.bucket.list({cursor:t.cursor,delimiter:t.delimiter,limit:y,prefix:r});return{cursor:d.cursor,objects:d.objects.map(g=>M(g)),truncated:d.truncated}},l=r=>{if(!e.publicBaseUrl)throw new a("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getUrl()");c(r);const t=r.split("/").map(o=>encodeURIComponent(o)).join("/");return`${k(e.publicBaseUrl)}/${t}`},h=async(r,t={})=>{if(!e.publicBaseUrl)throw new a("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getSignedUrl()");if(!e.signingSecret)throw new a("INTERNAL","@lunora/storage: `signingSecret` is required for getSignedUrl()");return c(r),R({baseUrl:e.publicBaseUrl,contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method,secret:e.signingSecret})};return{createMultipartUpload:async(r,t={})=>{if(c(r),!e.bucket.createMultipartUpload)throw new a("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})},delete:p,download:i,generateUploadUrl:async(r,t={})=>h(r,{contentType:t.contentType,expiresInSeconds:t.expiresInSeconds,method:"PUT"}),getMetadata:u,getPresignedUrl:async(r,t={})=>{if(!e.s3)throw new a("INTERNAL","@lunora/storage: `s3` credentials are required for getPresignedUrl() — pass { accountId, accessKeyId, secretAccessKey, bucket }");return c(r),I({credentials:e.s3,expiresInSeconds:t.expiresInSeconds,key:r,method:t.method})},getSignedUrl:h,getUrl:l,list:s,resumeMultipartUpload:(r,t)=>{if(c(r),typeof t!="string"||t.length===0)throw new a("VALIDATION_ERROR","@lunora/storage: resumeMultipartUpload requires a non-empty uploadId");if(!e.bucket.resumeMultipartUpload)throw new a("INTERNAL","@lunora/storage: bucket binding does not support multipart uploads (resumeMultipartUpload)");return e.bucket.resumeMultipartUpload(r,t)},store:async(r,t,o={})=>n(r,t,o),upload:n}};export{B as createStorage,v as scopeKey};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";import{t as R}from"./internal-DpjlEJPE.mjs";const U=(e,r)=>{if(e.size<r)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},f=new TextEncoder,h=10080*60,$=/^[a-z][a-z0-9+\-.]*:\/\//i,x=Array.from({length:32},(e,r)=>r),y=new RegExp(`[${x.map(e=>String.fromCodePoint(e)).join("")}]`,"u"),I=e=>{const r=String.fromCodePoint(...e);return btoa(r).replaceAll("+","-").replaceAll("/","_").replaceAll("=","")},L=e=>{const r=e.replaceAll("-","+").replaceAll("_","/")+"===".slice((e.length+3)%4),t=atob(r),n=new Uint8Array(t.length);for(let a=0;a<t.length;a+=1)n[a]=t.codePointAt(a)??0;return n},T=64,p=new Map,g=async e=>{const r=p.get(e);if(r)return r;U(p,T);const t=crypto.subtle.importKey("raw",f.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]);return p.set(e,t),t},w=e=>{try{return new URL(e).host}catch{return e.replace($,"").split("/")[0]??""}},v=e=>{if(y.test(e))throw new TypeError("hmac-url: value must not contain control characters (including CR/LF)")},H=e=>y.test(e),S=async(e,r)=>{const t=await g(e),n=await crypto.subtle.sign("HMAC",t,f.encode(r));return I(new Uint8Array(n))},C=async(e,r,t)=>{const n=await g(e);return crypto.subtle.verify("HMAC",n,t,f.encode(r))},E=/^\//,N=/^\/+$/u,b=(e,r,t,n,a)=>{const o=`${e}
|
|
2
|
+
${r.toLowerCase()}
|
|
3
|
+
${t}
|
|
4
|
+
${String(n)}`;return a===void 0?o:`${o}
|
|
5
|
+
${a}`},M=async e=>{const r=e.method??"GET",t=e.expiresInSeconds??3600;if(!Number.isFinite(t)||t<=0)throw new u("VALIDATION_ERROR","@lunora/storage: expiresInSeconds must be a positive finite number");if(t>h)throw new u("VALIDATION_ERROR",`@lunora/storage: expiresInSeconds must not exceed ${String(h)} (7 days)`);const n=r==="PUT"?e.contentType:void 0;let a="";try{a=new URL(e.baseUrl).pathname}catch{}if(a!==""&&!N.test(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`);v(e.key);const o=Math.floor(Date.now()/1e3)+t,l=w(e.baseUrl),s=await S(e.secret,b(r,l,e.key,o,n)),c=R(e.baseUrl),i=e.key.split("/").map(m=>encodeURIComponent(m)).join("/"),d=n===void 0?"":`&ct=${encodeURIComponent(n)}`;return`${c}/${i}?exp=${String(o)}&method=${r}&sig=${s}${d}`},O=async(e,r,t)=>{let n;try{n=e instanceof URL?e:new URL(e)}catch{return{reason:"malformed",valid:!1}}const a=n.searchParams.get("exp"),o=a===null?Number.NaN:Number(a),l=n.searchParams.get("sig"),s=n.searchParams.get("method")??"GET",c=n.searchParams.get("ct")??void 0;if(!l||!Number.isInteger(o))return{reason:"malformed",valid:!1};if(o<Math.floor(Date.now()/1e3))return{reason:"expired",valid:!1};if(s!=="GET"&&s!=="PUT")return{reason:"malformed",valid:!1};let i,d;try{i=n.pathname.replace(E,"").split("/").map(A=>decodeURIComponent(A)).join("/"),v(i),d=L(l)}catch{return{reason:"malformed",valid:!1}}const m=t?.expectedHost===void 0?n.host:w(t.expectedHost);return await C(r,b(s,m,i,o,c),d)?{contentType:c,key:i,method:s,valid:!0}:{reason:"bad_signature",valid:!1}};export{M as b,H as d,O as v};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/storage",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.18",
|
|
4
4
|
"description": "R2-backed storage for Lunora: typed buckets and signed URLs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
54
|
-
"@lunora/platform": "1.0.0-alpha.
|
|
53
|
+
"@lunora/errors": "1.0.0-alpha.12",
|
|
54
|
+
"@lunora/platform": "1.0.0-alpha.4",
|
|
55
55
|
"@visulima/storage": "1.0.5",
|
|
56
56
|
"aws4fetch": "1.0.20"
|
|
57
57
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"@lunora/errors";import{b as d,v as l}from"./signed-url-4m6WhCR1.mjs";import"./internal-DpjlEJPE.mjs";export{d as buildSignedUrl,l as verifySignedUrl};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{LunoraError as p}from"@lunora/errors";import{t as b}from"./internal-DpjlEJPE.mjs";const R=(e,r)=>{if(e.size<r)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},u=new TextEncoder,f=10080*60,U=/^[a-z][a-z0-9+\-.]*:\/\//i,x=Array.from({length:32},(e,r)=>r),g=new RegExp(`[${x.map(e=>String.fromCodePoint(e)).join("")}]`,"u"),T=e=>{const r=String.fromCodePoint(...e);return btoa(r).replaceAll("+","-").replaceAll("/","_").replaceAll("=","")},S=e=>{const r=e.replaceAll("-","+").replaceAll("_","/")+"===".slice((e.length+3)%4),t=atob(r),n=new Uint8Array(t.length);for(let o=0;o<t.length;o+=1)n[o]=t.codePointAt(o)??0;return n},C=64,m=new Map,h=async e=>{const r=m.get(e);if(r)return r;R(m,C);const t=crypto.subtle.importKey("raw",u.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]);return m.set(e,t),t},y=e=>{try{return new URL(e).host}catch{return e.replace(U,"").split("/")[0]??""}},w=e=>{if(g.test(e))throw new TypeError("hmac-url: value must not contain control characters (including CR/LF)")},H=e=>g.test(e),I=async(e,r)=>{const t=await h(e),n=await crypto.subtle.sign("HMAC",t,u.encode(r));return T(new Uint8Array(n))},E=async(e,r,t)=>{const n=await h(e);return crypto.subtle.verify("HMAC",n,t,u.encode(r))},P=/^\//,v=(e,r,t,n,o)=>{const a=`${e}
|
|
2
|
-
${r.toLowerCase()}
|
|
3
|
-
${t}
|
|
4
|
-
${String(n)}`;return o===void 0?a:`${a}
|
|
5
|
-
${o}`},M=async e=>{const r=e.method??"GET",t=e.expiresInSeconds??3600;if(!Number.isFinite(t)||t<=0)throw new p("VALIDATION_ERROR","@lunora/storage: expiresInSeconds must be a positive finite number");if(t>f)throw new p("VALIDATION_ERROR",`@lunora/storage: expiresInSeconds must not exceed ${String(f)} (7 days)`);const n=r==="PUT"?e.contentType:void 0;w(e.key);const o=Math.floor(Date.now()/1e3)+t,a=y(e.baseUrl),c=await I(e.secret,v(r,a,e.key,o,n)),s=b(e.baseUrl),l=e.key.split("/").map(d=>encodeURIComponent(d)).join("/"),i=n===void 0?"":`&ct=${encodeURIComponent(n)}`;return`${s}/${l}?exp=${String(o)}&method=${r}&sig=${c}${i}`},k=async(e,r,t)=>{let n;try{n=e instanceof URL?e:new URL(e)}catch{return{reason:"malformed",valid:!1}}const o=n.searchParams.get("exp"),a=o===null?Number.NaN:Number(o),c=n.searchParams.get("sig"),s=n.searchParams.get("method")??"GET",l=n.searchParams.get("ct")??void 0;if(!c||!Number.isInteger(a))return{reason:"malformed",valid:!1};if(a<Math.floor(Date.now()/1e3))return{reason:"expired",valid:!1};if(s!=="GET"&&s!=="PUT")return{reason:"malformed",valid:!1};let i,d;try{i=n.pathname.replace(P,"").split("/").map($=>decodeURIComponent($)).join("/"),w(i),d=S(c)}catch{return{reason:"malformed",valid:!1}}const A=t?.expectedHost===void 0?n.host:y(t.expectedHost);return await E(r,v(s,A,i,a,l),d)?{contentType:l,key:i,method:s,valid:!0}:{reason:"bad_signature",valid:!1}};export{M as b,H as d,k as v};
|