@marktiderman/genesis-core 1.4.0 → 1.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 +81 -0
- package/dist/{chunk-6MBACQYA.js → chunk-5NHR6ZXS.js} +10 -2
- package/dist/chunk-5NHR6ZXS.js.map +1 -0
- package/dist/chunk-BQOJX67X.js +31 -0
- package/dist/chunk-BQOJX67X.js.map +1 -0
- package/dist/{chunk-P4OIIRQS.js → chunk-DVW5GXWZ.js} +70 -26
- package/dist/chunk-DVW5GXWZ.js.map +1 -0
- package/dist/chunk-HT43MPKW.js +3 -0
- package/dist/{chunk-EL7YGOTY.js.map → chunk-HT43MPKW.js.map} +1 -1
- package/dist/chunk-HUNGCTRA.js +33 -0
- package/dist/chunk-HUNGCTRA.js.map +1 -0
- package/dist/{chunk-IHWWJUEN.js → chunk-IZT2O57C.js} +127 -39
- package/dist/chunk-IZT2O57C.js.map +1 -0
- package/dist/chunk-QH4P5MI2.js +278 -0
- package/dist/chunk-QH4P5MI2.js.map +1 -0
- package/dist/{chunk-NW26Z3LZ.js → chunk-RLY7TZUY.js} +178 -37
- package/dist/chunk-RLY7TZUY.js.map +1 -0
- package/dist/chunk-XQO4OH2N.js +32 -0
- package/dist/chunk-XQO4OH2N.js.map +1 -0
- package/dist/hooks/index.d.ts +5 -1
- package/dist/hooks/index.js +8 -4
- package/dist/hooks/mutation-mode.d.ts +109 -0
- package/dist/hooks/mutation-mode.js +3 -0
- package/dist/hooks/mutation-mode.js.map +1 -0
- package/dist/hooks/related-keys.d.ts +2 -1
- package/dist/hooks/use-capabilities.d.ts +21 -0
- package/dist/hooks/use-capabilities.js +5 -0
- package/dist/hooks/use-capabilities.js.map +1 -0
- package/dist/hooks/use-one.d.ts +2 -1
- package/dist/hooks/use-resource-form.d.ts +35 -2
- package/dist/hooks/use-resource-form.js +5 -2
- package/dist/hooks/use-resource.d.ts +29 -5
- package/dist/hooks/use-resource.js +2 -1
- package/dist/hooks/use-unsaved-changes-guard.d.ts +33 -0
- package/dist/hooks/use-unsaved-changes-guard.js +3 -0
- package/dist/hooks/use-unsaved-changes-guard.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +10 -6
- package/dist/provider/index.d.ts +77 -5
- package/dist/provider/index.js +4 -2
- package/dist/read-outcome-BQkYcV4t.d.ts +88 -0
- package/dist/schema/index.d.ts +4 -3
- package/dist/schema/index.js +2 -1
- package/dist/{types-zILrlHuV.d.ts → types-C5bjsbxz.d.ts} +12 -279
- package/dist/types-Dsqqr9s7.d.ts +281 -0
- package/package.json +1 -1
- package/dist/chunk-6MBACQYA.js.map +0 -1
- package/dist/chunk-EL7YGOTY.js +0 -3
- package/dist/chunk-IHWWJUEN.js.map +0 -1
- package/dist/chunk-NW26Z3LZ.js.map +0 -1
- package/dist/chunk-P4OIIRQS.js.map +0 -1
- package/dist/write-outcome-BE3Tk6bA.d.ts +0 -47
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-resource-form.ts"],"names":[],"mappings":"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","file":"chunk-IZT2O57C.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 useDelayedWrites,\n type MutationModeOptions,\n} from \"./mutation-mode\";\nimport { useUnsavedChangesGuard } from \"./use-unsaved-changes-guard\";\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 * 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 } = 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 const delayed = useDelayedWrites();\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) => {\n // A save handed off optimistically and then refused is unsaved again.\n savedCleanRef.current = false;\n setSavedClean(false);\n showError(error);\n if (onError) {\n onError(error);\n } else {\n console.error(`[useResourceForm] ${action} failed for \"${resource}\":`, error);\n }\n },\n [showError, onError, action, resource],\n );\n\n // Submit handler\n const handleSubmit = useCallback(\n async (formData: Record<string, unknown>) => {\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 delayed,\n send: async () => {\n const res =\n action === \"create\"\n ? await provider.create(resource, { data: formData })\n : await provider.update(resource, { id: recordId!, data: formData });\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: reportError,\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 [action, provider, resource, id, effectiveData, formSource, queryClient, onSuccess, autosaveEnabled, storage, invalidates, mutationMode, undoDelayMs, onUndoable, delayed, 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"]}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/hooks/mutation-mode.ts
|
|
4
|
+
var DEFAULT_UNDO_DELAY_MS = 5e3;
|
|
5
|
+
var pendingByClient = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function pendingMap(queryClient) {
|
|
7
|
+
let map = pendingByClient.get(queryClient);
|
|
8
|
+
if (!map) {
|
|
9
|
+
map = /* @__PURE__ */ new Map();
|
|
10
|
+
pendingByClient.set(queryClient, map);
|
|
11
|
+
}
|
|
12
|
+
return map;
|
|
13
|
+
}
|
|
14
|
+
function pendingWriteCount(queryClient, resource) {
|
|
15
|
+
return pendingMap(queryClient).get(resource) ?? 0;
|
|
16
|
+
}
|
|
17
|
+
function beginPending(queryClient, resource) {
|
|
18
|
+
const map = pendingMap(queryClient);
|
|
19
|
+
map.set(resource, (map.get(resource) ?? 0) + 1);
|
|
20
|
+
}
|
|
21
|
+
function endPending(queryClient, resource) {
|
|
22
|
+
const map = pendingMap(queryClient);
|
|
23
|
+
const left = Math.max(0, (map.get(resource) ?? 0) - 1);
|
|
24
|
+
if (left === 0) map.delete(resource);
|
|
25
|
+
else map.set(resource, left);
|
|
26
|
+
return left;
|
|
27
|
+
}
|
|
28
|
+
function invalidateResource(queryClient, resource) {
|
|
29
|
+
if (pendingWriteCount(queryClient, resource) > 0) return Promise.resolve();
|
|
30
|
+
return queryClient.invalidateQueries({ queryKey: [resource] });
|
|
31
|
+
}
|
|
32
|
+
function sameId(a, b) {
|
|
33
|
+
return a != null && b != null && String(a) === String(b);
|
|
34
|
+
}
|
|
35
|
+
function isListResult(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && Array.isArray(value.data);
|
|
37
|
+
}
|
|
38
|
+
function isDetailKeyFor(key, resource, id) {
|
|
39
|
+
return key[0] === resource && key[1] === "detail" && sameId(key[2], id);
|
|
40
|
+
}
|
|
41
|
+
function cachedRecord(queryClient, resource, id) {
|
|
42
|
+
for (const [key, value] of queryClient.getQueriesData({ queryKey: [resource, "detail"] })) {
|
|
43
|
+
if (isDetailKeyFor(key, resource, id) && value && typeof value === "object") return value;
|
|
44
|
+
}
|
|
45
|
+
for (const [, value] of queryClient.getQueriesData({ queryKey: [resource, "list"] })) {
|
|
46
|
+
if (!isListResult(value)) continue;
|
|
47
|
+
const found = value.data.find((row) => sameId(row.id, id));
|
|
48
|
+
if (found) return found;
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
function patchUpdate(queryClient, resource, id, data) {
|
|
53
|
+
const lists = [];
|
|
54
|
+
const details = [];
|
|
55
|
+
for (const [key, value] of queryClient.getQueriesData({ queryKey: [resource, "list"] })) {
|
|
56
|
+
if (!isListResult(value)) continue;
|
|
57
|
+
const index = value.data.findIndex((row) => sameId(row.id, id));
|
|
58
|
+
if (index < 0) continue;
|
|
59
|
+
const previous = value.data[index];
|
|
60
|
+
lists.push({ key, previous });
|
|
61
|
+
const rows = value.data.slice();
|
|
62
|
+
rows[index] = { ...previous, ...data, id: previous.id };
|
|
63
|
+
queryClient.setQueryData(key, { ...value, data: rows });
|
|
64
|
+
}
|
|
65
|
+
for (const [key, value] of queryClient.getQueriesData({ queryKey: [resource, "detail"] })) {
|
|
66
|
+
if (!isDetailKeyFor(key, resource, id) || !value || typeof value !== "object") continue;
|
|
67
|
+
details.push({ key, previous: value });
|
|
68
|
+
queryClient.setQueryData(key, { ...value, ...data, id: value.id });
|
|
69
|
+
}
|
|
70
|
+
return () => {
|
|
71
|
+
for (const { key, previous } of lists) {
|
|
72
|
+
const now = queryClient.getQueryData(key);
|
|
73
|
+
if (!isListResult(now)) continue;
|
|
74
|
+
const index = now.data.findIndex((row) => sameId(row.id, id));
|
|
75
|
+
if (index < 0) continue;
|
|
76
|
+
const rows = now.data.slice();
|
|
77
|
+
rows[index] = previous;
|
|
78
|
+
queryClient.setQueryData(key, { ...now, data: rows });
|
|
79
|
+
}
|
|
80
|
+
for (const { key, previous } of details) queryClient.setQueryData(key, previous);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function patchDelete(queryClient, resource, id) {
|
|
84
|
+
const lists = [];
|
|
85
|
+
for (const [key, value] of queryClient.getQueriesData({ queryKey: [resource, "list"] })) {
|
|
86
|
+
if (!isListResult(value)) continue;
|
|
87
|
+
const index = value.data.findIndex((row) => sameId(row.id, id));
|
|
88
|
+
if (index < 0) continue;
|
|
89
|
+
lists.push({ key, index, previous: value.data[index] });
|
|
90
|
+
const rows = value.data.filter((_, i) => i !== index);
|
|
91
|
+
queryClient.setQueryData(key, {
|
|
92
|
+
...value,
|
|
93
|
+
data: rows,
|
|
94
|
+
total: typeof value.total === "number" ? Math.max(0, value.total - 1) : value.total
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return () => {
|
|
98
|
+
for (const { key, index, previous } of lists) {
|
|
99
|
+
const now = queryClient.getQueryData(key);
|
|
100
|
+
if (!isListResult(now) || now.data.some((row) => sameId(row.id, id))) continue;
|
|
101
|
+
const rows = now.data.slice();
|
|
102
|
+
rows.splice(Math.min(index, rows.length), 0, previous);
|
|
103
|
+
queryClient.setQueryData(key, {
|
|
104
|
+
...now,
|
|
105
|
+
data: rows,
|
|
106
|
+
total: typeof now.total === "number" ? now.total + 1 : now.total
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
var allDelayed = /* @__PURE__ */ new Set();
|
|
112
|
+
function onBeforeUnload(event) {
|
|
113
|
+
if (allDelayed.size === 0) return;
|
|
114
|
+
for (const write of [...allDelayed]) write.flush();
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
event.returnValue = "";
|
|
117
|
+
}
|
|
118
|
+
function trackDelayed(write) {
|
|
119
|
+
allDelayed.add(write);
|
|
120
|
+
if (allDelayed.size === 1 && typeof window !== "undefined") {
|
|
121
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function untrackDelayed(write) {
|
|
125
|
+
if (!allDelayed.delete(write)) return;
|
|
126
|
+
if (allDelayed.size === 0 && typeof window !== "undefined") {
|
|
127
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function useDelayedWrites() {
|
|
131
|
+
const [writes] = useState(() => /* @__PURE__ */ new Set());
|
|
132
|
+
useEffect(
|
|
133
|
+
() => () => {
|
|
134
|
+
for (const write of [...writes]) write.flush();
|
|
135
|
+
},
|
|
136
|
+
[writes]
|
|
137
|
+
);
|
|
138
|
+
return writes;
|
|
139
|
+
}
|
|
140
|
+
async function providerServesUndo(queryClient, provider, resource) {
|
|
141
|
+
if (!provider.undo || !provider.capabilities) return false;
|
|
142
|
+
try {
|
|
143
|
+
const caps = await queryClient.fetchQuery({
|
|
144
|
+
// The same key as useCapabilities, so the answer is shared.
|
|
145
|
+
queryKey: ["__capabilities", resource],
|
|
146
|
+
queryFn: () => provider.capabilities(resource),
|
|
147
|
+
staleTime: Infinity,
|
|
148
|
+
// The write waits on this answer; an app's default retries would
|
|
149
|
+
// hold it for seconds.
|
|
150
|
+
retry: false
|
|
151
|
+
});
|
|
152
|
+
return caps?.undo === true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function asError(error) {
|
|
158
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
159
|
+
}
|
|
160
|
+
async function executeWrite(args) {
|
|
161
|
+
const { queryClient, provider, resource, kind, id, data, mode, send, committed, failedLater } = args;
|
|
162
|
+
if (mode === "pessimistic") {
|
|
163
|
+
const { record } = await send();
|
|
164
|
+
await committed(record);
|
|
165
|
+
return { status: "committed", record };
|
|
166
|
+
}
|
|
167
|
+
const viaProviderUndo = mode === "undoable" && await providerServesUndo(queryClient, provider, resource);
|
|
168
|
+
await queryClient.cancelQueries({ queryKey: [resource] });
|
|
169
|
+
beginPending(queryClient, resource);
|
|
170
|
+
let rollback = () => {
|
|
171
|
+
};
|
|
172
|
+
if (kind === "update" && id != null && data) rollback = patchUpdate(queryClient, resource, id, data);
|
|
173
|
+
else if (kind === "delete" && id != null) rollback = patchDelete(queryClient, resource, id);
|
|
174
|
+
let ended = false;
|
|
175
|
+
const end = () => {
|
|
176
|
+
if (ended) return;
|
|
177
|
+
ended = true;
|
|
178
|
+
if (endPending(queryClient, resource) === 0) {
|
|
179
|
+
void queryClient.invalidateQueries({ queryKey: [resource] });
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const commitNow = async () => {
|
|
183
|
+
let result;
|
|
184
|
+
try {
|
|
185
|
+
result = await send();
|
|
186
|
+
} catch (error) {
|
|
187
|
+
rollback();
|
|
188
|
+
end();
|
|
189
|
+
failedLater(asError(error));
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await committed(result.record);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(`[genesis] after-write callback failed for "${resource}":`, error);
|
|
196
|
+
}
|
|
197
|
+
end();
|
|
198
|
+
return result;
|
|
199
|
+
};
|
|
200
|
+
const delayMs = Math.max(0, args.undoDelayMs ?? DEFAULT_UNDO_DELAY_MS);
|
|
201
|
+
if (mode === "optimistic") {
|
|
202
|
+
void commitNow();
|
|
203
|
+
return { status: "applied" };
|
|
204
|
+
}
|
|
205
|
+
if (viaProviderUndo) {
|
|
206
|
+
void commitNow().then((result) => {
|
|
207
|
+
const auditId = result?.audit?.id;
|
|
208
|
+
if (!result || !args.onUndoable) return;
|
|
209
|
+
if (!auditId) {
|
|
210
|
+
console.warn(
|
|
211
|
+
`[genesis] "${resource}" serves undo, but its ${kind} returned no audit entry; no undo offered.`
|
|
212
|
+
);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let undone = false;
|
|
216
|
+
args.onUndoable({
|
|
217
|
+
resource,
|
|
218
|
+
kind,
|
|
219
|
+
id: id ?? result.record?.id,
|
|
220
|
+
strategy: "provider",
|
|
221
|
+
delayMs,
|
|
222
|
+
commit: () => {
|
|
223
|
+
},
|
|
224
|
+
undo: async () => {
|
|
225
|
+
if (undone) return false;
|
|
226
|
+
undone = true;
|
|
227
|
+
let ok = true;
|
|
228
|
+
try {
|
|
229
|
+
await provider.undo(resource, { auditId });
|
|
230
|
+
} catch (error) {
|
|
231
|
+
ok = false;
|
|
232
|
+
failedLater(asError(error));
|
|
233
|
+
}
|
|
234
|
+
await invalidateResource(queryClient, resource);
|
|
235
|
+
return ok;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
return { status: "applied" };
|
|
240
|
+
}
|
|
241
|
+
let state = "waiting";
|
|
242
|
+
const write = {
|
|
243
|
+
flush: () => {
|
|
244
|
+
if (state !== "waiting") return;
|
|
245
|
+
state = "sent";
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
args.delayed.delete(write);
|
|
248
|
+
untrackDelayed(write);
|
|
249
|
+
void commitNow();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const timer = setTimeout(write.flush, delayMs);
|
|
253
|
+
args.delayed.add(write);
|
|
254
|
+
trackDelayed(write);
|
|
255
|
+
args.onUndoable?.({
|
|
256
|
+
resource,
|
|
257
|
+
kind,
|
|
258
|
+
id,
|
|
259
|
+
strategy: "delay",
|
|
260
|
+
delayMs,
|
|
261
|
+
commit: write.flush,
|
|
262
|
+
undo: async () => {
|
|
263
|
+
if (state !== "waiting") return false;
|
|
264
|
+
state = "cancelled";
|
|
265
|
+
clearTimeout(timer);
|
|
266
|
+
args.delayed.delete(write);
|
|
267
|
+
untrackDelayed(write);
|
|
268
|
+
rollback();
|
|
269
|
+
end();
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return { status: "applied" };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export { DEFAULT_UNDO_DELAY_MS, cachedRecord, executeWrite, invalidateResource, pendingWriteCount, useDelayedWrites };
|
|
277
|
+
//# sourceMappingURL=chunk-QH4P5MI2.js.map
|
|
278
|
+
//# sourceMappingURL=chunk-QH4P5MI2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/mutation-mode.ts"],"names":[],"mappings":"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","file":"chunk-QH4P5MI2.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Mutation modes — when a write reaches the cache, and when the provider.\n//\n// - `pessimistic` (default): wait for the provider, then refetch. Today's\n// behaviour.\n// - `optimistic`: patch the TanStack Query cache at once, send the write, and\n// put the cache back (and report the error) if it fails.\n// - `undoable`: patch the cache at once and offer an undo (`onUndoable`);\n// the write is sent after a delay unless undone. A provider that serves\n// `undo` (capabilities `undo: true`) gets the write at once instead, and\n// the undo asks the provider to reverse it.\n//\n// Core has no UI: the undo toast is the caller's (`onUndoable`). genesis-ui's\n// ResourceForm and ResourcePage show a sonner toast.\n// ---------------------------------------------------------------------------\n\nimport { useEffect, useState } from \"react\";\nimport type { QueryClient, QueryKey } from \"@tanstack/react-query\";\nimport type { BaseRecord, DataProvider, Identifier, ListResult } from \"../provider/types\";\nimport type { AuditEntry, ProviderCapabilities } from \"../schema/types\";\n\nexport type MutationMode = \"pessimistic\" | \"optimistic\" | \"undoable\";\n\n/** How long an `undoable` write waits for an undo before it is sent. */\nexport const DEFAULT_UNDO_DELAY_MS = 5000;\n\n/** An undoable write, as handed to `onUndoable` so a UI can offer the undo. */\nexport interface UndoableWrite {\n resource: string;\n kind: \"create\" | \"update\" | \"delete\";\n /** The record's id; absent for a create (it has none until it is sent). */\n id?: Identifier;\n /**\n * - `delay`: the write has not been sent. `undo()` discards it; `commit()`\n * sends it now instead of waiting.\n * - `provider`: the write was sent at once, because the provider serves\n * `undo`. `undo()` asks the provider to reverse it; `commit()` does\n * nothing.\n */\n strategy: \"delay\" | \"provider\";\n /** How long the undo is offered, in ms (the toast's duration). */\n delayMs: number;\n /**\n * Take the write back. Resolves true when it was undone, false when it\n * could not be: already sent under `delay` (the window closed, or the hook\n * unmounted), already undone, or the provider's undo failed (reported\n * through `onError`). Safe to call more than once.\n */\n undo: () => Promise<boolean>;\n /** Send a `delay` write now. */\n commit: () => void;\n}\n\nexport interface MutationModeOptions {\n /** Default `\"pessimistic\"`. */\n mutationMode?: MutationMode;\n /** How long an `undoable` write waits, in ms. Default 5000. */\n undoDelayMs?: number;\n /** Called with every `undoable` write, to show an undo control. */\n onUndoable?: (write: UndoableWrite) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Writes in flight per QueryClient and resource\n//\n// While an optimistic or undoable write on a resource is pending, refetching\n// that resource would bring back the server's rows and wipe the patch. So a\n// write's `[resource]` invalidation waits until no patched write on the\n// resource is left; the last one to settle refetches.\n// ---------------------------------------------------------------------------\n\nconst pendingByClient = new WeakMap<QueryClient, Map<string, number>>();\n\nfunction pendingMap(queryClient: QueryClient): Map<string, number> {\n let map = pendingByClient.get(queryClient);\n if (!map) {\n map = new Map();\n pendingByClient.set(queryClient, map);\n }\n return map;\n}\n\n/** Optimistic or undoable writes on `resource` not yet settled. */\nexport function pendingWriteCount(queryClient: QueryClient, resource: string): number {\n return pendingMap(queryClient).get(resource) ?? 0;\n}\n\nfunction beginPending(queryClient: QueryClient, resource: string): void {\n const map = pendingMap(queryClient);\n map.set(resource, (map.get(resource) ?? 0) + 1);\n}\n\n/** Returns how many are left. */\nfunction endPending(queryClient: QueryClient, resource: string): number {\n const map = pendingMap(queryClient);\n const left = Math.max(0, (map.get(resource) ?? 0) - 1);\n if (left === 0) map.delete(resource);\n else map.set(resource, left);\n return left;\n}\n\n/**\n * Invalidate `[resource]`, unless a patched write on it is still pending\n * (the last one to settle refetches).\n */\nexport function invalidateResource(queryClient: QueryClient, resource: string): Promise<void> {\n if (pendingWriteCount(queryClient, resource) > 0) return Promise.resolve();\n return queryClient.invalidateQueries({ queryKey: [resource] });\n}\n\n// ---------------------------------------------------------------------------\n// Cache patches\n//\n// Lists live at [resource, \"list\", params] as ListResult; one record at\n// [resource, \"detail\", id, select]. A patch touches only the record it is\n// about, and its rollback puts back only that record, so two writes on\n// different records never undo each other.\n// ---------------------------------------------------------------------------\n\nfunction sameId(a: unknown, b: unknown): boolean {\n return a != null && b != null && String(a) === String(b);\n}\n\nfunction isListResult(value: unknown): value is ListResult<BaseRecord> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { data?: unknown }).data)\n );\n}\n\nfunction isDetailKeyFor(key: QueryKey, resource: string, id: Identifier): boolean {\n return key[0] === resource && key[1] === \"detail\" && sameId(key[2], id);\n}\n\n/** The record as the cache has it now, from a detail entry or any list. */\nexport function cachedRecord(\n queryClient: QueryClient,\n resource: string,\n id: Identifier,\n): BaseRecord | undefined {\n for (const [key, value] of queryClient.getQueriesData<unknown>({ queryKey: [resource, \"detail\"] })) {\n if (isDetailKeyFor(key, resource, id) && value && typeof value === \"object\") return value as BaseRecord;\n }\n for (const [, value] of queryClient.getQueriesData<unknown>({ queryKey: [resource, \"list\"] })) {\n if (!isListResult(value)) continue;\n const found = value.data.find((row) => sameId(row.id, id));\n if (found) return found;\n }\n return undefined;\n}\n\ntype Rollback = () => void;\n\nfunction patchUpdate(\n queryClient: QueryClient,\n resource: string,\n id: Identifier,\n data: Record<string, unknown>,\n): Rollback {\n const lists: { key: QueryKey; previous: BaseRecord }[] = [];\n const details: { key: QueryKey; previous: unknown }[] = [];\n for (const [key, value] of queryClient.getQueriesData<unknown>({ queryKey: [resource, \"list\"] })) {\n if (!isListResult(value)) continue;\n const index = value.data.findIndex((row) => sameId(row.id, id));\n if (index < 0) continue;\n const previous = value.data[index]!;\n lists.push({ key, previous });\n const rows = value.data.slice();\n rows[index] = { ...previous, ...data, id: previous.id };\n queryClient.setQueryData(key, { ...value, data: rows });\n }\n for (const [key, value] of queryClient.getQueriesData<unknown>({ queryKey: [resource, \"detail\"] })) {\n if (!isDetailKeyFor(key, resource, id) || !value || typeof value !== \"object\") continue;\n details.push({ key, previous: value });\n queryClient.setQueryData(key, { ...(value as BaseRecord), ...data, id: (value as BaseRecord).id });\n }\n return () => {\n for (const { key, previous } of lists) {\n const now = queryClient.getQueryData<unknown>(key);\n if (!isListResult(now)) continue;\n const index = now.data.findIndex((row) => sameId(row.id, id));\n if (index < 0) continue;\n const rows = now.data.slice();\n rows[index] = previous;\n queryClient.setQueryData(key, { ...now, data: rows });\n }\n for (const { key, previous } of details) queryClient.setQueryData(key, previous);\n };\n}\n\nfunction patchDelete(queryClient: QueryClient, resource: string, id: Identifier): Rollback {\n const lists: { key: QueryKey; index: number; previous: BaseRecord }[] = [];\n for (const [key, value] of queryClient.getQueriesData<unknown>({ queryKey: [resource, \"list\"] })) {\n if (!isListResult(value)) continue;\n const index = value.data.findIndex((row) => sameId(row.id, id));\n if (index < 0) continue;\n lists.push({ key, index, previous: value.data[index]! });\n const rows = value.data.filter((_, i) => i !== index);\n queryClient.setQueryData(key, {\n ...value,\n data: rows,\n total: typeof value.total === \"number\" ? Math.max(0, value.total - 1) : value.total,\n });\n }\n return () => {\n for (const { key, index, previous } of lists) {\n const now = queryClient.getQueryData<unknown>(key);\n if (!isListResult(now) || now.data.some((row) => sameId(row.id, id))) continue;\n const rows = now.data.slice();\n rows.splice(Math.min(index, rows.length), 0, previous);\n queryClient.setQueryData(key, {\n ...now,\n data: rows,\n total: typeof now.total === \"number\" ? now.total + 1 : now.total,\n });\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// Pending (delayed) writes — flushed on unmount and before the page unloads\n// ---------------------------------------------------------------------------\n\ninterface DelayedWrite {\n /** Sends it now, if it is still waiting. */\n flush: () => void;\n}\n\nconst allDelayed = new Set<DelayedWrite>();\n\nfunction onBeforeUnload(event: BeforeUnloadEvent): void {\n if (allDelayed.size === 0) return;\n // Send what is waiting, and ask the browser to hold the page so the\n // requests can finish (the user may still leave).\n for (const write of [...allDelayed]) write.flush();\n event.preventDefault();\n event.returnValue = \"\";\n}\n\nfunction trackDelayed(write: DelayedWrite): void {\n allDelayed.add(write);\n if (allDelayed.size === 1 && typeof window !== \"undefined\") {\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n }\n}\n\nfunction untrackDelayed(write: DelayedWrite): void {\n if (!allDelayed.delete(write)) return;\n if (allDelayed.size === 0 && typeof window !== \"undefined\") {\n window.removeEventListener(\"beforeunload\", onBeforeUnload);\n }\n}\n\n/** The delayed writes one hook instance started, flushed when it unmounts. */\nexport type DelayedWrites = Set<DelayedWrite>;\n\n/**\n * A hook's own set of delayed writes. When the hook unmounts (the form\n * closes, the route changes) every write still waiting is sent at once,\n * as React Admin does: leaving is not undoing.\n */\nexport function useDelayedWrites(): DelayedWrites {\n const [writes] = useState<DelayedWrites>(() => new Set());\n useEffect(\n () => () => {\n for (const write of [...writes]) write.flush();\n },\n [writes],\n );\n return writes;\n}\n\n// ---------------------------------------------------------------------------\n// The engine\n// ---------------------------------------------------------------------------\n\nexport interface ExecuteWriteArgs<R extends BaseRecord | void> {\n queryClient: QueryClient;\n provider: DataProvider;\n resource: string;\n kind: \"create\" | \"update\" | \"delete\";\n id?: Identifier;\n /**\n * The values to merge into the cached row (update). Leave out to patch\n * nothing, e.g. when the values are form-shaped and the row is not.\n */\n data?: Record<string, unknown>;\n mode: MutationMode;\n undoDelayMs?: number;\n onUndoable?: (write: UndoableWrite) => void;\n /** The delayed writes of the calling hook instance. */\n delayed: DelayedWrites;\n /** Sends the write. */\n send: () => Promise<{ record: R; audit?: AuditEntry }>;\n /**\n * After the write committed. Invalidates and calls success callbacks; its\n * `[resource]` invalidation should go through `invalidateResource`.\n */\n committed: (record: R) => Promise<void> | void;\n /** A write that failed after the caller was already told it went through. */\n failedLater: (error: Error) => void;\n}\n\nexport type ExecuteWriteResult<R> =\n | { status: \"committed\"; record: R }\n | { status: \"applied\" };\n\nasync function providerServesUndo(\n queryClient: QueryClient,\n provider: DataProvider,\n resource: string,\n): Promise<boolean> {\n if (!provider.undo || !provider.capabilities) return false;\n try {\n const caps = await queryClient.fetchQuery<ProviderCapabilities>({\n // The same key as useCapabilities, so the answer is shared.\n queryKey: [\"__capabilities\", resource],\n queryFn: () => provider.capabilities!(resource),\n staleTime: Infinity,\n // The write waits on this answer; an app's default retries would\n // hold it for seconds.\n retry: false,\n });\n return caps?.undo === true;\n } catch {\n return false;\n }\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\n/**\n * Runs one write in the given mode.\n *\n * - `pessimistic`: sends it and waits; a failure rejects.\n * - `optimistic` / `undoable`: resolves `{ status: \"applied\" }` as soon as\n * the cache is patched. A later failure rolls the patch back and goes to\n * `failedLater`, never to the returned promise.\n */\nexport async function executeWrite<R extends BaseRecord | void>(\n args: ExecuteWriteArgs<R>,\n): Promise<ExecuteWriteResult<R>> {\n const { queryClient, provider, resource, kind, id, data, mode, send, committed, failedLater } = args;\n\n if (mode === \"pessimistic\") {\n const { record } = await send();\n await committed(record);\n return { status: \"committed\", record };\n }\n\n const viaProviderUndo = mode === \"undoable\" && (await providerServesUndo(queryClient, provider, resource));\n\n // A fetch already on its way would land after the patch and overwrite it.\n await queryClient.cancelQueries({ queryKey: [resource] });\n\n beginPending(queryClient, resource);\n let rollback: Rollback = () => {};\n if (kind === \"update\" && id != null && data) rollback = patchUpdate(queryClient, resource, id, data);\n else if (kind === \"delete\" && id != null) rollback = patchDelete(queryClient, resource, id);\n // A create has no cache entry yet: its id and its place in each sorted,\n // filtered list are the server's to say.\n\n let ended = false;\n const end = () => {\n if (ended) return;\n ended = true;\n if (endPending(queryClient, resource) === 0) {\n void queryClient.invalidateQueries({ queryKey: [resource] });\n }\n };\n\n const commitNow = async (): Promise<{ record: R; audit?: AuditEntry } | undefined> => {\n let result: { record: R; audit?: AuditEntry };\n try {\n result = await send();\n } catch (error) {\n rollback();\n end();\n failedLater(asError(error));\n return undefined;\n }\n try {\n await committed(result.record);\n } catch (error) {\n // The write did commit; only a callback failed.\n console.error(`[genesis] after-write callback failed for \"${resource}\":`, error);\n }\n end();\n return result;\n };\n\n const delayMs = Math.max(0, args.undoDelayMs ?? DEFAULT_UNDO_DELAY_MS);\n\n if (mode === \"optimistic\") {\n void commitNow();\n return { status: \"applied\" };\n }\n\n if (viaProviderUndo) {\n void commitNow().then((result) => {\n const auditId = result?.audit?.id;\n if (!result || !args.onUndoable) return;\n if (!auditId) {\n console.warn(\n `[genesis] \"${resource}\" serves undo, but its ${kind} returned no audit entry; no undo offered.`,\n );\n return;\n }\n let undone = false;\n args.onUndoable({\n resource,\n kind,\n id: id ?? (result.record as BaseRecord | undefined)?.id,\n strategy: \"provider\",\n delayMs,\n commit: () => {},\n undo: async () => {\n if (undone) return false;\n undone = true;\n let ok = true;\n try {\n await provider.undo!(resource, { auditId });\n } catch (error) {\n ok = false;\n failedLater(asError(error));\n }\n await invalidateResource(queryClient, resource);\n return ok;\n },\n });\n });\n return { status: \"applied\" };\n }\n\n // Delayed: wait, then send unless undone or flushed first.\n let state: \"waiting\" | \"sent\" | \"cancelled\" = \"waiting\";\n const write: DelayedWrite = {\n flush: () => {\n if (state !== \"waiting\") return;\n state = \"sent\";\n clearTimeout(timer);\n args.delayed.delete(write);\n untrackDelayed(write);\n void commitNow();\n },\n };\n const timer = setTimeout(write.flush, delayMs);\n args.delayed.add(write);\n trackDelayed(write);\n\n args.onUndoable?.({\n resource,\n kind,\n id,\n strategy: \"delay\",\n delayMs,\n commit: write.flush,\n undo: async () => {\n if (state !== \"waiting\") return false;\n state = \"cancelled\";\n clearTimeout(timer);\n args.delayed.delete(write);\n untrackDelayed(write);\n rollback();\n end();\n return true;\n },\n });\n\n return { status: \"applied\" };\n}\n"]}
|