@velajs/storage 0.3.1 → 0.3.2

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/dist/body-BMUcBeQ0.js +93 -0
  3. package/dist/body-BMUcBeQ0.js.map +1 -0
  4. package/dist/client/index.d.ts +49 -46
  5. package/dist/client/index.js +207 -228
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/drivers/memory/index.d.ts +17 -13
  8. package/dist/drivers/memory/index.js +190 -197
  9. package/dist/drivers/memory/index.js.map +1 -0
  10. package/dist/drivers/r2/index.d.ts +7 -4
  11. package/dist/drivers/r2/index.js +162 -160
  12. package/dist/drivers/r2/index.js.map +1 -0
  13. package/dist/drivers/r2-http/index.d.ts +23 -19
  14. package/dist/drivers/r2-http/index.js +52 -45
  15. package/dist/drivers/r2-http/index.js.map +1 -0
  16. package/dist/drivers/s3/index.d.ts +79 -5
  17. package/dist/drivers/s3/index.js +2 -3
  18. package/dist/index.d.ts +271 -17
  19. package/dist/index.js +1078 -16
  20. package/dist/index.js.map +1 -0
  21. package/dist/middleware/index.d.ts +177 -15
  22. package/dist/middleware/index.js +553 -8
  23. package/dist/middleware/index.js.map +1 -0
  24. package/dist/protocol.types-C5PPBD12.d.ts +80 -0
  25. package/dist/r2.types-Bw4Ft83t.d.ts +71 -0
  26. package/dist/retry-DXtO6R9D.js +119 -0
  27. package/dist/retry-DXtO6R9D.js.map +1 -0
  28. package/dist/s3-DrE9NrnD.js +557 -0
  29. package/dist/s3-DrE9NrnD.js.map +1 -0
  30. package/dist/storage.error-DVCOkJhc.js +49 -0
  31. package/dist/storage.error-DVCOkJhc.js.map +1 -0
  32. package/dist/storage.types-BJN7Yp1J.d.ts +288 -0
  33. package/dist/storagesdk/index.d.ts +73 -69
  34. package/dist/storagesdk/index.js +117 -130
  35. package/dist/storagesdk/index.js.map +1 -0
  36. package/dist/stored-file-2Yl07eRc.js +105 -0
  37. package/dist/stored-file-2Yl07eRc.js.map +1 -0
  38. package/dist/testing/index.d.ts +12 -8
  39. package/dist/testing/index.js +36 -53
  40. package/dist/testing/index.js.map +1 -0
  41. package/package.json +62 -48
  42. package/dist/base64.d.ts +0 -8
  43. package/dist/base64.js +0 -25
  44. package/dist/decorators/inject-storage.decorator.d.ts +0 -17
  45. package/dist/decorators/inject-storage.decorator.js +0 -21
  46. package/dist/drivers/r2/r2.types.d.ts +0 -67
  47. package/dist/drivers/r2/r2.types.js +0 -5
  48. package/dist/drivers/s3/s3-client.d.ts +0 -33
  49. package/dist/drivers/s3/s3-client.js +0 -154
  50. package/dist/drivers/s3/s3.driver.d.ts +0 -4
  51. package/dist/drivers/s3/s3.driver.js +0 -407
  52. package/dist/drivers/s3/s3.types.d.ts +0 -28
  53. package/dist/drivers/s3/s3.types.js +0 -1
  54. package/dist/drivers/s3/xml.d.ts +0 -6
  55. package/dist/drivers/s3/xml.js +0 -52
  56. package/dist/http/authorizer.types.d.ts +0 -59
  57. package/dist/http/authorizer.types.js +0 -1
  58. package/dist/http/http-helpers.d.ts +0 -22
  59. package/dist/http/http-helpers.js +0 -80
  60. package/dist/http/protocol.types.d.ts +0 -76
  61. package/dist/http/protocol.types.js +0 -1
  62. package/dist/internal/body.d.ts +0 -18
  63. package/dist/internal/body.js +0 -90
  64. package/dist/internal/retry.d.ts +0 -41
  65. package/dist/internal/retry.js +0 -127
  66. package/dist/internal/stored-file.d.ts +0 -27
  67. package/dist/internal/stored-file.js +0 -114
  68. package/dist/middleware/cache.d.ts +0 -50
  69. package/dist/middleware/cache.js +0 -101
  70. package/dist/middleware/compose.d.ts +0 -12
  71. package/dist/middleware/compose.js +0 -11
  72. package/dist/middleware/compression.d.ts +0 -16
  73. package/dist/middleware/compression.js +0 -101
  74. package/dist/middleware/encryption.d.ts +0 -14
  75. package/dist/middleware/encryption.js +0 -134
  76. package/dist/middleware/failover.d.ts +0 -18
  77. package/dist/middleware/failover.js +0 -52
  78. package/dist/middleware/retry.d.ts +0 -14
  79. package/dist/middleware/retry.js +0 -47
  80. package/dist/middleware/versioning.d.ts +0 -36
  81. package/dist/middleware/versioning.js +0 -89
  82. package/dist/middleware/wrap.d.ts +0 -11
  83. package/dist/middleware/wrap.js +0 -46
  84. package/dist/object-key.d.ts +0 -17
  85. package/dist/object-key.js +0 -42
  86. package/dist/storage.controller.d.ts +0 -21
  87. package/dist/storage.controller.js +0 -386
  88. package/dist/storage.error.d.ts +0 -38
  89. package/dist/storage.error.js +0 -52
  90. package/dist/storage.facade.d.ts +0 -47
  91. package/dist/storage.facade.js +0 -443
  92. package/dist/storage.module.d.ts +0 -46
  93. package/dist/storage.module.js +0 -113
  94. package/dist/storage.service.d.ts +0 -42
  95. package/dist/storage.service.js +0 -86
  96. package/dist/storage.tokens.d.ts +0 -13
  97. package/dist/storage.tokens.js +0 -26
  98. package/dist/storage.types.d.ts +0 -245
  99. package/dist/storage.types.js +0 -1
@@ -1,407 +0,0 @@
1
- import { byteLengthOf } from "../../internal/body.js";
2
- import { createStoredFile } from "../../internal/stored-file.js";
3
- import { StorageError } from "../../storage.error.js";
4
- import { S3Client } from "./s3-client.js";
5
- import { escapeXml, tagBlocks, tagText } from "./xml.js";
6
- const encoder = new TextEncoder();
7
- const DEFAULT_EXPIRES = 3600;
8
- const DEFAULT_PART_SIZE = 5 * 1024 * 1024;
9
- function stripQuotes(s) {
10
- return s ? s.replace(/^"|"$/g, '') : undefined;
11
- }
12
- function parseDate(s) {
13
- if (!s) return undefined;
14
- const t = Date.parse(s);
15
- return Number.isFinite(t) ? t : undefined;
16
- }
17
- function formatRange(range) {
18
- return `bytes=${range.start}-${range.end ?? ''}`;
19
- }
20
- function metaFromHeaders(headers) {
21
- const meta = {};
22
- headers.forEach((value, key)=>{
23
- if (key.startsWith('x-amz-meta-')) meta[key.slice('x-amz-meta-'.length)] = value;
24
- });
25
- return Object.keys(meta).length ? meta : undefined;
26
- }
27
- function joinPublic(base, key) {
28
- const enc = key.split('/').map(encodeURIComponent).join('/');
29
- return `${base.replace(/\/$/, '')}/${enc}`;
30
- }
31
- /** Map an S3 `<Code>` (and/or HTTP status) to our error taxonomy. */ function mapS3Code(code, status) {
32
- switch(code){
33
- case 'NoSuchKey':
34
- case 'NoSuchUpload':
35
- case 'NoSuchBucket':
36
- return 'NotFound';
37
- case 'AccessDenied':
38
- case 'InvalidAccessKeyId':
39
- case 'SignatureDoesNotMatch':
40
- case 'AllAccessDisabled':
41
- return 'AccessDenied';
42
- case 'SlowDown':
43
- case 'RequestTimeout':
44
- return 'RateLimited';
45
- default:
46
- break;
47
- }
48
- if (status === 404) return 'NotFound';
49
- if (status === 403 || status === 401) return 'AccessDenied';
50
- if (status === 409) return 'Conflict';
51
- if (status === 429) return 'RateLimited';
52
- if (status >= 500) return 'Provider';
53
- if (status >= 400) return 'InvalidRequest';
54
- return 'Provider'; // e.g. a 200 response carrying an <Error> body
55
- }
56
- function toError(status, code, message, key) {
57
- const mapped = mapS3Code(code, status);
58
- const retryable = mapped === 'Provider' || mapped === 'RateLimited' || mapped === 'Timeout' || mapped === 'Network';
59
- return new StorageError(mapped, message ?? code ?? `S3 error${key ? ` (${key})` : ''}`, {
60
- status: status >= 400 ? status : undefined,
61
- retryable,
62
- // `message`/`code` are the provider's own <Message>/<Code> — never client-safe,
63
- // even on a 4xx status (e.g. AccessDenied/NoSuchKey text can carry internal detail).
64
- internal: true
65
- });
66
- }
67
- async function s3Error(res, key) {
68
- let code;
69
- let message;
70
- try {
71
- const body = await res.text();
72
- code = tagText(body, 'Code');
73
- message = tagText(body, 'Message');
74
- } catch {
75
- /* ignore body read errors */ }
76
- return toError(res.status, code, message, key);
77
- }
78
- function parseErrorBody(status, body, key) {
79
- return toError(status, tagText(body, 'Code'), tagText(body, 'Message'), key);
80
- }
81
- /** Add `duplex: 'half'` when streaming a request body (required by fetch). */ function withDuplex(init, body) {
82
- if (body instanceof ReadableStream) {
83
- return {
84
- ...init,
85
- duplex: 'half'
86
- };
87
- }
88
- return init;
89
- }
90
- /** S3 / S3-compatible driver signed with aws4fetch (edge-safe). */ export function s3Driver(options) {
91
- const client = new S3Client(options);
92
- const expires = options.defaultUrlExpiresIn ?? DEFAULT_EXPIRES;
93
- function uploadHeaders(opts, unsignedPayload) {
94
- const h = new Headers();
95
- if (unsignedPayload) h.set('x-amz-content-sha256', 'UNSIGNED-PAYLOAD');
96
- if (opts?.contentType) h.set('content-type', opts.contentType);
97
- if (opts?.cacheControl) h.set('cache-control', opts.cacheControl);
98
- if (opts?.metadata) for (const [k, v] of Object.entries(opts.metadata))h.set(`x-amz-meta-${k}`, v);
99
- return h;
100
- }
101
- function multipartHandle(key, uploadId, createOpts) {
102
- return {
103
- key,
104
- uploadId,
105
- async uploadPart (partNumber, part, o) {
106
- const url = client.objectUrl(key, {
107
- partNumber: String(partNumber),
108
- uploadId
109
- });
110
- const res = await client.send(url, withDuplex({
111
- method: 'PUT',
112
- headers: new Headers({
113
- 'x-amz-content-sha256': 'UNSIGNED-PAYLOAD'
114
- }),
115
- body: part,
116
- signal: o?.signal
117
- }, part));
118
- if (!res.ok) throw await s3Error(res, key);
119
- const etag = res.headers.get('etag');
120
- if (!etag) throw new StorageError('Provider', `missing ETag for part ${partNumber}`);
121
- return {
122
- partNumber,
123
- etag
124
- };
125
- },
126
- async complete (parts, o) {
127
- const xml = '<?xml version="1.0" encoding="UTF-8"?><CompleteMultipartUpload>' + [
128
- ...parts
129
- ].sort((a, b)=>a.partNumber - b.partNumber).map((p)=>`<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${escapeXml(p.etag)}</ETag></Part>`).join('') + '</CompleteMultipartUpload>';
130
- const res = await client.send(client.objectUrl(key, {
131
- uploadId
132
- }), {
133
- method: 'POST',
134
- headers: new Headers({
135
- 'content-type': 'application/xml'
136
- }),
137
- body: encoder.encode(xml),
138
- signal: o?.signal
139
- });
140
- const body = await res.text();
141
- if (!res.ok || body.includes('<Error')) throw parseErrorBody(res.status, body, key);
142
- return {
143
- key,
144
- size: 0,
145
- contentType: createOpts?.contentType ?? 'application/octet-stream',
146
- etag: stripQuotes(tagText(body, 'ETag'))
147
- };
148
- },
149
- async abort (o) {
150
- const res = await client.send(client.objectUrl(key, {
151
- uploadId
152
- }), {
153
- method: 'DELETE',
154
- signal: o?.signal
155
- });
156
- if (!res.ok && res.status !== 404) throw await s3Error(res, key);
157
- },
158
- async listParts (o) {
159
- const res = await client.send(client.objectUrl(key, {
160
- uploadId
161
- }), {
162
- method: 'GET',
163
- signal: o?.signal
164
- });
165
- const xml = await res.text();
166
- if (!res.ok) throw parseErrorBody(res.status, xml, key);
167
- return tagBlocks(xml, 'Part').map((b)=>({
168
- partNumber: Number(tagText(b, 'PartNumber') ?? 0),
169
- etag: stripQuotes(tagText(b, 'ETag')) ?? '',
170
- size: Number(tagText(b, 'Size') ?? 0)
171
- }));
172
- }
173
- };
174
- }
175
- const signedMultipart = {
176
- async create (key, o) {
177
- const res = await client.send(client.objectUrl(key, {
178
- uploads: ''
179
- }), {
180
- method: 'POST',
181
- headers: uploadHeaders({
182
- contentType: o.contentType,
183
- metadata: o.metadata
184
- }, false)
185
- });
186
- const xml = await res.text();
187
- if (!res.ok) throw parseErrorBody(res.status, xml, key);
188
- const uploadId = tagText(xml, 'UploadId');
189
- if (!uploadId) throw new StorageError('Parse', 'missing UploadId');
190
- return {
191
- uploadId,
192
- partSize: o.partSize ?? DEFAULT_PART_SIZE
193
- };
194
- },
195
- async signPart (key, uploadId, partNumber, o) {
196
- const url = await client.presign(key, 'PUT', o?.expiresIn ?? expires, {
197
- partNumber: String(partNumber),
198
- uploadId
199
- });
200
- return {
201
- url
202
- };
203
- },
204
- complete (key, uploadId, parts) {
205
- return multipartHandle(key, uploadId).complete(parts);
206
- },
207
- abort (key, uploadId) {
208
- return multipartHandle(key, uploadId).abort();
209
- }
210
- };
211
- return {
212
- name: options.name ?? 's3',
213
- raw: client,
214
- supportsRange: true,
215
- supportsDelimiter: true,
216
- supportsMetadata: true,
217
- supportsCacheControl: true,
218
- supportsServerSideCopy: true,
219
- reportsUploadProgress: false,
220
- signedUrl: {
221
- supported: true,
222
- upload: true
223
- },
224
- async upload (key, body, opts) {
225
- const size = byteLengthOf(body) ?? 0;
226
- const res = await client.send(client.objectUrl(key), withDuplex({
227
- method: 'PUT',
228
- headers: uploadHeaders(opts, true),
229
- body: body,
230
- signal: opts?.signal
231
- }, body));
232
- if (!res.ok) throw await s3Error(res, key);
233
- return {
234
- key,
235
- size,
236
- contentType: opts?.contentType ?? 'application/octet-stream',
237
- etag: stripQuotes(res.headers.get('etag')),
238
- lastModified: parseDate(res.headers.get('last-modified'))
239
- };
240
- },
241
- async download (key, opts) {
242
- const headers = new Headers();
243
- if (opts?.range) headers.set('Range', formatRange(opts.range));
244
- const res = await client.send(client.objectUrl(key), {
245
- method: 'GET',
246
- headers,
247
- signal: opts?.signal
248
- });
249
- if (!res.ok || !res.body) {
250
- if (res.status === 404) throw new StorageError('NotFound', `not found: ${key}`, {
251
- status: 404
252
- });
253
- throw await s3Error(res, key);
254
- }
255
- return createStoredFile({
256
- key,
257
- size: Number(res.headers.get('content-length') ?? 0),
258
- type: res.headers.get('content-type') ?? 'application/octet-stream',
259
- etag: stripQuotes(res.headers.get('etag')),
260
- lastModified: parseDate(res.headers.get('last-modified')),
261
- metadata: metaFromHeaders(res.headers)
262
- }, {
263
- kind: 'stream',
264
- stream: res.body
265
- });
266
- },
267
- async head (key, opts) {
268
- const res = await client.send(client.objectUrl(key), {
269
- method: 'HEAD',
270
- signal: opts?.signal
271
- });
272
- if (!res.ok) {
273
- if (res.status === 404) throw new StorageError('NotFound', `not found: ${key}`, {
274
- status: 404
275
- });
276
- throw await s3Error(res, key);
277
- }
278
- return createStoredFile({
279
- key,
280
- size: Number(res.headers.get('content-length') ?? 0),
281
- type: res.headers.get('content-type') ?? 'application/octet-stream',
282
- etag: stripQuotes(res.headers.get('etag')),
283
- lastModified: parseDate(res.headers.get('last-modified')),
284
- metadata: metaFromHeaders(res.headers)
285
- }, {
286
- kind: 'lazy',
287
- fetch: ()=>client.send(client.objectUrl(key), {
288
- method: 'GET'
289
- })
290
- });
291
- },
292
- async exists (key, opts) {
293
- const res = await client.send(client.objectUrl(key), {
294
- method: 'HEAD',
295
- signal: opts?.signal
296
- });
297
- if (res.status === 404) return false;
298
- if (!res.ok) throw await s3Error(res, key);
299
- return true;
300
- },
301
- async delete (key, opts) {
302
- const res = await client.send(client.objectUrl(key), {
303
- method: 'DELETE',
304
- signal: opts?.signal
305
- });
306
- if (!res.ok && res.status !== 404) throw await s3Error(res, key);
307
- },
308
- async copy (from, to, opts) {
309
- const source = `/${client.bucket}/${from.split('/').map(encodeURIComponent).join('/')}`;
310
- const res = await client.send(client.objectUrl(to), {
311
- method: 'PUT',
312
- headers: new Headers({
313
- 'x-amz-copy-source': source
314
- }),
315
- signal: opts?.signal
316
- });
317
- const body = await res.text();
318
- if (!res.ok || body.includes('<Error')) throw parseErrorBody(res.status, body, to);
319
- },
320
- async list (opts) {
321
- const query = {
322
- 'list-type': '2'
323
- };
324
- if (opts?.prefix) query.prefix = opts.prefix;
325
- if (opts?.delimiter) query.delimiter = opts.delimiter;
326
- if (opts?.cursor) query['continuation-token'] = opts.cursor;
327
- if (opts?.limit) query['max-keys'] = String(opts.limit);
328
- const res = await client.send(client.objectUrl('', query), {
329
- method: 'GET',
330
- signal: opts?.signal
331
- });
332
- const xml = await res.text();
333
- if (!res.ok) throw parseErrorBody(res.status, xml);
334
- const items = tagBlocks(xml, 'Contents').map((b)=>{
335
- const k = tagText(b, 'Key') ?? '';
336
- return createStoredFile({
337
- key: k,
338
- size: Number(tagText(b, 'Size') ?? 0),
339
- type: 'application/octet-stream',
340
- etag: stripQuotes(tagText(b, 'ETag')),
341
- lastModified: parseDate(tagText(b, 'LastModified'))
342
- }, {
343
- kind: 'lazy',
344
- fetch: ()=>client.send(client.objectUrl(k), {
345
- method: 'GET'
346
- })
347
- });
348
- });
349
- const prefixes = tagBlocks(xml, 'CommonPrefixes').map((b)=>tagText(b, 'Prefix')).filter((p)=>!!p);
350
- const cursor = tagText(xml, 'IsTruncated') === 'true' ? tagText(xml, 'NextContinuationToken') : undefined;
351
- return {
352
- items,
353
- prefixes: prefixes.length ? prefixes : undefined,
354
- cursor
355
- };
356
- },
357
- async url (key, opts) {
358
- const forceSign = !!opts?.responseContentDisposition;
359
- if (options.publicBaseUrl && !forceSign) return joinPublic(options.publicBaseUrl, key);
360
- const query = opts?.responseContentDisposition ? {
361
- 'response-content-disposition': opts.responseContentDisposition
362
- } : undefined;
363
- return client.presign(key, 'GET', opts?.expiresIn ?? expires, query);
364
- },
365
- async signedUploadUrl (key, opts) {
366
- if (opts.maxSize != null || opts.minSize != null) {
367
- const { url, fields } = await client.signPostPolicy(key, {
368
- expiresIn: opts.expiresIn,
369
- contentType: opts.contentType,
370
- maxSize: opts.maxSize,
371
- minSize: opts.minSize
372
- });
373
- return {
374
- method: 'POST',
375
- url,
376
- fields
377
- };
378
- }
379
- const url = await client.presign(key, 'PUT', opts.expiresIn);
380
- return {
381
- method: 'PUT',
382
- url,
383
- headers: opts.contentType ? {
384
- 'content-type': opts.contentType
385
- } : undefined
386
- };
387
- },
388
- async createMultipartUpload (key, opts) {
389
- const res = await client.send(client.objectUrl(key, {
390
- uploads: ''
391
- }), {
392
- method: 'POST',
393
- headers: uploadHeaders(opts, false),
394
- signal: opts?.signal
395
- });
396
- const xml = await res.text();
397
- if (!res.ok) throw parseErrorBody(res.status, xml, key);
398
- const uploadId = tagText(xml, 'UploadId');
399
- if (!uploadId) throw new StorageError('Parse', 'missing UploadId');
400
- return multipartHandle(key, uploadId, opts);
401
- },
402
- resumeMultipartUpload (key, uploadId) {
403
- return multipartHandle(key, uploadId);
404
- },
405
- signedMultipart
406
- };
407
- }
@@ -1,28 +0,0 @@
1
- export interface S3Credentials {
2
- accessKeyId: string;
3
- secretAccessKey: string;
4
- sessionToken?: string;
5
- }
6
- export interface S3DriverOptions {
7
- /**
8
- * Service endpoint. Examples:
9
- * - AWS: `https://s3.us-east-1.amazonaws.com`
10
- * - R2: `https://<accountId>.r2.cloudflarestorage.com`
11
- * - MinIO: `http://localhost:9000`
12
- */
13
- endpoint: string;
14
- /** AWS region (`auto` for R2). */
15
- region: string;
16
- bucket: string;
17
- credentials: S3Credentials;
18
- /** Path-style addressing (`endpoint/bucket/key`). Required by MinIO/R2/most non-AWS. */
19
- forcePathStyle?: boolean;
20
- /** CDN/public origin; when set, `url()` returns a permanent link unless signing is forced. */
21
- publicBaseUrl?: string;
22
- /** Default `expiresIn` (seconds) for presigned URLs. Default 3600. */
23
- defaultUrlExpiresIn?: number;
24
- /** Override the fetch implementation (tests). */
25
- fetch?: typeof fetch;
26
- /** Friendly driver name (defaults to `s3`). */
27
- name?: string;
28
- }
@@ -1 +0,0 @@
1
- export { };
@@ -1,6 +0,0 @@
1
- export declare function unescapeXml(s: string): string;
2
- export declare function escapeXml(s: string): string;
3
- /** First `<tag>…</tag>` inner text (entity-decoded), or `undefined`. */
4
- export declare function tagText(xml: string, tag: string): string | undefined;
5
- /** All `<tag>…</tag>` inner blocks (raw, not decoded — for nested extraction). */
6
- export declare function tagBlocks(xml: string, tag: string): string[];
@@ -1,52 +0,0 @@
1
- // Dependency-free, edge-safe S3 XML helpers. Workers has no `DOMParser` and
2
- // the AWS XML parser is banned, so we extract from S3's fixed, shallow,
3
- // documented response shapes (ListBucketResult, Initiate/CompleteMultipartUpload,
4
- // Error, ...) with anchored, ReDoS-free regexes. We never parse untrusted XML.
5
- const ENTITIES = {
6
- amp: '&',
7
- lt: '<',
8
- gt: '>',
9
- quot: '"',
10
- apos: "'"
11
- };
12
- export function unescapeXml(s) {
13
- return s.replace(/&(#x?[0-9a-fA-F]+|amp|lt|gt|quot|apos);/g, (_m, e)=>{
14
- if (e[0] === '#') {
15
- const code = e[1] === 'x' ? Number.parseInt(e.slice(2), 16) : Number.parseInt(e.slice(1), 10);
16
- return Number.isFinite(code) ? String.fromCodePoint(code) : _m;
17
- }
18
- return ENTITIES[e] ?? _m;
19
- });
20
- }
21
- export function escapeXml(s) {
22
- return s.replace(/[&<>"']/g, (c)=>{
23
- switch(c){
24
- case '&':
25
- return '&amp;';
26
- case '<':
27
- return '&lt;';
28
- case '>':
29
- return '&gt;';
30
- case '"':
31
- return '&quot;';
32
- default:
33
- return '&apos;';
34
- }
35
- });
36
- }
37
- // Each pattern uses a single non-greedy `[\s\S]*?` bounded by a literal close
38
- // tag → linear time, no nested quantifiers, no catastrophic backtracking.
39
- function tagPattern(tag) {
40
- return `<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)</${tag}>`;
41
- }
42
- /** First `<tag>…</tag>` inner text (entity-decoded), or `undefined`. */ export function tagText(xml, tag) {
43
- const m = new RegExp(tagPattern(tag)).exec(xml);
44
- return m ? unescapeXml(m[1]) : undefined;
45
- }
46
- /** All `<tag>…</tag>` inner blocks (raw, not decoded — for nested extraction). */ export function tagBlocks(xml, tag) {
47
- const re = new RegExp(tagPattern(tag), 'g');
48
- const out = [];
49
- let m;
50
- while((m = re.exec(xml)) !== null)out.push(m[1]);
51
- return out;
52
- }
@@ -1,59 +0,0 @@
1
- import type { Context } from 'hono';
2
- /**
3
- * The typed action a request wants to perform. The authorizer receives this
4
- * and can deny, allow, or allow-with-overrides (rewriting the effective key /
5
- * prefix / limits) — the load-bearing IDOR / cross-tenant guard.
6
- */
7
- export type StorageAction = {
8
- type: 'sign-upload';
9
- key: string;
10
- contentType?: string;
11
- size?: number;
12
- } | {
13
- type: 'multipart-create';
14
- key: string;
15
- contentType?: string;
16
- } | {
17
- type: 'multipart-sign-part';
18
- key: string;
19
- uploadId: string;
20
- partNumber: number;
21
- } | {
22
- type: 'multipart-complete';
23
- key: string;
24
- uploadId: string;
25
- } | {
26
- type: 'multipart-abort';
27
- key: string;
28
- uploadId: string;
29
- } | {
30
- type: 'download';
31
- key: string;
32
- } | {
33
- type: 'head';
34
- key: string;
35
- } | {
36
- type: 'list';
37
- prefix?: string;
38
- } | {
39
- type: 'delete';
40
- keys: string[];
41
- };
42
- export interface StorageAuthContext {
43
- /** The raw Web `Request` (edge-safe). */
44
- req: Request;
45
- /** The Hono context — `c.env` (bindings/secrets), `c.get('user')` from an upstream guard. */
46
- ctx: Context;
47
- /** The bucket name this controller is bound to. */
48
- driver: string;
49
- }
50
- /** Return this to ALLOW with server-side modifications. */
51
- export interface StorageAuthResult {
52
- key?: string;
53
- keys?: string[];
54
- prefix?: string;
55
- maxSize?: number;
56
- expiresIn?: number;
57
- metadata?: Record<string, string>;
58
- }
59
- export type StorageAuthorizer = (action: StorageAction, context: StorageAuthContext) => boolean | StorageAuthResult | Promise<boolean | StorageAuthResult>;
@@ -1 +0,0 @@
1
- export { };
@@ -1,22 +0,0 @@
1
- import type { StorageErrorCode } from '../storage.error';
2
- import type { ByteRange, StoredFile } from '../storage.types';
3
- import type { StorageWireErrorCode } from './protocol.types';
4
- /** Map a core error code to (wire code, HTTP status). */
5
- export declare function mapError(code: StorageErrorCode): {
6
- wire: StorageWireErrorCode;
7
- status: number;
8
- };
9
- export declare function serializeStored(file: StoredFile): {
10
- key: string;
11
- name: string;
12
- size: number;
13
- type: string;
14
- lastModified: number | undefined;
15
- etag: string | undefined;
16
- metadata: Record<string, string> | undefined;
17
- };
18
- export declare function clampExpiry(requested: number | undefined, def: number, max: number): number;
19
- /** Parse an HTTP `Range` header (single range) into a {@link ByteRange}. */
20
- export declare function parseRange(header: string | undefined | null): ByteRange | undefined;
21
- /** Content-Disposition header — defaults to `attachment` (anti stored-XSS). */
22
- export declare function dispositionHeader(disposition: string | undefined, name: string): string;
@@ -1,80 +0,0 @@
1
- /** Map a core error code to (wire code, HTTP status). */ export function mapError(code) {
2
- switch(code){
3
- case 'NotFound':
4
- return {
5
- wire: 'not_found',
6
- status: 404
7
- };
8
- case 'AccessDenied':
9
- case 'ReadOnly':
10
- return {
11
- wire: 'forbidden',
12
- status: 403
13
- };
14
- case 'Unsupported':
15
- return {
16
- wire: 'capability_unsupported',
17
- status: 400
18
- };
19
- case 'InvalidKey':
20
- return {
21
- wire: 'invalid_key',
22
- status: 400
23
- };
24
- case 'InvalidRequest':
25
- case 'Aborted':
26
- return {
27
- wire: 'invalid_request',
28
- status: 400
29
- };
30
- case 'Conflict':
31
- return {
32
- wire: 'conflict',
33
- status: 409
34
- };
35
- case 'RateLimited':
36
- return {
37
- wire: 'rate_limited',
38
- status: 429
39
- };
40
- case 'Timeout':
41
- return {
42
- wire: 'upstream_error',
43
- status: 504
44
- };
45
- default:
46
- return {
47
- wire: 'upstream_error',
48
- status: 502
49
- };
50
- }
51
- }
52
- export function serializeStored(file) {
53
- return {
54
- key: file.key,
55
- name: file.name,
56
- size: file.size,
57
- type: file.type,
58
- lastModified: file.lastModified,
59
- etag: file.etag,
60
- metadata: file.metadata
61
- };
62
- }
63
- export function clampExpiry(requested, def, max) {
64
- return Math.min(requested ?? def, max);
65
- }
66
- /** Parse an HTTP `Range` header (single range) into a {@link ByteRange}. */ export function parseRange(header) {
67
- if (!header) return undefined;
68
- const m = /^bytes=(\d+)-(\d*)$/.exec(header.trim());
69
- if (!m) return undefined;
70
- const start = Number(m[1]);
71
- const end = m[2] === '' ? undefined : Number(m[2]);
72
- return {
73
- start,
74
- end
75
- };
76
- }
77
- /** Content-Disposition header — defaults to `attachment` (anti stored-XSS). */ export function dispositionHeader(disposition, name) {
78
- const filename = `filename*=UTF-8''${encodeURIComponent(name)}`;
79
- return disposition === 'inline' ? `inline; ${filename}` : `attachment; ${filename}`;
80
- }