@burojs/file-storage 0.4.0 → 0.5.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/README.md +1 -1
- package/dist/index.cjs +8 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -10
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,5 +9,5 @@ for the full architecture and rollout plan.
|
|
|
9
9
|
`inheritFileMapperBaseline(source, target)` copies the opaque edit capability
|
|
10
10
|
from a decoded record to a freshly assembled form payload. It does not expose
|
|
11
11
|
the retained backend value, is non-enumerable, and does not survive object
|
|
12
|
-
spread or JSON serialization. Ordinary
|
|
12
|
+
spread or JSON serialization. Ordinary `<RecordScreen>` submissions apply it
|
|
13
13
|
automatically; custom form assemblers can call it explicitly.
|
package/dist/index.cjs
CHANGED
|
@@ -720,7 +720,6 @@ const FILE_STORAGE_WRAPPED = Symbol.for("buro.fileStorage.wrapped");
|
|
|
720
720
|
const MUTATING = new Set([
|
|
721
721
|
"create",
|
|
722
722
|
"update",
|
|
723
|
-
"createMany",
|
|
724
723
|
"updateMany"
|
|
725
724
|
]);
|
|
726
725
|
function isProviderMap(value) {
|
|
@@ -751,8 +750,7 @@ function withFileStorage(provider, app) {
|
|
|
751
750
|
wrapped[key] = async (args) => {
|
|
752
751
|
const variables = args?.variables;
|
|
753
752
|
if (!variables || typeof variables !== "object" || variables instanceof FormData) return value(args);
|
|
754
|
-
if (
|
|
755
|
-
if (Array.isArray(variables)) return value(args);
|
|
753
|
+
if (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.`);
|
|
756
754
|
const spliced = splicePatches(variables);
|
|
757
755
|
if (!containsFile(spliced)) {
|
|
758
756
|
const passthrough = spliced === variables ? args : {
|
|
@@ -795,10 +793,10 @@ function planFor(app, registry, resource) {
|
|
|
795
793
|
let bodyEncoding = defaultEncoding === "base64" ? "multipart" : defaultEncoding;
|
|
796
794
|
const def = app.registry.get(resource);
|
|
797
795
|
for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {
|
|
796
|
+
if (field.kind !== "file") continue;
|
|
798
797
|
const ui = field.ui;
|
|
799
|
-
if (ui?.
|
|
800
|
-
|
|
801
|
-
const adapter = registry.get(ui.fileStorage);
|
|
798
|
+
if (!registry.has(ui?.fileStorage ?? "default")) continue;
|
|
799
|
+
const adapter = registry.get(ui?.fileStorage);
|
|
802
800
|
if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
|
|
803
801
|
knownEncodings.set(fieldName, adapter.encoding);
|
|
804
802
|
if (adapter.encoding !== "base64") bodyEncoding = adapter.encoding;
|
|
@@ -821,12 +819,12 @@ function assertFileStorageWiring(app, provider) {
|
|
|
821
819
|
for (const def of app.registry.list()) {
|
|
822
820
|
const seen = new Map();
|
|
823
821
|
for (const [fieldName, field] of Object.entries(def.fields ?? {})) {
|
|
822
|
+
if (field.kind !== "file") continue;
|
|
824
823
|
const ui = field.ui;
|
|
825
|
-
if (ui?.
|
|
826
|
-
|
|
827
|
-
const adapter = registry.get(ui.fileStorage);
|
|
824
|
+
if (!registry.has(ui?.fileStorage ?? "default")) continue;
|
|
825
|
+
const adapter = registry.get(ui?.fileStorage);
|
|
828
826
|
if (adapter.pipeline === "fused") {
|
|
829
|
-
if (!(ui
|
|
827
|
+
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
828
|
}
|
|
831
829
|
if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
|
|
832
830
|
if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["asString"],"sources":["../src/registry.ts","../src/plugin.ts","../src/mappers/ref.ts","../src/kind.ts","../src/mappers/scalar-handle.ts","../src/mappers/rich-image.ts","../src/mappers/junction.ts","../src/contract/run-contract.ts","../src/contract/fake-adapter.ts","../src/adapters/inline.ts","../src/adapters/nhost.ts","../src/adapters/supabase.ts","../src/adapters/s3.ts","../src/mappers/column-patch.ts","../src/adapters/pocketbase.ts","../src/wrapper/encode.ts","../src/wrapper/with-file-storage.ts","../src/wrapper/assert-wiring.ts","../src/baseline-carrier.ts","../src/index.ts"],"sourcesContent":["import type { FileStorageAdapter, FileStorageRegistry } from './types.js';\n\n/** Service name under which the registry is registered on the app. */\nexport const FILE_STORAGE_SERVICE = 'fileStorage' as const;\n\nexport function createFileStorageRegistry(\n map: Record<string, FileStorageAdapter>,\n): FileStorageRegistry {\n if (!map.default) {\n throw new Error(\n \"fileStoragePlugin requires a 'default' entry — every file field that \" +\n 'does not name a storage resolves to it.',\n );\n }\n const entries = new Map(Object.entries(map));\n return {\n get(name = 'default') {\n const found = entries.get(name);\n if (!found) {\n throw new Error(\n `Unknown file storage '${name}'. Registered: ${[...entries.keys()].join(', ')}.`,\n );\n }\n return found;\n },\n has: (name) => entries.has(name),\n names: () => [...entries.keys()],\n hasFused: () => [...entries.values()].some((a) => a.pipeline === 'fused'),\n };\n}\n","import type { Plugin } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nimport type { FileStorageAdapter } from './types.js';\n\n/**\n * Declare the app's file storages as a named map, mirroring the existing\n * multi-data-provider convention. `default` is required; other keys are free\n * form and are what a field names in `f.file({ fileStorage: '…' })`.\n */\nexport function fileStoragePlugin(map: Record<string, FileStorageAdapter>): Plugin {\n const adapters = Object.freeze({ ...map });\n return {\n name: '@burojs/file-storage',\n enforce: 'pre',\n setup: (api) => {\n api.runtime.registerService(FILE_STORAGE_SERVICE, () => createFileStorageRegistry(adapters));\n },\n };\n}\n","import type { FileRef } from '../types.js';\n\nlet seq = 0;\n\n/**\n * Mint a ref with a fresh client-side identity.\n *\n * Deliberately a counter rather than `crypto.randomUUID()`: ids are\n * process-local and never persisted, and a counter keeps test output\n * readable and snapshots stable.\n */\nexport function newRef(init: Partial<FileRef> = {}): FileRef {\n seq += 1;\n return { localId: `ref-${seq}`, ...init };\n}\n","import type { FileRef } from './types.js';\n\n/**\n * Kind by mime; the filename is consulted only when mime is unknown.\n *\n * A pure predicate over `FileRef`, not a UI concern — it lives next to the\n * type it describes so any renderer, in `@burojs/ui-shadcn` or a future UI\n * kit, answers \"is this an image?\" the same way. Two independently\n * maintained copies (one per renderer) drift the moment one gains a mime\n * type or extension the other lacks — which is exactly the bug this\n * function replaces: the deleted `isLikelyImageUrl` regexed an extension out\n * of the stored string, which can never match a uuid or other opaque handle.\n */\nexport function isImage(ref: FileRef): boolean {\n if (ref.mime) return ref.mime.startsWith('image/');\n const name = ref.name ?? '';\n return /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(name);\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\ntype ScalarColumn = string | string[] | null | undefined;\ntype ScalarWriteValue = string | File | (string | File)[] | null;\n\n/**\n * The column IS the handle: a URL, an id, a key, a path, a filename. One\n * mapper covers all four because the difference between them is meaning, not\n * structure — and three names for one function would be three things to keep\n * in sync for no behavioural gain.\n */\nexport function scalarHandle(): FileMapper<ScalarColumn, ScalarWriteValue> {\n return {\n carriesPending: true,\n\n read(column: ScalarColumn): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((entry) => entry !== '' && entry != null)\n .map((entry) => newRef({ handle: entry, raw: entry, name: tail(entry) }));\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): ScalarWriteValue {\n // A ref without a handle has not landed yet. On an `immediate`/\n // `handshake`/`resumable` pipeline that only ever means \"still\n // uploading\" — nothing to write. On a `fused` pipeline it means the\n // bytes are riding the record mutation itself, and `pending` is the\n // File to carry forward: writing the raw File here (rather than\n // omitting the ref) is what lets `withFileStorage` pick it up and\n // encode it into the mutation. Omitting it — the previous behaviour —\n // silently discarded every file added to a fused field using this\n // mapper, which is exactly the pairing `inlineFileStorage` ships as\n // its default.\n const values = next\n .map((r) => r.handle ?? r.pending)\n .filter((v): v is string | File => v != null);\n if (ctx.multiple) return values;\n return values[0] ?? null;\n },\n };\n}\n\n/** Last path segment, used as a display name when the backend gives none. */\nfunction tail(value: string): string | undefined {\n const seg = value.split('/').pop();\n return seg && seg !== value ? seg : value || undefined;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface RichImageOpts {\n /**\n * Who owns the focal point.\n *\n * - `'usage'` — it lives on the reference inside the record, so the same\n * asset crops differently in two records (Sanity-shaped).\n * - `'file'` — it is a property of the file that the storage owns; the\n * mapper leaves it alone (Directus-shaped).\n * - `false` — this column has none.\n *\n * There is no default. Guessing wrong silently corrupts a neighbouring\n * record's crop, which is exactly the failure nobody notices for months.\n */\n focus: 'usage' | 'file' | false;\n /** Dotted path to the handle inside the object. Defaults to `'id'`. */\n handleKey?: string;\n /** Keys lifted into `usage` when `focus === 'usage'`. */\n usageKeys?: string[];\n}\n\nconst DEFAULT_USAGE_KEYS = ['hotspot', 'crop'];\n\ntype Obj = Record<string, unknown>;\n\nexport function richImage(opts: RichImageOpts): FileMapper<unknown, unknown> {\n const handleKey = opts.handleKey ?? 'id';\n const usageKeys = opts.usageKeys ?? DEFAULT_USAGE_KEYS;\n\n function readOne(entry: Obj): FileRef {\n const usage =\n opts.focus === 'usage'\n ? pick(\n entry,\n usageKeys.filter((k) => entry[k] !== undefined),\n )\n : undefined;\n return newRef({\n handle: asString(getPath(entry, handleKey)),\n raw: entry,\n name: asString(entry.name ?? entry.filename_download),\n mime: asString(entry.mime ?? entry.type),\n size: asNumber(entry.size ?? entry.filesize),\n width: asNumber(entry.width),\n height: asNumber(entry.height),\n ...(usage && Object.keys(usage).length > 0 ? { usage } : {}),\n });\n }\n\n return {\n read(column, ctx: MapperCtx): FileRef[] {\n if (column == null) return [];\n const list = (Array.isArray(column) ? column : [column]) as Obj[];\n return list.filter((e) => e && typeof e === 'object').map(readOne);\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): unknown {\n const out = next\n .filter((r) => r.handle != null)\n .map((r) => {\n // Merge ONTO the original entry. Rebuilding it would delete every\n // key this mapper does not model — and real objects carry many.\n const base: Obj = isObj(r.raw) ? { ...r.raw } : {};\n setPath(base, handleKey, r.handle as string);\n if (opts.focus === 'usage' && r.usage) Object.assign(base, r.usage);\n return base;\n });\n if (ctx.multiple) return out;\n return out[0] ?? null;\n },\n };\n}\n\nfunction isObj(v: unknown): v is Obj {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\nfunction asNumber(v: unknown): number | undefined {\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\nfunction pick(src: Obj, keys: string[]): Obj {\n const out: Obj = {};\n for (const k of keys) out[k] = src[k];\n return out;\n}\nfunction getPath(src: Obj, path: string): unknown {\n return path.split('.').reduce<unknown>((acc, k) => (isObj(acc) ? acc[k] : undefined), src);\n}\nfunction setPath(target: Obj, path: string, value: string): void {\n const keys = path.split('.');\n const last = keys.pop() as string;\n let cursor: Obj = target;\n for (const k of keys) {\n const nextLevel = cursor[k];\n // Clone rather than mutate: `raw` is shared with the value we were handed.\n cursor[k] = isObj(nextLevel) ? { ...nextLevel } : {};\n cursor = cursor[k] as Obj;\n }\n cursor[last] = value;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface JunctionOpts {\n /** Column on the junction row holding the file's handle. */\n fileKey: string;\n /** Column holding the 1-based position. Omit when the table is unordered. */\n sortKey?: string;\n}\n\ntype Row = Record<string, unknown>;\n\n/**\n * A column that is an array of junction ROWS rather than of files — Directus\n * M2M being the canonical case. Each row carries its own primary key, which\n * must survive a reorder: dropping it would delete and recreate every row.\n */\nexport function junction(opts: JunctionOpts): FileMapper<Row[] | null | undefined, Row[]> {\n return {\n read(column, _ctx: MapperCtx): FileRef[] {\n if (!Array.isArray(column)) return [];\n return column\n .filter((row) => row && typeof row === 'object')\n .map((row) => newRef({ handle: asString(row[opts.fileKey]), raw: row }));\n },\n\n write(next: FileRef[], _prev: FileRef[], _ctx: MapperCtx): Row[] {\n return next\n .filter((r) => r.handle != null)\n .map((r, i) => {\n const base: Row = r.raw && typeof r.raw === 'object' ? { ...(r.raw as Row) } : {};\n base[opts.fileKey] = r.handle as string;\n if (opts.sortKey) base[opts.sortKey] = i + 1;\n return base;\n });\n },\n };\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\n","import type { FileMapper, FileStorageAdapter, MapperCtx, ResolveCtx } from '../types.js';\n\nexport interface ContractOpts {\n /** A FRESH adapter per case — shared state between cases hides bugs. */\n adapter: () => FileStorageAdapter;\n mapper: FileMapper;\n /** Representative column values this mapper reads, for the invariants. */\n sampleColumn: { single: unknown; multiple: unknown };\n /** Skip upload cases for adapters whose bytes ride the mutation. */\n skipUpload?: boolean;\n /**\n * Set for a mapper whose `write` returns a COLUMN PATCH rather than a column\n * value — `pocketbaseDiff` is the one in this package.\n *\n * Round-trip identity is meaningless for such a mapper: it never returns the\n * value it was given, it returns the keys needed to change it. The patch\n * invariants below replace the round-trip cases rather than sitting beside\n * them, because running both would demand two contradictory things of one\n * `write`.\n */\n writesColumnPatch?: boolean;\n /**\n * Context every `resolveUrl` case is called with. Defaults to `{}`.\n *\n * Supply it for an adapter whose URL cannot be built from a handle alone.\n * A PocketBase filename is meaningless by itself — the URL is assembled from\n * collection + record + filename — so that adapter correctly THROWS on an\n * empty context, and the contract must hand it a real one rather than\n * treating the throw as a conformance failure.\n */\n resolveCtx?: ResolveCtx;\n}\n\n/**\n * The test runner, injected.\n *\n * `src/` may not import vitest: this package promises zero runtime\n * dependencies, and a module under `src/` importing a test framework ships\n * that import in the published bundle. Injection also lets an adopter on jest\n * or node:test run the same contract.\n */\nexport interface ContractRunner {\n it: (name: string, fn: () => void | Promise<void>) => void;\n // Structural, so any assertion library with these shapes fits. The return\n // type must be `unknown`, not `never`: `never` demands the SOURCE function\n // return `never` too (return types are checked covariantly), which no real\n // assertion library does — vitest's `ExpectStatic` returns `Assertion<T>`.\n // `unknown` still rejects a non-function value while accepting any return\n // shape, so this stays structural without over-promising.\n expect: (actual: never) => unknown;\n}\n\nconst single: MapperCtx = { resource: 'demo', field: 'file', multiple: false };\nconst many: MapperCtx = { resource: 'demo', field: 'files', multiple: true };\n\n/**\n * The conformance suite every adapter owes. Call inside a `describe`.\n *\n * Exported from the package so adopters can point it at their own adapter —\n * the claim \"any API can be stretched onto this port\" is only worth something\n * if it is executable by the person doing the stretching.\n */\nexport function runFileStorageContract(opts: ContractOpts, runner: ContractRunner): void {\n const { mapper } = opts;\n const it = runner.it;\n // biome-ignore lint/suspicious/noExplicitAny: structural assertion shim\n const expect = runner.expect as unknown as (actual: unknown) => any;\n\n it('declares a pipeline', () => {\n expect(['immediate', 'fused', 'handshake', 'resumable']).toContain(opts.adapter().pipeline);\n });\n\n it('ships a default mapper', () => {\n const m = opts.adapter().defaultMapper;\n expect(typeof m.read).toBe('function');\n expect(typeof m.write).toBe('function');\n });\n\n if (opts.writesColumnPatch) {\n it('emits an empty patch when nothing changed', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as { keys?: Record<string, unknown> };\n expect(Object.keys(out.keys ?? {}).length).toBe(0);\n });\n\n it('emits only patch keys, never the raw column value', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as Record<string, unknown>;\n expect(typeof out.keys).toBe('object');\n });\n } else {\n it('round-trips an untouched single value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.single, single);\n expect(mapper.write(refs, refs, single)).toEqual(opts.sampleColumn.single);\n });\n\n it('round-trips an untouched multiple value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(mapper.write(refs, refs, many)).toEqual(opts.sampleColumn.multiple);\n });\n }\n\n it('reads null as no files', () => {\n expect(mapper.read(null, single)).toEqual([]);\n });\n\n it('gives every ref a distinct localId', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(new Set(refs.map((r) => r.localId)).size).toBe(refs.length);\n });\n\n if (!opts.skipUpload) {\n it('reports progress and resolves to a ref carrying a handle', async () => {\n const adapter = opts.adapter();\n const seen: number[] = [];\n const ref = await adapter.upload(new File(['hello'], 'a.txt', { type: 'text/plain' }), {\n signal: new AbortController().signal,\n onProgress: (loaded) => seen.push(loaded),\n });\n expect(ref.handle).toBeTruthy();\n expect(seen.length).toBeGreaterThan(0);\n expect(seen.at(-1)).toBe(5);\n });\n\n it('rejects with an AbortError when the signal is already aborted', async () => {\n const adapter = opts.adapter();\n const ac = new AbortController();\n ac.abort();\n await expect(\n adapter.upload(new File(['x'], 'x.txt'), {\n signal: ac.signal,\n onProgress: () => {},\n }),\n ).rejects.toThrow(/abort/i);\n });\n\n it('accepts a retry after a failure', async () => {\n // A GENUINE, guaranteed failure — not \"whatever the first call happens\n // to do\" (the version this replaced called `upload` twice with fresh\n // signals and asserted `expect(first).toBeTruthy()`, where `first` was\n // always the string `'ok'` or `'failed'`; both are truthy, so the\n // assertion could not fail even if the adapter never actually failed\n // once). An already-aborted signal is guaranteed to reject (every\n // reference adapter's own \"rejects with an AbortError\" case above\n // proves it does), so this exercises a REAL failure and then proves the\n // adapter's internal state survived it well enough to accept a real\n // retry — no leftover lock, counter, or half-open request wedges the\n // next call.\n const adapter = opts.adapter();\n const file = new File(['x'], 'x.txt');\n const aborted = new AbortController();\n aborted.abort();\n await expect(\n adapter.upload(file, { signal: aborted.signal, onProgress: () => {} }),\n ).rejects.toThrow();\n const second = await adapter.upload(file, {\n signal: new AbortController().signal,\n onProgress: () => {},\n });\n expect(second.handle).toBeTruthy();\n });\n }\n\n it('resolves a URL for a ref', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n const url = typeof resolved === 'string' ? resolved : resolved.url;\n expect(url).toMatch(/^https?:\\/\\//);\n });\n\n it('either returns a permanent string or states a future expiry — never neither', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n if (typeof resolved === 'string') return;\n // The object arm means \"signed\". A signed URL with no deadline would be\n // cached forever, which is the exact failure this port exists to prevent,\n // so silence is a contract violation rather than a permitted default.\n expect(typeof resolved.expiresAt).toBe('number');\n expect(resolved.expiresAt).toBeGreaterThan(Date.now());\n });\n\n it('leaves unreportable metadata absent rather than guessing', async () => {\n const adapter = opts.adapter();\n if (!adapter.stat) return;\n const [out] = await adapter.stat([{ localId: 'x', handle: 'definitely-not-stored' }]);\n expect(out?.size).not.toBe(0);\n });\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type { FileRef, FileStorageAdapter, ResolveCtx, UploadCtx } from '../types.js';\n\nexport interface FakeAdapterOpts {\n /** Emit this many progress ticks before resolving. Default 2. */\n progressTicks?: number;\n /** Make every upload reject with this message. */\n failWith?: string;\n /** Return signed URLs that expire this many ms from resolution. */\n signedTtlMs?: number;\n /** Default `'immediate'`. Set `'fused'` to stand in for an inline/PocketBase-shaped storage. */\n pipeline?: 'immediate' | 'fused';\n}\n\n/**\n * In-memory adapter. Exists so the contract suite can be tested against\n * something known-correct, and so adopters have a baseline to diff against\n * when their own adapter fails a case.\n */\nexport function createFakeAdapter(opts: FakeAdapterOpts = {}): FileStorageAdapter & {\n stored: Map<string, File>;\n removed: string[];\n} {\n const stored = new Map<string, File>();\n const removed: string[] = [];\n let seq = 0;\n\n return {\n stored,\n removed,\n pipeline: opts.pipeline ?? 'immediate',\n defaultMapper: scalarHandle(),\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n const ticks = opts.progressTicks ?? 2;\n for (let i = 1; i <= ticks; i += 1) {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // Yield so an abort issued from a test lands between ticks.\n await Promise.resolve();\n ctx.onProgress(Math.round((file.size * i) / ticks), file.size);\n }\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n if (opts.failWith) throw new Error(opts.failWith);\n seq += 1;\n const handle = `fake-${seq}-${file.name}`;\n stored.set(handle, file);\n return newRef({ handle, name: file.name, mime: file.type || undefined, size: file.size });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx) {\n const q = ctx.variant?.width ? `?w=${ctx.variant.width}` : '';\n const url = `https://fake.test/${ref.handle}${q}`;\n if (opts.signedTtlMs == null) return url;\n return Promise.resolve({ url, expiresAt: Date.now() + opts.signedTtlMs });\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n return refs.map((r) => {\n const f = r.handle ? stored.get(r.handle) : undefined;\n // Honesty rule: report nothing rather than guess when the file is\n // unknown to this store.\n return f ? { ...r, name: f.name, size: f.size, mime: f.type || undefined } : r;\n });\n },\n\n async remove(ref: FileRef): Promise<void> {\n if (ref.handle) {\n stored.delete(ref.handle);\n removed.push(ref.handle);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n FusedEncoding,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface InlineFileStorageOpts {\n /**\n * How the data-provider wrapper should encode the bytes into the mutation.\n * `base64` transforms the value; `multipart` and `graphql-upload` set the\n * request body and are mutually exclusive with each other.\n */\n encoding: FusedEncoding;\n /**\n * How a saved file is read back. `ctx.resource` and `ctx.record` are given\n * because backends of this shape usually address files through the record\n * that owns them.\n */\n resolveUrl: (ref: FileRef, ctx: ResolveCtx) => ResolvedUrl | Promise<ResolvedUrl>;\n}\n\n/**\n * The storage that is not a storage: the backend takes the file inside the\n * record mutation, so there is no upload endpoint and no handle until the\n * mutation succeeds.\n *\n * `upload` deliberately does nothing but wrap the File in a ref. Progress here\n * belongs to the mutation, and it is `withFileStorage` — the only code on the\n * request path — that can report it.\n */\nexport function inlineFileStorage(opts: InlineFileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'fused',\n encoding: opts.encoding,\n defaultMapper: scalarHandle(),\n // Nothing was uploaded ahead of the save, so nothing can be orphaned.\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref, ctx) {\n return opts.resolveUrl(ref, ctx);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface NhostFileStorageOpts {\n /** Storage service base URL, e.g. `https://<project>.storage.<region>.nhost.run`. */\n baseUrl: string;\n /**\n * Access token supplier. Injected rather than read from localStorage: the\n * v0.2 implementation hardcoded a storage key and a window global, which\n * meant one app shape was supported and no test could drive it.\n */\n getToken: () => string | null | Promise<string | null>;\n bucketId?: string;\n orphans?: OrphanPolicy;\n fetchImpl?: typeof fetch;\n}\n\ninterface NhostFileMeta {\n id?: string;\n name?: string;\n size?: number;\n mimeType?: string;\n}\n\n/**\n * nhost storage: bytes go up first, the column keeps the file's UUID, and the\n * display URL is signed and short-lived.\n *\n * ⚠️ The handle is the id, NOT the presigned URL. Storing the presigned URL —\n * as v0.2 did — writes a link with a TTL into the database.\n */\nexport function nhostFileStorage(opts: NhostFileStorageOpts): FileStorageAdapter {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const base = opts.baseUrl.replace(/\\/+$/, '');\n\n async function authHeaders(): Promise<Record<string, string>> {\n const token = await opts.getToken();\n if (!token) throw new Error('nhostFileStorage: no access token — the user is not signed in');\n return { Authorization: `Bearer ${token}` };\n }\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const headers = await authHeaders();\n const fd = new FormData();\n fd.append('file[]', file);\n if (opts.bucketId) fd.append('bucket-id', opts.bucketId);\n\n // fetch has no upload progress; report the two endpoints we can honestly\n // report rather than faking a curve.\n ctx.onProgress(0, file.size);\n const res = await fetchImpl(`${base}/v1/files`, {\n method: 'POST',\n headers,\n body: fd,\n signal: ctx.signal,\n });\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`nhost upload failed: ${res.status} ${body}`.trim());\n }\n const json = (await res.json()) as NhostFileMeta | { processedFiles?: NhostFileMeta[] };\n const meta = 'processedFiles' in json ? json.processedFiles?.[0] : (json as NhostFileMeta);\n if (!meta?.id) throw new Error('nhost upload returned no file id');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: meta.id,\n name: meta.name ?? file.name,\n mime: meta.mimeType ?? file.type ?? undefined,\n size: meta.size ?? file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const headers = await authHeaders();\n const params = new URLSearchParams();\n if (ctx.variant?.width) params.set('w', String(ctx.variant.width));\n if (ctx.variant?.height) params.set('h', String(ctx.variant.height));\n const qs = params.toString();\n const res = await fetchImpl(\n `${base}/v1/files/${ref.handle}/presignedurl${qs ? `?${qs}` : ''}`,\n { headers },\n );\n if (!res.ok) throw new Error(`nhost presign failed: ${res.status}`);\n const json = (await res.json()) as { url?: string; expiration?: number };\n if (!json.url) throw new Error('nhost presign returned no url');\n return {\n url: json.url,\n // `expiration` is a lifetime in seconds; the cache wants an instant.\n expiresAt: Date.now() + (json.expiration ?? 1800) * 1000,\n };\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n const headers = await authHeaders();\n return Promise.all(\n refs.map(async (ref) => {\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'HEAD',\n headers,\n });\n if (!res.ok) return ref;\n // ⚠️ `headers.get` returns null for a missing header, and\n // `Number(null)` is 0 — which `Number.isFinite` happily accepts. Read\n // the header first and only convert when it is actually present, or a\n // file of unknown size reports as literally zero bytes and every\n // size column downstream believes it. That is the honesty rule\n // (`types.ts`: never guess, never substitute a zero).\n const rawSize = res.headers.get('content-length');\n const size = rawSize == null ? Number.NaN : Number(rawSize);\n const mime = res.headers.get('content-type') ?? undefined;\n return {\n ...ref,\n ...(Number.isFinite(size) ? { size } : {}),\n ...(mime ? { mime } : {}),\n };\n }),\n );\n },\n\n async remove(ref: FileRef): Promise<void> {\n const headers = await authHeaders();\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'DELETE',\n headers,\n });\n if (!res.ok && res.status !== 404) {\n throw new Error(`nhost delete failed: ${res.status}`);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * The slice of `@supabase/supabase-js` this adapter uses, declared\n * structurally so the package keeps zero runtime dependencies and the adapter\n * can be driven by a plain object in tests.\n */\nexport interface SupabaseStorageLike {\n storage: {\n from(bucket: string): {\n upload(\n path: string,\n file: File,\n opts?: { contentType?: string; upsert?: boolean },\n ): Promise<{ data: { path: string } | null; error: { message: string } | null }>;\n getPublicUrl(path: string): { data: { publicUrl: string } };\n createSignedUrl(\n path: string,\n expiresIn: number,\n ): Promise<{ data: { signedUrl: string } | null; error: { message: string } | null }>;\n remove(paths: string[]): Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n}\n\nexport interface SupabaseFileStorageOpts {\n client: SupabaseStorageLike;\n bucket: string;\n /** Public buckets yield permanent URLs; signed buckets yield expiring ones. */\n access: 'public' | 'signed';\n /** Lifetime of a signed URL in seconds. Default 3600. */\n signedTtlSec?: number;\n /** Prepended to every generated object path, e.g. `'avatars'`. */\n pathPrefix?: string;\n orphans?: OrphanPolicy;\n}\n\n/**\n * A per-upload path component that survives a module reload.\n *\n * ⚠️ A module-scoped counter is NOT enough here, even though one is fine for\n * `FileRef.localId`. `localId` is process-local and never persisted; this\n * string becomes the object's PATH IN THE BUCKET. A counter restarts at 0 on\n * every page load and on every serverless cold start, so a user who uploads\n * `photo.png`, reloads, and uploads it again generates the same path twice and\n * gets Supabase's `400 Asset Already Exists` — with `upsert` correctly off,\n * that is a hard failure rather than a silent overwrite.\n */\nfunction pathStamp(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return crypto.randomUUID().slice(0, 8);\n }\n // `padEnd` is not decoration: Math.random() may legally return 0, and\n // (0).toString(36).slice(2, 10) is the empty string — which would put every\n // upload of a given filename back on one colliding path, the exact bug this\n // function exists to prevent.\n return Math.random().toString(36).slice(2, 10).padEnd(8, '0');\n}\n\nexport function supabaseFileStorage(opts: SupabaseFileStorageOpts): FileStorageAdapter {\n const bucket = () => opts.client.storage.from(opts.bucket);\n const ttl = opts.signedTtlSec ?? 3600;\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // A fresh path per upload. Supabase's own guidance is to write to a new\n // path rather than to enable upsert: overwriting races against CDN\n // propagation and the last writer wins silently.\n const stamp = `${pathStamp()}-${file.name}`;\n const path = opts.pathPrefix ? `${opts.pathPrefix}/${stamp}` : stamp;\n\n ctx.onProgress(0, file.size);\n const { data, error } = await bucket().upload(path, file, {\n contentType: file.type || undefined,\n });\n if (error) throw new Error(`Supabase upload failed: ${error.message}`);\n if (!data) throw new Error('Supabase upload returned no path');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: data.path,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, _ctx: ResolveCtx): Promise<ResolvedUrl> {\n const path = ref.handle as string;\n if (opts.access === 'public') {\n // Permanent: a plain string, so it may be cached and server-rendered.\n return bucket().getPublicUrl(path).data.publicUrl;\n }\n const { data, error } = await bucket().createSignedUrl(path, ttl);\n if (error) throw new Error(`Supabase sign failed: ${error.message}`);\n if (!data) throw new Error('Supabase sign returned no url');\n return { url: data.signedUrl, expiresAt: Date.now() + ttl * 1000 };\n },\n\n async remove(ref: FileRef): Promise<void> {\n const { error } = await bucket().remove([ref.handle as string]);\n if (error) throw new Error(`Supabase remove failed: ${error.message}`);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/** What the adopter's signing endpoint hands back. */\nexport interface SignedTarget {\n /**\n * The object key. **The server chooses it, not the client** — letting the\n * browser name the object is how buckets get overwritten by whoever asks.\n */\n key: string;\n url: string;\n method?: 'PUT' | 'POST';\n /** Headers that were part of the signature. Sent verbatim. */\n headers?: Record<string, string>;\n /** POST-policy form fields. Sent before the file part. */\n fields?: Record<string, string>;\n}\n\nexport interface S3FileStorageOpts {\n /** Ask your backend to sign. Authorization happens there, not here. */\n sign: (file: { name: string; type: string; size: number }) => Promise<SignedTarget>;\n /** CDN or bucket origin for permanent read URLs. */\n publicBaseUrl?: string;\n /** Supply for private buckets; return an expiring URL. */\n signView?: (key: string, ctx: ResolveCtx) => Promise<ResolvedUrl>;\n remove?: (key: string) => Promise<void>;\n orphans?: OrphanPolicy;\n /** Injected for tests. Defaults to `new XMLHttpRequest()`. */\n xhrImpl?: () => XMLHttpRequest;\n}\n\n/**\n * Direct-to-bucket upload behind a signed handshake: ask the server to sign,\n * send the bytes straight to storage, keep the key.\n *\n * Uses XHR rather than fetch on purpose — browsers expose no upload progress\n * on fetch, and this is the pipeline whose files are large enough for the\n * absence to be felt.\n */\nexport function s3FileStorage(opts: S3FileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'handshake',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n // Check BEFORE signing, like the sibling adapters do. Signing is a round\n // trip to the adopter's own backend; an upload the user already\n // cancelled has no business making it.\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const target = await opts.sign({ name: file.name, type: file.type, size: file.size });\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n\n const xhr = (opts.xhrImpl ?? (() => new XMLHttpRequest()))();\n const method = target.method ?? 'PUT';\n\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => xhr.abort();\n ctx.signal.addEventListener('abort', onAbort, { once: true });\n\n xhr.upload.addEventListener('progress', (e) => {\n const ev = e as ProgressEvent;\n ctx.onProgress(ev.loaded, ev.total || file.size);\n });\n xhr.addEventListener('load', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n if (xhr.status >= 200 && xhr.status < 300) {\n ctx.onProgress(file.size, file.size);\n resolve();\n } else reject(new Error(`S3 upload failed: ${xhr.status}`));\n });\n xhr.addEventListener('error', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n reject(new Error('S3 upload failed: network error'));\n });\n xhr.addEventListener('abort', () => {\n reject(new DOMException('Aborted', 'AbortError'));\n });\n\n xhr.open(method, target.url);\n for (const [k, v] of Object.entries(target.headers ?? {})) {\n xhr.setRequestHeader(k, v);\n }\n\n if (method === 'POST') {\n const fd = new FormData();\n for (const [k, v] of Object.entries(target.fields ?? {})) fd.append(k, v);\n // ⚠️ The file part goes LAST: S3 ignores every field that follows it.\n fd.append('file', file);\n xhr.send(fd);\n } else {\n xhr.send(file);\n }\n });\n\n return newRef({\n handle: target.key,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx): ResolvedUrl | Promise<ResolvedUrl> {\n const key = ref.handle as string;\n if (opts.signView) return opts.signView(key, ctx);\n if (opts.publicBaseUrl) return `${opts.publicBaseUrl.replace(/\\/+$/, '')}/${key}`;\n throw new Error(\n 's3FileStorage: pass publicBaseUrl for a public bucket or signView for a private one',\n );\n },\n\n ...(opts.remove\n ? {\n async remove(ref: FileRef): Promise<void> {\n await opts.remove?.(ref.handle as string);\n },\n }\n : {}),\n };\n}\n","/**\n * A mapper's answer when the change cannot be expressed as \"the new value of\n * this column\".\n *\n * PocketBase updates a multi-value file field through SIBLING keys — `docs+`\n * to append, `docs-` to remove named files — so there is no single value to\n * return. Rather than special-casing one backend inside the write path, a\n * mapper may return this marker and `withFileStorage` splices its keys into\n * the payload instead of assigning to the field's own key.\n */\nexport const COLUMN_PATCH = Symbol.for('buro.fileStorage.columnPatch');\n\nexport interface ColumnPatch {\n [COLUMN_PATCH]: true;\n keys: Record<string, unknown>;\n}\n\nexport function columnPatch(keys: Record<string, unknown>): ColumnPatch {\n return { [COLUMN_PATCH]: true, keys };\n}\n\nexport function isColumnPatch(value: unknown): value is ColumnPatch {\n return typeof value === 'object' && value !== null && COLUMN_PATCH in value;\n}\n","import { columnPatch } from '../mappers/column-patch.js';\nimport { newRef } from '../mappers/ref.js';\nimport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n MapperCtx,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * PocketBase stores a filename per file and updates multi-value fields by\n * DIFF: `field+` appends, `field-` removes by name. Single-value fields are\n * replaced outright, and cleared with an empty string.\n */\nexport function pocketbaseDiff(): FileMapper<string | string[] | null | undefined, unknown> {\n return {\n carriesPending: true,\n\n read(column): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((n) => typeof n === 'string' && n !== '')\n .map((name) => newRef({ handle: name, raw: name, name }));\n },\n\n write(next: FileRef[], prev: FileRef[], ctx: MapperCtx): unknown {\n const added = next.filter((r) => r.pending).map((r) => r.pending as File);\n\n if (!ctx.multiple) {\n // No diff protocol for single-value fields — assign or clear.\n if (added.length > 0) return columnPatch({ [ctx.field]: added[0] as File });\n const kept = next.find((r) => r.handle);\n if (kept) return columnPatch({});\n return prev.length > 0 ? columnPatch({ [ctx.field]: '' }) : columnPatch({});\n }\n\n const keptHandles = new Set(next.filter((r) => r.handle).map((r) => r.handle as string));\n const removed = prev\n .map((r) => r.handle)\n .filter((h): h is string => h != null && !keptHandles.has(h));\n\n const keys: Record<string, unknown> = {};\n if (added.length > 0) keys[`${ctx.field}+`] = added;\n if (removed.length > 0) keys[`${ctx.field}-`] = removed;\n return columnPatch(keys);\n },\n };\n}\n\nexport interface PocketbaseFileStorageOpts {\n /** PocketBase origin, e.g. `http://127.0.0.1:8090`. */\n baseUrl: string;\n /** Set when the file field is marked Protected in the collection schema. */\n protectedFiles?: boolean;\n /** Supplies a short-lived file token — `pb.files.getToken()`. */\n getFileToken?: () => Promise<string>;\n /** Lifetime assumed for a file token, in seconds. Default 120. */\n tokenTtlSec?: number;\n}\n\n/**\n * PocketBase: bytes ride the record mutation as multipart, the column keeps a\n * filename that only means anything alongside its record, images get thumbs\n * from a query parameter, and protected files need a short-lived token.\n *\n * It is the hardest of the four reference pipelines, which is why it is the\n * one the example app runs against.\n */\nexport function pocketbaseFileStorage(opts: PocketbaseFileStorageOpts): FileStorageAdapter {\n const base = opts.baseUrl.replace(/\\/+$/, '');\n const ttl = opts.tokenTtlSec ?? 120;\n\n return {\n pipeline: 'fused',\n encoding: 'multipart',\n defaultMapper: pocketbaseDiff(),\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n // Fused: hold the bytes for the mutation.\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const recordId = (ctx.record as { id?: string } | undefined)?.id;\n if (!ctx.resource || !recordId) {\n throw new Error(\n 'pocketbaseFileStorage: a file URL needs its collection and record id — ' +\n 'the filename alone does not identify a file in PocketBase.',\n );\n }\n const params = new URLSearchParams();\n if (ctx.variant?.width || ctx.variant?.height) {\n const w = ctx.variant.width ?? 0;\n const h = ctx.variant.height ?? 0;\n // `f` fits inside the box instead of cropping to it.\n params.set('thumb', `${w}x${h}f`);\n }\n if (ctx.disposition === 'attachment') params.set('download', '1');\n\n if (opts.protectedFiles) {\n if (!opts.getFileToken) {\n throw new Error(\n 'pocketbaseFileStorage: protectedFiles is set but getFileToken was not supplied',\n );\n }\n const token = await opts.getFileToken();\n params.set('token', token);\n const qs = params.toString();\n return {\n url: `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`,\n expiresAt: Date.now() + ttl * 1000,\n };\n }\n\n const qs = params.toString();\n return `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`;\n },\n };\n}\n","import { isColumnPatch } from '../mappers/column-patch.js';\nimport { type EncodingPlan, encodingFor } from './with-file-storage.js';\n\n/**\n * Splice column patches into the payload; leave everything else alone.\n *\n * Reference identity is preserved ONLY when the payload carries no column\n * patch at all. The moment even an EMPTY patch (`{ keys: {} }` — \"nothing\n * changed for this field\") is present, the field's own key must still be\n * dropped: forwarding the raw patch marker as the field's value would\n * overwrite the column with `{ keys: {} }` instead of leaving it alone. So a\n * fresh object is built whenever any patch is present, empty or not — this\n * function does not claim otherwise.\n */\nexport function splicePatches(variables: Record<string, unknown>): Record<string, unknown> {\n let touched = false;\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (isColumnPatch(value)) {\n touched = true;\n Object.assign(out, value.keys);\n continue;\n }\n out[key] = value;\n }\n return touched ? out : variables;\n}\n\n/**\n * Does this payload actually carry bytes? Everything downstream hinges on it.\n *\n * ⚠️ No depth cap. An earlier version stopped at four levels, which meant a\n * File nested deeper was silently declared absent — the payload then took the\n * untouched fast path and the File reached `JSON.stringify` and became `{}`.\n * Today's mappers never nest a File below the first level, so the cap was\n * unreachable; a future rich-content mapper would have made it a silent\n * data-loss bug. Cycles are handled by tracking visited objects rather than by\n * limiting depth.\n */\nexport function containsFile(value: unknown, seen = new WeakSet<object>()): boolean {\n if (typeof File !== 'undefined' && value instanceof File) return true;\n if (!value || typeof value !== 'object') return false;\n if (seen.has(value)) return false;\n seen.add(value);\n if (Array.isArray(value)) return value.some((v) => containsFile(v, seen));\n return Object.values(value as Record<string, unknown>).some((v) => containsFile(v, seen));\n}\n\n/** Getter-safe recursive check used to reject unsupported bulk column patches. */\nexport function containsColumnPatch(value: unknown, seen = new WeakSet<object>()): boolean {\n if (!value || typeof value !== 'object') return false;\n try {\n if (isColumnPatch(value)) return true;\n if (seen.has(value)) return false;\n seen.add(value);\n const descriptors = Object.getOwnPropertyDescriptors(value);\n return Object.entries(descriptors).some(\n ([key, descriptor]) =>\n key !== 'length' && 'value' in descriptor && containsColumnPatch(descriptor.value, seen),\n );\n } catch {\n return false;\n }\n}\n\nasync function toDataUrl(file: File): Promise<string> {\n const buf = await file.arrayBuffer();\n const bytes = new Uint8Array(buf);\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n const b64 = typeof btoa === 'function' ? btoa(binary) : Buffer.from(bytes).toString('base64');\n return `data:${file.type || 'application/octet-stream'};base64,${b64}`;\n}\n\n/**\n * Turn a payload carrying Files into something the provider can send.\n *\n * `base64` is a VALUE transform, so the payload stays a plain object and\n * composes with anything. `multipart` and `graphql-upload` produce a FormData\n * — the provider must send it as the body verbatim and must NOT set a\n * content-type, because the browser writes the boundary.\n */\nexport async function encodePayload(\n variables: Record<string, unknown>,\n plan: EncodingPlan,\n): Promise<Record<string, unknown> | FormData> {\n // base64 first: it is a VALUE transform, so those Files stop being Files\n // before anything decides what the request body looks like.\n const staged: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (encodingFor(key, plan) !== 'base64') {\n staged[key] = value;\n continue;\n }\n if (value instanceof File) staged[key] = await toDataUrl(value);\n else if (Array.isArray(value)) {\n staged[key] = await Promise.all(\n value.map((v) => (v instanceof File ? toDataUrl(v) : Promise.resolve(v))),\n );\n } else staged[key] = value;\n }\n\n // Nothing left to carry in the body — keep it a plain JSON payload.\n if (!containsFile(staged)) return staged;\n\n const encoding = plan.bodyEncoding;\n const fd = new FormData();\n\n // ⚠️ Both loops below read `staged`, not `variables`. A base64-designated\n // key already holds its data-URL STRING in `staged` at this point — reading\n // `variables` here would re-discover the original File and append it to the\n // FormData as raw binary, silently undoing the base64 conversion the moment\n // any OTHER field on the same call needs a body encoding. That is the exact\n // failure this plan revision exists to close; it must not reopen here.\n if (encoding === 'graphql-upload') {\n // multipart request spec: `operations` + `map` first, then the file parts.\n const files: File[] = [];\n const map: Record<string, string[]> = {};\n const scrubbed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) {\n map[String(files.length)] = [`variables.${key}`];\n files.push(value);\n scrubbed[key] = null;\n } else scrubbed[key] = value;\n }\n fd.append('operations', JSON.stringify({ variables: scrubbed }));\n fd.append('map', JSON.stringify(map));\n files.forEach((f, i) => {\n fd.append(String(i), f);\n });\n return fd;\n }\n\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) fd.append(key, value);\n else if (Array.isArray(value)) {\n for (const item of value) {\n fd.append(key, item instanceof File ? item : String(item));\n }\n } else if (value == null) fd.append(key, '');\n else if (typeof value === 'object') fd.append(key, JSON.stringify(value));\n else fd.append(key, String(value));\n }\n return fd;\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { containsColumnPatch, containsFile, encodePayload, splicePatches } from './encode.js';\n\n/** Stamped on a wrapped provider so the wiring assertion can see it. */\nexport const FILE_STORAGE_WRAPPED = Symbol.for('buro.fileStorage.wrapped');\n\nconst MUTATING = new Set(['create', 'update', 'createMany', 'updateMany']);\n\nfunction isProviderMap(value: unknown): value is Record<string, Record<string, unknown>> {\n if (!value || typeof value !== 'object') return false;\n const entries = Object.values(value as Record<string, unknown>);\n return entries.length > 0 && entries.every((v) => v && typeof v === 'object' && !('then' in v));\n}\n\n/**\n * Rewrite outgoing mutations that actually carry bytes.\n *\n * The decision is PER REQUEST, from the payload — never global. Mixing\n * pipelines is the ordinary case (a storage service for most fields, two\n * places that take the file inline), so a payload with no File is handed to\n * the underlying provider untouched, by reference: no FormData, no\n * re-encoding, no surprise.\n */\nexport function withFileStorage<T>(provider: T, app: AppRuntime): T {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) {\n throw new Error(\n 'withFileStorage: no file storage registered — add fileStoragePlugin({...}) to defineApp',\n );\n }\n\n // Wrap a map of providers entry by entry, so `meta.dataProviderName` keeps\n // composing with file fields.\n if (isProviderMap(provider) && !(provider as Record<string, unknown>).getList) {\n const out: Record<string, unknown> = {};\n for (const [name, entry] of Object.entries(provider as Record<string, unknown>)) {\n out[name] = withFileStorage(entry, app);\n }\n Object.defineProperty(out, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return out as T;\n }\n\n const source = provider as Record<string, unknown>;\n const wrapped: Record<string, unknown> = Object.create(\n Object.getPrototypeOf(source) as object | null,\n );\n\n for (const key of allKeys(source)) {\n const value = source[key];\n if (typeof value !== 'function' || !MUTATING.has(key)) {\n wrapped[key] = value;\n continue;\n }\n wrapped[key] = async (args: Record<string, unknown>) => {\n const variables = args?.variables;\n if (!variables || typeof variables !== 'object' || variables instanceof FormData) {\n return (value as (a: unknown) => unknown)(args);\n }\n if (\n (key === 'createMany' || key === 'updateMany') &&\n (containsFile(variables) || containsColumnPatch(variables))\n ) {\n throw new Error(\n `withFileStorage: bulk file mutations are not supported for ${key}; submit one record at a time so FileMapper patches and request-body encoding stay unambiguous.`,\n );\n }\n // `createMany` carries an array of complete records. With no File\n // or ColumnPatch there is nothing for this wrapper to do, and treating\n // the array as a record would silently turn it into an object keyed by\n // \"0\", \"1\", ... . Preserve the provider-compatible array verbatim.\n if (Array.isArray(variables)) return (value as (a: unknown) => unknown)(args);\n const spliced = splicePatches(variables as Record<string, unknown>);\n if (!containsFile(spliced)) {\n // Untouched — pass the ORIGINAL object so identity is preserved.\n const passthrough = spliced === variables ? args : { ...args, variables: spliced };\n return (value as (a: unknown) => unknown)(passthrough);\n }\n // Per RESOURCE, not per provider — see planFor.\n const encoded = await encodePayload(spliced, planFor(app, registry, String(args.resource)));\n return (value as (a: unknown) => unknown)({ ...args, variables: encoded });\n };\n }\n\n Object.defineProperty(wrapped, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return wrapped as T;\n}\n\nfunction allKeys(source: Record<string, unknown>): string[] {\n const keys = new Set(Object.keys(source));\n const proto = Object.getPrototypeOf(source) as object | null;\n if (proto && proto !== Object.prototype) {\n for (const k of Object.getOwnPropertyNames(proto)) if (k !== 'constructor') keys.add(k);\n }\n return [...keys];\n}\n\n/**\n * Which encoding each payload key needs, and which body encoding the rest\n * fall back to — for ONE resource.\n *\n * ⚠️ This must be resolved per resource, not once per provider. An earlier\n * version picked a single encoding from the whole registry, so an app with a\n * multipart storage anywhere and a base64 storage anywhere else sent the\n * base64 field's File into FormData as raw binary — never encoded, no boot\n * error, no runtime error, silently wrong data. It also contradicted the\n * type's own promise that base64 \"composes with anything\", which is exactly\n * why `assertFileStorageWiring` permits that combination.\n *\n * `base64` is a VALUE transform: those fields are converted first and stop\n * being Files. Only what remains decides the request body.\n */\nexport interface EncodingPlan {\n /** Encoding EXPLICITLY declared for a field, via `f.file({ fileStorage })`. Authoritative. */\n knownEncodings: Map<string, FusedEncoding>;\n /**\n * Assumed for a payload key with NO declared field to consult — the\n * resource isn't registered, or has no `f.file()` for this key. Mirrors\n * `ui.fileStorage ?? 'default'`, the exact resolution a KNOWN field with no\n * explicit storage name already gets, extended to \"no field info at all\".\n * This is what keeps a single-storage app working without ever having to\n * declare its fields through `@burojs/field-types`.\n */\n defaultEncoding: FusedEncoding;\n /** Body encoding for whatever Files remain once base64 keys are converted. */\n bodyEncoding: Exclude<FusedEncoding, 'base64'>;\n}\n\n/** `docs+` / `docs-` are patch keys for the field `docs`. */\nexport function fieldOfKey(key: string): string {\n return key.endsWith('+') || key.endsWith('-') ? key.slice(0, -1) : key;\n}\n\n/** The encoding one payload key resolves to: its field's, or the plan's default. */\nexport function encodingFor(key: string, plan: EncodingPlan): FusedEncoding {\n return plan.knownEncodings.get(fieldOfKey(key)) ?? plan.defaultEncoding;\n}\n\nfunction planFor(app: AppRuntime, registry: FileStorageRegistry, resource: string): EncodingPlan {\n const knownEncodings = new Map<string, FusedEncoding>();\n\n const defaultAdapter = registry.has('default') ? registry.get('default') : undefined;\n const defaultEncoding: FusedEncoding =\n defaultAdapter?.pipeline === 'fused' && defaultAdapter.encoding\n ? defaultAdapter.encoding\n : 'multipart';\n let bodyEncoding: Exclude<FusedEncoding, 'base64'> =\n defaultEncoding === 'base64' ? 'multipart' : defaultEncoding;\n\n const def = app.registry.get(resource);\n for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {\n const ui = (field as { ui?: { component?: string; fileStorage?: string } }).ui;\n if (ui?.component !== 'file') continue;\n if (!registry.has(ui.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui.fileStorage);\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n knownEncodings.set(fieldName, adapter.encoding);\n if (adapter.encoding !== 'base64') bodyEncoding = adapter.encoding;\n }\n return { knownEncodings, defaultEncoding, bodyEncoding };\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileMapper, FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { FILE_STORAGE_WRAPPED } from './with-file-storage.js';\n\n/** Encodings that claim the request body, and so cannot share one request. */\nconst BODY_ENCODINGS: FusedEncoding[] = ['multipart', 'graphql-upload'];\n\n/**\n * Three checks that must fail at boot rather than at runtime.\n *\n * 1. A fused storage with an unwrapped provider. Files in `variables` reach\n * `JSON.stringify` and become `{}` — the upload silently does nothing, the\n * request succeeds, and nobody finds out until someone opens the record.\n * 2. A fused storage paired with a mapper that cannot carry a `pending` file\n * forward (`mapper.carriesPending` is not set). A ref on a fused pipeline\n * never has a `handle` before the mutation runs — only `pending` — so a\n * mapper that writes nothing for a handle-less ref (the ordinary case:\n * `richImage`, `junction`) silently drops every file added through that\n * field. This is the same class of failure as #1 — a fused storage whose\n * mapper cannot carry the file forward is exactly as broken as one with no\n * wrapper — so it gets the same treatment: fail loudly before any user\n * touches the form, not discover it when a saved record turns out empty.\n * 3. Two incompatible body encodings on one resource. One request has one\n * body; discovering that during a save is discovering it too late.\n *\n * Called by the host composition layer that owns both the app and its data\n * provider.\n */\nexport function assertFileStorageWiring(app: AppRuntime, provider: unknown): void {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) return;\n\n if (registry.hasFused()) {\n const stamped =\n provider != null &&\n (typeof provider === 'object' || typeof provider === 'function') &&\n FILE_STORAGE_WRAPPED in (provider as object);\n if (!stamped) {\n throw new Error(\n 'A fused file storage is registered, but the data provider is not wrapped. ' +\n 'Files ride inside the record mutation for this pipeline, so an unwrapped ' +\n 'provider will JSON.stringify them into `{}` and lose them silently. ' +\n 'Wrap it once: dataProvider: withFileStorage(myDataProvider, app)',\n );\n }\n }\n\n for (const def of app.registry.list()) {\n const seen = new Map<FusedEncoding, string>();\n for (const [fieldName, field] of Object.entries(def.fields ?? {})) {\n const ui = (\n field as {\n ui?: { component?: string; fileStorage?: string; mapper?: unknown };\n }\n ).ui;\n if (ui?.component !== 'file') continue;\n if (!registry.has(ui.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui.fileStorage);\n\n if (adapter.pipeline === 'fused') {\n const mapper = (ui.mapper as FileMapper | undefined) ?? adapter.defaultMapper;\n if (!mapper.carriesPending) {\n throw new Error(\n `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 })\\`.`,\n );\n }\n }\n\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;\n const previous = seen.get(adapter.encoding);\n if (previous == null) seen.set(adapter.encoding, fieldName);\n if (seen.size > 1) {\n const entries = [...seen.entries()];\n const [encA, fieldA] = entries[0] as [FusedEncoding, string];\n const [encB, fieldB] = entries[1] as [FusedEncoding, string];\n throw new Error(\n `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.`,\n );\n }\n }\n }\n}\n","const FILE_MAPPER_BASELINE_CARRIER = Symbol.for('buro.fileMapper.baselineCarrier');\n\n/**\n * Copy the opaque mapped-file baseline capability from a decoded record to a\n * fresh edit payload. The physical baseline itself remains private to the\n * provider wrapper; this helper can neither inspect nor serialize it.\n */\nexport function inheritFileMapperBaseline<T extends object>(source: object, target: T): T {\n let descriptor: PropertyDescriptor | undefined;\n try {\n descriptor = Object.getOwnPropertyDescriptor(source, FILE_MAPPER_BASELINE_CARRIER);\n } catch {\n return target;\n }\n if (\n descriptor === undefined ||\n !('value' in descriptor) ||\n descriptor.value === null ||\n typeof descriptor.value !== 'object'\n ) {\n return target;\n }\n Object.defineProperty(target, FILE_MAPPER_BASELINE_CARRIER, {\n value: descriptor.value,\n enumerable: false,\n configurable: true,\n writable: false,\n });\n return target;\n}\n","export const PACKAGE_NAME = '@burojs/file-storage' as const;\n\nexport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n FileStorageRegistry,\n FusedEncoding,\n MapperCtx,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n UploadPipeline,\n} from './types.js';\nexport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nexport { fileStoragePlugin } from './plugin.js';\nexport { newRef } from './mappers/ref.js';\nexport { isImage } from './kind.js';\nexport { scalarHandle } from './mappers/scalar-handle.js';\nexport { richImage, type RichImageOpts } from './mappers/rich-image.js';\nexport { junction, type JunctionOpts } from './mappers/junction.js';\nexport {\n runFileStorageContract,\n type ContractOpts,\n type ContractRunner,\n} from './contract/run-contract.js';\nexport { createFakeAdapter, type FakeAdapterOpts } from './contract/fake-adapter.js';\nexport { inlineFileStorage, type InlineFileStorageOpts } from './adapters/inline.js';\nexport { nhostFileStorage, type NhostFileStorageOpts } from './adapters/nhost.js';\nexport {\n supabaseFileStorage,\n type SupabaseFileStorageOpts,\n type SupabaseStorageLike,\n} from './adapters/supabase.js';\nexport { s3FileStorage, type S3FileStorageOpts, type SignedTarget } from './adapters/s3.js';\nexport {\n pocketbaseDiff,\n pocketbaseFileStorage,\n type PocketbaseFileStorageOpts,\n} from './adapters/pocketbase.js';\nexport {\n COLUMN_PATCH,\n columnPatch,\n isColumnPatch,\n type ColumnPatch,\n} from './mappers/column-patch.js';\nexport { FILE_STORAGE_WRAPPED, withFileStorage } from './wrapper/with-file-storage.js';\nexport { assertFileStorageWiring } from './wrapper/assert-wiring.js';\nexport {\n containsColumnPatch,\n containsFile,\n encodePayload,\n splicePatches,\n} from './wrapper/encode.js';\nexport { inheritFileMapperBaseline } from './baseline-carrier.js';\n"],"mappings":";;AAGA,MAAa,uBAAuB;AAEpC,SAAgB,0BACd,KACqB;CACrB,IAAI,CAAC,IAAI,SACP,MAAM,IAAI,MACR,8GAEF;CAEF,MAAM,UAAU,IAAI,IAAI,OAAO,QAAQ,GAAG,CAAC;CAC3C,OAAO;EACL,IAAI,OAAO,WAAW;GACpB,MAAM,QAAQ,QAAQ,IAAI,IAAI;GAC9B,IAAI,CAAC,OACH,MAAM,IAAI,MACR,yBAAyB,KAAK,iBAAiB,CAAC,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,EAChF;GAEF,OAAO;EACT;EACA,MAAM,SAAS,QAAQ,IAAI,IAAI;EAC/B,aAAa,CAAC,GAAG,QAAQ,KAAK,CAAC;EAC/B,gBAAgB,CAAC,GAAG,QAAQ,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,aAAa,OAAO;CAC1E;AACF;;;ACpBA,SAAgB,kBAAkB,KAAiD;CACjF,MAAM,WAAW,OAAO,OAAO,EAAE,GAAG,IAAI,CAAC;CACzC,OAAO;EACL,MAAM;EACN,SAAS;EACT,QAAQ,QAAQ;GACd,IAAI,QAAQ,gBAAgB,4BAA4B,0BAA0B,QAAQ,CAAC;EAC7F;CACF;AACF;;;AChBA,IAAI,MAAM;AASV,SAAgB,OAAO,OAAyB,CAAC,GAAY;CAC3D,OAAO;CACP,OAAO;EAAE,SAAS,OAAO;EAAO,GAAG;CAAK;AAC1C;;;ACDA,SAAgB,QAAQ,KAAuB;CAC7C,IAAI,IAAI,MAAM,OAAO,IAAI,KAAK,WAAW,QAAQ;CACjD,MAAM,OAAO,IAAI,QAAQ;CACzB,OAAO,oCAAoC,KAAK,IAAI;AACtD;;;ACLA,SAAgB,eAA2D;CACzE,OAAO;EACL,gBAAgB;EAEhB,KAAK,QAAiC;GACpC,IAAI,UAAU,MAAM,OAAO,CAAC;GAE5B,QADa,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM,EAAA,CAElD,QAAQ,UAAU,UAAU,MAAM,SAAS,IAAI,CAAC,CAChD,KAAK,UAAU,OAAO;IAAE,QAAQ;IAAO,KAAK;IAAO,MAAM,KAAK,KAAK;GAAE,CAAC,CAAC;EAC5E;EAEA,MAAM,MAAiB,OAAkB,KAAkC;GAWzE,MAAM,SAAS,KACZ,KAAK,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CACjC,QAAQ,MAA0B,KAAK,IAAI;GAC9C,IAAI,IAAI,UAAU,OAAO;GACzB,OAAO,OAAO,MAAM;EACtB;CACF;AACF;AAGA,SAAS,KAAK,OAAmC;CAC/C,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI;CACjC,OAAO,OAAO,QAAQ,QAAQ,MAAM,SAAS,KAAA;AAC/C;;;ACzBA,MAAM,qBAAqB,CAAC,WAAW,MAAM;AAI7C,SAAgB,UAAU,MAAmD;CAC3E,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,YAAY,KAAK,aAAa;CAEpC,SAAS,QAAQ,OAAqB;EACpC,MAAM,QACJ,KAAK,UAAU,UACX,KACE,OACA,UAAU,QAAQ,MAAM,MAAM,OAAO,KAAA,CAAS,CAChD,IACA,KAAA;EACN,OAAO,OAAO;GACZ,QAAQA,WAAS,QAAQ,OAAO,SAAS,CAAC;GAC1C,KAAK;GACL,MAAMA,WAAS,MAAM,QAAQ,MAAM,iBAAiB;GACpD,MAAMA,WAAS,MAAM,QAAQ,MAAM,IAAI;GACvC,MAAM,SAAS,MAAM,QAAQ,MAAM,QAAQ;GAC3C,OAAO,SAAS,MAAM,KAAK;GAC3B,QAAQ,SAAS,MAAM,MAAM;GAC7B,GAAI,SAAS,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,EAAE,MAAM,IAAI,CAAC;EAC5D,CAAC;CACH;CAEA,OAAO;EACL,KAAK,QAAQ,KAA2B;GACtC,IAAI,UAAU,MAAM,OAAO,CAAC;GAE5B,QADc,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM,EAAA,CAC1C,QAAQ,MAAM,KAAK,OAAO,MAAM,QAAQ,CAAC,CAAC,IAAI,OAAO;EACnE;EAEA,MAAM,MAAiB,OAAkB,KAAyB;GAChE,MAAM,MAAM,KACT,QAAQ,MAAM,EAAE,UAAU,IAAI,CAAC,CAC/B,KAAK,MAAM;IAGV,MAAM,OAAY,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC;IACjD,QAAQ,MAAM,WAAW,EAAE,MAAgB;IAC3C,IAAI,KAAK,UAAU,WAAW,EAAE,OAAO,OAAO,OAAO,MAAM,EAAE,KAAK;IAClE,OAAO;GACT,CAAC;GACH,IAAI,IAAI,UAAU,OAAO;GACzB,OAAO,IAAI,MAAM;EACnB;CACF;AACF;AAEA,SAAS,MAAM,GAAsB;CACnC,OAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AACA,SAASA,WAAS,GAAgC;CAChD,OAAO,OAAO,MAAM,WAAW,IAAI,KAAA;AACrC;AACA,SAAS,SAAS,GAAgC;CAChD,OAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IAAI,IAAI,KAAA;AAC3D;AACA,SAAS,KAAK,KAAU,MAAqB;CAC3C,MAAM,MAAW,CAAC;CAClB,KAAK,MAAM,KAAK,MAAM,IAAI,KAAK,IAAI;CACnC,OAAO;AACT;AACA,SAAS,QAAQ,KAAU,MAAuB;CAChD,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,QAAiB,KAAK,MAAO,MAAM,GAAG,IAAI,IAAI,KAAK,KAAA,GAAY,GAAG;AAC3F;AACA,SAAS,QAAQ,QAAa,MAAc,OAAqB;CAC/D,MAAM,OAAO,KAAK,MAAM,GAAG;CAC3B,MAAM,OAAO,KAAK,IAAI;CACtB,IAAI,SAAc;CAClB,KAAK,MAAM,KAAK,MAAM;EACpB,MAAM,YAAY,OAAO;EAEzB,OAAO,KAAK,MAAM,SAAS,IAAI,EAAE,GAAG,UAAU,IAAI,CAAC;EACnD,SAAS,OAAO;CAClB;CACA,OAAO,QAAQ;AACjB;;;ACtFA,SAAgB,SAAS,MAAiE;CACxF,OAAO;EACL,KAAK,QAAQ,MAA4B;GACvC,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;GACpC,OAAO,OACJ,QAAQ,QAAQ,OAAO,OAAO,QAAQ,QAAQ,CAAC,CAC/C,KAAK,QAAQ,OAAO;IAAE,QAAQ,SAAS,IAAI,KAAK,QAAQ;IAAG,KAAK;GAAI,CAAC,CAAC;EAC3E;EAEA,MAAM,MAAiB,OAAkB,MAAwB;GAC/D,OAAO,KACJ,QAAQ,MAAM,EAAE,UAAU,IAAI,CAAC,CAC/B,KAAK,GAAG,MAAM;IACb,MAAM,OAAY,EAAE,OAAO,OAAO,EAAE,QAAQ,WAAW,EAAE,GAAI,EAAE,IAAY,IAAI,CAAC;IAChF,KAAK,KAAK,WAAW,EAAE;IACvB,IAAI,KAAK,SAAS,KAAK,KAAK,WAAW,IAAI;IAC3C,OAAO;GACT,CAAC;EACL;CACF;AACF;AAEA,SAAS,SAAS,GAAgC;CAChD,OAAO,OAAO,MAAM,WAAW,IAAI,KAAA;AACrC;;;ACWA,MAAM,SAAoB;CAAE,UAAU;CAAQ,OAAO;CAAQ,UAAU;AAAM;AAC7E,MAAM,OAAkB;CAAE,UAAU;CAAQ,OAAO;CAAS,UAAU;AAAK;AAS3E,SAAgB,uBAAuB,MAAoB,QAA8B;CACvF,MAAM,EAAE,WAAW;CACnB,MAAM,KAAK,OAAO;CAElB,MAAM,SAAS,OAAO;CAEtB,GAAG,6BAA6B;EAC9B,OAAO;GAAC;GAAa;GAAS;GAAa;EAAW,CAAC,CAAC,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,QAAQ;CAC5F,CAAC;CAED,GAAG,gCAAgC;EACjC,MAAM,IAAI,KAAK,QAAQ,CAAC,CAAC;EACzB,OAAO,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,UAAU;EACrC,OAAO,OAAO,EAAE,KAAK,CAAC,CAAC,KAAK,UAAU;CACxC,CAAC;CAED,IAAI,KAAK,mBAAmB;EAC1B,GAAG,mDAAmD;GACpD,MAAM,OAAO,OAAO,KAAK,KAAK,aAAa,UAAU,IAAI;GACzD,MAAM,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI;GACzC,OAAO,OAAO,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC;EACnD,CAAC;EAED,GAAG,2DAA2D;GAC5D,MAAM,OAAO,OAAO,KAAK,KAAK,aAAa,UAAU,IAAI;GACzD,MAAM,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI;GACzC,OAAO,OAAO,IAAI,IAAI,CAAC,CAAC,KAAK,QAAQ;EACvC,CAAC;CACH,OAAO;EACL,GAAG,yDAAyD;GAC1D,MAAM,OAAO,OAAO,KAAK,KAAK,aAAa,QAAQ,MAAM;GACzD,OAAO,OAAO,MAAM,MAAM,MAAM,MAAM,CAAC,CAAC,CAAC,QAAQ,KAAK,aAAa,MAAM;EAC3E,CAAC;EAED,GAAG,2DAA2D;GAC5D,MAAM,OAAO,OAAO,KAAK,KAAK,aAAa,UAAU,IAAI;GACzD,OAAO,OAAO,MAAM,MAAM,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,aAAa,QAAQ;EAC3E,CAAC;CACH;CAEA,GAAG,gCAAgC;EACjC,OAAO,OAAO,KAAK,MAAM,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC9C,CAAC;CAED,GAAG,4CAA4C;EAC7C,MAAM,OAAO,OAAO,KAAK,KAAK,aAAa,UAAU,IAAI;EACzD,OAAO,IAAI,IAAI,KAAK,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;CACnE,CAAC;CAED,IAAI,CAAC,KAAK,YAAY;EACpB,GAAG,4DAA4D,YAAY;GACzE,MAAM,UAAU,KAAK,QAAQ;GAC7B,MAAM,OAAiB,CAAC;GACxB,MAAM,MAAM,MAAM,QAAQ,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,SAAS,EAAE,MAAM,aAAa,CAAC,GAAG;IACrF,QAAQ,IAAI,gBAAgB,CAAC,CAAC;IAC9B,aAAa,WAAW,KAAK,KAAK,MAAM;GAC1C,CAAC;GACD,OAAO,IAAI,MAAM,CAAC,CAAC,WAAW;GAC9B,OAAO,KAAK,MAAM,CAAC,CAAC,gBAAgB,CAAC;GACrC,OAAO,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;EAC5B,CAAC;EAED,GAAG,iEAAiE,YAAY;GAC9E,MAAM,UAAU,KAAK,QAAQ;GAC7B,MAAM,KAAK,IAAI,gBAAgB;GAC/B,GAAG,MAAM;GACT,MAAM,OACJ,QAAQ,OAAO,IAAI,KAAK,CAAC,GAAG,GAAG,OAAO,GAAG;IACvC,QAAQ,GAAG;IACX,kBAAkB,CAAC;GACrB,CAAC,CACH,CAAC,CAAC,QAAQ,QAAQ,QAAQ;EAC5B,CAAC;EAED,GAAG,mCAAmC,YAAY;GAYhD,MAAM,UAAU,KAAK,QAAQ;GAC7B,MAAM,OAAO,IAAI,KAAK,CAAC,GAAG,GAAG,OAAO;GACpC,MAAM,UAAU,IAAI,gBAAgB;GACpC,QAAQ,MAAM;GACd,MAAM,OACJ,QAAQ,OAAO,MAAM;IAAE,QAAQ,QAAQ;IAAQ,kBAAkB,CAAC;GAAE,CAAC,CACvE,CAAC,CAAC,QAAQ,QAAQ;GAClB,MAAM,SAAS,MAAM,QAAQ,OAAO,MAAM;IACxC,QAAQ,IAAI,gBAAgB,CAAC,CAAC;IAC9B,kBAAkB,CAAC;GACrB,CAAC;GACD,OAAO,OAAO,MAAM,CAAC,CAAC,WAAW;EACnC,CAAC;CACH;CAEA,GAAG,4BAA4B,YAAY;EAEzC,MAAM,WAAW,MADD,KAAK,QACQ,CAAC,CAAC,WAAW;GAAE,SAAS;GAAK,QAAQ;EAAI,GAAG,KAAK,cAAc,CAAC,CAAC;EAC9F,MAAM,MAAM,OAAO,aAAa,WAAW,WAAW,SAAS;EAC/D,OAAO,GAAG,CAAC,CAAC,QAAQ,cAAc;CACpC,CAAC;CAED,GAAG,+EAA+E,YAAY;EAE5F,MAAM,WAAW,MADD,KAAK,QACQ,CAAC,CAAC,WAAW;GAAE,SAAS;GAAK,QAAQ;EAAI,GAAG,KAAK,cAAc,CAAC,CAAC;EAC9F,IAAI,OAAO,aAAa,UAAU;EAIlC,OAAO,OAAO,SAAS,SAAS,CAAC,CAAC,KAAK,QAAQ;EAC/C,OAAO,SAAS,SAAS,CAAC,CAAC,gBAAgB,KAAK,IAAI,CAAC;CACvD,CAAC;CAED,GAAG,4DAA4D,YAAY;EACzE,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,CAAC,QAAQ,MAAM;EACnB,MAAM,CAAC,OAAO,MAAM,QAAQ,KAAK,CAAC;GAAE,SAAS;GAAK,QAAQ;EAAwB,CAAC,CAAC;EACpF,OAAO,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;CAC9B,CAAC;AACH;;;ACvKA,SAAgB,kBAAkB,OAAwB,CAAC,GAGzD;CACA,MAAM,SAAS,IAAI,IAAkB;CACrC,MAAM,UAAoB,CAAC;CAC3B,IAAI,MAAM;CAEV,OAAO;EACL;EACA;EACA,UAAU,KAAK,YAAY;EAC3B,eAAe,aAAa;EAE5B,MAAM,OAAO,MAAY,KAAkC;GACzD,MAAM,QAAQ,KAAK,iBAAiB;GACpC,KAAK,IAAI,IAAI,GAAG,KAAK,OAAO,KAAK,GAAG;IAClC,IAAI,IAAI,OAAO,SAAS,MAAM,IAAI,aAAa,WAAW,YAAY;IAEtE,MAAM,QAAQ,QAAQ;IACtB,IAAI,WAAW,KAAK,MAAO,KAAK,OAAO,IAAK,KAAK,GAAG,KAAK,IAAI;GAC/D;GACA,IAAI,IAAI,OAAO,SAAS,MAAM,IAAI,aAAa,WAAW,YAAY;GACtE,IAAI,KAAK,UAAU,MAAM,IAAI,MAAM,KAAK,QAAQ;GAChD,OAAO;GACP,MAAM,SAAS,QAAQ,IAAI,GAAG,KAAK;GACnC,OAAO,IAAI,QAAQ,IAAI;GACvB,OAAO,OAAO;IAAE;IAAQ,MAAM,KAAK;IAAM,MAAM,KAAK,QAAQ,KAAA;IAAW,MAAM,KAAK;GAAK,CAAC;EAC1F;EAEA,WAAW,KAAc,KAAiB;GACxC,MAAM,IAAI,IAAI,SAAS,QAAQ,MAAM,IAAI,QAAQ,UAAU;GAC3D,MAAM,MAAM,qBAAqB,IAAI,SAAS;GAC9C,IAAI,KAAK,eAAe,MAAM,OAAO;GACrC,OAAO,QAAQ,QAAQ;IAAE;IAAK,WAAW,KAAK,IAAI,IAAI,KAAK;GAAY,CAAC;EAC1E;EAEA,MAAM,KAAK,MAAqC;GAC9C,OAAO,KAAK,KAAK,MAAM;IACrB,MAAM,IAAI,EAAE,SAAS,OAAO,IAAI,EAAE,MAAM,IAAI,KAAA;IAG5C,OAAO,IAAI;KAAE,GAAG;KAAG,MAAM,EAAE;KAAM,MAAM,EAAE;KAAM,MAAM,EAAE,QAAQ,KAAA;IAAU,IAAI;GAC/E,CAAC;EACH;EAEA,MAAM,OAAO,KAA6B;GACxC,IAAI,IAAI,QAAQ;IACd,OAAO,OAAO,IAAI,MAAM;IACxB,QAAQ,KAAK,IAAI,MAAM;GACzB;EACF;CACF;AACF;;;ACtCA,SAAgB,kBAAkB,MAAiD;CACjF,OAAO;EACL,UAAU;EACV,UAAU,KAAK;EACf,eAAe,aAAa;EAE5B,SAAS;EAET,MAAM,OAAO,MAAY,MAAmC;GAC1D,OAAO,OAAO;IACZ,SAAS;IACT,MAAM,KAAK;IACX,MAAM,KAAK,QAAQ,KAAA;IACnB,MAAM,KAAK;GACb,CAAC;EACH;EAEA,WAAW,KAAK,KAAK;GACnB,OAAO,KAAK,WAAW,KAAK,GAAG;EACjC;CACF;AACF;;;ACjBA,SAAgB,iBAAiB,MAAgD;CAC/E,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;CAE5C,eAAe,cAA+C;EAC5D,MAAM,QAAQ,MAAM,KAAK,SAAS;EAClC,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,+DAA+D;EAC3F,OAAO,EAAE,eAAe,UAAU,QAAQ;CAC5C;CAEA,OAAO;EACL,UAAU;EACV,eAAe,aAAa;EAC5B,SAAS,KAAK,WAAW;EAEzB,MAAM,OAAO,MAAY,KAAkC;GACzD,IAAI,IAAI,OAAO,SAAS,MAAM,IAAI,aAAa,WAAW,YAAY;GACtE,MAAM,UAAU,MAAM,YAAY;GAClC,MAAM,KAAK,IAAI,SAAS;GACxB,GAAG,OAAO,UAAU,IAAI;GACxB,IAAI,KAAK,UAAU,GAAG,OAAO,aAAa,KAAK,QAAQ;GAIvD,IAAI,WAAW,GAAG,KAAK,IAAI;GAC3B,MAAM,MAAM,MAAM,UAAU,GAAG,KAAK,YAAY;IAC9C,QAAQ;IACR;IACA,MAAM;IACN,QAAQ,IAAI;GACd,CAAC;GACD,IAAI,CAAC,IAAI,IAAI;IACX,MAAM,OAAO,MAAM,IAAI,KAAK,CAAC,CAAC,YAAY,EAAE;IAC5C,MAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,OAAO,KAAK,CAAC;GACrE;GACA,MAAM,OAAQ,MAAM,IAAI,KAAK;GAC7B,MAAM,OAAO,oBAAoB,OAAO,KAAK,iBAAiB,KAAM;GACpE,IAAI,CAAC,MAAM,IAAI,MAAM,IAAI,MAAM,kCAAkC;GACjE,IAAI,WAAW,KAAK,MAAM,KAAK,IAAI;GAEnC,OAAO,OAAO;IACZ,QAAQ,KAAK;IACb,MAAM,KAAK,QAAQ,KAAK;IACxB,MAAM,KAAK,YAAY,KAAK,QAAQ,KAAA;IACpC,MAAM,KAAK,QAAQ,KAAK;GAC1B,CAAC;EACH;EAEA,MAAM,WAAW,KAAc,KAAuC;GACpE,MAAM,UAAU,MAAM,YAAY;GAClC,MAAM,SAAS,IAAI,gBAAgB;GACnC,IAAI,IAAI,SAAS,OAAO,OAAO,IAAI,KAAK,OAAO,IAAI,QAAQ,KAAK,CAAC;GACjE,IAAI,IAAI,SAAS,QAAQ,OAAO,IAAI,KAAK,OAAO,IAAI,QAAQ,MAAM,CAAC;GACnE,MAAM,KAAK,OAAO,SAAS;GAC3B,MAAM,MAAM,MAAM,UAChB,GAAG,KAAK,YAAY,IAAI,OAAO,eAAe,KAAK,IAAI,OAAO,MAC9D,EAAE,QAAQ,CACZ;GACA,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,MAAM,yBAAyB,IAAI,QAAQ;GAClE,MAAM,OAAQ,MAAM,IAAI,KAAK;GAC7B,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,MAAM,+BAA+B;GAC9D,OAAO;IACL,KAAK,KAAK;IAEV,WAAW,KAAK,IAAI,KAAK,KAAK,cAAc,QAAQ;GACtD;EACF;EAEA,MAAM,KAAK,MAAqC;GAC9C,MAAM,UAAU,MAAM,YAAY;GAClC,OAAO,QAAQ,IACb,KAAK,IAAI,OAAO,QAAQ;IACtB,MAAM,MAAM,MAAM,UAAU,GAAG,KAAK,YAAY,IAAI,UAAU;KAC5D,QAAQ;KACR;IACF,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO;IAOpB,MAAM,UAAU,IAAI,QAAQ,IAAI,gBAAgB;IAChD,MAAM,OAAO,WAAW,OAAO,MAAa,OAAO,OAAO;IAC1D,MAAM,OAAO,IAAI,QAAQ,IAAI,cAAc,KAAK,KAAA;IAChD,OAAO;KACL,GAAG;KACH,GAAI,OAAO,SAAS,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC;KACxC,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;IACzB;GACF,CAAC,CACH;EACF;EAEA,MAAM,OAAO,KAA6B;GACxC,MAAM,UAAU,MAAM,YAAY;GAClC,MAAM,MAAM,MAAM,UAAU,GAAG,KAAK,YAAY,IAAI,UAAU;IAC5D,QAAQ;IACR;GACF,CAAC;GACD,IAAI,CAAC,IAAI,MAAM,IAAI,WAAW,KAC5B,MAAM,IAAI,MAAM,wBAAwB,IAAI,QAAQ;EAExD;CACF;AACF;;;ACxFA,SAAS,YAAoB;CAC3B,IAAI,OAAO,WAAW,eAAe,gBAAgB,QACnD,OAAO,OAAO,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC;CAMvC,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,OAAO,GAAG,GAAG;AAC9D;AAEA,SAAgB,oBAAoB,MAAmD;CACrF,MAAM,eAAe,KAAK,OAAO,QAAQ,KAAK,KAAK,MAAM;CACzD,MAAM,MAAM,KAAK,gBAAgB;CAEjC,OAAO;EACL,UAAU;EACV,eAAe,aAAa;EAC5B,SAAS,KAAK,WAAW;EAEzB,MAAM,OAAO,MAAY,KAAkC;GACzD,IAAI,IAAI,OAAO,SAAS,MAAM,IAAI,aAAa,WAAW,YAAY;GAItE,MAAM,QAAQ,GAAG,UAAU,EAAE,GAAG,KAAK;GACrC,MAAM,OAAO,KAAK,aAAa,GAAG,KAAK,WAAW,GAAG,UAAU;GAE/D,IAAI,WAAW,GAAG,KAAK,IAAI;GAC3B,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,CAAC,CAAC,OAAO,MAAM,MAAM,EACxD,aAAa,KAAK,QAAQ,KAAA,EAC5B,CAAC;GACD,IAAI,OAAO,MAAM,IAAI,MAAM,2BAA2B,MAAM,SAAS;GACrE,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,kCAAkC;GAC7D,IAAI,WAAW,KAAK,MAAM,KAAK,IAAI;GAEnC,OAAO,OAAO;IACZ,QAAQ,KAAK;IACb,MAAM,KAAK;IACX,MAAM,KAAK,QAAQ,KAAA;IACnB,MAAM,KAAK;GACb,CAAC;EACH;EAEA,MAAM,WAAW,KAAc,MAAwC;GACrE,MAAM,OAAO,IAAI;GACjB,IAAI,KAAK,WAAW,UAElB,OAAO,OAAO,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,KAAK;GAE1C,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,CAAC,CAAC,gBAAgB,MAAM,GAAG;GAChE,IAAI,OAAO,MAAM,IAAI,MAAM,yBAAyB,MAAM,SAAS;GACnE,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,+BAA+B;GAC1D,OAAO;IAAE,KAAK,KAAK;IAAW,WAAW,KAAK,IAAI,IAAI,MAAM;GAAK;EACnE;EAEA,MAAM,OAAO,KAA6B;GACxC,MAAM,EAAE,UAAU,MAAM,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,MAAgB,CAAC;GAC9D,IAAI,OAAO,MAAM,IAAI,MAAM,2BAA2B,MAAM,SAAS;EACvE;CACF;AACF;;;ACvEA,SAAgB,cAAc,MAA6C;CACzE,OAAO;EACL,UAAU;EACV,eAAe,aAAa;EAC5B,SAAS,KAAK,WAAW;EAEzB,MAAM,OAAO,MAAY,KAAkC;GAIzD,IAAI,IAAI,OAAO,SAAS,MAAM,IAAI,aAAa,WAAW,YAAY;GACtE,MAAM,SAAS,MAAM,KAAK,KAAK;IAAE,MAAM,KAAK;IAAM,MAAM,KAAK;IAAM,MAAM,KAAK;GAAK,CAAC;GACpF,IAAI,IAAI,OAAO,SAAS,MAAM,IAAI,aAAa,WAAW,YAAY;GAEtE,MAAM,OAAO,KAAK,kBAAkB,IAAI,eAAe,GAAA,CAAI;GAC3D,MAAM,SAAS,OAAO,UAAU;GAEhC,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,MAAM,gBAAgB,IAAI,MAAM;IAChC,IAAI,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IAE5D,IAAI,OAAO,iBAAiB,aAAa,MAAM;KAC7C,MAAM,KAAK;KACX,IAAI,WAAW,GAAG,QAAQ,GAAG,SAAS,KAAK,IAAI;IACjD,CAAC;IACD,IAAI,iBAAiB,cAAc;KACjC,IAAI,OAAO,oBAAoB,SAAS,OAAO;KAC/C,IAAI,IAAI,UAAU,OAAO,IAAI,SAAS,KAAK;MACzC,IAAI,WAAW,KAAK,MAAM,KAAK,IAAI;MACnC,QAAQ;KACV,OAAO,OAAO,IAAI,MAAM,qBAAqB,IAAI,QAAQ,CAAC;IAC5D,CAAC;IACD,IAAI,iBAAiB,eAAe;KAClC,IAAI,OAAO,oBAAoB,SAAS,OAAO;KAC/C,OAAO,IAAI,MAAM,iCAAiC,CAAC;IACrD,CAAC;IACD,IAAI,iBAAiB,eAAe;KAClC,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC;IAClD,CAAC;IAED,IAAI,KAAK,QAAQ,OAAO,GAAG;IAC3B,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,GACtD,IAAI,iBAAiB,GAAG,CAAC;IAG3B,IAAI,WAAW,QAAQ;KACrB,MAAM,KAAK,IAAI,SAAS;KACxB,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,OAAO,UAAU,CAAC,CAAC,GAAG,GAAG,OAAO,GAAG,CAAC;KAExE,GAAG,OAAO,QAAQ,IAAI;KACtB,IAAI,KAAK,EAAE;IACb,OACE,IAAI,KAAK,IAAI;GAEjB,CAAC;GAED,OAAO,OAAO;IACZ,QAAQ,OAAO;IACf,MAAM,KAAK;IACX,MAAM,KAAK,QAAQ,KAAA;IACnB,MAAM,KAAK;GACb,CAAC;EACH;EAEA,WAAW,KAAc,KAAqD;GAC5E,MAAM,MAAM,IAAI;GAChB,IAAI,KAAK,UAAU,OAAO,KAAK,SAAS,KAAK,GAAG;GAChD,IAAI,KAAK,eAAe,OAAO,GAAG,KAAK,cAAc,QAAQ,QAAQ,EAAE,EAAE,GAAG;GAC5E,MAAM,IAAI,MACR,qFACF;EACF;EAEA,GAAI,KAAK,SACL,EACE,MAAM,OAAO,KAA6B;GACxC,MAAM,KAAK,SAAS,IAAI,MAAgB;EAC1C,EACF,IACA,CAAC;CACP;AACF;;;ACtHA,MAAa,eAAe,OAAO,IAAI,8BAA8B;AAOrE,SAAgB,YAAY,MAA4C;CACtE,OAAO;GAAG,eAAe;EAAM;CAAK;AACtC;AAEA,SAAgB,cAAc,OAAsC;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,gBAAgB;AACxE;;;ACNA,SAAgB,iBAA4E;CAC1F,OAAO;EACL,gBAAgB;EAEhB,KAAK,QAAmB;GACtB,IAAI,UAAU,MAAM,OAAO,CAAC;GAE5B,QADa,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM,EAAA,CAElD,QAAQ,MAAM,OAAO,MAAM,YAAY,MAAM,EAAE,CAAC,CAChD,KAAK,SAAS,OAAO;IAAE,QAAQ;IAAM,KAAK;IAAM;GAAK,CAAC,CAAC;EAC5D;EAEA,MAAM,MAAiB,MAAiB,KAAyB;GAC/D,MAAM,QAAQ,KAAK,QAAQ,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,MAAM,EAAE,OAAe;GAExE,IAAI,CAAC,IAAI,UAAU;IAEjB,IAAI,MAAM,SAAS,GAAG,OAAO,YAAY,GAAG,IAAI,QAAQ,MAAM,GAAW,CAAC;IAE1E,IADa,KAAK,MAAM,MAAM,EAAE,MACzB,GAAG,OAAO,YAAY,CAAC,CAAC;IAC/B,OAAO,KAAK,SAAS,IAAI,YAAY,GAAG,IAAI,QAAQ,GAAG,CAAC,IAAI,YAAY,CAAC,CAAC;GAC5E;GAEA,MAAM,cAAc,IAAI,IAAI,KAAK,QAAQ,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM,EAAE,MAAgB,CAAC;GACvF,MAAM,UAAU,KACb,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAmB,KAAK,QAAQ,CAAC,YAAY,IAAI,CAAC,CAAC;GAE9D,MAAM,OAAgC,CAAC;GACvC,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,IAAI,MAAM,MAAM;GAC9C,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,IAAI,MAAM,MAAM;GAChD,OAAO,YAAY,IAAI;EACzB;CACF;AACF;AAqBA,SAAgB,sBAAsB,MAAqD;CACzF,MAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;CAC5C,MAAM,MAAM,KAAK,eAAe;CAEhC,OAAO;EACL,UAAU;EACV,UAAU;EACV,eAAe,eAAe;EAC9B,SAAS;EAET,MAAM,OAAO,MAAY,MAAmC;GAE1D,OAAO,OAAO;IACZ,SAAS;IACT,MAAM,KAAK;IACX,MAAM,KAAK,QAAQ,KAAA;IACnB,MAAM,KAAK;GACb,CAAC;EACH;EAEA,MAAM,WAAW,KAAc,KAAuC;GACpE,MAAM,WAAY,IAAI,QAAwC;GAC9D,IAAI,CAAC,IAAI,YAAY,CAAC,UACpB,MAAM,IAAI,MACR,mIAEF;GAEF,MAAM,SAAS,IAAI,gBAAgB;GACnC,IAAI,IAAI,SAAS,SAAS,IAAI,SAAS,QAAQ;IAC7C,MAAM,IAAI,IAAI,QAAQ,SAAS;IAC/B,MAAM,IAAI,IAAI,QAAQ,UAAU;IAEhC,OAAO,IAAI,SAAS,GAAG,EAAE,GAAG,EAAE,EAAE;GAClC;GACA,IAAI,IAAI,gBAAgB,cAAc,OAAO,IAAI,YAAY,GAAG;GAEhE,IAAI,KAAK,gBAAgB;IACvB,IAAI,CAAC,KAAK,cACR,MAAM,IAAI,MACR,gFACF;IAEF,MAAM,QAAQ,MAAM,KAAK,aAAa;IACtC,OAAO,IAAI,SAAS,KAAK;IACzB,MAAM,KAAK,OAAO,SAAS;IAC3B,OAAO;KACL,KAAK,GAAG,KAAK,aAAa,IAAI,SAAS,GAAG,SAAS,GAAG,IAAI,SAAS,KAAK,IAAI,OAAO;KACnF,WAAW,KAAK,IAAI,IAAI,MAAM;IAChC;GACF;GAEA,MAAM,KAAK,OAAO,SAAS;GAC3B,OAAO,GAAG,KAAK,aAAa,IAAI,SAAS,GAAG,SAAS,GAAG,IAAI,SAAS,KAAK,IAAI,OAAO;EACvF;CACF;AACF;;;AClHA,SAAgB,cAAc,WAA6D;CACzF,IAAI,UAAU;CACd,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,GAAG;EACpD,IAAI,cAAc,KAAK,GAAG;GACxB,UAAU;GACV,OAAO,OAAO,KAAK,MAAM,IAAI;GAC7B;EACF;EACA,IAAI,OAAO;CACb;CACA,OAAO,UAAU,MAAM;AACzB;AAaA,SAAgB,aAAa,OAAgB,OAAO,IAAI,QAAgB,GAAY;CAClF,IAAI,OAAO,SAAS,eAAe,iBAAiB,MAAM,OAAO;CACjE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,IAAI,KAAK,IAAI,KAAK,GAAG,OAAO;CAC5B,KAAK,IAAI,KAAK;CACd,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC;CACxE,OAAO,OAAO,OAAO,KAAgC,CAAC,CAAC,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC;AAC1F;AAGA,SAAgB,oBAAoB,OAAgB,OAAO,IAAI,QAAgB,GAAY;CACzF,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,IAAI;EACF,IAAI,cAAc,KAAK,GAAG,OAAO;EACjC,IAAI,KAAK,IAAI,KAAK,GAAG,OAAO;EAC5B,KAAK,IAAI,KAAK;EACd,MAAM,cAAc,OAAO,0BAA0B,KAAK;EAC1D,OAAO,OAAO,QAAQ,WAAW,CAAC,CAAC,MAChC,CAAC,KAAK,gBACL,QAAQ,YAAY,WAAW,cAAc,oBAAoB,WAAW,OAAO,IAAI,CAC3F;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,UAAU,MAA6B;CACpD,MAAM,MAAM,MAAM,KAAK,YAAY;CACnC,MAAM,QAAQ,IAAI,WAAW,GAAG;CAChC,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OAAO,UAAU,OAAO,aAAa,IAAI;CAC5D,MAAM,MAAM,OAAO,SAAS,aAAa,KAAK,MAAM,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,QAAQ;CAC5F,OAAO,QAAQ,KAAK,QAAQ,2BAA2B,UAAU;AACnE;AAUA,eAAsB,cACpB,WACA,MAC6C;CAG7C,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,GAAG;EACpD,IAAI,YAAY,KAAK,IAAI,MAAM,UAAU;GACvC,OAAO,OAAO;GACd;EACF;EACA,IAAI,iBAAiB,MAAM,OAAO,OAAO,MAAM,UAAU,KAAK;OACzD,IAAI,MAAM,QAAQ,KAAK,GAC1B,OAAO,OAAO,MAAM,QAAQ,IAC1B,MAAM,KAAK,MAAO,aAAa,OAAO,UAAU,CAAC,IAAI,QAAQ,QAAQ,CAAC,CAAE,CAC1E;OACK,OAAO,OAAO;CACvB;CAGA,IAAI,CAAC,aAAa,MAAM,GAAG,OAAO;CAElC,MAAM,WAAW,KAAK;CACtB,MAAM,KAAK,IAAI,SAAS;CAQxB,IAAI,aAAa,kBAAkB;EAEjC,MAAM,QAAgB,CAAC;EACvB,MAAM,MAAgC,CAAC;EACvC,MAAM,WAAoC,CAAC;EAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,iBAAiB,MAAM;GACzB,IAAI,OAAO,MAAM,MAAM,KAAK,CAAC,aAAa,KAAK;GAC/C,MAAM,KAAK,KAAK;GAChB,SAAS,OAAO;EAClB,OAAO,SAAS,OAAO;EAEzB,GAAG,OAAO,cAAc,KAAK,UAAU,EAAE,WAAW,SAAS,CAAC,CAAC;EAC/D,GAAG,OAAO,OAAO,KAAK,UAAU,GAAG,CAAC;EACpC,MAAM,SAAS,GAAG,MAAM;GACtB,GAAG,OAAO,OAAO,CAAC,GAAG,CAAC;EACxB,CAAC;EACD,OAAO;CACT;CAEA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,iBAAiB,MAAM,GAAG,OAAO,KAAK,KAAK;MAC1C,IAAI,MAAM,QAAQ,KAAK,GAC1B,KAAK,MAAM,QAAQ,OACjB,GAAG,OAAO,KAAK,gBAAgB,OAAO,OAAO,OAAO,IAAI,CAAC;MAEtD,IAAI,SAAS,MAAM,GAAG,OAAO,KAAK,EAAE;MACtC,IAAI,OAAO,UAAU,UAAU,GAAG,OAAO,KAAK,KAAK,UAAU,KAAK,CAAC;MACnE,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;CAEnC,OAAO;AACT;;;AC3IA,MAAa,uBAAuB,OAAO,IAAI,0BAA0B;AAEzE,MAAM,WAAW,IAAI,IAAI;CAAC;CAAU;CAAU;CAAc;AAAY,CAAC;AAEzE,SAAS,cAAc,OAAkE;CACvF,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,UAAU,OAAO,OAAO,KAAgC;CAC9D,OAAO,QAAQ,SAAS,KAAK,QAAQ,OAAO,MAAM,KAAK,OAAO,MAAM,YAAY,EAAE,UAAU,EAAE;AAChG;AAWA,SAAgB,gBAAmB,UAAa,KAAoB;CAClE,MAAM,WAAW,IAAI,WAAgC,oBAAoB;CACzE,IAAI,CAAC,UACH,MAAM,IAAI,MACR,yFACF;CAKF,IAAI,cAAc,QAAQ,KAAK,CAAE,SAAqC,SAAS;EAC7E,MAAM,MAA+B,CAAC;EACtC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAmC,GAC5E,IAAI,QAAQ,gBAAgB,OAAO,GAAG;EAExC,OAAO,eAAe,KAAK,sBAAsB;GAAE,OAAO;GAAM,YAAY;EAAM,CAAC;EACnF,OAAO;CACT;CAEA,MAAM,SAAS;CACf,MAAM,UAAmC,OAAO,OAC9C,OAAO,eAAe,MAAM,CAC9B;CAEA,KAAK,MAAM,OAAO,QAAQ,MAAM,GAAG;EACjC,MAAM,QAAQ,OAAO;EACrB,IAAI,OAAO,UAAU,cAAc,CAAC,SAAS,IAAI,GAAG,GAAG;GACrD,QAAQ,OAAO;GACf;EACF;EACA,QAAQ,OAAO,OAAO,SAAkC;GACtD,MAAM,YAAY,MAAM;GACxB,IAAI,CAAC,aAAa,OAAO,cAAc,YAAY,qBAAqB,UACtE,OAAQ,MAAkC,IAAI;GAEhD,KACG,QAAQ,gBAAgB,QAAQ,kBAChC,aAAa,SAAS,KAAK,oBAAoB,SAAS,IAEzD,MAAM,IAAI,MACR,8DAA8D,IAAI,gGACpE;GAMF,IAAI,MAAM,QAAQ,SAAS,GAAG,OAAQ,MAAkC,IAAI;GAC5E,MAAM,UAAU,cAAc,SAAoC;GAClE,IAAI,CAAC,aAAa,OAAO,GAAG;IAE1B,MAAM,cAAc,YAAY,YAAY,OAAO;KAAE,GAAG;KAAM,WAAW;IAAQ;IACjF,OAAQ,MAAkC,WAAW;GACvD;GAEA,MAAM,UAAU,MAAM,cAAc,SAAS,QAAQ,KAAK,UAAU,OAAO,KAAK,QAAQ,CAAC,CAAC;GAC1F,OAAQ,MAAkC;IAAE,GAAG;IAAM,WAAW;GAAQ,CAAC;EAC3E;CACF;CAEA,OAAO,eAAe,SAAS,sBAAsB;EAAE,OAAO;EAAM,YAAY;CAAM,CAAC;CACvF,OAAO;AACT;AAEA,SAAS,QAAQ,QAA2C;CAC1D,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC;CACxC,MAAM,QAAQ,OAAO,eAAe,MAAM;CAC1C,IAAI,SAAS,UAAU,OAAO,WACvB;OAAA,MAAM,KAAK,OAAO,oBAAoB,KAAK,GAAG,IAAI,MAAM,eAAe,KAAK,IAAI,CAAC;CAAA;CAExF,OAAO,CAAC,GAAG,IAAI;AACjB;AAkCA,SAAgB,WAAW,KAAqB;CAC9C,OAAO,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI;AACrE;AAGA,SAAgB,YAAY,KAAa,MAAmC;CAC1E,OAAO,KAAK,eAAe,IAAI,WAAW,GAAG,CAAC,KAAK,KAAK;AAC1D;AAEA,SAAS,QAAQ,KAAiB,UAA+B,UAAgC;CAC/F,MAAM,iBAAiB,IAAI,IAA2B;CAEtD,MAAM,iBAAiB,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,KAAA;CAC3E,MAAM,kBACJ,gBAAgB,aAAa,WAAW,eAAe,WACnD,eAAe,WACf;CACN,IAAI,eACF,oBAAoB,WAAW,cAAc;CAE/C,MAAM,MAAM,IAAI,SAAS,IAAI,QAAQ;CACrC,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,GAAG;EAClE,MAAM,KAAM,MAAgE;EAC5E,IAAI,IAAI,cAAc,QAAQ;EAC9B,IAAI,CAAC,SAAS,IAAI,GAAG,eAAe,SAAS,GAAG;EAChD,MAAM,UAAU,SAAS,IAAI,GAAG,WAAW;EAC3C,IAAI,QAAQ,aAAa,WAAW,CAAC,QAAQ,UAAU;EACvD,eAAe,IAAI,WAAW,QAAQ,QAAQ;EAC9C,IAAI,QAAQ,aAAa,UAAU,eAAe,QAAQ;CAC5D;CACA,OAAO;EAAE;EAAgB;EAAiB;CAAa;AACzD;;;AC3JA,MAAM,iBAAkC,CAAC,aAAa,gBAAgB;AAuBtE,SAAgB,wBAAwB,KAAiB,UAAyB;CAChF,MAAM,WAAW,IAAI,WAAgC,oBAAoB;CACzE,IAAI,CAAC,UAAU;CAEf,IAAI,SAAS,SAAS,GAKhB;MAAA,EAHF,YAAY,SACX,OAAO,aAAa,YAAY,OAAO,aAAa,eACrD,wBAAyB,WAEzB,MAAM,IAAI,MACR,yRAIF;CAAA;CAIJ,KAAK,MAAM,OAAO,IAAI,SAAS,KAAK,GAAG;EACrC,MAAM,OAAO,IAAI,IAA2B;EAC5C,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,IAAI,UAAU,CAAC,CAAC,GAAG;GACjE,MAAM,KACJ,MAGA;GACF,IAAI,IAAI,cAAc,QAAQ;GAC9B,IAAI,CAAC,SAAS,IAAI,GAAG,eAAe,SAAS,GAAG;GAChD,MAAM,UAAU,SAAS,IAAI,GAAG,WAAW;GAE3C,IAAI,QAAQ,aAAa,SAEnB;QAAA,EADY,GAAG,UAAqC,QAAQ,cAAA,CACpD,gBACV,MAAM,IAAI,MACR,aAAa,IAAI,KAAK,WAAW,UAAU,wgBAC7C;GAAA;GAIJ,IAAI,QAAQ,aAAa,WAAW,CAAC,QAAQ,UAAU;GACvD,IAAI,CAAC,eAAe,SAAS,QAAQ,QAAQ,GAAG;GAEhD,IADiB,KAAK,IAAI,QAAQ,QACvB,KAAK,MAAM,KAAK,IAAI,QAAQ,UAAU,SAAS;GAC1D,IAAI,KAAK,OAAO,GAAG;IACjB,MAAM,UAAU,CAAC,GAAG,KAAK,QAAQ,CAAC;IAClC,MAAM,CAAC,MAAM,UAAU,QAAQ;IAC/B,MAAM,CAAC,MAAM,UAAU,QAAQ;IAC/B,MAAM,IAAI,MACR,aAAa,IAAI,KAAK,6CAA6C,OAAO,UAAU,KAAK,cAAc,OAAO,UAAU,KAAK,iKAC/H;GACF;EACF;CACF;AACF;;;ACnFA,MAAM,+BAA+B,OAAO,IAAI,iCAAiC;AAOjF,SAAgB,0BAA4C,QAAgB,QAAc;CACxF,IAAI;CACJ,IAAI;EACF,aAAa,OAAO,yBAAyB,QAAQ,4BAA4B;CACnF,QAAQ;EACN,OAAO;CACT;CACA,IACE,eAAe,KAAA,KACf,EAAE,WAAW,eACb,WAAW,UAAU,QACrB,OAAO,WAAW,UAAU,UAE5B,OAAO;CAET,OAAO,eAAe,QAAQ,8BAA8B;EAC1D,OAAO,WAAW;EAClB,YAAY;EACZ,cAAc;EACd,UAAU;CACZ,CAAC;CACD,OAAO;AACT;;;AC7BA,MAAa,eAAe"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["asString"],"sources":["../src/registry.ts","../src/plugin.ts","../src/mappers/ref.ts","../src/kind.ts","../src/mappers/scalar-handle.ts","../src/mappers/rich-image.ts","../src/mappers/junction.ts","../src/contract/run-contract.ts","../src/contract/fake-adapter.ts","../src/adapters/inline.ts","../src/adapters/nhost.ts","../src/adapters/supabase.ts","../src/adapters/s3.ts","../src/mappers/column-patch.ts","../src/adapters/pocketbase.ts","../src/wrapper/encode.ts","../src/wrapper/with-file-storage.ts","../src/wrapper/assert-wiring.ts","../src/baseline-carrier.ts","../src/index.ts"],"sourcesContent":["import type { FileStorageAdapter, FileStorageRegistry } from './types.js';\n\n/** Service name under which the registry is registered on the app. */\nexport const FILE_STORAGE_SERVICE = 'fileStorage' as const;\n\nexport function createFileStorageRegistry(\n map: Record<string, FileStorageAdapter>,\n): FileStorageRegistry {\n if (!map.default) {\n throw new Error(\n \"fileStoragePlugin requires a 'default' entry — every file field that \" +\n 'does not name a storage resolves to it.',\n );\n }\n const entries = new Map(Object.entries(map));\n return {\n get(name = 'default') {\n const found = entries.get(name);\n if (!found) {\n throw new Error(\n `Unknown file storage '${name}'. Registered: ${[...entries.keys()].join(', ')}.`,\n );\n }\n return found;\n },\n has: (name) => entries.has(name),\n names: () => [...entries.keys()],\n hasFused: () => [...entries.values()].some((a) => a.pipeline === 'fused'),\n };\n}\n","import type { Plugin } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nimport type { FileStorageAdapter } from './types.js';\n\n/**\n * Declare the app's file storages as a named map, mirroring the existing\n * multi-data-provider convention. `default` is required; other keys are free\n * form and are what a field names in `f.file({ fileStorage: '…' })`.\n */\nexport function fileStoragePlugin(map: Record<string, FileStorageAdapter>): Plugin {\n const adapters = Object.freeze({ ...map });\n return {\n name: '@burojs/file-storage',\n enforce: 'pre',\n setup: (api) => {\n api.runtime.registerService(FILE_STORAGE_SERVICE, () => createFileStorageRegistry(adapters));\n },\n };\n}\n","import type { FileRef } from '../types.js';\n\nlet seq = 0;\n\n/**\n * Mint a ref with a fresh client-side identity.\n *\n * Deliberately a counter rather than `crypto.randomUUID()`: ids are\n * process-local and never persisted, and a counter keeps test output\n * readable and snapshots stable.\n */\nexport function newRef(init: Partial<FileRef> = {}): FileRef {\n seq += 1;\n return { localId: `ref-${seq}`, ...init };\n}\n","import type { FileRef } from './types.js';\n\n/**\n * Kind by mime; the filename is consulted only when mime is unknown.\n *\n * A pure predicate over `FileRef`, not a UI concern — it lives next to the\n * type it describes so any renderer, in `@burojs/ui-shadcn` or a future UI\n * kit, answers \"is this an image?\" the same way. Two independently\n * maintained copies (one per renderer) drift the moment one gains a mime\n * type or extension the other lacks — which is exactly the bug this\n * function replaces: the deleted `isLikelyImageUrl` regexed an extension out\n * of the stored string, which can never match a uuid or other opaque handle.\n */\nexport function isImage(ref: FileRef): boolean {\n if (ref.mime) return ref.mime.startsWith('image/');\n const name = ref.name ?? '';\n return /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(name);\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\ntype ScalarColumn = string | string[] | null | undefined;\ntype ScalarWriteValue = string | File | (string | File)[] | null;\n\n/**\n * The column IS the handle: a URL, an id, a key, a path, a filename. One\n * mapper covers all four because the difference between them is meaning, not\n * structure — and three names for one function would be three things to keep\n * in sync for no behavioural gain.\n */\nexport function scalarHandle(): FileMapper<ScalarColumn, ScalarWriteValue> {\n return {\n carriesPending: true,\n\n read(column: ScalarColumn): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((entry) => entry !== '' && entry != null)\n .map((entry) => newRef({ handle: entry, raw: entry, name: tail(entry) }));\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): ScalarWriteValue {\n // A ref without a handle has not landed yet. On an `immediate`/\n // `handshake`/`resumable` pipeline that only ever means \"still\n // uploading\" — nothing to write. On a `fused` pipeline it means the\n // bytes are riding the record mutation itself, and `pending` is the\n // File to carry forward: writing the raw File here (rather than\n // omitting the ref) is what lets `withFileStorage` pick it up and\n // encode it into the mutation. Omitting it — the previous behaviour —\n // silently discarded every file added to a fused field using this\n // mapper, which is exactly the pairing `inlineFileStorage` ships as\n // its default.\n const values = next\n .map((r) => r.handle ?? r.pending)\n .filter((v): v is string | File => v != null);\n if (ctx.multiple) return values;\n return values[0] ?? null;\n },\n };\n}\n\n/** Last path segment, used as a display name when the backend gives none. */\nfunction tail(value: string): string | undefined {\n const seg = value.split('/').pop();\n return seg && seg !== value ? seg : value || undefined;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface RichImageOpts {\n /**\n * Who owns the focal point.\n *\n * - `'usage'` — it lives on the reference inside the record, so the same\n * asset crops differently in two records (Sanity-shaped).\n * - `'file'` — it is a property of the file that the storage owns; the\n * mapper leaves it alone (Directus-shaped).\n * - `false` — this column has none.\n *\n * There is no default. Guessing wrong silently corrupts a neighbouring\n * record's crop, which is exactly the failure nobody notices for months.\n */\n focus: 'usage' | 'file' | false;\n /** Dotted path to the handle inside the object. Defaults to `'id'`. */\n handleKey?: string;\n /** Keys lifted into `usage` when `focus === 'usage'`. */\n usageKeys?: string[];\n}\n\nconst DEFAULT_USAGE_KEYS = ['hotspot', 'crop'];\n\ntype Obj = Record<string, unknown>;\n\nexport function richImage(opts: RichImageOpts): FileMapper<unknown, unknown> {\n const handleKey = opts.handleKey ?? 'id';\n const usageKeys = opts.usageKeys ?? DEFAULT_USAGE_KEYS;\n\n function readOne(entry: Obj): FileRef {\n const usage =\n opts.focus === 'usage'\n ? pick(\n entry,\n usageKeys.filter((k) => entry[k] !== undefined),\n )\n : undefined;\n return newRef({\n handle: asString(getPath(entry, handleKey)),\n raw: entry,\n name: asString(entry.name ?? entry.filename_download),\n mime: asString(entry.mime ?? entry.type),\n size: asNumber(entry.size ?? entry.filesize),\n width: asNumber(entry.width),\n height: asNumber(entry.height),\n ...(usage && Object.keys(usage).length > 0 ? { usage } : {}),\n });\n }\n\n return {\n read(column, ctx: MapperCtx): FileRef[] {\n if (column == null) return [];\n const list = (Array.isArray(column) ? column : [column]) as Obj[];\n return list.filter((e) => e && typeof e === 'object').map(readOne);\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): unknown {\n const out = next\n .filter((r) => r.handle != null)\n .map((r) => {\n // Merge ONTO the original entry. Rebuilding it would delete every\n // key this mapper does not model — and real objects carry many.\n const base: Obj = isObj(r.raw) ? { ...r.raw } : {};\n setPath(base, handleKey, r.handle as string);\n if (opts.focus === 'usage' && r.usage) Object.assign(base, r.usage);\n return base;\n });\n if (ctx.multiple) return out;\n return out[0] ?? null;\n },\n };\n}\n\nfunction isObj(v: unknown): v is Obj {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\nfunction asNumber(v: unknown): number | undefined {\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\nfunction pick(src: Obj, keys: string[]): Obj {\n const out: Obj = {};\n for (const k of keys) out[k] = src[k];\n return out;\n}\nfunction getPath(src: Obj, path: string): unknown {\n return path.split('.').reduce<unknown>((acc, k) => (isObj(acc) ? acc[k] : undefined), src);\n}\nfunction setPath(target: Obj, path: string, value: string): void {\n const keys = path.split('.');\n const last = keys.pop() as string;\n let cursor: Obj = target;\n for (const k of keys) {\n const nextLevel = cursor[k];\n // Clone rather than mutate: `raw` is shared with the value we were handed.\n cursor[k] = isObj(nextLevel) ? { ...nextLevel } : {};\n cursor = cursor[k] as Obj;\n }\n cursor[last] = value;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface JunctionOpts {\n /** Column on the junction row holding the file's handle. */\n fileKey: string;\n /** Column holding the 1-based position. Omit when the table is unordered. */\n sortKey?: string;\n}\n\ntype Row = Record<string, unknown>;\n\n/**\n * A column that is an array of junction ROWS rather than of files — Directus\n * M2M being the canonical case. Each row carries its own primary key, which\n * must survive a reorder: dropping it would delete and recreate every row.\n */\nexport function junction(opts: JunctionOpts): FileMapper<Row[] | null | undefined, Row[]> {\n return {\n read(column, _ctx: MapperCtx): FileRef[] {\n if (!Array.isArray(column)) return [];\n return column\n .filter((row) => row && typeof row === 'object')\n .map((row) => newRef({ handle: asString(row[opts.fileKey]), raw: row }));\n },\n\n write(next: FileRef[], _prev: FileRef[], _ctx: MapperCtx): Row[] {\n return next\n .filter((r) => r.handle != null)\n .map((r, i) => {\n const base: Row = r.raw && typeof r.raw === 'object' ? { ...(r.raw as Row) } : {};\n base[opts.fileKey] = r.handle as string;\n if (opts.sortKey) base[opts.sortKey] = i + 1;\n return base;\n });\n },\n };\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\n","import type { FileMapper, FileStorageAdapter, MapperCtx, ResolveCtx } from '../types.js';\n\nexport interface ContractOpts {\n /** A FRESH adapter per case — shared state between cases hides bugs. */\n adapter: () => FileStorageAdapter;\n mapper: FileMapper;\n /** Representative column values this mapper reads, for the invariants. */\n sampleColumn: { single: unknown; multiple: unknown };\n /** Skip upload cases for adapters whose bytes ride the mutation. */\n skipUpload?: boolean;\n /**\n * Set for a mapper whose `write` returns a COLUMN PATCH rather than a column\n * value — `pocketbaseDiff` is the one in this package.\n *\n * Round-trip identity is meaningless for such a mapper: it never returns the\n * value it was given, it returns the keys needed to change it. The patch\n * invariants below replace the round-trip cases rather than sitting beside\n * them, because running both would demand two contradictory things of one\n * `write`.\n */\n writesColumnPatch?: boolean;\n /**\n * Context every `resolveUrl` case is called with. Defaults to `{}`.\n *\n * Supply it for an adapter whose URL cannot be built from a handle alone.\n * A PocketBase filename is meaningless by itself — the URL is assembled from\n * collection + record + filename — so that adapter correctly THROWS on an\n * empty context, and the contract must hand it a real one rather than\n * treating the throw as a conformance failure.\n */\n resolveCtx?: ResolveCtx;\n}\n\n/**\n * The test runner, injected.\n *\n * `src/` may not import vitest: this package promises zero runtime\n * dependencies, and a module under `src/` importing a test framework ships\n * that import in the published bundle. Injection also lets an adopter on jest\n * or node:test run the same contract.\n */\nexport interface ContractRunner {\n it: (name: string, fn: () => void | Promise<void>) => void;\n // Structural, so any assertion library with these shapes fits. The return\n // type must be `unknown`, not `never`: `never` demands the SOURCE function\n // return `never` too (return types are checked covariantly), which no real\n // assertion library does — vitest's `ExpectStatic` returns `Assertion<T>`.\n // `unknown` still rejects a non-function value while accepting any return\n // shape, so this stays structural without over-promising.\n expect: (actual: never) => unknown;\n}\n\nconst single: MapperCtx = { resource: 'demo', field: 'file', multiple: false };\nconst many: MapperCtx = { resource: 'demo', field: 'files', multiple: true };\n\n/**\n * The conformance suite every adapter owes. Call inside a `describe`.\n *\n * Exported from the package so adopters can point it at their own adapter —\n * the claim \"any API can be stretched onto this port\" is only worth something\n * if it is executable by the person doing the stretching.\n */\nexport function runFileStorageContract(opts: ContractOpts, runner: ContractRunner): void {\n const { mapper } = opts;\n const it = runner.it;\n // biome-ignore lint/suspicious/noExplicitAny: structural assertion shim\n const expect = runner.expect as unknown as (actual: unknown) => any;\n\n it('declares a pipeline', () => {\n expect(['immediate', 'fused', 'handshake', 'resumable']).toContain(opts.adapter().pipeline);\n });\n\n it('ships a default mapper', () => {\n const m = opts.adapter().defaultMapper;\n expect(typeof m.read).toBe('function');\n expect(typeof m.write).toBe('function');\n });\n\n if (opts.writesColumnPatch) {\n it('emits an empty patch when nothing changed', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as { keys?: Record<string, unknown> };\n expect(Object.keys(out.keys ?? {}).length).toBe(0);\n });\n\n it('emits only patch keys, never the raw column value', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as Record<string, unknown>;\n expect(typeof out.keys).toBe('object');\n });\n } else {\n it('round-trips an untouched single value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.single, single);\n expect(mapper.write(refs, refs, single)).toEqual(opts.sampleColumn.single);\n });\n\n it('round-trips an untouched multiple value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(mapper.write(refs, refs, many)).toEqual(opts.sampleColumn.multiple);\n });\n }\n\n it('reads null as no files', () => {\n expect(mapper.read(null, single)).toEqual([]);\n });\n\n it('gives every ref a distinct localId', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(new Set(refs.map((r) => r.localId)).size).toBe(refs.length);\n });\n\n if (!opts.skipUpload) {\n it('reports progress and resolves to a ref carrying a handle', async () => {\n const adapter = opts.adapter();\n const seen: number[] = [];\n const ref = await adapter.upload(new File(['hello'], 'a.txt', { type: 'text/plain' }), {\n signal: new AbortController().signal,\n onProgress: (loaded) => seen.push(loaded),\n });\n expect(ref.handle).toBeTruthy();\n expect(seen.length).toBeGreaterThan(0);\n expect(seen.at(-1)).toBe(5);\n });\n\n it('rejects with an AbortError when the signal is already aborted', async () => {\n const adapter = opts.adapter();\n const ac = new AbortController();\n ac.abort();\n await expect(\n adapter.upload(new File(['x'], 'x.txt'), {\n signal: ac.signal,\n onProgress: () => {},\n }),\n ).rejects.toThrow(/abort/i);\n });\n\n it('accepts a retry after a failure', async () => {\n // A GENUINE, guaranteed failure — not \"whatever the first call happens\n // to do\" (the version this replaced called `upload` twice with fresh\n // signals and asserted `expect(first).toBeTruthy()`, where `first` was\n // always the string `'ok'` or `'failed'`; both are truthy, so the\n // assertion could not fail even if the adapter never actually failed\n // once). An already-aborted signal is guaranteed to reject (every\n // reference adapter's own \"rejects with an AbortError\" case above\n // proves it does), so this exercises a REAL failure and then proves the\n // adapter's internal state survived it well enough to accept a real\n // retry — no leftover lock, counter, or half-open request wedges the\n // next call.\n const adapter = opts.adapter();\n const file = new File(['x'], 'x.txt');\n const aborted = new AbortController();\n aborted.abort();\n await expect(\n adapter.upload(file, { signal: aborted.signal, onProgress: () => {} }),\n ).rejects.toThrow();\n const second = await adapter.upload(file, {\n signal: new AbortController().signal,\n onProgress: () => {},\n });\n expect(second.handle).toBeTruthy();\n });\n }\n\n it('resolves a URL for a ref', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n const url = typeof resolved === 'string' ? resolved : resolved.url;\n expect(url).toMatch(/^https?:\\/\\//);\n });\n\n it('either returns a permanent string or states a future expiry — never neither', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n if (typeof resolved === 'string') return;\n // The object arm means \"signed\". A signed URL with no deadline would be\n // cached forever, which is the exact failure this port exists to prevent,\n // so silence is a contract violation rather than a permitted default.\n expect(typeof resolved.expiresAt).toBe('number');\n expect(resolved.expiresAt).toBeGreaterThan(Date.now());\n });\n\n it('leaves unreportable metadata absent rather than guessing', async () => {\n const adapter = opts.adapter();\n if (!adapter.stat) return;\n const [out] = await adapter.stat([{ localId: 'x', handle: 'definitely-not-stored' }]);\n expect(out?.size).not.toBe(0);\n });\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type { FileRef, FileStorageAdapter, ResolveCtx, UploadCtx } from '../types.js';\n\nexport interface FakeAdapterOpts {\n /** Emit this many progress ticks before resolving. Default 2. */\n progressTicks?: number;\n /** Make every upload reject with this message. */\n failWith?: string;\n /** Return signed URLs that expire this many ms from resolution. */\n signedTtlMs?: number;\n /** Default `'immediate'`. Set `'fused'` to stand in for an inline/PocketBase-shaped storage. */\n pipeline?: 'immediate' | 'fused';\n}\n\n/**\n * In-memory adapter. Exists so the contract suite can be tested against\n * something known-correct, and so adopters have a baseline to diff against\n * when their own adapter fails a case.\n */\nexport function createFakeAdapter(opts: FakeAdapterOpts = {}): FileStorageAdapter & {\n stored: Map<string, File>;\n removed: string[];\n} {\n const stored = new Map<string, File>();\n const removed: string[] = [];\n let seq = 0;\n\n return {\n stored,\n removed,\n pipeline: opts.pipeline ?? 'immediate',\n defaultMapper: scalarHandle(),\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n const ticks = opts.progressTicks ?? 2;\n for (let i = 1; i <= ticks; i += 1) {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // Yield so an abort issued from a test lands between ticks.\n await Promise.resolve();\n ctx.onProgress(Math.round((file.size * i) / ticks), file.size);\n }\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n if (opts.failWith) throw new Error(opts.failWith);\n seq += 1;\n const handle = `fake-${seq}-${file.name}`;\n stored.set(handle, file);\n return newRef({ handle, name: file.name, mime: file.type || undefined, size: file.size });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx) {\n const q = ctx.variant?.width ? `?w=${ctx.variant.width}` : '';\n const url = `https://fake.test/${ref.handle}${q}`;\n if (opts.signedTtlMs == null) return url;\n return Promise.resolve({ url, expiresAt: Date.now() + opts.signedTtlMs });\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n return refs.map((r) => {\n const f = r.handle ? stored.get(r.handle) : undefined;\n // Honesty rule: report nothing rather than guess when the file is\n // unknown to this store.\n return f ? { ...r, name: f.name, size: f.size, mime: f.type || undefined } : r;\n });\n },\n\n async remove(ref: FileRef): Promise<void> {\n if (ref.handle) {\n stored.delete(ref.handle);\n removed.push(ref.handle);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n FusedEncoding,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface InlineFileStorageOpts {\n /**\n * How the data-provider wrapper should encode the bytes into the mutation.\n * `base64` transforms the value; `multipart` and `graphql-upload` set the\n * request body and are mutually exclusive with each other.\n */\n encoding: FusedEncoding;\n /**\n * How a saved file is read back. `ctx.resource` and `ctx.record` are given\n * because backends of this shape usually address files through the record\n * that owns them.\n */\n resolveUrl: (ref: FileRef, ctx: ResolveCtx) => ResolvedUrl | Promise<ResolvedUrl>;\n}\n\n/**\n * The storage that is not a storage: the backend takes the file inside the\n * record mutation, so there is no upload endpoint and no handle until the\n * mutation succeeds.\n *\n * `upload` deliberately does nothing but wrap the File in a ref. Progress here\n * belongs to the mutation, and it is `withFileStorage` — the only code on the\n * request path — that can report it.\n */\nexport function inlineFileStorage(opts: InlineFileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'fused',\n encoding: opts.encoding,\n defaultMapper: scalarHandle(),\n // Nothing was uploaded ahead of the save, so nothing can be orphaned.\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref, ctx) {\n return opts.resolveUrl(ref, ctx);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface NhostFileStorageOpts {\n /** Storage service base URL, e.g. `https://<project>.storage.<region>.nhost.run`. */\n baseUrl: string;\n /**\n * Access token supplier. Injected rather than read from localStorage: the\n * v0.2 implementation hardcoded a storage key and a window global, which\n * meant one app shape was supported and no test could drive it.\n */\n getToken: () => string | null | Promise<string | null>;\n bucketId?: string;\n orphans?: OrphanPolicy;\n fetchImpl?: typeof fetch;\n}\n\ninterface NhostFileMeta {\n id?: string;\n name?: string;\n size?: number;\n mimeType?: string;\n}\n\n/**\n * nhost storage: bytes go up first, the column keeps the file's UUID, and the\n * display URL is signed and short-lived.\n *\n * ⚠️ The handle is the id, NOT the presigned URL. Storing the presigned URL —\n * as v0.2 did — writes a link with a TTL into the database.\n */\nexport function nhostFileStorage(opts: NhostFileStorageOpts): FileStorageAdapter {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const base = opts.baseUrl.replace(/\\/+$/, '');\n\n async function authHeaders(): Promise<Record<string, string>> {\n const token = await opts.getToken();\n if (!token) throw new Error('nhostFileStorage: no access token — the user is not signed in');\n return { Authorization: `Bearer ${token}` };\n }\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const headers = await authHeaders();\n const fd = new FormData();\n fd.append('file[]', file);\n if (opts.bucketId) fd.append('bucket-id', opts.bucketId);\n\n // fetch has no upload progress; report the two endpoints we can honestly\n // report rather than faking a curve.\n ctx.onProgress(0, file.size);\n const res = await fetchImpl(`${base}/v1/files`, {\n method: 'POST',\n headers,\n body: fd,\n signal: ctx.signal,\n });\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`nhost upload failed: ${res.status} ${body}`.trim());\n }\n const json = (await res.json()) as NhostFileMeta | { processedFiles?: NhostFileMeta[] };\n const meta = 'processedFiles' in json ? json.processedFiles?.[0] : (json as NhostFileMeta);\n if (!meta?.id) throw new Error('nhost upload returned no file id');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: meta.id,\n name: meta.name ?? file.name,\n mime: meta.mimeType ?? file.type ?? undefined,\n size: meta.size ?? file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const headers = await authHeaders();\n const params = new URLSearchParams();\n if (ctx.variant?.width) params.set('w', String(ctx.variant.width));\n if (ctx.variant?.height) params.set('h', String(ctx.variant.height));\n const qs = params.toString();\n const res = await fetchImpl(\n `${base}/v1/files/${ref.handle}/presignedurl${qs ? `?${qs}` : ''}`,\n { headers },\n );\n if (!res.ok) throw new Error(`nhost presign failed: ${res.status}`);\n const json = (await res.json()) as { url?: string; expiration?: number };\n if (!json.url) throw new Error('nhost presign returned no url');\n return {\n url: json.url,\n // `expiration` is a lifetime in seconds; the cache wants an instant.\n expiresAt: Date.now() + (json.expiration ?? 1800) * 1000,\n };\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n const headers = await authHeaders();\n return Promise.all(\n refs.map(async (ref) => {\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'HEAD',\n headers,\n });\n if (!res.ok) return ref;\n // ⚠️ `headers.get` returns null for a missing header, and\n // `Number(null)` is 0 — which `Number.isFinite` happily accepts. Read\n // the header first and only convert when it is actually present, or a\n // file of unknown size reports as literally zero bytes and every\n // size column downstream believes it. That is the honesty rule\n // (`types.ts`: never guess, never substitute a zero).\n const rawSize = res.headers.get('content-length');\n const size = rawSize == null ? Number.NaN : Number(rawSize);\n const mime = res.headers.get('content-type') ?? undefined;\n return {\n ...ref,\n ...(Number.isFinite(size) ? { size } : {}),\n ...(mime ? { mime } : {}),\n };\n }),\n );\n },\n\n async remove(ref: FileRef): Promise<void> {\n const headers = await authHeaders();\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'DELETE',\n headers,\n });\n if (!res.ok && res.status !== 404) {\n throw new Error(`nhost delete failed: ${res.status}`);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * The slice of `@supabase/supabase-js` this adapter uses, declared\n * structurally so the package keeps zero runtime dependencies and the adapter\n * can be driven by a plain object in tests.\n */\nexport interface SupabaseStorageLike {\n storage: {\n from(bucket: string): {\n upload(\n path: string,\n file: File,\n opts?: { contentType?: string; upsert?: boolean },\n ): Promise<{ data: { path: string } | null; error: { message: string } | null }>;\n getPublicUrl(path: string): { data: { publicUrl: string } };\n createSignedUrl(\n path: string,\n expiresIn: number,\n ): Promise<{ data: { signedUrl: string } | null; error: { message: string } | null }>;\n remove(paths: string[]): Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n}\n\nexport interface SupabaseFileStorageOpts {\n client: SupabaseStorageLike;\n bucket: string;\n /** Public buckets yield permanent URLs; signed buckets yield expiring ones. */\n access: 'public' | 'signed';\n /** Lifetime of a signed URL in seconds. Default 3600. */\n signedTtlSec?: number;\n /** Prepended to every generated object path, e.g. `'avatars'`. */\n pathPrefix?: string;\n orphans?: OrphanPolicy;\n}\n\n/**\n * A per-upload path component that survives a module reload.\n *\n * ⚠️ A module-scoped counter is NOT enough here, even though one is fine for\n * `FileRef.localId`. `localId` is process-local and never persisted; this\n * string becomes the object's PATH IN THE BUCKET. A counter restarts at 0 on\n * every page load and on every serverless cold start, so a user who uploads\n * `photo.png`, reloads, and uploads it again generates the same path twice and\n * gets Supabase's `400 Asset Already Exists` — with `upsert` correctly off,\n * that is a hard failure rather than a silent overwrite.\n */\nfunction pathStamp(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return crypto.randomUUID().slice(0, 8);\n }\n // `padEnd` is not decoration: Math.random() may legally return 0, and\n // (0).toString(36).slice(2, 10) is the empty string — which would put every\n // upload of a given filename back on one colliding path, the exact bug this\n // function exists to prevent.\n return Math.random().toString(36).slice(2, 10).padEnd(8, '0');\n}\n\nexport function supabaseFileStorage(opts: SupabaseFileStorageOpts): FileStorageAdapter {\n const bucket = () => opts.client.storage.from(opts.bucket);\n const ttl = opts.signedTtlSec ?? 3600;\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // A fresh path per upload. Supabase's own guidance is to write to a new\n // path rather than to enable upsert: overwriting races against CDN\n // propagation and the last writer wins silently.\n const stamp = `${pathStamp()}-${file.name}`;\n const path = opts.pathPrefix ? `${opts.pathPrefix}/${stamp}` : stamp;\n\n ctx.onProgress(0, file.size);\n const { data, error } = await bucket().upload(path, file, {\n contentType: file.type || undefined,\n });\n if (error) throw new Error(`Supabase upload failed: ${error.message}`);\n if (!data) throw new Error('Supabase upload returned no path');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: data.path,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, _ctx: ResolveCtx): Promise<ResolvedUrl> {\n const path = ref.handle as string;\n if (opts.access === 'public') {\n // Permanent: a plain string, so it may be cached and server-rendered.\n return bucket().getPublicUrl(path).data.publicUrl;\n }\n const { data, error } = await bucket().createSignedUrl(path, ttl);\n if (error) throw new Error(`Supabase sign failed: ${error.message}`);\n if (!data) throw new Error('Supabase sign returned no url');\n return { url: data.signedUrl, expiresAt: Date.now() + ttl * 1000 };\n },\n\n async remove(ref: FileRef): Promise<void> {\n const { error } = await bucket().remove([ref.handle as string]);\n if (error) throw new Error(`Supabase remove failed: ${error.message}`);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/** What the adopter's signing endpoint hands back. */\nexport interface SignedTarget {\n /**\n * The object key. **The server chooses it, not the client** — letting the\n * browser name the object is how buckets get overwritten by whoever asks.\n */\n key: string;\n url: string;\n method?: 'PUT' | 'POST';\n /** Headers that were part of the signature. Sent verbatim. */\n headers?: Record<string, string>;\n /** POST-policy form fields. Sent before the file part. */\n fields?: Record<string, string>;\n}\n\nexport interface S3FileStorageOpts {\n /** Ask your backend to sign. Authorization happens there, not here. */\n sign: (file: { name: string; type: string; size: number }) => Promise<SignedTarget>;\n /** CDN or bucket origin for permanent read URLs. */\n publicBaseUrl?: string;\n /** Supply for private buckets; return an expiring URL. */\n signView?: (key: string, ctx: ResolveCtx) => Promise<ResolvedUrl>;\n remove?: (key: string) => Promise<void>;\n orphans?: OrphanPolicy;\n /** Injected for tests. Defaults to `new XMLHttpRequest()`. */\n xhrImpl?: () => XMLHttpRequest;\n}\n\n/**\n * Direct-to-bucket upload behind a signed handshake: ask the server to sign,\n * send the bytes straight to storage, keep the key.\n *\n * Uses XHR rather than fetch on purpose — browsers expose no upload progress\n * on fetch, and this is the pipeline whose files are large enough for the\n * absence to be felt.\n */\nexport function s3FileStorage(opts: S3FileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'handshake',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n // Check BEFORE signing, like the sibling adapters do. Signing is a round\n // trip to the adopter's own backend; an upload the user already\n // cancelled has no business making it.\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const target = await opts.sign({ name: file.name, type: file.type, size: file.size });\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n\n const xhr = (opts.xhrImpl ?? (() => new XMLHttpRequest()))();\n const method = target.method ?? 'PUT';\n\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => xhr.abort();\n ctx.signal.addEventListener('abort', onAbort, { once: true });\n\n xhr.upload.addEventListener('progress', (e) => {\n const ev = e as ProgressEvent;\n ctx.onProgress(ev.loaded, ev.total || file.size);\n });\n xhr.addEventListener('load', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n if (xhr.status >= 200 && xhr.status < 300) {\n ctx.onProgress(file.size, file.size);\n resolve();\n } else reject(new Error(`S3 upload failed: ${xhr.status}`));\n });\n xhr.addEventListener('error', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n reject(new Error('S3 upload failed: network error'));\n });\n xhr.addEventListener('abort', () => {\n reject(new DOMException('Aborted', 'AbortError'));\n });\n\n xhr.open(method, target.url);\n for (const [k, v] of Object.entries(target.headers ?? {})) {\n xhr.setRequestHeader(k, v);\n }\n\n if (method === 'POST') {\n const fd = new FormData();\n for (const [k, v] of Object.entries(target.fields ?? {})) fd.append(k, v);\n // ⚠️ The file part goes LAST: S3 ignores every field that follows it.\n fd.append('file', file);\n xhr.send(fd);\n } else {\n xhr.send(file);\n }\n });\n\n return newRef({\n handle: target.key,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx): ResolvedUrl | Promise<ResolvedUrl> {\n const key = ref.handle as string;\n if (opts.signView) return opts.signView(key, ctx);\n if (opts.publicBaseUrl) return `${opts.publicBaseUrl.replace(/\\/+$/, '')}/${key}`;\n throw new Error(\n 's3FileStorage: pass publicBaseUrl for a public bucket or signView for a private one',\n );\n },\n\n ...(opts.remove\n ? {\n async remove(ref: FileRef): Promise<void> {\n await opts.remove?.(ref.handle as string);\n },\n }\n : {}),\n };\n}\n","/**\n * A mapper's answer when the change cannot be expressed as \"the new value of\n * this column\".\n *\n * PocketBase updates a multi-value file field through SIBLING keys — `docs+`\n * to append, `docs-` to remove named files — so there is no single value to\n * return. Rather than special-casing one backend inside the write path, a\n * mapper may return this marker and `withFileStorage` splices its keys into\n * the payload instead of assigning to the field's own key.\n */\nexport const COLUMN_PATCH = Symbol.for('buro.fileStorage.columnPatch');\n\nexport interface ColumnPatch {\n [COLUMN_PATCH]: true;\n keys: Record<string, unknown>;\n}\n\nexport function columnPatch(keys: Record<string, unknown>): ColumnPatch {\n return { [COLUMN_PATCH]: true, keys };\n}\n\nexport function isColumnPatch(value: unknown): value is ColumnPatch {\n return typeof value === 'object' && value !== null && COLUMN_PATCH in value;\n}\n","import { columnPatch } from '../mappers/column-patch.js';\nimport { newRef } from '../mappers/ref.js';\nimport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n MapperCtx,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * PocketBase stores a filename per file and updates multi-value fields by\n * DIFF: `field+` appends, `field-` removes by name. Single-value fields are\n * replaced outright, and cleared with an empty string.\n */\nexport function pocketbaseDiff(): FileMapper<string | string[] | null | undefined, unknown> {\n return {\n carriesPending: true,\n\n read(column): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((n) => typeof n === 'string' && n !== '')\n .map((name) => newRef({ handle: name, raw: name, name }));\n },\n\n write(next: FileRef[], prev: FileRef[], ctx: MapperCtx): unknown {\n const added = next.filter((r) => r.pending).map((r) => r.pending as File);\n\n if (!ctx.multiple) {\n // No diff protocol for single-value fields — assign or clear.\n if (added.length > 0) return columnPatch({ [ctx.field]: added[0] as File });\n const kept = next.find((r) => r.handle);\n if (kept) return columnPatch({});\n return prev.length > 0 ? columnPatch({ [ctx.field]: '' }) : columnPatch({});\n }\n\n const keptHandles = new Set(next.filter((r) => r.handle).map((r) => r.handle as string));\n const removed = prev\n .map((r) => r.handle)\n .filter((h): h is string => h != null && !keptHandles.has(h));\n\n const keys: Record<string, unknown> = {};\n if (added.length > 0) keys[`${ctx.field}+`] = added;\n if (removed.length > 0) keys[`${ctx.field}-`] = removed;\n return columnPatch(keys);\n },\n };\n}\n\nexport interface PocketbaseFileStorageOpts {\n /** PocketBase origin, e.g. `http://127.0.0.1:8090`. */\n baseUrl: string;\n /** Set when the file field is marked Protected in the collection schema. */\n protectedFiles?: boolean;\n /** Supplies a short-lived file token — `pb.files.getToken()`. */\n getFileToken?: () => Promise<string>;\n /** Lifetime assumed for a file token, in seconds. Default 120. */\n tokenTtlSec?: number;\n}\n\n/**\n * PocketBase: bytes ride the record mutation as multipart, the column keeps a\n * filename that only means anything alongside its record, images get thumbs\n * from a query parameter, and protected files need a short-lived token.\n *\n * It is the hardest of the four reference pipelines, which is why it is the\n * one the example app runs against.\n */\nexport function pocketbaseFileStorage(opts: PocketbaseFileStorageOpts): FileStorageAdapter {\n const base = opts.baseUrl.replace(/\\/+$/, '');\n const ttl = opts.tokenTtlSec ?? 120;\n\n return {\n pipeline: 'fused',\n encoding: 'multipart',\n defaultMapper: pocketbaseDiff(),\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n // Fused: hold the bytes for the mutation.\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const recordId = (ctx.record as { id?: string } | undefined)?.id;\n if (!ctx.resource || !recordId) {\n throw new Error(\n 'pocketbaseFileStorage: a file URL needs its collection and record id — ' +\n 'the filename alone does not identify a file in PocketBase.',\n );\n }\n const params = new URLSearchParams();\n if (ctx.variant?.width || ctx.variant?.height) {\n const w = ctx.variant.width ?? 0;\n const h = ctx.variant.height ?? 0;\n // `f` fits inside the box instead of cropping to it.\n params.set('thumb', `${w}x${h}f`);\n }\n if (ctx.disposition === 'attachment') params.set('download', '1');\n\n if (opts.protectedFiles) {\n if (!opts.getFileToken) {\n throw new Error(\n 'pocketbaseFileStorage: protectedFiles is set but getFileToken was not supplied',\n );\n }\n const token = await opts.getFileToken();\n params.set('token', token);\n const qs = params.toString();\n return {\n url: `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`,\n expiresAt: Date.now() + ttl * 1000,\n };\n }\n\n const qs = params.toString();\n return `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`;\n },\n };\n}\n","import { isColumnPatch } from '../mappers/column-patch.js';\nimport { type EncodingPlan, encodingFor } from './with-file-storage.js';\n\n/**\n * Splice column patches into the payload; leave everything else alone.\n *\n * Reference identity is preserved ONLY when the payload carries no column\n * patch at all. The moment even an EMPTY patch (`{ keys: {} }` — \"nothing\n * changed for this field\") is present, the field's own key must still be\n * dropped: forwarding the raw patch marker as the field's value would\n * overwrite the column with `{ keys: {} }` instead of leaving it alone. So a\n * fresh object is built whenever any patch is present, empty or not — this\n * function does not claim otherwise.\n */\nexport function splicePatches(variables: Record<string, unknown>): Record<string, unknown> {\n let touched = false;\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (isColumnPatch(value)) {\n touched = true;\n Object.assign(out, value.keys);\n continue;\n }\n out[key] = value;\n }\n return touched ? out : variables;\n}\n\n/**\n * Does this payload actually carry bytes? Everything downstream hinges on it.\n *\n * ⚠️ No depth cap. An earlier version stopped at four levels, which meant a\n * File nested deeper was silently declared absent — the payload then took the\n * untouched fast path and the File reached `JSON.stringify` and became `{}`.\n * Today's mappers never nest a File below the first level, so the cap was\n * unreachable; a future rich-content mapper would have made it a silent\n * data-loss bug. Cycles are handled by tracking visited objects rather than by\n * limiting depth.\n */\nexport function containsFile(value: unknown, seen = new WeakSet<object>()): boolean {\n if (typeof File !== 'undefined' && value instanceof File) return true;\n if (!value || typeof value !== 'object') return false;\n if (seen.has(value)) return false;\n seen.add(value);\n if (Array.isArray(value)) return value.some((v) => containsFile(v, seen));\n return Object.values(value as Record<string, unknown>).some((v) => containsFile(v, seen));\n}\n\n/** Getter-safe recursive check used to reject unsupported bulk column patches. */\nexport function containsColumnPatch(value: unknown, seen = new WeakSet<object>()): boolean {\n if (!value || typeof value !== 'object') return false;\n try {\n if (isColumnPatch(value)) return true;\n if (seen.has(value)) return false;\n seen.add(value);\n const descriptors = Object.getOwnPropertyDescriptors(value);\n return Object.entries(descriptors).some(\n ([key, descriptor]) =>\n key !== 'length' && 'value' in descriptor && containsColumnPatch(descriptor.value, seen),\n );\n } catch {\n return false;\n }\n}\n\nasync function toDataUrl(file: File): Promise<string> {\n const buf = await file.arrayBuffer();\n const bytes = new Uint8Array(buf);\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n const b64 = typeof btoa === 'function' ? btoa(binary) : Buffer.from(bytes).toString('base64');\n return `data:${file.type || 'application/octet-stream'};base64,${b64}`;\n}\n\n/**\n * Turn a payload carrying Files into something the provider can send.\n *\n * `base64` is a VALUE transform, so the payload stays a plain object and\n * composes with anything. `multipart` and `graphql-upload` produce a FormData\n * — the provider must send it as the body verbatim and must NOT set a\n * content-type, because the browser writes the boundary.\n */\nexport async function encodePayload(\n variables: Record<string, unknown>,\n plan: EncodingPlan,\n): Promise<Record<string, unknown> | FormData> {\n // base64 first: it is a VALUE transform, so those Files stop being Files\n // before anything decides what the request body looks like.\n const staged: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (encodingFor(key, plan) !== 'base64') {\n staged[key] = value;\n continue;\n }\n if (value instanceof File) staged[key] = await toDataUrl(value);\n else if (Array.isArray(value)) {\n staged[key] = await Promise.all(\n value.map((v) => (v instanceof File ? toDataUrl(v) : Promise.resolve(v))),\n );\n } else staged[key] = value;\n }\n\n // Nothing left to carry in the body — keep it a plain JSON payload.\n if (!containsFile(staged)) return staged;\n\n const encoding = plan.bodyEncoding;\n const fd = new FormData();\n\n // ⚠️ Both loops below read `staged`, not `variables`. A base64-designated\n // key already holds its data-URL STRING in `staged` at this point — reading\n // `variables` here would re-discover the original File and append it to the\n // FormData as raw binary, silently undoing the base64 conversion the moment\n // any OTHER field on the same call needs a body encoding. That is the exact\n // failure this plan revision exists to close; it must not reopen here.\n if (encoding === 'graphql-upload') {\n // multipart request spec: `operations` + `map` first, then the file parts.\n const files: File[] = [];\n const map: Record<string, string[]> = {};\n const scrubbed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) {\n map[String(files.length)] = [`variables.${key}`];\n files.push(value);\n scrubbed[key] = null;\n } else scrubbed[key] = value;\n }\n fd.append('operations', JSON.stringify({ variables: scrubbed }));\n fd.append('map', JSON.stringify(map));\n files.forEach((f, i) => {\n fd.append(String(i), f);\n });\n return fd;\n }\n\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) fd.append(key, value);\n else if (Array.isArray(value)) {\n for (const item of value) {\n fd.append(key, item instanceof File ? item : String(item));\n }\n } else if (value == null) fd.append(key, '');\n else if (typeof value === 'object') fd.append(key, JSON.stringify(value));\n else fd.append(key, String(value));\n }\n return fd;\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { containsColumnPatch, containsFile, encodePayload, splicePatches } from './encode.js';\n\n/** Stamped on a wrapped provider so the wiring assertion can see it. */\nexport const FILE_STORAGE_WRAPPED = Symbol.for('buro.fileStorage.wrapped');\n\nconst MUTATING = new Set(['create', 'update', 'updateMany']);\n\nfunction isProviderMap(value: unknown): value is Record<string, Record<string, unknown>> {\n if (!value || typeof value !== 'object') return false;\n const entries = Object.values(value as Record<string, unknown>);\n return entries.length > 0 && entries.every((v) => v && typeof v === 'object' && !('then' in v));\n}\n\n/**\n * Rewrite outgoing mutations that actually carry bytes.\n *\n * The decision is PER REQUEST, from the payload — never global. Mixing\n * pipelines is the ordinary case (a storage service for most fields, two\n * places that take the file inline), so a payload with no File is handed to\n * the underlying provider untouched, by reference: no FormData, no\n * re-encoding, no surprise.\n */\nexport function withFileStorage<T>(provider: T, app: AppRuntime): T {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) {\n throw new Error(\n 'withFileStorage: no file storage registered — add fileStoragePlugin({...}) to defineApp',\n );\n }\n\n // Wrap a map of providers entry by entry, so `meta.dataProviderName` keeps\n // composing with file fields.\n if (isProviderMap(provider) && !(provider as Record<string, unknown>).getList) {\n const out: Record<string, unknown> = {};\n for (const [name, entry] of Object.entries(provider as Record<string, unknown>)) {\n out[name] = withFileStorage(entry, app);\n }\n Object.defineProperty(out, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return out as T;\n }\n\n const source = provider as Record<string, unknown>;\n const wrapped: Record<string, unknown> = Object.create(\n Object.getPrototypeOf(source) as object | null,\n );\n\n for (const key of allKeys(source)) {\n const value = source[key];\n if (typeof value !== 'function' || !MUTATING.has(key)) {\n wrapped[key] = value;\n continue;\n }\n wrapped[key] = async (args: Record<string, unknown>) => {\n const variables = args?.variables;\n if (!variables || typeof variables !== 'object' || variables instanceof FormData) {\n return (value as (a: unknown) => unknown)(args);\n }\n if (key === 'updateMany' && (containsFile(variables) || containsColumnPatch(variables))) {\n throw new Error(\n `withFileStorage: bulk file mutations are not supported for ${key}; submit one record at a time so FileMapper patches and request-body encoding stay unambiguous.`,\n );\n }\n const spliced = splicePatches(variables as Record<string, unknown>);\n if (!containsFile(spliced)) {\n // Untouched — pass the ORIGINAL object so identity is preserved.\n const passthrough = spliced === variables ? args : { ...args, variables: spliced };\n return (value as (a: unknown) => unknown)(passthrough);\n }\n // Per RESOURCE, not per provider — see planFor.\n const encoded = await encodePayload(spliced, planFor(app, registry, String(args.resource)));\n return (value as (a: unknown) => unknown)({ ...args, variables: encoded });\n };\n }\n\n Object.defineProperty(wrapped, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return wrapped as T;\n}\n\nfunction allKeys(source: Record<string, unknown>): string[] {\n const keys = new Set(Object.keys(source));\n const proto = Object.getPrototypeOf(source) as object | null;\n if (proto && proto !== Object.prototype) {\n for (const k of Object.getOwnPropertyNames(proto)) if (k !== 'constructor') keys.add(k);\n }\n return [...keys];\n}\n\n/**\n * Which encoding each payload key needs, and which body encoding the rest\n * fall back to — for ONE resource.\n *\n * ⚠️ This must be resolved per resource, not once per provider. An earlier\n * version picked a single encoding from the whole registry, so an app with a\n * multipart storage anywhere and a base64 storage anywhere else sent the\n * base64 field's File into FormData as raw binary — never encoded, no boot\n * error, no runtime error, silently wrong data. It also contradicted the\n * type's own promise that base64 \"composes with anything\", which is exactly\n * why `assertFileStorageWiring` permits that combination.\n *\n * `base64` is a VALUE transform: those fields are converted first and stop\n * being Files. Only what remains decides the request body.\n */\nexport interface EncodingPlan {\n /** Encoding EXPLICITLY declared for a field, via `f.file({ fileStorage })`. Authoritative. */\n knownEncodings: Map<string, FusedEncoding>;\n /**\n * Assumed for a payload key with NO declared field to consult — the\n * resource isn't registered, or has no `f.file()` for this key. Mirrors\n * `ui.fileStorage ?? 'default'`, the exact resolution a KNOWN field with no\n * explicit storage name already gets, extended to \"no field info at all\".\n * This is what keeps a single-storage app working without ever having to\n * declare its fields through `@burojs/field-types`.\n */\n defaultEncoding: FusedEncoding;\n /** Body encoding for whatever Files remain once base64 keys are converted. */\n bodyEncoding: Exclude<FusedEncoding, 'base64'>;\n}\n\n/** `docs+` / `docs-` are patch keys for the field `docs`. */\nexport function fieldOfKey(key: string): string {\n return key.endsWith('+') || key.endsWith('-') ? key.slice(0, -1) : key;\n}\n\n/** The encoding one payload key resolves to: its field's, or the plan's default. */\nexport function encodingFor(key: string, plan: EncodingPlan): FusedEncoding {\n return plan.knownEncodings.get(fieldOfKey(key)) ?? plan.defaultEncoding;\n}\n\nfunction planFor(app: AppRuntime, registry: FileStorageRegistry, resource: string): EncodingPlan {\n const knownEncodings = new Map<string, FusedEncoding>();\n\n const defaultAdapter = registry.has('default') ? registry.get('default') : undefined;\n const defaultEncoding: FusedEncoding =\n defaultAdapter?.pipeline === 'fused' && defaultAdapter.encoding\n ? defaultAdapter.encoding\n : 'multipart';\n let bodyEncoding: Exclude<FusedEncoding, 'base64'> =\n defaultEncoding === 'base64' ? 'multipart' : defaultEncoding;\n\n const def = app.registry.get(resource);\n for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {\n if ((field as { kind?: string }).kind !== 'file') continue;\n const ui = (field as { ui?: { fileStorage?: string } }).ui;\n if (!registry.has(ui?.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui?.fileStorage);\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n knownEncodings.set(fieldName, adapter.encoding);\n if (adapter.encoding !== 'base64') bodyEncoding = adapter.encoding;\n }\n return { knownEncodings, defaultEncoding, bodyEncoding };\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileMapper, FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { FILE_STORAGE_WRAPPED } from './with-file-storage.js';\n\n/** Encodings that claim the request body, and so cannot share one request. */\nconst BODY_ENCODINGS: FusedEncoding[] = ['multipart', 'graphql-upload'];\n\n/**\n * Three checks that must fail at boot rather than at runtime.\n *\n * 1. A fused storage with an unwrapped provider. Files in `variables` reach\n * `JSON.stringify` and become `{}` — the upload silently does nothing, the\n * request succeeds, and nobody finds out until someone opens the record.\n * 2. A fused storage paired with a mapper that cannot carry a `pending` file\n * forward (`mapper.carriesPending` is not set). A ref on a fused pipeline\n * never has a `handle` before the mutation runs — only `pending` — so a\n * mapper that writes nothing for a handle-less ref (the ordinary case:\n * `richImage`, `junction`) silently drops every file added through that\n * field. This is the same class of failure as #1 — a fused storage whose\n * mapper cannot carry the file forward is exactly as broken as one with no\n * wrapper — so it gets the same treatment: fail loudly before any user\n * touches the form, not discover it when a saved record turns out empty.\n * 3. Two incompatible body encodings on one resource. One request has one\n * body; discovering that during a save is discovering it too late.\n *\n * Called by the host composition layer that owns both the app and its data\n * provider.\n */\nexport function assertFileStorageWiring(app: AppRuntime, provider: unknown): void {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) return;\n\n if (registry.hasFused()) {\n const stamped =\n provider != null &&\n (typeof provider === 'object' || typeof provider === 'function') &&\n FILE_STORAGE_WRAPPED in (provider as object);\n if (!stamped) {\n throw new Error(\n 'A fused file storage is registered, but the data provider is not wrapped. ' +\n 'Files ride inside the record mutation for this pipeline, so an unwrapped ' +\n 'provider will JSON.stringify them into `{}` and lose them silently. ' +\n 'Wrap it once: dataProvider: withFileStorage(myDataProvider, app)',\n );\n }\n }\n\n for (const def of app.registry.list()) {\n const seen = new Map<FusedEncoding, string>();\n for (const [fieldName, field] of Object.entries(def.fields ?? {})) {\n if ((field as { kind?: string }).kind !== 'file') continue;\n const ui = (field as { ui?: { fileStorage?: string; mapper?: unknown } }).ui;\n if (!registry.has(ui?.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui?.fileStorage);\n\n if (adapter.pipeline === 'fused') {\n const mapper = (ui?.mapper as FileMapper | undefined) ?? adapter.defaultMapper;\n if (!mapper.carriesPending) {\n throw new Error(\n `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 })\\`.`,\n );\n }\n }\n\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;\n const previous = seen.get(adapter.encoding);\n if (previous == null) seen.set(adapter.encoding, fieldName);\n if (seen.size > 1) {\n const entries = [...seen.entries()];\n const [encA, fieldA] = entries[0] as [FusedEncoding, string];\n const [encB, fieldB] = entries[1] as [FusedEncoding, string];\n throw new Error(\n `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.`,\n );\n }\n }\n }\n}\n","const FILE_MAPPER_BASELINE_CARRIER = Symbol.for('buro.fileMapper.baselineCarrier');\n\n/**\n * Copy the opaque mapped-file baseline capability from a decoded record to a\n * fresh edit payload. The physical baseline itself remains private to the\n * provider wrapper; this helper can neither inspect nor serialize it.\n */\nexport function inheritFileMapperBaseline<T extends object>(source: object, target: T): T {\n let descriptor: PropertyDescriptor | undefined;\n try {\n descriptor = Object.getOwnPropertyDescriptor(source, FILE_MAPPER_BASELINE_CARRIER);\n } catch {\n return target;\n }\n if (\n descriptor === undefined ||\n !('value' in descriptor) ||\n descriptor.value === null ||\n typeof descriptor.value !== 'object'\n ) {\n return target;\n }\n Object.defineProperty(target, FILE_MAPPER_BASELINE_CARRIER, {\n value: descriptor.value,\n enumerable: false,\n configurable: true,\n writable: false,\n });\n return target;\n}\n","export const PACKAGE_NAME = '@burojs/file-storage' as const;\n\nexport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n FileStorageRegistry,\n FusedEncoding,\n MapperCtx,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n UploadPipeline,\n} from './types.js';\nexport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nexport { fileStoragePlugin } from './plugin.js';\nexport { newRef } from './mappers/ref.js';\nexport { isImage } from './kind.js';\nexport { scalarHandle } from './mappers/scalar-handle.js';\nexport { richImage, type RichImageOpts } from './mappers/rich-image.js';\nexport { junction, type JunctionOpts } from './mappers/junction.js';\nexport {\n runFileStorageContract,\n type ContractOpts,\n type ContractRunner,\n} from './contract/run-contract.js';\nexport { createFakeAdapter, type FakeAdapterOpts } from './contract/fake-adapter.js';\nexport { inlineFileStorage, type InlineFileStorageOpts } from './adapters/inline.js';\nexport { nhostFileStorage, type NhostFileStorageOpts } from './adapters/nhost.js';\nexport {\n supabaseFileStorage,\n type SupabaseFileStorageOpts,\n type SupabaseStorageLike,\n} from './adapters/supabase.js';\nexport { s3FileStorage, type S3FileStorageOpts, type SignedTarget } from './adapters/s3.js';\nexport {\n pocketbaseDiff,\n pocketbaseFileStorage,\n type PocketbaseFileStorageOpts,\n} from './adapters/pocketbase.js';\nexport {\n COLUMN_PATCH,\n columnPatch,\n isColumnPatch,\n type ColumnPatch,\n} from './mappers/column-patch.js';\nexport { FILE_STORAGE_WRAPPED, withFileStorage } from './wrapper/with-file-storage.js';\nexport { assertFileStorageWiring } from './wrapper/assert-wiring.js';\nexport {\n containsColumnPatch,\n containsFile,\n encodePayload,\n splicePatches,\n} from './wrapper/encode.js';\nexport { inheritFileMapperBaseline } from './baseline-carrier.js';\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -719,7 +719,6 @@ const FILE_STORAGE_WRAPPED = Symbol.for("buro.fileStorage.wrapped");
|
|
|
719
719
|
const MUTATING = new Set([
|
|
720
720
|
"create",
|
|
721
721
|
"update",
|
|
722
|
-
"createMany",
|
|
723
722
|
"updateMany"
|
|
724
723
|
]);
|
|
725
724
|
function isProviderMap(value) {
|
|
@@ -750,8 +749,7 @@ function withFileStorage(provider, app) {
|
|
|
750
749
|
wrapped[key] = async (args) => {
|
|
751
750
|
const variables = args?.variables;
|
|
752
751
|
if (!variables || typeof variables !== "object" || variables instanceof FormData) return value(args);
|
|
753
|
-
if (
|
|
754
|
-
if (Array.isArray(variables)) return value(args);
|
|
752
|
+
if (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
753
|
const spliced = splicePatches(variables);
|
|
756
754
|
if (!containsFile(spliced)) {
|
|
757
755
|
const passthrough = spliced === variables ? args : {
|
|
@@ -794,10 +792,10 @@ function planFor(app, registry, resource) {
|
|
|
794
792
|
let bodyEncoding = defaultEncoding === "base64" ? "multipart" : defaultEncoding;
|
|
795
793
|
const def = app.registry.get(resource);
|
|
796
794
|
for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {
|
|
795
|
+
if (field.kind !== "file") continue;
|
|
797
796
|
const ui = field.ui;
|
|
798
|
-
if (ui?.
|
|
799
|
-
|
|
800
|
-
const adapter = registry.get(ui.fileStorage);
|
|
797
|
+
if (!registry.has(ui?.fileStorage ?? "default")) continue;
|
|
798
|
+
const adapter = registry.get(ui?.fileStorage);
|
|
801
799
|
if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
|
|
802
800
|
knownEncodings.set(fieldName, adapter.encoding);
|
|
803
801
|
if (adapter.encoding !== "base64") bodyEncoding = adapter.encoding;
|
|
@@ -820,12 +818,12 @@ function assertFileStorageWiring(app, provider) {
|
|
|
820
818
|
for (const def of app.registry.list()) {
|
|
821
819
|
const seen = new Map();
|
|
822
820
|
for (const [fieldName, field] of Object.entries(def.fields ?? {})) {
|
|
821
|
+
if (field.kind !== "file") continue;
|
|
823
822
|
const ui = field.ui;
|
|
824
|
-
if (ui?.
|
|
825
|
-
|
|
826
|
-
const adapter = registry.get(ui.fileStorage);
|
|
823
|
+
if (!registry.has(ui?.fileStorage ?? "default")) continue;
|
|
824
|
+
const adapter = registry.get(ui?.fileStorage);
|
|
827
825
|
if (adapter.pipeline === "fused") {
|
|
828
|
-
if (!(ui
|
|
826
|
+
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 })\`.`);
|
|
829
827
|
}
|
|
830
828
|
if (adapter.pipeline !== "fused" || !adapter.encoding) continue;
|
|
831
829
|
if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["asString"],"sources":["../src/registry.ts","../src/plugin.ts","../src/mappers/ref.ts","../src/kind.ts","../src/mappers/scalar-handle.ts","../src/mappers/rich-image.ts","../src/mappers/junction.ts","../src/contract/run-contract.ts","../src/contract/fake-adapter.ts","../src/adapters/inline.ts","../src/adapters/nhost.ts","../src/adapters/supabase.ts","../src/adapters/s3.ts","../src/mappers/column-patch.ts","../src/adapters/pocketbase.ts","../src/wrapper/encode.ts","../src/wrapper/with-file-storage.ts","../src/wrapper/assert-wiring.ts","../src/baseline-carrier.ts","../src/index.ts"],"sourcesContent":["import type { FileStorageAdapter, FileStorageRegistry } from './types.js';\n\n/** Service name under which the registry is registered on the app. */\nexport const FILE_STORAGE_SERVICE = 'fileStorage' as const;\n\nexport function createFileStorageRegistry(\n map: Record<string, FileStorageAdapter>,\n): FileStorageRegistry {\n if (!map.default) {\n throw new Error(\n \"fileStoragePlugin requires a 'default' entry — every file field that \" +\n 'does not name a storage resolves to it.',\n );\n }\n const entries = new Map(Object.entries(map));\n return {\n get(name = 'default') {\n const found = entries.get(name);\n if (!found) {\n throw new Error(\n `Unknown file storage '${name}'. Registered: ${[...entries.keys()].join(', ')}.`,\n );\n }\n return found;\n },\n has: (name) => entries.has(name),\n names: () => [...entries.keys()],\n hasFused: () => [...entries.values()].some((a) => a.pipeline === 'fused'),\n };\n}\n","import type { Plugin } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nimport type { FileStorageAdapter } from './types.js';\n\n/**\n * Declare the app's file storages as a named map, mirroring the existing\n * multi-data-provider convention. `default` is required; other keys are free\n * form and are what a field names in `f.file({ fileStorage: '…' })`.\n */\nexport function fileStoragePlugin(map: Record<string, FileStorageAdapter>): Plugin {\n const adapters = Object.freeze({ ...map });\n return {\n name: '@burojs/file-storage',\n enforce: 'pre',\n setup: (api) => {\n api.runtime.registerService(FILE_STORAGE_SERVICE, () => createFileStorageRegistry(adapters));\n },\n };\n}\n","import type { FileRef } from '../types.js';\n\nlet seq = 0;\n\n/**\n * Mint a ref with a fresh client-side identity.\n *\n * Deliberately a counter rather than `crypto.randomUUID()`: ids are\n * process-local and never persisted, and a counter keeps test output\n * readable and snapshots stable.\n */\nexport function newRef(init: Partial<FileRef> = {}): FileRef {\n seq += 1;\n return { localId: `ref-${seq}`, ...init };\n}\n","import type { FileRef } from './types.js';\n\n/**\n * Kind by mime; the filename is consulted only when mime is unknown.\n *\n * A pure predicate over `FileRef`, not a UI concern — it lives next to the\n * type it describes so any renderer, in `@burojs/ui-shadcn` or a future UI\n * kit, answers \"is this an image?\" the same way. Two independently\n * maintained copies (one per renderer) drift the moment one gains a mime\n * type or extension the other lacks — which is exactly the bug this\n * function replaces: the deleted `isLikelyImageUrl` regexed an extension out\n * of the stored string, which can never match a uuid or other opaque handle.\n */\nexport function isImage(ref: FileRef): boolean {\n if (ref.mime) return ref.mime.startsWith('image/');\n const name = ref.name ?? '';\n return /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(name);\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\ntype ScalarColumn = string | string[] | null | undefined;\ntype ScalarWriteValue = string | File | (string | File)[] | null;\n\n/**\n * The column IS the handle: a URL, an id, a key, a path, a filename. One\n * mapper covers all four because the difference between them is meaning, not\n * structure — and three names for one function would be three things to keep\n * in sync for no behavioural gain.\n */\nexport function scalarHandle(): FileMapper<ScalarColumn, ScalarWriteValue> {\n return {\n carriesPending: true,\n\n read(column: ScalarColumn): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((entry) => entry !== '' && entry != null)\n .map((entry) => newRef({ handle: entry, raw: entry, name: tail(entry) }));\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): ScalarWriteValue {\n // A ref without a handle has not landed yet. On an `immediate`/\n // `handshake`/`resumable` pipeline that only ever means \"still\n // uploading\" — nothing to write. On a `fused` pipeline it means the\n // bytes are riding the record mutation itself, and `pending` is the\n // File to carry forward: writing the raw File here (rather than\n // omitting the ref) is what lets `withFileStorage` pick it up and\n // encode it into the mutation. Omitting it — the previous behaviour —\n // silently discarded every file added to a fused field using this\n // mapper, which is exactly the pairing `inlineFileStorage` ships as\n // its default.\n const values = next\n .map((r) => r.handle ?? r.pending)\n .filter((v): v is string | File => v != null);\n if (ctx.multiple) return values;\n return values[0] ?? null;\n },\n };\n}\n\n/** Last path segment, used as a display name when the backend gives none. */\nfunction tail(value: string): string | undefined {\n const seg = value.split('/').pop();\n return seg && seg !== value ? seg : value || undefined;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface RichImageOpts {\n /**\n * Who owns the focal point.\n *\n * - `'usage'` — it lives on the reference inside the record, so the same\n * asset crops differently in two records (Sanity-shaped).\n * - `'file'` — it is a property of the file that the storage owns; the\n * mapper leaves it alone (Directus-shaped).\n * - `false` — this column has none.\n *\n * There is no default. Guessing wrong silently corrupts a neighbouring\n * record's crop, which is exactly the failure nobody notices for months.\n */\n focus: 'usage' | 'file' | false;\n /** Dotted path to the handle inside the object. Defaults to `'id'`. */\n handleKey?: string;\n /** Keys lifted into `usage` when `focus === 'usage'`. */\n usageKeys?: string[];\n}\n\nconst DEFAULT_USAGE_KEYS = ['hotspot', 'crop'];\n\ntype Obj = Record<string, unknown>;\n\nexport function richImage(opts: RichImageOpts): FileMapper<unknown, unknown> {\n const handleKey = opts.handleKey ?? 'id';\n const usageKeys = opts.usageKeys ?? DEFAULT_USAGE_KEYS;\n\n function readOne(entry: Obj): FileRef {\n const usage =\n opts.focus === 'usage'\n ? pick(\n entry,\n usageKeys.filter((k) => entry[k] !== undefined),\n )\n : undefined;\n return newRef({\n handle: asString(getPath(entry, handleKey)),\n raw: entry,\n name: asString(entry.name ?? entry.filename_download),\n mime: asString(entry.mime ?? entry.type),\n size: asNumber(entry.size ?? entry.filesize),\n width: asNumber(entry.width),\n height: asNumber(entry.height),\n ...(usage && Object.keys(usage).length > 0 ? { usage } : {}),\n });\n }\n\n return {\n read(column, ctx: MapperCtx): FileRef[] {\n if (column == null) return [];\n const list = (Array.isArray(column) ? column : [column]) as Obj[];\n return list.filter((e) => e && typeof e === 'object').map(readOne);\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): unknown {\n const out = next\n .filter((r) => r.handle != null)\n .map((r) => {\n // Merge ONTO the original entry. Rebuilding it would delete every\n // key this mapper does not model — and real objects carry many.\n const base: Obj = isObj(r.raw) ? { ...r.raw } : {};\n setPath(base, handleKey, r.handle as string);\n if (opts.focus === 'usage' && r.usage) Object.assign(base, r.usage);\n return base;\n });\n if (ctx.multiple) return out;\n return out[0] ?? null;\n },\n };\n}\n\nfunction isObj(v: unknown): v is Obj {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\nfunction asNumber(v: unknown): number | undefined {\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\nfunction pick(src: Obj, keys: string[]): Obj {\n const out: Obj = {};\n for (const k of keys) out[k] = src[k];\n return out;\n}\nfunction getPath(src: Obj, path: string): unknown {\n return path.split('.').reduce<unknown>((acc, k) => (isObj(acc) ? acc[k] : undefined), src);\n}\nfunction setPath(target: Obj, path: string, value: string): void {\n const keys = path.split('.');\n const last = keys.pop() as string;\n let cursor: Obj = target;\n for (const k of keys) {\n const nextLevel = cursor[k];\n // Clone rather than mutate: `raw` is shared with the value we were handed.\n cursor[k] = isObj(nextLevel) ? { ...nextLevel } : {};\n cursor = cursor[k] as Obj;\n }\n cursor[last] = value;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface JunctionOpts {\n /** Column on the junction row holding the file's handle. */\n fileKey: string;\n /** Column holding the 1-based position. Omit when the table is unordered. */\n sortKey?: string;\n}\n\ntype Row = Record<string, unknown>;\n\n/**\n * A column that is an array of junction ROWS rather than of files — Directus\n * M2M being the canonical case. Each row carries its own primary key, which\n * must survive a reorder: dropping it would delete and recreate every row.\n */\nexport function junction(opts: JunctionOpts): FileMapper<Row[] | null | undefined, Row[]> {\n return {\n read(column, _ctx: MapperCtx): FileRef[] {\n if (!Array.isArray(column)) return [];\n return column\n .filter((row) => row && typeof row === 'object')\n .map((row) => newRef({ handle: asString(row[opts.fileKey]), raw: row }));\n },\n\n write(next: FileRef[], _prev: FileRef[], _ctx: MapperCtx): Row[] {\n return next\n .filter((r) => r.handle != null)\n .map((r, i) => {\n const base: Row = r.raw && typeof r.raw === 'object' ? { ...(r.raw as Row) } : {};\n base[opts.fileKey] = r.handle as string;\n if (opts.sortKey) base[opts.sortKey] = i + 1;\n return base;\n });\n },\n };\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\n","import type { FileMapper, FileStorageAdapter, MapperCtx, ResolveCtx } from '../types.js';\n\nexport interface ContractOpts {\n /** A FRESH adapter per case — shared state between cases hides bugs. */\n adapter: () => FileStorageAdapter;\n mapper: FileMapper;\n /** Representative column values this mapper reads, for the invariants. */\n sampleColumn: { single: unknown; multiple: unknown };\n /** Skip upload cases for adapters whose bytes ride the mutation. */\n skipUpload?: boolean;\n /**\n * Set for a mapper whose `write` returns a COLUMN PATCH rather than a column\n * value — `pocketbaseDiff` is the one in this package.\n *\n * Round-trip identity is meaningless for such a mapper: it never returns the\n * value it was given, it returns the keys needed to change it. The patch\n * invariants below replace the round-trip cases rather than sitting beside\n * them, because running both would demand two contradictory things of one\n * `write`.\n */\n writesColumnPatch?: boolean;\n /**\n * Context every `resolveUrl` case is called with. Defaults to `{}`.\n *\n * Supply it for an adapter whose URL cannot be built from a handle alone.\n * A PocketBase filename is meaningless by itself — the URL is assembled from\n * collection + record + filename — so that adapter correctly THROWS on an\n * empty context, and the contract must hand it a real one rather than\n * treating the throw as a conformance failure.\n */\n resolveCtx?: ResolveCtx;\n}\n\n/**\n * The test runner, injected.\n *\n * `src/` may not import vitest: this package promises zero runtime\n * dependencies, and a module under `src/` importing a test framework ships\n * that import in the published bundle. Injection also lets an adopter on jest\n * or node:test run the same contract.\n */\nexport interface ContractRunner {\n it: (name: string, fn: () => void | Promise<void>) => void;\n // Structural, so any assertion library with these shapes fits. The return\n // type must be `unknown`, not `never`: `never` demands the SOURCE function\n // return `never` too (return types are checked covariantly), which no real\n // assertion library does — vitest's `ExpectStatic` returns `Assertion<T>`.\n // `unknown` still rejects a non-function value while accepting any return\n // shape, so this stays structural without over-promising.\n expect: (actual: never) => unknown;\n}\n\nconst single: MapperCtx = { resource: 'demo', field: 'file', multiple: false };\nconst many: MapperCtx = { resource: 'demo', field: 'files', multiple: true };\n\n/**\n * The conformance suite every adapter owes. Call inside a `describe`.\n *\n * Exported from the package so adopters can point it at their own adapter —\n * the claim \"any API can be stretched onto this port\" is only worth something\n * if it is executable by the person doing the stretching.\n */\nexport function runFileStorageContract(opts: ContractOpts, runner: ContractRunner): void {\n const { mapper } = opts;\n const it = runner.it;\n // biome-ignore lint/suspicious/noExplicitAny: structural assertion shim\n const expect = runner.expect as unknown as (actual: unknown) => any;\n\n it('declares a pipeline', () => {\n expect(['immediate', 'fused', 'handshake', 'resumable']).toContain(opts.adapter().pipeline);\n });\n\n it('ships a default mapper', () => {\n const m = opts.adapter().defaultMapper;\n expect(typeof m.read).toBe('function');\n expect(typeof m.write).toBe('function');\n });\n\n if (opts.writesColumnPatch) {\n it('emits an empty patch when nothing changed', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as { keys?: Record<string, unknown> };\n expect(Object.keys(out.keys ?? {}).length).toBe(0);\n });\n\n it('emits only patch keys, never the raw column value', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as Record<string, unknown>;\n expect(typeof out.keys).toBe('object');\n });\n } else {\n it('round-trips an untouched single value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.single, single);\n expect(mapper.write(refs, refs, single)).toEqual(opts.sampleColumn.single);\n });\n\n it('round-trips an untouched multiple value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(mapper.write(refs, refs, many)).toEqual(opts.sampleColumn.multiple);\n });\n }\n\n it('reads null as no files', () => {\n expect(mapper.read(null, single)).toEqual([]);\n });\n\n it('gives every ref a distinct localId', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(new Set(refs.map((r) => r.localId)).size).toBe(refs.length);\n });\n\n if (!opts.skipUpload) {\n it('reports progress and resolves to a ref carrying a handle', async () => {\n const adapter = opts.adapter();\n const seen: number[] = [];\n const ref = await adapter.upload(new File(['hello'], 'a.txt', { type: 'text/plain' }), {\n signal: new AbortController().signal,\n onProgress: (loaded) => seen.push(loaded),\n });\n expect(ref.handle).toBeTruthy();\n expect(seen.length).toBeGreaterThan(0);\n expect(seen.at(-1)).toBe(5);\n });\n\n it('rejects with an AbortError when the signal is already aborted', async () => {\n const adapter = opts.adapter();\n const ac = new AbortController();\n ac.abort();\n await expect(\n adapter.upload(new File(['x'], 'x.txt'), {\n signal: ac.signal,\n onProgress: () => {},\n }),\n ).rejects.toThrow(/abort/i);\n });\n\n it('accepts a retry after a failure', async () => {\n // A GENUINE, guaranteed failure — not \"whatever the first call happens\n // to do\" (the version this replaced called `upload` twice with fresh\n // signals and asserted `expect(first).toBeTruthy()`, where `first` was\n // always the string `'ok'` or `'failed'`; both are truthy, so the\n // assertion could not fail even if the adapter never actually failed\n // once). An already-aborted signal is guaranteed to reject (every\n // reference adapter's own \"rejects with an AbortError\" case above\n // proves it does), so this exercises a REAL failure and then proves the\n // adapter's internal state survived it well enough to accept a real\n // retry — no leftover lock, counter, or half-open request wedges the\n // next call.\n const adapter = opts.adapter();\n const file = new File(['x'], 'x.txt');\n const aborted = new AbortController();\n aborted.abort();\n await expect(\n adapter.upload(file, { signal: aborted.signal, onProgress: () => {} }),\n ).rejects.toThrow();\n const second = await adapter.upload(file, {\n signal: new AbortController().signal,\n onProgress: () => {},\n });\n expect(second.handle).toBeTruthy();\n });\n }\n\n it('resolves a URL for a ref', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n const url = typeof resolved === 'string' ? resolved : resolved.url;\n expect(url).toMatch(/^https?:\\/\\//);\n });\n\n it('either returns a permanent string or states a future expiry — never neither', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n if (typeof resolved === 'string') return;\n // The object arm means \"signed\". A signed URL with no deadline would be\n // cached forever, which is the exact failure this port exists to prevent,\n // so silence is a contract violation rather than a permitted default.\n expect(typeof resolved.expiresAt).toBe('number');\n expect(resolved.expiresAt).toBeGreaterThan(Date.now());\n });\n\n it('leaves unreportable metadata absent rather than guessing', async () => {\n const adapter = opts.adapter();\n if (!adapter.stat) return;\n const [out] = await adapter.stat([{ localId: 'x', handle: 'definitely-not-stored' }]);\n expect(out?.size).not.toBe(0);\n });\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type { FileRef, FileStorageAdapter, ResolveCtx, UploadCtx } from '../types.js';\n\nexport interface FakeAdapterOpts {\n /** Emit this many progress ticks before resolving. Default 2. */\n progressTicks?: number;\n /** Make every upload reject with this message. */\n failWith?: string;\n /** Return signed URLs that expire this many ms from resolution. */\n signedTtlMs?: number;\n /** Default `'immediate'`. Set `'fused'` to stand in for an inline/PocketBase-shaped storage. */\n pipeline?: 'immediate' | 'fused';\n}\n\n/**\n * In-memory adapter. Exists so the contract suite can be tested against\n * something known-correct, and so adopters have a baseline to diff against\n * when their own adapter fails a case.\n */\nexport function createFakeAdapter(opts: FakeAdapterOpts = {}): FileStorageAdapter & {\n stored: Map<string, File>;\n removed: string[];\n} {\n const stored = new Map<string, File>();\n const removed: string[] = [];\n let seq = 0;\n\n return {\n stored,\n removed,\n pipeline: opts.pipeline ?? 'immediate',\n defaultMapper: scalarHandle(),\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n const ticks = opts.progressTicks ?? 2;\n for (let i = 1; i <= ticks; i += 1) {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // Yield so an abort issued from a test lands between ticks.\n await Promise.resolve();\n ctx.onProgress(Math.round((file.size * i) / ticks), file.size);\n }\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n if (opts.failWith) throw new Error(opts.failWith);\n seq += 1;\n const handle = `fake-${seq}-${file.name}`;\n stored.set(handle, file);\n return newRef({ handle, name: file.name, mime: file.type || undefined, size: file.size });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx) {\n const q = ctx.variant?.width ? `?w=${ctx.variant.width}` : '';\n const url = `https://fake.test/${ref.handle}${q}`;\n if (opts.signedTtlMs == null) return url;\n return Promise.resolve({ url, expiresAt: Date.now() + opts.signedTtlMs });\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n return refs.map((r) => {\n const f = r.handle ? stored.get(r.handle) : undefined;\n // Honesty rule: report nothing rather than guess when the file is\n // unknown to this store.\n return f ? { ...r, name: f.name, size: f.size, mime: f.type || undefined } : r;\n });\n },\n\n async remove(ref: FileRef): Promise<void> {\n if (ref.handle) {\n stored.delete(ref.handle);\n removed.push(ref.handle);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n FusedEncoding,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface InlineFileStorageOpts {\n /**\n * How the data-provider wrapper should encode the bytes into the mutation.\n * `base64` transforms the value; `multipart` and `graphql-upload` set the\n * request body and are mutually exclusive with each other.\n */\n encoding: FusedEncoding;\n /**\n * How a saved file is read back. `ctx.resource` and `ctx.record` are given\n * because backends of this shape usually address files through the record\n * that owns them.\n */\n resolveUrl: (ref: FileRef, ctx: ResolveCtx) => ResolvedUrl | Promise<ResolvedUrl>;\n}\n\n/**\n * The storage that is not a storage: the backend takes the file inside the\n * record mutation, so there is no upload endpoint and no handle until the\n * mutation succeeds.\n *\n * `upload` deliberately does nothing but wrap the File in a ref. Progress here\n * belongs to the mutation, and it is `withFileStorage` — the only code on the\n * request path — that can report it.\n */\nexport function inlineFileStorage(opts: InlineFileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'fused',\n encoding: opts.encoding,\n defaultMapper: scalarHandle(),\n // Nothing was uploaded ahead of the save, so nothing can be orphaned.\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref, ctx) {\n return opts.resolveUrl(ref, ctx);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface NhostFileStorageOpts {\n /** Storage service base URL, e.g. `https://<project>.storage.<region>.nhost.run`. */\n baseUrl: string;\n /**\n * Access token supplier. Injected rather than read from localStorage: the\n * v0.2 implementation hardcoded a storage key and a window global, which\n * meant one app shape was supported and no test could drive it.\n */\n getToken: () => string | null | Promise<string | null>;\n bucketId?: string;\n orphans?: OrphanPolicy;\n fetchImpl?: typeof fetch;\n}\n\ninterface NhostFileMeta {\n id?: string;\n name?: string;\n size?: number;\n mimeType?: string;\n}\n\n/**\n * nhost storage: bytes go up first, the column keeps the file's UUID, and the\n * display URL is signed and short-lived.\n *\n * ⚠️ The handle is the id, NOT the presigned URL. Storing the presigned URL —\n * as v0.2 did — writes a link with a TTL into the database.\n */\nexport function nhostFileStorage(opts: NhostFileStorageOpts): FileStorageAdapter {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const base = opts.baseUrl.replace(/\\/+$/, '');\n\n async function authHeaders(): Promise<Record<string, string>> {\n const token = await opts.getToken();\n if (!token) throw new Error('nhostFileStorage: no access token — the user is not signed in');\n return { Authorization: `Bearer ${token}` };\n }\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const headers = await authHeaders();\n const fd = new FormData();\n fd.append('file[]', file);\n if (opts.bucketId) fd.append('bucket-id', opts.bucketId);\n\n // fetch has no upload progress; report the two endpoints we can honestly\n // report rather than faking a curve.\n ctx.onProgress(0, file.size);\n const res = await fetchImpl(`${base}/v1/files`, {\n method: 'POST',\n headers,\n body: fd,\n signal: ctx.signal,\n });\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`nhost upload failed: ${res.status} ${body}`.trim());\n }\n const json = (await res.json()) as NhostFileMeta | { processedFiles?: NhostFileMeta[] };\n const meta = 'processedFiles' in json ? json.processedFiles?.[0] : (json as NhostFileMeta);\n if (!meta?.id) throw new Error('nhost upload returned no file id');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: meta.id,\n name: meta.name ?? file.name,\n mime: meta.mimeType ?? file.type ?? undefined,\n size: meta.size ?? file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const headers = await authHeaders();\n const params = new URLSearchParams();\n if (ctx.variant?.width) params.set('w', String(ctx.variant.width));\n if (ctx.variant?.height) params.set('h', String(ctx.variant.height));\n const qs = params.toString();\n const res = await fetchImpl(\n `${base}/v1/files/${ref.handle}/presignedurl${qs ? `?${qs}` : ''}`,\n { headers },\n );\n if (!res.ok) throw new Error(`nhost presign failed: ${res.status}`);\n const json = (await res.json()) as { url?: string; expiration?: number };\n if (!json.url) throw new Error('nhost presign returned no url');\n return {\n url: json.url,\n // `expiration` is a lifetime in seconds; the cache wants an instant.\n expiresAt: Date.now() + (json.expiration ?? 1800) * 1000,\n };\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n const headers = await authHeaders();\n return Promise.all(\n refs.map(async (ref) => {\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'HEAD',\n headers,\n });\n if (!res.ok) return ref;\n // ⚠️ `headers.get` returns null for a missing header, and\n // `Number(null)` is 0 — which `Number.isFinite` happily accepts. Read\n // the header first and only convert when it is actually present, or a\n // file of unknown size reports as literally zero bytes and every\n // size column downstream believes it. That is the honesty rule\n // (`types.ts`: never guess, never substitute a zero).\n const rawSize = res.headers.get('content-length');\n const size = rawSize == null ? Number.NaN : Number(rawSize);\n const mime = res.headers.get('content-type') ?? undefined;\n return {\n ...ref,\n ...(Number.isFinite(size) ? { size } : {}),\n ...(mime ? { mime } : {}),\n };\n }),\n );\n },\n\n async remove(ref: FileRef): Promise<void> {\n const headers = await authHeaders();\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'DELETE',\n headers,\n });\n if (!res.ok && res.status !== 404) {\n throw new Error(`nhost delete failed: ${res.status}`);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * The slice of `@supabase/supabase-js` this adapter uses, declared\n * structurally so the package keeps zero runtime dependencies and the adapter\n * can be driven by a plain object in tests.\n */\nexport interface SupabaseStorageLike {\n storage: {\n from(bucket: string): {\n upload(\n path: string,\n file: File,\n opts?: { contentType?: string; upsert?: boolean },\n ): Promise<{ data: { path: string } | null; error: { message: string } | null }>;\n getPublicUrl(path: string): { data: { publicUrl: string } };\n createSignedUrl(\n path: string,\n expiresIn: number,\n ): Promise<{ data: { signedUrl: string } | null; error: { message: string } | null }>;\n remove(paths: string[]): Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n}\n\nexport interface SupabaseFileStorageOpts {\n client: SupabaseStorageLike;\n bucket: string;\n /** Public buckets yield permanent URLs; signed buckets yield expiring ones. */\n access: 'public' | 'signed';\n /** Lifetime of a signed URL in seconds. Default 3600. */\n signedTtlSec?: number;\n /** Prepended to every generated object path, e.g. `'avatars'`. */\n pathPrefix?: string;\n orphans?: OrphanPolicy;\n}\n\n/**\n * A per-upload path component that survives a module reload.\n *\n * ⚠️ A module-scoped counter is NOT enough here, even though one is fine for\n * `FileRef.localId`. `localId` is process-local and never persisted; this\n * string becomes the object's PATH IN THE BUCKET. A counter restarts at 0 on\n * every page load and on every serverless cold start, so a user who uploads\n * `photo.png`, reloads, and uploads it again generates the same path twice and\n * gets Supabase's `400 Asset Already Exists` — with `upsert` correctly off,\n * that is a hard failure rather than a silent overwrite.\n */\nfunction pathStamp(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return crypto.randomUUID().slice(0, 8);\n }\n // `padEnd` is not decoration: Math.random() may legally return 0, and\n // (0).toString(36).slice(2, 10) is the empty string — which would put every\n // upload of a given filename back on one colliding path, the exact bug this\n // function exists to prevent.\n return Math.random().toString(36).slice(2, 10).padEnd(8, '0');\n}\n\nexport function supabaseFileStorage(opts: SupabaseFileStorageOpts): FileStorageAdapter {\n const bucket = () => opts.client.storage.from(opts.bucket);\n const ttl = opts.signedTtlSec ?? 3600;\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // A fresh path per upload. Supabase's own guidance is to write to a new\n // path rather than to enable upsert: overwriting races against CDN\n // propagation and the last writer wins silently.\n const stamp = `${pathStamp()}-${file.name}`;\n const path = opts.pathPrefix ? `${opts.pathPrefix}/${stamp}` : stamp;\n\n ctx.onProgress(0, file.size);\n const { data, error } = await bucket().upload(path, file, {\n contentType: file.type || undefined,\n });\n if (error) throw new Error(`Supabase upload failed: ${error.message}`);\n if (!data) throw new Error('Supabase upload returned no path');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: data.path,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, _ctx: ResolveCtx): Promise<ResolvedUrl> {\n const path = ref.handle as string;\n if (opts.access === 'public') {\n // Permanent: a plain string, so it may be cached and server-rendered.\n return bucket().getPublicUrl(path).data.publicUrl;\n }\n const { data, error } = await bucket().createSignedUrl(path, ttl);\n if (error) throw new Error(`Supabase sign failed: ${error.message}`);\n if (!data) throw new Error('Supabase sign returned no url');\n return { url: data.signedUrl, expiresAt: Date.now() + ttl * 1000 };\n },\n\n async remove(ref: FileRef): Promise<void> {\n const { error } = await bucket().remove([ref.handle as string]);\n if (error) throw new Error(`Supabase remove failed: ${error.message}`);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/** What the adopter's signing endpoint hands back. */\nexport interface SignedTarget {\n /**\n * The object key. **The server chooses it, not the client** — letting the\n * browser name the object is how buckets get overwritten by whoever asks.\n */\n key: string;\n url: string;\n method?: 'PUT' | 'POST';\n /** Headers that were part of the signature. Sent verbatim. */\n headers?: Record<string, string>;\n /** POST-policy form fields. Sent before the file part. */\n fields?: Record<string, string>;\n}\n\nexport interface S3FileStorageOpts {\n /** Ask your backend to sign. Authorization happens there, not here. */\n sign: (file: { name: string; type: string; size: number }) => Promise<SignedTarget>;\n /** CDN or bucket origin for permanent read URLs. */\n publicBaseUrl?: string;\n /** Supply for private buckets; return an expiring URL. */\n signView?: (key: string, ctx: ResolveCtx) => Promise<ResolvedUrl>;\n remove?: (key: string) => Promise<void>;\n orphans?: OrphanPolicy;\n /** Injected for tests. Defaults to `new XMLHttpRequest()`. */\n xhrImpl?: () => XMLHttpRequest;\n}\n\n/**\n * Direct-to-bucket upload behind a signed handshake: ask the server to sign,\n * send the bytes straight to storage, keep the key.\n *\n * Uses XHR rather than fetch on purpose — browsers expose no upload progress\n * on fetch, and this is the pipeline whose files are large enough for the\n * absence to be felt.\n */\nexport function s3FileStorage(opts: S3FileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'handshake',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n // Check BEFORE signing, like the sibling adapters do. Signing is a round\n // trip to the adopter's own backend; an upload the user already\n // cancelled has no business making it.\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const target = await opts.sign({ name: file.name, type: file.type, size: file.size });\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n\n const xhr = (opts.xhrImpl ?? (() => new XMLHttpRequest()))();\n const method = target.method ?? 'PUT';\n\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => xhr.abort();\n ctx.signal.addEventListener('abort', onAbort, { once: true });\n\n xhr.upload.addEventListener('progress', (e) => {\n const ev = e as ProgressEvent;\n ctx.onProgress(ev.loaded, ev.total || file.size);\n });\n xhr.addEventListener('load', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n if (xhr.status >= 200 && xhr.status < 300) {\n ctx.onProgress(file.size, file.size);\n resolve();\n } else reject(new Error(`S3 upload failed: ${xhr.status}`));\n });\n xhr.addEventListener('error', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n reject(new Error('S3 upload failed: network error'));\n });\n xhr.addEventListener('abort', () => {\n reject(new DOMException('Aborted', 'AbortError'));\n });\n\n xhr.open(method, target.url);\n for (const [k, v] of Object.entries(target.headers ?? {})) {\n xhr.setRequestHeader(k, v);\n }\n\n if (method === 'POST') {\n const fd = new FormData();\n for (const [k, v] of Object.entries(target.fields ?? {})) fd.append(k, v);\n // ⚠️ The file part goes LAST: S3 ignores every field that follows it.\n fd.append('file', file);\n xhr.send(fd);\n } else {\n xhr.send(file);\n }\n });\n\n return newRef({\n handle: target.key,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx): ResolvedUrl | Promise<ResolvedUrl> {\n const key = ref.handle as string;\n if (opts.signView) return opts.signView(key, ctx);\n if (opts.publicBaseUrl) return `${opts.publicBaseUrl.replace(/\\/+$/, '')}/${key}`;\n throw new Error(\n 's3FileStorage: pass publicBaseUrl for a public bucket or signView for a private one',\n );\n },\n\n ...(opts.remove\n ? {\n async remove(ref: FileRef): Promise<void> {\n await opts.remove?.(ref.handle as string);\n },\n }\n : {}),\n };\n}\n","/**\n * A mapper's answer when the change cannot be expressed as \"the new value of\n * this column\".\n *\n * PocketBase updates a multi-value file field through SIBLING keys — `docs+`\n * to append, `docs-` to remove named files — so there is no single value to\n * return. Rather than special-casing one backend inside the write path, a\n * mapper may return this marker and `withFileStorage` splices its keys into\n * the payload instead of assigning to the field's own key.\n */\nexport const COLUMN_PATCH = Symbol.for('buro.fileStorage.columnPatch');\n\nexport interface ColumnPatch {\n [COLUMN_PATCH]: true;\n keys: Record<string, unknown>;\n}\n\nexport function columnPatch(keys: Record<string, unknown>): ColumnPatch {\n return { [COLUMN_PATCH]: true, keys };\n}\n\nexport function isColumnPatch(value: unknown): value is ColumnPatch {\n return typeof value === 'object' && value !== null && COLUMN_PATCH in value;\n}\n","import { columnPatch } from '../mappers/column-patch.js';\nimport { newRef } from '../mappers/ref.js';\nimport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n MapperCtx,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * PocketBase stores a filename per file and updates multi-value fields by\n * DIFF: `field+` appends, `field-` removes by name. Single-value fields are\n * replaced outright, and cleared with an empty string.\n */\nexport function pocketbaseDiff(): FileMapper<string | string[] | null | undefined, unknown> {\n return {\n carriesPending: true,\n\n read(column): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((n) => typeof n === 'string' && n !== '')\n .map((name) => newRef({ handle: name, raw: name, name }));\n },\n\n write(next: FileRef[], prev: FileRef[], ctx: MapperCtx): unknown {\n const added = next.filter((r) => r.pending).map((r) => r.pending as File);\n\n if (!ctx.multiple) {\n // No diff protocol for single-value fields — assign or clear.\n if (added.length > 0) return columnPatch({ [ctx.field]: added[0] as File });\n const kept = next.find((r) => r.handle);\n if (kept) return columnPatch({});\n return prev.length > 0 ? columnPatch({ [ctx.field]: '' }) : columnPatch({});\n }\n\n const keptHandles = new Set(next.filter((r) => r.handle).map((r) => r.handle as string));\n const removed = prev\n .map((r) => r.handle)\n .filter((h): h is string => h != null && !keptHandles.has(h));\n\n const keys: Record<string, unknown> = {};\n if (added.length > 0) keys[`${ctx.field}+`] = added;\n if (removed.length > 0) keys[`${ctx.field}-`] = removed;\n return columnPatch(keys);\n },\n };\n}\n\nexport interface PocketbaseFileStorageOpts {\n /** PocketBase origin, e.g. `http://127.0.0.1:8090`. */\n baseUrl: string;\n /** Set when the file field is marked Protected in the collection schema. */\n protectedFiles?: boolean;\n /** Supplies a short-lived file token — `pb.files.getToken()`. */\n getFileToken?: () => Promise<string>;\n /** Lifetime assumed for a file token, in seconds. Default 120. */\n tokenTtlSec?: number;\n}\n\n/**\n * PocketBase: bytes ride the record mutation as multipart, the column keeps a\n * filename that only means anything alongside its record, images get thumbs\n * from a query parameter, and protected files need a short-lived token.\n *\n * It is the hardest of the four reference pipelines, which is why it is the\n * one the example app runs against.\n */\nexport function pocketbaseFileStorage(opts: PocketbaseFileStorageOpts): FileStorageAdapter {\n const base = opts.baseUrl.replace(/\\/+$/, '');\n const ttl = opts.tokenTtlSec ?? 120;\n\n return {\n pipeline: 'fused',\n encoding: 'multipart',\n defaultMapper: pocketbaseDiff(),\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n // Fused: hold the bytes for the mutation.\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const recordId = (ctx.record as { id?: string } | undefined)?.id;\n if (!ctx.resource || !recordId) {\n throw new Error(\n 'pocketbaseFileStorage: a file URL needs its collection and record id — ' +\n 'the filename alone does not identify a file in PocketBase.',\n );\n }\n const params = new URLSearchParams();\n if (ctx.variant?.width || ctx.variant?.height) {\n const w = ctx.variant.width ?? 0;\n const h = ctx.variant.height ?? 0;\n // `f` fits inside the box instead of cropping to it.\n params.set('thumb', `${w}x${h}f`);\n }\n if (ctx.disposition === 'attachment') params.set('download', '1');\n\n if (opts.protectedFiles) {\n if (!opts.getFileToken) {\n throw new Error(\n 'pocketbaseFileStorage: protectedFiles is set but getFileToken was not supplied',\n );\n }\n const token = await opts.getFileToken();\n params.set('token', token);\n const qs = params.toString();\n return {\n url: `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`,\n expiresAt: Date.now() + ttl * 1000,\n };\n }\n\n const qs = params.toString();\n return `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`;\n },\n };\n}\n","import { isColumnPatch } from '../mappers/column-patch.js';\nimport { type EncodingPlan, encodingFor } from './with-file-storage.js';\n\n/**\n * Splice column patches into the payload; leave everything else alone.\n *\n * Reference identity is preserved ONLY when the payload carries no column\n * patch at all. The moment even an EMPTY patch (`{ keys: {} }` — \"nothing\n * changed for this field\") is present, the field's own key must still be\n * dropped: forwarding the raw patch marker as the field's value would\n * overwrite the column with `{ keys: {} }` instead of leaving it alone. So a\n * fresh object is built whenever any patch is present, empty or not — this\n * function does not claim otherwise.\n */\nexport function splicePatches(variables: Record<string, unknown>): Record<string, unknown> {\n let touched = false;\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (isColumnPatch(value)) {\n touched = true;\n Object.assign(out, value.keys);\n continue;\n }\n out[key] = value;\n }\n return touched ? out : variables;\n}\n\n/**\n * Does this payload actually carry bytes? Everything downstream hinges on it.\n *\n * ⚠️ No depth cap. An earlier version stopped at four levels, which meant a\n * File nested deeper was silently declared absent — the payload then took the\n * untouched fast path and the File reached `JSON.stringify` and became `{}`.\n * Today's mappers never nest a File below the first level, so the cap was\n * unreachable; a future rich-content mapper would have made it a silent\n * data-loss bug. Cycles are handled by tracking visited objects rather than by\n * limiting depth.\n */\nexport function containsFile(value: unknown, seen = new WeakSet<object>()): boolean {\n if (typeof File !== 'undefined' && value instanceof File) return true;\n if (!value || typeof value !== 'object') return false;\n if (seen.has(value)) return false;\n seen.add(value);\n if (Array.isArray(value)) return value.some((v) => containsFile(v, seen));\n return Object.values(value as Record<string, unknown>).some((v) => containsFile(v, seen));\n}\n\n/** Getter-safe recursive check used to reject unsupported bulk column patches. */\nexport function containsColumnPatch(value: unknown, seen = new WeakSet<object>()): boolean {\n if (!value || typeof value !== 'object') return false;\n try {\n if (isColumnPatch(value)) return true;\n if (seen.has(value)) return false;\n seen.add(value);\n const descriptors = Object.getOwnPropertyDescriptors(value);\n return Object.entries(descriptors).some(\n ([key, descriptor]) =>\n key !== 'length' && 'value' in descriptor && containsColumnPatch(descriptor.value, seen),\n );\n } catch {\n return false;\n }\n}\n\nasync function toDataUrl(file: File): Promise<string> {\n const buf = await file.arrayBuffer();\n const bytes = new Uint8Array(buf);\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n const b64 = typeof btoa === 'function' ? btoa(binary) : Buffer.from(bytes).toString('base64');\n return `data:${file.type || 'application/octet-stream'};base64,${b64}`;\n}\n\n/**\n * Turn a payload carrying Files into something the provider can send.\n *\n * `base64` is a VALUE transform, so the payload stays a plain object and\n * composes with anything. `multipart` and `graphql-upload` produce a FormData\n * — the provider must send it as the body verbatim and must NOT set a\n * content-type, because the browser writes the boundary.\n */\nexport async function encodePayload(\n variables: Record<string, unknown>,\n plan: EncodingPlan,\n): Promise<Record<string, unknown> | FormData> {\n // base64 first: it is a VALUE transform, so those Files stop being Files\n // before anything decides what the request body looks like.\n const staged: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (encodingFor(key, plan) !== 'base64') {\n staged[key] = value;\n continue;\n }\n if (value instanceof File) staged[key] = await toDataUrl(value);\n else if (Array.isArray(value)) {\n staged[key] = await Promise.all(\n value.map((v) => (v instanceof File ? toDataUrl(v) : Promise.resolve(v))),\n );\n } else staged[key] = value;\n }\n\n // Nothing left to carry in the body — keep it a plain JSON payload.\n if (!containsFile(staged)) return staged;\n\n const encoding = plan.bodyEncoding;\n const fd = new FormData();\n\n // ⚠️ Both loops below read `staged`, not `variables`. A base64-designated\n // key already holds its data-URL STRING in `staged` at this point — reading\n // `variables` here would re-discover the original File and append it to the\n // FormData as raw binary, silently undoing the base64 conversion the moment\n // any OTHER field on the same call needs a body encoding. That is the exact\n // failure this plan revision exists to close; it must not reopen here.\n if (encoding === 'graphql-upload') {\n // multipart request spec: `operations` + `map` first, then the file parts.\n const files: File[] = [];\n const map: Record<string, string[]> = {};\n const scrubbed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) {\n map[String(files.length)] = [`variables.${key}`];\n files.push(value);\n scrubbed[key] = null;\n } else scrubbed[key] = value;\n }\n fd.append('operations', JSON.stringify({ variables: scrubbed }));\n fd.append('map', JSON.stringify(map));\n files.forEach((f, i) => {\n fd.append(String(i), f);\n });\n return fd;\n }\n\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) fd.append(key, value);\n else if (Array.isArray(value)) {\n for (const item of value) {\n fd.append(key, item instanceof File ? item : String(item));\n }\n } else if (value == null) fd.append(key, '');\n else if (typeof value === 'object') fd.append(key, JSON.stringify(value));\n else fd.append(key, String(value));\n }\n return fd;\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { containsColumnPatch, containsFile, encodePayload, splicePatches } from './encode.js';\n\n/** Stamped on a wrapped provider so the wiring assertion can see it. */\nexport const FILE_STORAGE_WRAPPED = Symbol.for('buro.fileStorage.wrapped');\n\nconst MUTATING = new Set(['create', 'update', 'createMany', 'updateMany']);\n\nfunction isProviderMap(value: unknown): value is Record<string, Record<string, unknown>> {\n if (!value || typeof value !== 'object') return false;\n const entries = Object.values(value as Record<string, unknown>);\n return entries.length > 0 && entries.every((v) => v && typeof v === 'object' && !('then' in v));\n}\n\n/**\n * Rewrite outgoing mutations that actually carry bytes.\n *\n * The decision is PER REQUEST, from the payload — never global. Mixing\n * pipelines is the ordinary case (a storage service for most fields, two\n * places that take the file inline), so a payload with no File is handed to\n * the underlying provider untouched, by reference: no FormData, no\n * re-encoding, no surprise.\n */\nexport function withFileStorage<T>(provider: T, app: AppRuntime): T {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) {\n throw new Error(\n 'withFileStorage: no file storage registered — add fileStoragePlugin({...}) to defineApp',\n );\n }\n\n // Wrap a map of providers entry by entry, so `meta.dataProviderName` keeps\n // composing with file fields.\n if (isProviderMap(provider) && !(provider as Record<string, unknown>).getList) {\n const out: Record<string, unknown> = {};\n for (const [name, entry] of Object.entries(provider as Record<string, unknown>)) {\n out[name] = withFileStorage(entry, app);\n }\n Object.defineProperty(out, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return out as T;\n }\n\n const source = provider as Record<string, unknown>;\n const wrapped: Record<string, unknown> = Object.create(\n Object.getPrototypeOf(source) as object | null,\n );\n\n for (const key of allKeys(source)) {\n const value = source[key];\n if (typeof value !== 'function' || !MUTATING.has(key)) {\n wrapped[key] = value;\n continue;\n }\n wrapped[key] = async (args: Record<string, unknown>) => {\n const variables = args?.variables;\n if (!variables || typeof variables !== 'object' || variables instanceof FormData) {\n return (value as (a: unknown) => unknown)(args);\n }\n if (\n (key === 'createMany' || key === 'updateMany') &&\n (containsFile(variables) || containsColumnPatch(variables))\n ) {\n throw new Error(\n `withFileStorage: bulk file mutations are not supported for ${key}; submit one record at a time so FileMapper patches and request-body encoding stay unambiguous.`,\n );\n }\n // `createMany` carries an array of complete records. With no File\n // or ColumnPatch there is nothing for this wrapper to do, and treating\n // the array as a record would silently turn it into an object keyed by\n // \"0\", \"1\", ... . Preserve the provider-compatible array verbatim.\n if (Array.isArray(variables)) return (value as (a: unknown) => unknown)(args);\n const spliced = splicePatches(variables as Record<string, unknown>);\n if (!containsFile(spliced)) {\n // Untouched — pass the ORIGINAL object so identity is preserved.\n const passthrough = spliced === variables ? args : { ...args, variables: spliced };\n return (value as (a: unknown) => unknown)(passthrough);\n }\n // Per RESOURCE, not per provider — see planFor.\n const encoded = await encodePayload(spliced, planFor(app, registry, String(args.resource)));\n return (value as (a: unknown) => unknown)({ ...args, variables: encoded });\n };\n }\n\n Object.defineProperty(wrapped, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return wrapped as T;\n}\n\nfunction allKeys(source: Record<string, unknown>): string[] {\n const keys = new Set(Object.keys(source));\n const proto = Object.getPrototypeOf(source) as object | null;\n if (proto && proto !== Object.prototype) {\n for (const k of Object.getOwnPropertyNames(proto)) if (k !== 'constructor') keys.add(k);\n }\n return [...keys];\n}\n\n/**\n * Which encoding each payload key needs, and which body encoding the rest\n * fall back to — for ONE resource.\n *\n * ⚠️ This must be resolved per resource, not once per provider. An earlier\n * version picked a single encoding from the whole registry, so an app with a\n * multipart storage anywhere and a base64 storage anywhere else sent the\n * base64 field's File into FormData as raw binary — never encoded, no boot\n * error, no runtime error, silently wrong data. It also contradicted the\n * type's own promise that base64 \"composes with anything\", which is exactly\n * why `assertFileStorageWiring` permits that combination.\n *\n * `base64` is a VALUE transform: those fields are converted first and stop\n * being Files. Only what remains decides the request body.\n */\nexport interface EncodingPlan {\n /** Encoding EXPLICITLY declared for a field, via `f.file({ fileStorage })`. Authoritative. */\n knownEncodings: Map<string, FusedEncoding>;\n /**\n * Assumed for a payload key with NO declared field to consult — the\n * resource isn't registered, or has no `f.file()` for this key. Mirrors\n * `ui.fileStorage ?? 'default'`, the exact resolution a KNOWN field with no\n * explicit storage name already gets, extended to \"no field info at all\".\n * This is what keeps a single-storage app working without ever having to\n * declare its fields through `@burojs/field-types`.\n */\n defaultEncoding: FusedEncoding;\n /** Body encoding for whatever Files remain once base64 keys are converted. */\n bodyEncoding: Exclude<FusedEncoding, 'base64'>;\n}\n\n/** `docs+` / `docs-` are patch keys for the field `docs`. */\nexport function fieldOfKey(key: string): string {\n return key.endsWith('+') || key.endsWith('-') ? key.slice(0, -1) : key;\n}\n\n/** The encoding one payload key resolves to: its field's, or the plan's default. */\nexport function encodingFor(key: string, plan: EncodingPlan): FusedEncoding {\n return plan.knownEncodings.get(fieldOfKey(key)) ?? plan.defaultEncoding;\n}\n\nfunction planFor(app: AppRuntime, registry: FileStorageRegistry, resource: string): EncodingPlan {\n const knownEncodings = new Map<string, FusedEncoding>();\n\n const defaultAdapter = registry.has('default') ? registry.get('default') : undefined;\n const defaultEncoding: FusedEncoding =\n defaultAdapter?.pipeline === 'fused' && defaultAdapter.encoding\n ? defaultAdapter.encoding\n : 'multipart';\n let bodyEncoding: Exclude<FusedEncoding, 'base64'> =\n defaultEncoding === 'base64' ? 'multipart' : defaultEncoding;\n\n const def = app.registry.get(resource);\n for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {\n const ui = (field as { ui?: { component?: string; fileStorage?: string } }).ui;\n if (ui?.component !== 'file') continue;\n if (!registry.has(ui.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui.fileStorage);\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n knownEncodings.set(fieldName, adapter.encoding);\n if (adapter.encoding !== 'base64') bodyEncoding = adapter.encoding;\n }\n return { knownEncodings, defaultEncoding, bodyEncoding };\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileMapper, FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { FILE_STORAGE_WRAPPED } from './with-file-storage.js';\n\n/** Encodings that claim the request body, and so cannot share one request. */\nconst BODY_ENCODINGS: FusedEncoding[] = ['multipart', 'graphql-upload'];\n\n/**\n * Three checks that must fail at boot rather than at runtime.\n *\n * 1. A fused storage with an unwrapped provider. Files in `variables` reach\n * `JSON.stringify` and become `{}` — the upload silently does nothing, the\n * request succeeds, and nobody finds out until someone opens the record.\n * 2. A fused storage paired with a mapper that cannot carry a `pending` file\n * forward (`mapper.carriesPending` is not set). A ref on a fused pipeline\n * never has a `handle` before the mutation runs — only `pending` — so a\n * mapper that writes nothing for a handle-less ref (the ordinary case:\n * `richImage`, `junction`) silently drops every file added through that\n * field. This is the same class of failure as #1 — a fused storage whose\n * mapper cannot carry the file forward is exactly as broken as one with no\n * wrapper — so it gets the same treatment: fail loudly before any user\n * touches the form, not discover it when a saved record turns out empty.\n * 3. Two incompatible body encodings on one resource. One request has one\n * body; discovering that during a save is discovering it too late.\n *\n * Called by the host composition layer that owns both the app and its data\n * provider.\n */\nexport function assertFileStorageWiring(app: AppRuntime, provider: unknown): void {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) return;\n\n if (registry.hasFused()) {\n const stamped =\n provider != null &&\n (typeof provider === 'object' || typeof provider === 'function') &&\n FILE_STORAGE_WRAPPED in (provider as object);\n if (!stamped) {\n throw new Error(\n 'A fused file storage is registered, but the data provider is not wrapped. ' +\n 'Files ride inside the record mutation for this pipeline, so an unwrapped ' +\n 'provider will JSON.stringify them into `{}` and lose them silently. ' +\n 'Wrap it once: dataProvider: withFileStorage(myDataProvider, app)',\n );\n }\n }\n\n for (const def of app.registry.list()) {\n const seen = new Map<FusedEncoding, string>();\n for (const [fieldName, field] of Object.entries(def.fields ?? {})) {\n const ui = (\n field as {\n ui?: { component?: string; fileStorage?: string; mapper?: unknown };\n }\n ).ui;\n if (ui?.component !== 'file') continue;\n if (!registry.has(ui.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui.fileStorage);\n\n if (adapter.pipeline === 'fused') {\n const mapper = (ui.mapper as FileMapper | undefined) ?? adapter.defaultMapper;\n if (!mapper.carriesPending) {\n throw new Error(\n `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 })\\`.`,\n );\n }\n }\n\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;\n const previous = seen.get(adapter.encoding);\n if (previous == null) seen.set(adapter.encoding, fieldName);\n if (seen.size > 1) {\n const entries = [...seen.entries()];\n const [encA, fieldA] = entries[0] as [FusedEncoding, string];\n const [encB, fieldB] = entries[1] as [FusedEncoding, string];\n throw new Error(\n `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.`,\n );\n }\n }\n }\n}\n","const FILE_MAPPER_BASELINE_CARRIER = Symbol.for('buro.fileMapper.baselineCarrier');\n\n/**\n * Copy the opaque mapped-file baseline capability from a decoded record to a\n * fresh edit payload. The physical baseline itself remains private to the\n * provider wrapper; this helper can neither inspect nor serialize it.\n */\nexport function inheritFileMapperBaseline<T extends object>(source: object, target: T): T {\n let descriptor: PropertyDescriptor | undefined;\n try {\n descriptor = Object.getOwnPropertyDescriptor(source, FILE_MAPPER_BASELINE_CARRIER);\n } catch {\n return target;\n }\n if (\n descriptor === undefined ||\n !('value' in descriptor) ||\n descriptor.value === null ||\n typeof descriptor.value !== 'object'\n ) {\n return target;\n }\n Object.defineProperty(target, FILE_MAPPER_BASELINE_CARRIER, {\n value: descriptor.value,\n enumerable: false,\n configurable: true,\n writable: false,\n });\n return target;\n}\n","export const PACKAGE_NAME = '@burojs/file-storage' as const;\n\nexport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n FileStorageRegistry,\n FusedEncoding,\n MapperCtx,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n UploadPipeline,\n} from './types.js';\nexport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nexport { fileStoragePlugin } from './plugin.js';\nexport { newRef } from './mappers/ref.js';\nexport { isImage } from './kind.js';\nexport { scalarHandle } from './mappers/scalar-handle.js';\nexport { richImage, type RichImageOpts } from './mappers/rich-image.js';\nexport { junction, type JunctionOpts } from './mappers/junction.js';\nexport {\n runFileStorageContract,\n type ContractOpts,\n type ContractRunner,\n} from './contract/run-contract.js';\nexport { createFakeAdapter, type FakeAdapterOpts } from './contract/fake-adapter.js';\nexport { inlineFileStorage, type InlineFileStorageOpts } from './adapters/inline.js';\nexport { nhostFileStorage, type NhostFileStorageOpts } from './adapters/nhost.js';\nexport {\n supabaseFileStorage,\n type SupabaseFileStorageOpts,\n type SupabaseStorageLike,\n} from './adapters/supabase.js';\nexport { s3FileStorage, type S3FileStorageOpts, type SignedTarget } from './adapters/s3.js';\nexport {\n pocketbaseDiff,\n pocketbaseFileStorage,\n type PocketbaseFileStorageOpts,\n} from './adapters/pocketbase.js';\nexport {\n COLUMN_PATCH,\n columnPatch,\n isColumnPatch,\n type ColumnPatch,\n} from './mappers/column-patch.js';\nexport { FILE_STORAGE_WRAPPED, withFileStorage } from './wrapper/with-file-storage.js';\nexport { assertFileStorageWiring } from './wrapper/assert-wiring.js';\nexport {\n containsColumnPatch,\n containsFile,\n encodePayload,\n splicePatches,\n} from './wrapper/encode.js';\nexport { inheritFileMapperBaseline } from './baseline-carrier.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["asString"],"sources":["../src/registry.ts","../src/plugin.ts","../src/mappers/ref.ts","../src/kind.ts","../src/mappers/scalar-handle.ts","../src/mappers/rich-image.ts","../src/mappers/junction.ts","../src/contract/run-contract.ts","../src/contract/fake-adapter.ts","../src/adapters/inline.ts","../src/adapters/nhost.ts","../src/adapters/supabase.ts","../src/adapters/s3.ts","../src/mappers/column-patch.ts","../src/adapters/pocketbase.ts","../src/wrapper/encode.ts","../src/wrapper/with-file-storage.ts","../src/wrapper/assert-wiring.ts","../src/baseline-carrier.ts","../src/index.ts"],"sourcesContent":["import type { FileStorageAdapter, FileStorageRegistry } from './types.js';\n\n/** Service name under which the registry is registered on the app. */\nexport const FILE_STORAGE_SERVICE = 'fileStorage' as const;\n\nexport function createFileStorageRegistry(\n map: Record<string, FileStorageAdapter>,\n): FileStorageRegistry {\n if (!map.default) {\n throw new Error(\n \"fileStoragePlugin requires a 'default' entry — every file field that \" +\n 'does not name a storage resolves to it.',\n );\n }\n const entries = new Map(Object.entries(map));\n return {\n get(name = 'default') {\n const found = entries.get(name);\n if (!found) {\n throw new Error(\n `Unknown file storage '${name}'. Registered: ${[...entries.keys()].join(', ')}.`,\n );\n }\n return found;\n },\n has: (name) => entries.has(name),\n names: () => [...entries.keys()],\n hasFused: () => [...entries.values()].some((a) => a.pipeline === 'fused'),\n };\n}\n","import type { Plugin } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nimport type { FileStorageAdapter } from './types.js';\n\n/**\n * Declare the app's file storages as a named map, mirroring the existing\n * multi-data-provider convention. `default` is required; other keys are free\n * form and are what a field names in `f.file({ fileStorage: '…' })`.\n */\nexport function fileStoragePlugin(map: Record<string, FileStorageAdapter>): Plugin {\n const adapters = Object.freeze({ ...map });\n return {\n name: '@burojs/file-storage',\n enforce: 'pre',\n setup: (api) => {\n api.runtime.registerService(FILE_STORAGE_SERVICE, () => createFileStorageRegistry(adapters));\n },\n };\n}\n","import type { FileRef } from '../types.js';\n\nlet seq = 0;\n\n/**\n * Mint a ref with a fresh client-side identity.\n *\n * Deliberately a counter rather than `crypto.randomUUID()`: ids are\n * process-local and never persisted, and a counter keeps test output\n * readable and snapshots stable.\n */\nexport function newRef(init: Partial<FileRef> = {}): FileRef {\n seq += 1;\n return { localId: `ref-${seq}`, ...init };\n}\n","import type { FileRef } from './types.js';\n\n/**\n * Kind by mime; the filename is consulted only when mime is unknown.\n *\n * A pure predicate over `FileRef`, not a UI concern — it lives next to the\n * type it describes so any renderer, in `@burojs/ui-shadcn` or a future UI\n * kit, answers \"is this an image?\" the same way. Two independently\n * maintained copies (one per renderer) drift the moment one gains a mime\n * type or extension the other lacks — which is exactly the bug this\n * function replaces: the deleted `isLikelyImageUrl` regexed an extension out\n * of the stored string, which can never match a uuid or other opaque handle.\n */\nexport function isImage(ref: FileRef): boolean {\n if (ref.mime) return ref.mime.startsWith('image/');\n const name = ref.name ?? '';\n return /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(name);\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\ntype ScalarColumn = string | string[] | null | undefined;\ntype ScalarWriteValue = string | File | (string | File)[] | null;\n\n/**\n * The column IS the handle: a URL, an id, a key, a path, a filename. One\n * mapper covers all four because the difference between them is meaning, not\n * structure — and three names for one function would be three things to keep\n * in sync for no behavioural gain.\n */\nexport function scalarHandle(): FileMapper<ScalarColumn, ScalarWriteValue> {\n return {\n carriesPending: true,\n\n read(column: ScalarColumn): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((entry) => entry !== '' && entry != null)\n .map((entry) => newRef({ handle: entry, raw: entry, name: tail(entry) }));\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): ScalarWriteValue {\n // A ref without a handle has not landed yet. On an `immediate`/\n // `handshake`/`resumable` pipeline that only ever means \"still\n // uploading\" — nothing to write. On a `fused` pipeline it means the\n // bytes are riding the record mutation itself, and `pending` is the\n // File to carry forward: writing the raw File here (rather than\n // omitting the ref) is what lets `withFileStorage` pick it up and\n // encode it into the mutation. Omitting it — the previous behaviour —\n // silently discarded every file added to a fused field using this\n // mapper, which is exactly the pairing `inlineFileStorage` ships as\n // its default.\n const values = next\n .map((r) => r.handle ?? r.pending)\n .filter((v): v is string | File => v != null);\n if (ctx.multiple) return values;\n return values[0] ?? null;\n },\n };\n}\n\n/** Last path segment, used as a display name when the backend gives none. */\nfunction tail(value: string): string | undefined {\n const seg = value.split('/').pop();\n return seg && seg !== value ? seg : value || undefined;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface RichImageOpts {\n /**\n * Who owns the focal point.\n *\n * - `'usage'` — it lives on the reference inside the record, so the same\n * asset crops differently in two records (Sanity-shaped).\n * - `'file'` — it is a property of the file that the storage owns; the\n * mapper leaves it alone (Directus-shaped).\n * - `false` — this column has none.\n *\n * There is no default. Guessing wrong silently corrupts a neighbouring\n * record's crop, which is exactly the failure nobody notices for months.\n */\n focus: 'usage' | 'file' | false;\n /** Dotted path to the handle inside the object. Defaults to `'id'`. */\n handleKey?: string;\n /** Keys lifted into `usage` when `focus === 'usage'`. */\n usageKeys?: string[];\n}\n\nconst DEFAULT_USAGE_KEYS = ['hotspot', 'crop'];\n\ntype Obj = Record<string, unknown>;\n\nexport function richImage(opts: RichImageOpts): FileMapper<unknown, unknown> {\n const handleKey = opts.handleKey ?? 'id';\n const usageKeys = opts.usageKeys ?? DEFAULT_USAGE_KEYS;\n\n function readOne(entry: Obj): FileRef {\n const usage =\n opts.focus === 'usage'\n ? pick(\n entry,\n usageKeys.filter((k) => entry[k] !== undefined),\n )\n : undefined;\n return newRef({\n handle: asString(getPath(entry, handleKey)),\n raw: entry,\n name: asString(entry.name ?? entry.filename_download),\n mime: asString(entry.mime ?? entry.type),\n size: asNumber(entry.size ?? entry.filesize),\n width: asNumber(entry.width),\n height: asNumber(entry.height),\n ...(usage && Object.keys(usage).length > 0 ? { usage } : {}),\n });\n }\n\n return {\n read(column, ctx: MapperCtx): FileRef[] {\n if (column == null) return [];\n const list = (Array.isArray(column) ? column : [column]) as Obj[];\n return list.filter((e) => e && typeof e === 'object').map(readOne);\n },\n\n write(next: FileRef[], _prev: FileRef[], ctx: MapperCtx): unknown {\n const out = next\n .filter((r) => r.handle != null)\n .map((r) => {\n // Merge ONTO the original entry. Rebuilding it would delete every\n // key this mapper does not model — and real objects carry many.\n const base: Obj = isObj(r.raw) ? { ...r.raw } : {};\n setPath(base, handleKey, r.handle as string);\n if (opts.focus === 'usage' && r.usage) Object.assign(base, r.usage);\n return base;\n });\n if (ctx.multiple) return out;\n return out[0] ?? null;\n },\n };\n}\n\nfunction isObj(v: unknown): v is Obj {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\nfunction asNumber(v: unknown): number | undefined {\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\nfunction pick(src: Obj, keys: string[]): Obj {\n const out: Obj = {};\n for (const k of keys) out[k] = src[k];\n return out;\n}\nfunction getPath(src: Obj, path: string): unknown {\n return path.split('.').reduce<unknown>((acc, k) => (isObj(acc) ? acc[k] : undefined), src);\n}\nfunction setPath(target: Obj, path: string, value: string): void {\n const keys = path.split('.');\n const last = keys.pop() as string;\n let cursor: Obj = target;\n for (const k of keys) {\n const nextLevel = cursor[k];\n // Clone rather than mutate: `raw` is shared with the value we were handed.\n cursor[k] = isObj(nextLevel) ? { ...nextLevel } : {};\n cursor = cursor[k] as Obj;\n }\n cursor[last] = value;\n}\n","import type { FileMapper, FileRef, MapperCtx } from '../types.js';\nimport { newRef } from './ref.js';\n\nexport interface JunctionOpts {\n /** Column on the junction row holding the file's handle. */\n fileKey: string;\n /** Column holding the 1-based position. Omit when the table is unordered. */\n sortKey?: string;\n}\n\ntype Row = Record<string, unknown>;\n\n/**\n * A column that is an array of junction ROWS rather than of files — Directus\n * M2M being the canonical case. Each row carries its own primary key, which\n * must survive a reorder: dropping it would delete and recreate every row.\n */\nexport function junction(opts: JunctionOpts): FileMapper<Row[] | null | undefined, Row[]> {\n return {\n read(column, _ctx: MapperCtx): FileRef[] {\n if (!Array.isArray(column)) return [];\n return column\n .filter((row) => row && typeof row === 'object')\n .map((row) => newRef({ handle: asString(row[opts.fileKey]), raw: row }));\n },\n\n write(next: FileRef[], _prev: FileRef[], _ctx: MapperCtx): Row[] {\n return next\n .filter((r) => r.handle != null)\n .map((r, i) => {\n const base: Row = r.raw && typeof r.raw === 'object' ? { ...(r.raw as Row) } : {};\n base[opts.fileKey] = r.handle as string;\n if (opts.sortKey) base[opts.sortKey] = i + 1;\n return base;\n });\n },\n };\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\n","import type { FileMapper, FileStorageAdapter, MapperCtx, ResolveCtx } from '../types.js';\n\nexport interface ContractOpts {\n /** A FRESH adapter per case — shared state between cases hides bugs. */\n adapter: () => FileStorageAdapter;\n mapper: FileMapper;\n /** Representative column values this mapper reads, for the invariants. */\n sampleColumn: { single: unknown; multiple: unknown };\n /** Skip upload cases for adapters whose bytes ride the mutation. */\n skipUpload?: boolean;\n /**\n * Set for a mapper whose `write` returns a COLUMN PATCH rather than a column\n * value — `pocketbaseDiff` is the one in this package.\n *\n * Round-trip identity is meaningless for such a mapper: it never returns the\n * value it was given, it returns the keys needed to change it. The patch\n * invariants below replace the round-trip cases rather than sitting beside\n * them, because running both would demand two contradictory things of one\n * `write`.\n */\n writesColumnPatch?: boolean;\n /**\n * Context every `resolveUrl` case is called with. Defaults to `{}`.\n *\n * Supply it for an adapter whose URL cannot be built from a handle alone.\n * A PocketBase filename is meaningless by itself — the URL is assembled from\n * collection + record + filename — so that adapter correctly THROWS on an\n * empty context, and the contract must hand it a real one rather than\n * treating the throw as a conformance failure.\n */\n resolveCtx?: ResolveCtx;\n}\n\n/**\n * The test runner, injected.\n *\n * `src/` may not import vitest: this package promises zero runtime\n * dependencies, and a module under `src/` importing a test framework ships\n * that import in the published bundle. Injection also lets an adopter on jest\n * or node:test run the same contract.\n */\nexport interface ContractRunner {\n it: (name: string, fn: () => void | Promise<void>) => void;\n // Structural, so any assertion library with these shapes fits. The return\n // type must be `unknown`, not `never`: `never` demands the SOURCE function\n // return `never` too (return types are checked covariantly), which no real\n // assertion library does — vitest's `ExpectStatic` returns `Assertion<T>`.\n // `unknown` still rejects a non-function value while accepting any return\n // shape, so this stays structural without over-promising.\n expect: (actual: never) => unknown;\n}\n\nconst single: MapperCtx = { resource: 'demo', field: 'file', multiple: false };\nconst many: MapperCtx = { resource: 'demo', field: 'files', multiple: true };\n\n/**\n * The conformance suite every adapter owes. Call inside a `describe`.\n *\n * Exported from the package so adopters can point it at their own adapter —\n * the claim \"any API can be stretched onto this port\" is only worth something\n * if it is executable by the person doing the stretching.\n */\nexport function runFileStorageContract(opts: ContractOpts, runner: ContractRunner): void {\n const { mapper } = opts;\n const it = runner.it;\n // biome-ignore lint/suspicious/noExplicitAny: structural assertion shim\n const expect = runner.expect as unknown as (actual: unknown) => any;\n\n it('declares a pipeline', () => {\n expect(['immediate', 'fused', 'handshake', 'resumable']).toContain(opts.adapter().pipeline);\n });\n\n it('ships a default mapper', () => {\n const m = opts.adapter().defaultMapper;\n expect(typeof m.read).toBe('function');\n expect(typeof m.write).toBe('function');\n });\n\n if (opts.writesColumnPatch) {\n it('emits an empty patch when nothing changed', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as { keys?: Record<string, unknown> };\n expect(Object.keys(out.keys ?? {}).length).toBe(0);\n });\n\n it('emits only patch keys, never the raw column value', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n const out = mapper.write(refs, refs, many) as Record<string, unknown>;\n expect(typeof out.keys).toBe('object');\n });\n } else {\n it('round-trips an untouched single value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.single, single);\n expect(mapper.write(refs, refs, single)).toEqual(opts.sampleColumn.single);\n });\n\n it('round-trips an untouched multiple value unchanged', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(mapper.write(refs, refs, many)).toEqual(opts.sampleColumn.multiple);\n });\n }\n\n it('reads null as no files', () => {\n expect(mapper.read(null, single)).toEqual([]);\n });\n\n it('gives every ref a distinct localId', () => {\n const refs = mapper.read(opts.sampleColumn.multiple, many);\n expect(new Set(refs.map((r) => r.localId)).size).toBe(refs.length);\n });\n\n if (!opts.skipUpload) {\n it('reports progress and resolves to a ref carrying a handle', async () => {\n const adapter = opts.adapter();\n const seen: number[] = [];\n const ref = await adapter.upload(new File(['hello'], 'a.txt', { type: 'text/plain' }), {\n signal: new AbortController().signal,\n onProgress: (loaded) => seen.push(loaded),\n });\n expect(ref.handle).toBeTruthy();\n expect(seen.length).toBeGreaterThan(0);\n expect(seen.at(-1)).toBe(5);\n });\n\n it('rejects with an AbortError when the signal is already aborted', async () => {\n const adapter = opts.adapter();\n const ac = new AbortController();\n ac.abort();\n await expect(\n adapter.upload(new File(['x'], 'x.txt'), {\n signal: ac.signal,\n onProgress: () => {},\n }),\n ).rejects.toThrow(/abort/i);\n });\n\n it('accepts a retry after a failure', async () => {\n // A GENUINE, guaranteed failure — not \"whatever the first call happens\n // to do\" (the version this replaced called `upload` twice with fresh\n // signals and asserted `expect(first).toBeTruthy()`, where `first` was\n // always the string `'ok'` or `'failed'`; both are truthy, so the\n // assertion could not fail even if the adapter never actually failed\n // once). An already-aborted signal is guaranteed to reject (every\n // reference adapter's own \"rejects with an AbortError\" case above\n // proves it does), so this exercises a REAL failure and then proves the\n // adapter's internal state survived it well enough to accept a real\n // retry — no leftover lock, counter, or half-open request wedges the\n // next call.\n const adapter = opts.adapter();\n const file = new File(['x'], 'x.txt');\n const aborted = new AbortController();\n aborted.abort();\n await expect(\n adapter.upload(file, { signal: aborted.signal, onProgress: () => {} }),\n ).rejects.toThrow();\n const second = await adapter.upload(file, {\n signal: new AbortController().signal,\n onProgress: () => {},\n });\n expect(second.handle).toBeTruthy();\n });\n }\n\n it('resolves a URL for a ref', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n const url = typeof resolved === 'string' ? resolved : resolved.url;\n expect(url).toMatch(/^https?:\\/\\//);\n });\n\n it('either returns a permanent string or states a future expiry — never neither', async () => {\n const adapter = opts.adapter();\n const resolved = await adapter.resolveUrl({ localId: 'x', handle: 'h' }, opts.resolveCtx ?? {});\n if (typeof resolved === 'string') return;\n // The object arm means \"signed\". A signed URL with no deadline would be\n // cached forever, which is the exact failure this port exists to prevent,\n // so silence is a contract violation rather than a permitted default.\n expect(typeof resolved.expiresAt).toBe('number');\n expect(resolved.expiresAt).toBeGreaterThan(Date.now());\n });\n\n it('leaves unreportable metadata absent rather than guessing', async () => {\n const adapter = opts.adapter();\n if (!adapter.stat) return;\n const [out] = await adapter.stat([{ localId: 'x', handle: 'definitely-not-stored' }]);\n expect(out?.size).not.toBe(0);\n });\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type { FileRef, FileStorageAdapter, ResolveCtx, UploadCtx } from '../types.js';\n\nexport interface FakeAdapterOpts {\n /** Emit this many progress ticks before resolving. Default 2. */\n progressTicks?: number;\n /** Make every upload reject with this message. */\n failWith?: string;\n /** Return signed URLs that expire this many ms from resolution. */\n signedTtlMs?: number;\n /** Default `'immediate'`. Set `'fused'` to stand in for an inline/PocketBase-shaped storage. */\n pipeline?: 'immediate' | 'fused';\n}\n\n/**\n * In-memory adapter. Exists so the contract suite can be tested against\n * something known-correct, and so adopters have a baseline to diff against\n * when their own adapter fails a case.\n */\nexport function createFakeAdapter(opts: FakeAdapterOpts = {}): FileStorageAdapter & {\n stored: Map<string, File>;\n removed: string[];\n} {\n const stored = new Map<string, File>();\n const removed: string[] = [];\n let seq = 0;\n\n return {\n stored,\n removed,\n pipeline: opts.pipeline ?? 'immediate',\n defaultMapper: scalarHandle(),\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n const ticks = opts.progressTicks ?? 2;\n for (let i = 1; i <= ticks; i += 1) {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // Yield so an abort issued from a test lands between ticks.\n await Promise.resolve();\n ctx.onProgress(Math.round((file.size * i) / ticks), file.size);\n }\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n if (opts.failWith) throw new Error(opts.failWith);\n seq += 1;\n const handle = `fake-${seq}-${file.name}`;\n stored.set(handle, file);\n return newRef({ handle, name: file.name, mime: file.type || undefined, size: file.size });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx) {\n const q = ctx.variant?.width ? `?w=${ctx.variant.width}` : '';\n const url = `https://fake.test/${ref.handle}${q}`;\n if (opts.signedTtlMs == null) return url;\n return Promise.resolve({ url, expiresAt: Date.now() + opts.signedTtlMs });\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n return refs.map((r) => {\n const f = r.handle ? stored.get(r.handle) : undefined;\n // Honesty rule: report nothing rather than guess when the file is\n // unknown to this store.\n return f ? { ...r, name: f.name, size: f.size, mime: f.type || undefined } : r;\n });\n },\n\n async remove(ref: FileRef): Promise<void> {\n if (ref.handle) {\n stored.delete(ref.handle);\n removed.push(ref.handle);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n FusedEncoding,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface InlineFileStorageOpts {\n /**\n * How the data-provider wrapper should encode the bytes into the mutation.\n * `base64` transforms the value; `multipart` and `graphql-upload` set the\n * request body and are mutually exclusive with each other.\n */\n encoding: FusedEncoding;\n /**\n * How a saved file is read back. `ctx.resource` and `ctx.record` are given\n * because backends of this shape usually address files through the record\n * that owns them.\n */\n resolveUrl: (ref: FileRef, ctx: ResolveCtx) => ResolvedUrl | Promise<ResolvedUrl>;\n}\n\n/**\n * The storage that is not a storage: the backend takes the file inside the\n * record mutation, so there is no upload endpoint and no handle until the\n * mutation succeeds.\n *\n * `upload` deliberately does nothing but wrap the File in a ref. Progress here\n * belongs to the mutation, and it is `withFileStorage` — the only code on the\n * request path — that can report it.\n */\nexport function inlineFileStorage(opts: InlineFileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'fused',\n encoding: opts.encoding,\n defaultMapper: scalarHandle(),\n // Nothing was uploaded ahead of the save, so nothing can be orphaned.\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref, ctx) {\n return opts.resolveUrl(ref, ctx);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\nexport interface NhostFileStorageOpts {\n /** Storage service base URL, e.g. `https://<project>.storage.<region>.nhost.run`. */\n baseUrl: string;\n /**\n * Access token supplier. Injected rather than read from localStorage: the\n * v0.2 implementation hardcoded a storage key and a window global, which\n * meant one app shape was supported and no test could drive it.\n */\n getToken: () => string | null | Promise<string | null>;\n bucketId?: string;\n orphans?: OrphanPolicy;\n fetchImpl?: typeof fetch;\n}\n\ninterface NhostFileMeta {\n id?: string;\n name?: string;\n size?: number;\n mimeType?: string;\n}\n\n/**\n * nhost storage: bytes go up first, the column keeps the file's UUID, and the\n * display URL is signed and short-lived.\n *\n * ⚠️ The handle is the id, NOT the presigned URL. Storing the presigned URL —\n * as v0.2 did — writes a link with a TTL into the database.\n */\nexport function nhostFileStorage(opts: NhostFileStorageOpts): FileStorageAdapter {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const base = opts.baseUrl.replace(/\\/+$/, '');\n\n async function authHeaders(): Promise<Record<string, string>> {\n const token = await opts.getToken();\n if (!token) throw new Error('nhostFileStorage: no access token — the user is not signed in');\n return { Authorization: `Bearer ${token}` };\n }\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const headers = await authHeaders();\n const fd = new FormData();\n fd.append('file[]', file);\n if (opts.bucketId) fd.append('bucket-id', opts.bucketId);\n\n // fetch has no upload progress; report the two endpoints we can honestly\n // report rather than faking a curve.\n ctx.onProgress(0, file.size);\n const res = await fetchImpl(`${base}/v1/files`, {\n method: 'POST',\n headers,\n body: fd,\n signal: ctx.signal,\n });\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`nhost upload failed: ${res.status} ${body}`.trim());\n }\n const json = (await res.json()) as NhostFileMeta | { processedFiles?: NhostFileMeta[] };\n const meta = 'processedFiles' in json ? json.processedFiles?.[0] : (json as NhostFileMeta);\n if (!meta?.id) throw new Error('nhost upload returned no file id');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: meta.id,\n name: meta.name ?? file.name,\n mime: meta.mimeType ?? file.type ?? undefined,\n size: meta.size ?? file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const headers = await authHeaders();\n const params = new URLSearchParams();\n if (ctx.variant?.width) params.set('w', String(ctx.variant.width));\n if (ctx.variant?.height) params.set('h', String(ctx.variant.height));\n const qs = params.toString();\n const res = await fetchImpl(\n `${base}/v1/files/${ref.handle}/presignedurl${qs ? `?${qs}` : ''}`,\n { headers },\n );\n if (!res.ok) throw new Error(`nhost presign failed: ${res.status}`);\n const json = (await res.json()) as { url?: string; expiration?: number };\n if (!json.url) throw new Error('nhost presign returned no url');\n return {\n url: json.url,\n // `expiration` is a lifetime in seconds; the cache wants an instant.\n expiresAt: Date.now() + (json.expiration ?? 1800) * 1000,\n };\n },\n\n async stat(refs: FileRef[]): Promise<FileRef[]> {\n const headers = await authHeaders();\n return Promise.all(\n refs.map(async (ref) => {\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'HEAD',\n headers,\n });\n if (!res.ok) return ref;\n // ⚠️ `headers.get` returns null for a missing header, and\n // `Number(null)` is 0 — which `Number.isFinite` happily accepts. Read\n // the header first and only convert when it is actually present, or a\n // file of unknown size reports as literally zero bytes and every\n // size column downstream believes it. That is the honesty rule\n // (`types.ts`: never guess, never substitute a zero).\n const rawSize = res.headers.get('content-length');\n const size = rawSize == null ? Number.NaN : Number(rawSize);\n const mime = res.headers.get('content-type') ?? undefined;\n return {\n ...ref,\n ...(Number.isFinite(size) ? { size } : {}),\n ...(mime ? { mime } : {}),\n };\n }),\n );\n },\n\n async remove(ref: FileRef): Promise<void> {\n const headers = await authHeaders();\n const res = await fetchImpl(`${base}/v1/files/${ref.handle}`, {\n method: 'DELETE',\n headers,\n });\n if (!res.ok && res.status !== 404) {\n throw new Error(`nhost delete failed: ${res.status}`);\n }\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * The slice of `@supabase/supabase-js` this adapter uses, declared\n * structurally so the package keeps zero runtime dependencies and the adapter\n * can be driven by a plain object in tests.\n */\nexport interface SupabaseStorageLike {\n storage: {\n from(bucket: string): {\n upload(\n path: string,\n file: File,\n opts?: { contentType?: string; upsert?: boolean },\n ): Promise<{ data: { path: string } | null; error: { message: string } | null }>;\n getPublicUrl(path: string): { data: { publicUrl: string } };\n createSignedUrl(\n path: string,\n expiresIn: number,\n ): Promise<{ data: { signedUrl: string } | null; error: { message: string } | null }>;\n remove(paths: string[]): Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n}\n\nexport interface SupabaseFileStorageOpts {\n client: SupabaseStorageLike;\n bucket: string;\n /** Public buckets yield permanent URLs; signed buckets yield expiring ones. */\n access: 'public' | 'signed';\n /** Lifetime of a signed URL in seconds. Default 3600. */\n signedTtlSec?: number;\n /** Prepended to every generated object path, e.g. `'avatars'`. */\n pathPrefix?: string;\n orphans?: OrphanPolicy;\n}\n\n/**\n * A per-upload path component that survives a module reload.\n *\n * ⚠️ A module-scoped counter is NOT enough here, even though one is fine for\n * `FileRef.localId`. `localId` is process-local and never persisted; this\n * string becomes the object's PATH IN THE BUCKET. A counter restarts at 0 on\n * every page load and on every serverless cold start, so a user who uploads\n * `photo.png`, reloads, and uploads it again generates the same path twice and\n * gets Supabase's `400 Asset Already Exists` — with `upsert` correctly off,\n * that is a hard failure rather than a silent overwrite.\n */\nfunction pathStamp(): string {\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return crypto.randomUUID().slice(0, 8);\n }\n // `padEnd` is not decoration: Math.random() may legally return 0, and\n // (0).toString(36).slice(2, 10) is the empty string — which would put every\n // upload of a given filename back on one colliding path, the exact bug this\n // function exists to prevent.\n return Math.random().toString(36).slice(2, 10).padEnd(8, '0');\n}\n\nexport function supabaseFileStorage(opts: SupabaseFileStorageOpts): FileStorageAdapter {\n const bucket = () => opts.client.storage.from(opts.bucket);\n const ttl = opts.signedTtlSec ?? 3600;\n\n return {\n pipeline: 'immediate',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n // A fresh path per upload. Supabase's own guidance is to write to a new\n // path rather than to enable upsert: overwriting races against CDN\n // propagation and the last writer wins silently.\n const stamp = `${pathStamp()}-${file.name}`;\n const path = opts.pathPrefix ? `${opts.pathPrefix}/${stamp}` : stamp;\n\n ctx.onProgress(0, file.size);\n const { data, error } = await bucket().upload(path, file, {\n contentType: file.type || undefined,\n });\n if (error) throw new Error(`Supabase upload failed: ${error.message}`);\n if (!data) throw new Error('Supabase upload returned no path');\n ctx.onProgress(file.size, file.size);\n\n return newRef({\n handle: data.path,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, _ctx: ResolveCtx): Promise<ResolvedUrl> {\n const path = ref.handle as string;\n if (opts.access === 'public') {\n // Permanent: a plain string, so it may be cached and server-rendered.\n return bucket().getPublicUrl(path).data.publicUrl;\n }\n const { data, error } = await bucket().createSignedUrl(path, ttl);\n if (error) throw new Error(`Supabase sign failed: ${error.message}`);\n if (!data) throw new Error('Supabase sign returned no url');\n return { url: data.signedUrl, expiresAt: Date.now() + ttl * 1000 };\n },\n\n async remove(ref: FileRef): Promise<void> {\n const { error } = await bucket().remove([ref.handle as string]);\n if (error) throw new Error(`Supabase remove failed: ${error.message}`);\n },\n };\n}\n","import { newRef } from '../mappers/ref.js';\nimport { scalarHandle } from '../mappers/scalar-handle.js';\nimport type {\n FileRef,\n FileStorageAdapter,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/** What the adopter's signing endpoint hands back. */\nexport interface SignedTarget {\n /**\n * The object key. **The server chooses it, not the client** — letting the\n * browser name the object is how buckets get overwritten by whoever asks.\n */\n key: string;\n url: string;\n method?: 'PUT' | 'POST';\n /** Headers that were part of the signature. Sent verbatim. */\n headers?: Record<string, string>;\n /** POST-policy form fields. Sent before the file part. */\n fields?: Record<string, string>;\n}\n\nexport interface S3FileStorageOpts {\n /** Ask your backend to sign. Authorization happens there, not here. */\n sign: (file: { name: string; type: string; size: number }) => Promise<SignedTarget>;\n /** CDN or bucket origin for permanent read URLs. */\n publicBaseUrl?: string;\n /** Supply for private buckets; return an expiring URL. */\n signView?: (key: string, ctx: ResolveCtx) => Promise<ResolvedUrl>;\n remove?: (key: string) => Promise<void>;\n orphans?: OrphanPolicy;\n /** Injected for tests. Defaults to `new XMLHttpRequest()`. */\n xhrImpl?: () => XMLHttpRequest;\n}\n\n/**\n * Direct-to-bucket upload behind a signed handshake: ask the server to sign,\n * send the bytes straight to storage, keep the key.\n *\n * Uses XHR rather than fetch on purpose — browsers expose no upload progress\n * on fetch, and this is the pipeline whose files are large enough for the\n * absence to be felt.\n */\nexport function s3FileStorage(opts: S3FileStorageOpts): FileStorageAdapter {\n return {\n pipeline: 'handshake',\n defaultMapper: scalarHandle(),\n orphans: opts.orphans ?? 'best-effort',\n\n async upload(file: File, ctx: UploadCtx): Promise<FileRef> {\n // Check BEFORE signing, like the sibling adapters do. Signing is a round\n // trip to the adopter's own backend; an upload the user already\n // cancelled has no business making it.\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n const target = await opts.sign({ name: file.name, type: file.type, size: file.size });\n if (ctx.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n\n const xhr = (opts.xhrImpl ?? (() => new XMLHttpRequest()))();\n const method = target.method ?? 'PUT';\n\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => xhr.abort();\n ctx.signal.addEventListener('abort', onAbort, { once: true });\n\n xhr.upload.addEventListener('progress', (e) => {\n const ev = e as ProgressEvent;\n ctx.onProgress(ev.loaded, ev.total || file.size);\n });\n xhr.addEventListener('load', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n if (xhr.status >= 200 && xhr.status < 300) {\n ctx.onProgress(file.size, file.size);\n resolve();\n } else reject(new Error(`S3 upload failed: ${xhr.status}`));\n });\n xhr.addEventListener('error', () => {\n ctx.signal.removeEventListener('abort', onAbort);\n reject(new Error('S3 upload failed: network error'));\n });\n xhr.addEventListener('abort', () => {\n reject(new DOMException('Aborted', 'AbortError'));\n });\n\n xhr.open(method, target.url);\n for (const [k, v] of Object.entries(target.headers ?? {})) {\n xhr.setRequestHeader(k, v);\n }\n\n if (method === 'POST') {\n const fd = new FormData();\n for (const [k, v] of Object.entries(target.fields ?? {})) fd.append(k, v);\n // ⚠️ The file part goes LAST: S3 ignores every field that follows it.\n fd.append('file', file);\n xhr.send(fd);\n } else {\n xhr.send(file);\n }\n });\n\n return newRef({\n handle: target.key,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n resolveUrl(ref: FileRef, ctx: ResolveCtx): ResolvedUrl | Promise<ResolvedUrl> {\n const key = ref.handle as string;\n if (opts.signView) return opts.signView(key, ctx);\n if (opts.publicBaseUrl) return `${opts.publicBaseUrl.replace(/\\/+$/, '')}/${key}`;\n throw new Error(\n 's3FileStorage: pass publicBaseUrl for a public bucket or signView for a private one',\n );\n },\n\n ...(opts.remove\n ? {\n async remove(ref: FileRef): Promise<void> {\n await opts.remove?.(ref.handle as string);\n },\n }\n : {}),\n };\n}\n","/**\n * A mapper's answer when the change cannot be expressed as \"the new value of\n * this column\".\n *\n * PocketBase updates a multi-value file field through SIBLING keys — `docs+`\n * to append, `docs-` to remove named files — so there is no single value to\n * return. Rather than special-casing one backend inside the write path, a\n * mapper may return this marker and `withFileStorage` splices its keys into\n * the payload instead of assigning to the field's own key.\n */\nexport const COLUMN_PATCH = Symbol.for('buro.fileStorage.columnPatch');\n\nexport interface ColumnPatch {\n [COLUMN_PATCH]: true;\n keys: Record<string, unknown>;\n}\n\nexport function columnPatch(keys: Record<string, unknown>): ColumnPatch {\n return { [COLUMN_PATCH]: true, keys };\n}\n\nexport function isColumnPatch(value: unknown): value is ColumnPatch {\n return typeof value === 'object' && value !== null && COLUMN_PATCH in value;\n}\n","import { columnPatch } from '../mappers/column-patch.js';\nimport { newRef } from '../mappers/ref.js';\nimport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n MapperCtx,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n} from '../types.js';\n\n/**\n * PocketBase stores a filename per file and updates multi-value fields by\n * DIFF: `field+` appends, `field-` removes by name. Single-value fields are\n * replaced outright, and cleared with an empty string.\n */\nexport function pocketbaseDiff(): FileMapper<string | string[] | null | undefined, unknown> {\n return {\n carriesPending: true,\n\n read(column): FileRef[] {\n if (column == null) return [];\n const list = Array.isArray(column) ? column : [column];\n return list\n .filter((n) => typeof n === 'string' && n !== '')\n .map((name) => newRef({ handle: name, raw: name, name }));\n },\n\n write(next: FileRef[], prev: FileRef[], ctx: MapperCtx): unknown {\n const added = next.filter((r) => r.pending).map((r) => r.pending as File);\n\n if (!ctx.multiple) {\n // No diff protocol for single-value fields — assign or clear.\n if (added.length > 0) return columnPatch({ [ctx.field]: added[0] as File });\n const kept = next.find((r) => r.handle);\n if (kept) return columnPatch({});\n return prev.length > 0 ? columnPatch({ [ctx.field]: '' }) : columnPatch({});\n }\n\n const keptHandles = new Set(next.filter((r) => r.handle).map((r) => r.handle as string));\n const removed = prev\n .map((r) => r.handle)\n .filter((h): h is string => h != null && !keptHandles.has(h));\n\n const keys: Record<string, unknown> = {};\n if (added.length > 0) keys[`${ctx.field}+`] = added;\n if (removed.length > 0) keys[`${ctx.field}-`] = removed;\n return columnPatch(keys);\n },\n };\n}\n\nexport interface PocketbaseFileStorageOpts {\n /** PocketBase origin, e.g. `http://127.0.0.1:8090`. */\n baseUrl: string;\n /** Set when the file field is marked Protected in the collection schema. */\n protectedFiles?: boolean;\n /** Supplies a short-lived file token — `pb.files.getToken()`. */\n getFileToken?: () => Promise<string>;\n /** Lifetime assumed for a file token, in seconds. Default 120. */\n tokenTtlSec?: number;\n}\n\n/**\n * PocketBase: bytes ride the record mutation as multipart, the column keeps a\n * filename that only means anything alongside its record, images get thumbs\n * from a query parameter, and protected files need a short-lived token.\n *\n * It is the hardest of the four reference pipelines, which is why it is the\n * one the example app runs against.\n */\nexport function pocketbaseFileStorage(opts: PocketbaseFileStorageOpts): FileStorageAdapter {\n const base = opts.baseUrl.replace(/\\/+$/, '');\n const ttl = opts.tokenTtlSec ?? 120;\n\n return {\n pipeline: 'fused',\n encoding: 'multipart',\n defaultMapper: pocketbaseDiff(),\n orphans: 'keep',\n\n async upload(file: File, _ctx: UploadCtx): Promise<FileRef> {\n // Fused: hold the bytes for the mutation.\n return newRef({\n pending: file,\n name: file.name,\n mime: file.type || undefined,\n size: file.size,\n });\n },\n\n async resolveUrl(ref: FileRef, ctx: ResolveCtx): Promise<ResolvedUrl> {\n const recordId = (ctx.record as { id?: string } | undefined)?.id;\n if (!ctx.resource || !recordId) {\n throw new Error(\n 'pocketbaseFileStorage: a file URL needs its collection and record id — ' +\n 'the filename alone does not identify a file in PocketBase.',\n );\n }\n const params = new URLSearchParams();\n if (ctx.variant?.width || ctx.variant?.height) {\n const w = ctx.variant.width ?? 0;\n const h = ctx.variant.height ?? 0;\n // `f` fits inside the box instead of cropping to it.\n params.set('thumb', `${w}x${h}f`);\n }\n if (ctx.disposition === 'attachment') params.set('download', '1');\n\n if (opts.protectedFiles) {\n if (!opts.getFileToken) {\n throw new Error(\n 'pocketbaseFileStorage: protectedFiles is set but getFileToken was not supplied',\n );\n }\n const token = await opts.getFileToken();\n params.set('token', token);\n const qs = params.toString();\n return {\n url: `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`,\n expiresAt: Date.now() + ttl * 1000,\n };\n }\n\n const qs = params.toString();\n return `${base}/api/files/${ctx.resource}/${recordId}/${ref.handle}${qs ? `?${qs}` : ''}`;\n },\n };\n}\n","import { isColumnPatch } from '../mappers/column-patch.js';\nimport { type EncodingPlan, encodingFor } from './with-file-storage.js';\n\n/**\n * Splice column patches into the payload; leave everything else alone.\n *\n * Reference identity is preserved ONLY when the payload carries no column\n * patch at all. The moment even an EMPTY patch (`{ keys: {} }` — \"nothing\n * changed for this field\") is present, the field's own key must still be\n * dropped: forwarding the raw patch marker as the field's value would\n * overwrite the column with `{ keys: {} }` instead of leaving it alone. So a\n * fresh object is built whenever any patch is present, empty or not — this\n * function does not claim otherwise.\n */\nexport function splicePatches(variables: Record<string, unknown>): Record<string, unknown> {\n let touched = false;\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (isColumnPatch(value)) {\n touched = true;\n Object.assign(out, value.keys);\n continue;\n }\n out[key] = value;\n }\n return touched ? out : variables;\n}\n\n/**\n * Does this payload actually carry bytes? Everything downstream hinges on it.\n *\n * ⚠️ No depth cap. An earlier version stopped at four levels, which meant a\n * File nested deeper was silently declared absent — the payload then took the\n * untouched fast path and the File reached `JSON.stringify` and became `{}`.\n * Today's mappers never nest a File below the first level, so the cap was\n * unreachable; a future rich-content mapper would have made it a silent\n * data-loss bug. Cycles are handled by tracking visited objects rather than by\n * limiting depth.\n */\nexport function containsFile(value: unknown, seen = new WeakSet<object>()): boolean {\n if (typeof File !== 'undefined' && value instanceof File) return true;\n if (!value || typeof value !== 'object') return false;\n if (seen.has(value)) return false;\n seen.add(value);\n if (Array.isArray(value)) return value.some((v) => containsFile(v, seen));\n return Object.values(value as Record<string, unknown>).some((v) => containsFile(v, seen));\n}\n\n/** Getter-safe recursive check used to reject unsupported bulk column patches. */\nexport function containsColumnPatch(value: unknown, seen = new WeakSet<object>()): boolean {\n if (!value || typeof value !== 'object') return false;\n try {\n if (isColumnPatch(value)) return true;\n if (seen.has(value)) return false;\n seen.add(value);\n const descriptors = Object.getOwnPropertyDescriptors(value);\n return Object.entries(descriptors).some(\n ([key, descriptor]) =>\n key !== 'length' && 'value' in descriptor && containsColumnPatch(descriptor.value, seen),\n );\n } catch {\n return false;\n }\n}\n\nasync function toDataUrl(file: File): Promise<string> {\n const buf = await file.arrayBuffer();\n const bytes = new Uint8Array(buf);\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n const b64 = typeof btoa === 'function' ? btoa(binary) : Buffer.from(bytes).toString('base64');\n return `data:${file.type || 'application/octet-stream'};base64,${b64}`;\n}\n\n/**\n * Turn a payload carrying Files into something the provider can send.\n *\n * `base64` is a VALUE transform, so the payload stays a plain object and\n * composes with anything. `multipart` and `graphql-upload` produce a FormData\n * — the provider must send it as the body verbatim and must NOT set a\n * content-type, because the browser writes the boundary.\n */\nexport async function encodePayload(\n variables: Record<string, unknown>,\n plan: EncodingPlan,\n): Promise<Record<string, unknown> | FormData> {\n // base64 first: it is a VALUE transform, so those Files stop being Files\n // before anything decides what the request body looks like.\n const staged: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(variables)) {\n if (encodingFor(key, plan) !== 'base64') {\n staged[key] = value;\n continue;\n }\n if (value instanceof File) staged[key] = await toDataUrl(value);\n else if (Array.isArray(value)) {\n staged[key] = await Promise.all(\n value.map((v) => (v instanceof File ? toDataUrl(v) : Promise.resolve(v))),\n );\n } else staged[key] = value;\n }\n\n // Nothing left to carry in the body — keep it a plain JSON payload.\n if (!containsFile(staged)) return staged;\n\n const encoding = plan.bodyEncoding;\n const fd = new FormData();\n\n // ⚠️ Both loops below read `staged`, not `variables`. A base64-designated\n // key already holds its data-URL STRING in `staged` at this point — reading\n // `variables` here would re-discover the original File and append it to the\n // FormData as raw binary, silently undoing the base64 conversion the moment\n // any OTHER field on the same call needs a body encoding. That is the exact\n // failure this plan revision exists to close; it must not reopen here.\n if (encoding === 'graphql-upload') {\n // multipart request spec: `operations` + `map` first, then the file parts.\n const files: File[] = [];\n const map: Record<string, string[]> = {};\n const scrubbed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) {\n map[String(files.length)] = [`variables.${key}`];\n files.push(value);\n scrubbed[key] = null;\n } else scrubbed[key] = value;\n }\n fd.append('operations', JSON.stringify({ variables: scrubbed }));\n fd.append('map', JSON.stringify(map));\n files.forEach((f, i) => {\n fd.append(String(i), f);\n });\n return fd;\n }\n\n for (const [key, value] of Object.entries(staged)) {\n if (value instanceof File) fd.append(key, value);\n else if (Array.isArray(value)) {\n for (const item of value) {\n fd.append(key, item instanceof File ? item : String(item));\n }\n } else if (value == null) fd.append(key, '');\n else if (typeof value === 'object') fd.append(key, JSON.stringify(value));\n else fd.append(key, String(value));\n }\n return fd;\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { containsColumnPatch, containsFile, encodePayload, splicePatches } from './encode.js';\n\n/** Stamped on a wrapped provider so the wiring assertion can see it. */\nexport const FILE_STORAGE_WRAPPED = Symbol.for('buro.fileStorage.wrapped');\n\nconst MUTATING = new Set(['create', 'update', 'updateMany']);\n\nfunction isProviderMap(value: unknown): value is Record<string, Record<string, unknown>> {\n if (!value || typeof value !== 'object') return false;\n const entries = Object.values(value as Record<string, unknown>);\n return entries.length > 0 && entries.every((v) => v && typeof v === 'object' && !('then' in v));\n}\n\n/**\n * Rewrite outgoing mutations that actually carry bytes.\n *\n * The decision is PER REQUEST, from the payload — never global. Mixing\n * pipelines is the ordinary case (a storage service for most fields, two\n * places that take the file inline), so a payload with no File is handed to\n * the underlying provider untouched, by reference: no FormData, no\n * re-encoding, no surprise.\n */\nexport function withFileStorage<T>(provider: T, app: AppRuntime): T {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) {\n throw new Error(\n 'withFileStorage: no file storage registered — add fileStoragePlugin({...}) to defineApp',\n );\n }\n\n // Wrap a map of providers entry by entry, so `meta.dataProviderName` keeps\n // composing with file fields.\n if (isProviderMap(provider) && !(provider as Record<string, unknown>).getList) {\n const out: Record<string, unknown> = {};\n for (const [name, entry] of Object.entries(provider as Record<string, unknown>)) {\n out[name] = withFileStorage(entry, app);\n }\n Object.defineProperty(out, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return out as T;\n }\n\n const source = provider as Record<string, unknown>;\n const wrapped: Record<string, unknown> = Object.create(\n Object.getPrototypeOf(source) as object | null,\n );\n\n for (const key of allKeys(source)) {\n const value = source[key];\n if (typeof value !== 'function' || !MUTATING.has(key)) {\n wrapped[key] = value;\n continue;\n }\n wrapped[key] = async (args: Record<string, unknown>) => {\n const variables = args?.variables;\n if (!variables || typeof variables !== 'object' || variables instanceof FormData) {\n return (value as (a: unknown) => unknown)(args);\n }\n if (key === 'updateMany' && (containsFile(variables) || containsColumnPatch(variables))) {\n throw new Error(\n `withFileStorage: bulk file mutations are not supported for ${key}; submit one record at a time so FileMapper patches and request-body encoding stay unambiguous.`,\n );\n }\n const spliced = splicePatches(variables as Record<string, unknown>);\n if (!containsFile(spliced)) {\n // Untouched — pass the ORIGINAL object so identity is preserved.\n const passthrough = spliced === variables ? args : { ...args, variables: spliced };\n return (value as (a: unknown) => unknown)(passthrough);\n }\n // Per RESOURCE, not per provider — see planFor.\n const encoded = await encodePayload(spliced, planFor(app, registry, String(args.resource)));\n return (value as (a: unknown) => unknown)({ ...args, variables: encoded });\n };\n }\n\n Object.defineProperty(wrapped, FILE_STORAGE_WRAPPED, { value: true, enumerable: false });\n return wrapped as T;\n}\n\nfunction allKeys(source: Record<string, unknown>): string[] {\n const keys = new Set(Object.keys(source));\n const proto = Object.getPrototypeOf(source) as object | null;\n if (proto && proto !== Object.prototype) {\n for (const k of Object.getOwnPropertyNames(proto)) if (k !== 'constructor') keys.add(k);\n }\n return [...keys];\n}\n\n/**\n * Which encoding each payload key needs, and which body encoding the rest\n * fall back to — for ONE resource.\n *\n * ⚠️ This must be resolved per resource, not once per provider. An earlier\n * version picked a single encoding from the whole registry, so an app with a\n * multipart storage anywhere and a base64 storage anywhere else sent the\n * base64 field's File into FormData as raw binary — never encoded, no boot\n * error, no runtime error, silently wrong data. It also contradicted the\n * type's own promise that base64 \"composes with anything\", which is exactly\n * why `assertFileStorageWiring` permits that combination.\n *\n * `base64` is a VALUE transform: those fields are converted first and stop\n * being Files. Only what remains decides the request body.\n */\nexport interface EncodingPlan {\n /** Encoding EXPLICITLY declared for a field, via `f.file({ fileStorage })`. Authoritative. */\n knownEncodings: Map<string, FusedEncoding>;\n /**\n * Assumed for a payload key with NO declared field to consult — the\n * resource isn't registered, or has no `f.file()` for this key. Mirrors\n * `ui.fileStorage ?? 'default'`, the exact resolution a KNOWN field with no\n * explicit storage name already gets, extended to \"no field info at all\".\n * This is what keeps a single-storage app working without ever having to\n * declare its fields through `@burojs/field-types`.\n */\n defaultEncoding: FusedEncoding;\n /** Body encoding for whatever Files remain once base64 keys are converted. */\n bodyEncoding: Exclude<FusedEncoding, 'base64'>;\n}\n\n/** `docs+` / `docs-` are patch keys for the field `docs`. */\nexport function fieldOfKey(key: string): string {\n return key.endsWith('+') || key.endsWith('-') ? key.slice(0, -1) : key;\n}\n\n/** The encoding one payload key resolves to: its field's, or the plan's default. */\nexport function encodingFor(key: string, plan: EncodingPlan): FusedEncoding {\n return plan.knownEncodings.get(fieldOfKey(key)) ?? plan.defaultEncoding;\n}\n\nfunction planFor(app: AppRuntime, registry: FileStorageRegistry, resource: string): EncodingPlan {\n const knownEncodings = new Map<string, FusedEncoding>();\n\n const defaultAdapter = registry.has('default') ? registry.get('default') : undefined;\n const defaultEncoding: FusedEncoding =\n defaultAdapter?.pipeline === 'fused' && defaultAdapter.encoding\n ? defaultAdapter.encoding\n : 'multipart';\n let bodyEncoding: Exclude<FusedEncoding, 'base64'> =\n defaultEncoding === 'base64' ? 'multipart' : defaultEncoding;\n\n const def = app.registry.get(resource);\n for (const [fieldName, field] of Object.entries(def?.fields ?? {})) {\n if ((field as { kind?: string }).kind !== 'file') continue;\n const ui = (field as { ui?: { fileStorage?: string } }).ui;\n if (!registry.has(ui?.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui?.fileStorage);\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n knownEncodings.set(fieldName, adapter.encoding);\n if (adapter.encoding !== 'base64') bodyEncoding = adapter.encoding;\n }\n return { knownEncodings, defaultEncoding, bodyEncoding };\n}\n","import type { AppRuntime } from '@burojs/core';\nimport { FILE_STORAGE_SERVICE } from '../registry.js';\nimport type { FileMapper, FileStorageRegistry, FusedEncoding } from '../types.js';\nimport { FILE_STORAGE_WRAPPED } from './with-file-storage.js';\n\n/** Encodings that claim the request body, and so cannot share one request. */\nconst BODY_ENCODINGS: FusedEncoding[] = ['multipart', 'graphql-upload'];\n\n/**\n * Three checks that must fail at boot rather than at runtime.\n *\n * 1. A fused storage with an unwrapped provider. Files in `variables` reach\n * `JSON.stringify` and become `{}` — the upload silently does nothing, the\n * request succeeds, and nobody finds out until someone opens the record.\n * 2. A fused storage paired with a mapper that cannot carry a `pending` file\n * forward (`mapper.carriesPending` is not set). A ref on a fused pipeline\n * never has a `handle` before the mutation runs — only `pending` — so a\n * mapper that writes nothing for a handle-less ref (the ordinary case:\n * `richImage`, `junction`) silently drops every file added through that\n * field. This is the same class of failure as #1 — a fused storage whose\n * mapper cannot carry the file forward is exactly as broken as one with no\n * wrapper — so it gets the same treatment: fail loudly before any user\n * touches the form, not discover it when a saved record turns out empty.\n * 3. Two incompatible body encodings on one resource. One request has one\n * body; discovering that during a save is discovering it too late.\n *\n * Called by the host composition layer that owns both the app and its data\n * provider.\n */\nexport function assertFileStorageWiring(app: AppRuntime, provider: unknown): void {\n const registry = app.getService<FileStorageRegistry>(FILE_STORAGE_SERVICE);\n if (!registry) return;\n\n if (registry.hasFused()) {\n const stamped =\n provider != null &&\n (typeof provider === 'object' || typeof provider === 'function') &&\n FILE_STORAGE_WRAPPED in (provider as object);\n if (!stamped) {\n throw new Error(\n 'A fused file storage is registered, but the data provider is not wrapped. ' +\n 'Files ride inside the record mutation for this pipeline, so an unwrapped ' +\n 'provider will JSON.stringify them into `{}` and lose them silently. ' +\n 'Wrap it once: dataProvider: withFileStorage(myDataProvider, app)',\n );\n }\n }\n\n for (const def of app.registry.list()) {\n const seen = new Map<FusedEncoding, string>();\n for (const [fieldName, field] of Object.entries(def.fields ?? {})) {\n if ((field as { kind?: string }).kind !== 'file') continue;\n const ui = (field as { ui?: { fileStorage?: string; mapper?: unknown } }).ui;\n if (!registry.has(ui?.fileStorage ?? 'default')) continue;\n const adapter = registry.get(ui?.fileStorage);\n\n if (adapter.pipeline === 'fused') {\n const mapper = (ui?.mapper as FileMapper | undefined) ?? adapter.defaultMapper;\n if (!mapper.carriesPending) {\n throw new Error(\n `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 })\\`.`,\n );\n }\n }\n\n if (adapter.pipeline !== 'fused' || !adapter.encoding) continue;\n if (!BODY_ENCODINGS.includes(adapter.encoding)) continue;\n const previous = seen.get(adapter.encoding);\n if (previous == null) seen.set(adapter.encoding, fieldName);\n if (seen.size > 1) {\n const entries = [...seen.entries()];\n const [encA, fieldA] = entries[0] as [FusedEncoding, string];\n const [encB, fieldB] = entries[1] as [FusedEncoding, string];\n throw new Error(\n `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.`,\n );\n }\n }\n }\n}\n","const FILE_MAPPER_BASELINE_CARRIER = Symbol.for('buro.fileMapper.baselineCarrier');\n\n/**\n * Copy the opaque mapped-file baseline capability from a decoded record to a\n * fresh edit payload. The physical baseline itself remains private to the\n * provider wrapper; this helper can neither inspect nor serialize it.\n */\nexport function inheritFileMapperBaseline<T extends object>(source: object, target: T): T {\n let descriptor: PropertyDescriptor | undefined;\n try {\n descriptor = Object.getOwnPropertyDescriptor(source, FILE_MAPPER_BASELINE_CARRIER);\n } catch {\n return target;\n }\n if (\n descriptor === undefined ||\n !('value' in descriptor) ||\n descriptor.value === null ||\n typeof descriptor.value !== 'object'\n ) {\n return target;\n }\n Object.defineProperty(target, FILE_MAPPER_BASELINE_CARRIER, {\n value: descriptor.value,\n enumerable: false,\n configurable: true,\n writable: false,\n });\n return target;\n}\n","export const PACKAGE_NAME = '@burojs/file-storage' as const;\n\nexport type {\n FileMapper,\n FileRef,\n FileStorageAdapter,\n FileStorageRegistry,\n FusedEncoding,\n MapperCtx,\n OrphanPolicy,\n ResolveCtx,\n ResolvedUrl,\n UploadCtx,\n UploadPipeline,\n} from './types.js';\nexport { FILE_STORAGE_SERVICE, createFileStorageRegistry } from './registry.js';\nexport { fileStoragePlugin } from './plugin.js';\nexport { newRef } from './mappers/ref.js';\nexport { isImage } from './kind.js';\nexport { scalarHandle } from './mappers/scalar-handle.js';\nexport { richImage, type RichImageOpts } from './mappers/rich-image.js';\nexport { junction, type JunctionOpts } from './mappers/junction.js';\nexport {\n runFileStorageContract,\n type ContractOpts,\n type ContractRunner,\n} from './contract/run-contract.js';\nexport { createFakeAdapter, type FakeAdapterOpts } from './contract/fake-adapter.js';\nexport { inlineFileStorage, type InlineFileStorageOpts } from './adapters/inline.js';\nexport { nhostFileStorage, type NhostFileStorageOpts } from './adapters/nhost.js';\nexport {\n supabaseFileStorage,\n type SupabaseFileStorageOpts,\n type SupabaseStorageLike,\n} from './adapters/supabase.js';\nexport { s3FileStorage, type S3FileStorageOpts, type SignedTarget } from './adapters/s3.js';\nexport {\n pocketbaseDiff,\n pocketbaseFileStorage,\n type PocketbaseFileStorageOpts,\n} from './adapters/pocketbase.js';\nexport {\n COLUMN_PATCH,\n columnPatch,\n isColumnPatch,\n type ColumnPatch,\n} from './mappers/column-patch.js';\nexport { FILE_STORAGE_WRAPPED, withFileStorage } from './wrapper/with-file-storage.js';\nexport { assertFileStorageWiring } from './wrapper/assert-wiring.js';\nexport {\n containsColumnPatch,\n containsFile,\n encodePayload,\n splicePatches,\n} from './wrapper/encode.js';\nexport { inheritFileMapperBaseline } from './baseline-carrier.js';\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burojs/file-storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Buro: storage port for file fields — adapters, mappers, contract suite",
|
|
6
6
|
"type": "module",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@burojs/core": "0.
|
|
28
|
+
"@burojs/core": "0.5.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"expect-type": "^1.1.0",
|
|
32
32
|
"typescript": "^5.5.0",
|
|
33
33
|
"vitest": "^4.0.0",
|
|
34
|
-
"@burojs/field-types": "0.
|
|
34
|
+
"@burojs/field-types": "0.5.0",
|
|
35
35
|
"@burojs/tooling-eslint": "0.0.0",
|
|
36
36
|
"@burojs/tooling-globals": "0.0.0",
|
|
37
37
|
"@burojs/tooling-tsconfig": "0.0.0"
|