@marktiderman/genesis-core 1.5.0 → 1.6.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 +143 -10
- package/dist/chunk-ADD2RIGA.js +108 -0
- package/dist/chunk-ADD2RIGA.js.map +1 -0
- package/dist/{chunk-IZT2O57C.js → chunk-CKGGE6WA.js} +36 -16
- package/dist/chunk-CKGGE6WA.js.map +1 -0
- package/dist/{chunk-DVW5GXWZ.js → chunk-KV6KH3L2.js} +16 -11
- package/dist/chunk-KV6KH3L2.js.map +1 -0
- package/dist/chunk-KXVHDK3Q.js +71 -0
- package/dist/chunk-KXVHDK3Q.js.map +1 -0
- package/dist/chunk-OBUBP4BY.js +556 -0
- package/dist/chunk-OBUBP4BY.js.map +1 -0
- package/dist/chunk-V3GDAELU.js +3 -0
- package/dist/{chunk-HT43MPKW.js.map → chunk-V3GDAELU.js.map} +1 -1
- package/dist/{chunk-RLY7TZUY.js → chunk-Y475Z7BM.js} +247 -22
- package/dist/chunk-Y475Z7BM.js.map +1 -0
- package/dist/{chunk-ZK5C3OOT.js → chunk-ZKMAU6NN.js} +12 -3
- package/dist/chunk-ZKMAU6NN.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -2
- package/dist/hooks/index.js +7 -5
- 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-infinite-resource.d.ts +50 -0
- package/dist/hooks/use-infinite-resource.js +6 -0
- package/dist/hooks/use-infinite-resource.js.map +1 -0
- package/dist/hooks/use-live.d.ts +27 -0
- package/dist/hooks/use-live.js +5 -0
- package/dist/hooks/use-live.js.map +1 -0
- package/dist/hooks/use-one.d.ts +9 -2
- package/dist/hooks/use-one.js +2 -1
- package/dist/hooks/use-resource-form.d.ts +10 -2
- package/dist/hooks/use-resource-form.js +5 -3
- package/dist/hooks/use-resource.d.ts +14 -2
- package/dist/hooks/use-resource.js +4 -2
- package/dist/index.d.ts +6 -4
- package/dist/index.js +9 -7
- package/dist/provider/index.d.ts +59 -7
- package/dist/provider/index.js +1 -1
- package/dist/{read-outcome-BQkYcV4t.d.ts → read-outcome-C_9HbXvb.d.ts} +1 -1
- package/dist/schema/index.d.ts +4 -4
- package/dist/{types-C5bjsbxz.d.ts → types-C7FJtFXV.d.ts} +20 -0
- package/dist/{types-Dsqqr9s7.d.ts → types-CS9UpxQS.d.ts} +73 -4
- package/package.json +1 -1
- package/dist/chunk-DVW5GXWZ.js.map +0 -1
- package/dist/chunk-HT43MPKW.js +0 -3
- 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/dist/chunk-ZK5C3OOT.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { UseQueryResult, UseMutationResult } from '@tanstack/react-query';
|
|
2
|
-
import {
|
|
2
|
+
import { n as SortParam, F as FilterParam, j as InvalidationKey, k as ListResult, L as ListParams, I as Identifier, B as BaseRecord } from '../types-CS9UpxQS.js';
|
|
3
3
|
import { MutationModeOptions } from './mutation-mode.js';
|
|
4
|
-
import '
|
|
4
|
+
import { LiveOptions } from './use-live.js';
|
|
5
|
+
import '../types-C7FJtFXV.js';
|
|
5
6
|
import '../form-types.js';
|
|
6
7
|
|
|
7
8
|
interface UseResourceOptions<T> extends MutationModeOptions {
|
|
@@ -40,6 +41,17 @@ interface UseResourceOptions<T> extends MutationModeOptions {
|
|
|
40
41
|
* rather than flashing a skeleton. Default false.
|
|
41
42
|
*/
|
|
42
43
|
keepPreviousData?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Keep the list current while mounted: subscribe to the provider's change
|
|
46
|
+
* events (`DataProvider.subscribe`) and refetch this resource's cached
|
|
47
|
+
* reads (lists and records) when it reports one, once per burst of
|
|
48
|
+
* events. Events are hints: rows always come from the provider's normal
|
|
49
|
+
* read. Pending optimistic and undoable writes stay applied over the
|
|
50
|
+
* refetch. Needs a provider that serves `subscribe` for the resource
|
|
51
|
+
* (`capabilities(resource).realtime`); otherwise it does nothing. Off by
|
|
52
|
+
* default.
|
|
53
|
+
*/
|
|
54
|
+
live?: boolean | LiveOptions;
|
|
43
55
|
/**
|
|
44
56
|
* After the write commits, with the provider's row. In `optimistic` and
|
|
45
57
|
* `undoable` modes that is after `create` / `update` / `remove` have
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { useResource } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { useResource } from '../chunk-KV6KH3L2.js';
|
|
2
|
+
import '../chunk-OBUBP4BY.js';
|
|
3
3
|
import '../chunk-7NUL32W7.js';
|
|
4
|
+
import '../chunk-BQOJX67X.js';
|
|
5
|
+
import '../chunk-KXVHDK3Q.js';
|
|
4
6
|
import '../chunk-GGSXSI6Z.js';
|
|
5
7
|
import '../chunk-ZYJUEJ4Z.js';
|
|
6
8
|
//# sourceMappingURL=use-resource.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
export { A as ArrayFilter, B as BaseRecord, a as BulkChange, b as BulkResult, C as CreateParams, c as CreateResult, D as DataProvider, d as DeleteParams, e as DeleteResult, F as FilterParam, G as GetManyParams, f as GetManyResult, g as GetOneParams, h as GetOneResult, H as HistoryParams, I as Identifier, i as ImportParams, j as InvalidationKey, L as ListParams, k as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, l as RowFailure, S as ScalarFilter,
|
|
2
|
-
export { D as DataReadError, a as DataReadErrorOptions, b as DataWriteError, c as DataWriteErrorOptions, R as ReadOutcome, W as WriteOutcome, i as isDataReadError, d as isDataWriteError, r as readFailureMessage } from './read-outcome-
|
|
3
|
-
export { DataProviderRoot, DataProviderRootProps, GenesisProvider, GenesisProviderProps, MockProviderOptions, PostgrestFieldErrors, SupabaseClient, SupabaseClientInput, SupabaseProviderOptions, SupabaseResourceConfig, SupabaseWriteContext, createMockProvider, createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, useDataProvider, useStorage, writeOutcomeFromCode } from './provider/index.js';
|
|
1
|
+
export { A as ArrayFilter, B as BaseRecord, a as BulkChange, b as BulkResult, C as CreateParams, c as CreateResult, D as DataProvider, d as DeleteParams, e as DeleteResult, F as FilterParam, G as GetManyParams, f as GetManyResult, g as GetOneParams, h as GetOneResult, H as HistoryParams, I as Identifier, i as ImportParams, j as InvalidationKey, L as ListParams, k as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, l as ResourceChangeEvent, m as RowFailure, S as ScalarFilter, n as SortParam, o as SubscribeParams, U as UpdateParams, p as UpdateResult, W as WriteEvent } from './types-CS9UpxQS.js';
|
|
2
|
+
export { D as DataReadError, a as DataReadErrorOptions, b as DataWriteError, c as DataWriteErrorOptions, R as ReadOutcome, W as WriteOutcome, i as isDataReadError, d as isDataWriteError, r as readFailureMessage } from './read-outcome-C_9HbXvb.js';
|
|
3
|
+
export { DataProviderRoot, DataProviderRootProps, GenesisProvider, GenesisProviderProps, MockProviderOptions, PostgrestFieldErrors, SupabaseClient, SupabaseClientInput, SupabaseProviderOptions, SupabaseRealtimeChannel, SupabaseRealtimePayload, SupabaseResourceConfig, SupabaseWriteContext, createMockProvider, createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, useDataProvider, useStorage, writeOutcomeFromCode } from './provider/index.js';
|
|
4
4
|
export { S as StorageAdapter } from './types-DKcxHXxq.js';
|
|
5
5
|
export { createLocalStorageAdapter, createMemoryStorageAdapter, createNoopStorageAdapter } from './storage/index.js';
|
|
6
6
|
export { UseResourceOptions, UseResourceReturn, useResource } from './hooks/use-resource.js';
|
|
7
7
|
export { UseOneOptions, useOne } from './hooks/use-one.js';
|
|
8
|
+
export { InfinitePageParam, UseInfiniteResourceOptions, UseInfiniteResourceReturn, useInfiniteResource } from './hooks/use-infinite-resource.js';
|
|
8
9
|
export { useCapabilities } from './hooks/use-capabilities.js';
|
|
10
|
+
export { DEFAULT_LIVE_DEBOUNCE_MS, LiveOptions } from './hooks/use-live.js';
|
|
9
11
|
export { UseResourceFormOptions, UseResourceFormReturn, buildArrayItemDefaults, useResourceForm } from './hooks/use-resource-form.js';
|
|
10
12
|
export { UseWizardReturn, WizardStep, useWizard } from './hooks/use-wizard.js';
|
|
11
13
|
export { DEFAULT_UNDO_DELAY_MS, MutationMode, MutationModeOptions, UndoableWrite, pendingWriteCount } from './hooks/mutation-mode.js';
|
|
12
14
|
export { DEFAULT_UNSAVED_CHANGES_MESSAGE, UseUnsavedChangesGuardOptions, UseUnsavedChangesGuardReturn, useUnsavedChangesGuard } from './hooks/use-unsaved-changes-guard.js';
|
|
13
15
|
export { FieldType, ResourceFormFieldDef, SelectOption, FieldType as WidgetHint } from './form-types.js';
|
|
14
|
-
export { A as AuditEntry, D as DataRow, F as FieldPermissions, a as FieldReference, b as FieldSchema, c as FilterOperator, P as ProviderCapabilities, R as RecordComment, d as ResourceAccess, e as ResourceContract, f as ResourceRelation, g as ResourceSchema, h as ResourceSummary, i as RowAction, S as StorageType, V as VersionField } from './types-
|
|
16
|
+
export { A as AuditEntry, D as DataRow, F as FieldPermissions, a as FieldReference, b as FieldSchema, c as FilterOperator, P as ProviderCapabilities, R as RecordComment, d as ResourceAccess, e as ResourceContract, f as ResourceRelation, g as ResourceSchema, h as ResourceSummary, i as RowAction, S as StorageType, V as VersionField } from './types-C7FJtFXV.js';
|
|
15
17
|
export { decodeRecordKey, encodeRecordKey, keyFields } from './schema/index.js';
|
|
16
18
|
export { titleCase } from './utils.js';
|
|
17
19
|
import 'react';
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import './chunk-XCX2TRBL.js';
|
|
2
|
+
export { createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, writeOutcomeFromCode } from './chunk-Y475Z7BM.js';
|
|
2
3
|
export { decodeRecordKey, encodeRecordKey, keyFields } from './chunk-KV2HW4G4.js';
|
|
3
4
|
export { DataReadError, isDataReadError, readFailureMessage } from './chunk-HUNGCTRA.js';
|
|
4
|
-
import './chunk-XCX2TRBL.js';
|
|
5
5
|
import './chunk-65SXNHWE.js';
|
|
6
|
-
import './chunk-
|
|
7
|
-
export { buildArrayItemDefaults, useResourceForm } from './chunk-
|
|
8
|
-
export {
|
|
6
|
+
import './chunk-V3GDAELU.js';
|
|
7
|
+
export { buildArrayItemDefaults, useResourceForm } from './chunk-CKGGE6WA.js';
|
|
8
|
+
export { useOne } from './chunk-ZKMAU6NN.js';
|
|
9
|
+
export { useResource } from './chunk-KV6KH3L2.js';
|
|
9
10
|
export { DEFAULT_UNSAVED_CHANGES_MESSAGE, useUnsavedChangesGuard } from './chunk-XQO4OH2N.js';
|
|
10
11
|
export { useWizard } from './chunk-7KOLYF27.js';
|
|
11
12
|
export { DataWriteError, isDataWriteError } from './chunk-5NHR6ZXS.js';
|
|
12
13
|
export { titleCase } from './chunk-RYEXWQFD.js';
|
|
13
|
-
export { DEFAULT_UNDO_DELAY_MS, pendingWriteCount } from './chunk-
|
|
14
|
+
export { DEFAULT_UNDO_DELAY_MS, pendingWriteCount } from './chunk-OBUBP4BY.js';
|
|
14
15
|
import './chunk-7NUL32W7.js';
|
|
15
16
|
export { useCapabilities } from './chunk-BQOJX67X.js';
|
|
16
|
-
export {
|
|
17
|
+
export { useInfiniteResource } from './chunk-ADD2RIGA.js';
|
|
18
|
+
export { DEFAULT_LIVE_DEBOUNCE_MS } from './chunk-KXVHDK3Q.js';
|
|
17
19
|
export { DataProviderRoot, GenesisProvider, createMockProvider, useDataProvider, useStorage } from './chunk-GGSXSI6Z.js';
|
|
18
20
|
export { createLocalStorageAdapter, createMemoryStorageAdapter, createNoopStorageAdapter } from './chunk-ZYJUEJ4Z.js';
|
|
19
21
|
//# sourceMappingURL=index.js.map
|
package/dist/provider/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { D as DataProvider, I as Identifier, B as BaseRecord, j as InvalidationKey, W as WriteEvent } from '../types-
|
|
2
|
-
export { A as ArrayFilter, a as BulkChange, b as BulkResult, C as CreateParams, c as CreateResult, d as DeleteParams, e as DeleteResult, F as FilterParam, G as GetManyParams, f as GetManyResult, g as GetOneParams, h as GetOneResult, H as HistoryParams, i as ImportParams, L as ListParams, k as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, l as RowFailure, S as ScalarFilter,
|
|
3
|
-
import { D as DataReadError, R as ReadOutcome, b as DataWriteError, W as WriteOutcome } from '../read-outcome-
|
|
4
|
-
export { a as DataReadErrorOptions, c as DataWriteErrorOptions, i as isDataReadError, d as isDataWriteError, r as readFailureMessage } from '../read-outcome-
|
|
1
|
+
import { D as DataProvider, I as Identifier, B as BaseRecord, j as InvalidationKey, W as WriteEvent } from '../types-CS9UpxQS.js';
|
|
2
|
+
export { A as ArrayFilter, a as BulkChange, b as BulkResult, C as CreateParams, c as CreateResult, d as DeleteParams, e as DeleteResult, F as FilterParam, G as GetManyParams, f as GetManyResult, g as GetOneParams, h as GetOneResult, H as HistoryParams, i as ImportParams, L as ListParams, k as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, l as ResourceChangeEvent, m as RowFailure, S as ScalarFilter, n as SortParam, o as SubscribeParams, U as UpdateParams, p as UpdateResult } from '../types-CS9UpxQS.js';
|
|
3
|
+
import { D as DataReadError, R as ReadOutcome, b as DataWriteError, W as WriteOutcome } from '../read-outcome-C_9HbXvb.js';
|
|
4
|
+
export { a as DataReadErrorOptions, c as DataWriteErrorOptions, i as isDataReadError, d as isDataWriteError, r as readFailureMessage } from '../read-outcome-C_9HbXvb.js';
|
|
5
5
|
import * as react from 'react';
|
|
6
6
|
import { ReactNode } from 'react';
|
|
7
7
|
import { QueryClient } from '@tanstack/react-query';
|
|
8
8
|
import { S as StorageAdapter } from '../types-DKcxHXxq.js';
|
|
9
|
-
import '../types-
|
|
9
|
+
import '../types-C7FJtFXV.js';
|
|
10
10
|
import '../form-types.js';
|
|
11
11
|
|
|
12
12
|
interface MockProviderOptions {
|
|
@@ -37,6 +37,7 @@ interface SupabaseQueryBuilder {
|
|
|
37
37
|
or(filters: string): SupabaseQueryBuilder;
|
|
38
38
|
order(column: string, options?: {
|
|
39
39
|
ascending?: boolean;
|
|
40
|
+
nullsFirst?: boolean;
|
|
40
41
|
}): SupabaseQueryBuilder;
|
|
41
42
|
range(from: number, to: number): SupabaseQueryBuilder;
|
|
42
43
|
single(): SupabaseQueryBuilder;
|
|
@@ -55,6 +56,35 @@ interface SupabaseQueryBuilder {
|
|
|
55
56
|
*/
|
|
56
57
|
interface SupabaseClient {
|
|
57
58
|
from(table: string): SupabaseQueryBuilder;
|
|
59
|
+
/** Supabase Realtime; needed only for `subscribe` (see `SupabaseProviderOptions.realtime`). */
|
|
60
|
+
channel?(name: string): SupabaseRealtimeChannel;
|
|
61
|
+
/** Releases a channel made by `channel()`. */
|
|
62
|
+
removeChannel?(channel: SupabaseRealtimeChannel): unknown;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One `postgres_changes` message, as Supabase Realtime sends it. Only the
|
|
66
|
+
* fields the provider reads; a real payload has more.
|
|
67
|
+
*/
|
|
68
|
+
interface SupabaseRealtimePayload {
|
|
69
|
+
eventType: "INSERT" | "UPDATE" | "DELETE";
|
|
70
|
+
/** The row after an insert or update (empty for a delete). */
|
|
71
|
+
new?: Record<string, unknown>;
|
|
72
|
+
/** For a delete or update: the old row's key (all of it with `REPLICA IDENTITY FULL`). */
|
|
73
|
+
old?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The part of a Supabase `RealtimeChannel` the provider uses. A real
|
|
77
|
+
* `@supabase/supabase-js` channel satisfies it.
|
|
78
|
+
*/
|
|
79
|
+
interface SupabaseRealtimeChannel {
|
|
80
|
+
on(type: "postgres_changes", filter: {
|
|
81
|
+
event: "*" | "INSERT" | "UPDATE" | "DELETE";
|
|
82
|
+
schema: string;
|
|
83
|
+
table: string;
|
|
84
|
+
filter?: string;
|
|
85
|
+
}, callback: (payload: SupabaseRealtimePayload) => void): SupabaseRealtimeChannel;
|
|
86
|
+
subscribe(callback?: (status: string, error?: unknown) => void): SupabaseRealtimeChannel;
|
|
87
|
+
unsubscribe?(): unknown;
|
|
58
88
|
}
|
|
59
89
|
/**
|
|
60
90
|
* What `createSupabaseProvider` actually requires from its `client` argument
|
|
@@ -72,6 +102,9 @@ interface SupabaseClient {
|
|
|
72
102
|
*/
|
|
73
103
|
interface SupabaseClientInput {
|
|
74
104
|
from(table: string): unknown;
|
|
105
|
+
/** Supabase Realtime, for `subscribe`. Any real client's `channel` fits. */
|
|
106
|
+
channel?(name: string): unknown;
|
|
107
|
+
removeChannel?(channel: unknown): unknown;
|
|
75
108
|
}
|
|
76
109
|
/** What a write hook is told about the write it is part of. */
|
|
77
110
|
interface SupabaseWriteContext {
|
|
@@ -138,6 +171,14 @@ interface SupabaseResourceConfig {
|
|
|
138
171
|
* `useResource` and `useResourceForm` invalidate after their writes.
|
|
139
172
|
*/
|
|
140
173
|
invalidates?: InvalidationKey[] | ((event: WriteEvent) => InvalidationKey[]);
|
|
174
|
+
/**
|
|
175
|
+
* Serve `subscribe` for this resource through Supabase Realtime
|
|
176
|
+
* (`postgres_changes`), so `live` lists and records refresh when the table
|
|
177
|
+
* changes. The table must be in the `supabase_realtime` publication, with
|
|
178
|
+
* RLS on. `false` turns it off even when `SupabaseProviderOptions.realtime`
|
|
179
|
+
* covers the resource.
|
|
180
|
+
*/
|
|
181
|
+
realtime?: boolean;
|
|
141
182
|
}
|
|
142
183
|
/** Options for `createSupabaseProvider`. All optional; none changes reads. */
|
|
143
184
|
interface SupabaseProviderOptions {
|
|
@@ -175,6 +216,13 @@ interface SupabaseProviderOptions {
|
|
|
175
216
|
resource: string;
|
|
176
217
|
operation: "getList" | "getOne" | "getMany";
|
|
177
218
|
}) => void | Promise<void>;
|
|
219
|
+
/**
|
|
220
|
+
* Which resources `subscribe` serves through Supabase Realtime: `true` for
|
|
221
|
+
* all, a list of names, or a test. Off by default; a resource's own
|
|
222
|
+
* `resources[name].realtime` wins. Needs a client with `channel()` (any
|
|
223
|
+
* `@supabase/supabase-js` client).
|
|
224
|
+
*/
|
|
225
|
+
realtime?: boolean | readonly string[] | ((resource: string) => boolean);
|
|
178
226
|
}
|
|
179
227
|
/**
|
|
180
228
|
* The Supabase data provider. Reads and writes go through the client you
|
|
@@ -227,7 +275,11 @@ interface PostgrestFieldErrors {
|
|
|
227
275
|
* Reads which columns a Postgres constraint error is about, from PostgREST's
|
|
228
276
|
* `message` / `details`, and gives safe messages for them. Only plain column
|
|
229
277
|
* names are taken from the store's text; the values it echoes are never
|
|
230
|
-
* used.
|
|
278
|
+
* used. A unique violation whose columns cannot be read (a complex
|
|
279
|
+
* expression index, no details), or an insert/update foreign-key violation
|
|
280
|
+
* whose key is an expression, gets the safe sentence with no `fields`, so the
|
|
281
|
+
* store's raw text (constraint name, echoed value) never reaches the user.
|
|
282
|
+
* Returns `undefined` for anything else.
|
|
231
283
|
*
|
|
232
284
|
* - `23505` unique violation, `Key (email)=(…) already exists.` → each
|
|
233
285
|
* column: "This value is already in use." (several columns: "This
|
|
@@ -306,4 +358,4 @@ type GenesisProviderProps = {
|
|
|
306
358
|
*/
|
|
307
359
|
declare function GenesisProvider({ children, provider, mock, storage, queryClient: queryClientProp, }: GenesisProviderProps): react.JSX.Element;
|
|
308
360
|
|
|
309
|
-
export { BaseRecord, DataProvider, DataProviderRoot, type DataProviderRootProps, DataReadError, DataWriteError, GenesisProvider, type GenesisProviderProps, Identifier, InvalidationKey, type MockProviderOptions, type PostgrestFieldErrors, ReadOutcome, type SupabaseClient, type SupabaseClientInput, type SupabaseProviderOptions, type SupabaseResourceConfig, type SupabaseWriteContext, WriteEvent, WriteOutcome, createMockProvider, createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, useDataProvider, useStorage, writeOutcomeFromCode };
|
|
361
|
+
export { BaseRecord, DataProvider, DataProviderRoot, type DataProviderRootProps, DataReadError, DataWriteError, GenesisProvider, type GenesisProviderProps, Identifier, InvalidationKey, type MockProviderOptions, type PostgrestFieldErrors, ReadOutcome, type SupabaseClient, type SupabaseClientInput, type SupabaseProviderOptions, type SupabaseRealtimeChannel, type SupabaseRealtimePayload, type SupabaseResourceConfig, type SupabaseWriteContext, WriteEvent, WriteOutcome, createMockProvider, createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, useDataProvider, useStorage, writeOutcomeFromCode };
|
package/dist/provider/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, writeOutcomeFromCode } from '../chunk-
|
|
1
|
+
export { createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, writeOutcomeFromCode } from '../chunk-Y475Z7BM.js';
|
|
2
2
|
export { DataReadError, isDataReadError, readFailureMessage } from '../chunk-HUNGCTRA.js';
|
|
3
3
|
export { DataWriteError, isDataWriteError } from '../chunk-5NHR6ZXS.js';
|
|
4
4
|
import '../chunk-RYEXWQFD.js';
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { e as ResourceContract, D as DataRow } from '../types-
|
|
2
|
-
export { A as AuditEntry, F as FieldPermissions, a as FieldReference, b as FieldSchema, c as FilterOperator, P as ProviderCapabilities, R as RecordComment, d as ResourceAccess, f as ResourceRelation, g as ResourceSchema, h as ResourceSummary, i as RowAction, S as StorageType, V as VersionField } from '../types-
|
|
3
|
-
import { I as Identifier } from '../types-
|
|
4
|
-
export { D as DataReadError, a as DataReadErrorOptions, b as DataWriteError, c as DataWriteErrorOptions, R as ReadOutcome, W as WriteOutcome, i as isDataReadError, d as isDataWriteError, r as readFailureMessage } from '../read-outcome-
|
|
1
|
+
import { e as ResourceContract, D as DataRow } from '../types-C7FJtFXV.js';
|
|
2
|
+
export { A as AuditEntry, F as FieldPermissions, a as FieldReference, b as FieldSchema, c as FilterOperator, P as ProviderCapabilities, R as RecordComment, d as ResourceAccess, f as ResourceRelation, g as ResourceSchema, h as ResourceSummary, i as RowAction, S as StorageType, V as VersionField } from '../types-C7FJtFXV.js';
|
|
3
|
+
import { I as Identifier } from '../types-CS9UpxQS.js';
|
|
4
|
+
export { D as DataReadError, a as DataReadErrorOptions, b as DataWriteError, c as DataWriteErrorOptions, R as ReadOutcome, W as WriteOutcome, i as isDataReadError, d as isDataWriteError, r as readFailureMessage } from '../read-outcome-C_9HbXvb.js';
|
|
5
5
|
export { FieldType as WidgetHint } from '../form-types.js';
|
|
6
6
|
|
|
7
7
|
/** The key of a contract as a list, whichever form `key` was declared in. */
|
|
@@ -250,6 +250,26 @@ interface ProviderCapabilities {
|
|
|
250
250
|
* of removing it. Asked per resource: `capabilities(resource)`.
|
|
251
251
|
*/
|
|
252
252
|
readonly softDelete?: boolean;
|
|
253
|
+
/**
|
|
254
|
+
* `create`, `update` and `deleteOne` honour `keepalive: true` in their
|
|
255
|
+
* params (e.g. `fetch(url, { keepalive: true })`), so a write sent while
|
|
256
|
+
* the page unloads still reaches the server. Without it, closing the tab
|
|
257
|
+
* while an optimistic or undoable write is being sent shows the browser's
|
|
258
|
+
* "Leave site?" prompt.
|
|
259
|
+
*/
|
|
260
|
+
readonly keepaliveWrites?: boolean;
|
|
261
|
+
/**
|
|
262
|
+
* `getList` serves cursor (keyset) paging: `pagination.cursor` in,
|
|
263
|
+
* `nextCursor` out. Pages stay correct while rows are added or removed,
|
|
264
|
+
* and a deep page costs no more than the first.
|
|
265
|
+
*/
|
|
266
|
+
readonly cursorPagination?: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* `subscribe` reports changes to this resource (asked per resource:
|
|
269
|
+
* `capabilities(resource)`), so `live` lists and records refresh when
|
|
270
|
+
* someone else changes them.
|
|
271
|
+
*/
|
|
272
|
+
readonly realtime?: boolean;
|
|
253
273
|
}
|
|
254
274
|
/** One audit record, as committed alongside the edit it describes. */
|
|
255
275
|
interface AuditEntry {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AuditEntry, g as ResourceSchema, h as ResourceSummary, P as ProviderCapabilities, R as RecordComment } from './types-
|
|
1
|
+
import { A as AuditEntry, g as ResourceSchema, h as ResourceSummary, P as ProviderCapabilities, R as RecordComment } from './types-C7FJtFXV.js';
|
|
2
2
|
|
|
3
3
|
/** Record identifier — supports both string UUIDs and numeric auto-increment IDs. */
|
|
4
4
|
type Identifier = string | number;
|
|
@@ -7,11 +7,20 @@ type BaseRecord = {
|
|
|
7
7
|
id: Identifier;
|
|
8
8
|
[key: string]: unknown;
|
|
9
9
|
};
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Pagination parameters: offset (`page`) or cursor (keyset).
|
|
12
|
+
*
|
|
13
|
+
* Cursor paging: set `cursor` to `null` for the first page and to the
|
|
14
|
+
* previous page's `ListResult.nextCursor` for each next one. A provider that
|
|
15
|
+
* serves it (`capabilities().cursorPagination`) then ignores `page` and
|
|
16
|
+
* returns `nextCursor`; one that doesn't ignores `cursor` and pages by
|
|
17
|
+
* `page`, returning no `nextCursor`. `useInfiniteResource` handles both.
|
|
18
|
+
*/
|
|
11
19
|
interface PaginationParam {
|
|
12
20
|
page: number;
|
|
13
21
|
perPage: number;
|
|
14
|
-
cursor
|
|
22
|
+
/** `null`: the first page, by cursor. A string: the page after that cursor. Opaque. */
|
|
23
|
+
cursor?: string | null;
|
|
15
24
|
}
|
|
16
25
|
/** Sort direction for a single field. */
|
|
17
26
|
interface SortParam {
|
|
@@ -67,7 +76,16 @@ interface ListParams {
|
|
|
67
76
|
/** Result of `getList`. */
|
|
68
77
|
interface ListResult<T> {
|
|
69
78
|
data: T[];
|
|
79
|
+
/**
|
|
80
|
+
* Rows matching the filters and search. In cursor mode a provider may
|
|
81
|
+
* count only on the first page (`cursor: null`) and return `-1` after it.
|
|
82
|
+
*/
|
|
70
83
|
total: number;
|
|
84
|
+
/**
|
|
85
|
+
* Cursor mode only: the cursor for the next page, or `null` when this is
|
|
86
|
+
* the last one. Absent when the provider paged by offset.
|
|
87
|
+
*/
|
|
88
|
+
nextCursor?: string | null;
|
|
71
89
|
}
|
|
72
90
|
/** Parameters for `getOne`. */
|
|
73
91
|
interface GetOneParams {
|
|
@@ -90,6 +108,13 @@ interface GetManyResult<T> {
|
|
|
90
108
|
/** Parameters for `create`. */
|
|
91
109
|
interface CreateParams<T> {
|
|
92
110
|
data: Partial<T>;
|
|
111
|
+
/**
|
|
112
|
+
* Sent while the page unloads (an `undoable` write flushed on close or
|
|
113
|
+
* reload). A fetch-based provider should pass `keepalive: true` to `fetch`
|
|
114
|
+
* so the request outlives the page, and report `keepaliveWrites` in its
|
|
115
|
+
* capabilities. Others ignore it.
|
|
116
|
+
*/
|
|
117
|
+
keepalive?: boolean;
|
|
93
118
|
}
|
|
94
119
|
/** Result of `create`. */
|
|
95
120
|
interface CreateResult<T> {
|
|
@@ -113,6 +138,8 @@ interface UpdateParams<T> {
|
|
|
113
138
|
* ignore it.
|
|
114
139
|
*/
|
|
115
140
|
expectedVersion?: string | null;
|
|
141
|
+
/** See `CreateParams.keepalive`. */
|
|
142
|
+
keepalive?: boolean;
|
|
116
143
|
}
|
|
117
144
|
/** Result of `update`. */
|
|
118
145
|
interface UpdateResult<T> {
|
|
@@ -127,6 +154,8 @@ interface DeleteParams {
|
|
|
127
154
|
id: Identifier;
|
|
128
155
|
/** The version token the row was read at; see `UpdateParams.expectedVersion`. */
|
|
129
156
|
expectedVersion?: string | null;
|
|
157
|
+
/** See `CreateParams.keepalive`. */
|
|
158
|
+
keepalive?: boolean;
|
|
130
159
|
}
|
|
131
160
|
/** Result of `deleteOne`. */
|
|
132
161
|
interface DeleteResult {
|
|
@@ -181,6 +210,34 @@ interface WriteEvent {
|
|
|
181
210
|
/** The row as written, when the provider has it (not for a delete). */
|
|
182
211
|
record?: BaseRecord;
|
|
183
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* A change to a resource, as `DataProvider.subscribe` reports it.
|
|
215
|
+
*
|
|
216
|
+
* Treat it as a HINT that cached reads are stale, never as data to show:
|
|
217
|
+
* the Genesis hooks refetch through the provider's normal (RLS-checked)
|
|
218
|
+
* read instead of writing `record` into the cache. `changed` says only that
|
|
219
|
+
* something may have changed (e.g. after a reconnect, when events may have
|
|
220
|
+
* been missed), so every cached read of the resource should be refetched.
|
|
221
|
+
*/
|
|
222
|
+
type ResourceChangeEvent = {
|
|
223
|
+
type: "created" | "updated" | "deleted";
|
|
224
|
+
/** The changed record's id, when the provider knows it. */
|
|
225
|
+
id?: Identifier;
|
|
226
|
+
/** The record as the provider saw it, when it sends one. A hint only. */
|
|
227
|
+
record?: BaseRecord;
|
|
228
|
+
} | {
|
|
229
|
+
type: "changed";
|
|
230
|
+
};
|
|
231
|
+
/** What a `subscribe` call watches. Every field narrows; none is required. */
|
|
232
|
+
interface SubscribeParams {
|
|
233
|
+
/** Watch one record only. */
|
|
234
|
+
id?: Identifier;
|
|
235
|
+
/**
|
|
236
|
+
* The list's filters. A provider MAY use them to send fewer events; it
|
|
237
|
+
* may also ignore them and report every change to the resource.
|
|
238
|
+
*/
|
|
239
|
+
filters?: FilterParam[];
|
|
240
|
+
}
|
|
184
241
|
/**
|
|
185
242
|
* A cache key to invalidate after a write. Shaped like a TanStack Query
|
|
186
243
|
* `QueryKey` (`readonly unknown[]`), matched by prefix.
|
|
@@ -270,6 +327,18 @@ interface DataProvider {
|
|
|
270
327
|
* `prepare`). `useResourceForm` applies it in edit mode.
|
|
271
328
|
*/
|
|
272
329
|
toFormValues?(resource: string, record: Record<string, unknown>): Record<string, unknown>;
|
|
330
|
+
/**
|
|
331
|
+
* Watch a resource for changes made anywhere (other users, other tabs,
|
|
332
|
+
* server jobs). Calls `callback` for each change until the returned
|
|
333
|
+
* function is called, which must release everything the subscription
|
|
334
|
+
* holds (sockets, channels). Report `realtime: true` in `capabilities()`
|
|
335
|
+
* for the resources it serves; for others it may return a no-op.
|
|
336
|
+
*
|
|
337
|
+
* Events are invalidation hints: `useResource({ live: true })` and
|
|
338
|
+
* `useOne(..., { live: true })` refetch on them and never trust their
|
|
339
|
+
* payload.
|
|
340
|
+
*/
|
|
341
|
+
subscribe?(resource: string, params: SubscribeParams, callback: (event: ResourceChangeEvent) => void): () => void;
|
|
273
342
|
/**
|
|
274
343
|
* Other cache keys a committed write makes stale, beyond the resource's
|
|
275
344
|
* own `[resource]` keys. `useResource` and `useResourceForm` invalidate
|
|
@@ -278,4 +347,4 @@ interface DataProvider {
|
|
|
278
347
|
invalidates?(event: WriteEvent): InvalidationKey[];
|
|
279
348
|
}
|
|
280
349
|
|
|
281
|
-
export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, FilterParam as F, GetManyParams as G, HistoryParams as H, Identifier as I, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, WriteEvent as W, BulkChange as a, BulkResult as b, CreateResult as c, DeleteParams as d, DeleteResult as e, GetManyResult as f, GetOneParams as g, GetOneResult as h, ImportParams as i, InvalidationKey as j, ListResult as k,
|
|
350
|
+
export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, FilterParam as F, GetManyParams as G, HistoryParams as H, Identifier as I, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, WriteEvent as W, BulkChange as a, BulkResult as b, CreateResult as c, DeleteParams as d, DeleteResult as e, GetManyResult as f, GetOneParams as g, GetOneResult as h, ImportParams as i, InvalidationKey as j, ListResult as k, ResourceChangeEvent as l, RowFailure as m, SortParam as n, SubscribeParams as o, UpdateResult as p };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/use-resource.ts"],"names":["keepPreviousData","keepPreviousPlaceholder"],"mappings":"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","file":"chunk-DVW5GXWZ.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 useDelayedWrites,\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 // Waits while an optimistic/undoable write on the resource is pending.\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 delayed = useDelayedWrites();\n const modeArgs = {\n queryClient,\n provider,\n resource,\n mode: mutationMode,\n undoDelayMs,\n onUndoable,\n delayed,\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 () => {\n const result = await provider.create<T>(resource, { data });\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 () => {\n const result = await provider.update<T>(resource, { id, data });\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 () => {\n const result = await provider.deleteOne(resource, { id });\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"]}
|
package/dist/chunk-HT43MPKW.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"]}
|