@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,76 +0,0 @@
1
- import type { SignedUpload, UploadResult, UploadedPart } from '../storage.types';
2
- export interface SignUploadRequest {
3
- key: string;
4
- contentType?: string;
5
- size?: number;
6
- metadata?: Record<string, string>;
7
- expiresIn?: number;
8
- }
9
- export interface MultipartCreateRequest {
10
- key: string;
11
- contentType?: string;
12
- metadata?: Record<string, string>;
13
- partSize?: number;
14
- }
15
- export interface SignPartRequest {
16
- key: string;
17
- uploadId: string;
18
- partNumber: number;
19
- }
20
- export interface MultipartCompleteRequest {
21
- key: string;
22
- uploadId: string;
23
- parts: UploadedPart[];
24
- }
25
- export interface MultipartAbortRequest {
26
- key: string;
27
- uploadId: string;
28
- }
29
- export interface DeleteRequest {
30
- keys: string[];
31
- }
32
- export interface SignUploadResponse {
33
- key: string;
34
- upload: SignedUpload;
35
- }
36
- export interface MultipartCreateResponse {
37
- key: string;
38
- uploadId: string;
39
- partSize: number;
40
- }
41
- export interface SignPartResponse {
42
- partNumber: number;
43
- method: 'PUT';
44
- url: string;
45
- headers?: Record<string, string>;
46
- }
47
- export type MultipartCompleteResponse = UploadResult;
48
- export interface OkResponse {
49
- ok: true;
50
- }
51
- /** JSON-safe {@link StoredFile} projection (no body accessors). */
52
- export interface StoredFileMeta {
53
- key: string;
54
- name: string;
55
- size: number;
56
- type: string;
57
- lastModified?: number;
58
- etag?: string;
59
- metadata?: Record<string, string>;
60
- }
61
- export interface ListResponse {
62
- items: StoredFileMeta[];
63
- prefixes?: string[];
64
- cursor?: string;
65
- }
66
- export interface SignDownloadResponse {
67
- url: string;
68
- expiresAt: number;
69
- }
70
- export type StorageWireErrorCode = 'forbidden' | 'invalid_key' | 'invalid_request' | 'not_found' | 'conflict' | 'rate_limited' | 'capability_unsupported' | 'upstream_error';
71
- export interface StorageErrorBody {
72
- error: {
73
- code: StorageWireErrorCode;
74
- message: string;
75
- };
76
- }
@@ -1 +0,0 @@
1
- export { };
@@ -1,18 +0,0 @@
1
- import type { Body } from '../storage.types';
2
- /** Byte length when known synchronously; `undefined` for unknown-length streams. */
3
- export declare function byteLengthOf(body: Body): number | undefined;
4
- /** True for the unknown-length stream case. */
5
- export declare function isStream(body: Body): body is ReadableStream<Uint8Array>;
6
- /** Buffer any body into bytes (used for hashing / part chunking). */
7
- export declare function toBytes(body: Body): Promise<Uint8Array>;
8
- /** View any body as a byte stream without buffering when it already is one. */
9
- export declare function toStream(body: Body): ReadableStream<Uint8Array>;
10
- /** Wrap a stream so `onLoaded` is called with the cumulative byte count. */
11
- export declare function countingStream(src: ReadableStream<Uint8Array>, onLoaded: (loaded: number) => void): ReadableStream<Uint8Array>;
12
- /** Concatenate ordered byte chunks into one buffer. */
13
- export declare function concatChunks(chunks: Uint8Array[]): Uint8Array;
14
- /**
15
- * Yield fixed-size (`partSize`) chunks from a byte stream, buffering only one
16
- * part at a time. The final chunk may be smaller. Empty input yields nothing.
17
- */
18
- export declare function chunkStream(src: ReadableStream<Uint8Array>, partSize: number): AsyncGenerator<Uint8Array>;
@@ -1,90 +0,0 @@
1
- const encoder = new TextEncoder();
2
- /** Byte length when known synchronously; `undefined` for unknown-length streams. */ export function byteLengthOf(body) {
3
- if (typeof body === 'string') return encoder.encode(body).byteLength;
4
- if (body instanceof Blob) return body.size; // File extends Blob
5
- if (body instanceof ArrayBuffer) return body.byteLength;
6
- if (ArrayBuffer.isView(body)) return body.byteLength;
7
- return undefined; // ReadableStream
8
- }
9
- /** True for the unknown-length stream case. */ export function isStream(body) {
10
- return body instanceof ReadableStream;
11
- }
12
- /** Buffer any body into bytes (used for hashing / part chunking). */ export async function toBytes(body) {
13
- if (typeof body === 'string') return encoder.encode(body);
14
- if (body instanceof Uint8Array) return body;
15
- if (body instanceof ArrayBuffer) return new Uint8Array(body);
16
- if (ArrayBuffer.isView(body)) {
17
- // Copy into a fresh ArrayBuffer-backed view (the source buffer is
18
- // ArrayBufferLike, which may be a SharedArrayBuffer).
19
- const slice = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
20
- return new Uint8Array(slice);
21
- }
22
- if (body instanceof Blob) return new Uint8Array(await body.arrayBuffer());
23
- return new Uint8Array(await new Response(body).arrayBuffer()); // ReadableStream
24
- }
25
- /** View any body as a byte stream without buffering when it already is one. */ export function toStream(body) {
26
- if (body instanceof ReadableStream) return body;
27
- const res = new Response(body);
28
- return res.body ?? emptyStream();
29
- }
30
- function emptyStream() {
31
- return new ReadableStream({
32
- start (controller) {
33
- controller.close();
34
- }
35
- });
36
- }
37
- /** Wrap a stream so `onLoaded` is called with the cumulative byte count. */ export function countingStream(src, onLoaded) {
38
- let loaded = 0;
39
- const reader = src.getReader();
40
- return new ReadableStream({
41
- async pull (controller) {
42
- const { done, value } = await reader.read();
43
- if (done) {
44
- controller.close();
45
- return;
46
- }
47
- loaded += value.byteLength;
48
- onLoaded(loaded);
49
- controller.enqueue(value);
50
- },
51
- cancel (reason) {
52
- return reader.cancel(reason);
53
- }
54
- });
55
- }
56
- /** Concatenate ordered byte chunks into one buffer. */ export function concatChunks(chunks) {
57
- let total = 0;
58
- for (const c of chunks)total += c.byteLength;
59
- const out = new Uint8Array(total);
60
- let offset = 0;
61
- for (const c of chunks){
62
- out.set(c, offset);
63
- offset += c.byteLength;
64
- }
65
- return out;
66
- }
67
- /**
68
- * Yield fixed-size (`partSize`) chunks from a byte stream, buffering only one
69
- * part at a time. The final chunk may be smaller. Empty input yields nothing.
70
- */ export async function* chunkStream(src, partSize) {
71
- const reader = src.getReader();
72
- let buffer = new Uint8Array(0);
73
- try {
74
- for(;;){
75
- const { done, value } = await reader.read();
76
- if (done) break;
77
- buffer = buffer.length === 0 ? value : concatChunks([
78
- buffer,
79
- value
80
- ]);
81
- while(buffer.byteLength >= partSize){
82
- yield buffer.subarray(0, partSize);
83
- buffer = buffer.subarray(partSize);
84
- }
85
- }
86
- if (buffer.byteLength > 0) yield buffer;
87
- } finally{
88
- reader.releaseLock();
89
- }
90
- }
@@ -1,41 +0,0 @@
1
- import { StorageError } from '../storage.error';
2
- import type { RetryOptions } from '../storage.types';
3
- /**
4
- * Combine multiple abort signals into one. The result aborts as soon as any
5
- * input aborts, adopting that input's reason. Listeners are cleaned up on
6
- * first abort. `undefined` inputs are ignored.
7
- */
8
- export declare function anySignal(signals: Array<AbortSignal | undefined>): AbortSignal;
9
- /** A controller that aborts after `ms`; call `clear()` to cancel the timer. */
10
- export declare function timeoutController(ms: number): {
11
- controller: AbortController;
12
- clear: () => void;
13
- };
14
- /** Resolve after `ms`, or reject with `Aborted` if `signal` fires first. */
15
- export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
16
- export interface NormalizedRetry {
17
- max: number;
18
- backoff: (ctx: {
19
- attempt: number;
20
- error: StorageError;
21
- }) => number;
22
- }
23
- export declare function normalizeRetry(retries: RetryOptions | undefined): NormalizedRetry;
24
- export interface RunOptions {
25
- retries?: RetryOptions;
26
- timeout?: number;
27
- signal?: AbortSignal;
28
- }
29
- /**
30
- * Run `fn` with per-attempt timeout, caller-abort propagation, and
31
- * retryable-error backoff. `fn` receives the combined per-attempt signal.
32
- *
33
- * - Caller-abort (`opts.signal`) → `Aborted`, never retried.
34
- * - Per-attempt timeout → `Timeout` (retryable by default, within budget).
35
- * - Only errors with `error.retryable` are retried, up to `retries.max`.
36
- */
37
- export declare function runWithRetry<T>(fn: (signal: AbortSignal | undefined) => Promise<T>, opts: RunOptions, onRetry?: (info: {
38
- attempt: number;
39
- delayMs: number;
40
- error: StorageError;
41
- }) => void): Promise<T>;
@@ -1,127 +0,0 @@
1
- import { StorageError, isAbort } from "../storage.error.js";
2
- // Retry/timeout/abort plumbing. Deliberately does NOT use `AbortSignal.any`
3
- // (only added in Node 20.3 — `engines.node >= 20` allows 20.0–20.2) nor
4
- // `AbortSignal.timeout`; both are re-implemented from `AbortController` +
5
- // `setTimeout` so the package runs on every target from 20.0 up.
6
- /**
7
- * Combine multiple abort signals into one. The result aborts as soon as any
8
- * input aborts, adopting that input's reason. Listeners are cleaned up on
9
- * first abort. `undefined` inputs are ignored.
10
- */ export function anySignal(signals) {
11
- const controller = new AbortController();
12
- const live = signals.filter((s)=>s != null);
13
- const onAbort = (reason)=>{
14
- cleanup();
15
- if (!controller.signal.aborted) controller.abort(reason);
16
- };
17
- const cleanup = ()=>{
18
- for (const s of live)s.removeEventListener('abort', handlers.get(s));
19
- };
20
- const handlers = new Map();
21
- for (const s of live){
22
- if (s.aborted) {
23
- controller.abort(s.reason);
24
- return controller.signal;
25
- }
26
- const h = ()=>onAbort(s.reason);
27
- handlers.set(s, h);
28
- s.addEventListener('abort', h, {
29
- once: true
30
- });
31
- }
32
- return controller.signal;
33
- }
34
- /** A controller that aborts after `ms`; call `clear()` to cancel the timer. */ export function timeoutController(ms) {
35
- const controller = new AbortController();
36
- const timer = setTimeout(()=>{
37
- controller.abort(new StorageError('Timeout', `operation timed out after ${ms}ms`));
38
- }, ms);
39
- return {
40
- controller,
41
- clear: ()=>clearTimeout(timer)
42
- };
43
- }
44
- /** Resolve after `ms`, or reject with `Aborted` if `signal` fires first. */ export function sleep(ms, signal) {
45
- return new Promise((resolve, reject)=>{
46
- if (signal?.aborted) {
47
- reject(new StorageError('Aborted', 'operation aborted'));
48
- return;
49
- }
50
- const timer = setTimeout(()=>{
51
- signal?.removeEventListener('abort', onAbort);
52
- resolve();
53
- }, ms);
54
- const onAbort = ()=>{
55
- clearTimeout(timer);
56
- reject(new StorageError('Aborted', 'operation aborted'));
57
- };
58
- signal?.addEventListener('abort', onAbort, {
59
- once: true
60
- });
61
- });
62
- }
63
- const DEFAULT_BASE_MS = 100;
64
- const DEFAULT_CAP_MS = 2000;
65
- function defaultBackoff({ attempt }) {
66
- const expo = Math.min(DEFAULT_CAP_MS, DEFAULT_BASE_MS * 2 ** (attempt - 1));
67
- return expo + Math.floor(Math.random() * DEFAULT_BASE_MS); // full-ish jitter
68
- }
69
- export function normalizeRetry(retries) {
70
- if (retries == null) return {
71
- max: 0,
72
- backoff: defaultBackoff
73
- };
74
- if (typeof retries === 'number') return {
75
- max: Math.max(0, retries),
76
- backoff: defaultBackoff
77
- };
78
- return {
79
- max: Math.max(0, retries.max),
80
- backoff: retries.backoff ?? defaultBackoff
81
- };
82
- }
83
- /**
84
- * Run `fn` with per-attempt timeout, caller-abort propagation, and
85
- * retryable-error backoff. `fn` receives the combined per-attempt signal.
86
- *
87
- * - Caller-abort (`opts.signal`) → `Aborted`, never retried.
88
- * - Per-attempt timeout → `Timeout` (retryable by default, within budget).
89
- * - Only errors with `error.retryable` are retried, up to `retries.max`.
90
- */ export async function runWithRetry(fn, opts, onRetry) {
91
- const { max, backoff } = normalizeRetry(opts.retries);
92
- const useTimeout = typeof opts.timeout === 'number' && opts.timeout > 0;
93
- let attempt = 0;
94
- for(;;){
95
- if (opts.signal?.aborted) throw new StorageError('Aborted', 'operation aborted');
96
- const to = useTimeout ? timeoutController(opts.timeout) : undefined;
97
- const signal = to ? anySignal([
98
- opts.signal,
99
- to.controller.signal
100
- ]) : opts.signal;
101
- try {
102
- return await fn(signal);
103
- } catch (e) {
104
- // Hard caller cancellation: never retried.
105
- if (opts.signal?.aborted) throw new StorageError('Aborted', 'operation aborted', {
106
- cause: e
107
- });
108
- const error = to?.controller.signal.aborted && isAbort(e) ? new StorageError('Timeout', `operation timed out after ${opts.timeout}ms`, {
109
- cause: e
110
- }) : StorageError.wrap(e);
111
- if (attempt >= max || !error.retryable) throw error;
112
- attempt += 1;
113
- const delayMs = Math.max(0, backoff({
114
- attempt,
115
- error
116
- }));
117
- onRetry?.({
118
- attempt,
119
- delayMs,
120
- error
121
- });
122
- await sleep(delayMs, opts.signal);
123
- } finally{
124
- to?.clear();
125
- }
126
- }
127
- }
@@ -1,27 +0,0 @@
1
- import type { StoredFile } from '../storage.types';
2
- export interface StoredFileMeta {
3
- key: string;
4
- name?: string;
5
- size: number;
6
- type?: string;
7
- lastModified?: number;
8
- etag?: string;
9
- metadata?: Record<string, string>;
10
- }
11
- /**
12
- * Where a {@link StoredFile}'s bytes come from:
13
- * - `bytes` — already in memory (memory driver, buffered reads).
14
- * - `stream` — a single-use byte stream (a live download response body).
15
- * - `lazy` — a thunk that issues the GET on first body access (head()).
16
- */
17
- export type StoredFileSource = {
18
- kind: 'bytes';
19
- bytes: Uint8Array;
20
- } | {
21
- kind: 'stream';
22
- stream: ReadableStream<Uint8Array>;
23
- } | {
24
- kind: 'lazy';
25
- fetch: () => Promise<Response>;
26
- };
27
- export declare function createStoredFile(meta: StoredFileMeta, source: StoredFileSource): StoredFile;
@@ -1,114 +0,0 @@
1
- import { StorageError } from "../storage.error.js";
2
- function nameFromKey(key) {
3
- const i = key.lastIndexOf('/');
4
- return i === -1 ? key : key.slice(i + 1);
5
- }
6
- function bytesToStream(bytes) {
7
- return new ReadableStream({
8
- start (controller) {
9
- controller.enqueue(bytes);
10
- controller.close();
11
- }
12
- });
13
- }
14
- function toArrayBuffer(bytes) {
15
- const ab = new ArrayBuffer(bytes.byteLength);
16
- new Uint8Array(ab).set(bytes);
17
- return ab;
18
- }
19
- let StoredFileImpl = class StoredFileImpl {
20
- key;
21
- name;
22
- size;
23
- type;
24
- lastModified;
25
- etag;
26
- metadata;
27
- #source;
28
- #bytes;
29
- #streamTaken = false;
30
- constructor(meta, source){
31
- this.key = meta.key;
32
- this.name = meta.name ?? nameFromKey(meta.key);
33
- this.size = meta.size;
34
- this.type = meta.type ?? 'application/octet-stream';
35
- this.lastModified = meta.lastModified;
36
- this.etag = meta.etag;
37
- this.metadata = meta.metadata;
38
- this.#source = source;
39
- if (source.kind === 'bytes') this.#bytes = source.bytes;
40
- }
41
- async #buffer() {
42
- if (this.#bytes) return this.#bytes;
43
- const s = this.#source;
44
- if (s.kind === 'bytes') {
45
- this.#bytes = s.bytes;
46
- return this.#bytes;
47
- }
48
- if (s.kind === 'stream') {
49
- if (this.#streamTaken) {
50
- throw new StorageError('Provider', `body of ${this.key} already consumed`);
51
- }
52
- this.#streamTaken = true;
53
- this.#bytes = new Uint8Array(await new Response(s.stream).arrayBuffer());
54
- return this.#bytes;
55
- }
56
- // s.kind === 'lazy'
57
- const res = await s.fetch();
58
- if (!res.ok) throw StorageError.fromStatus(res.status, `download failed: ${this.key}`);
59
- this.#bytes = new Uint8Array(await res.arrayBuffer());
60
- return this.#bytes;
61
- }
62
- async arrayBuffer() {
63
- return toArrayBuffer(await this.#buffer());
64
- }
65
- async text() {
66
- return new TextDecoder().decode(await this.#buffer());
67
- }
68
- async blob() {
69
- const b = await this.#buffer();
70
- return new Blob([
71
- toArrayBuffer(b)
72
- ], {
73
- type: this.type
74
- });
75
- }
76
- stream() {
77
- if (this.#bytes) return bytesToStream(this.#bytes);
78
- const s = this.#source;
79
- if (s.kind === 'bytes') return bytesToStream(s.bytes);
80
- if (s.kind === 'stream') {
81
- if (this.#streamTaken) {
82
- throw new StorageError('Provider', `body of ${this.key} already consumed`);
83
- }
84
- this.#streamTaken = true;
85
- return s.stream;
86
- }
87
- // lazy: fetch on first pull
88
- let reader;
89
- const fetchFn = s.fetch;
90
- const key = this.key;
91
- return new ReadableStream({
92
- async pull (controller) {
93
- let r = reader;
94
- if (!r) {
95
- const res = await fetchFn();
96
- if (!res.ok || !res.body) {
97
- throw StorageError.fromStatus(res.status, `download failed: ${key}`);
98
- }
99
- r = res.body.getReader();
100
- reader = r;
101
- }
102
- const { done, value } = await r.read();
103
- if (done) controller.close();
104
- else controller.enqueue(value);
105
- },
106
- cancel (reason) {
107
- return reader?.cancel(reason);
108
- }
109
- });
110
- }
111
- };
112
- export function createStoredFile(meta, source) {
113
- return new StoredFileImpl(meta, source);
114
- }
@@ -1,50 +0,0 @@
1
- import { type Middleware } from './wrap';
2
- export interface CachedMeta {
3
- key: string;
4
- name: string;
5
- size: number;
6
- type: string;
7
- lastModified?: number;
8
- etag?: string;
9
- metadata?: Record<string, string>;
10
- }
11
- export interface CacheEntry {
12
- expiresAt: number;
13
- kind: 'meta' | 'exists';
14
- meta?: CachedMeta;
15
- exists?: boolean;
16
- }
17
- type MaybePromise<T> = T | Promise<T>;
18
- export interface CacheStore {
19
- get(key: string): MaybePromise<CacheEntry | undefined>;
20
- set(key: string, value: CacheEntry, ttlMs: number): MaybePromise<void>;
21
- delete(key: string): MaybePromise<void>;
22
- }
23
- /** In-memory, per-isolate cache with lazy TTL + insertion-order eviction (no timers). */
24
- export declare class MapCacheStore implements CacheStore {
25
- #private;
26
- private readonly maxEntries;
27
- private readonly now;
28
- constructor(maxEntries?: number, now?: () => number);
29
- get(key: string): CacheEntry | undefined;
30
- set(key: string, value: CacheEntry): void;
31
- delete(key: string): void;
32
- }
33
- export interface CacheOptions {
34
- store?: CacheStore;
35
- ttlMs?: number;
36
- /** Which reads to cache. Default: head + exists (body/list are not cached). */
37
- cache?: {
38
- head?: boolean;
39
- exists?: boolean;
40
- };
41
- keyPrefix?: string;
42
- now?: () => number;
43
- }
44
- /**
45
- * Read-through cache for `head` / `exists`. Bodies are never cached (a `head`
46
- * hit still lazily fetches on body access), and `list` is not cached
47
- * (cross-prefix invalidation is intractable). Writes invalidate the key.
48
- */
49
- export declare function cache(opts?: CacheOptions): Middleware;
50
- export {};
@@ -1,101 +0,0 @@
1
- import { createStoredFile } from "../internal/stored-file.js";
2
- import { passthrough } from "./wrap.js";
3
- /** In-memory, per-isolate cache with lazy TTL + insertion-order eviction (no timers). */ export class MapCacheStore {
4
- maxEntries;
5
- now;
6
- #map = new Map();
7
- constructor(maxEntries = 1000, now = Date.now){
8
- this.maxEntries = maxEntries;
9
- this.now = now;
10
- }
11
- get(key) {
12
- const e = this.#map.get(key);
13
- if (!e) return undefined;
14
- if (e.expiresAt <= this.now()) {
15
- this.#map.delete(key);
16
- return undefined;
17
- }
18
- return e;
19
- }
20
- set(key, value) {
21
- this.#map.delete(key);
22
- this.#map.set(key, value);
23
- if (this.#map.size > this.maxEntries) {
24
- const oldest = this.#map.keys().next().value;
25
- if (oldest !== undefined) this.#map.delete(oldest);
26
- }
27
- }
28
- delete(key) {
29
- this.#map.delete(key);
30
- }
31
- }
32
- function toMeta(f) {
33
- return {
34
- key: f.key,
35
- name: f.name,
36
- size: f.size,
37
- type: f.type,
38
- lastModified: f.lastModified,
39
- etag: f.etag,
40
- metadata: f.metadata
41
- };
42
- }
43
- /**
44
- * Read-through cache for `head` / `exists`. Bodies are never cached (a `head`
45
- * hit still lazily fetches on body access), and `list` is not cached
46
- * (cross-prefix invalidation is intractable). Writes invalidate the key.
47
- */ export function cache(opts = {}) {
48
- const now = opts.now ?? Date.now;
49
- const store = opts.store ?? new MapCacheStore(1000, now);
50
- const ttl = opts.ttlMs ?? 60_000;
51
- const doHead = opts.cache?.head ?? true;
52
- const doExists = opts.cache?.exists ?? true;
53
- const ns = (k)=>`${opts.keyPrefix ?? ''}${k}`;
54
- return (inner)=>{
55
- const invalidate = async (...keys)=>{
56
- await Promise.all(keys.map((k)=>store.delete(ns(k))));
57
- };
58
- return passthrough(inner, {
59
- head: doHead ? async (k, o)=>{
60
- const hit = await store.get(ns(k));
61
- if (hit?.kind === 'meta' && hit.meta) {
62
- return createStoredFile(hit.meta, {
63
- kind: 'lazy',
64
- fetch: async ()=>new Response((await inner.download(k)).stream())
65
- });
66
- }
67
- const file = await inner.head(k, o);
68
- await store.set(ns(k), {
69
- kind: 'meta',
70
- meta: toMeta(file),
71
- expiresAt: now() + ttl
72
- }, ttl);
73
- return file;
74
- } : undefined,
75
- exists: doExists ? async (k, o)=>{
76
- const hit = await store.get(ns(k));
77
- if (hit?.kind === 'exists' && typeof hit.exists === 'boolean') return hit.exists;
78
- const exists = await inner.exists(k, o);
79
- await store.set(ns(k), {
80
- kind: 'exists',
81
- exists,
82
- expiresAt: now() + ttl
83
- }, ttl);
84
- return exists;
85
- } : undefined,
86
- upload: async (k, b, o)=>{
87
- const r = await inner.upload(k, b, o);
88
- await invalidate(k);
89
- return r;
90
- },
91
- delete: async (k, o)=>{
92
- await inner.delete(k, o);
93
- await invalidate(k);
94
- },
95
- copy: async (f, t, o)=>{
96
- await inner.copy(f, t, o);
97
- await invalidate(t);
98
- }
99
- });
100
- };
101
- }
@@ -1,12 +0,0 @@
1
- import type { StorageDriver } from '../storage.types';
2
- import type { Middleware } from './wrap';
3
- /**
4
- * Compose middlewares over a base driver. Listed innermost → outermost:
5
- * `compose(base, a, b, c)` yields `c(b(a(base)))`, so on any call `c` runs
6
- * first (outermost) and `base` last. The same wrapper handles read and write,
7
- * so the reverse order on download comes for free.
8
- *
9
- * There is deliberately NO default preset — each middleware is opt-in, so you
10
- * never accidentally ship, e.g., compress-then-encrypt (a CRIME/BREACH vector).
11
- */
12
- export declare function compose(base: StorageDriver, ...middlewares: Middleware[]): StorageDriver;
@@ -1,11 +0,0 @@
1
- /**
2
- * Compose middlewares over a base driver. Listed innermost → outermost:
3
- * `compose(base, a, b, c)` yields `c(b(a(base)))`, so on any call `c` runs
4
- * first (outermost) and `base` last. The same wrapper handles read and write,
5
- * so the reverse order on download comes for free.
6
- *
7
- * There is deliberately NO default preset — each middleware is opt-in, so you
8
- * never accidentally ship, e.g., compress-then-encrypt (a CRIME/BREACH vector).
9
- */ export function compose(base, ...middlewares) {
10
- return middlewares.reduce((driver, wrap)=>wrap(driver), base);
11
- }