@burojs/file-storage 0.2.0 → 0.4.0

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.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { Plugin, AppRuntime } from '@burojs/core';
2
-
1
+ import { AppRuntime, Plugin } from "@burojs/core";
2
+ //#region src/types.d.ts
3
3
  /** How a file's bytes travel relative to the record mutation. */
4
4
  type UploadPipeline = 'immediate' | 'fused' | 'handshake' | 'resumable';
5
5
  /**
@@ -30,51 +30,51 @@ type OrphanPolicy = 'best-effort' | 'keep';
30
30
  * so, rather than leaving the consumer to guess.
31
31
  */
32
32
  type ResolvedUrl = string | {
33
- url: string;
34
- expiresAt: number;
33
+ url: string;
34
+ expiresAt: number;
35
35
  };
36
36
  /** The framework's in-memory normal form for one file. */
37
37
  interface FileRef {
38
- /** Client-side identity. Present from the moment the file is picked. */
39
- localId: string;
40
- /**
41
- * Storage identity — id, key, path or filename, whichever this backend
42
- * uses. Absent until the bytes have landed, and always absent for `fused`
43
- * until the mutation succeeds.
44
- */
45
- handle?: string;
46
- /** Raw bytes not yet sent. Present for `fused` and for `defer: true`. */
47
- pending?: File;
48
- name?: string;
49
- mime?: string;
50
- size?: number;
51
- width?: number;
52
- height?: number;
53
- /**
54
- * Metadata that belongs to THIS REFERENCE rather than to the file — a
55
- * Sanity-style hotspot/crop, an alt text that differs per usage, a sort
56
- * position. Opaque to the framework; carried through to the renderer.
57
- */
58
- usage?: Record<string, unknown>;
59
- /**
60
- * The original column entry this ref was read from. Absent for newly added
61
- * files.
62
- *
63
- * ⚠️ `write` MUST merge onto this rather than rebuild the value. A Strapi
64
- * media object carries `provider_metadata`, `createdAt`, `caption` and more;
65
- * reading four known keys and writing four keys back DELETES the rest.
66
- */
67
- raw?: unknown;
38
+ /** Client-side identity. Present from the moment the file is picked. */
39
+ localId: string;
40
+ /**
41
+ * Storage identity — id, key, path or filename, whichever this backend
42
+ * uses. Absent until the bytes have landed, and always absent for `fused`
43
+ * until the mutation succeeds.
44
+ */
45
+ handle?: string;
46
+ /** Raw bytes not yet sent. Present for `fused` and for `defer: true`. */
47
+ pending?: File;
48
+ name?: string;
49
+ mime?: string;
50
+ size?: number;
51
+ width?: number;
52
+ height?: number;
53
+ /**
54
+ * Metadata that belongs to THIS REFERENCE rather than to the file — a
55
+ * Sanity-style hotspot/crop, an alt text that differs per usage, a sort
56
+ * position. Opaque to the framework; carried through to the renderer.
57
+ */
58
+ usage?: Record<string, unknown>;
59
+ /**
60
+ * The original column entry this ref was read from. Absent for newly added
61
+ * files.
62
+ *
63
+ * ⚠️ `write` MUST merge onto this rather than rebuild the value. A Strapi
64
+ * media object carries `provider_metadata`, `createdAt`, `caption` and more;
65
+ * reading four known keys and writing four keys back DELETES the rest.
66
+ */
67
+ raw?: unknown;
68
68
  }
69
69
  interface MapperCtx {
70
- /** Registry name of the resource being read or written. */
71
- resource: string;
72
- /** Field name this column belongs to. */
73
- field: string;
74
- /** Declared cardinality — a single-value mapper must not emit an array. */
75
- multiple: boolean;
76
- /** The record the column belongs to. Absent on create. */
77
- record?: Record<string, unknown>;
70
+ /** Registry name of the resource being read or written. */
71
+ resource: string;
72
+ /** Field name this column belongs to. */
73
+ field: string;
74
+ /** Declared cardinality — a single-value mapper must not emit an array. */
75
+ multiple: boolean;
76
+ /** The record the column belongs to. Absent on create. */
77
+ record?: Record<string, unknown>;
78
78
  }
79
79
  /**
80
80
  * Translates between the record's column and the normalised form.
@@ -87,88 +87,91 @@ interface MapperCtx {
87
87
  * only an id.
88
88
  */
89
89
  interface FileMapper<TRead = unknown, TWrite = TRead> {
90
- read(column: TRead, ctx: MapperCtx): FileRef[];
91
- /**
92
- * `prev` is not decoration: some backends update a multi-value field by
93
- * DIFF rather than replacement (PocketBase uses `field+` to add and
94
- * `field-` to remove), and a diff cannot be computed without the old value.
95
- */
96
- write(next: FileRef[], prev: FileRef[], ctx: MapperCtx): TWrite;
97
- /**
98
- * Declares that `write` carries a `pending`-only ref (no `handle` yet)
99
- * forward into its output — typically by embedding the raw `File` itself —
100
- * rather than silently omitting it.
101
- *
102
- * This matters because a ref on a `fused`-pipeline storage NEVER has a
103
- * `handle` before the record mutation runs: the bytes ride the mutation
104
- * itself, so `pending` is all a freshly-added file ever has until the
105
- * backend accepts it. A mapper that only knows how to write a `handle`
106
- * (the ordinary case — `richImage`, `junction`) silently drops that file
107
- * with no error the moment it is paired with a `fused` adapter.
108
- * `assertFileStorageWiring` refuses that pairing at boot rather than
109
- * discovering it via a vanished upload.
110
- *
111
- * Absent/false is the safe default: a mapper written assuming every ref
112
- * already has a handle by the time `write` sees it should not claim
113
- * otherwise. `scalarHandle` and `pocketbaseDiff` are the two mappers that
114
- * set this.
115
- */
116
- carriesPending?: boolean;
90
+ read(column: TRead, ctx: MapperCtx): FileRef[];
91
+ /**
92
+ * `prev` is not decoration: some backends update a multi-value field by
93
+ * DIFF rather than replacement (PocketBase uses `field+` to add and
94
+ * `field-` to remove), and a diff cannot be computed without the old value.
95
+ */
96
+ write(next: FileRef[], prev: FileRef[], ctx: MapperCtx): TWrite;
97
+ /**
98
+ * Declares that `write` carries a `pending`-only ref (no `handle` yet)
99
+ * forward into its output — typically by embedding the raw `File` itself —
100
+ * rather than silently omitting it.
101
+ *
102
+ * This matters because a ref on a `fused`-pipeline storage NEVER has a
103
+ * `handle` before the record mutation runs: the bytes ride the mutation
104
+ * itself, so `pending` is all a freshly-added file ever has until the
105
+ * backend accepts it. A mapper that only knows how to write a `handle`
106
+ * (the ordinary case — `richImage`, `junction`) silently drops that file
107
+ * with no error the moment it is paired with a `fused` adapter.
108
+ * `assertFileStorageWiring` refuses that pairing at boot rather than
109
+ * discovering it via a vanished upload.
110
+ *
111
+ * Absent/false is the safe default: a mapper written assuming every ref
112
+ * already has a handle by the time `write` sees it should not claim
113
+ * otherwise. `scalarHandle` and `pocketbaseDiff` are the two mappers that
114
+ * set this.
115
+ */
116
+ carriesPending?: boolean;
117
117
  }
118
118
  interface UploadCtx {
119
- signal: AbortSignal;
120
- onProgress: (loaded: number, total: number) => void;
119
+ signal: AbortSignal;
120
+ onProgress: (loaded: number, total: number) => void;
121
121
  }
122
122
  interface ResolveCtx {
123
- variant?: {
124
- width?: number;
125
- height?: number;
126
- };
127
- disposition?: 'inline' | 'attachment';
128
- resource?: string;
129
- record?: Record<string, unknown>;
123
+ variant?: {
124
+ width?: number;
125
+ height?: number;
126
+ };
127
+ disposition?: 'inline' | 'attachment';
128
+ resource?: string;
129
+ record?: Record<string, unknown>;
130
130
  }
131
131
  interface FileStorageAdapter {
132
- pipeline: UploadPipeline;
133
- /** Used by every field that does not name its own mapper. */
134
- defaultMapper: FileMapper;
135
- /** Only meaningful when `pipeline === 'fused'`. */
136
- encoding?: FusedEncoding;
137
- /** Storage-level default; a field may override. */
138
- orphans?: OrphanPolicy;
139
- /** For a `fused` adapter this is a no-op — the bytes have nowhere to go yet. */
140
- upload(file: File, ctx: UploadCtx): Promise<FileRef>;
141
- resolveUrl(ref: FileRef, ctx: ResolveCtx): ResolvedUrl | Promise<ResolvedUrl>;
142
- /**
143
- * Batched metadata read. Implement only when the column holds a bare handle
144
- * with no name/mime/size — otherwise the framework never calls it.
145
- *
146
- * Honesty rule: a field this backend cannot report is left ABSENT on the
147
- * returned ref. Never guess, and never substitute a zero.
148
- */
149
- stat?(refs: FileRef[]): Promise<FileRef[]>;
150
- remove?(ref: FileRef): Promise<void>;
132
+ pipeline: UploadPipeline;
133
+ /** Used by every field that does not name its own mapper. */
134
+ defaultMapper: FileMapper;
135
+ /** Only meaningful when `pipeline === 'fused'`. */
136
+ encoding?: FusedEncoding;
137
+ /** Storage-level default; a field may override. */
138
+ orphans?: OrphanPolicy;
139
+ /** For a `fused` adapter this is a no-op — the bytes have nowhere to go yet. */
140
+ upload(file: File, ctx: UploadCtx): Promise<FileRef>;
141
+ resolveUrl(ref: FileRef, ctx: ResolveCtx): ResolvedUrl | Promise<ResolvedUrl>;
142
+ /**
143
+ * Batched metadata read. Implement only when the column holds a bare handle
144
+ * with no name/mime/size — otherwise the framework never calls it.
145
+ *
146
+ * Honesty rule: a field this backend cannot report is left ABSENT on the
147
+ * returned ref. Never guess, and never substitute a zero.
148
+ */
149
+ stat?(refs: FileRef[]): Promise<FileRef[]>;
150
+ remove?(ref: FileRef): Promise<void>;
151
151
  }
152
152
  interface FileStorageRegistry {
153
- /** `undefined` resolves the `default` entry. Throws on an unknown name. */
154
- get(name?: string): FileStorageAdapter;
155
- has(name: string): boolean;
156
- names(): string[];
157
- /** True when any registered adapter carries its bytes inside the mutation. */
158
- hasFused(): boolean;
153
+ /** `undefined` resolves the `default` entry. Throws on an unknown name. */
154
+ get(name?: string): FileStorageAdapter;
155
+ has(name: string): boolean;
156
+ names(): string[];
157
+ /** True when any registered adapter carries its bytes inside the mutation. */
158
+ hasFused(): boolean;
159
159
  }
160
-
160
+ //#endregion
161
+ //#region src/registry.d.ts
161
162
  /** Service name under which the registry is registered on the app. */
162
163
  declare const FILE_STORAGE_SERVICE: "fileStorage";
163
164
  declare function createFileStorageRegistry(map: Record<string, FileStorageAdapter>): FileStorageRegistry;
164
-
165
+ //#endregion
166
+ //#region src/plugin.d.ts
165
167
  /**
166
168
  * Declare the app's file storages as a named map, mirroring the existing
167
169
  * multi-data-provider convention. `default` is required; other keys are free
168
170
  * form and are what a field names in `f.file({ fileStorage: '…' })`.
169
171
  */
170
172
  declare function fileStoragePlugin(map: Record<string, FileStorageAdapter>): Plugin;
171
-
173
+ //#endregion
174
+ //#region src/mappers/ref.d.ts
172
175
  /**
173
176
  * Mint a ref with a fresh client-side identity.
174
177
  *
@@ -177,7 +180,8 @@ declare function fileStoragePlugin(map: Record<string, FileStorageAdapter>): Plu
177
180
  * readable and snapshots stable.
178
181
  */
179
182
  declare function newRef(init?: Partial<FileRef>): FileRef;
180
-
183
+ //#endregion
184
+ //#region src/kind.d.ts
181
185
  /**
182
186
  * Kind by mime; the filename is consulted only when mime is unknown.
183
187
  *
@@ -190,7 +194,8 @@ declare function newRef(init?: Partial<FileRef>): FileRef;
190
194
  * of the stored string, which can never match a uuid or other opaque handle.
191
195
  */
192
196
  declare function isImage(ref: FileRef): boolean;
193
-
197
+ //#endregion
198
+ //#region src/mappers/scalar-handle.d.ts
194
199
  type ScalarColumn = string | string[] | null | undefined;
195
200
  type ScalarWriteValue = string | File | (string | File)[] | null;
196
201
  /**
@@ -200,33 +205,35 @@ type ScalarWriteValue = string | File | (string | File)[] | null;
200
205
  * in sync for no behavioural gain.
201
206
  */
202
207
  declare function scalarHandle(): FileMapper<ScalarColumn, ScalarWriteValue>;
203
-
208
+ //#endregion
209
+ //#region src/mappers/rich-image.d.ts
204
210
  interface RichImageOpts {
205
- /**
206
- * Who owns the focal point.
207
- *
208
- * - `'usage'` — it lives on the reference inside the record, so the same
209
- * asset crops differently in two records (Sanity-shaped).
210
- * - `'file'` — it is a property of the file that the storage owns; the
211
- * mapper leaves it alone (Directus-shaped).
212
- * - `false` — this column has none.
213
- *
214
- * There is no default. Guessing wrong silently corrupts a neighbouring
215
- * record's crop, which is exactly the failure nobody notices for months.
216
- */
217
- focus: 'usage' | 'file' | false;
218
- /** Dotted path to the handle inside the object. Defaults to `'id'`. */
219
- handleKey?: string;
220
- /** Keys lifted into `usage` when `focus === 'usage'`. */
221
- usageKeys?: string[];
211
+ /**
212
+ * Who owns the focal point.
213
+ *
214
+ * - `'usage'` — it lives on the reference inside the record, so the same
215
+ * asset crops differently in two records (Sanity-shaped).
216
+ * - `'file'` — it is a property of the file that the storage owns; the
217
+ * mapper leaves it alone (Directus-shaped).
218
+ * - `false` — this column has none.
219
+ *
220
+ * There is no default. Guessing wrong silently corrupts a neighbouring
221
+ * record's crop, which is exactly the failure nobody notices for months.
222
+ */
223
+ focus: 'usage' | 'file' | false;
224
+ /** Dotted path to the handle inside the object. Defaults to `'id'`. */
225
+ handleKey?: string;
226
+ /** Keys lifted into `usage` when `focus === 'usage'`. */
227
+ usageKeys?: string[];
222
228
  }
223
229
  declare function richImage(opts: RichImageOpts): FileMapper<unknown, unknown>;
224
-
230
+ //#endregion
231
+ //#region src/mappers/junction.d.ts
225
232
  interface JunctionOpts {
226
- /** Column on the junction row holding the file's handle. */
227
- fileKey: string;
228
- /** Column holding the 1-based position. Omit when the table is unordered. */
229
- sortKey?: string;
233
+ /** Column on the junction row holding the file's handle. */
234
+ fileKey: string;
235
+ /** Column holding the 1-based position. Omit when the table is unordered. */
236
+ sortKey?: string;
230
237
  }
231
238
  type Row = Record<string, unknown>;
232
239
  /**
@@ -235,39 +242,40 @@ type Row = Record<string, unknown>;
235
242
  * must survive a reorder: dropping it would delete and recreate every row.
236
243
  */
237
244
  declare function junction(opts: JunctionOpts): FileMapper<Row[] | null | undefined, Row[]>;
238
-
245
+ //#endregion
246
+ //#region src/contract/run-contract.d.ts
239
247
  interface ContractOpts {
240
- /** A FRESH adapter per case — shared state between cases hides bugs. */
241
- adapter: () => FileStorageAdapter;
242
- mapper: FileMapper;
243
- /** Representative column values this mapper reads, for the invariants. */
244
- sampleColumn: {
245
- single: unknown;
246
- multiple: unknown;
247
- };
248
- /** Skip upload cases for adapters whose bytes ride the mutation. */
249
- skipUpload?: boolean;
250
- /**
251
- * Set for a mapper whose `write` returns a COLUMN PATCH rather than a column
252
- * value — `pocketbaseDiff` is the one in this package.
253
- *
254
- * Round-trip identity is meaningless for such a mapper: it never returns the
255
- * value it was given, it returns the keys needed to change it. The patch
256
- * invariants below replace the round-trip cases rather than sitting beside
257
- * them, because running both would demand two contradictory things of one
258
- * `write`.
259
- */
260
- writesColumnPatch?: boolean;
261
- /**
262
- * Context every `resolveUrl` case is called with. Defaults to `{}`.
263
- *
264
- * Supply it for an adapter whose URL cannot be built from a handle alone.
265
- * A PocketBase filename is meaningless by itself — the URL is assembled from
266
- * collection + record + filename — so that adapter correctly THROWS on an
267
- * empty context, and the contract must hand it a real one rather than
268
- * treating the throw as a conformance failure.
269
- */
270
- resolveCtx?: ResolveCtx;
248
+ /** A FRESH adapter per case — shared state between cases hides bugs. */
249
+ adapter: () => FileStorageAdapter;
250
+ mapper: FileMapper;
251
+ /** Representative column values this mapper reads, for the invariants. */
252
+ sampleColumn: {
253
+ single: unknown;
254
+ multiple: unknown;
255
+ };
256
+ /** Skip upload cases for adapters whose bytes ride the mutation. */
257
+ skipUpload?: boolean;
258
+ /**
259
+ * Set for a mapper whose `write` returns a COLUMN PATCH rather than a column
260
+ * value — `pocketbaseDiff` is the one in this package.
261
+ *
262
+ * Round-trip identity is meaningless for such a mapper: it never returns the
263
+ * value it was given, it returns the keys needed to change it. The patch
264
+ * invariants below replace the round-trip cases rather than sitting beside
265
+ * them, because running both would demand two contradictory things of one
266
+ * `write`.
267
+ */
268
+ writesColumnPatch?: boolean;
269
+ /**
270
+ * Context every `resolveUrl` case is called with. Defaults to `{}`.
271
+ *
272
+ * Supply it for an adapter whose URL cannot be built from a handle alone.
273
+ * A PocketBase filename is meaningless by itself — the URL is assembled from
274
+ * collection + record + filename — so that adapter correctly THROWS on an
275
+ * empty context, and the contract must hand it a real one rather than
276
+ * treating the throw as a conformance failure.
277
+ */
278
+ resolveCtx?: ResolveCtx;
271
279
  }
272
280
  /**
273
281
  * The test runner, injected.
@@ -278,8 +286,8 @@ interface ContractOpts {
278
286
  * or node:test run the same contract.
279
287
  */
280
288
  interface ContractRunner {
281
- it: (name: string, fn: () => void | Promise<void>) => void;
282
- expect: (actual: never) => unknown;
289
+ it: (name: string, fn: () => void | Promise<void>) => void;
290
+ expect: (actual: never) => unknown;
283
291
  }
284
292
  /**
285
293
  * The conformance suite every adapter owes. Call inside a `describe`.
@@ -289,16 +297,17 @@ interface ContractRunner {
289
297
  * if it is executable by the person doing the stretching.
290
298
  */
291
299
  declare function runFileStorageContract(opts: ContractOpts, runner: ContractRunner): void;
292
-
300
+ //#endregion
301
+ //#region src/contract/fake-adapter.d.ts
293
302
  interface FakeAdapterOpts {
294
- /** Emit this many progress ticks before resolving. Default 2. */
295
- progressTicks?: number;
296
- /** Make every upload reject with this message. */
297
- failWith?: string;
298
- /** Return signed URLs that expire this many ms from resolution. */
299
- signedTtlMs?: number;
300
- /** Default `'immediate'`. Set `'fused'` to stand in for an inline/PocketBase-shaped storage. */
301
- pipeline?: 'immediate' | 'fused';
303
+ /** Emit this many progress ticks before resolving. Default 2. */
304
+ progressTicks?: number;
305
+ /** Make every upload reject with this message. */
306
+ failWith?: string;
307
+ /** Return signed URLs that expire this many ms from resolution. */
308
+ signedTtlMs?: number;
309
+ /** Default `'immediate'`. Set `'fused'` to stand in for an inline/PocketBase-shaped storage. */
310
+ pipeline?: 'immediate' | 'fused';
302
311
  }
303
312
  /**
304
313
  * In-memory adapter. Exists so the contract suite can be tested against
@@ -306,23 +315,24 @@ interface FakeAdapterOpts {
306
315
  * when their own adapter fails a case.
307
316
  */
308
317
  declare function createFakeAdapter(opts?: FakeAdapterOpts): FileStorageAdapter & {
309
- stored: Map<string, File>;
310
- removed: string[];
318
+ stored: Map<string, File>;
319
+ removed: string[];
311
320
  };
312
-
321
+ //#endregion
322
+ //#region src/adapters/inline.d.ts
313
323
  interface InlineFileStorageOpts {
314
- /**
315
- * How the data-provider wrapper should encode the bytes into the mutation.
316
- * `base64` transforms the value; `multipart` and `graphql-upload` set the
317
- * request body and are mutually exclusive with each other.
318
- */
319
- encoding: FusedEncoding;
320
- /**
321
- * How a saved file is read back. `ctx.resource` and `ctx.record` are given
322
- * because backends of this shape usually address files through the record
323
- * that owns them.
324
- */
325
- resolveUrl: (ref: FileRef, ctx: ResolveCtx) => ResolvedUrl | Promise<ResolvedUrl>;
324
+ /**
325
+ * How the data-provider wrapper should encode the bytes into the mutation.
326
+ * `base64` transforms the value; `multipart` and `graphql-upload` set the
327
+ * request body and are mutually exclusive with each other.
328
+ */
329
+ encoding: FusedEncoding;
330
+ /**
331
+ * How a saved file is read back. `ctx.resource` and `ctx.record` are given
332
+ * because backends of this shape usually address files through the record
333
+ * that owns them.
334
+ */
335
+ resolveUrl: (ref: FileRef, ctx: ResolveCtx) => ResolvedUrl | Promise<ResolvedUrl>;
326
336
  }
327
337
  /**
328
338
  * The storage that is not a storage: the backend takes the file inside the
@@ -334,19 +344,20 @@ interface InlineFileStorageOpts {
334
344
  * request path — that can report it.
335
345
  */
336
346
  declare function inlineFileStorage(opts: InlineFileStorageOpts): FileStorageAdapter;
337
-
347
+ //#endregion
348
+ //#region src/adapters/nhost.d.ts
338
349
  interface NhostFileStorageOpts {
339
- /** Storage service base URL, e.g. `https://<project>.storage.<region>.nhost.run`. */
340
- baseUrl: string;
341
- /**
342
- * Access token supplier. Injected rather than read from localStorage: the
343
- * v0.2 implementation hardcoded a storage key and a window global, which
344
- * meant one app shape was supported and no test could drive it.
345
- */
346
- getToken: () => string | null | Promise<string | null>;
347
- bucketId?: string;
348
- orphans?: OrphanPolicy;
349
- fetchImpl?: typeof fetch;
350
+ /** Storage service base URL, e.g. `https://<project>.storage.<region>.nhost.run`. */
351
+ baseUrl: string;
352
+ /**
353
+ * Access token supplier. Injected rather than read from localStorage: the
354
+ * v0.2 implementation hardcoded a storage key and a window global, which
355
+ * meant one app shape was supported and no test could drive it.
356
+ */
357
+ getToken: () => string | null | Promise<string | null>;
358
+ bucketId?: string;
359
+ orphans?: OrphanPolicy;
360
+ fetchImpl?: typeof fetch;
350
361
  }
351
362
  /**
352
363
  * nhost storage: bytes go up first, the column keeps the file's UUID, and the
@@ -356,90 +367,92 @@ interface NhostFileStorageOpts {
356
367
  * as v0.2 did — writes a link with a TTL into the database.
357
368
  */
358
369
  declare function nhostFileStorage(opts: NhostFileStorageOpts): FileStorageAdapter;
359
-
370
+ //#endregion
371
+ //#region src/adapters/supabase.d.ts
360
372
  /**
361
373
  * The slice of `@supabase/supabase-js` this adapter uses, declared
362
374
  * structurally so the package keeps zero runtime dependencies and the adapter
363
375
  * can be driven by a plain object in tests.
364
376
  */
365
377
  interface SupabaseStorageLike {
366
- storage: {
367
- from(bucket: string): {
368
- upload(path: string, file: File, opts?: {
369
- contentType?: string;
370
- upsert?: boolean;
371
- }): Promise<{
372
- data: {
373
- path: string;
374
- } | null;
375
- error: {
376
- message: string;
377
- } | null;
378
- }>;
379
- getPublicUrl(path: string): {
380
- data: {
381
- publicUrl: string;
382
- };
383
- };
384
- createSignedUrl(path: string, expiresIn: number): Promise<{
385
- data: {
386
- signedUrl: string;
387
- } | null;
388
- error: {
389
- message: string;
390
- } | null;
391
- }>;
392
- remove(paths: string[]): Promise<{
393
- data: unknown;
394
- error: {
395
- message: string;
396
- } | null;
397
- }>;
378
+ storage: {
379
+ from(bucket: string): {
380
+ upload(path: string, file: File, opts?: {
381
+ contentType?: string;
382
+ upsert?: boolean;
383
+ }): Promise<{
384
+ data: {
385
+ path: string;
386
+ } | null;
387
+ error: {
388
+ message: string;
389
+ } | null;
390
+ }>;
391
+ getPublicUrl(path: string): {
392
+ data: {
393
+ publicUrl: string;
398
394
  };
395
+ };
396
+ createSignedUrl(path: string, expiresIn: number): Promise<{
397
+ data: {
398
+ signedUrl: string;
399
+ } | null;
400
+ error: {
401
+ message: string;
402
+ } | null;
403
+ }>;
404
+ remove(paths: string[]): Promise<{
405
+ data: unknown;
406
+ error: {
407
+ message: string;
408
+ } | null;
409
+ }>;
399
410
  };
411
+ };
400
412
  }
401
413
  interface SupabaseFileStorageOpts {
402
- client: SupabaseStorageLike;
403
- bucket: string;
404
- /** Public buckets yield permanent URLs; signed buckets yield expiring ones. */
405
- access: 'public' | 'signed';
406
- /** Lifetime of a signed URL in seconds. Default 3600. */
407
- signedTtlSec?: number;
408
- /** Prepended to every generated object path, e.g. `'avatars'`. */
409
- pathPrefix?: string;
410
- orphans?: OrphanPolicy;
414
+ client: SupabaseStorageLike;
415
+ bucket: string;
416
+ /** Public buckets yield permanent URLs; signed buckets yield expiring ones. */
417
+ access: 'public' | 'signed';
418
+ /** Lifetime of a signed URL in seconds. Default 3600. */
419
+ signedTtlSec?: number;
420
+ /** Prepended to every generated object path, e.g. `'avatars'`. */
421
+ pathPrefix?: string;
422
+ orphans?: OrphanPolicy;
411
423
  }
412
424
  declare function supabaseFileStorage(opts: SupabaseFileStorageOpts): FileStorageAdapter;
413
-
425
+ //#endregion
426
+ //#region src/adapters/s3.d.ts
414
427
  /** What the adopter's signing endpoint hands back. */
415
428
  interface SignedTarget {
416
- /**
417
- * The object key. **The server chooses it, not the client** — letting the
418
- * browser name the object is how buckets get overwritten by whoever asks.
419
- */
420
- key: string;
421
- url: string;
422
- method?: 'PUT' | 'POST';
423
- /** Headers that were part of the signature. Sent verbatim. */
424
- headers?: Record<string, string>;
425
- /** POST-policy form fields. Sent before the file part. */
426
- fields?: Record<string, string>;
429
+ /**
430
+ * The object key. **The server chooses it, not the client** — letting the
431
+ * browser name the object is how buckets get overwritten by whoever asks.
432
+ */
433
+ key: string;
434
+ url: string;
435
+ method?: 'PUT' | 'POST';
436
+ /** Headers that were part of the signature. Sent verbatim. */
437
+ headers?: Record<string, string>;
438
+ /** POST-policy form fields. Sent before the file part. */
439
+ fields?: Record<string, string>;
427
440
  }
428
441
  interface S3FileStorageOpts {
429
- /** Ask your backend to sign. Authorization happens there, not here. */
430
- sign: (file: {
431
- name: string;
432
- type: string;
433
- size: number;
434
- }) => Promise<SignedTarget>;
435
- /** CDN or bucket origin for permanent read URLs. */
436
- publicBaseUrl?: string;
437
- /** Supply for private buckets; return an expiring URL. */
438
- signView?: (key: string, ctx: ResolveCtx) => Promise<ResolvedUrl>;
439
- remove?: (key: string) => Promise<void>;
440
- orphans?: OrphanPolicy;
441
- /** Injected for tests. Defaults to `new XMLHttpRequest()`. */
442
- xhrImpl?: () => XMLHttpRequest;
442
+ /** Ask your backend to sign. Authorization happens there, not here. */
443
+ sign: (file: {
444
+ name: string;
445
+ type: string;
446
+ size: number;
447
+ }) => Promise<SignedTarget>;
448
+ /** CDN or bucket origin for permanent read URLs. */
449
+ publicBaseUrl?: string;
450
+ /** Supply for private buckets; return an expiring URL. */
451
+ signView?: (key: string, ctx: ResolveCtx) => Promise<ResolvedUrl>;
452
+ remove?: (key: string) => Promise<void>;
453
+ orphans?: OrphanPolicy;
454
+ /** Injected for tests. Defaults to `new XMLHttpRequest()`. */
455
+ xhrImpl?: () => XMLHttpRequest;
443
456
  }
444
457
  /**
445
458
  * Direct-to-bucket upload behind a signed handshake: ask the server to sign,
@@ -450,7 +463,8 @@ interface S3FileStorageOpts {
450
463
  * absence to be felt.
451
464
  */
452
465
  declare function s3FileStorage(opts: S3FileStorageOpts): FileStorageAdapter;
453
-
466
+ //#endregion
467
+ //#region src/adapters/pocketbase.d.ts
454
468
  /**
455
469
  * PocketBase stores a filename per file and updates multi-value fields by
456
470
  * DIFF: `field+` appends, `field-` removes by name. Single-value fields are
@@ -458,14 +472,14 @@ declare function s3FileStorage(opts: S3FileStorageOpts): FileStorageAdapter;
458
472
  */
459
473
  declare function pocketbaseDiff(): FileMapper<string | string[] | null | undefined, unknown>;
460
474
  interface PocketbaseFileStorageOpts {
461
- /** PocketBase origin, e.g. `http://127.0.0.1:8090`. */
462
- baseUrl: string;
463
- /** Set when the file field is marked Protected in the collection schema. */
464
- protectedFiles?: boolean;
465
- /** Supplies a short-lived file token — `pb.files.getToken()`. */
466
- getFileToken?: () => Promise<string>;
467
- /** Lifetime assumed for a file token, in seconds. Default 120. */
468
- tokenTtlSec?: number;
475
+ /** PocketBase origin, e.g. `http://127.0.0.1:8090`. */
476
+ baseUrl: string;
477
+ /** Set when the file field is marked Protected in the collection schema. */
478
+ protectedFiles?: boolean;
479
+ /** Supplies a short-lived file token — `pb.files.getToken()`. */
480
+ getFileToken?: () => Promise<string>;
481
+ /** Lifetime assumed for a file token, in seconds. Default 120. */
482
+ tokenTtlSec?: number;
469
483
  }
470
484
  /**
471
485
  * PocketBase: bytes ride the record mutation as multipart, the column keeps a
@@ -476,7 +490,8 @@ interface PocketbaseFileStorageOpts {
476
490
  * one the example app runs against.
477
491
  */
478
492
  declare function pocketbaseFileStorage(opts: PocketbaseFileStorageOpts): FileStorageAdapter;
479
-
493
+ //#endregion
494
+ //#region src/mappers/column-patch.d.ts
480
495
  /**
481
496
  * A mapper's answer when the change cannot be expressed as "the new value of
482
497
  * this column".
@@ -489,12 +504,13 @@ declare function pocketbaseFileStorage(opts: PocketbaseFileStorageOpts): FileSto
489
504
  */
490
505
  declare const COLUMN_PATCH: unique symbol;
491
506
  interface ColumnPatch {
492
- [COLUMN_PATCH]: true;
493
- keys: Record<string, unknown>;
507
+ [COLUMN_PATCH]: true;
508
+ keys: Record<string, unknown>;
494
509
  }
495
510
  declare function columnPatch(keys: Record<string, unknown>): ColumnPatch;
496
511
  declare function isColumnPatch(value: unknown): value is ColumnPatch;
497
-
512
+ //#endregion
513
+ //#region src/wrapper/with-file-storage.d.ts
498
514
  /** Stamped on a wrapped provider so the wiring assertion can see it. */
499
515
  declare const FILE_STORAGE_WRAPPED: unique symbol;
500
516
  /**
@@ -523,21 +539,22 @@ declare function withFileStorage<T>(provider: T, app: AppRuntime): T;
523
539
  * being Files. Only what remains decides the request body.
524
540
  */
525
541
  interface EncodingPlan {
526
- /** Encoding EXPLICITLY declared for a field, via `f.file({ fileStorage })`. Authoritative. */
527
- knownEncodings: Map<string, FusedEncoding>;
528
- /**
529
- * Assumed for a payload key with NO declared field to consult — the
530
- * resource isn't registered, or has no `f.file()` for this key. Mirrors
531
- * `ui.fileStorage ?? 'default'`, the exact resolution a KNOWN field with no
532
- * explicit storage name already gets, extended to "no field info at all".
533
- * This is what keeps a single-storage app working without ever having to
534
- * declare its fields through `@burojs/field-types`.
535
- */
536
- defaultEncoding: FusedEncoding;
537
- /** Body encoding for whatever Files remain once base64 keys are converted. */
538
- bodyEncoding: Exclude<FusedEncoding, 'base64'>;
542
+ /** Encoding EXPLICITLY declared for a field, via `f.file({ fileStorage })`. Authoritative. */
543
+ knownEncodings: Map<string, FusedEncoding>;
544
+ /**
545
+ * Assumed for a payload key with NO declared field to consult — the
546
+ * resource isn't registered, or has no `f.file()` for this key. Mirrors
547
+ * `ui.fileStorage ?? 'default'`, the exact resolution a KNOWN field with no
548
+ * explicit storage name already gets, extended to "no field info at all".
549
+ * This is what keeps a single-storage app working without ever having to
550
+ * declare its fields through `@burojs/field-types`.
551
+ */
552
+ defaultEncoding: FusedEncoding;
553
+ /** Body encoding for whatever Files remain once base64 keys are converted. */
554
+ bodyEncoding: Exclude<FusedEncoding, 'base64'>;
539
555
  }
540
-
556
+ //#endregion
557
+ //#region src/wrapper/assert-wiring.d.ts
541
558
  /**
542
559
  * Three checks that must fail at boot rather than at runtime.
543
560
  *
@@ -560,7 +577,8 @@ interface EncodingPlan {
560
577
  * provider.
561
578
  */
562
579
  declare function assertFileStorageWiring(app: AppRuntime, provider: unknown): void;
563
-
580
+ //#endregion
581
+ //#region src/wrapper/encode.d.ts
564
582
  /**
565
583
  * Splice column patches into the payload; leave everything else alone.
566
584
  *
@@ -596,14 +614,17 @@ declare function containsColumnPatch(value: unknown, seen?: WeakSet<object>): bo
596
614
  * content-type, because the browser writes the boundary.
597
615
  */
598
616
  declare function encodePayload(variables: Record<string, unknown>, plan: EncodingPlan): Promise<Record<string, unknown> | FormData>;
599
-
617
+ //#endregion
618
+ //#region src/baseline-carrier.d.ts
600
619
  /**
601
620
  * Copy the opaque mapped-file baseline capability from a decoded record to a
602
621
  * fresh edit payload. The physical baseline itself remains private to the
603
622
  * provider wrapper; this helper can neither inspect nor serialize it.
604
623
  */
605
624
  declare function inheritFileMapperBaseline<T extends object>(source: object, target: T): T;
606
-
625
+ //#endregion
626
+ //#region src/index.d.ts
607
627
  declare const PACKAGE_NAME: "@burojs/file-storage";
608
-
628
+ //#endregion
609
629
  export { COLUMN_PATCH, type ColumnPatch, type ContractOpts, type ContractRunner, FILE_STORAGE_SERVICE, FILE_STORAGE_WRAPPED, type FakeAdapterOpts, type FileMapper, type FileRef, type FileStorageAdapter, type FileStorageRegistry, type FusedEncoding, type InlineFileStorageOpts, type JunctionOpts, type MapperCtx, type NhostFileStorageOpts, type OrphanPolicy, PACKAGE_NAME, type PocketbaseFileStorageOpts, type ResolveCtx, type ResolvedUrl, type RichImageOpts, type S3FileStorageOpts, type SignedTarget, type SupabaseFileStorageOpts, type SupabaseStorageLike, type UploadCtx, type UploadPipeline, assertFileStorageWiring, columnPatch, containsColumnPatch, containsFile, createFakeAdapter, createFileStorageRegistry, encodePayload, fileStoragePlugin, inheritFileMapperBaseline, inlineFileStorage, isColumnPatch, isImage, junction, newRef, nhostFileStorage, pocketbaseDiff, pocketbaseFileStorage, richImage, runFileStorageContract, s3FileStorage, scalarHandle, splicePatches, supabaseFileStorage, withFileStorage };
630
+ //# sourceMappingURL=index.d.cts.map