@ontrails/store 0.2.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/src/types.ts ADDED
@@ -0,0 +1,654 @@
1
+ import type { Signal } from '@ontrails/core';
2
+ import type { StoreAccessorProtocol } from '@ontrails/core/store';
3
+ import type { z } from 'zod';
4
+
5
+ /**
6
+ * Backend-agnostic persistence shapes that an adapter can interpret.
7
+ */
8
+ export type StoreKind = 'tabular' | 'document' | 'file' | 'kv' | 'cache';
9
+
10
+ /**
11
+ * Store-level options applied to the authored definition.
12
+ */
13
+ export interface StoreOptions {
14
+ readonly kind?: StoreKind;
15
+ }
16
+
17
+ /**
18
+ * Object schema accepted by the store definition layer.
19
+ */
20
+ export type StoreObjectSchema = z.ZodObject<Record<string, z.ZodType>>;
21
+
22
+ /**
23
+ * String field names available on a store-backed entity schema.
24
+ */
25
+ export type StoreFieldKey<TSchema extends StoreObjectSchema> = Extract<
26
+ keyof z.output<TSchema>,
27
+ string
28
+ >;
29
+
30
+ type VersionedSchema<
31
+ TSchema extends StoreObjectSchema,
32
+ TVersioned extends boolean | undefined,
33
+ > = TVersioned extends true
34
+ ? TSchema extends z.ZodObject<infer TShape>
35
+ ? z.ZodObject<TShape & { version: z.ZodNumber }>
36
+ : never
37
+ : TSchema;
38
+
39
+ type GeneratedFieldNames<
40
+ TSchema extends StoreObjectSchema,
41
+ TGenerated extends readonly string[] | undefined,
42
+ > = TGenerated extends readonly StoreFieldKey<TSchema>[]
43
+ ? TGenerated[number]
44
+ : never;
45
+
46
+ // We intentionally use Zod v4's shape-based object infer helpers here because
47
+ // the public z.input/z.output path widens away the generic equality we need at
48
+ // store/core trail boundaries. These $-prefixed helpers are semi-internal Zod
49
+ // v4 API; that maintenance tradeoff is acceptable here because the public path
50
+ // does not preserve the boundary-proof shape we need.
51
+ type ObjectInputOf<TShape extends z.ZodRawShape> = z.core.$InferObjectInput<
52
+ TShape,
53
+ Record<never, never>
54
+ >;
55
+
56
+ type ObjectOutputOf<TShape extends z.ZodRawShape> = z.core.$InferObjectOutput<
57
+ TShape,
58
+ Record<never, never>
59
+ >;
60
+
61
+ /**
62
+ * Seed row accepted for one table fixture.
63
+ *
64
+ * Generated fields may be supplied explicitly, but they are optional so test
65
+ * fixtures can omit timestamps and similar server-managed values when the mock
66
+ * store can synthesize them.
67
+ *
68
+ * Feeds input inference through the object's `shape` via a conditional
69
+ * `infer` so fixture inputs stay structurally aligned with the derived
70
+ * create/upsert inputs they meet at store/core trail boundaries.
71
+ *
72
+ * This is not the same type-level path core's `deriveTrail()` uses — core
73
+ * still goes through `z.input<TEntity>` / `z.output<TEntity>` — but at
74
+ * concrete instantiations it collapses to the same structural fixture shape
75
+ * while preserving generic equality across the store/core seam.
76
+ */
77
+ export type StoreFixtureInput<
78
+ TSchema extends StoreObjectSchema,
79
+ TGenerated extends readonly string[] | undefined =
80
+ | readonly string[]
81
+ | undefined,
82
+ > = [TSchema] extends [z.ZodObject<infer TShape>]
83
+ ? Omit<
84
+ ObjectInputOf<TShape>,
85
+ Extract<
86
+ GeneratedFieldNames<TSchema, TGenerated>,
87
+ keyof ObjectInputOf<TShape>
88
+ >
89
+ > &
90
+ Partial<
91
+ Pick<
92
+ ObjectInputOf<TShape>,
93
+ Extract<
94
+ GeneratedFieldNames<TSchema, TGenerated>,
95
+ keyof ObjectInputOf<TShape>
96
+ >
97
+ >
98
+ >
99
+ : never;
100
+
101
+ /**
102
+ * Normalized fixture row after schema validation and default application.
103
+ *
104
+ * Mirror of {@link StoreFixtureInput} on the output side — computed through
105
+ * `$InferObjectOutput<TShape, Record<never, never>>` so row types stay
106
+ * structurally aligned with the entity output shapes they compose against.
107
+ *
108
+ * As with {@link StoreFixtureInput}, this is a shape-based equivalent rather
109
+ * than the identical `z.output<TSchema>` inference path core uses directly.
110
+ */
111
+ export type StoreFixtureRow<
112
+ TSchema extends StoreObjectSchema,
113
+ TGenerated extends readonly string[] | undefined =
114
+ | readonly string[]
115
+ | undefined,
116
+ > = [TSchema] extends [z.ZodObject<infer TShape>]
117
+ ? Omit<
118
+ ObjectOutputOf<TShape>,
119
+ Extract<
120
+ GeneratedFieldNames<TSchema, TGenerated>,
121
+ keyof ObjectOutputOf<TShape>
122
+ >
123
+ > &
124
+ Partial<
125
+ Pick<
126
+ ObjectOutputOf<TShape>,
127
+ Extract<
128
+ GeneratedFieldNames<TSchema, TGenerated>,
129
+ keyof ObjectOutputOf<TShape>
130
+ >
131
+ >
132
+ >
133
+ : never;
134
+
135
+ /**
136
+ * Adapter-owned search metadata.
137
+ *
138
+ * The core package keeps this opaque on purpose. Search behavior is declared
139
+ * here and interpreted by a concrete adapter later.
140
+ */
141
+ export type StoreSearchDefinition = Readonly<Record<string, unknown>>;
142
+
143
+ /**
144
+ * Change signals derived from one store entity definition.
145
+ */
146
+ export interface StoreTableSignals<TPayload> {
147
+ readonly created: Signal<TPayload>;
148
+ readonly updated: Signal<TPayload>;
149
+ readonly removed: Signal<TPayload>;
150
+ }
151
+
152
+ /**
153
+ * Shared fields for all store table input variants.
154
+ */
155
+ interface StoreTableInputBase<
156
+ TSchema extends StoreObjectSchema = StoreObjectSchema,
157
+ TGenerated extends readonly StoreFieldKey<TSchema>[] | undefined =
158
+ | readonly StoreFieldKey<TSchema>[]
159
+ | undefined,
160
+ TVersioned extends boolean | undefined = boolean | undefined,
161
+ > {
162
+ readonly fixtures?: readonly StoreFixtureInput<
163
+ VersionedSchema<TSchema, TVersioned>,
164
+ GeneratedFieldsOfShape<TSchema, TGenerated, TVersioned>
165
+ >[];
166
+ readonly generated?: TGenerated;
167
+ readonly indexed?: readonly StoreFieldKey<TSchema>[];
168
+ readonly indexes?: readonly StoreFieldKey<TSchema>[];
169
+ readonly references?: Readonly<
170
+ Partial<Record<StoreFieldKey<TSchema>, string>>
171
+ >;
172
+ readonly schema: TSchema;
173
+ readonly search?: StoreSearchDefinition;
174
+ readonly versioned?: TVersioned;
175
+ }
176
+
177
+ /**
178
+ * Authored metadata for one store entity.
179
+ *
180
+ * At least one of `identity` or `primaryKey` must be provided. Omitting both
181
+ * is a compile-time error — `resolveIdentity` would throw at runtime without
182
+ * this guard.
183
+ */
184
+ export type StoreTableInput<
185
+ TSchema extends StoreObjectSchema = StoreObjectSchema,
186
+ TGenerated extends readonly StoreFieldKey<TSchema>[] | undefined =
187
+ | readonly StoreFieldKey<TSchema>[]
188
+ | undefined,
189
+ TVersioned extends boolean | undefined = boolean | undefined,
190
+ > =
191
+ | (StoreTableInputBase<TSchema, TGenerated, TVersioned> & {
192
+ readonly identity: StoreFieldKey<TSchema>;
193
+ readonly primaryKey?: StoreFieldKey<TSchema>;
194
+ })
195
+ | (StoreTableInputBase<TSchema, TGenerated, TVersioned> & {
196
+ readonly identity?: StoreFieldKey<TSchema>;
197
+ readonly primaryKey: StoreFieldKey<TSchema>;
198
+ });
199
+
200
+ /**
201
+ * Record of authored tables passed to `store(...)`.
202
+ */
203
+ export type StoreTablesInput = Record<
204
+ string,
205
+ StoreTableInput<
206
+ StoreObjectSchema,
207
+ readonly StoreFieldKey<StoreObjectSchema>[] | undefined,
208
+ boolean | undefined
209
+ >
210
+ >;
211
+
212
+ type DeclaredGeneratedFieldsOfInput<TInput extends StoreTableInput> =
213
+ TInput['generated'] extends readonly StoreFieldKey<TInput['schema']>[]
214
+ ? TInput['generated']
215
+ : readonly [];
216
+
217
+ type GeneratedFieldsOfShape<
218
+ TSchema extends StoreObjectSchema,
219
+ TGenerated extends readonly StoreFieldKey<TSchema>[] | undefined,
220
+ TVersioned extends boolean | undefined,
221
+ > = TVersioned extends true
222
+ ? readonly [
223
+ ...(TGenerated extends readonly StoreFieldKey<TSchema>[]
224
+ ? TGenerated
225
+ : readonly []),
226
+ 'version',
227
+ ]
228
+ : TGenerated extends readonly StoreFieldKey<TSchema>[]
229
+ ? TGenerated
230
+ : readonly [];
231
+
232
+ type VersionedFieldsOfInput<TInput extends StoreTableInput> =
233
+ TInput['versioned'] extends true ? true : false;
234
+
235
+ type SchemaOfInput<TInput extends StoreTableInput> = VersionedSchema<
236
+ TInput['schema'],
237
+ VersionedFieldsOfInput<TInput>
238
+ >;
239
+
240
+ /**
241
+ * Preserve generated fields when present, otherwise normalize to an empty tuple.
242
+ */
243
+ export type GeneratedFieldsOfInput<TInput extends StoreTableInput> =
244
+ GeneratedFieldsOfShape<
245
+ TInput['schema'],
246
+ DeclaredGeneratedFieldsOfInput<TInput>,
247
+ VersionedFieldsOfInput<TInput>
248
+ >;
249
+
250
+ /**
251
+ * Preserve the authored identity field.
252
+ */
253
+ export type IdentityFieldOfInput<TInput extends StoreTableInput> =
254
+ TInput['identity'] extends StoreFieldKey<TInput['schema']>
255
+ ? TInput['identity']
256
+ : TInput['primaryKey'] extends StoreFieldKey<TInput['schema']>
257
+ ? TInput['primaryKey']
258
+ : never;
259
+
260
+ /**
261
+ * Preserve indexed fields when present, otherwise normalize to an empty tuple.
262
+ *
263
+ * At runtime `resolveIndexed` merges both `indexed` and `indexes` arrays, so
264
+ * this type mirrors that behavior: when both are present, the result is the
265
+ * union of both tuples. When only one is provided, it is used directly.
266
+ */
267
+ export type IndexedFieldsOfInput<TInput extends StoreTableInput> =
268
+ TInput['indexed'] extends readonly StoreFieldKey<TInput['schema']>[]
269
+ ? TInput['indexes'] extends readonly StoreFieldKey<TInput['schema']>[]
270
+ ? readonly [...TInput['indexed'], ...TInput['indexes']]
271
+ : TInput['indexed']
272
+ : TInput['indexes'] extends readonly StoreFieldKey<TInput['schema']>[]
273
+ ? TInput['indexes']
274
+ : readonly [];
275
+
276
+ /**
277
+ * Backward-compatible alias for code that still uses the SQL-shaped name.
278
+ */
279
+ export type IndexFieldsOfInput<TInput extends StoreTableInput> =
280
+ TInput['indexes'] extends readonly StoreFieldKey<TInput['schema']>[]
281
+ ? TInput['indexes']
282
+ : IndexedFieldsOfInput<TInput>;
283
+
284
+ /**
285
+ * Preserve references when present, otherwise normalize to an empty object.
286
+ */
287
+ export type ReferencesOfInput<TInput extends StoreTableInput> =
288
+ TInput['references'] extends Readonly<
289
+ Partial<Record<StoreFieldKey<TInput['schema']>, string>>
290
+ >
291
+ ? TInput['references']
292
+ : Readonly<Record<never, never>>;
293
+
294
+ /**
295
+ * Preserve fixtures when present, otherwise normalize to an empty tuple.
296
+ */
297
+ export type FixturesOfInput<TInput extends StoreTableInput> =
298
+ TInput['fixtures'] extends readonly StoreFixtureInput<
299
+ SchemaOfInput<TInput>,
300
+ GeneratedFieldsOfInput<TInput>
301
+ >[]
302
+ ? readonly StoreFixtureRow<
303
+ SchemaOfInput<TInput>,
304
+ GeneratedFieldsOfInput<TInput>
305
+ >[]
306
+ : readonly [];
307
+
308
+ /**
309
+ * Generated store table contract derived from authored metadata.
310
+ */
311
+ export interface StoreTable<
312
+ TInput extends StoreTableInput = StoreTableInput,
313
+ TName extends string = string,
314
+ > {
315
+ readonly fixtureSchema: StoreObjectSchema;
316
+ readonly fixtures: FixturesOfInput<TInput>;
317
+ readonly generated: GeneratedFieldsOfInput<TInput>;
318
+ readonly identity: IdentityFieldOfInput<TInput>;
319
+ readonly indexed: IndexedFieldsOfInput<TInput>;
320
+ readonly indexes: IndexedFieldsOfInput<TInput>;
321
+ readonly insertSchema: StoreObjectSchema;
322
+ readonly name: TName;
323
+ readonly primaryKey: IdentityFieldOfInput<TInput>;
324
+ readonly references: ReferencesOfInput<TInput>;
325
+ readonly schema: SchemaOfInput<TInput>;
326
+ readonly search?: StoreSearchDefinition | undefined;
327
+ readonly signals: StoreTableSignals<z.output<SchemaOfInput<TInput>>>;
328
+ readonly updateSchema: StoreObjectSchema;
329
+ readonly versioned: VersionedFieldsOfInput<TInput>;
330
+ }
331
+
332
+ /**
333
+ * Full store definition returned by `store(...)`.
334
+ */
335
+ export interface StoreDefinition<
336
+ TTables extends StoreTablesInput = StoreTablesInput,
337
+ > {
338
+ readonly get: <TName extends Extract<keyof TTables, string>>(
339
+ name: TName
340
+ ) => StoreTable<TTables[TName], TName>;
341
+ readonly kind: StoreKind;
342
+ readonly signals: readonly Signal<unknown>[];
343
+ readonly tableNames: readonly Extract<keyof TTables, string>[];
344
+ readonly tables: {
345
+ readonly [TName in keyof TTables]: StoreTable<
346
+ TTables[TName],
347
+ Extract<TName, string>
348
+ >;
349
+ };
350
+ readonly type: 'store';
351
+ }
352
+
353
+ /**
354
+ * Structural view of any normalized store table.
355
+ *
356
+ * This stays broad on purpose so adapter packages can accept concrete store
357
+ * definitions returned by `store(...)` without erasing their table-specific
358
+ * types back to one canonical generic instantiation.
359
+ */
360
+ export interface AnyStoreTable {
361
+ readonly fixtureSchema: StoreObjectSchema;
362
+ readonly fixtures: readonly Record<string, unknown>[];
363
+ readonly generated: readonly string[];
364
+ readonly identity: string;
365
+ readonly indexed: readonly string[];
366
+ readonly indexes: readonly string[];
367
+ readonly insertSchema: StoreObjectSchema;
368
+ readonly name: string;
369
+ readonly primaryKey: string;
370
+ readonly references: Readonly<Partial<Record<string, string>>>;
371
+ readonly schema: StoreObjectSchema;
372
+ readonly search?: StoreSearchDefinition | undefined;
373
+ readonly signals: StoreTableSignals<unknown>;
374
+ readonly updateSchema: StoreObjectSchema;
375
+ readonly versioned: boolean;
376
+ }
377
+
378
+ /**
379
+ * Structural view of any normalized store definition.
380
+ */
381
+ export interface AnyStoreDefinition {
382
+ readonly kind: StoreKind;
383
+ readonly signals: readonly Signal<unknown>[];
384
+ readonly tableNames: readonly string[];
385
+ readonly tables: Readonly<Record<string, AnyStoreTable>>;
386
+ readonly type: 'store';
387
+ }
388
+
389
+ type GeneratedFieldKeysOf<TTable extends AnyStoreTable> = readonly Extract<
390
+ TTable['generated'][number],
391
+ StoreFieldKey<TTable['schema']>
392
+ >[];
393
+
394
+ /**
395
+ * Full entity type represented by one store table.
396
+ */
397
+ export type EntityOf<TTable extends AnyStoreTable> = z.output<TTable['schema']>;
398
+
399
+ /**
400
+ * Fixture seed input accepted for one table.
401
+ */
402
+ export type FixtureInputOf<TTable extends AnyStoreTable> = StoreFixtureInput<
403
+ TTable['schema'],
404
+ GeneratedFieldKeysOf<TTable>
405
+ >;
406
+
407
+ /**
408
+ * Normalized fixture row available on one table.
409
+ */
410
+ export type FixtureOf<TTable extends AnyStoreTable> = StoreFixtureRow<
411
+ TTable['schema'],
412
+ GeneratedFieldKeysOf<TTable>
413
+ >;
414
+
415
+ /**
416
+ * Identity field name for one store entity.
417
+ */
418
+ export type IdentityOf<TTable extends AnyStoreTable> = TTable['identity'];
419
+
420
+ /**
421
+ * Server-managed fields for one store table.
422
+ */
423
+ export type GeneratedKeysOf<TTable extends AnyStoreTable> = Extract<
424
+ TTable['generated'][number],
425
+ StoreFieldKey<TTable['schema']>
426
+ >;
427
+
428
+ /**
429
+ * Insert shape: entity minus generated fields, with defaulted fields optional.
430
+ *
431
+ * Uses `$InferObjectInput<TShape, Record<never, never>>` via a
432
+ * conditional `infer` on the object's `shape`. At concrete instantiations
433
+ * this collapses to the same structural shape as
434
+ * `Omit<z.input<TTable['schema']>, GeneratedKeysOf<TTable>>`, but the
435
+ * shape-based form lets TypeScript prove structural equality with
436
+ * `CreateInputOf<Entity, ...>` at trail boundaries without widening
437
+ * generic call sites to `Record<string, unknown>`.
438
+ *
439
+ * Defaulted fields remain optional because `$InferObjectInput` honors
440
+ * `OptionalInSchema`, mirroring the runtime insert schema behavior.
441
+ */
442
+ export type InsertOf<TTable extends AnyStoreTable> = [
443
+ TTable['schema'],
444
+ ] extends [z.ZodObject<infer TShape>]
445
+ ? Omit<ObjectInputOf<TShape>, GeneratedKeysOf<TTable>>
446
+ : never;
447
+
448
+ /**
449
+ * Update shape: partial insert minus the primary key (immutable identifier).
450
+ */
451
+ export type UpdateOf<TTable extends AnyStoreTable> = Partial<
452
+ Omit<InsertOf<TTable>, IdentityOf<TTable>>
453
+ >;
454
+
455
+ /**
456
+ * Upsert shape: entity payload with generated fields remaining optional.
457
+ *
458
+ * This matches the backend-agnostic "create or replace" contract while
459
+ * still allowing adapters to synthesize generated values like IDs and
460
+ * timestamps when the caller omits them.
461
+ */
462
+ export type UpsertOf<TTable extends AnyStoreTable> = FixtureInputOf<TTable>;
463
+
464
+ /**
465
+ * Typed filter shape for list operations.
466
+ */
467
+ export type FiltersOf<TTable extends AnyStoreTable> = Partial<EntityOf<TTable>>;
468
+
469
+ /**
470
+ * Common pagination controls for store list operations.
471
+ */
472
+ export interface StoreListOptions {
473
+ readonly limit?: number;
474
+ readonly offset?: number;
475
+ }
476
+
477
+ /**
478
+ * Shared identifier type for read/write accessors.
479
+ */
480
+ export type StoreIdentifierOf<TTable extends AnyStoreTable> =
481
+ EntityOf<TTable>[Extract<IdentityOf<TTable>, keyof EntityOf<TTable>>];
482
+
483
+ /**
484
+ * Access mode for a bound store connection or resource.
485
+ */
486
+ export type StoreAccessMode = 'readonly' | 'readwrite';
487
+
488
+ /**
489
+ * Read-only table operations that every bound store must expose.
490
+ */
491
+ export interface ReadOnlyStoreTableAccessor<TTable extends AnyStoreTable> {
492
+ /** Retrieve a single entity by identity. Returns `null` when not found. */
493
+ get(id: StoreIdentifierOf<TTable>): Promise<EntityOf<TTable> | null>;
494
+ /**
495
+ * List entities, optionally filtered. Returns all rows when no filters are
496
+ * provided. Returns an empty array when no rows match.
497
+ */
498
+ list(
499
+ filters?: FiltersOf<TTable>,
500
+ options?: StoreListOptions
501
+ ): Promise<readonly EntityOf<TTable>[]>;
502
+ }
503
+
504
+ /**
505
+ * Backend-agnostic writable operations layered on top of the read contract.
506
+ */
507
+ export interface StoreAccessor<
508
+ TTable extends AnyStoreTable,
509
+ > extends ReadOnlyStoreTableAccessor<TTable> {
510
+ /**
511
+ * Create or replace one entity using the store's identity field.
512
+ *
513
+ * @throws {AlreadyExistsError} On primary key or unique constraint violation.
514
+ *
515
+ * @remarks
516
+ * This is an intentional throw-based boundary: store adapters throw typed
517
+ * errors (`AlreadyExistsError`) rather than returning `Result`. Trail
518
+ * implementations that call store accessors should catch and convert to
519
+ * `Result.err()` at their level. A future safe variant returning `Result`
520
+ * is planned but deferred to avoid cascading changes across all adapters.
521
+ */
522
+ upsert(input: UpsertOf<TTable>): Promise<EntityOf<TTable>>;
523
+ /**
524
+ * Remove an entity by identity. Returns `{ deleted: true }` when the
525
+ * row was found and removed, `{ deleted: false }` when no matching row
526
+ * existed (not an error).
527
+ */
528
+ remove(id: StoreIdentifierOf<TTable>): Promise<{ readonly deleted: boolean }>;
529
+ }
530
+
531
+ // ---------------------------------------------------------------------------
532
+ // Compile-time assertion: StoreAccessor satisfies the core accessor protocol.
533
+ //
534
+ // `@ontrails/core/store` declares a structural protocol that `deriveTrail()`
535
+ // uses to synthesize default implementations without importing `@ontrails/store`. We
536
+ // pin the relationship here rather than in core so that any drift between
537
+ // the two shapes fails the store build immediately. If this check fails, the
538
+ // protocol in core has diverged from the store accessor contract — fix the
539
+ // protocol shape, not this assertion.
540
+ // ---------------------------------------------------------------------------
541
+
542
+ type AssertExtends<TActual, TExpected> = TActual extends TExpected
543
+ ? true
544
+ : never;
545
+
546
+ /**
547
+ * Pins the structural relationship between `StoreAccessor` and the core
548
+ * `StoreAccessorProtocol`. If this check resolves to `never`, the protocol in
549
+ * core has drifted from the store accessor contract — fix the protocol shape,
550
+ * not this assertion.
551
+ */
552
+ const storeAccessorProtocolCheck: AssertExtends<
553
+ StoreAccessor<AnyStoreTable>,
554
+ StoreAccessorProtocol<
555
+ UpsertOf<AnyStoreTable>,
556
+ EntityOf<AnyStoreTable>,
557
+ StoreIdentifierOf<AnyStoreTable>,
558
+ FiltersOf<AnyStoreTable>
559
+ >
560
+ > = true;
561
+
562
+ void storeAccessorProtocolCheck;
563
+
564
+ /**
565
+ * Tabular writable operations layered on top of the backend-agnostic
566
+ * contract.
567
+ */
568
+ export interface StoreTableAccessor<
569
+ TTable extends AnyStoreTable,
570
+ > extends StoreAccessor<TTable> {
571
+ /**
572
+ * Insert a new entity.
573
+ *
574
+ * Tabular adapters can expose this convenience when the backend has a
575
+ * native distinction between create and update.
576
+ */
577
+ insert(input: InsertOf<TTable>): Promise<EntityOf<TTable>>;
578
+ /**
579
+ * Patch an entity by identity with partial fields. Returns the updated
580
+ * entity, or `null` when no row with that ID exists.
581
+ *
582
+ * @remarks
583
+ * On versioned tables, `update` does **not** participate in optimistic
584
+ * concurrency control. The `UpdateOf<TTable>` shape is derived by omitting
585
+ * generated fields — including the framework-managed `version` column — so
586
+ * any `version` value is dropped before reaching the adapter and the
587
+ * adapter always auto-increments without comparing. Callers that need
588
+ * lost-update protection must use {@link StoreAccessor.upsert | `upsert`}
589
+ * instead and pass the expected `version` in the payload.
590
+ */
591
+ update(
592
+ id: StoreIdentifierOf<TTable>,
593
+ input: UpdateOf<TTable>
594
+ ): Promise<EntityOf<TTable> | null>;
595
+ }
596
+
597
+ /**
598
+ * Connection shape exposed by a read-only bound store.
599
+ */
600
+ export type ReadOnlyStoreConnection<TStore extends AnyStoreDefinition> = {
601
+ readonly [TName in keyof TStore['tables']]: ReadOnlyStoreTableAccessor<
602
+ TStore['tables'][TName]
603
+ >;
604
+ };
605
+
606
+ /**
607
+ * Backend-agnostic connection shape exposed by a writable bound store.
608
+ */
609
+ export type StoreConnection<TStore extends AnyStoreDefinition> = {
610
+ readonly [TName in keyof TStore['tables']]: StoreAccessor<
611
+ TStore['tables'][TName]
612
+ >;
613
+ };
614
+
615
+ /**
616
+ * Tabular connection shape exposed by adapters that distinguish insert and
617
+ * patch operations from the generalized `upsert` contract.
618
+ */
619
+ export type StoreTableConnection<TStore extends AnyStoreDefinition> = {
620
+ readonly [TName in keyof TStore['tables']]: StoreTableAccessor<
621
+ TStore['tables'][TName]
622
+ >;
623
+ };
624
+
625
+ /**
626
+ * Optional fixture overrides used when building a mock store connection.
627
+ *
628
+ * The shape is a partial map keyed by table name; each entry is a list of
629
+ * fixture inputs validated against the table's fixture schema. Adapters
630
+ * share this type so every store backend seeds mocks the same way.
631
+ */
632
+ export type StoreMockSeed<TDef extends AnyStoreDefinition> = Partial<{
633
+ readonly [TName in keyof TDef['tables']]: readonly FixtureInputOf<
634
+ TDef['tables'][TName]
635
+ >[];
636
+ }>;
637
+
638
+ /**
639
+ * Shared adapter options every store backend accepts.
640
+ *
641
+ * Concrete adapters extend this shape with their backend-specific fields
642
+ * (e.g. `url`, `dir`). Aligning the authored surface here lets the framework
643
+ * reason about adapter options uniformly — one shape, many renderings.
644
+ */
645
+ export interface StoreAdapterOptions<TDef extends AnyStoreDefinition> {
646
+ /** Optional resource id override. Defaults to `"store"`. */
647
+ readonly id?: string;
648
+ /** Human-readable description surfaced on the resource definition. */
649
+ readonly description?: string;
650
+ /** Free-form metadata for downstream tooling and governance. */
651
+ readonly meta?: Record<string, unknown>;
652
+ /** Optional per-table fixture overrides used by the mock resource factory. */
653
+ readonly mockSeed?: StoreMockSeed<TDef>;
654
+ }