@lunora/storage 1.0.0-alpha.4 → 1.0.0-alpha.5

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,4 +1,4 @@
1
- export { createBucketStorage } from './packem_shared/createBucketStorage-4Xk7-5CN.mjs';
2
- export { createStorage, scopeKey } from './packem_shared/createStorage-DoE7gOqo.mjs';
1
+ export { createBucketStorage } from './packem_shared/createBucketStorage-JYdrS4e5.mjs';
2
+ export { createStorage, scopeKey } from './packem_shared/createStorage-fwSr3UdU.mjs';
3
3
  export { buildPresignedUrl } from './packem_shared/buildPresignedUrl-DzPwi1bY.mjs';
4
- export { buildSignedUrl, verifySignedUrl } from './packem_shared/buildSignedUrl-O-vsHfyW.mjs';
4
+ export { buildSignedUrl, verifySignedUrl } from './packem_shared/buildSignedUrl-DX535yKv.mjs';
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const textEncoder = new TextEncoder();
2
4
  const MAX_EXPIRES_IN_SECONDS = 7 * 24 * 60 * 60;
3
5
  const SCHEME_PREFIX_RE = /^[a-z][a-z0-9+\-.]*:\/\//i;
@@ -52,10 +54,10 @@ const buildSignedUrl = async (args) => {
52
54
  const method = args.method ?? "GET";
53
55
  const expiresInSeconds = args.expiresInSeconds ?? 60 * 60;
54
56
  if (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0) {
55
- throw new Error("@lunora/storage: expiresInSeconds must be a positive finite number");
57
+ throw new LunoraError("INTERNAL", "@lunora/storage: expiresInSeconds must be a positive finite number");
56
58
  }
57
59
  if (expiresInSeconds > MAX_EXPIRES_IN_SECONDS) {
58
- throw new Error(`@lunora/storage: expiresInSeconds must not exceed ${String(MAX_EXPIRES_IN_SECONDS)} (7 days)`);
60
+ throw new LunoraError("INTERNAL", `@lunora/storage: expiresInSeconds must not exceed ${String(MAX_EXPIRES_IN_SECONDS)} (7 days)`);
59
61
  }
60
62
  const contentType = method === "PUT" ? args.contentType : void 0;
61
63
  const exp = Math.floor(Date.now() / 1e3) + expiresInSeconds;
@@ -1,22 +1,24 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const createBucketStorage = (buckets, options = {}) => {
2
4
  const names = Object.keys(buckets);
3
5
  const [firstName] = names;
4
6
  if (firstName === void 0) {
5
- throw new Error("@lunora/storage: createBucketStorage requires at least one bucket");
7
+ throw new LunoraError("INTERNAL", "@lunora/storage: createBucketStorage requires at least one bucket");
6
8
  }
7
9
  if (options.default !== void 0 && !buckets[options.default]) {
8
- throw new Error(`@lunora/storage: default bucket "${options.default}" is not in the bucket map (have: ${names.join(", ")})`);
10
+ throw new LunoraError("INTERNAL", `@lunora/storage: default bucket "${options.default}" is not in the bucket map (have: ${names.join(", ")})`);
9
11
  }
10
12
  const defaultTag = options.default ?? "default";
11
13
  const defaultBinding = buckets[defaultTag] ?? buckets[firstName];
12
14
  if (defaultBinding === void 0) {
13
- throw new Error(`@lunora/storage: default bucket "${defaultTag}" is not in the bucket map (have: ${names.join(", ")})`);
15
+ throw new LunoraError("INTERNAL", `@lunora/storage: default bucket "${defaultTag}" is not in the bucket map (have: ${names.join(", ")})`);
14
16
  }
15
17
  const addressable = [.../* @__PURE__ */ new Set([defaultTag, ...names])];
16
18
  const make = (name) => {
17
19
  const target = name === defaultTag ? defaultBinding : buckets[name];
18
20
  if (!target) {
19
- throw new Error(`@lunora/storage: no bucket registered for "${name}". Known buckets: ${addressable.join(", ")}`);
21
+ throw new LunoraError("INTERNAL", `@lunora/storage: no bucket registered for "${name}". Known buckets: ${addressable.join(", ")}`);
20
22
  }
21
23
  return { ...target, bucket: (next) => make(next), bucketName: name };
22
24
  };
@@ -1,5 +1,6 @@
1
+ import { LunoraError } from '@lunora/errors';
1
2
  import { buildPresignedUrl } from './buildPresignedUrl-DzPwi1bY.mjs';
2
- import { buildSignedUrl } from './buildSignedUrl-O-vsHfyW.mjs';
3
+ import { buildSignedUrl } from './buildSignedUrl-DX535yKv.mjs';
3
4
 
4
5
  const MAX_KEY_LENGTH = 1024;
5
6
  const MAX_LIST_LIMIT = 1e3;
@@ -83,21 +84,21 @@ const trimTrailingSlashes = (value) => {
83
84
  };
84
85
  const validateKey = (key) => {
85
86
  if (typeof key !== "string" || key.length === 0) {
86
- throw new Error("@lunora/storage: key must be a non-empty string");
87
+ throw new LunoraError("INTERNAL", "@lunora/storage: key must be a non-empty string");
87
88
  }
88
89
  if (key.length > MAX_KEY_LENGTH) {
89
- throw new Error(`@lunora/storage: key exceeds ${String(MAX_KEY_LENGTH)}-byte limit`);
90
+ throw new LunoraError("INTERNAL", `@lunora/storage: key exceeds ${String(MAX_KEY_LENGTH)}-byte limit`);
90
91
  }
91
92
  if (key.includes("\0")) {
92
- throw new Error("@lunora/storage: key contains NUL byte");
93
+ throw new LunoraError("INTERNAL", "@lunora/storage: key contains NUL byte");
93
94
  }
94
95
  if (key.startsWith("/")) {
95
- throw new Error("@lunora/storage: key must not start with `/`");
96
+ throw new LunoraError("INTERNAL", "@lunora/storage: key must not start with `/`");
96
97
  }
97
98
  const segments = key.split("/");
98
99
  for (const segment of segments) {
99
100
  if (segment === "..") {
100
- throw new Error("@lunora/storage: key contains a `..` path component");
101
+ throw new LunoraError("INTERNAL", "@lunora/storage: key contains a `..` path component");
101
102
  }
102
103
  }
103
104
  };
@@ -107,22 +108,22 @@ const scopeKey = (prefix, key) => {
107
108
  const trimmedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
108
109
  const composed = `${trimmedPrefix}/${key}`;
109
110
  if (composed.length > MAX_KEY_LENGTH) {
110
- throw new Error(`@lunora/storage: scoped key exceeds ${String(MAX_KEY_LENGTH)}-byte limit`);
111
+ throw new LunoraError("INTERNAL", `@lunora/storage: scoped key exceeds ${String(MAX_KEY_LENGTH)}-byte limit`);
111
112
  }
112
113
  return composed;
113
114
  };
114
115
  const createStorage = (options) => {
115
116
  if (!options.bucket) {
116
- throw new Error("@lunora/storage: `bucket` is required");
117
+ throw new LunoraError("INTERNAL", "@lunora/storage: `bucket` is required");
117
118
  }
118
119
  const upload = async (key, body, uploadOptions = {}) => {
119
120
  validateKey(key);
120
121
  if (uploadOptions.allowedContentTypes !== void 0) {
121
122
  if (uploadOptions.contentType === void 0) {
122
- throw new Error("@lunora/storage: contentType is required when allowedContentTypes is set");
123
+ throw new LunoraError("INTERNAL", "@lunora/storage: contentType is required when allowedContentTypes is set");
123
124
  }
124
125
  if (!uploadOptions.allowedContentTypes.includes(uploadOptions.contentType)) {
125
- throw new Error(`@lunora/storage: contentType "${uploadOptions.contentType}" not in allowedContentTypes`);
126
+ throw new LunoraError("INTERNAL", `@lunora/storage: contentType "${uploadOptions.contentType}" not in allowedContentTypes`);
126
127
  }
127
128
  }
128
129
  let putBody = body;
@@ -134,7 +135,7 @@ const createStorage = (options) => {
134
135
  size = body.size;
135
136
  }
136
137
  if (size !== void 0 && size > uploadOptions.maxSize) {
137
- throw new Error(`@lunora/storage: body exceeds maxSize (${String(size)} > ${String(uploadOptions.maxSize)})`);
138
+ throw new LunoraError("INTERNAL", `@lunora/storage: body exceeds maxSize (${String(size)} > ${String(uploadOptions.maxSize)})`);
138
139
  }
139
140
  if (body instanceof ReadableStream) {
140
141
  putBody = enforceStreamMaxSize(body, uploadOptions.maxSize);
@@ -166,7 +167,7 @@ const createStorage = (options) => {
166
167
  };
167
168
  const list = async (prefix, listOptions = {}) => {
168
169
  if (prefix?.includes("\0")) {
169
- throw new Error("@lunora/storage: prefix contains NUL byte");
170
+ throw new LunoraError("INTERNAL", "@lunora/storage: prefix contains NUL byte");
170
171
  }
171
172
  const requested = listOptions.limit ?? DEFAULT_LIST_LIMIT;
172
173
  const limit = Math.min(Math.max(1, Math.floor(requested)), MAX_LIST_LIMIT);
@@ -175,7 +176,7 @@ const createStorage = (options) => {
175
176
  };
176
177
  const getUrl = (key) => {
177
178
  if (!options.publicBaseUrl) {
178
- throw new Error("@lunora/storage: `publicBaseUrl` is required for getUrl()");
179
+ throw new LunoraError("INTERNAL", "@lunora/storage: `publicBaseUrl` is required for getUrl()");
179
180
  }
180
181
  validateKey(key);
181
182
  const safeKey = key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
@@ -183,10 +184,10 @@ const createStorage = (options) => {
183
184
  };
184
185
  const getSignedUrl = async (key, signedOptions = {}) => {
185
186
  if (!options.publicBaseUrl) {
186
- throw new Error("@lunora/storage: `publicBaseUrl` is required for getSignedUrl()");
187
+ throw new LunoraError("INTERNAL", "@lunora/storage: `publicBaseUrl` is required for getSignedUrl()");
187
188
  }
188
189
  if (!options.signingSecret) {
189
- throw new Error("@lunora/storage: `signingSecret` is required for getSignedUrl()");
190
+ throw new LunoraError("INTERNAL", "@lunora/storage: `signingSecret` is required for getSignedUrl()");
190
191
  }
191
192
  validateKey(key);
192
193
  return buildSignedUrl({
@@ -201,7 +202,7 @@ const createStorage = (options) => {
201
202
  const createMultipartUpload = async (key, multipartOptions = {}) => {
202
203
  validateKey(key);
203
204
  if (!options.bucket.createMultipartUpload) {
204
- throw new Error("@lunora/storage: bucket binding does not support multipart uploads (createMultipartUpload)");
205
+ throw new LunoraError("INTERNAL", "@lunora/storage: bucket binding does not support multipart uploads (createMultipartUpload)");
205
206
  }
206
207
  return options.bucket.createMultipartUpload(key, {
207
208
  customMetadata: multipartOptions.customMetadata,
@@ -211,16 +212,19 @@ const createStorage = (options) => {
211
212
  const resumeMultipartUpload = (key, uploadId) => {
212
213
  validateKey(key);
213
214
  if (typeof uploadId !== "string" || uploadId.length === 0) {
214
- throw new Error("@lunora/storage: resumeMultipartUpload requires a non-empty uploadId");
215
+ throw new LunoraError("INTERNAL", "@lunora/storage: resumeMultipartUpload requires a non-empty uploadId");
215
216
  }
216
217
  if (!options.bucket.resumeMultipartUpload) {
217
- throw new Error("@lunora/storage: bucket binding does not support multipart uploads (resumeMultipartUpload)");
218
+ throw new LunoraError("INTERNAL", "@lunora/storage: bucket binding does not support multipart uploads (resumeMultipartUpload)");
218
219
  }
219
220
  return options.bucket.resumeMultipartUpload(key, uploadId);
220
221
  };
221
222
  const getPresignedUrl = async (key, presignedOptions = {}) => {
222
223
  if (!options.s3) {
223
- throw new Error("@lunora/storage: `s3` credentials are required for getPresignedUrl() — pass { accountId, accessKeyId, secretAccessKey, bucket }");
224
+ throw new LunoraError(
225
+ "INTERNAL",
226
+ "@lunora/storage: `s3` credentials are required for getPresignedUrl() — pass { accountId, accessKeyId, secretAccessKey, bucket }"
227
+ );
224
228
  }
225
229
  validateKey(key);
226
230
  return buildPresignedUrl({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/storage",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.5",
4
4
  "description": "R2-backed storage for Lunora: typed buckets and signed URLs",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -45,6 +45,9 @@
45
45
  "publishConfig": {
46
46
  "access": "public"
47
47
  },
48
+ "dependencies": {
49
+ "@lunora/errors": "1.0.0-alpha.1"
50
+ },
48
51
  "engines": {
49
52
  "node": "^22.15.0 || >=24.11.0"
50
53
  }