@marktiderman/genesis-core 1.5.0 → 1.5.1
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 +31 -10
- package/dist/{chunk-IZT2O57C.js → chunk-3JSFTLDV.js} +35 -15
- package/dist/chunk-3JSFTLDV.js.map +1 -0
- package/dist/{chunk-DVW5GXWZ.js → chunk-6Q477CHB.js} +13 -11
- package/dist/chunk-6Q477CHB.js.map +1 -0
- package/dist/{chunk-RLY7TZUY.js → chunk-E5VYFAQO.js} +47 -13
- package/dist/chunk-E5VYFAQO.js.map +1 -0
- package/dist/chunk-F3BE7NGM.js +520 -0
- package/dist/chunk-F3BE7NGM.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +3 -3
- package/dist/hooks/mutation-mode.d.ts +40 -30
- package/dist/hooks/mutation-mode.js +4 -1
- package/dist/hooks/related-keys.d.ts +2 -2
- package/dist/hooks/use-capabilities.d.ts +1 -1
- package/dist/hooks/use-one.d.ts +2 -2
- package/dist/hooks/use-resource-form.d.ts +10 -2
- package/dist/hooks/use-resource-form.js +3 -2
- package/dist/hooks/use-resource.d.ts +2 -2
- package/dist/hooks/use-resource.js +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/provider/index.d.ts +10 -6
- package/dist/provider/index.js +1 -1
- package/dist/{read-outcome-BQkYcV4t.d.ts → read-outcome-kgNhfuST.d.ts} +1 -1
- package/dist/schema/index.d.ts +4 -4
- package/dist/{types-C5bjsbxz.d.ts → types-5cuflvha.d.ts} +8 -0
- package/dist/{types-Dsqqr9s7.d.ts → types-DDOmG9hj.d.ts} +12 -1
- package/package.json +1 -1
- package/dist/chunk-DVW5GXWZ.js.map +0 -1
- package/dist/chunk-IZT2O57C.js.map +0 -1
- package/dist/chunk-QH4P5MI2.js +0 -278
- package/dist/chunk-QH4P5MI2.js.map +0 -1
- package/dist/chunk-RLY7TZUY.js.map +0 -1
package/README.md
CHANGED
|
@@ -160,8 +160,11 @@ plain sentence (the raw text stays on `cause`):
|
|
|
160
160
|
| `23503` foreign key (insert/update) | `details`: `Key (owner_id)=(…) is not present in table …` | "The selected item doesn't exist." | "Owner Id: the selected item doesn't exist." |
|
|
161
161
|
|
|
162
162
|
Only plain column names are taken from the store's text; the values it echoes
|
|
163
|
-
and constraint names never reach a message.
|
|
164
|
-
|
|
163
|
+
and constraint names never reach a message. A unique index on one column in a
|
|
164
|
+
simple expression (`lower(email::text)`) maps to that column; a unique
|
|
165
|
+
violation whose columns cannot be read still gets the safe sentence "This value
|
|
166
|
+
is already in use." with no field. Anything else (check constraints) is left
|
|
167
|
+
unmapped. Your own provider or `prepare` rule can
|
|
165
168
|
throw `new DataWriteError("invalid", "…", { fields: { slug: "Pick another slug." } })`.
|
|
166
169
|
|
|
167
170
|
## Mutation modes
|
|
@@ -186,10 +189,26 @@ throw `new DataWriteError("invalid", "…", { fields: { slug: "Pick another slug
|
|
|
186
189
|
`undo` method) gets an `undoable` write at once; `undo()` then calls
|
|
187
190
|
`provider.undo(resource, { auditId })` with the audit id the write returned
|
|
188
191
|
(`strategy: "provider"`). Without an audit id no undo is offered.
|
|
189
|
-
- **
|
|
190
|
-
the
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
- **Undo outlives the form.** Pending writes belong to the `QueryClient`, not
|
|
193
|
+
to the hook: a form or page that unmounts during the delay (a dialog that
|
|
194
|
+
closes on success, a route change) leaves its writes waiting, and the undo
|
|
195
|
+
toast still works. They are sent when the delay ends.
|
|
196
|
+
- **Closing the tab sends.** On `beforeunload` and `pagehide`, waiting writes
|
|
197
|
+
are sent at once with `keepalive: true` in their params (`CreateParams`,
|
|
198
|
+
`UpdateParams`, `DeleteParams`). A fetch-based provider should pass it to
|
|
199
|
+
`fetch` and report `keepaliveWrites: true` in its capabilities. The
|
|
200
|
+
"Leave site?" prompt shows only while a write is being sent without that
|
|
201
|
+
guarantee (the built-in Supabase provider cannot pass keepalive through
|
|
202
|
+
supabase-js, so it prompts while a write is in flight).
|
|
203
|
+
- **Writes on one record compose.** They are sent in the order they were
|
|
204
|
+
made, one after another; sending a later one (its delay ended, `commit()`)
|
|
205
|
+
first sends the earlier ones still waiting, which can then no longer be
|
|
206
|
+
undone. Undoing (or a failure of) an earlier edit takes only its own fields
|
|
207
|
+
out of the cache, keeping later pending edits shown. A later pending edit
|
|
208
|
+
that kept the earlier edit's values unchanged (a form opened from the
|
|
209
|
+
patched cache) has them put back before it is sent, so it never sends the
|
|
210
|
+
undone values; one whose values are not row-shaped (`toFormValues`) cannot
|
|
211
|
+
be fixed, so it is dropped and reported through `onError`.
|
|
193
212
|
- **What is patched:** an update merges the values into the row in every
|
|
194
213
|
cached list (`[resource, "list", …]`) and detail (`[resource, "detail", id,
|
|
195
214
|
…]`); a delete removes the row and lowers `total`. A create patches nothing
|
|
@@ -197,10 +216,12 @@ throw `new DataWriteError("invalid", "…", { fields: { slug: "Pick another slug
|
|
|
197
216
|
`undoable` only change when it is sent and when the caller moves on. A
|
|
198
217
|
rollback restores only the row it patched, so concurrent writes on other
|
|
199
218
|
rows survive.
|
|
200
|
-
- **
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
219
|
+
- **Refetches keep the patch.** While a write is pending, its patch is
|
|
220
|
+
re-applied to any `[resource, "list", …]` or `[resource, "detail", …]` data
|
|
221
|
+
that lands in the cache: a refetch, a window-focus refetch, a new page,
|
|
222
|
+
filter or search. A deleted row stays gone and an edit stays shown until the
|
|
223
|
+
write settles; then the server's rows win. A fetch already in flight is not
|
|
224
|
+
cancelled. Each write refetches `[resource]` when it settles.
|
|
204
225
|
|
|
205
226
|
```ts
|
|
206
227
|
const tasks = useResource("tasks", {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useUnsavedChangesGuard } from './chunk-XQO4OH2N.js';
|
|
2
2
|
import { isDataWriteError } from './chunk-5NHR6ZXS.js';
|
|
3
3
|
import { titleCase } from './chunk-RYEXWQFD.js';
|
|
4
|
-
import {
|
|
4
|
+
import { sameValue, cachedRecord, executeWrite, invalidateResource } from './chunk-F3BE7NGM.js';
|
|
5
5
|
import { relatedKeys } from './chunk-7NUL32W7.js';
|
|
6
6
|
import { useOne } from './chunk-ZK5C3OOT.js';
|
|
7
7
|
import { useDataProvider, useStorage } from './chunk-GGSXSI6Z.js';
|
|
@@ -11,6 +11,13 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import { useQueryClient } from '@tanstack/react-query';
|
|
13
13
|
|
|
14
|
+
function structuredCloneSafe(value) {
|
|
15
|
+
try {
|
|
16
|
+
return typeof structuredClone === "function" ? structuredClone(value) : value;
|
|
17
|
+
} catch {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
14
21
|
function inferFieldType(value) {
|
|
15
22
|
if (typeof value === "boolean") return "switch";
|
|
16
23
|
if (typeof value === "number") return "number";
|
|
@@ -241,7 +248,8 @@ function useResourceForm(options) {
|
|
|
241
248
|
warnWhenUnsaved = false,
|
|
242
249
|
mutationMode = "pessimistic",
|
|
243
250
|
undoDelayMs,
|
|
244
|
-
onUndoable
|
|
251
|
+
onUndoable,
|
|
252
|
+
visible = true
|
|
245
253
|
} = options;
|
|
246
254
|
const autosaveEnabled = !!autosave;
|
|
247
255
|
const debounceMs = typeof autosave === "object" ? autosave.debounceMs ?? 1e3 : 1e3;
|
|
@@ -354,7 +362,15 @@ function useResourceForm(options) {
|
|
|
354
362
|
savedCleanRef.current = true;
|
|
355
363
|
setSavedClean(true);
|
|
356
364
|
}, []);
|
|
357
|
-
const
|
|
365
|
+
const mountedRef = useRef(true);
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
mountedRef.current = true;
|
|
368
|
+
return () => {
|
|
369
|
+
mountedRef.current = false;
|
|
370
|
+
};
|
|
371
|
+
}, []);
|
|
372
|
+
const visibleRef = useRef(visible);
|
|
373
|
+
visibleRef.current = visible;
|
|
358
374
|
const showError = useCallback(
|
|
359
375
|
(error) => {
|
|
360
376
|
const fieldErrors = isDataWriteError(error) ? error.fields : void 0;
|
|
@@ -378,20 +394,24 @@ function useResourceForm(options) {
|
|
|
378
394
|
[form, resolvedFields]
|
|
379
395
|
);
|
|
380
396
|
const reportError = useCallback(
|
|
381
|
-
(error) => {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
397
|
+
(error, handedOff) => {
|
|
398
|
+
const onForm = !handedOff || mountedRef.current && visibleRef.current && sameValue(form.getValues(), handedOff);
|
|
399
|
+
if (onForm) {
|
|
400
|
+
savedCleanRef.current = false;
|
|
401
|
+
setSavedClean(false);
|
|
402
|
+
showError(error);
|
|
403
|
+
}
|
|
385
404
|
if (onError) {
|
|
386
405
|
onError(error);
|
|
387
406
|
} else {
|
|
388
407
|
console.error(`[useResourceForm] ${action} failed for "${resource}":`, error);
|
|
389
408
|
}
|
|
390
409
|
},
|
|
391
|
-
[showError, onError, action, resource]
|
|
410
|
+
[form, showError, onError, action, resource]
|
|
392
411
|
);
|
|
393
412
|
const handleSubmit = useCallback(
|
|
394
413
|
async (formData) => {
|
|
414
|
+
const shown = structuredCloneSafe(form.getValues());
|
|
395
415
|
try {
|
|
396
416
|
let recordId;
|
|
397
417
|
if (action === "edit") {
|
|
@@ -413,9 +433,9 @@ function useResourceForm(options) {
|
|
|
413
433
|
mode: mutationMode,
|
|
414
434
|
undoDelayMs,
|
|
415
435
|
onUndoable,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const res = action === "create" ? await provider.create(resource, { data: formData }) : await provider.update(resource, { id: recordId, data: formData });
|
|
436
|
+
send: async (ctx) => {
|
|
437
|
+
const keepalive = ctx.keepalive ? { keepalive: true } : {};
|
|
438
|
+
const res = action === "create" ? await provider.create(resource, { data: formData, ...keepalive }) : await provider.update(resource, { id: recordId, data: ctx.data ?? formData, ...keepalive });
|
|
419
439
|
return { record: res.data, audit: res.audit };
|
|
420
440
|
},
|
|
421
441
|
committed: async (result) => {
|
|
@@ -439,7 +459,7 @@ function useResourceForm(options) {
|
|
|
439
459
|
);
|
|
440
460
|
await Promise.all(related.map((queryKey) => queryClient.invalidateQueries({ queryKey: [...queryKey] })));
|
|
441
461
|
},
|
|
442
|
-
failedLater: reportError
|
|
462
|
+
failedLater: (error) => reportError(error, shown)
|
|
443
463
|
});
|
|
444
464
|
markSaved();
|
|
445
465
|
if (outcome.status === "committed") {
|
|
@@ -453,7 +473,7 @@ function useResourceForm(options) {
|
|
|
453
473
|
reportError(err instanceof Error ? err : new Error(String(err)));
|
|
454
474
|
}
|
|
455
475
|
},
|
|
456
|
-
[action, provider, resource, id, effectiveData, formSource, queryClient, onSuccess, autosaveEnabled, storage, invalidates, mutationMode, undoDelayMs, onUndoable,
|
|
476
|
+
[form, action, provider, resource, id, effectiveData, formSource, queryClient, onSuccess, autosaveEnabled, storage, invalidates, mutationMode, undoDelayMs, onUndoable, reportError, markSaved]
|
|
457
477
|
);
|
|
458
478
|
const onSubmit = useCallback(
|
|
459
479
|
(e) => form.handleSubmit(handleSubmit)(e),
|
|
@@ -473,5 +493,5 @@ function useResourceForm(options) {
|
|
|
473
493
|
}
|
|
474
494
|
|
|
475
495
|
export { buildArrayItemDefaults, useResourceForm };
|
|
476
|
-
//# sourceMappingURL=chunk-
|
|
477
|
-
//# sourceMappingURL=chunk-
|
|
496
|
+
//# sourceMappingURL=chunk-3JSFTLDV.js.map
|
|
497
|
+
//# sourceMappingURL=chunk-3JSFTLDV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-resource-form.ts"],"names":[],"mappings":"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","file":"chunk-3JSFTLDV.js","sourcesContent":["import { useMemo, useCallback, useEffect, useRef, useState } from \"react\";\nimport { useForm, type UseFormReturn, type Resolver } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { z } from \"zod\";\nimport { useOne } from \"./use-one\";\nimport { useDataProvider, useStorage } from \"../provider/context\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { titleCase } from \"../utils\";\nimport type { ResourceFormFieldDef, FieldType } from \"../form-types\";\nimport type { BaseRecord, InvalidationKey } from \"../provider/types\";\nimport { relatedKeys } from \"./related-keys\";\nimport { isDataWriteError } from \"../schema/write-outcome\";\nimport {\n cachedRecord,\n executeWrite,\n invalidateResource,\n sameValue,\n type MutationModeOptions,\n} from \"./mutation-mode\";\nimport { useUnsavedChangesGuard } from \"./use-unsaved-changes-guard\";\n\n/** A deep copy where the platform allows one (form values are plain data). */\nfunction structuredCloneSafe<T>(value: T): T {\n try {\n return typeof structuredClone === \"function\" ? structuredClone(value) : value;\n } catch {\n return value;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseResourceFormOptions extends MutationModeOptions {\n resource: string;\n action: \"create\" | \"edit\";\n id?: string;\n initialData?: Record<string, unknown>;\n fields?: ResourceFormFieldDef[];\n /** Full dataset used to auto-detect select fields from unique values. */\n listData?: Record<string, unknown>[];\n /**\n * The form is done. After the save in `pessimistic` mode (with the stored\n * row); at once in `optimistic` and `undoable` modes (with the submitted\n * values over the record, and no `id` for a create).\n */\n onSuccess?: (data: Record<string, unknown>) => void;\n /**\n * The save failed. In `optimistic` and `undoable` modes the form may be\n * gone by then, so show the error from here (genesis-ui's ResourceForm\n * toasts it when no `onError` is given).\n */\n onError?: (error: Error) => void;\n /**\n * Warn before unsaved changes are lost. Default false. While the form is\n * dirty, closing or reloading the tab shows the browser's prompt, and\n * `confirmLeave()` asks before in-app navigation (wire it to a router\n * blocker; see SKILL.md \"Unsaved changes\").\n */\n warnWhenUnsaved?: boolean | { message?: string };\n /**\n * Whether the form is on screen. Default true. Pass false while it is\n * hidden but still mounted (a closed dialog). An optimistic or undoable\n * save that fails later is put back on the form (field errors, unsaved\n * again) only while the form is mounted, visible and still holds the\n * values it saved; otherwise the failure only reaches `onError`.\n */\n visible?: boolean;\n /**\n * Pre-fill create form defaults from URL query params: a param named like a\n * field (`?status=draft`) becomes that field's starting value. Default true.\n * Pass false for a form that should not read the page's URL, e.g. one\n * opened from a list whose own filters live in the query string.\n */\n prefillFromUrl?: boolean;\n /**\n * Generate a placeholder (\"Enter name\", \"name@example.com\") for a field\n * whose `placeholder` is unset. Default true. A placeholder that only\n * repeats the label adds noise without information, so `ResourceForm`\n * turns this off unless asked.\n */\n autoPlaceholder?: boolean;\n /** Validation trigger mode. Default \"onBlur\". */\n validationMode?: \"onBlur\" | \"onChange\" | \"onSubmit\";\n /** Enable autosave drafts to localStorage for create mode. */\n autosave?: boolean | { debounceMs?: number };\n /**\n * Other query keys to invalidate after a save, on top of `[resource]` and\n * the provider's own `invalidates`.\n */\n invalidates?: InvalidationKey[];\n}\n\nexport interface UseResourceFormReturn {\n form: UseFormReturn<Record<string, unknown>>;\n resolvedFields: ResourceFormFieldDef[];\n onSubmit: (e?: { preventDefault?: () => void }) => Promise<void>;\n /** Raw save handler — use with form.handleSubmit(handleSave, onInvalid). */\n handleSave: (data: Record<string, unknown>) => Promise<void>;\n isSaving: boolean;\n isLoading: boolean;\n /**\n * The form has changes that have not been saved. False again once a save\n * succeeds (or, in optimistic / undoable modes, is handed off).\n */\n isDirty: boolean;\n /**\n * For in-app navigation: true when it is fine to leave (nothing unsaved,\n * `warnWhenUnsaved` off, or the user confirmed). Asks with\n * `window.confirm` otherwise.\n */\n confirmLeave: () => boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Infer a field type from a value */\nfunction inferFieldType(value: unknown): FieldType {\n if (typeof value === \"boolean\") return \"switch\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"string\") {\n if (/^\\d{4}-\\d{2}-\\d{2}/.test(value)) return \"date\";\n if (value.includes(\"@\") && value.includes(\".\")) return \"email\";\n if (/^https?:\\/\\//.test(value)) return \"url\";\n if (value.length > 100) return \"textarea\";\n }\n return \"text\";\n}\n\n/** Try to infer a select field from unique values across the full dataset */\nfunction inferSelectFromData(\n key: string,\n allData: Record<string, unknown>[] | undefined,\n): { type: \"select\"; options: { value: string; label: string }[] } | null {\n if (!allData || allData.length === 0) return null;\n const uniqueValues = new Set<string>();\n for (const row of allData) {\n const v = row[key];\n if (typeof v === \"string\" && v.length > 0) uniqueValues.add(v);\n }\n if (uniqueValues.size > 0 && uniqueValues.size <= 10) {\n return {\n type: \"select\" as const,\n options: Array.from(uniqueValues)\n .sort()\n .map((v) => ({ value: v, label: v })),\n };\n }\n return null;\n}\n\n/** Generate a sensible placeholder for an auto-inferred field */\nfunction defaultPlaceholder(key: string, type: FieldType): string {\n switch (type) {\n case \"email\": return \"name@example.com\";\n case \"url\": return \"https://...\";\n case \"number\": return \"0\";\n case \"date\": return \"YYYY-MM-DD\";\n case \"textarea\": return `Enter ${titleCase(key).toLowerCase()}...`;\n default: return `Enter ${titleCase(key).toLowerCase()}`;\n }\n}\n\n/** Resolve fields from data keys when none provided */\nfunction resolveFields(\n fields: ResourceFormFieldDef[] | undefined,\n data: Record<string, unknown> | undefined,\n allData?: Record<string, unknown>[],\n autoPlaceholder = true,\n): ResourceFormFieldDef[] {\n const placeholderFor = (key: string, type: FieldType): string | undefined =>\n autoPlaceholder ? defaultPlaceholder(key, type) : undefined;\n if (fields && fields.length > 0) {\n return fields.map((f) => {\n const inferred = f.type ?? (data ? inferFieldType(data[f.key]) : \"text\");\n return {\n ...f,\n label: f.label ?? titleCase(f.key),\n type: inferred,\n placeholder: f.placeholder ?? placeholderFor(f.key, inferred),\n required: f.required ?? (inferred !== \"switch\" && inferred !== \"checkbox\" && inferred !== \"hidden\"),\n };\n });\n }\n\n if (!data) return [];\n\n return Object.keys(data)\n .filter((key) => key !== \"id\" && key !== \"created_at\" && key !== \"updated_at\")\n .map((key) => {\n // Check for select-like field first (string with few unique values)\n if (typeof data[key] === \"string\") {\n const selectInfo = inferSelectFromData(key, allData);\n if (selectInfo) {\n return {\n key,\n label: titleCase(key),\n type: selectInfo.type,\n options: selectInfo.options,\n placeholder: autoPlaceholder\n ? `Select ${titleCase(key).toLowerCase()}...`\n : undefined,\n required: true,\n };\n }\n }\n\n const inferred = inferFieldType(data[key]);\n return {\n key,\n label: titleCase(key),\n type: inferred,\n placeholder: placeholderFor(key, inferred),\n required: inferred !== \"switch\" && inferred !== \"checkbox\" && inferred !== \"hidden\",\n };\n });\n}\n\n/** Build a zod schema from resolved fields */\nfunction buildSchema(fields: ResourceFormFieldDef[]) {\n const shape: Record<string, z.ZodTypeAny> = {};\n\n for (const field of fields) {\n const fieldType = field.type ?? \"text\";\n\n if (field.hidden || fieldType === \"hidden\") continue;\n\n // File/image fields store File objects — z.any() is intentional here because\n // the File API shape varies by platform (browser File, React Native asset, etc.)\n // and form libraries may wrap files differently.\n if (fieldType === \"file\" || fieldType === \"image\") {\n shape[field.key] = field.required !== false ? z.unknown().refine((v) => v != null, { message: `${field.label ?? titleCase(field.key)} is required` }) : z.unknown().optional();\n continue;\n }\n\n switch (fieldType) {\n case \"number\": {\n let schema = z.coerce.number({ message: \"Must be a number\" });\n if (field.min != null) schema = schema.min(field.min);\n if (field.max != null) schema = schema.max(field.max);\n shape[field.key] = field.required !== false\n ? schema\n : schema.optional();\n break;\n }\n case \"array\": {\n if (field.arrayFields) {\n const itemShape: Record<string, z.ZodTypeAny> = {};\n for (const sub of field.arrayFields) {\n const subType = sub.type ?? \"text\";\n if (subType === \"number\") {\n let s = z.coerce.number({ message: \"Must be a number\" });\n if (sub.min != null) s = s.min(sub.min);\n if (sub.max != null) s = s.max(sub.max);\n itemShape[sub.key] = sub.required !== false ? s : s.optional();\n } else if (subType === \"switch\" || subType === \"checkbox\") {\n itemShape[sub.key] = z.boolean().optional();\n } else {\n let s = z.string();\n if (sub.required !== false) {\n s = s.min(1, `${sub.label ?? titleCase(sub.key)} is required`);\n }\n if (subType === \"email\") s = s.email(\"Invalid email address\");\n if (subType === \"url\") s = s.url(\"Invalid URL\");\n if (sub.maxLength != null) {\n s = s.max(\n sub.maxLength,\n `${sub.label ?? titleCase(sub.key)} must be ${sub.maxLength} characters or fewer`,\n );\n }\n itemShape[sub.key] = sub.required === false ? s.optional().or(z.literal(\"\")) : s;\n }\n }\n let arraySchema: z.ZodTypeAny = z.array(z.object(itemShape));\n if (field.arrayMin != null) arraySchema = (arraySchema as z.ZodArray<z.ZodTypeAny>).min(field.arrayMin, `Minimum ${field.arrayMin} items required`);\n if (field.arrayMax != null) arraySchema = (arraySchema as z.ZodArray<z.ZodTypeAny>).max(field.arrayMax, `Maximum ${field.arrayMax} items allowed`);\n shape[field.key] = arraySchema;\n }\n break;\n }\n case \"switch\":\n case \"checkbox\": {\n shape[field.key] = z.boolean().optional();\n break;\n }\n default: {\n // text, email, url, textarea, date, select\n let schema = z.string();\n if (field.required !== false) {\n schema = schema.min(1, `${field.label ?? titleCase(field.key)} is required`);\n }\n if (fieldType === \"email\") {\n schema = schema.email(\"Invalid email address\");\n }\n if (fieldType === \"url\") {\n schema = schema.url(\"Invalid URL\");\n }\n if (field.maxLength != null) {\n schema = schema.max(\n field.maxLength,\n `${field.label ?? titleCase(field.key)} must be ${field.maxLength} characters or fewer`,\n );\n }\n shape[field.key] = field.required === false\n ? schema.optional().or(z.literal(\"\"))\n : schema;\n break;\n }\n }\n }\n\n return z.object(shape).passthrough();\n}\n\n/** Build default values for a single array item from its sub-field definitions */\nexport function buildArrayItemDefaults(\n arrayFields: ResourceFormFieldDef[],\n): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n for (const sub of arrayFields) {\n const subType = sub.type ?? \"text\";\n if (subType === \"number\") defaults[sub.key] = undefined;\n else if (subType === \"switch\" || subType === \"checkbox\") defaults[sub.key] = false;\n else defaults[sub.key] = sub.defaultValue ?? \"\";\n }\n return defaults;\n}\n\n/** Build default values from fields and existing data */\nfunction buildDefaults(\n fields: ResourceFormFieldDef[],\n data: Record<string, unknown> | undefined,\n action: \"create\" | \"edit\",\n prefillFromUrl: boolean,\n): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n for (const field of fields) {\n if (data && field.key in data) {\n defaults[field.key] = data[field.key];\n } else if (field.defaultValue !== undefined) {\n defaults[field.key] = field.defaultValue;\n } else {\n const fieldType = field.type ?? \"text\";\n switch (fieldType) {\n case \"array\":\n defaults[field.key] = field.arrayMin\n ? Array.from({ length: field.arrayMin }, () =>\n buildArrayItemDefaults(field.arrayFields ?? []),\n )\n : [];\n break;\n case \"number\":\n defaults[field.key] = undefined;\n break;\n case \"switch\":\n case \"checkbox\":\n defaults[field.key] = false;\n break;\n default:\n defaults[field.key] = \"\";\n break;\n }\n }\n }\n\n // URL pre-fill for create mode\n if (typeof window !== \"undefined\" && typeof window.location !== \"undefined\" && typeof window.location.search === \"string\" && action === \"create\" && prefillFromUrl !== false) {\n const params = new URLSearchParams(window.location.search);\n for (const field of fields) {\n const val = params.get(field.key);\n if (val != null) {\n defaults[field.key] = field.type === \"number\" ? Number(val) : val;\n }\n }\n }\n\n return defaults;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useResourceForm(options: UseResourceFormOptions): UseResourceFormReturn {\n const {\n resource,\n action,\n id,\n initialData,\n fields: fieldsProp,\n listData,\n onSuccess,\n onError,\n prefillFromUrl = true,\n autoPlaceholder = true,\n validationMode,\n autosave,\n invalidates,\n warnWhenUnsaved = false,\n mutationMode = \"pessimistic\",\n undoDelayMs,\n onUndoable,\n visible = true,\n } = options;\n\n const autosaveEnabled = !!autosave;\n const debounceMs = typeof autosave === \"object\" ? autosave.debounceMs ?? 1000 : 1000;\n\n const provider = useDataProvider();\n const storage = useStorage();\n const queryClient = useQueryClient();\n\n // Fetch record for edit mode when no initialData\n const needsFetch = action === \"edit\" && !initialData && !!id;\n const fetchResult = useOne(\n resource,\n needsFetch ? id : undefined,\n { enabled: needsFetch },\n );\n\n const fetchedData = fetchResult.data ?? undefined;\n const effectiveData = initialData ?? fetchedData;\n const isLoading = needsFetch && fetchResult.isLoading;\n\n // In edit mode the form starts from the provider's form shape of the row\n // (`DataProvider.toFormValues`), for a resource whose form differs from\n // its table. The record id still comes from the row itself.\n const formSource = useMemo(\n () =>\n action === \"edit\" && effectiveData && provider.toFormValues\n ? provider.toFormValues(resource, effectiveData)\n : effectiveData,\n [action, effectiveData, provider, resource],\n );\n\n // Resolve fields\n const resolvedFields = useMemo(\n () => resolveFields(fieldsProp, formSource, listData, autoPlaceholder),\n [fieldsProp, formSource, listData, autoPlaceholder],\n );\n\n // Build schema\n const schema = useMemo(\n () => buildSchema(resolvedFields),\n [resolvedFields],\n );\n\n // Build defaults (with optional draft restoration)\n const defaultValues = useMemo(() => {\n const defaults = buildDefaults(resolvedFields, formSource, action, prefillFromUrl);\n if (autosaveEnabled && action === \"create\") {\n const key = `genesis-draft:${resource}:new`;\n const saved = storage.getItem(key);\n if (saved) {\n try {\n const draft = JSON.parse(saved);\n if (draft && typeof draft === \"object\" && !Array.isArray(draft)) {\n // Only restore keys that exist in the field definitions\n const validKeys = new Set(resolvedFields.map((f) => f.key));\n for (const k of Object.keys(draft)) {\n if (validKeys.has(k)) {\n defaults[k] = draft[k];\n }\n }\n } else {\n console.warn(`[useResourceForm] Draft for \"${resource}\" has unexpected shape, removing`);\n storage.removeItem(key);\n }\n } catch {\n console.warn(`[useResourceForm] Corrupt draft for \"${resource}\", removing`);\n storage.removeItem(key);\n }\n }\n }\n return defaults;\n }, [resolvedFields, formSource, action, prefillFromUrl, autosaveEnabled, resource, storage]);\n\n // Form\n // Cast the resolver because zod v4's passthrough() produces `unknown` for input,\n // but react-hook-form expects FieldValues (Record<string, any>).\n const resolver = useMemo(\n () => zodResolver(schema) as unknown as Resolver<Record<string, unknown>>,\n [schema],\n );\n\n const form = useForm<Record<string, unknown>>({\n resolver,\n defaultValues,\n values: action === \"edit\" ? defaultValues : undefined,\n mode: validationMode ?? \"onBlur\",\n reValidateMode: \"onChange\",\n shouldUnregister: true,\n });\n\n // Autosave effect — persist form values to storage for create mode\n useEffect(() => {\n if (!autosaveEnabled || action !== \"create\") return;\n const key = `genesis-draft:${resource}:new`;\n let timer: ReturnType<typeof setTimeout>;\n\n const subscription = form.watch((values) => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n try {\n storage.setItem(key, JSON.stringify(values));\n } catch (err) {\n console.warn(`[useResourceForm] Autosave failed for \"${resource}\":`, err instanceof Error ? err.message : String(err));\n }\n }, debounceMs);\n });\n\n return () => {\n clearTimeout(timer);\n subscription.unsubscribe();\n };\n }, [autosaveEnabled, resource, action, form, debounceMs, storage]);\n\n // Unsaved changes: RHF's isDirty, cleared by a successful save until the\n // next edit (a create form keeps its values after saving, so RHF alone\n // would still call it dirty).\n const rhfDirty = form.formState.isDirty;\n const [savedClean, setSavedClean] = useState(false);\n const savedCleanRef = useRef(false);\n useEffect(() => {\n const subscription = form.watch((_values, info) => {\n // `watch` also reports resets (no field name); only an edit dirties.\n if (!info.name) return;\n savedCleanRef.current = false;\n setSavedClean(false);\n });\n return () => subscription.unsubscribe();\n }, [form]);\n const isDirty = rhfDirty && !savedClean;\n const warn = !!warnWhenUnsaved;\n const { confirmLeave: guardConfirm } = useUnsavedChangesGuard({\n when: warn && isDirty,\n message: typeof warnWhenUnsaved === \"object\" ? warnWhenUnsaved.message : undefined,\n });\n const confirmLeave = useCallback(\n // The ref, not state: a router blocker may ask in the same tick as the\n // save's onSuccess navigates, before a re-render.\n () => (!warn || savedCleanRef.current ? true : guardConfirm()),\n [warn, guardConfirm],\n );\n const markSaved = useCallback(() => {\n savedCleanRef.current = true;\n setSavedClean(true);\n }, []);\n\n // A save that fails after it was handed off only comes back to the form\n // while the user can still see it.\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n const visibleRef = useRef(visible);\n visibleRef.current = visible;\n\n /** A failed save, on the form: per-field messages and a root summary. */\n const showError = useCallback(\n (error: Error) => {\n const fieldErrors = isDataWriteError(error) ? error.fields : undefined;\n const known = new Set(resolvedFields.map((f) => f.key));\n let first: string | undefined;\n if (fieldErrors) {\n for (const [field, message] of Object.entries(fieldErrors)) {\n if (!known.has(field)) continue;\n form.setError(field, { type: \"server\", message });\n first ??= field;\n }\n }\n form.setError(\"root\", { type: \"server\", message: error.message });\n if (first) {\n try {\n form.setFocus(first);\n } catch {\n // Not rendered (hidden or custom renderForm): the summary still says it.\n }\n }\n },\n [form, resolvedFields],\n );\n\n const reportError = useCallback(\n (error: Error, handedOff?: Record<string, unknown>) => {\n // A save handed off optimistically and then refused is unsaved again,\n // if the form still shows what it saved; a closed, hidden or reset\n // form is left alone.\n const onForm =\n !handedOff ||\n (mountedRef.current && visibleRef.current && sameValue(form.getValues(), handedOff));\n if (onForm) {\n savedCleanRef.current = false;\n setSavedClean(false);\n showError(error);\n }\n if (onError) {\n onError(error);\n } else {\n console.error(`[useResourceForm] ${action} failed for \"${resource}\":`, error);\n }\n },\n [form, showError, onError, action, resource],\n );\n\n // Submit handler\n const handleSubmit = useCallback(\n async (formData: Record<string, unknown>) => {\n // What the form shows as this save goes out (raw input values, before\n // any resolver coercion), to tell later whether it still does.\n const shown = structuredCloneSafe(form.getValues());\n try {\n let recordId: string | number | undefined;\n if (action === \"edit\") {\n recordId = id ?? String((effectiveData as Record<string, unknown>)?.id ?? \"\");\n if (!recordId) {\n throw new Error(\"Cannot update record: no ID available. Provide an id prop or ensure the record has an id field.\");\n }\n }\n const before = recordId != null ? cachedRecord(queryClient, resource, recordId) : undefined;\n const outcome = await executeWrite<BaseRecord>({\n queryClient,\n provider,\n resource,\n kind: action === \"create\" ? \"create\" : \"update\",\n id: recordId,\n // The cache holds rows; patch it only when the form's values are\n // row-shaped (no provider.toFormValues reshaping this record).\n data: formSource === effectiveData ? formData : undefined,\n mode: mutationMode,\n undoDelayMs,\n onUndoable,\n send: async (ctx) => {\n const keepalive = ctx.keepalive ? { keepalive: true } : {};\n const res =\n action === \"create\"\n ? await provider.create(resource, { data: formData, ...keepalive })\n : await provider.update(resource, { id: recordId!, data: ctx.data ?? formData, ...keepalive });\n return { record: res.data, audit: res.audit };\n },\n committed: async (result) => {\n // Clear draft on successful save\n if (autosaveEnabled && action === \"create\") {\n try {\n storage.removeItem(`genesis-draft:${resource}:new`);\n } catch (err) {\n console.warn(`[useResourceForm] Draft cleanup failed for \"${resource}\":`, err instanceof Error ? err.message : String(err));\n }\n }\n // Invalidate queries to refresh data: the resource's own, then any\n // related keys the provider or the caller named.\n await invalidateResource(queryClient, resource);\n const related = relatedKeys(\n provider,\n {\n resource,\n kind: action === \"create\" ? \"create\" : \"update\",\n id: (recordId ?? result.id) as string | number,\n record: result,\n },\n invalidates,\n );\n await Promise.all(related.map((queryKey) => queryClient.invalidateQueries({ queryKey: [...queryKey] })));\n },\n failedLater: (error) => reportError(error, shown),\n });\n markSaved();\n if (outcome.status === \"committed\") {\n onSuccess?.(outcome.record);\n } else {\n onSuccess?.(\n action === \"edit\"\n ? { ...(before ?? effectiveData ?? {}), ...formData, id: recordId }\n : { ...formData },\n );\n }\n } catch (err) {\n reportError(err instanceof Error ? err : new Error(String(err)));\n }\n },\n [form, action, provider, resource, id, effectiveData, formSource, queryClient, onSuccess, autosaveEnabled, storage, invalidates, mutationMode, undoDelayMs, onUndoable, reportError, markSaved],\n );\n\n const onSubmit = useCallback(\n (e?: { preventDefault?: () => void }) => form.handleSubmit(handleSubmit)(e as React.BaseSyntheticEvent),\n [form, handleSubmit],\n );\n\n const isSaving = form.formState.isSubmitting;\n\n return {\n form,\n resolvedFields,\n onSubmit,\n handleSave: handleSubmit,\n isSaving,\n isLoading,\n isDirty,\n confirmLeave,\n };\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { invalidateResource,
|
|
1
|
+
import { invalidateResource, executeWrite, cachedRecord } from './chunk-F3BE7NGM.js';
|
|
2
2
|
import { relatedKeys } from './chunk-7NUL32W7.js';
|
|
3
3
|
import { useDataProvider } from './chunk-GGSXSI6Z.js';
|
|
4
4
|
import { useState, useCallback, useMemo } from 'react';
|
|
@@ -104,7 +104,6 @@ function useResource(resource, options = {}) {
|
|
|
104
104
|
[resource]
|
|
105
105
|
);
|
|
106
106
|
const reportError = onError ?? defaultOnError;
|
|
107
|
-
const delayed = useDelayedWrites();
|
|
108
107
|
const modeArgs = {
|
|
109
108
|
queryClient,
|
|
110
109
|
provider,
|
|
@@ -112,7 +111,6 @@ function useResource(resource, options = {}) {
|
|
|
112
111
|
mode: mutationMode,
|
|
113
112
|
undoDelayMs,
|
|
114
113
|
onUndoable,
|
|
115
|
-
delayed,
|
|
116
114
|
failedLater: reportError
|
|
117
115
|
};
|
|
118
116
|
const createMutation = useMutation({
|
|
@@ -121,8 +119,8 @@ function useResource(resource, options = {}) {
|
|
|
121
119
|
...modeArgs,
|
|
122
120
|
kind: "create",
|
|
123
121
|
data,
|
|
124
|
-
send: async () => {
|
|
125
|
-
const result = await provider.create(resource, { data });
|
|
122
|
+
send: async ({ keepalive }) => {
|
|
123
|
+
const result = await provider.create(resource, { data, ...keepalive ? { keepalive } : {} });
|
|
126
124
|
return { record: result.data, audit: result.audit };
|
|
127
125
|
},
|
|
128
126
|
committed: (record) => {
|
|
@@ -142,8 +140,12 @@ function useResource(resource, options = {}) {
|
|
|
142
140
|
kind: "update",
|
|
143
141
|
id,
|
|
144
142
|
data,
|
|
145
|
-
send: async () => {
|
|
146
|
-
const result = await provider.update(resource, {
|
|
143
|
+
send: async (ctx) => {
|
|
144
|
+
const result = await provider.update(resource, {
|
|
145
|
+
id,
|
|
146
|
+
data: ctx.data ?? data,
|
|
147
|
+
...ctx.keepalive ? { keepalive: true } : {}
|
|
148
|
+
});
|
|
147
149
|
return { record: result.data, audit: result.audit };
|
|
148
150
|
},
|
|
149
151
|
committed: (record) => {
|
|
@@ -161,8 +163,8 @@ function useResource(resource, options = {}) {
|
|
|
161
163
|
...modeArgs,
|
|
162
164
|
kind: "delete",
|
|
163
165
|
id,
|
|
164
|
-
send: async () => {
|
|
165
|
-
const result = await provider.deleteOne(resource, { id });
|
|
166
|
+
send: async ({ keepalive }) => {
|
|
167
|
+
const result = await provider.deleteOne(resource, { id, ...keepalive ? { keepalive } : {} });
|
|
166
168
|
return { record: void 0, audit: result?.audit };
|
|
167
169
|
},
|
|
168
170
|
committed: () => {
|
|
@@ -205,5 +207,5 @@ function useResource(resource, options = {}) {
|
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
export { useResource };
|
|
208
|
-
//# sourceMappingURL=chunk-
|
|
209
|
-
//# sourceMappingURL=chunk-
|
|
210
|
+
//# sourceMappingURL=chunk-6Q477CHB.js.map
|
|
211
|
+
//# sourceMappingURL=chunk-6Q477CHB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-resource.ts"],"names":["keepPreviousData","keepPreviousPlaceholder"],"mappings":"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","file":"chunk-6Q477CHB.js","sourcesContent":["import { useState, useCallback, useMemo } from \"react\";\nimport {\n keepPreviousData as keepPreviousPlaceholder,\n useQuery,\n useMutation,\n useQueryClient,\n type UseQueryResult,\n type UseMutationResult,\n} from \"@tanstack/react-query\";\nimport { useDataProvider } from \"../provider/context\";\nimport type {\n BaseRecord,\n Identifier,\n SortParam,\n FilterParam,\n ListParams,\n ListResult,\n InvalidationKey,\n WriteEvent,\n} from \"../provider/types\";\nimport { relatedKeys } from \"./related-keys\";\nimport {\n cachedRecord,\n executeWrite,\n invalidateResource,\n type MutationModeOptions,\n} from \"./mutation-mode\";\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface UseResourceOptions<T> extends MutationModeOptions {\n defaultSort?: SortParam;\n /**\n * Re-synced whenever its value changes (deep-equal, order-independent),\n * not just its identity — safe to pass a scope/audience-derived array that\n * changes across renders. This only covers CHANGES on an already-mounted\n * hook, though: if this starts `undefined` while the real value resolves\n * asynchronously, gate `enabled` on that same readiness signal too — the\n * hook cannot tell \"no defaultFilters intended\" apart from \"not resolved\n * yet,\" so an ungated hook's very first fetch can still run unscoped.\n */\n defaultFilters?: FilterParam[];\n defaultPerPage?: number;\n /** The page to start on (from 1), e.g. restored from the URL. Default 1. */\n defaultPage?: number;\n /** The search to start with, e.g. restored from the URL. Default \"\". */\n defaultSearch?: string;\n /** Supabase select/join syntax, e.g. \"*, author:profiles(name)\". */\n select?: string;\n /** Fields to search across when setSearch is called. Required for server-side search. */\n searchFields?: string[];\n /** Disable auto-fetching. */\n enabled?: boolean;\n /**\n * Other query keys to invalidate after a create, update or delete, on top\n * of `[resource]` and the provider's own `invalidates` (e.g. `[[\"me\"]]`\n * when a write changes the signed-in user's roles).\n */\n invalidates?: InvalidationKey[];\n /**\n * While the next page / sort / search is loading, keep returning the\n * previous result (`list.isPlaceholderData` is true meanwhile) instead of\n * dropping to `undefined`. A paged list then stays on screen between pages\n * rather than flashing a skeleton. Default false.\n */\n keepPreviousData?: boolean;\n /**\n * After the write commits, with the provider's row. In `optimistic` and\n * `undoable` modes that is after `create` / `update` / `remove` have\n * already resolved.\n */\n onCreateSuccess?: (data: T) => void;\n onUpdateSuccess?: (data: T) => void;\n onDeleteSuccess?: () => void;\n /**\n * A write failed. In `optimistic` and `undoable` modes this is the only\n * place a failure is reported (the cache has already been put back), so\n * show it to the user here.\n */\n onError?: (error: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport interface UseResourceReturn<T> {\n /** TanStack Query result for the list query. */\n list: UseQueryResult<ListResult<T>>;\n /** Current list params (pagination, sort, filters, search). */\n params: ListParams;\n setSort: (sort: SortParam | undefined) => void;\n setFilters: (filters: FilterParam[]) => void;\n setPage: (page: number) => void;\n setPerPage: (perPage: number) => void;\n setSearch: (query: string) => void;\n /**\n * Create a new record. Returns the created item. In `optimistic` and\n * `undoable` modes it resolves at once with the values given (no `id` yet;\n * `onCreateSuccess` gets the stored row).\n */\n create: (data: Partial<T>) => Promise<T>;\n /**\n * Update an existing record by id. Returns the updated item. In\n * `optimistic` and `undoable` modes it resolves at once with the cached\n * row merged with `data`.\n */\n update: (id: Identifier, data: Partial<T>) => Promise<T>;\n /** Delete a record by id. Resolves at once in `optimistic` / `undoable` modes. */\n remove: (id: Identifier) => Promise<void>;\n /** True when any mutation is in-flight. */\n isMutating: boolean;\n createMutation: UseMutationResult<T, Error, Partial<T>>;\n updateMutation: UseMutationResult<T, Error, { id: Identifier; data: Partial<T> }>;\n deleteMutation: UseMutationResult<void, Error, Identifier>;\n}\n\n// ---------------------------------------------------------------------------\n// Filter identity — a deep-equality key independent of object-key order, so\n// a caller that rebuilds the same logical filters via a differently-ordered\n// spread (e.g. `{...base, operator: \"eq\"}` vs `{operator: \"eq\", ...base}`)\n// doesn't trip a spurious resync + pagination reset on an unrelated rerender.\n// ---------------------------------------------------------------------------\n\nfunction stableFilterKey(filters: FilterParam[] | undefined): string | undefined {\n if (!filters) return undefined;\n return JSON.stringify(\n filters.map((f) => {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(f).sort()) {\n sorted[key] = (f as unknown as Record<string, unknown>)[key];\n }\n return sorted;\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useResource<T extends BaseRecord = BaseRecord>(\n resource: string,\n options: UseResourceOptions<T> = {},\n): UseResourceReturn<T> {\n const {\n defaultSort,\n defaultFilters,\n defaultPerPage = 25,\n defaultPage = 1,\n defaultSearch = \"\",\n select,\n searchFields: searchFieldsOption,\n enabled = true,\n invalidates,\n keepPreviousData = false,\n onCreateSuccess,\n onUpdateSuccess,\n onDeleteSuccess,\n onError,\n mutationMode = \"pessimistic\",\n undoDelayMs,\n onUndoable,\n } = options;\n\n const provider = useDataProvider();\n const queryClient = useQueryClient();\n\n // -------------------------------------------------------------------------\n // State\n // -------------------------------------------------------------------------\n\n const [sort, setSortRaw] = useState<SortParam | undefined>(defaultSort);\n const [filters, setFiltersRaw] = useState<FilterParam[]>(defaultFilters ?? []);\n const [page, setPage] = useState(Math.max(1, Math.floor(defaultPage)));\n const [perPage, setPerPageRaw] = useState(defaultPerPage);\n const [searchQuery, setSearchQuery] = useState(defaultSearch);\n\n // `defaultFilters` often resolves asynchronously — a caller's scope/audience\n // (team, project, ...) can still be loading on first render, so the array\n // it passes in changes value (not just identity) after mount, and may later\n // clear back to undefined (e.g. the scope itself is cleared). Re-syncing\n // here, during render rather than in an effect, means `filters` is already\n // correct by the time `params`/`list` below compute for this pass — no\n // stray unscoped fetch on the frame before an effect would have caught up.\n //\n // This only closes the gap for `defaultFilters` CHANGING across renders on\n // an already-mounted, already-enabled hook (e.g. a scope switch). It cannot\n // help the very first render: `filters` still seeds from whatever\n // `defaultFilters` is on mount, and `undefined` here is genuinely ambiguous\n // between \"this caller never uses defaultFilters\" (in which case forcing a\n // fetch to wait would break it forever) and \"the real value hasn't resolved\n // yet.\" A caller in the second case MUST also gate `enabled` on that same\n // readiness signal for its very first fetch to be correctly scoped — this\n // hook has no way to tell the two cases apart on its own.\n const defaultFiltersKey = stableFilterKey(defaultFilters);\n const [syncedFiltersKey, setSyncedFiltersKey] = useState(defaultFiltersKey);\n if (defaultFiltersKey !== syncedFiltersKey) {\n setSyncedFiltersKey(defaultFiltersKey);\n setFiltersRaw(defaultFilters ?? []);\n setPage(1);\n }\n\n const setSort = useCallback((s: SortParam | undefined) => {\n setSortRaw(s);\n setPage(1);\n }, []);\n\n const setFilters = useCallback((f: FilterParam[]) => {\n setFiltersRaw(f);\n setPage(1);\n }, []);\n\n const setPerPage = useCallback((value: number) => {\n setPerPageRaw(value);\n setPage(1);\n }, []);\n\n const setSearch = useCallback((query: string) => {\n setSearchQuery(query);\n setPage(1);\n }, []);\n\n // -------------------------------------------------------------------------\n // Params\n // -------------------------------------------------------------------------\n\n const params = useMemo<ListParams>(() => {\n const p: ListParams = {\n pagination: { page, perPage },\n sort,\n filters: filters.length > 0 ? filters : undefined,\n select,\n };\n if (searchQuery && searchFieldsOption && searchFieldsOption.length > 0) {\n p.search = { query: searchQuery, fields: searchFieldsOption };\n }\n return p;\n }, [page, perPage, sort, filters, select, searchQuery, searchFieldsOption]);\n\n // -------------------------------------------------------------------------\n // List query\n // -------------------------------------------------------------------------\n\n const listQueryKey = useMemo(\n () => [resource, \"list\", params] as const,\n [resource, params],\n );\n\n const list = useQuery<ListResult<T>>({\n queryKey: listQueryKey,\n queryFn: () => provider.getList<T>(resource, params),\n enabled,\n placeholderData: keepPreviousData ? keepPreviousPlaceholder : undefined,\n });\n\n // -------------------------------------------------------------------------\n // Mutations\n // -------------------------------------------------------------------------\n\n const invalidateAfter = useCallback(\n (event: WriteEvent) => {\n // Pending optimistic/undoable patches are re-applied to the refetch.\n void invalidateResource(queryClient, resource);\n for (const queryKey of relatedKeys(provider, event, invalidates)) {\n void queryClient.invalidateQueries({ queryKey: [...queryKey] });\n }\n },\n [queryClient, resource, provider, invalidates],\n );\n\n const defaultOnError = useCallback(\n (error: Error) => {\n console.error(`[useResource] ${resource} mutation failed:`, error.message);\n },\n [resource],\n );\n const reportError = onError ?? defaultOnError;\n\n const modeArgs = {\n queryClient,\n provider,\n resource,\n mode: mutationMode,\n undoDelayMs,\n onUndoable,\n failedLater: reportError,\n };\n\n const createMutation = useMutation<T, Error, Partial<T>>({\n mutationFn: async (data) => {\n const outcome = await executeWrite<T>({\n ...modeArgs,\n kind: \"create\",\n data: data as Record<string, unknown>,\n send: async ({ keepalive }) => {\n const result = await provider.create<T>(resource, { data, ...(keepalive ? { keepalive } : {}) });\n return { record: result.data, audit: result.audit };\n },\n committed: (record) => {\n invalidateAfter({ resource, kind: \"create\", id: record.id, record });\n onCreateSuccess?.(record);\n },\n });\n return outcome.status === \"committed\" ? outcome.record : (data as T);\n },\n onError: reportError,\n });\n\n const updateMutation = useMutation<\n T,\n Error,\n { id: Identifier; data: Partial<T> }\n >({\n mutationFn: async ({ id, data }) => {\n const before = cachedRecord(queryClient, resource, id);\n const outcome = await executeWrite<T>({\n ...modeArgs,\n kind: \"update\",\n id,\n data: data as Record<string, unknown>,\n send: async (ctx) => {\n const result = await provider.update<T>(resource, {\n id,\n data: (ctx.data ?? data) as Partial<T>,\n ...(ctx.keepalive ? { keepalive: true } : {}),\n });\n return { record: result.data, audit: result.audit };\n },\n committed: (record) => {\n invalidateAfter({ resource, kind: \"update\", id, record });\n onUpdateSuccess?.(record);\n },\n });\n return outcome.status === \"committed\"\n ? outcome.record\n : ({ ...(before ?? {}), ...data, id: before?.id ?? id } as T);\n },\n onError: reportError,\n });\n\n const deleteMutation = useMutation<void, Error, Identifier>({\n mutationFn: async (id) => {\n await executeWrite<void>({\n ...modeArgs,\n kind: \"delete\",\n id,\n send: async ({ keepalive }) => {\n const result = await provider.deleteOne(resource, { id, ...(keepalive ? { keepalive } : {}) });\n return { record: undefined, audit: result?.audit };\n },\n committed: () => {\n invalidateAfter({ resource, kind: \"delete\", id });\n onDeleteSuccess?.();\n },\n });\n },\n onError: reportError,\n });\n\n // -------------------------------------------------------------------------\n // Convenience wrappers\n // -------------------------------------------------------------------------\n\n const create = useCallback(\n (data: Partial<T>) => createMutation.mutateAsync(data),\n [createMutation],\n );\n\n const update = useCallback(\n (id: Identifier, data: Partial<T>) =>\n updateMutation.mutateAsync({ id, data }),\n [updateMutation],\n );\n\n const remove = useCallback(\n (id: Identifier) => deleteMutation.mutateAsync(id),\n [deleteMutation],\n );\n\n const isMutating =\n createMutation.isPending ||\n updateMutation.isPending ||\n deleteMutation.isPending;\n\n return {\n list,\n params,\n setSort,\n setFilters,\n setPage,\n setPerPage,\n setSearch,\n create,\n update,\n remove,\n isMutating,\n createMutation,\n updateMutation,\n deleteMutation,\n };\n}\n"]}
|
|
@@ -21,11 +21,40 @@ function writeOutcomeFromCode(code) {
|
|
|
21
21
|
return "error";
|
|
22
22
|
}
|
|
23
23
|
var COLUMN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
24
|
+
var WRAPPED_COLUMN = /^[A-Za-z_][A-Za-z0-9_]*\(\(?([A-Za-z_][A-Za-z0-9_]*)\)?(?:::[A-Za-z_][A-Za-z0-9_ ]*)?\)$/;
|
|
25
|
+
function columnOf(expression) {
|
|
26
|
+
if (COLUMN.test(expression)) return expression;
|
|
27
|
+
return WRAPPED_COLUMN.exec(expression)?.[1];
|
|
28
|
+
}
|
|
24
29
|
function keyColumns(details, tail) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (!details.startsWith("Key (") || !tail.test(details)) return [];
|
|
31
|
+
let depth = 1;
|
|
32
|
+
let end = -1;
|
|
33
|
+
for (let i = "Key (".length; i < details.length; i++) {
|
|
34
|
+
const ch = details[i];
|
|
35
|
+
if (ch === "(") depth++;
|
|
36
|
+
else if (ch === ")" && --depth === 0) {
|
|
37
|
+
end = i;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (end < 0 || details.slice(end + 1, end + 3) !== "=(") return [];
|
|
42
|
+
const parts = [];
|
|
43
|
+
let level = 0;
|
|
44
|
+
let current = "";
|
|
45
|
+
for (const ch of details.slice("Key (".length, end)) {
|
|
46
|
+
if (ch === "(") level++;
|
|
47
|
+
else if (ch === ")") level--;
|
|
48
|
+
if (ch === "," && level === 0) {
|
|
49
|
+
parts.push(current.trim());
|
|
50
|
+
current = "";
|
|
51
|
+
} else {
|
|
52
|
+
current += ch;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
parts.push(current.trim());
|
|
56
|
+
const columns = parts.map(columnOf);
|
|
57
|
+
return columns.length > 0 && columns.every((c) => !!c) ? columns : [];
|
|
29
58
|
}
|
|
30
59
|
function listLabels(columns) {
|
|
31
60
|
const labels = columns.map((c) => titleCase(c));
|
|
@@ -38,7 +67,7 @@ function fieldErrorsFromPostgrest(error) {
|
|
|
38
67
|
const message = typeof error.message === "string" ? error.message : "";
|
|
39
68
|
if (code === "23505") {
|
|
40
69
|
const columns = keyColumns(details, /\) already exists\.?$/);
|
|
41
|
-
if (columns.length === 0) return
|
|
70
|
+
if (columns.length === 0) return { fields: {}, message: "This value is already in use." };
|
|
42
71
|
const each = columns.length === 1 ? "This value is already in use." : "This combination is already in use.";
|
|
43
72
|
return {
|
|
44
73
|
fields: Object.fromEntries(columns.map((c) => [c, each])),
|
|
@@ -54,8 +83,10 @@ function fieldErrorsFromPostgrest(error) {
|
|
|
54
83
|
};
|
|
55
84
|
}
|
|
56
85
|
if (code === "23503") {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
86
|
+
const tail = /\) is not present in table "[^"]*"\.?$/;
|
|
87
|
+
if (!tail.test(details)) return void 0;
|
|
88
|
+
const columns = keyColumns(details, tail);
|
|
89
|
+
if (columns.length === 0) return { fields: {}, message: "The selected item doesn't exist." };
|
|
59
90
|
return {
|
|
60
91
|
fields: Object.fromEntries(columns.map((c) => [c, "The selected item doesn't exist."])),
|
|
61
92
|
message: `${listLabels(columns)}: the selected item doesn't exist.`
|
|
@@ -71,7 +102,7 @@ function toDataWriteError(error) {
|
|
|
71
102
|
if (mapped) {
|
|
72
103
|
return new DataWriteError(writeOutcomeFromCode(code), mapped.message, {
|
|
73
104
|
code,
|
|
74
|
-
fields: mapped.fields,
|
|
105
|
+
fields: Object.keys(mapped.fields).length > 0 ? mapped.fields : void 0,
|
|
75
106
|
cause: error
|
|
76
107
|
});
|
|
77
108
|
}
|
|
@@ -80,11 +111,14 @@ function toDataWriteError(error) {
|
|
|
80
111
|
}
|
|
81
112
|
|
|
82
113
|
// src/provider/supabase-provider.ts
|
|
83
|
-
function isMissingColumn(error, column) {
|
|
114
|
+
function isMissingColumn(error, table, column) {
|
|
84
115
|
if (!error || typeof error !== "object") return false;
|
|
85
116
|
const { code, message } = error;
|
|
86
117
|
if (code !== "42703" || typeof message !== "string") return false;
|
|
87
|
-
|
|
118
|
+
const match = /^column (?:"?([^."\s]+)"?\.)?"?([^."\s]+)"? does not exist$/.exec(message.trim());
|
|
119
|
+
if (!match || match[2] !== column) return false;
|
|
120
|
+
const ownTable = table.split(".").pop()?.replace(/"/g, "");
|
|
121
|
+
return match[1] === void 0 || match[1] === ownTable;
|
|
88
122
|
}
|
|
89
123
|
var SERVED_OPERATORS = [
|
|
90
124
|
"eq",
|
|
@@ -291,7 +325,7 @@ function createSupabaseProvider(client, options = {}) {
|
|
|
291
325
|
};
|
|
292
326
|
const guessKey = configuredKey === void 0 && !keylessResources.has(resource);
|
|
293
327
|
let result = await run2(configuredKey ?? (keylessResources.has(resource) ? false : "id"));
|
|
294
|
-
if (guessKey && isMissingColumn(result.error, "id")) {
|
|
328
|
+
if (guessKey && isMissingColumn(result.error, resource, "id")) {
|
|
295
329
|
keylessResources.add(resource);
|
|
296
330
|
if (process.env.NODE_ENV !== "production") {
|
|
297
331
|
console.warn(
|
|
@@ -440,5 +474,5 @@ function createSupabaseProvider(client, options = {}) {
|
|
|
440
474
|
}
|
|
441
475
|
|
|
442
476
|
export { createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, writeOutcomeFromCode };
|
|
443
|
-
//# sourceMappingURL=chunk-
|
|
444
|
-
//# sourceMappingURL=chunk-
|
|
477
|
+
//# sourceMappingURL=chunk-E5VYFAQO.js.map
|
|
478
|
+
//# sourceMappingURL=chunk-E5VYFAQO.js.map
|