@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.cjs CHANGED
@@ -1,880 +1,868 @@
1
- 'use strict';
2
-
3
- // src/registry.ts
4
- var FILE_STORAGE_SERVICE = "fileStorage";
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/registry.ts
3
+ const FILE_STORAGE_SERVICE = "fileStorage";
5
4
  function createFileStorageRegistry(map) {
6
- if (!map.default) {
7
- throw new Error(
8
- "fileStoragePlugin requires a 'default' entry \u2014 every file field that does not name a storage resolves to it."
9
- );
10
- }
11
- const entries = new Map(Object.entries(map));
12
- return {
13
- get(name = "default") {
14
- const found = entries.get(name);
15
- if (!found) {
16
- throw new Error(
17
- `Unknown file storage '${name}'. Registered: ${[...entries.keys()].join(", ")}.`
18
- );
19
- }
20
- return found;
21
- },
22
- has: (name) => entries.has(name),
23
- names: () => [...entries.keys()],
24
- hasFused: () => [...entries.values()].some((a) => a.pipeline === "fused")
25
- };
26
- }
27
-
28
- // src/plugin.ts
5
+ if (!map.default) throw new Error("fileStoragePlugin requires a 'default' entry — every file field that does not name a storage resolves to it.");
6
+ const entries = new Map(Object.entries(map));
7
+ return {
8
+ get(name = "default") {
9
+ const found = entries.get(name);
10
+ if (!found) throw new Error(`Unknown file storage '${name}'. Registered: ${[...entries.keys()].join(", ")}.`);
11
+ return found;
12
+ },
13
+ has: (name) => entries.has(name),
14
+ names: () => [...entries.keys()],
15
+ hasFused: () => [...entries.values()].some((a) => a.pipeline === "fused")
16
+ };
17
+ }
18
+ //#endregion
19
+ //#region src/plugin.ts
29
20
  function fileStoragePlugin(map) {
30
- const adapters = Object.freeze({ ...map });
31
- return {
32
- name: "@burojs/file-storage",
33
- enforce: "pre",
34
- setup: (api) => {
35
- api.runtime.registerService(FILE_STORAGE_SERVICE, () => createFileStorageRegistry(adapters));
36
- }
37
- };
38
- }
39
-
40
- // src/mappers/ref.ts
41
- var seq = 0;
21
+ const adapters = Object.freeze({ ...map });
22
+ return {
23
+ name: "@burojs/file-storage",
24
+ enforce: "pre",
25
+ setup: (api) => {
26
+ api.runtime.registerService(FILE_STORAGE_SERVICE, () => createFileStorageRegistry(adapters));
27
+ }
28
+ };
29
+ }
30
+ //#endregion
31
+ //#region src/mappers/ref.ts
32
+ let seq = 0;
42
33
  function newRef(init = {}) {
43
- seq += 1;
44
- return { localId: `ref-${seq}`, ...init };
45
- }
46
-
47
- // src/kind.ts
34
+ seq += 1;
35
+ return {
36
+ localId: `ref-${seq}`,
37
+ ...init
38
+ };
39
+ }
40
+ //#endregion
41
+ //#region src/kind.ts
48
42
  function isImage(ref) {
49
- if (ref.mime) return ref.mime.startsWith("image/");
50
- const name = ref.name ?? "";
51
- return /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(name);
43
+ if (ref.mime) return ref.mime.startsWith("image/");
44
+ const name = ref.name ?? "";
45
+ return /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(name);
52
46
  }
53
-
54
- // src/mappers/scalar-handle.ts
47
+ //#endregion
48
+ //#region src/mappers/scalar-handle.ts
55
49
  function scalarHandle() {
56
- return {
57
- carriesPending: true,
58
- read(column) {
59
- if (column == null) return [];
60
- const list = Array.isArray(column) ? column : [column];
61
- return list.filter((entry) => entry !== "" && entry != null).map((entry) => newRef({ handle: entry, raw: entry, name: tail(entry) }));
62
- },
63
- write(next, _prev, ctx) {
64
- const values = next.map((r) => r.handle ?? r.pending).filter((v) => v != null);
65
- if (ctx.multiple) return values;
66
- return values[0] ?? null;
67
- }
68
- };
50
+ return {
51
+ carriesPending: true,
52
+ read(column) {
53
+ if (column == null) return [];
54
+ return (Array.isArray(column) ? column : [column]).filter((entry) => entry !== "" && entry != null).map((entry) => newRef({
55
+ handle: entry,
56
+ raw: entry,
57
+ name: tail(entry)
58
+ }));
59
+ },
60
+ write(next, _prev, ctx) {
61
+ const values = next.map((r) => r.handle ?? r.pending).filter((v) => v != null);
62
+ if (ctx.multiple) return values;
63
+ return values[0] ?? null;
64
+ }
65
+ };
69
66
  }
70
67
  function tail(value) {
71
- const seg = value.split("/").pop();
72
- return seg && seg !== value ? seg : value || void 0;
68
+ const seg = value.split("/").pop();
69
+ return seg && seg !== value ? seg : value || void 0;
73
70
  }
74
-
75
- // src/mappers/rich-image.ts
76
- var DEFAULT_USAGE_KEYS = ["hotspot", "crop"];
71
+ //#endregion
72
+ //#region src/mappers/rich-image.ts
73
+ const DEFAULT_USAGE_KEYS = ["hotspot", "crop"];
77
74
  function richImage(opts) {
78
- const handleKey = opts.handleKey ?? "id";
79
- const usageKeys = opts.usageKeys ?? DEFAULT_USAGE_KEYS;
80
- function readOne(entry) {
81
- const usage = opts.focus === "usage" ? pick(
82
- entry,
83
- usageKeys.filter((k) => entry[k] !== void 0)
84
- ) : void 0;
85
- return newRef({
86
- handle: asString(getPath(entry, handleKey)),
87
- raw: entry,
88
- name: asString(entry.name ?? entry.filename_download),
89
- mime: asString(entry.mime ?? entry.type),
90
- size: asNumber(entry.size ?? entry.filesize),
91
- width: asNumber(entry.width),
92
- height: asNumber(entry.height),
93
- ...usage && Object.keys(usage).length > 0 ? { usage } : {}
94
- });
95
- }
96
- return {
97
- read(column, ctx) {
98
- if (column == null) return [];
99
- const list = Array.isArray(column) ? column : [column];
100
- return list.filter((e) => e && typeof e === "object").map(readOne);
101
- },
102
- write(next, _prev, ctx) {
103
- const out = next.filter((r) => r.handle != null).map((r) => {
104
- const base = isObj(r.raw) ? { ...r.raw } : {};
105
- setPath(base, handleKey, r.handle);
106
- if (opts.focus === "usage" && r.usage) Object.assign(base, r.usage);
107
- return base;
108
- });
109
- if (ctx.multiple) return out;
110
- return out[0] ?? null;
111
- }
112
- };
75
+ const handleKey = opts.handleKey ?? "id";
76
+ const usageKeys = opts.usageKeys ?? DEFAULT_USAGE_KEYS;
77
+ function readOne(entry) {
78
+ const usage = opts.focus === "usage" ? pick(entry, usageKeys.filter((k) => entry[k] !== void 0)) : void 0;
79
+ return newRef({
80
+ handle: asString$1(getPath(entry, handleKey)),
81
+ raw: entry,
82
+ name: asString$1(entry.name ?? entry.filename_download),
83
+ mime: asString$1(entry.mime ?? entry.type),
84
+ size: asNumber(entry.size ?? entry.filesize),
85
+ width: asNumber(entry.width),
86
+ height: asNumber(entry.height),
87
+ ...usage && Object.keys(usage).length > 0 ? { usage } : {}
88
+ });
89
+ }
90
+ return {
91
+ read(column, ctx) {
92
+ if (column == null) return [];
93
+ return (Array.isArray(column) ? column : [column]).filter((e) => e && typeof e === "object").map(readOne);
94
+ },
95
+ write(next, _prev, ctx) {
96
+ const out = next.filter((r) => r.handle != null).map((r) => {
97
+ const base = isObj(r.raw) ? { ...r.raw } : {};
98
+ setPath(base, handleKey, r.handle);
99
+ if (opts.focus === "usage" && r.usage) Object.assign(base, r.usage);
100
+ return base;
101
+ });
102
+ if (ctx.multiple) return out;
103
+ return out[0] ?? null;
104
+ }
105
+ };
113
106
  }
114
107
  function isObj(v) {
115
- return typeof v === "object" && v !== null && !Array.isArray(v);
108
+ return typeof v === "object" && v !== null && !Array.isArray(v);
116
109
  }
117
- function asString(v) {
118
- return typeof v === "string" ? v : void 0;
110
+ function asString$1(v) {
111
+ return typeof v === "string" ? v : void 0;
119
112
  }
120
113
  function asNumber(v) {
121
- return typeof v === "number" && Number.isFinite(v) ? v : void 0;
114
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
122
115
  }
123
116
  function pick(src, keys) {
124
- const out = {};
125
- for (const k of keys) out[k] = src[k];
126
- return out;
117
+ const out = {};
118
+ for (const k of keys) out[k] = src[k];
119
+ return out;
127
120
  }
128
121
  function getPath(src, path) {
129
- return path.split(".").reduce((acc, k) => isObj(acc) ? acc[k] : void 0, src);
122
+ return path.split(".").reduce((acc, k) => isObj(acc) ? acc[k] : void 0, src);
130
123
  }
131
124
  function setPath(target, path, value) {
132
- const keys = path.split(".");
133
- const last = keys.pop();
134
- let cursor = target;
135
- for (const k of keys) {
136
- const nextLevel = cursor[k];
137
- cursor[k] = isObj(nextLevel) ? { ...nextLevel } : {};
138
- cursor = cursor[k];
139
- }
140
- cursor[last] = value;
141
- }
142
-
143
- // src/mappers/junction.ts
125
+ const keys = path.split(".");
126
+ const last = keys.pop();
127
+ let cursor = target;
128
+ for (const k of keys) {
129
+ const nextLevel = cursor[k];
130
+ cursor[k] = isObj(nextLevel) ? { ...nextLevel } : {};
131
+ cursor = cursor[k];
132
+ }
133
+ cursor[last] = value;
134
+ }
135
+ //#endregion
136
+ //#region src/mappers/junction.ts
144
137
  function junction(opts) {
145
- return {
146
- read(column, _ctx) {
147
- if (!Array.isArray(column)) return [];
148
- return column.filter((row) => row && typeof row === "object").map((row) => newRef({ handle: asString2(row[opts.fileKey]), raw: row }));
149
- },
150
- write(next, _prev, _ctx) {
151
- return next.filter((r) => r.handle != null).map((r, i) => {
152
- const base = r.raw && typeof r.raw === "object" ? { ...r.raw } : {};
153
- base[opts.fileKey] = r.handle;
154
- if (opts.sortKey) base[opts.sortKey] = i + 1;
155
- return base;
156
- });
157
- }
158
- };
159
- }
160
- function asString2(v) {
161
- return typeof v === "string" ? v : void 0;
138
+ return {
139
+ read(column, _ctx) {
140
+ if (!Array.isArray(column)) return [];
141
+ return column.filter((row) => row && typeof row === "object").map((row) => newRef({
142
+ handle: asString(row[opts.fileKey]),
143
+ raw: row
144
+ }));
145
+ },
146
+ write(next, _prev, _ctx) {
147
+ return next.filter((r) => r.handle != null).map((r, i) => {
148
+ const base = r.raw && typeof r.raw === "object" ? { ...r.raw } : {};
149
+ base[opts.fileKey] = r.handle;
150
+ if (opts.sortKey) base[opts.sortKey] = i + 1;
151
+ return base;
152
+ });
153
+ }
154
+ };
162
155
  }
163
-
164
- // src/contract/run-contract.ts
165
- var single = { resource: "demo", field: "file", multiple: false };
166
- var many = { resource: "demo", field: "files", multiple: true };
156
+ function asString(v) {
157
+ return typeof v === "string" ? v : void 0;
158
+ }
159
+ //#endregion
160
+ //#region src/contract/run-contract.ts
161
+ const single = {
162
+ resource: "demo",
163
+ field: "file",
164
+ multiple: false
165
+ };
166
+ const many = {
167
+ resource: "demo",
168
+ field: "files",
169
+ multiple: true
170
+ };
167
171
  function runFileStorageContract(opts, runner) {
168
- const { mapper } = opts;
169
- const it = runner.it;
170
- const expect = runner.expect;
171
- it("declares a pipeline", () => {
172
- expect(["immediate", "fused", "handshake", "resumable"]).toContain(opts.adapter().pipeline);
173
- });
174
- it("ships a default mapper", () => {
175
- const m = opts.adapter().defaultMapper;
176
- expect(typeof m.read).toBe("function");
177
- expect(typeof m.write).toBe("function");
178
- });
179
- if (opts.writesColumnPatch) {
180
- it("emits an empty patch when nothing changed", () => {
181
- const refs = mapper.read(opts.sampleColumn.multiple, many);
182
- const out = mapper.write(refs, refs, many);
183
- expect(Object.keys(out.keys ?? {}).length).toBe(0);
184
- });
185
- it("emits only patch keys, never the raw column value", () => {
186
- const refs = mapper.read(opts.sampleColumn.multiple, many);
187
- const out = mapper.write(refs, refs, many);
188
- expect(typeof out.keys).toBe("object");
189
- });
190
- } else {
191
- it("round-trips an untouched single value unchanged", () => {
192
- const refs = mapper.read(opts.sampleColumn.single, single);
193
- expect(mapper.write(refs, refs, single)).toEqual(opts.sampleColumn.single);
194
- });
195
- it("round-trips an untouched multiple value unchanged", () => {
196
- const refs = mapper.read(opts.sampleColumn.multiple, many);
197
- expect(mapper.write(refs, refs, many)).toEqual(opts.sampleColumn.multiple);
198
- });
199
- }
200
- it("reads null as no files", () => {
201
- expect(mapper.read(null, single)).toEqual([]);
202
- });
203
- it("gives every ref a distinct localId", () => {
204
- const refs = mapper.read(opts.sampleColumn.multiple, many);
205
- expect(new Set(refs.map((r) => r.localId)).size).toBe(refs.length);
206
- });
207
- if (!opts.skipUpload) {
208
- it("reports progress and resolves to a ref carrying a handle", async () => {
209
- const adapter = opts.adapter();
210
- const seen = [];
211
- const ref = await adapter.upload(new File(["hello"], "a.txt", { type: "text/plain" }), {
212
- signal: new AbortController().signal,
213
- onProgress: (loaded) => seen.push(loaded)
214
- });
215
- expect(ref.handle).toBeTruthy();
216
- expect(seen.length).toBeGreaterThan(0);
217
- expect(seen.at(-1)).toBe(5);
218
- });
219
- it("rejects with an AbortError when the signal is already aborted", async () => {
220
- const adapter = opts.adapter();
221
- const ac = new AbortController();
222
- ac.abort();
223
- await expect(
224
- adapter.upload(new File(["x"], "x.txt"), {
225
- signal: ac.signal,
226
- onProgress: () => {
227
- }
228
- })
229
- ).rejects.toThrow(/abort/i);
230
- });
231
- it("accepts a retry after a failure", async () => {
232
- const adapter = opts.adapter();
233
- const file = new File(["x"], "x.txt");
234
- const aborted = new AbortController();
235
- aborted.abort();
236
- await expect(
237
- adapter.upload(file, { signal: aborted.signal, onProgress: () => {
238
- } })
239
- ).rejects.toThrow();
240
- const second = await adapter.upload(file, {
241
- signal: new AbortController().signal,
242
- onProgress: () => {
243
- }
244
- });
245
- expect(second.handle).toBeTruthy();
246
- });
247
- }
248
- it("resolves a URL for a ref", async () => {
249
- const adapter = opts.adapter();
250
- const resolved = await adapter.resolveUrl({ localId: "x", handle: "h" }, opts.resolveCtx ?? {});
251
- const url = typeof resolved === "string" ? resolved : resolved.url;
252
- expect(url).toMatch(/^https?:\/\//);
253
- });
254
- it("either returns a permanent string or states a future expiry \u2014 never neither", async () => {
255
- const adapter = opts.adapter();
256
- const resolved = await adapter.resolveUrl({ localId: "x", handle: "h" }, opts.resolveCtx ?? {});
257
- if (typeof resolved === "string") return;
258
- expect(typeof resolved.expiresAt).toBe("number");
259
- expect(resolved.expiresAt).toBeGreaterThan(Date.now());
260
- });
261
- it("leaves unreportable metadata absent rather than guessing", async () => {
262
- const adapter = opts.adapter();
263
- if (!adapter.stat) return;
264
- const [out] = await adapter.stat([{ localId: "x", handle: "definitely-not-stored" }]);
265
- expect(out?.size).not.toBe(0);
266
- });
267
- }
268
-
269
- // src/contract/fake-adapter.ts
172
+ const { mapper } = opts;
173
+ const it = runner.it;
174
+ const expect = runner.expect;
175
+ it("declares a pipeline", () => {
176
+ expect([
177
+ "immediate",
178
+ "fused",
179
+ "handshake",
180
+ "resumable"
181
+ ]).toContain(opts.adapter().pipeline);
182
+ });
183
+ it("ships a default mapper", () => {
184
+ const m = opts.adapter().defaultMapper;
185
+ expect(typeof m.read).toBe("function");
186
+ expect(typeof m.write).toBe("function");
187
+ });
188
+ if (opts.writesColumnPatch) {
189
+ it("emits an empty patch when nothing changed", () => {
190
+ const refs = mapper.read(opts.sampleColumn.multiple, many);
191
+ const out = mapper.write(refs, refs, many);
192
+ expect(Object.keys(out.keys ?? {}).length).toBe(0);
193
+ });
194
+ it("emits only patch keys, never the raw column value", () => {
195
+ const refs = mapper.read(opts.sampleColumn.multiple, many);
196
+ const out = mapper.write(refs, refs, many);
197
+ expect(typeof out.keys).toBe("object");
198
+ });
199
+ } else {
200
+ it("round-trips an untouched single value unchanged", () => {
201
+ const refs = mapper.read(opts.sampleColumn.single, single);
202
+ expect(mapper.write(refs, refs, single)).toEqual(opts.sampleColumn.single);
203
+ });
204
+ it("round-trips an untouched multiple value unchanged", () => {
205
+ const refs = mapper.read(opts.sampleColumn.multiple, many);
206
+ expect(mapper.write(refs, refs, many)).toEqual(opts.sampleColumn.multiple);
207
+ });
208
+ }
209
+ it("reads null as no files", () => {
210
+ expect(mapper.read(null, single)).toEqual([]);
211
+ });
212
+ it("gives every ref a distinct localId", () => {
213
+ const refs = mapper.read(opts.sampleColumn.multiple, many);
214
+ expect(new Set(refs.map((r) => r.localId)).size).toBe(refs.length);
215
+ });
216
+ if (!opts.skipUpload) {
217
+ it("reports progress and resolves to a ref carrying a handle", async () => {
218
+ const adapter = opts.adapter();
219
+ const seen = [];
220
+ const ref = await adapter.upload(new File(["hello"], "a.txt", { type: "text/plain" }), {
221
+ signal: new AbortController().signal,
222
+ onProgress: (loaded) => seen.push(loaded)
223
+ });
224
+ expect(ref.handle).toBeTruthy();
225
+ expect(seen.length).toBeGreaterThan(0);
226
+ expect(seen.at(-1)).toBe(5);
227
+ });
228
+ it("rejects with an AbortError when the signal is already aborted", async () => {
229
+ const adapter = opts.adapter();
230
+ const ac = new AbortController();
231
+ ac.abort();
232
+ await expect(adapter.upload(new File(["x"], "x.txt"), {
233
+ signal: ac.signal,
234
+ onProgress: () => {}
235
+ })).rejects.toThrow(/abort/i);
236
+ });
237
+ it("accepts a retry after a failure", async () => {
238
+ const adapter = opts.adapter();
239
+ const file = new File(["x"], "x.txt");
240
+ const aborted = new AbortController();
241
+ aborted.abort();
242
+ await expect(adapter.upload(file, {
243
+ signal: aborted.signal,
244
+ onProgress: () => {}
245
+ })).rejects.toThrow();
246
+ const second = await adapter.upload(file, {
247
+ signal: new AbortController().signal,
248
+ onProgress: () => {}
249
+ });
250
+ expect(second.handle).toBeTruthy();
251
+ });
252
+ }
253
+ it("resolves a URL for a ref", async () => {
254
+ const resolved = await opts.adapter().resolveUrl({
255
+ localId: "x",
256
+ handle: "h"
257
+ }, opts.resolveCtx ?? {});
258
+ const url = typeof resolved === "string" ? resolved : resolved.url;
259
+ expect(url).toMatch(/^https?:\/\//);
260
+ });
261
+ it("either returns a permanent string or states a future expiry — never neither", async () => {
262
+ const resolved = await opts.adapter().resolveUrl({
263
+ localId: "x",
264
+ handle: "h"
265
+ }, opts.resolveCtx ?? {});
266
+ if (typeof resolved === "string") return;
267
+ expect(typeof resolved.expiresAt).toBe("number");
268
+ expect(resolved.expiresAt).toBeGreaterThan(Date.now());
269
+ });
270
+ it("leaves unreportable metadata absent rather than guessing", async () => {
271
+ const adapter = opts.adapter();
272
+ if (!adapter.stat) return;
273
+ const [out] = await adapter.stat([{
274
+ localId: "x",
275
+ handle: "definitely-not-stored"
276
+ }]);
277
+ expect(out?.size).not.toBe(0);
278
+ });
279
+ }
280
+ //#endregion
281
+ //#region src/contract/fake-adapter.ts
270
282
  function createFakeAdapter(opts = {}) {
271
- const stored = /* @__PURE__ */ new Map();
272
- const removed = [];
273
- let seq2 = 0;
274
- return {
275
- stored,
276
- removed,
277
- pipeline: opts.pipeline ?? "immediate",
278
- defaultMapper: scalarHandle(),
279
- async upload(file, ctx) {
280
- const ticks = opts.progressTicks ?? 2;
281
- for (let i = 1; i <= ticks; i += 1) {
282
- if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
283
- await Promise.resolve();
284
- ctx.onProgress(Math.round(file.size * i / ticks), file.size);
285
- }
286
- if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
287
- if (opts.failWith) throw new Error(opts.failWith);
288
- seq2 += 1;
289
- const handle = `fake-${seq2}-${file.name}`;
290
- stored.set(handle, file);
291
- return newRef({ handle, name: file.name, mime: file.type || void 0, size: file.size });
292
- },
293
- resolveUrl(ref, ctx) {
294
- const q = ctx.variant?.width ? `?w=${ctx.variant.width}` : "";
295
- const url = `https://fake.test/${ref.handle}${q}`;
296
- if (opts.signedTtlMs == null) return url;
297
- return Promise.resolve({ url, expiresAt: Date.now() + opts.signedTtlMs });
298
- },
299
- async stat(refs) {
300
- return refs.map((r) => {
301
- const f = r.handle ? stored.get(r.handle) : void 0;
302
- return f ? { ...r, name: f.name, size: f.size, mime: f.type || void 0 } : r;
303
- });
304
- },
305
- async remove(ref) {
306
- if (ref.handle) {
307
- stored.delete(ref.handle);
308
- removed.push(ref.handle);
309
- }
310
- }
311
- };
312
- }
313
-
314
- // src/adapters/inline.ts
283
+ const stored = new Map();
284
+ const removed = [];
285
+ let seq = 0;
286
+ return {
287
+ stored,
288
+ removed,
289
+ pipeline: opts.pipeline ?? "immediate",
290
+ defaultMapper: scalarHandle(),
291
+ async upload(file, ctx) {
292
+ const ticks = opts.progressTicks ?? 2;
293
+ for (let i = 1; i <= ticks; i += 1) {
294
+ if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
295
+ await Promise.resolve();
296
+ ctx.onProgress(Math.round(file.size * i / ticks), file.size);
297
+ }
298
+ if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
299
+ if (opts.failWith) throw new Error(opts.failWith);
300
+ seq += 1;
301
+ const handle = `fake-${seq}-${file.name}`;
302
+ stored.set(handle, file);
303
+ return newRef({
304
+ handle,
305
+ name: file.name,
306
+ mime: file.type || void 0,
307
+ size: file.size
308
+ });
309
+ },
310
+ resolveUrl(ref, ctx) {
311
+ const q = ctx.variant?.width ? `?w=${ctx.variant.width}` : "";
312
+ const url = `https://fake.test/${ref.handle}${q}`;
313
+ if (opts.signedTtlMs == null) return url;
314
+ return Promise.resolve({
315
+ url,
316
+ expiresAt: Date.now() + opts.signedTtlMs
317
+ });
318
+ },
319
+ async stat(refs) {
320
+ return refs.map((r) => {
321
+ const f = r.handle ? stored.get(r.handle) : void 0;
322
+ return f ? {
323
+ ...r,
324
+ name: f.name,
325
+ size: f.size,
326
+ mime: f.type || void 0
327
+ } : r;
328
+ });
329
+ },
330
+ async remove(ref) {
331
+ if (ref.handle) {
332
+ stored.delete(ref.handle);
333
+ removed.push(ref.handle);
334
+ }
335
+ }
336
+ };
337
+ }
338
+ //#endregion
339
+ //#region src/adapters/inline.ts
315
340
  function inlineFileStorage(opts) {
316
- return {
317
- pipeline: "fused",
318
- encoding: opts.encoding,
319
- defaultMapper: scalarHandle(),
320
- // Nothing was uploaded ahead of the save, so nothing can be orphaned.
321
- orphans: "keep",
322
- async upload(file, _ctx) {
323
- return newRef({
324
- pending: file,
325
- name: file.name,
326
- mime: file.type || void 0,
327
- size: file.size
328
- });
329
- },
330
- resolveUrl(ref, ctx) {
331
- return opts.resolveUrl(ref, ctx);
332
- }
333
- };
334
- }
335
-
336
- // src/adapters/nhost.ts
341
+ return {
342
+ pipeline: "fused",
343
+ encoding: opts.encoding,
344
+ defaultMapper: scalarHandle(),
345
+ orphans: "keep",
346
+ async upload(file, _ctx) {
347
+ return newRef({
348
+ pending: file,
349
+ name: file.name,
350
+ mime: file.type || void 0,
351
+ size: file.size
352
+ });
353
+ },
354
+ resolveUrl(ref, ctx) {
355
+ return opts.resolveUrl(ref, ctx);
356
+ }
357
+ };
358
+ }
359
+ //#endregion
360
+ //#region src/adapters/nhost.ts
337
361
  function nhostFileStorage(opts) {
338
- const fetchImpl = opts.fetchImpl ?? fetch;
339
- const base = opts.baseUrl.replace(/\/+$/, "");
340
- async function authHeaders() {
341
- const token = await opts.getToken();
342
- if (!token) throw new Error("nhostFileStorage: no access token \u2014 the user is not signed in");
343
- return { Authorization: `Bearer ${token}` };
344
- }
345
- return {
346
- pipeline: "immediate",
347
- defaultMapper: scalarHandle(),
348
- orphans: opts.orphans ?? "best-effort",
349
- async upload(file, ctx) {
350
- if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
351
- const headers = await authHeaders();
352
- const fd = new FormData();
353
- fd.append("file[]", file);
354
- if (opts.bucketId) fd.append("bucket-id", opts.bucketId);
355
- ctx.onProgress(0, file.size);
356
- const res = await fetchImpl(`${base}/v1/files`, {
357
- method: "POST",
358
- headers,
359
- body: fd,
360
- signal: ctx.signal
361
- });
362
- if (!res.ok) {
363
- const body = await res.text().catch(() => "");
364
- throw new Error(`nhost upload failed: ${res.status} ${body}`.trim());
365
- }
366
- const json = await res.json();
367
- const meta = "processedFiles" in json ? json.processedFiles?.[0] : json;
368
- if (!meta?.id) throw new Error("nhost upload returned no file id");
369
- ctx.onProgress(file.size, file.size);
370
- return newRef({
371
- handle: meta.id,
372
- name: meta.name ?? file.name,
373
- mime: meta.mimeType ?? file.type ?? void 0,
374
- size: meta.size ?? file.size
375
- });
376
- },
377
- async resolveUrl(ref, ctx) {
378
- const headers = await authHeaders();
379
- const params = new URLSearchParams();
380
- if (ctx.variant?.width) params.set("w", String(ctx.variant.width));
381
- if (ctx.variant?.height) params.set("h", String(ctx.variant.height));
382
- const qs = params.toString();
383
- const res = await fetchImpl(
384
- `${base}/v1/files/${ref.handle}/presignedurl${qs ? `?${qs}` : ""}`,
385
- { headers }
386
- );
387
- if (!res.ok) throw new Error(`nhost presign failed: ${res.status}`);
388
- const json = await res.json();
389
- if (!json.url) throw new Error("nhost presign returned no url");
390
- return {
391
- url: json.url,
392
- // `expiration` is a lifetime in seconds; the cache wants an instant.
393
- expiresAt: Date.now() + (json.expiration ?? 1800) * 1e3
394
- };
395
- },
396
- async stat(refs) {
397
- const headers = await authHeaders();
398
- return Promise.all(
399
- refs.map(async (ref) => {
400
- const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {
401
- method: "HEAD",
402
- headers
403
- });
404
- if (!res.ok) return ref;
405
- const rawSize = res.headers.get("content-length");
406
- const size = rawSize == null ? Number.NaN : Number(rawSize);
407
- const mime = res.headers.get("content-type") ?? void 0;
408
- return {
409
- ...ref,
410
- ...Number.isFinite(size) ? { size } : {},
411
- ...mime ? { mime } : {}
412
- };
413
- })
414
- );
415
- },
416
- async remove(ref) {
417
- const headers = await authHeaders();
418
- const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {
419
- method: "DELETE",
420
- headers
421
- });
422
- if (!res.ok && res.status !== 404) {
423
- throw new Error(`nhost delete failed: ${res.status}`);
424
- }
425
- }
426
- };
427
- }
428
-
429
- // src/adapters/supabase.ts
362
+ const fetchImpl = opts.fetchImpl ?? fetch;
363
+ const base = opts.baseUrl.replace(/\/+$/, "");
364
+ async function authHeaders() {
365
+ const token = await opts.getToken();
366
+ if (!token) throw new Error("nhostFileStorage: no access token — the user is not signed in");
367
+ return { Authorization: `Bearer ${token}` };
368
+ }
369
+ return {
370
+ pipeline: "immediate",
371
+ defaultMapper: scalarHandle(),
372
+ orphans: opts.orphans ?? "best-effort",
373
+ async upload(file, ctx) {
374
+ if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
375
+ const headers = await authHeaders();
376
+ const fd = new FormData();
377
+ fd.append("file[]", file);
378
+ if (opts.bucketId) fd.append("bucket-id", opts.bucketId);
379
+ ctx.onProgress(0, file.size);
380
+ const res = await fetchImpl(`${base}/v1/files`, {
381
+ method: "POST",
382
+ headers,
383
+ body: fd,
384
+ signal: ctx.signal
385
+ });
386
+ if (!res.ok) {
387
+ const body = await res.text().catch(() => "");
388
+ throw new Error(`nhost upload failed: ${res.status} ${body}`.trim());
389
+ }
390
+ const json = await res.json();
391
+ const meta = "processedFiles" in json ? json.processedFiles?.[0] : json;
392
+ if (!meta?.id) throw new Error("nhost upload returned no file id");
393
+ ctx.onProgress(file.size, file.size);
394
+ return newRef({
395
+ handle: meta.id,
396
+ name: meta.name ?? file.name,
397
+ mime: meta.mimeType ?? file.type ?? void 0,
398
+ size: meta.size ?? file.size
399
+ });
400
+ },
401
+ async resolveUrl(ref, ctx) {
402
+ const headers = await authHeaders();
403
+ const params = new URLSearchParams();
404
+ if (ctx.variant?.width) params.set("w", String(ctx.variant.width));
405
+ if (ctx.variant?.height) params.set("h", String(ctx.variant.height));
406
+ const qs = params.toString();
407
+ const res = await fetchImpl(`${base}/v1/files/${ref.handle}/presignedurl${qs ? `?${qs}` : ""}`, { headers });
408
+ if (!res.ok) throw new Error(`nhost presign failed: ${res.status}`);
409
+ const json = await res.json();
410
+ if (!json.url) throw new Error("nhost presign returned no url");
411
+ return {
412
+ url: json.url,
413
+ expiresAt: Date.now() + (json.expiration ?? 1800) * 1e3
414
+ };
415
+ },
416
+ async stat(refs) {
417
+ const headers = await authHeaders();
418
+ return Promise.all(refs.map(async (ref) => {
419
+ const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {
420
+ method: "HEAD",
421
+ headers
422
+ });
423
+ if (!res.ok) return ref;
424
+ const rawSize = res.headers.get("content-length");
425
+ const size = rawSize == null ? NaN : Number(rawSize);
426
+ const mime = res.headers.get("content-type") ?? void 0;
427
+ return {
428
+ ...ref,
429
+ ...Number.isFinite(size) ? { size } : {},
430
+ ...mime ? { mime } : {}
431
+ };
432
+ }));
433
+ },
434
+ async remove(ref) {
435
+ const headers = await authHeaders();
436
+ const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {
437
+ method: "DELETE",
438
+ headers
439
+ });
440
+ if (!res.ok && res.status !== 404) throw new Error(`nhost delete failed: ${res.status}`);
441
+ }
442
+ };
443
+ }
444
+ //#endregion
445
+ //#region src/adapters/supabase.ts
430
446
  function pathStamp() {
431
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
432
- return crypto.randomUUID().slice(0, 8);
433
- }
434
- return Math.random().toString(36).slice(2, 10).padEnd(8, "0");
447
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID().slice(0, 8);
448
+ return Math.random().toString(36).slice(2, 10).padEnd(8, "0");
435
449
  }
436
450
  function supabaseFileStorage(opts) {
437
- const bucket = () => opts.client.storage.from(opts.bucket);
438
- const ttl = opts.signedTtlSec ?? 3600;
439
- return {
440
- pipeline: "immediate",
441
- defaultMapper: scalarHandle(),
442
- orphans: opts.orphans ?? "best-effort",
443
- async upload(file, ctx) {
444
- if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
445
- const stamp = `${pathStamp()}-${file.name}`;
446
- const path = opts.pathPrefix ? `${opts.pathPrefix}/${stamp}` : stamp;
447
- ctx.onProgress(0, file.size);
448
- const { data, error } = await bucket().upload(path, file, {
449
- contentType: file.type || void 0
450
- });
451
- if (error) throw new Error(`Supabase upload failed: ${error.message}`);
452
- if (!data) throw new Error("Supabase upload returned no path");
453
- ctx.onProgress(file.size, file.size);
454
- return newRef({
455
- handle: data.path,
456
- name: file.name,
457
- mime: file.type || void 0,
458
- size: file.size
459
- });
460
- },
461
- async resolveUrl(ref, _ctx) {
462
- const path = ref.handle;
463
- if (opts.access === "public") {
464
- return bucket().getPublicUrl(path).data.publicUrl;
465
- }
466
- const { data, error } = await bucket().createSignedUrl(path, ttl);
467
- if (error) throw new Error(`Supabase sign failed: ${error.message}`);
468
- if (!data) throw new Error("Supabase sign returned no url");
469
- return { url: data.signedUrl, expiresAt: Date.now() + ttl * 1e3 };
470
- },
471
- async remove(ref) {
472
- const { error } = await bucket().remove([ref.handle]);
473
- if (error) throw new Error(`Supabase remove failed: ${error.message}`);
474
- }
475
- };
476
- }
477
-
478
- // src/adapters/s3.ts
451
+ const bucket = () => opts.client.storage.from(opts.bucket);
452
+ const ttl = opts.signedTtlSec ?? 3600;
453
+ return {
454
+ pipeline: "immediate",
455
+ defaultMapper: scalarHandle(),
456
+ orphans: opts.orphans ?? "best-effort",
457
+ async upload(file, ctx) {
458
+ if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
459
+ const stamp = `${pathStamp()}-${file.name}`;
460
+ const path = opts.pathPrefix ? `${opts.pathPrefix}/${stamp}` : stamp;
461
+ ctx.onProgress(0, file.size);
462
+ const { data, error } = await bucket().upload(path, file, { contentType: file.type || void 0 });
463
+ if (error) throw new Error(`Supabase upload failed: ${error.message}`);
464
+ if (!data) throw new Error("Supabase upload returned no path");
465
+ ctx.onProgress(file.size, file.size);
466
+ return newRef({
467
+ handle: data.path,
468
+ name: file.name,
469
+ mime: file.type || void 0,
470
+ size: file.size
471
+ });
472
+ },
473
+ async resolveUrl(ref, _ctx) {
474
+ const path = ref.handle;
475
+ if (opts.access === "public") return bucket().getPublicUrl(path).data.publicUrl;
476
+ const { data, error } = await bucket().createSignedUrl(path, ttl);
477
+ if (error) throw new Error(`Supabase sign failed: ${error.message}`);
478
+ if (!data) throw new Error("Supabase sign returned no url");
479
+ return {
480
+ url: data.signedUrl,
481
+ expiresAt: Date.now() + ttl * 1e3
482
+ };
483
+ },
484
+ async remove(ref) {
485
+ const { error } = await bucket().remove([ref.handle]);
486
+ if (error) throw new Error(`Supabase remove failed: ${error.message}`);
487
+ }
488
+ };
489
+ }
490
+ //#endregion
491
+ //#region src/adapters/s3.ts
479
492
  function s3FileStorage(opts) {
480
- return {
481
- pipeline: "handshake",
482
- defaultMapper: scalarHandle(),
483
- orphans: opts.orphans ?? "best-effort",
484
- async upload(file, ctx) {
485
- if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
486
- const target = await opts.sign({ name: file.name, type: file.type, size: file.size });
487
- if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
488
- const xhr = (opts.xhrImpl ?? (() => new XMLHttpRequest()))();
489
- const method = target.method ?? "PUT";
490
- await new Promise((resolve, reject) => {
491
- const onAbort = () => xhr.abort();
492
- ctx.signal.addEventListener("abort", onAbort, { once: true });
493
- xhr.upload.addEventListener("progress", (e) => {
494
- const ev = e;
495
- ctx.onProgress(ev.loaded, ev.total || file.size);
496
- });
497
- xhr.addEventListener("load", () => {
498
- ctx.signal.removeEventListener("abort", onAbort);
499
- if (xhr.status >= 200 && xhr.status < 300) {
500
- ctx.onProgress(file.size, file.size);
501
- resolve();
502
- } else reject(new Error(`S3 upload failed: ${xhr.status}`));
503
- });
504
- xhr.addEventListener("error", () => {
505
- ctx.signal.removeEventListener("abort", onAbort);
506
- reject(new Error("S3 upload failed: network error"));
507
- });
508
- xhr.addEventListener("abort", () => {
509
- reject(new DOMException("Aborted", "AbortError"));
510
- });
511
- xhr.open(method, target.url);
512
- for (const [k, v] of Object.entries(target.headers ?? {})) {
513
- xhr.setRequestHeader(k, v);
514
- }
515
- if (method === "POST") {
516
- const fd = new FormData();
517
- for (const [k, v] of Object.entries(target.fields ?? {})) fd.append(k, v);
518
- fd.append("file", file);
519
- xhr.send(fd);
520
- } else {
521
- xhr.send(file);
522
- }
523
- });
524
- return newRef({
525
- handle: target.key,
526
- name: file.name,
527
- mime: file.type || void 0,
528
- size: file.size
529
- });
530
- },
531
- resolveUrl(ref, ctx) {
532
- const key = ref.handle;
533
- if (opts.signView) return opts.signView(key, ctx);
534
- if (opts.publicBaseUrl) return `${opts.publicBaseUrl.replace(/\/+$/, "")}/${key}`;
535
- throw new Error(
536
- "s3FileStorage: pass publicBaseUrl for a public bucket or signView for a private one"
537
- );
538
- },
539
- ...opts.remove ? {
540
- async remove(ref) {
541
- await opts.remove?.(ref.handle);
542
- }
543
- } : {}
544
- };
545
- }
546
-
547
- // src/mappers/column-patch.ts
548
- var COLUMN_PATCH = /* @__PURE__ */ Symbol.for("buro.fileStorage.columnPatch");
493
+ return {
494
+ pipeline: "handshake",
495
+ defaultMapper: scalarHandle(),
496
+ orphans: opts.orphans ?? "best-effort",
497
+ async upload(file, ctx) {
498
+ if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
499
+ const target = await opts.sign({
500
+ name: file.name,
501
+ type: file.type,
502
+ size: file.size
503
+ });
504
+ if (ctx.signal.aborted) throw new DOMException("Aborted", "AbortError");
505
+ const xhr = (opts.xhrImpl ?? (() => new XMLHttpRequest()))();
506
+ const method = target.method ?? "PUT";
507
+ await new Promise((resolve, reject) => {
508
+ const onAbort = () => xhr.abort();
509
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
510
+ xhr.upload.addEventListener("progress", (e) => {
511
+ const ev = e;
512
+ ctx.onProgress(ev.loaded, ev.total || file.size);
513
+ });
514
+ xhr.addEventListener("load", () => {
515
+ ctx.signal.removeEventListener("abort", onAbort);
516
+ if (xhr.status >= 200 && xhr.status < 300) {
517
+ ctx.onProgress(file.size, file.size);
518
+ resolve();
519
+ } else reject(new Error(`S3 upload failed: ${xhr.status}`));
520
+ });
521
+ xhr.addEventListener("error", () => {
522
+ ctx.signal.removeEventListener("abort", onAbort);
523
+ reject(new Error("S3 upload failed: network error"));
524
+ });
525
+ xhr.addEventListener("abort", () => {
526
+ reject(new DOMException("Aborted", "AbortError"));
527
+ });
528
+ xhr.open(method, target.url);
529
+ for (const [k, v] of Object.entries(target.headers ?? {})) xhr.setRequestHeader(k, v);
530
+ if (method === "POST") {
531
+ const fd = new FormData();
532
+ for (const [k, v] of Object.entries(target.fields ?? {})) fd.append(k, v);
533
+ fd.append("file", file);
534
+ xhr.send(fd);
535
+ } else xhr.send(file);
536
+ });
537
+ return newRef({
538
+ handle: target.key,
539
+ name: file.name,
540
+ mime: file.type || void 0,
541
+ size: file.size
542
+ });
543
+ },
544
+ resolveUrl(ref, ctx) {
545
+ const key = ref.handle;
546
+ if (opts.signView) return opts.signView(key, ctx);
547
+ if (opts.publicBaseUrl) return `${opts.publicBaseUrl.replace(/\/+$/, "")}/${key}`;
548
+ throw new Error("s3FileStorage: pass publicBaseUrl for a public bucket or signView for a private one");
549
+ },
550
+ ...opts.remove ? { async remove(ref) {
551
+ await opts.remove?.(ref.handle);
552
+ } } : {}
553
+ };
554
+ }
555
+ //#endregion
556
+ //#region src/mappers/column-patch.ts
557
+ const COLUMN_PATCH = Symbol.for("buro.fileStorage.columnPatch");
549
558
  function columnPatch(keys) {
550
- return { [COLUMN_PATCH]: true, keys };
559
+ return {
560
+ [COLUMN_PATCH]: true,
561
+ keys
562
+ };
551
563
  }
552
564
  function isColumnPatch(value) {
553
- return typeof value === "object" && value !== null && COLUMN_PATCH in value;
565
+ return typeof value === "object" && value !== null && COLUMN_PATCH in value;
554
566
  }
555
-
556
- // src/adapters/pocketbase.ts
567
+ //#endregion
568
+ //#region src/adapters/pocketbase.ts
557
569
  function pocketbaseDiff() {
558
- return {
559
- carriesPending: true,
560
- read(column) {
561
- if (column == null) return [];
562
- const list = Array.isArray(column) ? column : [column];
563
- return list.filter((n) => typeof n === "string" && n !== "").map((name) => newRef({ handle: name, raw: name, name }));
564
- },
565
- write(next, prev, ctx) {
566
- const added = next.filter((r) => r.pending).map((r) => r.pending);
567
- if (!ctx.multiple) {
568
- if (added.length > 0) return columnPatch({ [ctx.field]: added[0] });
569
- const kept = next.find((r) => r.handle);
570
- if (kept) return columnPatch({});
571
- return prev.length > 0 ? columnPatch({ [ctx.field]: "" }) : columnPatch({});
572
- }
573
- const keptHandles = new Set(next.filter((r) => r.handle).map((r) => r.handle));
574
- const removed = prev.map((r) => r.handle).filter((h) => h != null && !keptHandles.has(h));
575
- const keys = {};
576
- if (added.length > 0) keys[`${ctx.field}+`] = added;
577
- if (removed.length > 0) keys[`${ctx.field}-`] = removed;
578
- return columnPatch(keys);
579
- }
580
- };
570
+ return {
571
+ carriesPending: true,
572
+ read(column) {
573
+ if (column == null) return [];
574
+ return (Array.isArray(column) ? column : [column]).filter((n) => typeof n === "string" && n !== "").map((name) => newRef({
575
+ handle: name,
576
+ raw: name,
577
+ name
578
+ }));
579
+ },
580
+ write(next, prev, ctx) {
581
+ const added = next.filter((r) => r.pending).map((r) => r.pending);
582
+ if (!ctx.multiple) {
583
+ if (added.length > 0) return columnPatch({ [ctx.field]: added[0] });
584
+ if (next.find((r) => r.handle)) return columnPatch({});
585
+ return prev.length > 0 ? columnPatch({ [ctx.field]: "" }) : columnPatch({});
586
+ }
587
+ const keptHandles = new Set(next.filter((r) => r.handle).map((r) => r.handle));
588
+ const removed = prev.map((r) => r.handle).filter((h) => h != null && !keptHandles.has(h));
589
+ const keys = {};
590
+ if (added.length > 0) keys[`${ctx.field}+`] = added;
591
+ if (removed.length > 0) keys[`${ctx.field}-`] = removed;
592
+ return columnPatch(keys);
593
+ }
594
+ };
581
595
  }
582
596
  function pocketbaseFileStorage(opts) {
583
- const base = opts.baseUrl.replace(/\/+$/, "");
584
- const ttl = opts.tokenTtlSec ?? 120;
585
- return {
586
- pipeline: "fused",
587
- encoding: "multipart",
588
- defaultMapper: pocketbaseDiff(),
589
- orphans: "keep",
590
- async upload(file, _ctx) {
591
- return newRef({
592
- pending: file,
593
- name: file.name,
594
- mime: file.type || void 0,
595
- size: file.size
596
- });
597
- },
598
- async resolveUrl(ref, ctx) {
599
- const recordId = ctx.record?.id;
600
- if (!ctx.resource || !recordId) {
601
- throw new Error(
602
- "pocketbaseFileStorage: a file URL needs its collection and record id \u2014 the filename alone does not identify a file in PocketBase."
603
- );
604
- }
605
- const params = new URLSearchParams();
606
- if (ctx.variant?.width || ctx.variant?.height) {
607
- const w = ctx.variant.width ?? 0;
608
- const h = ctx.variant.height ?? 0;
609
- params.set("thumb", `${w}x${h}f`);
610
- }
611
- if (ctx.disposition === "attachment") params.set("download", "1");
612
- if (opts.protectedFiles) {
613
- if (!opts.getFileToken) {
614
- throw new Error(
615
- "pocketbaseFileStorage: protectedFiles is set but getFileToken was not supplied"
616
- );
617
- }
618
- const token = await opts.getFileToken();
619
- params.set("token", token);
620
- const qs2 = params.toString();
621
- return {
622
- url: `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs2 ? `?${qs2}` : ""}`,
623
- expiresAt: Date.now() + ttl * 1e3
624
- };
625
- }
626
- const qs = params.toString();
627
- return `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ""}`;
628
- }
629
- };
630
- }
631
-
632
- // src/wrapper/encode.ts
597
+ const base = opts.baseUrl.replace(/\/+$/, "");
598
+ const ttl = opts.tokenTtlSec ?? 120;
599
+ return {
600
+ pipeline: "fused",
601
+ encoding: "multipart",
602
+ defaultMapper: pocketbaseDiff(),
603
+ orphans: "keep",
604
+ async upload(file, _ctx) {
605
+ return newRef({
606
+ pending: file,
607
+ name: file.name,
608
+ mime: file.type || void 0,
609
+ size: file.size
610
+ });
611
+ },
612
+ async resolveUrl(ref, ctx) {
613
+ const recordId = ctx.record?.id;
614
+ if (!ctx.resource || !recordId) throw new Error("pocketbaseFileStorage: a file URL needs its collection and record id — the filename alone does not identify a file in PocketBase.");
615
+ const params = new URLSearchParams();
616
+ if (ctx.variant?.width || ctx.variant?.height) {
617
+ const w = ctx.variant.width ?? 0;
618
+ const h = ctx.variant.height ?? 0;
619
+ params.set("thumb", `${w}x${h}f`);
620
+ }
621
+ if (ctx.disposition === "attachment") params.set("download", "1");
622
+ if (opts.protectedFiles) {
623
+ if (!opts.getFileToken) throw new Error("pocketbaseFileStorage: protectedFiles is set but getFileToken was not supplied");
624
+ const token = await opts.getFileToken();
625
+ params.set("token", token);
626
+ const qs = params.toString();
627
+ return {
628
+ url: `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ""}`,
629
+ expiresAt: Date.now() + ttl * 1e3
630
+ };
631
+ }
632
+ const qs = params.toString();
633
+ return `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ""}`;
634
+ }
635
+ };
636
+ }
637
+ //#endregion
638
+ //#region src/wrapper/encode.ts
633
639
  function splicePatches(variables) {
634
- let touched = false;
635
- const out = {};
636
- for (const [key, value] of Object.entries(variables)) {
637
- if (isColumnPatch(value)) {
638
- touched = true;
639
- Object.assign(out, value.keys);
640
- continue;
641
- }
642
- out[key] = value;
643
- }
644
- return touched ? out : variables;
645
- }
646
- function containsFile(value, seen = /* @__PURE__ */ new WeakSet()) {
647
- if (typeof File !== "undefined" && value instanceof File) return true;
648
- if (!value || typeof value !== "object") return false;
649
- if (seen.has(value)) return false;
650
- seen.add(value);
651
- if (Array.isArray(value)) return value.some((v) => containsFile(v, seen));
652
- return Object.values(value).some((v) => containsFile(v, seen));
653
- }
654
- function containsColumnPatch(value, seen = /* @__PURE__ */ new WeakSet()) {
655
- if (!value || typeof value !== "object") return false;
656
- try {
657
- if (isColumnPatch(value)) return true;
658
- if (seen.has(value)) return false;
659
- seen.add(value);
660
- const descriptors = Object.getOwnPropertyDescriptors(value);
661
- return Object.entries(descriptors).some(
662
- ([key, descriptor]) => key !== "length" && "value" in descriptor && containsColumnPatch(descriptor.value, seen)
663
- );
664
- } catch {
665
- return false;
666
- }
640
+ let touched = false;
641
+ const out = {};
642
+ for (const [key, value] of Object.entries(variables)) {
643
+ if (isColumnPatch(value)) {
644
+ touched = true;
645
+ Object.assign(out, value.keys);
646
+ continue;
647
+ }
648
+ out[key] = value;
649
+ }
650
+ return touched ? out : variables;
651
+ }
652
+ function containsFile(value, seen = new WeakSet()) {
653
+ if (typeof File !== "undefined" && value instanceof File) return true;
654
+ if (!value || typeof value !== "object") return false;
655
+ if (seen.has(value)) return false;
656
+ seen.add(value);
657
+ if (Array.isArray(value)) return value.some((v) => containsFile(v, seen));
658
+ return Object.values(value).some((v) => containsFile(v, seen));
659
+ }
660
+ function containsColumnPatch(value, seen = new WeakSet()) {
661
+ if (!value || typeof value !== "object") return false;
662
+ try {
663
+ if (isColumnPatch(value)) return true;
664
+ if (seen.has(value)) return false;
665
+ seen.add(value);
666
+ const descriptors = Object.getOwnPropertyDescriptors(value);
667
+ return Object.entries(descriptors).some(([key, descriptor]) => key !== "length" && "value" in descriptor && containsColumnPatch(descriptor.value, seen));
668
+ } catch {
669
+ return false;
670
+ }
667
671
  }
668
672
  async function toDataUrl(file) {
669
- const buf = await file.arrayBuffer();
670
- const bytes = new Uint8Array(buf);
671
- let binary = "";
672
- for (const byte of bytes) binary += String.fromCharCode(byte);
673
- const b64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64");
674
- return `data:${file.type || "application/octet-stream"};base64,${b64}`;
673
+ const buf = await file.arrayBuffer();
674
+ const bytes = new Uint8Array(buf);
675
+ let binary = "";
676
+ for (const byte of bytes) binary += String.fromCharCode(byte);
677
+ const b64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64");
678
+ return `data:${file.type || "application/octet-stream"};base64,${b64}`;
675
679
  }
676
680
  async function encodePayload(variables, plan) {
677
- const staged = {};
678
- for (const [key, value] of Object.entries(variables)) {
679
- if (encodingFor(key, plan) !== "base64") {
680
- staged[key] = value;
681
- continue;
682
- }
683
- if (value instanceof File) staged[key] = await toDataUrl(value);
684
- else if (Array.isArray(value)) {
685
- staged[key] = await Promise.all(
686
- value.map((v) => v instanceof File ? toDataUrl(v) : Promise.resolve(v))
687
- );
688
- } else staged[key] = value;
689
- }
690
- if (!containsFile(staged)) return staged;
691
- const encoding = plan.bodyEncoding;
692
- const fd = new FormData();
693
- if (encoding === "graphql-upload") {
694
- const files = [];
695
- const map = {};
696
- const scrubbed = {};
697
- for (const [key, value] of Object.entries(staged)) {
698
- if (value instanceof File) {
699
- map[String(files.length)] = [`variables.${key}`];
700
- files.push(value);
701
- scrubbed[key] = null;
702
- } else scrubbed[key] = value;
703
- }
704
- fd.append("operations", JSON.stringify({ variables: scrubbed }));
705
- fd.append("map", JSON.stringify(map));
706
- files.forEach((f, i) => fd.append(String(i), f));
707
- return fd;
708
- }
709
- for (const [key, value] of Object.entries(staged)) {
710
- if (value instanceof File) fd.append(key, value);
711
- else if (Array.isArray(value)) {
712
- for (const item of value) {
713
- fd.append(key, item instanceof File ? item : String(item));
714
- }
715
- } else if (value == null) fd.append(key, "");
716
- else if (typeof value === "object") fd.append(key, JSON.stringify(value));
717
- else fd.append(key, String(value));
718
- }
719
- return fd;
720
- }
721
-
722
- // src/wrapper/with-file-storage.ts
723
- var FILE_STORAGE_WRAPPED = /* @__PURE__ */ Symbol.for("buro.fileStorage.wrapped");
724
- var MUTATING = /* @__PURE__ */ new Set(["create", "update", "createMany", "updateMany"]);
681
+ const staged = {};
682
+ for (const [key, value] of Object.entries(variables)) {
683
+ if (encodingFor(key, plan) !== "base64") {
684
+ staged[key] = value;
685
+ continue;
686
+ }
687
+ if (value instanceof File) staged[key] = await toDataUrl(value);
688
+ else if (Array.isArray(value)) staged[key] = await Promise.all(value.map((v) => v instanceof File ? toDataUrl(v) : Promise.resolve(v)));
689
+ else staged[key] = value;
690
+ }
691
+ if (!containsFile(staged)) return staged;
692
+ const encoding = plan.bodyEncoding;
693
+ const fd = new FormData();
694
+ if (encoding === "graphql-upload") {
695
+ const files = [];
696
+ const map = {};
697
+ const scrubbed = {};
698
+ for (const [key, value] of Object.entries(staged)) if (value instanceof File) {
699
+ map[String(files.length)] = [`variables.${key}`];
700
+ files.push(value);
701
+ scrubbed[key] = null;
702
+ } else scrubbed[key] = value;
703
+ fd.append("operations", JSON.stringify({ variables: scrubbed }));
704
+ fd.append("map", JSON.stringify(map));
705
+ files.forEach((f, i) => {
706
+ fd.append(String(i), f);
707
+ });
708
+ return fd;
709
+ }
710
+ for (const [key, value] of Object.entries(staged)) if (value instanceof File) fd.append(key, value);
711
+ else if (Array.isArray(value)) for (const item of value) fd.append(key, item instanceof File ? item : String(item));
712
+ else if (value == null) fd.append(key, "");
713
+ else if (typeof value === "object") fd.append(key, JSON.stringify(value));
714
+ else fd.append(key, String(value));
715
+ return fd;
716
+ }
717
+ //#endregion
718
+ //#region src/wrapper/with-file-storage.ts
719
+ const FILE_STORAGE_WRAPPED = Symbol.for("buro.fileStorage.wrapped");
720
+ const MUTATING = new Set([
721
+ "create",
722
+ "update",
723
+ "createMany",
724
+ "updateMany"
725
+ ]);
725
726
  function isProviderMap(value) {
726
- if (!value || typeof value !== "object") return false;
727
- const entries = Object.values(value);
728
- return entries.length > 0 && entries.every((v) => v && typeof v === "object" && !("then" in v));
727
+ if (!value || typeof value !== "object") return false;
728
+ const entries = Object.values(value);
729
+ return entries.length > 0 && entries.every((v) => v && typeof v === "object" && !("then" in v));
729
730
  }
730
731
  function withFileStorage(provider, app) {
731
- const registry = app.getService(FILE_STORAGE_SERVICE);
732
- if (!registry) {
733
- throw new Error(
734
- "withFileStorage: no file storage registered \u2014 add fileStoragePlugin({...}) to defineApp"
735
- );
736
- }
737
- if (isProviderMap(provider) && !provider.getList) {
738
- const out = {};
739
- for (const [name, entry] of Object.entries(provider)) {
740
- out[name] = withFileStorage(entry, app);
741
- }
742
- Object.defineProperty(out, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });
743
- return out;
744
- }
745
- const source = provider;
746
- const wrapped = Object.create(
747
- Object.getPrototypeOf(source)
748
- );
749
- for (const key of allKeys(source)) {
750
- const value = source[key];
751
- if (typeof value !== "function" || !MUTATING.has(key)) {
752
- wrapped[key] = value;
753
- continue;
754
- }
755
- wrapped[key] = async (args) => {
756
- const variables = args?.variables;
757
- if (!variables || typeof variables !== "object" || variables instanceof FormData) {
758
- return value(args);
759
- }
760
- if ((key === "createMany" || key === "updateMany") && (containsFile(variables) || containsColumnPatch(variables))) {
761
- throw new Error(
762
- `withFileStorage: bulk file mutations are not supported for ${key}; submit one record at a time so FileMapper patches and request-body encoding stay unambiguous.`
763
- );
764
- }
765
- if (Array.isArray(variables)) return value(args);
766
- const spliced = splicePatches(variables);
767
- if (!containsFile(spliced)) {
768
- const passthrough = spliced === variables ? args : { ...args, variables: spliced };
769
- return value(passthrough);
770
- }
771
- const encoded = await encodePayload(spliced, planFor(app, registry, String(args.resource)));
772
- return value({ ...args, variables: encoded });
773
- };
774
- }
775
- Object.defineProperty(wrapped, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });
776
- return wrapped;
732
+ const registry = app.getService(FILE_STORAGE_SERVICE);
733
+ if (!registry) throw new Error("withFileStorage: no file storage registered — add fileStoragePlugin({...}) to defineApp");
734
+ if (isProviderMap(provider) && !provider.getList) {
735
+ const out = {};
736
+ for (const [name, entry] of Object.entries(provider)) out[name] = withFileStorage(entry, app);
737
+ Object.defineProperty(out, FILE_STORAGE_WRAPPED, {
738
+ value: true,
739
+ enumerable: false
740
+ });
741
+ return out;
742
+ }
743
+ const source = provider;
744
+ const wrapped = Object.create(Object.getPrototypeOf(source));
745
+ for (const key of allKeys(source)) {
746
+ const value = source[key];
747
+ if (typeof value !== "function" || !MUTATING.has(key)) {
748
+ wrapped[key] = value;
749
+ continue;
750
+ }
751
+ wrapped[key] = async (args) => {
752
+ const variables = args?.variables;
753
+ if (!variables || typeof variables !== "object" || variables instanceof FormData) return value(args);
754
+ if ((key === "createMany" || key === "updateMany") && (containsFile(variables) || containsColumnPatch(variables))) throw new Error(`withFileStorage: bulk file mutations are not supported for ${key}; submit one record at a time so FileMapper patches and request-body encoding stay unambiguous.`);
755
+ if (Array.isArray(variables)) return value(args);
756
+ const spliced = splicePatches(variables);
757
+ if (!containsFile(spliced)) {
758
+ const passthrough = spliced === variables ? args : {
759
+ ...args,
760
+ variables: spliced
761
+ };
762
+ return value(passthrough);
763
+ }
764
+ const encoded = await encodePayload(spliced, planFor(app, registry, String(args.resource)));
765
+ return value({
766
+ ...args,
767
+ variables: encoded
768
+ });
769
+ };
770
+ }
771
+ Object.defineProperty(wrapped, FILE_STORAGE_WRAPPED, {
772
+ value: true,
773
+ enumerable: false
774
+ });
775
+ return wrapped;
777
776
  }
778
777
  function allKeys(source) {
779
- const keys = new Set(Object.keys(source));
780
- const proto = Object.getPrototypeOf(source);
781
- if (proto && proto !== Object.prototype) {
782
- for (const k of Object.getOwnPropertyNames(proto)) if (k !== "constructor") keys.add(k);
783
- }
784
- return [...keys];
778
+ const keys = new Set(Object.keys(source));
779
+ const proto = Object.getPrototypeOf(source);
780
+ if (proto && proto !== Object.prototype) {
781
+ for (const k of Object.getOwnPropertyNames(proto)) if (k !== "constructor") keys.add(k);
782
+ }
783
+ return [...keys];
785
784
  }
786
785
  function fieldOfKey(key) {
787
- return key.endsWith("+") || key.endsWith("-") ? key.slice(0, -1) : key;
786
+ return key.endsWith("+") || key.endsWith("-") ? key.slice(0, -1) : key;
788
787
  }
789
788
  function encodingFor(key, plan) {
790
- return plan.knownEncodings.get(fieldOfKey(key)) ?? plan.defaultEncoding;
789
+ return plan.knownEncodings.get(fieldOfKey(key)) ?? plan.defaultEncoding;
791
790
  }
792
791
  function planFor(app, registry, resource) {
793
- const knownEncodings = /* @__PURE__ */ new Map();
794
- const defaultAdapter = registry.has("default") ? registry.get("default") : void 0;
795
- const defaultEncoding = defaultAdapter?.pipeline === "fused" && defaultAdapter.encoding ? defaultAdapter.encoding : "multipart";
796
- let bodyEncoding = defaultEncoding === "base64" ? "multipart" : defaultEncoding;
797
- const def = app.registry.get(resource);
798
- for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {
799
- const ui = field.ui;
800
- if (ui?.component !== "file") continue;
801
- if (!registry.has(ui.fileStorage ?? "default")) continue;
802
- const adapter = registry.get(ui.fileStorage);
803
- if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
804
- knownEncodings.set(fieldName, adapter.encoding);
805
- if (adapter.encoding !== "base64") bodyEncoding = adapter.encoding;
806
- }
807
- return { knownEncodings, defaultEncoding, bodyEncoding };
808
- }
809
-
810
- // src/wrapper/assert-wiring.ts
811
- var BODY_ENCODINGS = ["multipart", "graphql-upload"];
792
+ const knownEncodings = new Map();
793
+ const defaultAdapter = registry.has("default") ? registry.get("default") : void 0;
794
+ const defaultEncoding = defaultAdapter?.pipeline === "fused" && defaultAdapter.encoding ? defaultAdapter.encoding : "multipart";
795
+ let bodyEncoding = defaultEncoding === "base64" ? "multipart" : defaultEncoding;
796
+ const def = app.registry.get(resource);
797
+ for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {
798
+ const ui = field.ui;
799
+ if (ui?.component !== "file") continue;
800
+ if (!registry.has(ui.fileStorage ?? "default")) continue;
801
+ const adapter = registry.get(ui.fileStorage);
802
+ if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
803
+ knownEncodings.set(fieldName, adapter.encoding);
804
+ if (adapter.encoding !== "base64") bodyEncoding = adapter.encoding;
805
+ }
806
+ return {
807
+ knownEncodings,
808
+ defaultEncoding,
809
+ bodyEncoding
810
+ };
811
+ }
812
+ //#endregion
813
+ //#region src/wrapper/assert-wiring.ts
814
+ const BODY_ENCODINGS = ["multipart", "graphql-upload"];
812
815
  function assertFileStorageWiring(app, provider) {
813
- const registry = app.getService(FILE_STORAGE_SERVICE);
814
- if (!registry) return;
815
- if (registry.hasFused()) {
816
- const stamped = provider != null && (typeof provider === "object" || typeof provider === "function") && FILE_STORAGE_WRAPPED in provider;
817
- if (!stamped) {
818
- throw new Error(
819
- "A fused file storage is registered, but the data provider is not wrapped. Files ride inside the record mutation for this pipeline, so an unwrapped provider will JSON.stringify them into `{}` and lose them silently. Wrap it once: dataProvider: withFileStorage(myDataProvider, app)"
820
- );
821
- }
822
- }
823
- for (const def of app.registry.list()) {
824
- const seen = /* @__PURE__ */ new Map();
825
- for (const [fieldName, field] of Object.entries(def.fields ?? {})) {
826
- const ui = field.ui;
827
- if (ui?.component !== "file") continue;
828
- if (!registry.has(ui.fileStorage ?? "default")) continue;
829
- const adapter = registry.get(ui.fileStorage);
830
- if (adapter.pipeline === "fused") {
831
- const mapper = ui.mapper ?? adapter.defaultMapper;
832
- if (!mapper.carriesPending) {
833
- throw new Error(
834
- `Resource '${def.name}' field '${fieldName}' pairs a fused file storage with a mapper that cannot carry a pending file forward. A ref on a fused pipeline never has a \`handle\` before the record mutation runs \u2014 only \`pending\` \u2014 and this mapper's \`write\` has no \`carriesPending\` marker, meaning it drops any ref with no handle. That silently discards every file added through this field, with no error. Use a mapper built for this pipeline (scalarHandle, pocketbaseDiff), or give this field one that sets \`carriesPending: true\` via \`f.file({ mapper })\`.`
835
- );
836
- }
837
- }
838
- if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
839
- if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;
840
- const previous = seen.get(adapter.encoding);
841
- if (previous == null) seen.set(adapter.encoding, fieldName);
842
- if (seen.size > 1) {
843
- const entries = [...seen.entries()];
844
- const [encA, fieldA] = entries[0];
845
- const [encB, fieldB] = entries[1];
846
- throw new Error(
847
- `Resource '${def.name}' mixes two request-body encodings: field '${fieldA}' wants ${encA} and field '${fieldB}' wants ${encB}. One request has one body \u2014 move one of these fields to a storage using the same encoding, or to base64, which transforms values rather than claiming the body.`
848
- );
849
- }
850
- }
851
- }
852
- }
853
-
854
- // src/baseline-carrier.ts
855
- var FILE_MAPPER_BASELINE_CARRIER = /* @__PURE__ */ Symbol.for("buro.fileMapper.baselineCarrier");
816
+ const registry = app.getService(FILE_STORAGE_SERVICE);
817
+ if (!registry) return;
818
+ if (registry.hasFused()) {
819
+ if (!(provider != null && (typeof provider === "object" || typeof provider === "function") && FILE_STORAGE_WRAPPED in provider)) throw new Error("A fused file storage is registered, but the data provider is not wrapped. Files ride inside the record mutation for this pipeline, so an unwrapped provider will JSON.stringify them into `{}` and lose them silently. Wrap it once: dataProvider: withFileStorage(myDataProvider, app)");
820
+ }
821
+ for (const def of app.registry.list()) {
822
+ const seen = new Map();
823
+ for (const [fieldName, field] of Object.entries(def.fields ?? {})) {
824
+ const ui = field.ui;
825
+ if (ui?.component !== "file") continue;
826
+ if (!registry.has(ui.fileStorage ?? "default")) continue;
827
+ const adapter = registry.get(ui.fileStorage);
828
+ if (adapter.pipeline === "fused") {
829
+ if (!(ui.mapper ?? adapter.defaultMapper).carriesPending) throw new Error(`Resource '${def.name}' field '${fieldName}' pairs a fused file storage with a mapper that cannot carry a pending file forward. A ref on a fused pipeline never has a \`handle\` before the record mutation runs — only \`pending\` — and this mapper's \`write\` has no \`carriesPending\` marker, meaning it drops any ref with no handle. That silently discards every file added through this field, with no error. Use a mapper built for this pipeline (scalarHandle, pocketbaseDiff), or give this field one that sets \`carriesPending: true\` via \`f.file({ mapper })\`.`);
830
+ }
831
+ if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
832
+ if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;
833
+ if (seen.get(adapter.encoding) == null) seen.set(adapter.encoding, fieldName);
834
+ if (seen.size > 1) {
835
+ const entries = [...seen.entries()];
836
+ const [encA, fieldA] = entries[0];
837
+ const [encB, fieldB] = entries[1];
838
+ throw new Error(`Resource '${def.name}' mixes two request-body encodings: field '${fieldA}' wants ${encA} and field '${fieldB}' wants ${encB}. One request has one body — move one of these fields to a storage using the same encoding, or to base64, which transforms values rather than claiming the body.`);
839
+ }
840
+ }
841
+ }
842
+ }
843
+ //#endregion
844
+ //#region src/baseline-carrier.ts
845
+ const FILE_MAPPER_BASELINE_CARRIER = Symbol.for("buro.fileMapper.baselineCarrier");
856
846
  function inheritFileMapperBaseline(source, target) {
857
- let descriptor;
858
- try {
859
- descriptor = Object.getOwnPropertyDescriptor(source, FILE_MAPPER_BASELINE_CARRIER);
860
- } catch {
861
- return target;
862
- }
863
- if (descriptor === void 0 || !("value" in descriptor) || descriptor.value === null || typeof descriptor.value !== "object") {
864
- return target;
865
- }
866
- Object.defineProperty(target, FILE_MAPPER_BASELINE_CARRIER, {
867
- value: descriptor.value,
868
- enumerable: false,
869
- configurable: true,
870
- writable: false
871
- });
872
- return target;
873
- }
874
-
875
- // src/index.ts
876
- var PACKAGE_NAME = "@burojs/file-storage";
877
-
847
+ let descriptor;
848
+ try {
849
+ descriptor = Object.getOwnPropertyDescriptor(source, FILE_MAPPER_BASELINE_CARRIER);
850
+ } catch {
851
+ return target;
852
+ }
853
+ if (descriptor === void 0 || !("value" in descriptor) || descriptor.value === null || typeof descriptor.value !== "object") return target;
854
+ Object.defineProperty(target, FILE_MAPPER_BASELINE_CARRIER, {
855
+ value: descriptor.value,
856
+ enumerable: false,
857
+ configurable: true,
858
+ writable: false
859
+ });
860
+ return target;
861
+ }
862
+ //#endregion
863
+ //#region src/index.ts
864
+ const PACKAGE_NAME = "@burojs/file-storage";
865
+ //#endregion
878
866
  exports.COLUMN_PATCH = COLUMN_PATCH;
879
867
  exports.FILE_STORAGE_SERVICE = FILE_STORAGE_SERVICE;
880
868
  exports.FILE_STORAGE_WRAPPED = FILE_STORAGE_WRAPPED;
@@ -903,5 +891,5 @@ exports.scalarHandle = scalarHandle;
903
891
  exports.splicePatches = splicePatches;
904
892
  exports.supabaseFileStorage = supabaseFileStorage;
905
893
  exports.withFileStorage = withFileStorage;
906
- //# sourceMappingURL=index.cjs.map
894
+
907
895
  //# sourceMappingURL=index.cjs.map