@ontrails/store 1.0.0-beta.15 → 1.0.0-beta.16
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/CHANGELOG.md +36 -0
- package/README.md +31 -15
- package/package.json +11 -3
- package/src/{internal/signal-identity.ts → adapter-support.ts} +15 -14
- package/src/crud-doctrine.ts +43 -0
- package/src/index.ts +6 -1
- package/src/jsonfile/runtime.ts +3 -5
- package/src/jsonfile/types.ts +2 -2
- package/src/store.ts +5 -6
- package/src/testing.ts +2 -2
- package/src/trails/crud.ts +4 -3
- package/src/trails/index.ts +5 -0
- package/src/trails/reconcile.ts +2 -2
- package/src/trails/utils.ts +1 -1
- package/src/types.ts +27 -26
- package/.agents/notes/2026-04-04/handoff-202604032309-9e85a104.md +0 -38
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/internal/signal-identity.d.ts +0 -17
- package/dist/internal/signal-identity.d.ts.map +0 -1
- package/dist/internal/signal-identity.js +0 -81
- package/dist/internal/signal-identity.js.map +0 -1
- package/dist/jsonfile/index.d.ts +0 -3
- package/dist/jsonfile/index.d.ts.map +0 -1
- package/dist/jsonfile/index.js +0 -2
- package/dist/jsonfile/index.js.map +0 -1
- package/dist/jsonfile/runtime.d.ts +0 -19
- package/dist/jsonfile/runtime.d.ts.map +0 -1
- package/dist/jsonfile/runtime.js +0 -403
- package/dist/jsonfile/runtime.js.map +0 -1
- package/dist/jsonfile/types.d.ts +0 -40
- package/dist/jsonfile/types.d.ts.map +0 -1
- package/dist/jsonfile/types.js +0 -2
- package/dist/jsonfile/types.js.map +0 -1
- package/dist/store.d.ts +0 -16
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -250
- package/dist/store.js.map +0 -1
- package/dist/testing.d.ts +0 -28
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -90
- package/dist/testing.js.map +0 -1
- package/dist/trails/crud.d.ts +0 -38
- package/dist/trails/crud.d.ts.map +0 -1
- package/dist/trails/crud.js +0 -112
- package/dist/trails/crud.js.map +0 -1
- package/dist/trails/index.d.ts +0 -7
- package/dist/trails/index.d.ts.map +0 -1
- package/dist/trails/index.js +0 -4
- package/dist/trails/index.js.map +0 -1
- package/dist/trails/reconcile.d.ts +0 -36
- package/dist/trails/reconcile.d.ts.map +0 -1
- package/dist/trails/reconcile.js +0 -142
- package/dist/trails/reconcile.js.map +0 -1
- package/dist/trails/sync.d.ts +0 -25
- package/dist/trails/sync.d.ts.map +0 -1
- package/dist/trails/sync.js +0 -96
- package/dist/trails/sync.js.map +0 -1
- package/dist/trails/utils.d.ts +0 -51
- package/dist/trails/utils.d.ts.map +0 -1
- package/dist/trails/utils.js +0 -65
- package/dist/trails/utils.js.map +0 -1
- package/dist/types.d.ts +0 -394
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/src/__tests__/crud.test.ts +0 -346
- package/src/__tests__/jsonfile.test.ts +0 -502
- package/src/__tests__/store.test.ts +0 -539
- package/src/__tests__/sync-reconcile.test.ts +0 -646
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { ValidationError } from '@ontrails/core';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
AnyStoreTable,
|
|
7
|
-
EntityOf,
|
|
8
|
-
FixtureInputOf,
|
|
9
|
-
FixtureOf,
|
|
10
|
-
InsertOf,
|
|
11
|
-
UpdateOf,
|
|
12
|
-
} from '../index.js';
|
|
13
|
-
import { store } from '../index.js';
|
|
14
|
-
import { bindStoreDefinition } from '../internal/signal-identity.js';
|
|
15
|
-
|
|
16
|
-
const userSchema = z.object({
|
|
17
|
-
email: z.string().email(),
|
|
18
|
-
id: z.string(),
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const gistSchema = z.object({
|
|
22
|
-
createdAt: z.string(),
|
|
23
|
-
description: z.string().nullable().default(null),
|
|
24
|
-
id: z.string(),
|
|
25
|
-
isPublic: z.boolean().default(true),
|
|
26
|
-
ownerId: z.string(),
|
|
27
|
-
tags: z.array(z.string()).default([]),
|
|
28
|
-
updatedAt: z.string(),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const createStoreDefinition = () =>
|
|
32
|
-
store({
|
|
33
|
-
gists: {
|
|
34
|
-
generated: ['id', 'createdAt', 'updatedAt'],
|
|
35
|
-
identity: 'id',
|
|
36
|
-
indexed: ['ownerId'],
|
|
37
|
-
references: { ownerId: 'users' },
|
|
38
|
-
schema: gistSchema,
|
|
39
|
-
search: { fts: true },
|
|
40
|
-
},
|
|
41
|
-
users: {
|
|
42
|
-
generated: ['id'],
|
|
43
|
-
identity: 'id',
|
|
44
|
-
schema: userSchema,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const expectGistSignals = (
|
|
49
|
-
table: ReturnType<typeof createStoreDefinition>['tables']['gists']
|
|
50
|
-
) => {
|
|
51
|
-
expect(table.signals.created.id).toBe('gists.created');
|
|
52
|
-
expect(table.signals.updated.id).toBe('gists.updated');
|
|
53
|
-
expect(table.signals.removed.id).toBe('gists.removed');
|
|
54
|
-
expect(
|
|
55
|
-
table.signals.created.payload.parse({
|
|
56
|
-
createdAt: '2026-04-03T12:00:00.000Z',
|
|
57
|
-
id: 'gist-1',
|
|
58
|
-
ownerId: 'user-1',
|
|
59
|
-
updatedAt: '2026-04-03T12:00:00.000Z',
|
|
60
|
-
})
|
|
61
|
-
).toEqual({
|
|
62
|
-
createdAt: '2026-04-03T12:00:00.000Z',
|
|
63
|
-
description: null,
|
|
64
|
-
id: 'gist-1',
|
|
65
|
-
isPublic: true,
|
|
66
|
-
ownerId: 'user-1',
|
|
67
|
-
tags: [],
|
|
68
|
-
updatedAt: '2026-04-03T12:00:00.000Z',
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const expectNormalizedGistTable = (
|
|
73
|
-
table: ReturnType<typeof createStoreDefinition>['tables']['gists']
|
|
74
|
-
) => {
|
|
75
|
-
expect(table.name).toBe('gists');
|
|
76
|
-
expect(table.identity).toBe('id');
|
|
77
|
-
expect(table.primaryKey).toBe('id');
|
|
78
|
-
expect(table.generated).toEqual(['id', 'createdAt', 'updatedAt']);
|
|
79
|
-
expect(table.indexed).toEqual(['ownerId']);
|
|
80
|
-
expect(table.indexes).toEqual(['ownerId']);
|
|
81
|
-
expect(table.references).toEqual({ ownerId: 'users' });
|
|
82
|
-
expect(table.search).toEqual({ fts: true });
|
|
83
|
-
expectGistSignals(table);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const expectDerivedSchemas = (
|
|
87
|
-
table: ReturnType<typeof createStoreDefinition>['tables']['gists']
|
|
88
|
-
) => {
|
|
89
|
-
expect(table.schema).toBeDefined();
|
|
90
|
-
expect(table.fixtureSchema).toBeDefined();
|
|
91
|
-
expect(table.insertSchema).toBeDefined();
|
|
92
|
-
expect(table.updateSchema).toBeDefined();
|
|
93
|
-
|
|
94
|
-
expect(
|
|
95
|
-
table.insertSchema.parse({
|
|
96
|
-
ownerId: 'user-1',
|
|
97
|
-
})
|
|
98
|
-
).toEqual({
|
|
99
|
-
description: null,
|
|
100
|
-
isPublic: true,
|
|
101
|
-
ownerId: 'user-1',
|
|
102
|
-
tags: [],
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
expect(
|
|
106
|
-
table.updateSchema.parse({
|
|
107
|
-
description: 'Updated',
|
|
108
|
-
})
|
|
109
|
-
).toEqual({
|
|
110
|
-
description: 'Updated',
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const createTypeTestStore = () =>
|
|
115
|
-
store({
|
|
116
|
-
gists: {
|
|
117
|
-
generated: ['id', 'createdAt', 'updatedAt'],
|
|
118
|
-
identity: 'id',
|
|
119
|
-
schema: gistSchema,
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const createVersionedStoreDefinition = () =>
|
|
124
|
-
store({
|
|
125
|
-
gists: {
|
|
126
|
-
generated: ['id', 'createdAt', 'updatedAt'],
|
|
127
|
-
identity: 'id',
|
|
128
|
-
schema: gistSchema,
|
|
129
|
-
versioned: true,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
type VersionedGistTable = ReturnType<
|
|
134
|
-
typeof createVersionedStoreDefinition
|
|
135
|
-
>['tables']['gists'];
|
|
136
|
-
|
|
137
|
-
const createGistEntity = <TTable extends AnyStoreTable>() =>
|
|
138
|
-
({
|
|
139
|
-
createdAt: '2026-04-03T12:00:00.000Z',
|
|
140
|
-
description: null,
|
|
141
|
-
id: 'gist-1',
|
|
142
|
-
isPublic: true,
|
|
143
|
-
ownerId: 'user-1',
|
|
144
|
-
tags: ['core'],
|
|
145
|
-
updatedAt: '2026-04-03T12:00:00.000Z',
|
|
146
|
-
}) as EntityOf<TTable>;
|
|
147
|
-
|
|
148
|
-
const requireFixture = <T>(fixture: T | undefined): T => {
|
|
149
|
-
if (fixture === undefined) {
|
|
150
|
-
throw new Error('Expected fixture to be present');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return fixture;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const createVersionedGistTypeSamples = (): {
|
|
157
|
-
readonly entity: EntityOf<VersionedGistTable>;
|
|
158
|
-
readonly fixtureInput: FixtureInputOf<VersionedGistTable>;
|
|
159
|
-
readonly insertInput: InsertOf<VersionedGistTable>;
|
|
160
|
-
} => ({
|
|
161
|
-
entity: {
|
|
162
|
-
createdAt: '2026-04-03T12:00:00.000Z',
|
|
163
|
-
description: null,
|
|
164
|
-
id: 'gist-1',
|
|
165
|
-
isPublic: true,
|
|
166
|
-
ownerId: 'user-1',
|
|
167
|
-
tags: ['core'],
|
|
168
|
-
updatedAt: '2026-04-03T12:00:00.000Z',
|
|
169
|
-
version: 1,
|
|
170
|
-
},
|
|
171
|
-
fixtureInput: {
|
|
172
|
-
ownerId: 'user-2',
|
|
173
|
-
version: 2,
|
|
174
|
-
},
|
|
175
|
-
insertInput: {
|
|
176
|
-
ownerId: 'user-3',
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const expectVersionedGistTypeSamples = ({
|
|
181
|
-
entity,
|
|
182
|
-
fixtureInput,
|
|
183
|
-
insertInput,
|
|
184
|
-
}: ReturnType<typeof createVersionedGistTypeSamples>): void => {
|
|
185
|
-
expect(entity.version).toBe(1);
|
|
186
|
-
expect(fixtureInput.version).toBe(2);
|
|
187
|
-
expect(insertInput.ownerId).toBe('user-3');
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const expectVersionedFixtureDefaults = (
|
|
191
|
-
db: ReturnType<typeof createVersionedStoreDefinition>
|
|
192
|
-
): void => {
|
|
193
|
-
expect(db.tables.gists.fixtureSchema.parse({ ownerId: 'user-4' })).toEqual({
|
|
194
|
-
description: null,
|
|
195
|
-
isPublic: true,
|
|
196
|
-
ownerId: 'user-4',
|
|
197
|
-
tags: [],
|
|
198
|
-
});
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
describe('@ontrails/store', () => {
|
|
202
|
-
test('normalizes tables and derives insert/update schemas', () => {
|
|
203
|
-
const db = createStoreDefinition();
|
|
204
|
-
|
|
205
|
-
expect(db.kind).toBe('tabular');
|
|
206
|
-
expect(db.signals.map((candidate) => candidate.id)).toEqual([
|
|
207
|
-
'gists.created',
|
|
208
|
-
'gists.updated',
|
|
209
|
-
'gists.removed',
|
|
210
|
-
'users.created',
|
|
211
|
-
'users.updated',
|
|
212
|
-
'users.removed',
|
|
213
|
-
]);
|
|
214
|
-
expect(db.tableNames).toEqual(['gists', 'users']);
|
|
215
|
-
expect(db.type).toBe('store');
|
|
216
|
-
|
|
217
|
-
const table = db.tables.gists;
|
|
218
|
-
expectNormalizedGistTable(table);
|
|
219
|
-
expect(db.get('users')).toBe(db.tables.users);
|
|
220
|
-
expectDerivedSchemas(table);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test('accepts an explicit backend-agnostic kind', () => {
|
|
224
|
-
const db = store(
|
|
225
|
-
{
|
|
226
|
-
gists: {
|
|
227
|
-
identity: 'id',
|
|
228
|
-
schema: gistSchema,
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
{ kind: 'document' }
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
expect(db.kind).toBe('document');
|
|
235
|
-
expect(db.tables.gists.identity).toBe('id');
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test('normalizes fixtures through the derived fixture schema', () => {
|
|
239
|
-
const db = store({
|
|
240
|
-
gists: {
|
|
241
|
-
fixtures: [
|
|
242
|
-
{
|
|
243
|
-
id: 'gist-seed',
|
|
244
|
-
ownerId: 'user-1',
|
|
245
|
-
},
|
|
246
|
-
],
|
|
247
|
-
generated: ['id', 'createdAt', 'updatedAt'],
|
|
248
|
-
identity: 'id',
|
|
249
|
-
schema: gistSchema,
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
type GistTable = typeof db.tables.gists;
|
|
254
|
-
|
|
255
|
-
const fixtureInput: FixtureInputOf<GistTable> = {
|
|
256
|
-
id: 'gist-other',
|
|
257
|
-
ownerId: 'user-2',
|
|
258
|
-
};
|
|
259
|
-
const fixture: FixtureOf<GistTable> = requireFixture(
|
|
260
|
-
db.tables.gists.fixtures[0]
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
expect(fixtureInput.ownerId).toBe('user-2');
|
|
264
|
-
expect(fixture).toEqual({
|
|
265
|
-
description: null,
|
|
266
|
-
id: 'gist-seed',
|
|
267
|
-
isPublic: true,
|
|
268
|
-
ownerId: 'user-1',
|
|
269
|
-
tags: [],
|
|
270
|
-
});
|
|
271
|
-
expect(Object.isFrozen(fixture)).toBe(true);
|
|
272
|
-
expect(Object.isFrozen(db.tables.gists.fixtures)).toBe(true);
|
|
273
|
-
expect(db.tables.gists.fixtureSchema.parse({ ownerId: 'user-3' })).toEqual({
|
|
274
|
-
description: null,
|
|
275
|
-
isPublic: true,
|
|
276
|
-
ownerId: 'user-3',
|
|
277
|
-
tags: [],
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test('normalizes versioned tables with a framework-managed version field', () => {
|
|
282
|
-
const db = createVersionedStoreDefinition();
|
|
283
|
-
const samples = createVersionedGistTypeSamples();
|
|
284
|
-
|
|
285
|
-
expect(db.tables.gists.versioned).toBe(true);
|
|
286
|
-
expect(db.tables.gists.generated).toEqual([
|
|
287
|
-
'id',
|
|
288
|
-
'createdAt',
|
|
289
|
-
'updatedAt',
|
|
290
|
-
'version',
|
|
291
|
-
]);
|
|
292
|
-
expectVersionedGistTypeSamples(samples);
|
|
293
|
-
expect(db.tables.gists.schema.parse(samples.entity)).toEqual(
|
|
294
|
-
samples.entity
|
|
295
|
-
);
|
|
296
|
-
expect(
|
|
297
|
-
db.tables.gists.signals.updated.payload.parse({
|
|
298
|
-
createdAt: '2026-04-03T12:00:00.000Z',
|
|
299
|
-
id: 'gist-1',
|
|
300
|
-
ownerId: 'user-1',
|
|
301
|
-
updatedAt: '2026-04-03T12:00:00.000Z',
|
|
302
|
-
version: 2,
|
|
303
|
-
})
|
|
304
|
-
).toEqual({
|
|
305
|
-
createdAt: '2026-04-03T12:00:00.000Z',
|
|
306
|
-
description: null,
|
|
307
|
-
id: 'gist-1',
|
|
308
|
-
isPublic: true,
|
|
309
|
-
ownerId: 'user-1',
|
|
310
|
-
tags: [],
|
|
311
|
-
updatedAt: '2026-04-03T12:00:00.000Z',
|
|
312
|
-
version: 2,
|
|
313
|
-
});
|
|
314
|
-
expectVersionedFixtureDefaults(db);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test('rejects duplicate fixture primary keys when they are explicitly provided', () => {
|
|
318
|
-
expect(() =>
|
|
319
|
-
store({
|
|
320
|
-
gists: {
|
|
321
|
-
fixtures: [
|
|
322
|
-
{
|
|
323
|
-
id: 'gist-seed',
|
|
324
|
-
ownerId: 'user-1',
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
id: 'gist-seed',
|
|
328
|
-
ownerId: 'user-2',
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
generated: ['id', 'createdAt', 'updatedAt'],
|
|
332
|
-
identity: 'id',
|
|
333
|
-
schema: gistSchema,
|
|
334
|
-
},
|
|
335
|
-
})
|
|
336
|
-
).toThrow(
|
|
337
|
-
new ValidationError(
|
|
338
|
-
'Store table "gists" fixture 2 duplicates primary key "gist-seed"'
|
|
339
|
-
)
|
|
340
|
-
);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
test('rejects non-object schemas and unknown metadata fields', () => {
|
|
344
|
-
expect(() =>
|
|
345
|
-
store({
|
|
346
|
-
broken: {
|
|
347
|
-
identity: 'id' as never,
|
|
348
|
-
schema: z.string() as never,
|
|
349
|
-
},
|
|
350
|
-
})
|
|
351
|
-
).toThrow(
|
|
352
|
-
new ValidationError('Store table "broken" must use a Zod object schema')
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
expect(() =>
|
|
356
|
-
store({
|
|
357
|
-
gists: {
|
|
358
|
-
identity: 'slug' as never,
|
|
359
|
-
schema: gistSchema,
|
|
360
|
-
},
|
|
361
|
-
})
|
|
362
|
-
).toThrow(
|
|
363
|
-
new ValidationError(
|
|
364
|
-
'Store table "gists" declares identity "slug" that is not present on the schema'
|
|
365
|
-
)
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
expect(() =>
|
|
369
|
-
store({
|
|
370
|
-
gists: {
|
|
371
|
-
generated: ['missing'] as const as never,
|
|
372
|
-
identity: 'id',
|
|
373
|
-
schema: gistSchema,
|
|
374
|
-
},
|
|
375
|
-
})
|
|
376
|
-
).toThrow(
|
|
377
|
-
new ValidationError(
|
|
378
|
-
'Store table "gists" declares generated field "missing" that is not present on the schema'
|
|
379
|
-
)
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
expect(() =>
|
|
383
|
-
store({
|
|
384
|
-
gists: {
|
|
385
|
-
identity: 'id',
|
|
386
|
-
schema: gistSchema.extend({ version: z.number().int() }),
|
|
387
|
-
versioned: true,
|
|
388
|
-
},
|
|
389
|
-
})
|
|
390
|
-
).toThrow(
|
|
391
|
-
new ValidationError(
|
|
392
|
-
'Store table "gists" cannot declare a "version" field when versioned storage is enabled because the framework manages that field.'
|
|
393
|
-
)
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
expect(() =>
|
|
397
|
-
store({
|
|
398
|
-
gists: {
|
|
399
|
-
identity: 'id',
|
|
400
|
-
indexed: ['missing'] as const as never,
|
|
401
|
-
schema: gistSchema,
|
|
402
|
-
},
|
|
403
|
-
})
|
|
404
|
-
).toThrow(
|
|
405
|
-
new ValidationError(
|
|
406
|
-
'Store table "gists" declares indexed field "missing" that is not present on the schema'
|
|
407
|
-
)
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
expect(() =>
|
|
411
|
-
store({
|
|
412
|
-
gists: {
|
|
413
|
-
fixtures: [{ id: 'gist-seed' } as never],
|
|
414
|
-
generated: ['id', 'createdAt', 'updatedAt'],
|
|
415
|
-
identity: 'id',
|
|
416
|
-
schema: gistSchema,
|
|
417
|
-
},
|
|
418
|
-
})
|
|
419
|
-
).toThrow(
|
|
420
|
-
new ValidationError(
|
|
421
|
-
'Store table "gists" fixture 1 is invalid: Invalid input: expected string, received undefined'
|
|
422
|
-
)
|
|
423
|
-
);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
test('rejects bad references', () => {
|
|
427
|
-
expect(() =>
|
|
428
|
-
store({
|
|
429
|
-
gists: {
|
|
430
|
-
identity: 'id',
|
|
431
|
-
references: { missing: 'users' } as never,
|
|
432
|
-
schema: gistSchema,
|
|
433
|
-
},
|
|
434
|
-
users: {
|
|
435
|
-
identity: 'id',
|
|
436
|
-
schema: userSchema,
|
|
437
|
-
},
|
|
438
|
-
})
|
|
439
|
-
).toThrow(
|
|
440
|
-
new ValidationError(
|
|
441
|
-
'Store table "gists" declares reference field "missing" that is not present on the schema'
|
|
442
|
-
)
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
expect(() =>
|
|
446
|
-
store({
|
|
447
|
-
gists: {
|
|
448
|
-
identity: 'id',
|
|
449
|
-
references: { ownerId: 'accounts' },
|
|
450
|
-
schema: gistSchema,
|
|
451
|
-
},
|
|
452
|
-
users: {
|
|
453
|
-
identity: 'id',
|
|
454
|
-
schema: userSchema,
|
|
455
|
-
},
|
|
456
|
-
})
|
|
457
|
-
).toThrow(
|
|
458
|
-
new ValidationError(
|
|
459
|
-
'Store table "gists" references unknown table "accounts"'
|
|
460
|
-
)
|
|
461
|
-
);
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
test('keeps tabular aliases working for existing stores', () => {
|
|
465
|
-
const db = store({
|
|
466
|
-
gists: {
|
|
467
|
-
generated: ['id'],
|
|
468
|
-
indexes: ['ownerId'],
|
|
469
|
-
primaryKey: 'id',
|
|
470
|
-
schema: gistSchema,
|
|
471
|
-
},
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
expect(db.kind).toBe('tabular');
|
|
475
|
-
expect(db.tables.gists.identity).toBe('id');
|
|
476
|
-
expect(db.tables.gists.primaryKey).toBe('id');
|
|
477
|
-
expect(db.tables.gists.indexed).toEqual(['ownerId']);
|
|
478
|
-
expect(db.tables.gists.indexes).toEqual(['ownerId']);
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
test('type-level helpers expose connector-facing contracts', () => {
|
|
482
|
-
const db = createTypeTestStore();
|
|
483
|
-
|
|
484
|
-
type GistTable = typeof db.tables.gists;
|
|
485
|
-
|
|
486
|
-
const entity = createGistEntity<GistTable>();
|
|
487
|
-
|
|
488
|
-
const insert: InsertOf<GistTable> = {
|
|
489
|
-
description: null,
|
|
490
|
-
isPublic: true,
|
|
491
|
-
ownerId: 'user-1',
|
|
492
|
-
tags: ['core'],
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
const update: UpdateOf<GistTable> = {
|
|
496
|
-
description: 'Updated',
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
expect(insert.ownerId).toBe('user-1');
|
|
500
|
-
expect(update.description).toBe('Updated');
|
|
501
|
-
expect(entity.id).toBe('gist-1');
|
|
502
|
-
expect(db.tables.gists.name).toBe('gists');
|
|
503
|
-
});
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
describe('bindStoreDefinition', () => {
|
|
507
|
-
const definition = store({
|
|
508
|
-
gists: {
|
|
509
|
-
generated: ['id'],
|
|
510
|
-
identity: 'id',
|
|
511
|
-
schema: gistSchema,
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
test('rejects scopes containing ":"', () => {
|
|
516
|
-
expect(() => bindStoreDefinition(definition, 'bad:scope')).toThrow(
|
|
517
|
-
new ValidationError(
|
|
518
|
-
'Store resource id "bad:scope" is invalid: must be a non-empty string with no ":" characters and no whitespace.'
|
|
519
|
-
)
|
|
520
|
-
);
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
test('rejects scopes containing whitespace', () => {
|
|
524
|
-
expect(() => bindStoreDefinition(definition, 'bad scope')).toThrow(
|
|
525
|
-
ValidationError
|
|
526
|
-
);
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
test('rejects empty scopes', () => {
|
|
530
|
-
expect(() => bindStoreDefinition(definition, '')).toThrow(ValidationError);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
test('accepts valid scopes and scopes store signal ids', () => {
|
|
534
|
-
const bound = bindStoreDefinition(definition, 'primary');
|
|
535
|
-
expect(bound.tables.gists.signals.created.id).toBe('primary:gists.created');
|
|
536
|
-
expect(bound.tables.gists.signals.updated.id).toBe('primary:gists.updated');
|
|
537
|
-
expect(bound.tables.gists.signals.removed.id).toBe('primary:gists.removed');
|
|
538
|
-
});
|
|
539
|
-
});
|