@marktiderman/genesis-core 1.2.0 → 1.4.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.
Files changed (42) hide show
  1. package/README.md +142 -0
  2. package/dist/{chunk-4W4W7F4I.js → chunk-6MBACQYA.js} +6 -3
  3. package/dist/chunk-6MBACQYA.js.map +1 -0
  4. package/dist/chunk-7NUL32W7.js +14 -0
  5. package/dist/chunk-7NUL32W7.js.map +1 -0
  6. package/dist/{chunk-6V2TIN3G.js → chunk-GGSXSI6Z.js} +6 -5
  7. package/dist/chunk-GGSXSI6Z.js.map +1 -0
  8. package/dist/{chunk-5FTUB2M6.js → chunk-IHWWJUEN.js} +47 -15
  9. package/dist/chunk-IHWWJUEN.js.map +1 -0
  10. package/dist/chunk-NW26Z3LZ.js +303 -0
  11. package/dist/chunk-NW26Z3LZ.js.map +1 -0
  12. package/dist/{chunk-QFTMHOEH.js → chunk-P4OIIRQS.js} +22 -13
  13. package/dist/chunk-P4OIIRQS.js.map +1 -0
  14. package/dist/{chunk-3P4PHPMJ.js → chunk-ZK5C3OOT.js} +3 -3
  15. package/dist/{chunk-3P4PHPMJ.js.map → chunk-ZK5C3OOT.js.map} +1 -1
  16. package/dist/form-types.d.ts +6 -0
  17. package/dist/hooks/index.d.ts +1 -1
  18. package/dist/hooks/index.js +6 -5
  19. package/dist/hooks/related-keys.d.ts +11 -0
  20. package/dist/hooks/related-keys.js +3 -0
  21. package/dist/hooks/related-keys.js.map +1 -0
  22. package/dist/hooks/use-one.d.ts +1 -1
  23. package/dist/hooks/use-one.js +2 -2
  24. package/dist/hooks/use-resource-form.d.ts +19 -1
  25. package/dist/hooks/use-resource-form.js +4 -3
  26. package/dist/hooks/use-resource.d.ts +14 -1
  27. package/dist/hooks/use-resource.js +3 -2
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +8 -7
  30. package/dist/provider/index.d.ts +140 -6
  31. package/dist/provider/index.js +3 -3
  32. package/dist/schema/index.d.ts +3 -3
  33. package/dist/schema/index.js +1 -1
  34. package/dist/{types-C7UVaF87.d.ts → types-zILrlHuV.d.ts} +226 -4
  35. package/dist/{write-outcome-D8kjoQ1A.d.ts → write-outcome-BE3Tk6bA.d.ts} +10 -1
  36. package/package.json +2 -2
  37. package/dist/chunk-4W4W7F4I.js.map +0 -1
  38. package/dist/chunk-5FTUB2M6.js.map +0 -1
  39. package/dist/chunk-6AA4ISUA.js +0 -125
  40. package/dist/chunk-6AA4ISUA.js.map +0 -1
  41. package/dist/chunk-6V2TIN3G.js.map +0 -1
  42. package/dist/chunk-QFTMHOEH.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import { UseFormReturn } from 'react-hook-form';
2
2
  import { ResourceFormFieldDef } from '../form-types.js';
3
+ import { p as InvalidationKey } from '../types-zILrlHuV.js';
3
4
 
4
5
  interface UseResourceFormOptions {
5
6
  resource: string;
@@ -11,14 +12,31 @@ interface UseResourceFormOptions {
11
12
  listData?: Record<string, unknown>[];
12
13
  onSuccess?: (data: Record<string, unknown>) => void;
13
14
  onError?: (error: Error) => void;
14
- /** Pre-fill create form defaults from URL query params. Default true. */
15
+ /**
16
+ * Pre-fill create form defaults from URL query params: a param named like a
17
+ * field (`?status=draft`) becomes that field's starting value. Default true.
18
+ * Pass false for a form that should not read the page's URL, e.g. one
19
+ * opened from a list whose own filters live in the query string.
20
+ */
15
21
  prefillFromUrl?: boolean;
22
+ /**
23
+ * Generate a placeholder ("Enter name", "name@example.com") for a field
24
+ * whose `placeholder` is unset. Default true. A placeholder that only
25
+ * repeats the label adds noise without information, so `ResourceForm`
26
+ * turns this off unless asked.
27
+ */
28
+ autoPlaceholder?: boolean;
16
29
  /** Validation trigger mode. Default "onBlur". */
17
30
  validationMode?: "onBlur" | "onChange" | "onSubmit";
18
31
  /** Enable autosave drafts to localStorage for create mode. */
19
32
  autosave?: boolean | {
20
33
  debounceMs?: number;
21
34
  };
35
+ /**
36
+ * Other query keys to invalidate after a save, on top of `[resource]` and
37
+ * the provider's own `invalidates`.
38
+ */
39
+ invalidates?: InvalidationKey[];
22
40
  }
23
41
  interface UseResourceFormReturn {
24
42
  form: UseFormReturn<Record<string, unknown>>;
@@ -1,7 +1,8 @@
1
- export { buildArrayItemDefaults, useResourceForm } from '../chunk-5FTUB2M6.js';
1
+ export { buildArrayItemDefaults, useResourceForm } from '../chunk-IHWWJUEN.js';
2
2
  import '../chunk-RYEXWQFD.js';
3
- import '../chunk-3P4PHPMJ.js';
4
- import '../chunk-6V2TIN3G.js';
3
+ import '../chunk-ZK5C3OOT.js';
4
+ import '../chunk-7NUL32W7.js';
5
+ import '../chunk-GGSXSI6Z.js';
5
6
  import '../chunk-ZYJUEJ4Z.js';
6
7
  //# sourceMappingURL=use-resource-form.js.map
7
8
  //# sourceMappingURL=use-resource-form.js.map
@@ -1,5 +1,5 @@
1
1
  import { UseQueryResult, UseMutationResult } from '@tanstack/react-query';
2
- import { s as SortParam, i as FilterParam, m as ListResult, L as ListParams, I as Identifier, B as BaseRecord } from '../types-C7UVaF87.js';
2
+ import { E as SortParam, k as FilterParam, p as InvalidationKey, q as ListResult, L as ListParams, I as Identifier, B as BaseRecord } from '../types-zILrlHuV.js';
3
3
  import '../form-types.js';
4
4
 
5
5
  interface UseResourceOptions<T> {
@@ -21,6 +21,19 @@ interface UseResourceOptions<T> {
21
21
  searchFields?: string[];
22
22
  /** Disable auto-fetching. */
23
23
  enabled?: boolean;
24
+ /**
25
+ * Other query keys to invalidate after a create, update or delete, on top
26
+ * of `[resource]` and the provider's own `invalidates` (e.g. `[["me"]]`
27
+ * when a write changes the signed-in user's roles).
28
+ */
29
+ invalidates?: InvalidationKey[];
30
+ /**
31
+ * While the next page / sort / search is loading, keep returning the
32
+ * previous result (`list.isPlaceholderData` is true meanwhile) instead of
33
+ * dropping to `undefined`. A paged list then stays on screen between pages
34
+ * rather than flashing a skeleton. Default false.
35
+ */
36
+ keepPreviousData?: boolean;
24
37
  onCreateSuccess?: (data: T) => void;
25
38
  onUpdateSuccess?: (data: T) => void;
26
39
  onDeleteSuccess?: () => void;
@@ -1,5 +1,6 @@
1
- export { useResource } from '../chunk-QFTMHOEH.js';
2
- import '../chunk-6V2TIN3G.js';
1
+ export { useResource } from '../chunk-P4OIIRQS.js';
2
+ import '../chunk-7NUL32W7.js';
3
+ import '../chunk-GGSXSI6Z.js';
3
4
  import '../chunk-ZYJUEJ4Z.js';
4
5
  //# sourceMappingURL=use-resource.js.map
5
6
  //# sourceMappingURL=use-resource.js.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { A as ArrayFilter, a as AuditEntry, B as BaseRecord, C as CreateParams, b as CreateResult, D as DataProvider, c as DataRow, d as DeleteParams, e as DeleteResult, F as FieldPermissions, f as FieldReference, g as FieldSchema, h as FilterOperator, i as FilterParam, G as GetManyParams, j as GetManyResult, k as GetOneParams, l as GetOneResult, I as Identifier, L as ListParams, m as ListResult, N as NullFilter, P as PaginationParam, n as ProviderCapabilities, R as RangeFilter, o as ResourceAccess, p as ResourceContract, q as ResourceSchema, r as ResourceSummary, S as ScalarFilter, s as SortParam, t as StorageType, U as UpdateParams, u as UpdateResult, V as VersionField } from './types-C7UVaF87.js';
2
- export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from './write-outcome-D8kjoQ1A.js';
3
- export { DataProviderRoot, DataProviderRootProps, GenesisProvider, GenesisProviderProps, MockProviderOptions, SupabaseClient, SupabaseClientInput, createMockProvider, createSupabaseProvider, useDataProvider, useStorage } from './provider/index.js';
1
+ export { A as ArrayFilter, a as AuditEntry, B as BaseRecord, b as BulkChange, c as BulkResult, C as CreateParams, d as CreateResult, D as DataProvider, e as DataRow, f as DeleteParams, g as DeleteResult, F as FieldPermissions, h as FieldReference, i as FieldSchema, j as FilterOperator, k as FilterParam, G as GetManyParams, l as GetManyResult, m as GetOneParams, n as GetOneResult, H as HistoryParams, I as Identifier, o as ImportParams, p as InvalidationKey, L as ListParams, q as ListResult, N as NullFilter, P as PaginationParam, r as ProviderCapabilities, R as RangeFilter, s as RecordComment, t as ResourceAccess, u as ResourceContract, v as ResourceRelation, w as ResourceSchema, x as ResourceSummary, y as RowAction, z as RowFailure, S as ScalarFilter, E as SortParam, J as StorageType, U as UpdateParams, K as UpdateResult, V as VersionField, W as WriteEvent } from './types-zILrlHuV.js';
2
+ export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from './write-outcome-BE3Tk6bA.js';
3
+ export { DataProviderRoot, DataProviderRootProps, GenesisProvider, GenesisProviderProps, MockProviderOptions, SupabaseClient, SupabaseClientInput, SupabaseProviderOptions, SupabaseResourceConfig, SupabaseWriteContext, createMockProvider, createSupabaseProvider, 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';
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
- export { createSupabaseProvider } from './chunk-6AA4ISUA.js';
1
+ export { createSupabaseProvider, toDataWriteError, writeOutcomeFromCode } from './chunk-NW26Z3LZ.js';
2
2
  export { decodeRecordKey, encodeRecordKey, keyFields } from './chunk-KV2HW4G4.js';
3
- export { DataWriteError, isDataWriteError } from './chunk-4W4W7F4I.js';
3
+ export { DataWriteError, isDataWriteError } from './chunk-6MBACQYA.js';
4
4
  import './chunk-XCX2TRBL.js';
5
5
  import './chunk-65SXNHWE.js';
6
6
  import './chunk-EL7YGOTY.js';
7
- export { buildArrayItemDefaults, useResourceForm } from './chunk-5FTUB2M6.js';
7
+ export { useWizard } from './chunk-7KOLYF27.js';
8
+ export { buildArrayItemDefaults, useResourceForm } from './chunk-IHWWJUEN.js';
8
9
  export { titleCase } from './chunk-RYEXWQFD.js';
9
- export { useOne } from './chunk-3P4PHPMJ.js';
10
- export { useResource } from './chunk-QFTMHOEH.js';
11
- export { DataProviderRoot, GenesisProvider, createMockProvider, useDataProvider, useStorage } from './chunk-6V2TIN3G.js';
10
+ export { useOne } from './chunk-ZK5C3OOT.js';
11
+ export { useResource } from './chunk-P4OIIRQS.js';
12
+ import './chunk-7NUL32W7.js';
13
+ export { DataProviderRoot, GenesisProvider, createMockProvider, useDataProvider, useStorage } from './chunk-GGSXSI6Z.js';
12
14
  export { createLocalStorageAdapter, createMemoryStorageAdapter, createNoopStorageAdapter } from './chunk-ZYJUEJ4Z.js';
13
- export { useWizard } from './chunk-7KOLYF27.js';
14
15
  //# sourceMappingURL=index.js.map
15
16
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,10 @@
1
- import { D as DataProvider } from '../types-C7UVaF87.js';
2
- export { A as ArrayFilter, B as BaseRecord, C as CreateParams, b as CreateResult, d as DeleteParams, e as DeleteResult, i as FilterParam, G as GetManyParams, j as GetManyResult, k as GetOneParams, l as GetOneResult, I as Identifier, L as ListParams, m as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, S as ScalarFilter, s as SortParam, U as UpdateParams, u as UpdateResult } from '../types-C7UVaF87.js';
3
- export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from '../write-outcome-D8kjoQ1A.js';
1
+ import { D as DataProvider, I as Identifier, B as BaseRecord, p as InvalidationKey, W as WriteEvent } from '../types-zILrlHuV.js';
2
+ export { A as ArrayFilter, b as BulkChange, c as BulkResult, C as CreateParams, d as CreateResult, f as DeleteParams, g as DeleteResult, k as FilterParam, G as GetManyParams, l as GetManyResult, m as GetOneParams, n as GetOneResult, H as HistoryParams, o as ImportParams, L as ListParams, q as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, z as RowFailure, S as ScalarFilter, E as SortParam, U as UpdateParams, K as UpdateResult } from '../types-zILrlHuV.js';
3
+ import { D as DataWriteError, W as WriteOutcome } from '../write-outcome-BE3Tk6bA.js';
4
+ export { a as DataWriteErrorOptions, i as isDataWriteError } from '../write-outcome-BE3Tk6bA.js';
4
5
  import * as react from 'react';
5
6
  import { ReactNode } from 'react';
7
+ import { QueryClient } from '@tanstack/react-query';
6
8
  import { S as StorageAdapter } from '../types-DKcxHXxq.js';
7
9
  import '../form-types.js';
8
10
 
@@ -70,8 +72,124 @@ interface SupabaseClient {
70
72
  interface SupabaseClientInput {
71
73
  from(table: string): unknown;
72
74
  }
73
- declare function createSupabaseProvider(client: SupabaseClientInput): DataProvider;
75
+ /** What a write hook is told about the write it is part of. */
76
+ interface SupabaseWriteContext {
77
+ resource: string;
78
+ action: "create" | "update";
79
+ /** The row's id, for an update. */
80
+ id?: Identifier;
81
+ }
82
+ /** Per-resource rules for `createSupabaseProvider`. Every field is optional. */
83
+ interface SupabaseResourceConfig {
84
+ /** Singular noun for failure messages, e.g. "project". Defaults to "record". */
85
+ noun?: string;
86
+ /**
87
+ * `"bin"` for a table whose delete trigger moves the row to a recycle bin
88
+ * instead of removing it (see `SupabaseProviderOptions.binTables`).
89
+ * `"hard"` forces a plain delete even when `binTables` lists the table.
90
+ */
91
+ deleteMode?: "hard" | "bin";
92
+ /**
93
+ * Turns submitted values into the row to write: trim, drop read-only keys,
94
+ * validate. Runs before every create and update, including the `create`
95
+ * and `update` overrides. Throw (ideally `DataWriteError("invalid", ...)`)
96
+ * to refuse the write; nothing is sent.
97
+ */
98
+ prepare?: (values: Record<string, unknown>, context: SupabaseWriteContext) => Record<string, unknown> | Promise<Record<string, unknown>>;
99
+ /**
100
+ * Creates the row some other way than an insert, e.g. a security-definer
101
+ * RPC. Return the new row, or its id and the provider reads the row back
102
+ * (if the read-back fails, the create is still reported as saved, with
103
+ * just the id, so the user does not retry and make a second one). Throw to
104
+ * fail; a Supabase `{ message, code }` error is mapped like any other.
105
+ */
106
+ create?: (row: Record<string, unknown>) => Promise<BaseRecord | Identifier>;
107
+ /**
108
+ * Updates the row some other way than an update, e.g. an RPC. Return the
109
+ * updated row, or its id (or `true`) and the provider reads it back.
110
+ * Returning nothing (`undefined`/`null`/`false`) means no row changed: the provider then reads the
111
+ * row to say why (`forbidden` or `gone`), exactly as for a plain update,
112
+ * so a function that silently skips a row never reports a save.
113
+ */
114
+ update?: (id: Identifier, row: Record<string, unknown>) => Promise<BaseRecord | Identifier | boolean | null | undefined | void>;
115
+ /** Maps a stored row to edit-form values; see `DataProvider.toFormValues`. */
116
+ toFormValues?: (record: Record<string, unknown>) => Record<string, unknown>;
117
+ /**
118
+ * Other cache keys a committed write to this resource makes stale, e.g.
119
+ * `[["staff"], ["me"]]`. Served through `DataProvider.invalidates`, which
120
+ * `useResource` and `useResourceForm` invalidate after their writes.
121
+ */
122
+ invalidates?: InvalidationKey[] | ((event: WriteEvent) => InvalidationKey[]);
123
+ }
124
+ /** Options for `createSupabaseProvider`. All optional; none changes reads. */
125
+ interface SupabaseProviderOptions {
126
+ /**
127
+ * Recycle-bin tables: their delete trigger turns a delete into a "binned"
128
+ * mark and the binned row drops out of the caller's view (RLS or a view
129
+ * filter). The database reports such a delete as zero rows, so for these
130
+ * the provider checks the row is visible, deletes it, and reads it back:
131
+ * gone from view is success, still visible is `forbidden`. A row that was
132
+ * not visible beforehand is `gone`.
133
+ */
134
+ binTables?: readonly string[] | ((resource: string) => boolean);
135
+ /** Per-resource rules, keyed by resource (table) name. */
136
+ resources?: Record<string, SupabaseResourceConfig>;
137
+ /**
138
+ * Called after every committed create, update and delete, e.g. to
139
+ * invalidate caches the Genesis hooks don't know about. Awaited, but a
140
+ * failure is logged and never turns a committed write into a failed one.
141
+ */
142
+ onWrite?: (event: WriteEvent) => void | Promise<void>;
143
+ }
144
+ /**
145
+ * The Supabase data provider. Reads and writes go through the client you
146
+ * pass, under that client's session and RLS.
147
+ *
148
+ * Writes never report a save that did not happen:
149
+ * - `update` and `deleteOne` ask for the affected rows. Zero rows is a
150
+ * `DataWriteError`: the row is read back, and still visible means
151
+ * `forbidden` (RLS or a trigger refused the change), no longer visible
152
+ * means `gone`.
153
+ * - Database errors on writes are thrown as `DataWriteError` with the
154
+ * outcome their code means (`writeOutcomeFromCode`) and the code kept on
155
+ * `error.code`.
156
+ * - Recycle-bin tables (`binTables`) succeed when the row leaves the
157
+ * caller's view.
158
+ *
159
+ * Reads are unchanged: errors are thrown as an `Error` carrying `code`.
160
+ */
161
+ declare function createSupabaseProvider(client: SupabaseClientInput, options?: SupabaseProviderOptions): DataProvider;
162
+
163
+ /**
164
+ * Maps a Postgres SQLSTATE or PostgREST error code to the write outcome it
165
+ * means. Anything not listed is `error`: a code is only read as a refusal or
166
+ * a bad value when it says so.
167
+ *
168
+ * - `42501` (insufficient privilege, including "new row violates row-level
169
+ * security policy") → `forbidden`
170
+ * - `22***` (data exception: bad type, out of range) and `23***` (integrity
171
+ * constraint: `23505` unique, `23503` foreign key, `23502` not null,
172
+ * `23514` check) → `invalid`
173
+ * - `PGRST301`–`PGRST303` (JWT missing, expired or rejected), `28000`,
174
+ * `28P01` → `unauthenticated`
175
+ * - `PGRST116` (one row was asked for and none came back) → `gone`
176
+ */
177
+ declare function writeOutcomeFromCode(code: string | null | undefined): WriteOutcome;
178
+ /**
179
+ * Turns anything a write threw, or the `{ message, code }` error a Supabase
180
+ * client returned, into a `DataWriteError`. A `DataWriteError` passes through
181
+ * unchanged; anything else keeps its message and `code`, and is kept as the
182
+ * `cause`.
183
+ */
184
+ declare function toDataWriteError(error: unknown): DataWriteError;
74
185
 
186
+ /**
187
+ * Puts a `DataProvider` in context, and nothing else. Mount it inside your
188
+ * app's own `QueryClientProvider` and the Genesis hooks share that cache:
189
+ * the same query keys (`[resource, "list", params]`,
190
+ * `[resource, "detail", id, select]`), so invalidating `[resource]` from app
191
+ * code refreshes Genesis lists too, and vice versa.
192
+ */
75
193
  interface DataProviderRootProps {
76
194
  provider: DataProvider;
77
195
  children: ReactNode;
@@ -87,6 +205,14 @@ type GenesisProviderProps = {
87
205
  mock?: never;
88
206
  /** Platform-specific storage adapter. Defaults to localStorage (SSR-safe). */
89
207
  storage?: StorageAdapter;
208
+ /**
209
+ * The TanStack Query client to use. Without it, GenesisProvider creates
210
+ * its OWN client — a second cache beside any the app already has, so app
211
+ * queries and Genesis queries would not see each other's invalidations.
212
+ * An app with a `QueryClientProvider` should pass its client here, or
213
+ * use `DataProviderRoot` inside that provider instead.
214
+ */
215
+ queryClient?: QueryClient;
90
216
  } | {
91
217
  children: ReactNode;
92
218
  provider?: never;
@@ -97,7 +223,15 @@ type GenesisProviderProps = {
97
223
  };
98
224
  /** Platform-specific storage adapter. Defaults to localStorage (SSR-safe). */
99
225
  storage?: StorageAdapter;
226
+ /** See the `provider` variant. */
227
+ queryClient?: QueryClient;
100
228
  };
101
- declare function GenesisProvider({ children, provider, mock, storage, }: GenesisProviderProps): react.JSX.Element;
229
+ /**
230
+ * Convenience root: a `QueryClientProvider`, storage, and a data provider
231
+ * (yours, or a mock built from datasets). It creates its own `QueryClient`
232
+ * unless you pass `queryClient`; an app that already has one should pass it,
233
+ * or mount `DataProviderRoot` inside its existing `QueryClientProvider`.
234
+ */
235
+ declare function GenesisProvider({ children, provider, mock, storage, queryClient: queryClientProp, }: GenesisProviderProps): react.JSX.Element;
102
236
 
103
- export { DataProvider, DataProviderRoot, type DataProviderRootProps, GenesisProvider, type GenesisProviderProps, type MockProviderOptions, type SupabaseClient, type SupabaseClientInput, createMockProvider, createSupabaseProvider, useDataProvider, useStorage };
237
+ export { BaseRecord, DataProvider, DataProviderRoot, type DataProviderRootProps, DataWriteError, GenesisProvider, type GenesisProviderProps, Identifier, InvalidationKey, type MockProviderOptions, type SupabaseClient, type SupabaseClientInput, type SupabaseProviderOptions, type SupabaseResourceConfig, type SupabaseWriteContext, WriteEvent, WriteOutcome, createMockProvider, createSupabaseProvider, toDataWriteError, useDataProvider, useStorage, writeOutcomeFromCode };
@@ -1,6 +1,6 @@
1
- export { createSupabaseProvider } from '../chunk-6AA4ISUA.js';
2
- export { DataWriteError, isDataWriteError } from '../chunk-4W4W7F4I.js';
3
- export { DataProviderRoot, GenesisProvider, createMockProvider, useDataProvider, useStorage } from '../chunk-6V2TIN3G.js';
1
+ export { createSupabaseProvider, toDataWriteError, writeOutcomeFromCode } from '../chunk-NW26Z3LZ.js';
2
+ export { DataWriteError, isDataWriteError } from '../chunk-6MBACQYA.js';
3
+ export { DataProviderRoot, GenesisProvider, createMockProvider, useDataProvider, useStorage } from '../chunk-GGSXSI6Z.js';
4
4
  import '../chunk-ZYJUEJ4Z.js';
5
5
  //# sourceMappingURL=index.js.map
6
6
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
- import { p as ResourceContract, I as Identifier, c as DataRow } from '../types-C7UVaF87.js';
2
- export { a as AuditEntry, F as FieldPermissions, f as FieldReference, g as FieldSchema, h as FilterOperator, n as ProviderCapabilities, o as ResourceAccess, q as ResourceSchema, r as ResourceSummary, t as StorageType, V as VersionField } from '../types-C7UVaF87.js';
3
- export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from '../write-outcome-D8kjoQ1A.js';
1
+ import { u as ResourceContract, I as Identifier, e as DataRow } from '../types-zILrlHuV.js';
2
+ export { a as AuditEntry, F as FieldPermissions, h as FieldReference, i as FieldSchema, j as FilterOperator, r as ProviderCapabilities, s as RecordComment, t as ResourceAccess, v as ResourceRelation, w as ResourceSchema, x as ResourceSummary, y as RowAction, J as StorageType, V as VersionField } from '../types-zILrlHuV.js';
3
+ export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from '../write-outcome-BE3Tk6bA.js';
4
4
  export { FieldType as WidgetHint } from '../form-types.js';
5
5
 
6
6
  /** The key of a contract as a list, whichever form `key` was declared in. */
@@ -1,4 +1,4 @@
1
1
  export { decodeRecordKey, encodeRecordKey, keyFields } from '../chunk-KV2HW4G4.js';
2
- export { DataWriteError, isDataWriteError } from '../chunk-4W4W7F4I.js';
2
+ export { DataWriteError, isDataWriteError } from '../chunk-6MBACQYA.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -47,6 +47,8 @@ interface FieldSchema {
47
47
  readonly array?: boolean;
48
48
  /** Human note (surfaces in describe()/drift reports). */
49
49
  readonly description?: string;
50
+ /** Human label, e.g. "Date of birth". Absent means a UI derives one from `name`. */
51
+ readonly label?: string;
50
52
  /** The store's exact type, e.g. `timestamp with time zone` or `public.status`. */
51
53
  readonly dbType?: string;
52
54
  /** Which control fits this field. A hint: a UI may still choose another. */
@@ -68,7 +70,7 @@ interface FieldSchema {
68
70
  * One declaration of a resource: its fields and its identity.
69
71
  *
70
72
  * `key` names the identity field, or several for a composite key (a junction
71
- * table keyed on `(coach_id, client_id)`). Use {@link keyFields} to read it as
73
+ * table keyed on `(team_id, user_id)`). Use {@link keyFields} to read it as
72
74
  * a list without caring which form it took.
73
75
  */
74
76
  interface ResourceContract<Row extends DataRow = DataRow> {
@@ -97,6 +99,46 @@ interface ResourceAccess {
97
99
  readonly update: boolean;
98
100
  /** Why `update` (or `read`) is false, in one human sentence. */
99
101
  readonly reason?: string;
102
+ /** True when this user may add rows. Absent means the provider doesn't say. */
103
+ readonly create?: boolean;
104
+ /** True when this user may delete rows they can see. */
105
+ readonly delete?: boolean;
106
+ /** True when this user may read a row's audit history. */
107
+ readonly history?: boolean;
108
+ /** True when this user may read and add comments on rows they can see. */
109
+ readonly comment?: boolean;
110
+ /** Why `create`, `delete`, `history` or `comment` is false, in one human sentence each. */
111
+ readonly reasons?: {
112
+ readonly create?: string;
113
+ readonly delete?: string;
114
+ readonly history?: string;
115
+ readonly comment?: string;
116
+ };
117
+ }
118
+ /**
119
+ * How another resource relates to this one, from the store's foreign keys.
120
+ *
121
+ * - `one`: a field on THIS resource points at one row of `resource`.
122
+ * - `many`: rows of `resource` point at this row through their `field`.
123
+ * - `through`: many rows of `resource`, linked by the junction resource in
124
+ * `through` (a table whose key is two foreign keys).
125
+ */
126
+ interface ResourceRelation {
127
+ readonly kind: "one" | "many" | "through";
128
+ /** The related resource. */
129
+ readonly resource: string;
130
+ /**
131
+ * `one`: the field on this resource. `many`: the field on `resource` that
132
+ * holds this row's key. `through`: the junction's field holding this row's key.
133
+ */
134
+ readonly field: string;
135
+ /** For `through`: the junction resource, and its field pointing at `resource`. */
136
+ readonly through?: {
137
+ readonly resource: string;
138
+ readonly otherField: string;
139
+ };
140
+ /** True when at most one related row can exist (a unique foreign key). */
141
+ readonly unique?: boolean;
100
142
  }
101
143
  /**
102
144
  * The column a provider checks on every update so an edit never silently
@@ -111,7 +153,12 @@ interface VersionField {
111
153
  * `expectedVersion` echoes `String(row[field])` back unchanged.
112
154
  */
113
155
  readonly field: string;
114
- readonly kind: "integer" | "timestamp";
156
+ /**
157
+ * `row`: no column tracks versions, so the token is a fingerprint of the
158
+ * row's values, and `field` is a name the provider adds to each record
159
+ * (it is not one of the resource's fields).
160
+ */
161
+ readonly kind: "integer" | "timestamp" | "row";
115
162
  }
116
163
  /**
117
164
  * A resource as `describe()` reports it for the CURRENT user: the contract,
@@ -124,6 +171,32 @@ interface ResourceSchema<Row extends DataRow = DataRow> extends ResourceContract
124
171
  readonly access: ResourceAccess;
125
172
  /** Present only when writes can be version-checked. */
126
173
  readonly version?: VersionField;
174
+ /** The field that best names a row to a person (a name or title), for links and pickers. */
175
+ readonly labelField?: string;
176
+ /** Resources this one links to or is linked from. Only ones this user may see. */
177
+ readonly relations?: readonly ResourceRelation[];
178
+ /** Fields the user may set when creating a row. Absent when creating isn't allowed. */
179
+ readonly createFields?: readonly string[];
180
+ /** Row actions this user may run: each calls the app's own function on one row. */
181
+ readonly actions?: readonly RowAction[];
182
+ }
183
+ /** A named operation on one row, carried out by the app's own database function. */
184
+ interface RowAction {
185
+ readonly name: string;
186
+ readonly label: string;
187
+ /** Ask before running, with this question. */
188
+ readonly confirm?: string;
189
+ /** One sentence on what it does. */
190
+ readonly description?: string;
191
+ }
192
+ /** A comment on a record. */
193
+ interface RecordComment {
194
+ readonly id: string;
195
+ /** The signed-in user who wrote it (the token's `sub`). */
196
+ readonly author: string;
197
+ readonly body: string;
198
+ /** ISO 8601, as the store recorded it. */
199
+ readonly createdAt: string;
127
200
  }
128
201
  /** One entry of `listResources()`: enough to draw a sidebar. */
129
202
  interface ResourceSummary {
@@ -151,6 +224,21 @@ interface ProviderCapabilities {
151
224
  readonly versionedWrites: boolean;
152
225
  /** Records may be keyed by more than one field. */
153
226
  readonly compositeKeys: boolean;
227
+ /** Server-side sort by several fields at once (`ListParams.sorts`). */
228
+ readonly multiSort?: boolean;
229
+ /** `create` and `deleteOne` are served, subject to each resource's access. */
230
+ readonly create?: boolean;
231
+ readonly delete?: boolean;
232
+ /** `history` is served, subject to each resource's access. */
233
+ readonly history?: boolean;
234
+ /** `importRows` is served, with a dry run. */
235
+ readonly import?: boolean;
236
+ /** `undo` is served. */
237
+ readonly undo?: boolean;
238
+ /** `runAction` is served. */
239
+ readonly actions?: boolean;
240
+ /** `comments` and `addComment` are served. */
241
+ readonly comments?: boolean;
154
242
  }
155
243
  /** One audit record, as committed alongside the edit it describes. */
156
244
  interface AuditEntry {
@@ -162,7 +250,10 @@ interface AuditEntry {
162
250
  readonly resource: string;
163
251
  /** The record's key, field name → value. */
164
252
  readonly key: DataRow;
165
- readonly action: "create" | "update" | "delete";
253
+ /** `run` is a row action; `rowAction` names it. */
254
+ readonly action: "create" | "update" | "delete" | "run";
255
+ /** For `run`: which row action. */
256
+ readonly rowAction?: string;
166
257
  /** Values of the changed fields before the edit. */
167
258
  readonly before: DataRow | null;
168
259
  /** Values of the changed fields after the edit. */
@@ -219,6 +310,11 @@ type FilterParam = ScalarFilter | ArrayFilter | RangeFilter | NullFilter;
219
310
  interface ListParams {
220
311
  pagination?: PaginationParam;
221
312
  sort?: SortParam;
313
+ /**
314
+ * Sort by several fields, first one first. Wins over `sort` on a provider
315
+ * whose capabilities report `multiSort`; others use only `sort`.
316
+ */
317
+ sorts?: SortParam[];
222
318
  filters?: FilterParam[];
223
319
  /** Supabase select syntax, e.g. "*, author:profiles(name)". Mock provider ignores this. */
224
320
  select?: string;
@@ -258,6 +354,8 @@ interface CreateParams<T> {
258
354
  /** Result of `create`. */
259
355
  interface CreateResult<T> {
260
356
  data: T;
357
+ /** The audit entry committed with the new row, from a provider that audits. */
358
+ audit?: AuditEntry;
261
359
  }
262
360
  /** Parameters for `update`. */
263
361
  interface UpdateParams<T> {
@@ -287,13 +385,67 @@ interface UpdateResult<T> {
287
385
  /** Parameters for `deleteOne`. */
288
386
  interface DeleteParams {
289
387
  id: Identifier;
388
+ /** The version token the row was read at; see `UpdateParams.expectedVersion`. */
389
+ expectedVersion?: string | null;
290
390
  }
291
391
  /** Result of `deleteOne`. */
292
392
  interface DeleteResult {
293
393
  data: {
294
394
  id: Identifier;
295
395
  };
396
+ /** The audit entry committed with the deletion, from a provider that audits. */
397
+ audit?: AuditEntry;
398
+ }
399
+ /** One row's change in a bulk update. */
400
+ interface BulkChange {
401
+ id: Identifier;
402
+ data: Record<string, unknown>;
403
+ /** The version token the row was read at. */
404
+ expectedVersion?: string | null;
296
405
  }
406
+ /** Why one row of a bulk write or import didn't go through. */
407
+ interface RowFailure {
408
+ /** The row's position in the request, from 0. */
409
+ index: number;
410
+ id?: Identifier;
411
+ outcome: string;
412
+ message: string;
413
+ }
414
+ /** Result of a bulk write: every row changed, or none did. */
415
+ interface BulkResult {
416
+ /** Rows changed; always the number requested when `ok`. */
417
+ count: number;
418
+ ok: boolean;
419
+ /** Present when `ok` is false: every row that failed, so the caller can fix them all at once. */
420
+ failures?: RowFailure[];
421
+ /** One audit entry per row changed. */
422
+ audit?: AuditEntry[];
423
+ }
424
+ /** Parameters for `importRows`. */
425
+ interface ImportParams {
426
+ rows: Record<string, unknown>[];
427
+ /** Check every row against the database and change nothing. */
428
+ dryRun: boolean;
429
+ }
430
+ /** Parameters for `history`. */
431
+ interface HistoryParams {
432
+ id: Identifier;
433
+ /** Most recent first; default and maximum are the provider's. */
434
+ limit?: number;
435
+ }
436
+ /** A write that committed, as passed to a provider's `onWrite` and `invalidates`. */
437
+ interface WriteEvent {
438
+ resource: string;
439
+ kind: "create" | "update" | "delete";
440
+ id: Identifier;
441
+ /** The row as written, when the provider has it (not for a delete). */
442
+ record?: BaseRecord;
443
+ }
444
+ /**
445
+ * A cache key to invalidate after a write. Shaped like a TanStack Query
446
+ * `QueryKey` (`readonly unknown[]`), matched by prefix.
447
+ */
448
+ type InvalidationKey = readonly unknown[];
297
449
  interface DataProvider {
298
450
  getList<T extends BaseRecord = BaseRecord>(resource: string, params: ListParams): Promise<ListResult<T>>;
299
451
  getOne<T extends BaseRecord = BaseRecord>(resource: string, params: GetOneParams): Promise<GetOneResult<T>>;
@@ -314,6 +466,76 @@ interface DataProvider {
314
466
  * controls a provider cannot serve instead of letting them error.
315
467
  */
316
468
  capabilities?(resource?: string): Promise<ProviderCapabilities>;
469
+ /**
470
+ * A record's audit history, most recent first, limited to the fields this
471
+ * user may read.
472
+ */
473
+ history?(resource: string, params: HistoryParams): Promise<AuditEntry[]>;
474
+ /**
475
+ * Update many rows in ONE transaction. All of them change or none do: a
476
+ * single conflict, refusal or invalid value rolls back the rest, and the
477
+ * result lists every row that failed.
478
+ */
479
+ updateMany?(resource: string, params: {
480
+ changes: BulkChange[];
481
+ }): Promise<BulkResult>;
482
+ /** Delete many rows in one transaction, all or none, each at its version. */
483
+ deleteMany?(resource: string, params: {
484
+ items: {
485
+ id: Identifier;
486
+ expectedVersion?: string | null;
487
+ }[];
488
+ }): Promise<BulkResult>;
489
+ /**
490
+ * Add rows from an import. With `dryRun`, every row is tried against the
491
+ * database and nothing is kept, so the result lists every row that would
492
+ * fail; without it, all rows are added in one transaction or none are.
493
+ */
494
+ importRows?(resource: string, params: ImportParams): Promise<BulkResult>;
495
+ /**
496
+ * Undo one audited change with a compensating write: put the changed
497
+ * fields back, remove an added row, or restore a deleted one. Refused when
498
+ * the row has changed since.
499
+ */
500
+ undo?(resource: string, params: {
501
+ auditId: string;
502
+ }): Promise<{
503
+ audit?: AuditEntry;
504
+ }>;
505
+ /**
506
+ * Run one of the resource's row actions (`ResourceSchema.actions`) on a
507
+ * row, at the version it was read. The app's function does the work; the
508
+ * row's change is audited.
509
+ */
510
+ runAction?(resource: string, params: {
511
+ id: Identifier;
512
+ action: string;
513
+ expectedVersion?: string | null;
514
+ }): Promise<{
515
+ data: Record<string, unknown> | null;
516
+ audit?: AuditEntry;
517
+ }>;
518
+ /** A record's comments, oldest first. */
519
+ comments?(resource: string, params: {
520
+ id: Identifier;
521
+ }): Promise<RecordComment[]>;
522
+ /** Add a comment to a record, as the signed-in user. */
523
+ addComment?(resource: string, params: {
524
+ id: Identifier;
525
+ body: string;
526
+ }): Promise<RecordComment>;
527
+ /**
528
+ * Maps a stored row to the values an edit form starts from, for a resource
529
+ * whose form shape differs from its table (the reverse of a write's
530
+ * `prepare`). `useResourceForm` applies it in edit mode.
531
+ */
532
+ toFormValues?(resource: string, record: Record<string, unknown>): Record<string, unknown>;
533
+ /**
534
+ * Other cache keys a committed write makes stale, beyond the resource's
535
+ * own `[resource]` keys. `useResource` and `useResourceForm` invalidate
536
+ * them after their writes.
537
+ */
538
+ invalidates?(event: WriteEvent): InvalidationKey[];
317
539
  }
318
540
 
319
- export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, FieldPermissions as F, GetManyParams as G, Identifier as I, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, VersionField as V, AuditEntry as a, CreateResult as b, DataRow as c, DeleteParams as d, DeleteResult as e, FieldReference as f, FieldSchema as g, FilterOperator as h, FilterParam as i, GetManyResult as j, GetOneParams as k, GetOneResult as l, ListResult as m, ProviderCapabilities as n, ResourceAccess as o, ResourceContract as p, ResourceSchema as q, ResourceSummary as r, SortParam as s, StorageType as t, UpdateResult as u };
541
+ export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, SortParam as E, FieldPermissions as F, GetManyParams as G, HistoryParams as H, Identifier as I, StorageType as J, UpdateResult as K, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, VersionField as V, WriteEvent as W, AuditEntry as a, BulkChange as b, BulkResult as c, CreateResult as d, DataRow as e, DeleteParams as f, DeleteResult as g, FieldReference as h, FieldSchema as i, FilterOperator as j, FilterParam as k, GetManyResult as l, GetOneParams as m, GetOneResult as n, ImportParams as o, InvalidationKey as p, ListResult as q, ProviderCapabilities as r, RecordComment as s, ResourceAccess as t, ResourceContract as u, ResourceRelation as v, ResourceSchema as w, ResourceSummary as x, RowAction as y, RowFailure as z };