@marktiderman/genesis-core 1.2.0 → 1.3.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.
@@ -3,6 +3,6 @@ export { UseOneOptions, useOne } from './use-one.js';
3
3
  export { UseResourceFormOptions, UseResourceFormReturn, buildArrayItemDefaults, useResourceForm } from './use-resource-form.js';
4
4
  export { UseWizardReturn, WizardStep, useWizard } from './use-wizard.js';
5
5
  import '@tanstack/react-query';
6
- import '../types-C7UVaF87.js';
6
+ import '../types-DUbRb8qV.js';
7
7
  import '../form-types.js';
8
8
  import 'react-hook-form';
@@ -1,5 +1,5 @@
1
1
  import { UseQueryResult } from '@tanstack/react-query';
2
- import { B as BaseRecord, I as Identifier } from '../types-C7UVaF87.js';
2
+ import { B as BaseRecord, I as Identifier } from '../types-DUbRb8qV.js';
3
3
  import '../form-types.js';
4
4
 
5
5
  interface UseOneOptions {
@@ -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 { z as SortParam, k as FilterParam, p as ListResult, L as ListParams, I as Identifier, B as BaseRecord } from '../types-DUbRb8qV.js';
3
3
  import '../form-types.js';
4
4
 
5
5
  interface UseResourceOptions<T> {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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';
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, L as ListParams, p as ListResult, N as NullFilter, P as PaginationParam, q as ProviderCapabilities, R as RangeFilter, r as RecordComment, s as ResourceAccess, t as ResourceContract, u as ResourceRelation, v as ResourceSchema, w as ResourceSummary, x as RowAction, y as RowFailure, S as ScalarFilter, z as SortParam, E as StorageType, U as UpdateParams, J as UpdateResult, V as VersionField } from './types-DUbRb8qV.js';
2
+ export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from './write-outcome-aYToY3dK.js';
3
3
  export { DataProviderRoot, DataProviderRootProps, GenesisProvider, GenesisProviderProps, MockProviderOptions, SupabaseClient, SupabaseClientInput, createMockProvider, createSupabaseProvider, useDataProvider, useStorage } from './provider/index.js';
4
4
  export { S as StorageAdapter } from './types-DKcxHXxq.js';
5
5
  export { createLocalStorageAdapter, createMemoryStorageAdapter, createNoopStorageAdapter } from './storage/index.js';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { createSupabaseProvider } from './chunk-6AA4ISUA.js';
2
+ import './chunk-XCX2TRBL.js';
2
3
  export { decodeRecordKey, encodeRecordKey, keyFields } from './chunk-KV2HW4G4.js';
3
4
  export { DataWriteError, isDataWriteError } from './chunk-4W4W7F4I.js';
4
- import './chunk-XCX2TRBL.js';
5
5
  import './chunk-65SXNHWE.js';
6
6
  import './chunk-EL7YGOTY.js';
7
7
  export { buildArrayItemDefaults, useResourceForm } from './chunk-5FTUB2M6.js';
@@ -1,6 +1,6 @@
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 } from '../types-DUbRb8qV.js';
2
+ export { A as ArrayFilter, B as BaseRecord, 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, I as Identifier, o as ImportParams, L as ListParams, p as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, y as RowFailure, S as ScalarFilter, z as SortParam, U as UpdateParams, J as UpdateResult } from '../types-DUbRb8qV.js';
3
+ export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from '../write-outcome-aYToY3dK.js';
4
4
  import * as react from 'react';
5
5
  import { ReactNode } from 'react';
6
6
  import { S as StorageAdapter } from '../types-DKcxHXxq.js';
@@ -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 { t as ResourceContract, I as Identifier, e as DataRow } from '../types-DUbRb8qV.js';
2
+ export { a as AuditEntry, F as FieldPermissions, h as FieldReference, i as FieldSchema, j as FilterOperator, q as ProviderCapabilities, r as RecordComment, s as ResourceAccess, u as ResourceRelation, v as ResourceSchema, w as ResourceSummary, x as RowAction, E as StorageType, V as VersionField } from '../types-DUbRb8qV.js';
3
+ export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from '../write-outcome-aYToY3dK.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. */
@@ -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. */
@@ -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,12 +385,53 @@ 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;
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;
296
435
  }
297
436
  interface DataProvider {
298
437
  getList<T extends BaseRecord = BaseRecord>(resource: string, params: ListParams): Promise<ListResult<T>>;
@@ -314,6 +453,64 @@ interface DataProvider {
314
453
  * controls a provider cannot serve instead of letting them error.
315
454
  */
316
455
  capabilities?(resource?: string): Promise<ProviderCapabilities>;
456
+ /**
457
+ * A record's audit history, most recent first, limited to the fields this
458
+ * user may read.
459
+ */
460
+ history?(resource: string, params: HistoryParams): Promise<AuditEntry[]>;
461
+ /**
462
+ * Update many rows in ONE transaction. All of them change or none do: a
463
+ * single conflict, refusal or invalid value rolls back the rest, and the
464
+ * result lists every row that failed.
465
+ */
466
+ updateMany?(resource: string, params: {
467
+ changes: BulkChange[];
468
+ }): Promise<BulkResult>;
469
+ /** Delete many rows in one transaction, all or none, each at its version. */
470
+ deleteMany?(resource: string, params: {
471
+ items: {
472
+ id: Identifier;
473
+ expectedVersion?: string | null;
474
+ }[];
475
+ }): Promise<BulkResult>;
476
+ /**
477
+ * Add rows from an import. With `dryRun`, every row is tried against the
478
+ * database and nothing is kept, so the result lists every row that would
479
+ * fail; without it, all rows are added in one transaction or none are.
480
+ */
481
+ importRows?(resource: string, params: ImportParams): Promise<BulkResult>;
482
+ /**
483
+ * Undo one audited change with a compensating write: put the changed
484
+ * fields back, remove an added row, or restore a deleted one. Refused when
485
+ * the row has changed since.
486
+ */
487
+ undo?(resource: string, params: {
488
+ auditId: string;
489
+ }): Promise<{
490
+ audit?: AuditEntry;
491
+ }>;
492
+ /**
493
+ * Run one of the resource's row actions (`ResourceSchema.actions`) on a
494
+ * row, at the version it was read. The app's function does the work; the
495
+ * row's change is audited.
496
+ */
497
+ runAction?(resource: string, params: {
498
+ id: Identifier;
499
+ action: string;
500
+ expectedVersion?: string | null;
501
+ }): Promise<{
502
+ data: Record<string, unknown> | null;
503
+ audit?: AuditEntry;
504
+ }>;
505
+ /** A record's comments, oldest first. */
506
+ comments?(resource: string, params: {
507
+ id: Identifier;
508
+ }): Promise<RecordComment[]>;
509
+ /** Add a comment to a record, as the signed-in user. */
510
+ addComment?(resource: string, params: {
511
+ id: Identifier;
512
+ body: string;
513
+ }): Promise<RecordComment>;
317
514
  }
318
515
 
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 };
516
+ export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, StorageType as E, FieldPermissions as F, GetManyParams as G, HistoryParams as H, Identifier as I, UpdateResult as J, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, VersionField as V, 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, ListResult as p, ProviderCapabilities as q, RecordComment as r, ResourceAccess as s, ResourceContract as t, ResourceRelation as u, ResourceSchema as v, ResourceSummary as w, RowAction as x, RowFailure as y, SortParam as z };
@@ -1,4 +1,4 @@
1
- import { c as DataRow } from './types-C7UVaF87.js';
1
+ import { e as DataRow } from './types-DUbRb8qV.js';
2
2
 
3
3
  /**
4
4
  * - `conflict`: someone else changed the row since it was read. `current`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marktiderman/genesis-core",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,