@lunora/storage 1.0.0-alpha.21 → 1.0.0-alpha.23
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
CHANGED
|
@@ -52,6 +52,15 @@ interface UploadOptions {
|
|
|
52
52
|
* unbounded stream.
|
|
53
53
|
*/
|
|
54
54
|
maxSize?: number;
|
|
55
|
+
/**
|
|
56
|
+
* SHA-256 of the body (hex or a 32-byte buffer), recorded with the object.
|
|
57
|
+
*
|
|
58
|
+
* R2 only reports a checksum it was given: without this, `list()`/`head()`
|
|
59
|
+
* return no `sha256` and any later integrity check degrades to comparing
|
|
60
|
+
* sizes. R2 also verifies the digest itself on write, so supplying it turns
|
|
61
|
+
* the upload into a checked one.
|
|
62
|
+
*/
|
|
63
|
+
sha256?: ArrayBuffer | string;
|
|
55
64
|
}
|
|
56
65
|
interface ListOptions {
|
|
57
66
|
cursor?: string;
|
|
@@ -202,12 +211,6 @@ interface BucketStorage extends Storage {
|
|
|
202
211
|
declare const createBucketStorage: (buckets: Record<string, Storage>, options?: {
|
|
203
212
|
default?: string;
|
|
204
213
|
}) => BucketStorage;
|
|
205
|
-
/**
|
|
206
|
-
* Compose a per-tenant key from a scope prefix and a caller-supplied key.
|
|
207
|
-
* Both halves are validated — the prefix may not contain `..` or NUL either,
|
|
208
|
-
* and the resulting key must stay under R2's length ceiling. Recommended for
|
|
209
|
-
* any multi-tenant deployment so client-supplied keys can't address peer data.
|
|
210
|
-
*/
|
|
211
214
|
declare const scopeKey: (prefix: string, key: string) => string;
|
|
212
215
|
declare const createStorage: (options: LunoraStorageOptions) => Storage;
|
|
213
216
|
/** Parameters accepted by {@link buildPresignedUrl}. */
|
package/dist/index.d.ts
CHANGED
|
@@ -52,6 +52,15 @@ interface UploadOptions {
|
|
|
52
52
|
* unbounded stream.
|
|
53
53
|
*/
|
|
54
54
|
maxSize?: number;
|
|
55
|
+
/**
|
|
56
|
+
* SHA-256 of the body (hex or a 32-byte buffer), recorded with the object.
|
|
57
|
+
*
|
|
58
|
+
* R2 only reports a checksum it was given: without this, `list()`/`head()`
|
|
59
|
+
* return no `sha256` and any later integrity check degrades to comparing
|
|
60
|
+
* sizes. R2 also verifies the digest itself on write, so supplying it turns
|
|
61
|
+
* the upload into a checked one.
|
|
62
|
+
*/
|
|
63
|
+
sha256?: ArrayBuffer | string;
|
|
55
64
|
}
|
|
56
65
|
interface ListOptions {
|
|
57
66
|
cursor?: string;
|
|
@@ -202,12 +211,6 @@ interface BucketStorage extends Storage {
|
|
|
202
211
|
declare const createBucketStorage: (buckets: Record<string, Storage>, options?: {
|
|
203
212
|
default?: string;
|
|
204
213
|
}) => BucketStorage;
|
|
205
|
-
/**
|
|
206
|
-
* Compose a per-tenant key from a scope prefix and a caller-supplied key.
|
|
207
|
-
* Both halves are validated — the prefix may not contain `..` or NUL either,
|
|
208
|
-
* and the resulting key must stay under R2's length ceiling. Recommended for
|
|
209
|
-
* any multi-tenant deployment so client-supplied keys can't address peer data.
|
|
210
|
-
*/
|
|
211
214
|
declare const scopeKey: (prefix: string, key: string) => string;
|
|
212
215
|
declare const createStorage: (options: LunoraStorageOptions) => Storage;
|
|
213
216
|
/** Parameters accepted by {@link buildPresignedUrl}. */
|
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-DBrCSt6G.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{LunoraError as n}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 o=new Uint8Array(e);let i="";for(const p of o)i+=String.fromCodePoint(p);return btoa(i)},S=e=>{const o=e.checksums?.sha256;if(o===void 0)return e;const i=f(o),p=b(o);return new Proxy(e,{get(u,s){if(s==="sha256")return i;if(s==="sha256Base64")return p;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)}})},w=e=>{const o=e.checksums?.sha256;return{contentType:e.httpMetadata?.contentType,customMetadata:e.customMetadata,key:e.key,sha256:o===void 0?void 0:f(o),size:e.size,uploaded:e.uploaded===void 0?void 0:e.uploaded.getTime()}},E=e=>{const o=e.checksums?.sha256;return{checksums:e.checksums,customMetadata:e.customMetadata,etag:e.etag,httpEtag:e.httpEtag,httpMetadata:e.httpMetadata,key:e.key,sha256:o===void 0?void 0:f(o),sha256Base64:o===void 0?void 0:b(o),size:e.size,uploaded:e.uploaded}},M=(e,o)=>{let i=0;const p=s=>s instanceof ArrayBuffer||ArrayBuffer.isView(s)?s.byteLength:void 0,u=new TransformStream({transform(s,d){const h=p(s);if(h===void 0){d.error(new Error("@lunora/storage: stream chunk is not a byte chunk; cannot enforce maxSize"));return}if(i+=h,i>o){d.error(new Error(`@lunora/storage: stream body exceeds maxSize (> ${String(o)} bytes)`));return}d.enqueue(s)}});return e.pipeThrough(u)},c=e=>{if(typeof e!="string"||e.length===0)throw new n("VALIDATION_ERROR","@lunora/storage: key must be a non-empty string");if(e.length>m)throw new n("VALIDATION_ERROR",`@lunora/storage: key exceeds ${String(m)}-byte limit`);if(e.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: key contains NUL byte");if(T(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")},N=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`)}},B=(e,o)=>{c(e),c(o);const i=`${e.endsWith("/")?e.slice(0,-1):e}/${o}`;if(i.length>m)throw new n("VALIDATION_ERROR",`@lunora/storage: scoped key exceeds ${String(m)}-byte limit`);return i},_=e=>{if(!e.bucket)throw new n("INTERNAL","@lunora/storage: `bucket` is required");const o=async(r,t,a={})=>{c(r),N(a);let y=t;if(typeof a.maxSize=="number"){let g;if(t instanceof ArrayBuffer?g=t.byteLength:t instanceof Blob&&(g=t.size),g!==void 0&&g>a.maxSize)throw new n("PAYLOAD_TOO_LARGE",`@lunora/storage: body exceeds maxSize (${String(g)} > ${String(a.maxSize)})`);t instanceof ReadableStream&&(y=M(t,a.maxSize))}const l=await e.bucket.put(r,y,{customMetadata:a.customMetadata,httpMetadata:a.contentType?{contentType:a.contentType}:void 0,...a.sha256===void 0?{}:{sha256:a.sha256}});return{etag:l.etag,httpEtag:l.httpEtag??`"${l.etag}"`,key:l.key}},i=async(r,t={})=>{c(r);const a=await(t.range?e.bucket.get(r,{range:t.range}):e.bucket.get(r));return a&&S(a)},p=async r=>{c(r),await e.bucket.delete(r)},u=async r=>{if(c(r),e.bucket.head){const a=await e.bucket.head(r);return a&&w(a)}const t=await e.bucket.get(r,{range:{length:0}});return t&&w(t)},s=async(r,t={})=>{if(r?.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: prefix contains NUL byte");const a=t.limit??U,y=Math.min(Math.max(1,Math.floor(a)),A),l=await e.bucket.list({cursor:t.cursor,delimiter:t.delimiter,limit:y,prefix:r});return{cursor:l.cursor,objects:l.objects.map(g=>E(g)),truncated:l.truncated}},d=r=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getUrl()");c(r);const t=r.split("/").map(a=>encodeURIComponent(a)).join("/");return`${k(e.publicBaseUrl)}/${t}`},h=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 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 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})},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 n("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:d,list:s,resumeMultipartUpload:(r,t)=>{if(c(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)},store:o,upload:o}};export{_ as createStorage,B as scopeKey};
|
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.23",
|
|
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.16",
|
|
54
|
+
"@lunora/platform": "1.0.0-alpha.7",
|
|
55
55
|
"@visulima/storage": "1.0.5",
|
|
56
56
|
"aws4fetch": "1.0.20"
|
|
57
57
|
},
|
|
@@ -1 +0,0 @@
|
|
|
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 `/`");if(e.split("/").includes(".."))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:n,upload:n}};export{B as createStorage,v as scopeKey};
|