63 lines
2.3 KiB
TypeScript
63 lines
2.3 KiB
TypeScript
|
|
/**
|
||
|
|
* 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;
|
||
|
|
}
|