@lunora/storage 1.0.0-alpha.51 → 1.0.0-alpha.53
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 +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/DEFAULT_MAX_UPLOAD_BYTES-l43E2slu.mjs +1 -0
- package/dist/packem_shared/createBucketStorage-AqIMq050.mjs +1 -0
- package/dist/packem_shared/createStorage-C5wU9cbC.mjs +1 -0
- package/dist/upload.d.mts +3 -2
- package/dist/upload.d.ts +3 -2
- package/dist/upload.mjs +1 -1
- package/package.json +3 -3
- package/dist/packem_shared/DEFAULT_MAX_UPLOAD_BYTES-Dhj02auN.mjs +0 -1
- package/dist/packem_shared/createBucketStorage-D9fBoaEi.mjs +0 -1
- package/dist/packem_shared/createStorage-B-E6dH3Q.mjs +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -59,11 +59,20 @@ interface UploadOptions {
|
|
|
59
59
|
customMetadata?: Record<string, string>;
|
|
60
60
|
/**
|
|
61
61
|
* Maximum body size in bytes. For `ArrayBuffer`/`Blob` sources the length is
|
|
62
|
-
* known up front and rejected before the upload starts.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
62
|
+
* known up front and rejected before the upload starts.
|
|
63
|
+
*
|
|
64
|
+
* A `ReadableStream` has no length to check, and R2 refuses any stream whose
|
|
65
|
+
* length it cannot read — so a capped stream is READ INTO MEMORY under the
|
|
66
|
+
* cap and uploaded as a sized body. Nothing reaches the bucket if the body
|
|
67
|
+
* crosses the limit.
|
|
68
|
+
*
|
|
69
|
+
* `maxSize` is therefore also the memory ceiling for a streamed upload, and
|
|
70
|
+
* the isolate's ~128 MB is shared by every concurrent request — so on the
|
|
71
|
+
* stream path `maxSize` is itself capped at 16 MiB and rejected above it.
|
|
72
|
+
* For objects larger than that use `createMultipartUpload` /
|
|
73
|
+
* `createUploadHandler`, which upload without ever holding the whole object.
|
|
74
|
+
* Must be a finite, non-negative number; anything else (`Number(undefined)`
|
|
75
|
+
* is the usual source) is rejected as a `VALIDATION_ERROR`.
|
|
67
76
|
*/
|
|
68
77
|
maxSize?: number;
|
|
69
78
|
/**
|
|
@@ -80,7 +89,10 @@ interface ListOptions {
|
|
|
80
89
|
cursor?: string;
|
|
81
90
|
/** R2 list delimiter — when set, common prefixes group instead of listing. */
|
|
82
91
|
delimiter?: string;
|
|
83
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Defaults to 100, capped at 1000 (R2 limit). A ceiling, not a promise: R2
|
|
94
|
+
* may return fewer per page to fit the entry metadata.
|
|
95
|
+
*/
|
|
84
96
|
limit?: number;
|
|
85
97
|
}
|
|
86
98
|
interface SignedUrlOptions {
|
|
@@ -190,6 +202,10 @@ interface Storage {
|
|
|
190
202
|
* segment are rolled up into `delimitedPrefixes` (the "folders") and are NOT
|
|
191
203
|
* in `objects` — a folder browser needs both, so a listing whose `objects` is
|
|
192
204
|
* empty is not an empty directory.
|
|
205
|
+
*
|
|
206
|
+
* A page may hold FEWER objects than `options.limit`: R2 shrinks a page to
|
|
207
|
+
* fit the per-entry metadata this call asks for. Paginate on `truncated` /
|
|
208
|
+
* `cursor`, never on `objects.length === limit`.
|
|
193
209
|
*/
|
|
194
210
|
list: (prefix?: string, options?: ListOptions) => Promise<{
|
|
195
211
|
cursor?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -59,11 +59,20 @@ interface UploadOptions {
|
|
|
59
59
|
customMetadata?: Record<string, string>;
|
|
60
60
|
/**
|
|
61
61
|
* Maximum body size in bytes. For `ArrayBuffer`/`Blob` sources the length is
|
|
62
|
-
* known up front and rejected before the upload starts.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
62
|
+
* known up front and rejected before the upload starts.
|
|
63
|
+
*
|
|
64
|
+
* A `ReadableStream` has no length to check, and R2 refuses any stream whose
|
|
65
|
+
* length it cannot read — so a capped stream is READ INTO MEMORY under the
|
|
66
|
+
* cap and uploaded as a sized body. Nothing reaches the bucket if the body
|
|
67
|
+
* crosses the limit.
|
|
68
|
+
*
|
|
69
|
+
* `maxSize` is therefore also the memory ceiling for a streamed upload, and
|
|
70
|
+
* the isolate's ~128 MB is shared by every concurrent request — so on the
|
|
71
|
+
* stream path `maxSize` is itself capped at 16 MiB and rejected above it.
|
|
72
|
+
* For objects larger than that use `createMultipartUpload` /
|
|
73
|
+
* `createUploadHandler`, which upload without ever holding the whole object.
|
|
74
|
+
* Must be a finite, non-negative number; anything else (`Number(undefined)`
|
|
75
|
+
* is the usual source) is rejected as a `VALIDATION_ERROR`.
|
|
67
76
|
*/
|
|
68
77
|
maxSize?: number;
|
|
69
78
|
/**
|
|
@@ -80,7 +89,10 @@ interface ListOptions {
|
|
|
80
89
|
cursor?: string;
|
|
81
90
|
/** R2 list delimiter — when set, common prefixes group instead of listing. */
|
|
82
91
|
delimiter?: string;
|
|
83
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Defaults to 100, capped at 1000 (R2 limit). A ceiling, not a promise: R2
|
|
94
|
+
* may return fewer per page to fit the entry metadata.
|
|
95
|
+
*/
|
|
84
96
|
limit?: number;
|
|
85
97
|
}
|
|
86
98
|
interface SignedUrlOptions {
|
|
@@ -190,6 +202,10 @@ interface Storage {
|
|
|
190
202
|
* segment are rolled up into `delimitedPrefixes` (the "folders") and are NOT
|
|
191
203
|
* in `objects` — a folder browser needs both, so a listing whose `objects` is
|
|
192
204
|
* empty is not an empty directory.
|
|
205
|
+
*
|
|
206
|
+
* A page may hold FEWER objects than `options.limit`: R2 shrinks a page to
|
|
207
|
+
* fit the per-entry metadata this call asks for. Paginate on `truncated` /
|
|
208
|
+
* `cursor`, never on `objects.length === limit`.
|
|
193
209
|
*/
|
|
194
210
|
list: (prefix?: string, options?: ListOptions) => Promise<{
|
|
195
211
|
cursor?: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createBucketStorage as o}from"./packem_shared/createBucketStorage-
|
|
1
|
+
import{createBucketStorage as o}from"./packem_shared/createBucketStorage-AqIMq050.mjs";import{createStorage as i,scopeKey as d}from"./packem_shared/createStorage-C5wU9cbC.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{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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as n}from"@lunora/errors";const g=(e,a={})=>{const o=Object.keys(e),[u]=o;if(u===void 0)throw new n("INTERNAL","@lunora/storage: createBucketStorage requires at least one bucket");if(a.default!==void 0&&!Object.hasOwn(e,a.default))throw new n("INTERNAL",`@lunora/storage: default bucket "${a.default}" is not in the bucket map (have: ${o.join(", ")})`);const t=a.default??(e.default?"default":u),c=e[t];if(c===void 0)throw new n("INTERNAL",`@lunora/storage: default bucket "${t}" is not in the bucket map (have: ${o.join(", ")})`);const s=[...new Set([t,...o])],d=r=>{const f=Object.hasOwn(e,r)?e[r]:void 0,i=r===t?c:f;if(!i)throw new n("INTERNAL",`@lunora/storage: no bucket registered for "${r}". Known buckets: ${s.join(", ")}`);return{...i,bucket:l=>d(l),bucketName:r}};return d(t)};export{g as createBucketStorage};
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
package/dist/upload.d.mts
CHANGED
|
@@ -43,8 +43,9 @@ interface CreateUploadHandlerOptions {
|
|
|
43
43
|
/**
|
|
44
44
|
* Maximum accepted file size in bytes. Forwarded to the multipart parser
|
|
45
45
|
* (protocol `"multipart"`) and, for `"tus"`/`"chunked-rest"`, enforced by
|
|
46
|
-
* this handler itself against the request
|
|
47
|
-
*
|
|
46
|
+
* this handler itself against the largest size the request declares across
|
|
47
|
+
* `Upload-Length` (TUS), `X-Total-Size` (chunked REST) and `Content-Length`
|
|
48
|
+
* — see {@link declaredUploadSize}. Defaults to
|
|
48
49
|
* {@link DEFAULT_MAX_UPLOAD_BYTES} (100 MiB) — pass this to raise or lower
|
|
49
50
|
* the ceiling; there is no unbounded option.
|
|
50
51
|
*/
|
package/dist/upload.d.ts
CHANGED
|
@@ -43,8 +43,9 @@ interface CreateUploadHandlerOptions {
|
|
|
43
43
|
/**
|
|
44
44
|
* Maximum accepted file size in bytes. Forwarded to the multipart parser
|
|
45
45
|
* (protocol `"multipart"`) and, for `"tus"`/`"chunked-rest"`, enforced by
|
|
46
|
-
* this handler itself against the request
|
|
47
|
-
*
|
|
46
|
+
* this handler itself against the largest size the request declares across
|
|
47
|
+
* `Upload-Length` (TUS), `X-Total-Size` (chunked REST) and `Content-Length`
|
|
48
|
+
* — see {@link declaredUploadSize}. Defaults to
|
|
48
49
|
* {@link DEFAULT_MAX_UPLOAD_BYTES} (100 MiB) — pass this to raise or lower
|
|
49
50
|
* the ceiling; there is no unbounded option.
|
|
50
51
|
*/
|
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-l43E2slu.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.53",
|
|
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.33",
|
|
54
|
+
"@lunora/platform": "1.0.0-alpha.27",
|
|
55
55
|
"@visulima/storage": "2.0.0",
|
|
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,g="1.0.0",i=(e,t,r)=>{const n={"content-type":"application/json"};return e==="tus"&&(n["Tus-Resumable"]=g),Response.json({error:r},{headers:n,status:t})},s=e=>i(e,403,{code:"FORBIDDEN",message:"Upload denied by authorization policy",name:"ForbiddenError"}),f=e=>i(e,413,{code:"REQUEST_ENTITY_TOO_LARGE",message:"Upload exceeds the configured maxFileSize",name:"RequestEntityTooLargeError"}),S=(e,t)=>{if(t==="multipart")return;const r=e.headers.get("Upload-Length")??e.headers.get("Content-Length");if(r===null)return;const n=Number(r);return Number.isFinite(n)?n:void 0},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,n={maxFileSize:r,storage:e.storage},d=R(t,n),{authorize:o}=e;return o===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=S(a,t);if(c!==void 0&&c>r)return f(t);if(o!==void 0)try{if(await o({method:a.method,protocol:t,request:a,url:new URL(a.url)})!==!0)return s(t)}catch{return s(t)}return d.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";const g=(e,r={})=>{const a=Object.keys(e),[u]=a;if(u===void 0)throw new n("INTERNAL","@lunora/storage: createBucketStorage requires at least one bucket");if(r.default!==void 0&&!e[r.default])throw new n("INTERNAL",`@lunora/storage: default bucket "${r.default}" is not in the bucket map (have: ${a.join(", ")})`);const t=r.default??(e.default?"default":u),c=e[t];if(c===void 0)throw new n("INTERNAL",`@lunora/storage: default bucket "${t}" is not in the bucket map (have: ${a.join(", ")})`);const i=[...new Set([t,...a])],d=o=>{const f=o===t?c:e[o];if(!f)throw new n("INTERNAL",`@lunora/storage: no bucket registered for "${o}". Known buckets: ${i.join(", ")}`);return{...f,bucket:l=>d(l),bucketName:o}};return d(t)};export{g as createBucketStorage};
|
|
@@ -1 +0,0 @@
|
|
|
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};
|