@lunora/storage 1.0.0-alpha.69 → 1.0.0-alpha.70
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 +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/DEFAULT_MAX_UPLOAD_BYTES-CO3auxph.mjs +1 -0
- package/dist/packem_shared/createStorage-YPzdUgGl.mjs +1 -0
- package/dist/upload.d.mts +3 -1
- package/dist/upload.d.ts +3 -1
- package/dist/upload.mjs +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/DEFAULT_MAX_UPLOAD_BYTES-l43E2slu.mjs +0 -1
- package/dist/packem_shared/createStorage-C5wU9cbC.mjs +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -92,6 +92,9 @@ interface ListOptions {
|
|
|
92
92
|
/**
|
|
93
93
|
* Defaults to 100, capped at 1000 (R2 limit). A ceiling, not a promise: R2
|
|
94
94
|
* may return fewer per page to fit the entry metadata.
|
|
95
|
+
*
|
|
96
|
+
* Must be a positive integer when given — `0`, `12.5` and `NaN` throw rather
|
|
97
|
+
* than being coerced into a page size the caller never asked for.
|
|
95
98
|
*/
|
|
96
99
|
limit?: number;
|
|
97
100
|
}
|
|
@@ -224,12 +227,20 @@ interface Storage {
|
|
|
224
227
|
* alias for {@link Storage.upload} — it accepts the same {@link UploadOptions}
|
|
225
228
|
* so the `maxSize` / `allowedContentTypes` guards aren't lost behind the alias.
|
|
226
229
|
*/
|
|
227
|
-
store: (key: string, body: ReadableStream | ArrayBuffer | Blob, options?: UploadOptions) => Promise<{
|
|
230
|
+
store: (key: string, body: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string, options?: UploadOptions) => Promise<{
|
|
228
231
|
etag: string;
|
|
229
232
|
httpEtag: string;
|
|
230
233
|
key: string;
|
|
231
234
|
}>;
|
|
232
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Upload `body` to `key`, returning the stored key + etag.
|
|
237
|
+
*
|
|
238
|
+
* The accepted shapes mirror what R2's `put` stores bytes from — a view
|
|
239
|
+
* (`Uint8Array`) and a `string` included. They are listed here because
|
|
240
|
+
* `maxSize` measures every one of them; a shape the cap cannot measure is
|
|
241
|
+
* refused rather than uploaded uncapped.
|
|
242
|
+
*/
|
|
243
|
+
upload: (key: string, body: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string, options?: UploadOptions) => Promise<{
|
|
233
244
|
etag: string;
|
|
234
245
|
httpEtag: string;
|
|
235
246
|
key: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -92,6 +92,9 @@ interface ListOptions {
|
|
|
92
92
|
/**
|
|
93
93
|
* Defaults to 100, capped at 1000 (R2 limit). A ceiling, not a promise: R2
|
|
94
94
|
* may return fewer per page to fit the entry metadata.
|
|
95
|
+
*
|
|
96
|
+
* Must be a positive integer when given — `0`, `12.5` and `NaN` throw rather
|
|
97
|
+
* than being coerced into a page size the caller never asked for.
|
|
95
98
|
*/
|
|
96
99
|
limit?: number;
|
|
97
100
|
}
|
|
@@ -224,12 +227,20 @@ interface Storage {
|
|
|
224
227
|
* alias for {@link Storage.upload} — it accepts the same {@link UploadOptions}
|
|
225
228
|
* so the `maxSize` / `allowedContentTypes` guards aren't lost behind the alias.
|
|
226
229
|
*/
|
|
227
|
-
store: (key: string, body: ReadableStream | ArrayBuffer | Blob, options?: UploadOptions) => Promise<{
|
|
230
|
+
store: (key: string, body: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string, options?: UploadOptions) => Promise<{
|
|
228
231
|
etag: string;
|
|
229
232
|
httpEtag: string;
|
|
230
233
|
key: string;
|
|
231
234
|
}>;
|
|
232
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Upload `body` to `key`, returning the stored key + etag.
|
|
237
|
+
*
|
|
238
|
+
* The accepted shapes mirror what R2's `put` stores bytes from — a view
|
|
239
|
+
* (`Uint8Array`) and a `string` included. They are listed here because
|
|
240
|
+
* `maxSize` measures every one of them; a shape the cap cannot measure is
|
|
241
|
+
* refused rather than uploaded uncapped.
|
|
242
|
+
*/
|
|
243
|
+
upload: (key: string, body: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string, options?: UploadOptions) => Promise<{
|
|
233
244
|
etag: string;
|
|
234
245
|
httpEtag: string;
|
|
235
246
|
key: string;
|
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-
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as d}from"@lunora/errors";import{Rest as l,Multipart as m,Tus as h}from"@visulima/storage/handler/http/fetch";import{AwsLightStorage as f}from"@visulima/storage/provider/aws-light";const p=100*1024*1024,g="1.0.0",u=(e,t,r)=>{const o={"content-type":"application/json"};return e==="tus"&&(o["Tus-Resumable"]=g),Response.json({error:r},{headers:o,status:t})},s=e=>u(e,403,{code:"FORBIDDEN",message:"Upload denied by authorization policy",name:"ForbiddenError"}),S=e=>u(e,413,{code:"REQUEST_ENTITY_TOO_LARGE",message:"Upload exceeds the configured maxFileSize",name:"RequestEntityTooLargeError"}),R=(e,t)=>{if(t==="multipart")return;let r;for(const o of["Upload-Length","X-Total-Size","Content-Length"]){const i=e.headers.get(o);if(i===null)continue;const n=Number(i);Number.isFinite(n)&&(r===void 0||n>r)&&(r=n)}return r},b=(e,t)=>e==="chunked-rest"?new l(t):e==="multipart"?new m(t):new h(t),y=e=>{const t=e.protocol??"tus",r=e.maxFileSize??p;if(!Number.isFinite(r)||r<0)throw new d("VALIDATION_ERROR",`@lunora/storage: maxFileSize must be a finite, non-negative number (received ${String(r)})`);const o={maxFileSize:r,storage:e.storage},i=b(t,o),{authorize:n}=e;return n===void 0&&!e.silent&&!e.public&&console.warn("@lunora/storage: createUploadHandler() has no `authorize` — this mounts an unauthenticated, unbounded-write endpoint. Pass an RLS `authorize` gate, or set `public: true` (or `silent: true`) to confirm this bucket is intentionally open."),{fetch:async a=>{const c=R(a,t);if(c!==void 0&&c>r)return S(t);if(n!==void 0)try{if(await n({method:a.method,protocol:t,request:a,url:new URL(a.url)})!==!0)return s(t)}catch{return s(t)}return i.fetch(a)},protocol:t}},T=e=>new f({accessKeyId:e.accessKeyId,bucket:e.bucket,endpoint:e.endpoint??`https://${e.accountId}.r2.cloudflarestorage.com`,path:e.path??"/",region:"auto",secretAccessKey:e.secretAccessKey,...e.partSize===void 0?{}:{partSize:e.partSize}});export{p as DEFAULT_MAX_UPLOAD_BYTES,T as createR2UploadStorage,y as createUploadHandler};
|
|
@@ -0,0 +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};
|
package/dist/upload.d.mts
CHANGED
|
@@ -47,7 +47,9 @@ interface CreateUploadHandlerOptions {
|
|
|
47
47
|
* `Upload-Length` (TUS), `X-Total-Size` (chunked REST) and `Content-Length`
|
|
48
48
|
* — see {@link declaredUploadSize}. Defaults to
|
|
49
49
|
* {@link DEFAULT_MAX_UPLOAD_BYTES} (100 MiB) — pass this to raise or lower
|
|
50
|
-
* the ceiling; there is no unbounded option.
|
|
50
|
+
* the ceiling; there is no unbounded option. Must be a finite, non-negative
|
|
51
|
+
* number: anything else (notably a `NaN` from an unset env var) throws at
|
|
52
|
+
* construction rather than disabling the cap.
|
|
51
53
|
*/
|
|
52
54
|
maxFileSize?: number;
|
|
53
55
|
/** Which protocol to speak. Default `"tus"` (the resumable, pause/resume-capable one). */
|
package/dist/upload.d.ts
CHANGED
|
@@ -47,7 +47,9 @@ interface CreateUploadHandlerOptions {
|
|
|
47
47
|
* `Upload-Length` (TUS), `X-Total-Size` (chunked REST) and `Content-Length`
|
|
48
48
|
* — see {@link declaredUploadSize}. Defaults to
|
|
49
49
|
* {@link DEFAULT_MAX_UPLOAD_BYTES} (100 MiB) — pass this to raise or lower
|
|
50
|
-
* the ceiling; there is no unbounded option.
|
|
50
|
+
* the ceiling; there is no unbounded option. Must be a finite, non-negative
|
|
51
|
+
* number: anything else (notably a `NaN` from an unset env var) throws at
|
|
52
|
+
* construction rather than disabling the cap.
|
|
51
53
|
*/
|
|
52
54
|
maxFileSize?: number;
|
|
53
55
|
/** Which protocol to speak. Default `"tus"` (the resumable, pause/resume-capable one). */
|
package/dist/upload.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_MAX_UPLOAD_BYTES as r,createR2UploadStorage as o,createUploadHandler as t}from"./packem_shared/DEFAULT_MAX_UPLOAD_BYTES-
|
|
1
|
+
import{DEFAULT_MAX_UPLOAD_BYTES as r,createR2UploadStorage as o,createUploadHandler as t}from"./packem_shared/DEFAULT_MAX_UPLOAD_BYTES-CO3auxph.mjs";export{r as DEFAULT_MAX_UPLOAD_BYTES,o as createR2UploadStorage,t as createUploadHandler};
|
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.70",
|
|
4
4
|
"description": "R2-backed storage for Lunora: typed buckets and signed URLs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@lunora/errors": "1.0.0-alpha.36",
|
|
54
|
-
"@lunora/platform": "1.0.0-alpha.
|
|
54
|
+
"@lunora/platform": "1.0.0-alpha.32",
|
|
55
55
|
"@visulima/storage": "2.0.8",
|
|
56
56
|
"aws4fetch": "1.0.20"
|
|
57
57
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Rest as u,Multipart as l,Tus as h}from"@visulima/storage/handler/http/fetch";import{AwsLightStorage as p}from"@visulima/storage/provider/aws-light";const m=100*1024*1024,f="1.0.0",d=(e,t,r)=>{const o={"content-type":"application/json"};return e==="tus"&&(o["Tus-Resumable"]=f),Response.json({error:r},{headers:o,status:t})},i=e=>d(e,403,{code:"FORBIDDEN",message:"Upload denied by authorization policy",name:"ForbiddenError"}),g=e=>d(e,413,{code:"REQUEST_ENTITY_TOO_LARGE",message:"Upload exceeds the configured maxFileSize",name:"RequestEntityTooLargeError"}),S=(e,t)=>{if(t==="multipart")return;let r;for(const o of["Upload-Length","X-Total-Size","Content-Length"]){const c=e.headers.get(o);if(c===null)continue;const n=Number(c);Number.isFinite(n)&&(r===void 0||n>r)&&(r=n)}return r},R=(e,t)=>e==="chunked-rest"?new u(t):e==="multipart"?new l(t):new h(t),E=e=>{const t=e.protocol??"tus",r=e.maxFileSize??m,o={maxFileSize:r,storage:e.storage},c=R(t,o),{authorize:n}=e;return n===void 0&&!e.silent&&!e.public&&console.warn("@lunora/storage: createUploadHandler() has no `authorize` — this mounts an unauthenticated, unbounded-write endpoint. Pass an RLS `authorize` gate, or set `public: true` (or `silent: true`) to confirm this bucket is intentionally open."),{fetch:async a=>{const s=S(a,t);if(s!==void 0&&s>r)return g(t);if(n!==void 0)try{if(await n({method:a.method,protocol:t,request:a,url:new URL(a.url)})!==!0)return i(t)}catch{return i(t)}return c.fetch(a)},protocol:t}},b=e=>new p({accessKeyId:e.accessKeyId,bucket:e.bucket,endpoint:e.endpoint??`https://${e.accountId}.r2.cloudflarestorage.com`,path:e.path??"/",region:"auto",secretAccessKey:e.secretAccessKey,...e.partSize===void 0?{}:{partSize:e.partSize}});export{m as DEFAULT_MAX_UPLOAD_BYTES,b as createR2UploadStorage,E as createUploadHandler};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as n}from"@lunora/errors";import{t as U,h as S,a as A,b as f}from"./internal-B5yyxF10.mjs";import{buildPresignedUrl as _}from"./buildPresignedUrl-U55ktL8x.mjs";import{buildSignedUrl as k}from"./buildSignedUrl-czz1xjLU.mjs";const w=1024,v=1e3,D=100,T=16*1024*1024,O=e=>{const a=e.checksums?.sha256;if(a===void 0)return e;const i=f(a),g=A(new Uint8Array(a));return new Proxy(e,{get(u,s){if(s==="sha256")return i;if(s==="sha256Base64")return g;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)}})},x=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:A(new Uint8Array(a)),size:e.size,uploaded:e.uploaded}},B=async(e,a)=>{let i=0;const g=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 h=g(s);if(h===void 0){d.error(new n("VALIDATION_ERROR","@lunora/storage: stream chunk is not a byte chunk; cannot enforce maxSize"));return}if(i+=h.byteLength,i>a){d.error(new n("PAYLOAD_TOO_LARGE",`@lunora/storage: stream body exceeds maxSize (> ${String(a)} bytes)`));return}d.enqueue(h)}});return await new Response(e.pipeThrough(u)).blob()},V=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>T)throw new n("VALIDATION_ERROR",`@lunora/storage: maxSize ${String(a)} exceeds the ${String(T)}-byte ceiling for a streamed body, which must be buffered to be capped — use createMultipartUpload() or createUploadHandler() for objects this large`);return await B(e,a)}const i=e instanceof ArrayBuffer?e.byteLength:e.size;if(i>a)throw new n("PAYLOAD_TOO_LARGE",`@lunora/storage: body exceeds maxSize (${String(i)} > ${String(a)})`);return e},$=new TextEncoder,b=e=>$.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(b(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(S(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")},q=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)=>{o(e),o(a);const g=`${e.endsWith("/")?e.slice(0,-1):e}/${a}`;if(b(g)>w)throw new n("VALIDATION_ERROR",`@lunora/storage: scoped key exceeds ${String(w)}-byte limit`);return g},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(t,r,c={})=>{o(t),q(c);const m=c.maxSize===void 0?r:await V(r,c.maxSize),l=await e.bucket.put(t,m,{customMetadata:c.customMetadata,httpMetadata:c.contentType?{contentType:c.contentType}:void 0,...c.sha256===void 0?{}:{sha256:c.sha256}});return{etag:l.etag,httpEtag:l.httpEtag??`"${l.etag}"`,key:l.key}},i=async(t,r={})=>{o(t);const c=await(r.range?e.bucket.get(t,{range:r.range}):e.bucket.get(t));return c&&O(c)},g=async t=>{o(t),await e.bucket.delete(t)},u=async t=>{o(t);const r=e.bucket.head?await e.bucket.head(t):await e.bucket.get(t,{range:{length:0}});return r&&y(r)},s=async t=>{const r=await u(t);return r&&x(r)},d=async(t,r={})=>{if(t?.includes("\0"))throw new n("VALIDATION_ERROR","@lunora/storage: prefix contains NUL byte");const c=r.limit??D,m=Math.min(Math.max(1,Math.floor(c)),v),l=await e.bucket.list({cursor:r.cursor,delimiter:r.delimiter,include:["customMetadata","httpMetadata"],limit:m,prefix:t}),{delimitedPrefixes:E}=l;return{cursor:l.cursor,delimitedPrefixes:E,objects:l.objects.map(M=>y(M)),truncated:l.truncated}},h=t=>{if(!e.publicBaseUrl)throw new n("INTERNAL","@lunora/storage: `publicBaseUrl` is required for getUrl()");o(t);const r=t.split("/").map(c=>encodeURIComponent(c)).join("/");return`${U(e.publicBaseUrl)}/${r}`},R=async(t,r={})=>{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(t),k({baseUrl:e.publicBaseUrl,bucketName:e.bucketName,contentType:r.contentType,expiresInSeconds:r.expiresInSeconds,key:t,method:r.method,secret:e.signingSecret})},I=async(t,r={})=>{if(o(t),!e.bucket.createMultipartUpload)throw new n("INTERNAL","@lunora/storage: bucket binding does not support multipart uploads (createMultipartUpload)");return e.bucket.createMultipartUpload(t,{customMetadata:r.customMetadata,httpMetadata:r.contentType?{contentType:r.contentType}:void 0})},p=(t,r)=>{if(o(t),typeof r!="string"||r.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(t,r)},N=async(t,r={})=>{if(!e.s3)throw new n("INTERNAL","@lunora/storage: `s3` credentials are required for getPresignedUrl() — pass { accountId, accessKeyId, secretAccessKey, bucket }");return o(t),_({credentials:e.s3,expiresInSeconds:r.expiresInSeconds,key:t,method:r.method})},L=async(t,r={})=>R(t,{contentType:r.contentType,expiresInSeconds:r.expiresInSeconds,method:"PUT"});return{bucketName:e.bucketName,createMultipartUpload:I,delete:g,download:i,generateUploadUrl:L,getMetadata:s,getPresignedUrl:N,getSignedUrl:R,getUrl:h,head:u,list:d,resumeMultipartUpload:p,store:a,upload:a}};export{X as createStorage,F as scopeKey};
|