/** * Helpers for PocketBase FILE fields, whose read shape depends on how * many files are attached. * * ## The trap * * A `file` field with `maxSelect: 1` is written as an array — the PB * SDK and the API both want `signed_copy: [file]` — but PocketBase * RETURNS a bare STRING when exactly one file is attached, and an * array only when there are several. Verified against the live * `delivery_notes.signed_copy` field (maxSelect: 1, one file on * DN26100403): * * typeof : string * Array.isArray : false * value : "image_ar52u2hjfq.jpg" * value[0] : "i" <-- indexing silently yields a CHARACTER * * So `Array.isArray(x) ? x[0] : null` returns null for a note that very * much does have a signed copy, and `x[0]` in a template returns the * first letter of the filename instead of the filename. Both fail * quietly: no type error, no exception, just a wrong answer. One of * them (`deliveries/page.tsx`, which called `.join(", ")` on the value) * threw outright instead. * * ## Use these * * Anywhere a PB file field is READ. `firstFileName` for single-file * fields, `fileNames` when you want the whole list for display. */ /** A PB file field's read value: a bare string, or an array of them. */ export type PbFileValue = string | string[] | null | undefined; /** * The first filename on a PB file field, or null when empty. * Handles every shape PB can return: string, array, null, undefined. */ export function firstFileName(value: PbFileValue): string | null { if (!value) return null; if (typeof value === "string") return value.length > 0 ? value : null; if (Array.isArray(value)) { for (const entry of value) { if (typeof entry === "string" && entry.length > 0) return entry; } } return null; } /** Every filename on a PB file field, normalised to an array. */ export function fileNames(value: PbFileValue): string[] { if (!value) return []; if (typeof value === "string") return value.length > 0 ? [value] : []; return Array.isArray(value) ? value.filter((e): e is string => typeof e === "string" && e.length > 0) : []; } /** Is anything attached? Truthy check that also works on a bare string. */ export function hasFile(value: PbFileValue): boolean { return fileNames(value).length > 0; }