@ontrails/store 1.0.0-beta.14

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 (46) hide show
  1. package/.agents/notes/2026-04-04/handoff-202604032309-9e85a104.md +38 -0
  2. package/.turbo/turbo-build.log +1 -0
  3. package/.turbo/turbo-lint.log +3 -0
  4. package/.turbo/turbo-typecheck.log +1 -0
  5. package/CHANGELOG.md +12 -0
  6. package/README.md +213 -0
  7. package/dist/drizzle/index.d.ts +3 -0
  8. package/dist/drizzle/index.d.ts.map +1 -0
  9. package/dist/drizzle/index.js +2 -0
  10. package/dist/drizzle/index.js.map +1 -0
  11. package/dist/drizzle/runtime.d.ts +21 -0
  12. package/dist/drizzle/runtime.d.ts.map +1 -0
  13. package/dist/drizzle/runtime.js +458 -0
  14. package/dist/drizzle/runtime.js.map +1 -0
  15. package/dist/drizzle/schema.d.ts +15 -0
  16. package/dist/drizzle/schema.d.ts.map +1 -0
  17. package/dist/drizzle/schema.js +322 -0
  18. package/dist/drizzle/schema.js.map +1 -0
  19. package/dist/drizzle/types.d.ts +40 -0
  20. package/dist/drizzle/types.d.ts.map +1 -0
  21. package/dist/drizzle/types.js +2 -0
  22. package/dist/drizzle/types.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/store.d.ts +26 -0
  28. package/dist/store.d.ts.map +1 -0
  29. package/dist/store.js +192 -0
  30. package/dist/store.js.map +1 -0
  31. package/dist/types.d.ts +224 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +2 -0
  34. package/dist/types.js.map +1 -0
  35. package/package.json +29 -0
  36. package/src/__tests__/store.test.ts +333 -0
  37. package/src/drizzle/__tests__/drizzle.test.ts +469 -0
  38. package/src/drizzle/index.ts +17 -0
  39. package/src/drizzle/runtime.ts +853 -0
  40. package/src/drizzle/schema.ts +577 -0
  41. package/src/drizzle/types.ts +70 -0
  42. package/src/index.ts +39 -0
  43. package/src/store.ts +367 -0
  44. package/src/types.ts +361 -0
  45. package/tsconfig.json +9 -0
  46. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,224 @@
1
+ import type { z } from 'zod';
2
+ /**
3
+ * Object schema accepted by the store definition layer.
4
+ */
5
+ export type StoreObjectSchema = z.ZodObject<Record<string, z.ZodType>>;
6
+ /**
7
+ * String field names available on a store-backed entity schema.
8
+ */
9
+ export type StoreFieldKey<TSchema extends StoreObjectSchema> = Extract<keyof z.output<TSchema>, string>;
10
+ type GeneratedFieldNames<TSchema extends StoreObjectSchema, TGenerated extends readonly StoreFieldKey<TSchema>[] | undefined> = TGenerated extends readonly StoreFieldKey<TSchema>[] ? TGenerated[number] : never;
11
+ /**
12
+ * Seed row accepted for one table fixture.
13
+ *
14
+ * Generated fields may be supplied explicitly, but they are optional so test
15
+ * fixtures can omit timestamps and similar server-managed values when the mock
16
+ * store can synthesize them.
17
+ */
18
+ export type StoreFixtureInput<TSchema extends StoreObjectSchema, TGenerated extends readonly StoreFieldKey<TSchema>[] | undefined = readonly StoreFieldKey<TSchema>[] | undefined> = Omit<z.input<TSchema>, Extract<GeneratedFieldNames<TSchema, TGenerated>, keyof z.input<TSchema>>> & Partial<Pick<z.input<TSchema>, Extract<GeneratedFieldNames<TSchema, TGenerated>, keyof z.input<TSchema>>>>;
19
+ /**
20
+ * Normalized fixture row after schema validation and default application.
21
+ */
22
+ export type StoreFixtureRow<TSchema extends StoreObjectSchema, TGenerated extends readonly StoreFieldKey<TSchema>[] | undefined = readonly StoreFieldKey<TSchema>[] | undefined> = Omit<z.output<TSchema>, Extract<GeneratedFieldNames<TSchema, TGenerated>, keyof z.output<TSchema>>> & Partial<Pick<z.output<TSchema>, Extract<GeneratedFieldNames<TSchema, TGenerated>, keyof z.output<TSchema>>>>;
23
+ /**
24
+ * Connector-owned search metadata.
25
+ *
26
+ * The core package keeps this opaque on purpose. Search behavior is declared
27
+ * here and interpreted by a concrete connector later.
28
+ */
29
+ export type StoreSearchDefinition = Readonly<Record<string, unknown>>;
30
+ /**
31
+ * Authored metadata for one store table.
32
+ */
33
+ export interface StoreTableInput<TSchema extends StoreObjectSchema = StoreObjectSchema, TGenerated extends readonly StoreFieldKey<TSchema>[] | undefined = readonly StoreFieldKey<TSchema>[] | undefined> {
34
+ readonly fixtures?: readonly StoreFixtureInput<TSchema, TGenerated>[];
35
+ readonly generated?: TGenerated;
36
+ readonly indexes?: readonly StoreFieldKey<TSchema>[];
37
+ readonly primaryKey: StoreFieldKey<TSchema>;
38
+ readonly references?: Readonly<Partial<Record<StoreFieldKey<TSchema>, string>>>;
39
+ readonly schema: TSchema;
40
+ readonly search?: StoreSearchDefinition;
41
+ }
42
+ /**
43
+ * Record of authored tables passed to `store(...)`.
44
+ */
45
+ export type StoreTablesInput = Record<string, StoreTableInput<StoreObjectSchema, readonly StoreFieldKey<StoreObjectSchema>[] | undefined>>;
46
+ /**
47
+ * Preserve generated fields when present, otherwise normalize to an empty tuple.
48
+ */
49
+ export type GeneratedFieldsOfInput<TInput extends StoreTableInput> = TInput['generated'] extends readonly StoreFieldKey<TInput['schema']>[] ? TInput['generated'] : readonly [];
50
+ /**
51
+ * Preserve index fields when present, otherwise normalize to an empty tuple.
52
+ */
53
+ export type IndexFieldsOfInput<TInput extends StoreTableInput> = TInput['indexes'] extends readonly StoreFieldKey<TInput['schema']>[] ? TInput['indexes'] : readonly [];
54
+ /**
55
+ * Preserve references when present, otherwise normalize to an empty object.
56
+ */
57
+ export type ReferencesOfInput<TInput extends StoreTableInput> = TInput['references'] extends Readonly<Partial<Record<StoreFieldKey<TInput['schema']>, string>>> ? TInput['references'] : Readonly<Record<never, never>>;
58
+ /**
59
+ * Preserve fixtures when present, otherwise normalize to an empty tuple.
60
+ */
61
+ export type FixturesOfInput<TInput extends StoreTableInput> = TInput['fixtures'] extends readonly StoreFixtureInput<TInput['schema'], GeneratedFieldsOfInput<TInput>>[] ? readonly StoreFixtureRow<TInput['schema'], GeneratedFieldsOfInput<TInput>>[] : readonly [];
62
+ /**
63
+ * Generated store table contract derived from authored metadata.
64
+ */
65
+ export interface StoreTable<TInput extends StoreTableInput = StoreTableInput, TName extends string = string> {
66
+ readonly fixtureSchema: StoreObjectSchema;
67
+ readonly fixtures: FixturesOfInput<TInput>;
68
+ readonly generated: GeneratedFieldsOfInput<TInput>;
69
+ readonly indexes: IndexFieldsOfInput<TInput>;
70
+ readonly insertSchema: StoreObjectSchema;
71
+ readonly name: TName;
72
+ readonly primaryKey: TInput['primaryKey'];
73
+ readonly references: ReferencesOfInput<TInput>;
74
+ readonly schema: TInput['schema'];
75
+ readonly search?: TInput['search'];
76
+ readonly updateSchema: StoreObjectSchema;
77
+ }
78
+ /**
79
+ * Full store definition returned by `store(...)`.
80
+ */
81
+ export interface StoreDefinition<TTables extends StoreTablesInput = StoreTablesInput> {
82
+ readonly get: <TName extends Extract<keyof TTables, string>>(name: TName) => StoreTable<TTables[TName], TName>;
83
+ readonly kind: 'store';
84
+ readonly tableNames: readonly Extract<keyof TTables, string>[];
85
+ readonly tables: {
86
+ readonly [TName in keyof TTables]: StoreTable<TTables[TName], Extract<TName, string>>;
87
+ };
88
+ }
89
+ /**
90
+ * Structural view of any normalized store table.
91
+ *
92
+ * This stays broad on purpose so connector packages can accept concrete store
93
+ * definitions returned by `store(...)` without erasing their table-specific
94
+ * types back to one canonical generic instantiation.
95
+ */
96
+ export interface AnyStoreTable {
97
+ readonly fixtureSchema: StoreObjectSchema;
98
+ readonly fixtures: readonly Record<string, unknown>[];
99
+ readonly generated: readonly string[];
100
+ readonly indexes: readonly string[];
101
+ readonly insertSchema: StoreObjectSchema;
102
+ readonly name: string;
103
+ readonly primaryKey: string;
104
+ readonly references: Readonly<Partial<Record<string, string>>>;
105
+ readonly schema: StoreObjectSchema;
106
+ readonly search?: StoreSearchDefinition | undefined;
107
+ readonly updateSchema: StoreObjectSchema;
108
+ }
109
+ /**
110
+ * Structural view of any normalized store definition.
111
+ */
112
+ export interface AnyStoreDefinition {
113
+ readonly kind: 'store';
114
+ readonly tableNames: readonly string[];
115
+ readonly tables: Readonly<Record<string, AnyStoreTable>>;
116
+ }
117
+ type GeneratedFieldKeysOf<TTable extends AnyStoreTable> = readonly Extract<TTable['generated'][number], StoreFieldKey<TTable['schema']>>[];
118
+ /**
119
+ * Full entity type represented by one store table.
120
+ */
121
+ export type EntityOf<TTable extends AnyStoreTable> = z.output<TTable['schema']>;
122
+ /**
123
+ * Fixture seed input accepted for one table.
124
+ */
125
+ export type FixtureInputOf<TTable extends AnyStoreTable> = StoreFixtureInput<TTable['schema'], GeneratedFieldKeysOf<TTable>>;
126
+ /**
127
+ * Normalized fixture row available on one table.
128
+ */
129
+ export type FixtureOf<TTable extends AnyStoreTable> = StoreFixtureRow<TTable['schema'], GeneratedFieldKeysOf<TTable>>;
130
+ /**
131
+ * Primary-key field name for one store table.
132
+ */
133
+ export type PrimaryKeyOf<TTable extends AnyStoreTable> = TTable['primaryKey'];
134
+ /**
135
+ * Server-managed fields for one store table.
136
+ */
137
+ export type GeneratedKeysOf<TTable extends AnyStoreTable> = Extract<TTable['generated'][number], StoreFieldKey<TTable['schema']>>;
138
+ /**
139
+ * Insert shape: entity minus generated fields, with defaulted fields optional.
140
+ *
141
+ * Uses `z.input` so that fields with `.default()` are correctly represented as
142
+ * optional in the insert shape (matching the runtime insert schema behavior).
143
+ */
144
+ export type InsertOf<TTable extends AnyStoreTable> = Omit<z.input<TTable['schema']>, GeneratedKeysOf<TTable>>;
145
+ /**
146
+ * Update shape: partial insert minus the primary key (immutable identifier).
147
+ */
148
+ export type UpdateOf<TTable extends AnyStoreTable> = Partial<Omit<InsertOf<TTable>, PrimaryKeyOf<TTable>>>;
149
+ /**
150
+ * Typed filter shape for list operations.
151
+ */
152
+ export type FiltersOf<TTable extends AnyStoreTable> = Partial<EntityOf<TTable>>;
153
+ /**
154
+ * Common pagination controls for store list operations.
155
+ */
156
+ export interface StoreListOptions {
157
+ readonly limit?: number;
158
+ readonly offset?: number;
159
+ }
160
+ /**
161
+ * Shared identifier type for read/write accessors.
162
+ */
163
+ export type StoreIdentifierOf<TTable extends AnyStoreTable> = EntityOf<TTable>[Extract<PrimaryKeyOf<TTable>, keyof EntityOf<TTable>>];
164
+ /**
165
+ * Access mode for a bound store connection or provision.
166
+ */
167
+ export type StoreAccessMode = 'readonly' | 'readwrite';
168
+ /**
169
+ * Read-only table operations that every bound store must expose.
170
+ */
171
+ export interface ReadOnlyStoreTableAccessor<TTable extends AnyStoreTable> {
172
+ /** Retrieve a single entity by primary key. Returns `null` when not found. */
173
+ get(id: StoreIdentifierOf<TTable>): Promise<EntityOf<TTable> | null>;
174
+ /**
175
+ * List entities, optionally filtered. Returns all rows when no filters are
176
+ * provided. Returns an empty array when no rows match.
177
+ */
178
+ list(filters?: FiltersOf<TTable>, options?: StoreListOptions): Promise<readonly EntityOf<TTable>[]>;
179
+ }
180
+ /**
181
+ * Writable table operations layered on top of the read contract.
182
+ */
183
+ export interface StoreTableAccessor<TTable extends AnyStoreTable> extends ReadOnlyStoreTableAccessor<TTable> {
184
+ /**
185
+ * Insert a new entity.
186
+ *
187
+ * @throws {AlreadyExistsError} On primary key or unique constraint violation.
188
+ *
189
+ * @remarks
190
+ * This is an intentional throw-based boundary: store connectors throw typed
191
+ * errors (`AlreadyExistsError`) rather than returning `Result`. Trail
192
+ * implementations that call store accessors should catch and convert to
193
+ * `Result.err()` at their level. A future `safeInsert` returning `Result`
194
+ * is planned but deferred to avoid cascading changes across all connectors.
195
+ */
196
+ insert(input: InsertOf<TTable>): Promise<EntityOf<TTable>>;
197
+ /**
198
+ * Remove an entity by primary key. Returns `{ deleted: true }` when the
199
+ * row was found and removed, `{ deleted: false }` when no matching row
200
+ * existed (not an error).
201
+ */
202
+ remove(id: StoreIdentifierOf<TTable>): Promise<{
203
+ readonly deleted: boolean;
204
+ }>;
205
+ /**
206
+ * Patch an entity by primary key with partial fields. Returns the updated
207
+ * entity, or `null` when no row with that ID exists.
208
+ */
209
+ update(id: StoreIdentifierOf<TTable>, input: UpdateOf<TTable>): Promise<EntityOf<TTable> | null>;
210
+ }
211
+ /**
212
+ * Connection shape exposed by a read-only bound store.
213
+ */
214
+ export type ReadOnlyStoreConnection<TStore extends AnyStoreDefinition> = {
215
+ readonly [TName in keyof TStore['tables']]: ReadOnlyStoreTableAccessor<TStore['tables'][TName]>;
216
+ };
217
+ /**
218
+ * Connection shape exposed by a writable bound store.
219
+ */
220
+ export type StoreConnection<TStore extends AnyStoreDefinition> = {
221
+ readonly [TName in keyof TStore['tables']]: StoreTableAccessor<TStore['tables'][TName]>;
222
+ };
223
+ export {};
224
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@ontrails/store",
3
+ "version": "1.0.0-beta.14",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.ts",
7
+ "./drizzle": "./src/drizzle/index.ts",
8
+ "./package.json": "./package.json"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -b",
12
+ "test": "bun test",
13
+ "typecheck": "tsc --noEmit",
14
+ "lint": "oxlint ./src",
15
+ "clean": "rm -rf dist *.tsbuildinfo"
16
+ },
17
+ "dependencies": {
18
+ "@ontrails/core": "^1.0.0-beta.13"
19
+ },
20
+ "peerDependencies": {
21
+ "drizzle-orm": "^0.45.2",
22
+ "zod": "^4.3.5"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "drizzle-orm": {
26
+ "optional": true
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,333 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { ValidationError } from '@ontrails/core';
3
+ import { z } from 'zod';
4
+
5
+ import type {
6
+ EntityOf,
7
+ FixtureInputOf,
8
+ FixtureOf,
9
+ InsertOf,
10
+ UpdateOf,
11
+ } from '../index.js';
12
+ import {
13
+ entitySchemaOf,
14
+ fixtureSchemaOf,
15
+ insertSchemaOf,
16
+ store,
17
+ updateSchemaOf,
18
+ } from '../index.js';
19
+
20
+ const userSchema = z.object({
21
+ email: z.string().email(),
22
+ id: z.string(),
23
+ });
24
+
25
+ const gistSchema = z.object({
26
+ createdAt: z.string(),
27
+ description: z.string().nullable().default(null),
28
+ id: z.string(),
29
+ isPublic: z.boolean().default(true),
30
+ ownerId: z.string(),
31
+ tags: z.array(z.string()).default([]),
32
+ updatedAt: z.string(),
33
+ });
34
+
35
+ const createStoreDefinition = () =>
36
+ store({
37
+ gists: {
38
+ generated: ['id', 'createdAt', 'updatedAt'],
39
+ indexes: ['ownerId'],
40
+ primaryKey: 'id',
41
+ references: { ownerId: 'users' },
42
+ schema: gistSchema,
43
+ search: { fts: true },
44
+ },
45
+ users: {
46
+ generated: ['id'],
47
+ primaryKey: 'id',
48
+ schema: userSchema,
49
+ },
50
+ });
51
+
52
+ const expectNormalizedGistTable = (
53
+ table: ReturnType<typeof createStoreDefinition>['tables']['gists']
54
+ ) => {
55
+ expect(table.name).toBe('gists');
56
+ expect(table.primaryKey).toBe('id');
57
+ expect(table.generated).toEqual(['id', 'createdAt', 'updatedAt']);
58
+ expect(table.indexes).toEqual(['ownerId']);
59
+ expect(table.references).toEqual({ ownerId: 'users' });
60
+ expect(table.search).toEqual({ fts: true });
61
+ };
62
+
63
+ const expectDerivedSchemas = (
64
+ table: ReturnType<typeof createStoreDefinition>['tables']['gists']
65
+ ) => {
66
+ expect(entitySchemaOf(table)).toBe(table.schema);
67
+ expect(fixtureSchemaOf(table)).toBe(table.fixtureSchema);
68
+ expect(insertSchemaOf(table)).toBe(table.insertSchema);
69
+ expect(updateSchemaOf(table)).toBe(table.updateSchema);
70
+
71
+ expect(
72
+ table.insertSchema.parse({
73
+ ownerId: 'user-1',
74
+ })
75
+ ).toEqual({
76
+ description: null,
77
+ isPublic: true,
78
+ ownerId: 'user-1',
79
+ tags: [],
80
+ });
81
+
82
+ expect(
83
+ table.updateSchema.parse({
84
+ description: 'Updated',
85
+ })
86
+ ).toEqual({
87
+ description: 'Updated',
88
+ });
89
+ };
90
+
91
+ const createTypeTestStore = () =>
92
+ store({
93
+ gists: {
94
+ generated: ['id', 'createdAt', 'updatedAt'],
95
+ primaryKey: 'id',
96
+ schema: gistSchema,
97
+ },
98
+ });
99
+
100
+ const createGistEntity = <
101
+ TTable extends Parameters<typeof entitySchemaOf>[0],
102
+ >() =>
103
+ ({
104
+ createdAt: '2026-04-03T12:00:00.000Z',
105
+ description: null,
106
+ id: 'gist-1',
107
+ isPublic: true,
108
+ ownerId: 'user-1',
109
+ tags: ['core'],
110
+ updatedAt: '2026-04-03T12:00:00.000Z',
111
+ }) as EntityOf<TTable>;
112
+
113
+ const requireFixture = <T>(fixture: T | undefined): T => {
114
+ if (fixture === undefined) {
115
+ throw new Error('Expected fixture to be present');
116
+ }
117
+
118
+ return fixture;
119
+ };
120
+
121
+ describe('@ontrails/store', () => {
122
+ test('normalizes tables and derives insert/update schemas', () => {
123
+ const db = createStoreDefinition();
124
+
125
+ expect(db.kind).toBe('store');
126
+ expect(db.tableNames).toEqual(['gists', 'users']);
127
+
128
+ const table = db.tables.gists;
129
+ expectNormalizedGistTable(table);
130
+ expect(db.get('users')).toBe(db.tables.users);
131
+ expectDerivedSchemas(table);
132
+ });
133
+
134
+ test('normalizes fixtures through the derived fixture schema', () => {
135
+ const db = store({
136
+ gists: {
137
+ fixtures: [
138
+ {
139
+ id: 'gist-seed',
140
+ ownerId: 'user-1',
141
+ },
142
+ ],
143
+ generated: ['id', 'createdAt', 'updatedAt'],
144
+ primaryKey: 'id',
145
+ schema: gistSchema,
146
+ },
147
+ });
148
+
149
+ type GistTable = typeof db.tables.gists;
150
+
151
+ const fixtureInput: FixtureInputOf<GistTable> = {
152
+ id: 'gist-other',
153
+ ownerId: 'user-2',
154
+ };
155
+ const fixture: FixtureOf<GistTable> = requireFixture(
156
+ db.tables.gists.fixtures[0]
157
+ );
158
+
159
+ expect(fixtureInput.ownerId).toBe('user-2');
160
+ expect(fixture).toEqual({
161
+ description: null,
162
+ id: 'gist-seed',
163
+ isPublic: true,
164
+ ownerId: 'user-1',
165
+ tags: [],
166
+ });
167
+ expect(Object.isFrozen(fixture)).toBe(true);
168
+ expect(Object.isFrozen(db.tables.gists.fixtures)).toBe(true);
169
+ expect(db.tables.gists.fixtureSchema.parse({ ownerId: 'user-3' })).toEqual({
170
+ description: null,
171
+ isPublic: true,
172
+ ownerId: 'user-3',
173
+ tags: [],
174
+ });
175
+ });
176
+
177
+ test('rejects duplicate fixture primary keys when they are explicitly provided', () => {
178
+ expect(() =>
179
+ store({
180
+ gists: {
181
+ fixtures: [
182
+ {
183
+ id: 'gist-seed',
184
+ ownerId: 'user-1',
185
+ },
186
+ {
187
+ id: 'gist-seed',
188
+ ownerId: 'user-2',
189
+ },
190
+ ],
191
+ generated: ['id', 'createdAt', 'updatedAt'],
192
+ primaryKey: 'id',
193
+ schema: gistSchema,
194
+ },
195
+ })
196
+ ).toThrow(
197
+ new ValidationError(
198
+ 'Store table "gists" fixture 2 duplicates primary key "gist-seed"'
199
+ )
200
+ );
201
+ });
202
+
203
+ test('rejects non-object schemas and unknown metadata fields', () => {
204
+ expect(() =>
205
+ store({
206
+ broken: {
207
+ primaryKey: 'id' as never,
208
+ schema: z.string() as never,
209
+ },
210
+ })
211
+ ).toThrow(
212
+ new ValidationError('Store table "broken" must use a Zod object schema')
213
+ );
214
+
215
+ expect(() =>
216
+ store({
217
+ gists: {
218
+ primaryKey: 'slug' as never,
219
+ schema: gistSchema,
220
+ },
221
+ })
222
+ ).toThrow(
223
+ new ValidationError(
224
+ 'Store table "gists" declares primaryKey "slug" that is not present on the schema'
225
+ )
226
+ );
227
+
228
+ expect(() =>
229
+ store({
230
+ gists: {
231
+ generated: ['missing'] as const as never,
232
+ primaryKey: 'id',
233
+ schema: gistSchema,
234
+ },
235
+ })
236
+ ).toThrow(
237
+ new ValidationError(
238
+ 'Store table "gists" declares generated field "missing" that is not present on the schema'
239
+ )
240
+ );
241
+
242
+ expect(() =>
243
+ store({
244
+ gists: {
245
+ indexes: ['missing'] as const as never,
246
+ primaryKey: 'id',
247
+ schema: gistSchema,
248
+ },
249
+ })
250
+ ).toThrow(
251
+ new ValidationError(
252
+ 'Store table "gists" declares index field "missing" that is not present on the schema'
253
+ )
254
+ );
255
+
256
+ expect(() =>
257
+ store({
258
+ gists: {
259
+ fixtures: [{ id: 'gist-seed' } as never],
260
+ generated: ['id', 'createdAt', 'updatedAt'],
261
+ primaryKey: 'id',
262
+ schema: gistSchema,
263
+ },
264
+ })
265
+ ).toThrow(
266
+ new ValidationError(
267
+ 'Store table "gists" fixture 1 is invalid: Invalid input: expected string, received undefined'
268
+ )
269
+ );
270
+ });
271
+
272
+ test('rejects bad references', () => {
273
+ expect(() =>
274
+ store({
275
+ gists: {
276
+ primaryKey: 'id',
277
+ references: { missing: 'users' } as never,
278
+ schema: gistSchema,
279
+ },
280
+ users: {
281
+ primaryKey: 'id',
282
+ schema: userSchema,
283
+ },
284
+ })
285
+ ).toThrow(
286
+ new ValidationError(
287
+ 'Store table "gists" declares reference field "missing" that is not present on the schema'
288
+ )
289
+ );
290
+
291
+ expect(() =>
292
+ store({
293
+ gists: {
294
+ primaryKey: 'id',
295
+ references: { ownerId: 'accounts' },
296
+ schema: gistSchema,
297
+ },
298
+ users: {
299
+ primaryKey: 'id',
300
+ schema: userSchema,
301
+ },
302
+ })
303
+ ).toThrow(
304
+ new ValidationError(
305
+ 'Store table "gists" references unknown table "accounts"'
306
+ )
307
+ );
308
+ });
309
+
310
+ test('type-level helpers expose connector-facing contracts', () => {
311
+ const db = createTypeTestStore();
312
+
313
+ type GistTable = typeof db.tables.gists;
314
+
315
+ const entity = createGistEntity<GistTable>();
316
+
317
+ const insert: InsertOf<GistTable> = {
318
+ description: null,
319
+ isPublic: true,
320
+ ownerId: 'user-1',
321
+ tags: ['core'],
322
+ };
323
+
324
+ const update: UpdateOf<GistTable> = {
325
+ description: 'Updated',
326
+ };
327
+
328
+ expect(insert.ownerId).toBe('user-1');
329
+ expect(update.description).toBe('Updated');
330
+ expect(entity.id).toBe('gist-1');
331
+ expect(db.tables.gists.name).toBe('gists');
332
+ });
333
+ });