@pikku/kysely 0.13.24 → 0.13.28

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 (80) hide show
  1. package/CHANGELOG.md +271 -0
  2. package/dist/src/index.d.ts +4 -1
  3. package/dist/src/index.js +3 -1
  4. package/dist/src/kysely-analytics-service.d.ts +30 -0
  5. package/dist/src/kysely-analytics-service.js +80 -0
  6. package/dist/src/kysely-feature-flag-store.d.ts +37 -0
  7. package/dist/src/kysely-feature-flag-store.js +239 -0
  8. package/dist/src/kysely-scope-service.d.ts +1 -0
  9. package/dist/src/kysely-scope-service.js +29 -0
  10. package/dist/src/kysely-tables.d.ts +45 -0
  11. package/dist/src/schema/analytics.schema.d.ts +17 -0
  12. package/dist/src/schema/analytics.schema.js +48 -0
  13. package/dist/src/schema/flag.schema.d.ts +16 -0
  14. package/dist/src/schema/flag.schema.js +62 -0
  15. package/dist/src/schema/index.d.ts +2 -0
  16. package/dist/src/schema/index.js +6 -0
  17. package/package.json +11 -6
  18. package/run-tests.sh +9 -1
  19. package/bin/pikku-kysely-pure.ts +0 -59
  20. package/dist/src/kysely-lock-vault.d.ts +0 -34
  21. package/dist/src/kysely-lock-vault.js +0 -77
  22. package/dist/src/schema/data-lock.schema.d.ts +0 -15
  23. package/dist/src/schema/data-lock.schema.js +0 -27
  24. package/dist/tsconfig.tsbuildinfo +0 -1
  25. package/src/classification-crypto.test.ts +0 -143
  26. package/src/classification-crypto.ts +0 -163
  27. package/src/classification-plugin.pglite.test.ts +0 -226
  28. package/src/classification-plugin.test.ts +0 -265
  29. package/src/classification-plugin.ts +0 -249
  30. package/src/coercion-plugin.test.ts +0 -160
  31. package/src/coercion-plugin.ts +0 -187
  32. package/src/create-audited-kysely.test.ts +0 -560
  33. package/src/create-audited-kysely.ts +0 -254
  34. package/src/index.ts +0 -81
  35. package/src/kysely-agent-run-scores.ts +0 -47
  36. package/src/kysely-agent-run-service.ts +0 -244
  37. package/src/kysely-agent-run-state-service.ts +0 -200
  38. package/src/kysely-agent-storage-service.ts +0 -632
  39. package/src/kysely-audit-service.test.ts +0 -352
  40. package/src/kysely-audit-service.ts +0 -237
  41. package/src/kysely-channel-store.ts +0 -107
  42. package/src/kysely-credential-service.ts +0 -286
  43. package/src/kysely-deployment-service.ts +0 -163
  44. package/src/kysely-eventhub-store.ts +0 -53
  45. package/src/kysely-json.ts +0 -5
  46. package/src/kysely-scope-service.test.ts +0 -418
  47. package/src/kysely-scope-service.ts +0 -425
  48. package/src/kysely-secret-service.ts +0 -242
  49. package/src/kysely-services.test.ts +0 -405
  50. package/src/kysely-session-store.ts +0 -57
  51. package/src/kysely-tables.test.ts +0 -110
  52. package/src/kysely-tables.ts +0 -434
  53. package/src/kysely-virtual-user-run-store.test.ts +0 -369
  54. package/src/kysely-virtual-user-run-store.ts +0 -235
  55. package/src/kysely-virtual-user-schedule-store.test.ts +0 -187
  56. package/src/kysely-virtual-user-schedule-store.ts +0 -187
  57. package/src/kysely-webhook-service.test.ts +0 -153
  58. package/src/kysely-webhook-service.ts +0 -139
  59. package/src/kysely-workflow-mirror.test.ts +0 -259
  60. package/src/kysely-workflow-mirror.ts +0 -374
  61. package/src/kysely-workflow-run-service.ts +0 -305
  62. package/src/kysely-workflow-service.test.ts +0 -544
  63. package/src/kysely-workflow-service.ts +0 -820
  64. package/src/schema/agent.schema.ts +0 -162
  65. package/src/schema/audit.schema.ts +0 -57
  66. package/src/schema/channel.schema.ts +0 -38
  67. package/src/schema/credential.schema.ts +0 -58
  68. package/src/schema/deployment.schema.ts +0 -47
  69. package/src/schema/index.ts +0 -441
  70. package/src/schema/pikku-schema.types.ts +0 -169
  71. package/src/schema/schema.test.ts +0 -591
  72. package/src/schema/scope.schema.ts +0 -110
  73. package/src/schema/secret.schema.ts +0 -49
  74. package/src/schema/session.schema.ts +0 -20
  75. package/src/schema/virtual-user-schedule.schema.ts +0 -48
  76. package/src/schema/virtual-user.schema.ts +0 -106
  77. package/src/schema/webhook.schema.ts +0 -58
  78. package/src/schema/workflow.schema.ts +0 -150
  79. package/src/serialize-plugin.ts +0 -233
  80. package/tsconfig.json +0 -17
@@ -1,405 +0,0 @@
1
- import { describe, test, before, beforeEach, after } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { CamelCasePlugin, Kysely, SqliteDialect } from 'kysely'
4
- import { SerializePlugin } from './serialize-plugin.js'
5
- import Database from 'better-sqlite3'
6
- import { PostgresJSDialect } from 'kysely-postgres-js'
7
- import postgres from 'postgres'
8
- import { defineServiceTests } from '@pikku/core/testing'
9
-
10
- import type { KyselyPikkuDB } from './kysely-tables.js'
11
- import { KyselyChannelStore } from './kysely-channel-store.js'
12
- import { KyselyEventHubStore } from './kysely-eventhub-store.js'
13
- import { KyselyWorkflowService } from './kysely-workflow-service.js'
14
- import { KyselyWorkflowRunService } from './kysely-workflow-run-service.js'
15
- import { KyselyDeploymentService } from './kysely-deployment-service.js'
16
- import { KyselyAgentStorageService } from './kysely-agent-storage-service.js'
17
- import { KyselyAgentRunService } from './kysely-agent-run-service.js'
18
- import { KyselySecretService } from './kysely-secret-service.js'
19
- import { KyselyCredentialService } from './kysely-credential-service.js'
20
- import { KyselySessionStore } from './kysely-session-store.js'
21
- import {
22
- agentSchema,
23
- applyPikkuSchemas,
24
- channelSchema,
25
- credentialSchema,
26
- deploymentSchema,
27
- secretSchema,
28
- sessionSchema,
29
- workflowSchema,
30
- } from './schema/index.js'
31
-
32
- /**
33
- * Every schema the services under test boot against. They no longer create
34
- * their own tables, so the harness stands in for `pikku db migrate`.
35
- */
36
- const SERVICE_SCHEMAS = [
37
- channelSchema,
38
- workflowSchema,
39
- deploymentSchema,
40
- agentSchema,
41
- secretSchema,
42
- credentialSchema,
43
- sessionSchema,
44
- ]
45
-
46
- function createSqliteDb(): Kysely<KyselyPikkuDB> {
47
- return new Kysely<KyselyPikkuDB>({
48
- dialect: new SqliteDialect({
49
- database: new Database(':memory:'),
50
- }),
51
- plugins: [new CamelCasePlugin(), new SerializePlugin()],
52
- })
53
- }
54
-
55
- function createPostgresDb(): Kysely<KyselyPikkuDB> | null {
56
- const url = process.env.DATABASE_URL
57
- if (!url) return null
58
-
59
- return new Kysely<KyselyPikkuDB>({
60
- dialect: new PostgresJSDialect({ postgres: postgres(url) }),
61
- plugins: [new CamelCasePlugin()],
62
- })
63
- }
64
-
65
- async function dropAllTables(db: Kysely<KyselyPikkuDB>): Promise<void> {
66
- const tables = [
67
- 'pikku_deployment_functions',
68
- 'pikku_deployments',
69
- 'agent_tool_call',
70
- 'agent_message',
71
- 'agent_run_score',
72
- 'agent_run',
73
- 'agent_working_memory',
74
- 'agent_threads',
75
- 'channel_subscriptions',
76
- 'channels',
77
- 'workflow_step_history',
78
- 'workflow_step',
79
- 'workflow_runs',
80
- 'workflow_versions',
81
- 'secrets_audit',
82
- 'secret_kek_salts',
83
- 'secrets',
84
- 'credentials_audit',
85
- 'credential_kek_salts',
86
- 'credentials',
87
- ]
88
- for (const table of tables) {
89
- await db.schema.dropTable(table).ifExists().execute()
90
- }
91
- }
92
-
93
- function registerTests(
94
- dialectName: string,
95
- getDb: () => Kysely<KyselyPikkuDB>
96
- ) {
97
- defineServiceTests({
98
- name: dialectName,
99
- services: {
100
- channelStore: async () => {
101
- const s = new KyselyChannelStore(getDb())
102
- await s.init()
103
- return s
104
- },
105
- eventHubStore: async () => {
106
- const s = new KyselyEventHubStore(getDb())
107
- await s.init()
108
- return s
109
- },
110
- workflowService: async () => {
111
- const s = new KyselyWorkflowService(getDb())
112
- await s.init()
113
- return s
114
- },
115
- workflowRunService: async () => new KyselyWorkflowRunService(getDb()),
116
- deploymentService: async () => {
117
- const s = new KyselyDeploymentService(
118
- { heartbeatInterval: 60000, heartbeatTtl: 120000 },
119
- getDb()
120
- )
121
- await s.init()
122
- return s
123
- },
124
- agentStorageService: async () => {
125
- const s = new KyselyAgentStorageService(getDb())
126
- await s.init()
127
- return s
128
- },
129
- agentRunService: async () => new KyselyAgentRunService(getDb()),
130
- secretService: async (config) => {
131
- const s = new KyselySecretService(getDb(), config)
132
- await s.init()
133
- return s
134
- },
135
- credentialService: async (config) => {
136
- const s = new KyselyCredentialService(getDb(), config)
137
- await s.init()
138
- return s
139
- },
140
- sessionStore: async () => {
141
- const s = new KyselySessionStore(getDb())
142
- await s.init()
143
- return s
144
- },
145
- },
146
- })
147
-
148
- describe(`KyselySecretService audit [${dialectName}]`, () => {
149
- const kek = 'test-key-encryption-key-32chars!'
150
-
151
- test('audit logs writes, reads, and deletes', async () => {
152
- const service = new KyselySecretService(getDb(), {
153
- key: kek,
154
- audit: true,
155
- auditReads: true,
156
- })
157
- await service.init()
158
- await service.setSecret('audit-test', 'value')
159
- await service.getSecret('audit-test')
160
- await service.deleteSecret('audit-test')
161
-
162
- const logs = await getDb()
163
- .selectFrom('secretsAudit')
164
- .select(['secretKey', 'action'])
165
- .where('secretKey', '=', 'audit-test')
166
- .orderBy('performedAt', 'asc')
167
- .execute()
168
-
169
- assert.equal(logs.length, 3)
170
- assert.equal(logs[0]!.action, 'write')
171
- assert.equal(logs[1]!.action, 'read')
172
- assert.equal(logs[2]!.action, 'delete')
173
- })
174
-
175
- test('audit skips reads when auditReads is false', async () => {
176
- const service = new KyselySecretService(getDb(), {
177
- key: kek,
178
- audit: true,
179
- auditReads: false,
180
- })
181
- await service.init()
182
- await service.setSecret('no-read-audit', 'value')
183
- await service.getSecret('no-read-audit')
184
-
185
- const logs = await getDb()
186
- .selectFrom('secretsAudit')
187
- .select(['action'])
188
- .where('secretKey', '=', 'no-read-audit')
189
- .execute()
190
-
191
- assert.equal(logs.length, 1)
192
- assert.equal(logs[0]!.action, 'write')
193
- })
194
- })
195
-
196
- describe(`KyselySecretService KEK derivation [${dialectName}]`, () => {
197
- const kek = 'test-key-encryption-key-32chars!'
198
-
199
- beforeEach(async () => {
200
- await getDb().deleteFrom('secrets').execute()
201
- await getDb().deleteFrom('secretKekSalts').execute()
202
- })
203
-
204
- test('a KEK salt row is created once per key version', async () => {
205
- const service = new KyselySecretService(getDb(), { key: kek })
206
- await service.init()
207
- await service.setSecret('salt-a', 'one')
208
- await service.setSecret('salt-b', 'two')
209
-
210
- const rows = await getDb()
211
- .selectFrom('secretKekSalts')
212
- .select(['keyVersion', 'salt'])
213
- .execute()
214
-
215
- assert.equal(rows.length, 1)
216
- assert.equal(rows[0]!.keyVersion, 1)
217
- assert.ok(rows[0]!.salt.length > 0)
218
- })
219
-
220
- test('a second instance reuses the stored salt rather than minting one', async () => {
221
- const first = new KyselySecretService(getDb(), { key: kek })
222
- await first.init()
223
- await first.setSecret('shared-salt', 'value')
224
-
225
- const second = new KyselySecretService(getDb(), { key: kek })
226
- await second.init()
227
- assert.equal((await second.getSecret('shared-salt')).reveal(), 'value')
228
-
229
- const rows = await getDb()
230
- .selectFrom('secretKekSalts')
231
- .selectAll()
232
- .execute()
233
- assert.equal(rows.length, 1)
234
- })
235
-
236
- test('getSecrets over 50 secrets costs one KEK derivation, not 50', async () => {
237
- const service = new KyselySecretService(getDb(), { key: kek })
238
- await service.init()
239
-
240
- const keys: string[] = []
241
- for (let i = 0; i < 50; i++) {
242
- const key = `bulk-${i}`
243
- keys.push(key)
244
- await service.setSecret(key, `value-${i}`)
245
- }
246
-
247
- const reader = new KyselySecretService(getDb(), { key: kek })
248
- await reader.init()
249
-
250
- const start = performance.now()
251
- const out = await reader.getSecrets(keys)
252
- const elapsed = performance.now() - start
253
-
254
- assert.equal(Object.keys(out).length, 50)
255
- assert.equal(out['bulk-7']!.reveal(), 'value-7')
256
- assert.ok(
257
- elapsed < 400,
258
- `getSecrets over 50 secrets took ${elapsed.toFixed(0)}ms; one derivation per secret would cost seconds`
259
- )
260
- })
261
-
262
- test('rotateKEK over 50 secrets derives two KEKs, not 100', async () => {
263
- const previousKey = 'previous-key-encryption-key-32c!'
264
- const writer = new KyselySecretService(getDb(), {
265
- key: previousKey,
266
- keyVersion: 1,
267
- })
268
- await writer.init()
269
-
270
- for (let i = 0; i < 50; i++) {
271
- await writer.setSecret(`rotate-${i}`, `value-${i}`)
272
- }
273
-
274
- const rotator = new KyselySecretService(getDb(), {
275
- key: kek,
276
- keyVersion: 2,
277
- previousKey,
278
- })
279
- await rotator.init()
280
-
281
- const start = performance.now()
282
- const rotated = await rotator.rotateKEK()
283
- const elapsed = performance.now() - start
284
-
285
- assert.equal(rotated, 50)
286
- assert.equal((await rotator.getSecret('rotate-7')).reveal(), 'value-7')
287
- assert.ok(
288
- elapsed < 700,
289
- `rotating 50 secrets took ${elapsed.toFixed(0)}ms; two derivations per secret would cost seconds`
290
- )
291
-
292
- const salts = await getDb()
293
- .selectFrom('secretKekSalts')
294
- .select('keyVersion')
295
- .orderBy('keyVersion', 'asc')
296
- .execute()
297
- assert.deepEqual(
298
- salts.map((r) => r.keyVersion),
299
- [1, 2]
300
- )
301
- })
302
-
303
- test('a wrong KEK passphrase fails loudly rather than returning garbage', async () => {
304
- const writer = new KyselySecretService(getDb(), { key: kek })
305
- await writer.init()
306
- await writer.setSecret('wrong-kek', 'value')
307
-
308
- const reader = new KyselySecretService(getDb(), {
309
- key: 'a-completely-different-kek-32ch!',
310
- })
311
- await reader.init()
312
- await assert.rejects(() => reader.getSecret('wrong-kek'))
313
- })
314
- })
315
-
316
- describe(`KyselyCredentialService audit [${dialectName}]`, () => {
317
- const kek = 'test-key-encryption-key-32chars!'
318
-
319
- test('audit logs writes, reads, and deletes', async () => {
320
- const service = new KyselyCredentialService(getDb(), {
321
- key: kek,
322
- audit: true,
323
- auditReads: true,
324
- })
325
- await service.init()
326
- await service.set('audit-cred', { token: 'abc' }, 'user-1')
327
- await service.get('audit-cred', 'user-1')
328
- await service.delete('audit-cred', 'user-1')
329
-
330
- const logs = await getDb()
331
- .selectFrom('credentialsAudit')
332
- .select(['credentialName', 'userId', 'action'])
333
- .where('credentialName', '=', 'audit-cred')
334
- .orderBy('performedAt', 'asc')
335
- .execute()
336
-
337
- assert.equal(logs.length, 3)
338
- assert.equal(logs[0]!.action, 'write')
339
- assert.equal(logs[0]!.userId, 'user-1')
340
- assert.equal(logs[1]!.action, 'read')
341
- assert.equal(logs[2]!.action, 'delete')
342
- })
343
-
344
- test('audit logs global credential with null user_id', async () => {
345
- const service = new KyselyCredentialService(getDb(), {
346
- key: kek,
347
- audit: true,
348
- auditReads: true,
349
- })
350
- await service.init()
351
- await service.set('global-cred', { key: 'val' })
352
- await service.get('global-cred')
353
-
354
- const logs = await getDb()
355
- .selectFrom('credentialsAudit')
356
- .select(['credentialName', 'userId', 'action'])
357
- .where('credentialName', '=', 'global-cred')
358
- .execute()
359
-
360
- assert.equal(logs.length, 2)
361
- assert.equal(logs[0]!.userId, null)
362
- assert.equal(logs[1]!.userId, null)
363
- })
364
- })
365
- }
366
-
367
- describe('Kysely Services - SQLite', () => {
368
- let db: Kysely<KyselyPikkuDB>
369
-
370
- before(async () => {
371
- db = createSqliteDb()
372
- await applyPikkuSchemas(db, SERVICE_SCHEMAS)
373
- })
374
-
375
- after(async () => {
376
- await db.destroy()
377
- })
378
-
379
- registerTests('SQLite', () => db)
380
- })
381
-
382
- describe(
383
- 'Kysely Services - PostgreSQL',
384
- {
385
- skip: !process.env.DATABASE_URL ? 'DATABASE_URL not set' : undefined,
386
- },
387
- () => {
388
- let db: Kysely<KyselyPikkuDB>
389
-
390
- before(async () => {
391
- db = createPostgresDb()!
392
- await dropAllTables(db)
393
- await applyPikkuSchemas(db, SERVICE_SCHEMAS)
394
- })
395
-
396
- after(async () => {
397
- if (db) {
398
- await dropAllTables(db)
399
- await db.destroy()
400
- }
401
- })
402
-
403
- registerTests('PostgreSQL', () => db)
404
- }
405
- )
@@ -1,57 +0,0 @@
1
- import type { SessionStore } from '@pikku/core/services'
2
- import type { CoreUserSession } from '@pikku/core/types'
3
- import type { Kysely } from 'kysely'
4
- import type { KyselyPikkuDB } from './kysely-tables.js'
5
- import { parseJson } from './kysely-json.js'
6
- import { requirePikkuSchema } from './schema/index.js'
7
- import { sessionSchema } from './schema/session.schema.js'
8
-
9
- export class KyselySessionStore implements SessionStore {
10
- private initialized = false
11
-
12
- constructor(private db: Kysely<KyselyPikkuDB>) {}
13
-
14
- public async init(): Promise<void> {
15
- if (this.initialized) return
16
- await requirePikkuSchema(this.db, sessionSchema)
17
- this.initialized = true
18
- }
19
-
20
- async get(pikkuUserId: string): Promise<CoreUserSession | undefined> {
21
- const row = await this.db
22
- .selectFrom('pikkuUserSessions')
23
- .select(['session'])
24
- .where('pikkuUserId', '=', pikkuUserId)
25
- .executeTakeFirst()
26
-
27
- if (!row) {
28
- return undefined
29
- }
30
-
31
- return (parseJson(row.session) ?? undefined) as CoreUserSession | undefined
32
- }
33
-
34
- async set(pikkuUserId: string, session: CoreUserSession): Promise<void> {
35
- await this.db
36
- .insertInto('pikkuUserSessions')
37
- .values({
38
- pikkuUserId,
39
- session: JSON.stringify(session),
40
- updatedAt: new Date(),
41
- })
42
- .onConflict((oc) =>
43
- oc.column('pikkuUserId').doUpdateSet({
44
- session: JSON.stringify(session),
45
- updatedAt: new Date(),
46
- })
47
- )
48
- .execute()
49
- }
50
-
51
- async clear(pikkuUserId: string): Promise<void> {
52
- await this.db
53
- .deleteFrom('pikkuUserSessions')
54
- .where('pikkuUserId', '=', pikkuUserId)
55
- .execute()
56
- }
57
- }
@@ -1,110 +0,0 @@
1
- import { test, describe } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { readFileSync } from 'node:fs'
4
- import { join } from 'node:path'
5
- import { Kysely, SqliteDialect } from 'kysely'
6
- import Database from 'better-sqlite3'
7
-
8
- import { applyPikkuSchemas } from './schema/index.js'
9
-
10
- /**
11
- * `KyselyPikkuDB` and the schema declaration describe the same tables, and
12
- * nothing but this makes them agree.
13
- *
14
- * The types stay hand-written on purpose: they carry knowledge introspection
15
- * cannot recover — `WorkflowStatus` rather than `string`, `Generated<Date>`
16
- * rather than `Date`, and the comments explaining what a column holds.
17
- * Generating them from the DDL would trade all of that for one fewer file.
18
- *
19
- * What generating would have bought is drift protection, and that is what these
20
- * tests buy instead: add a column to a schema and forget the interface, or the
21
- * reverse, and the names stop matching here.
22
- */
23
-
24
- const SOURCE = readFileSync(
25
- join(import.meta.dirname, 'kysely-tables.ts'),
26
- 'utf8'
27
- )
28
-
29
- /** The same transform `CamelCasePlugin` applies to every declared identifier. */
30
- const physical = (name: string): string =>
31
- name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()
32
-
33
- const interfaceBody = (name: string): string => {
34
- const start = SOURCE.indexOf(`export interface ${name} {`)
35
- assert.notEqual(start, -1, `no interface ${name}`)
36
- const end = SOURCE.indexOf('\n}', start)
37
- return SOURCE.slice(SOURCE.indexOf('{', start) + 1, end)
38
- }
39
-
40
- const fieldsOf = (name: string): string[] =>
41
- interfaceBody(name)
42
- .split('\n')
43
- .map((line) => /^ {2}(\w+)(\??):/.exec(line)?.[1])
44
- .filter((field): field is string => field !== undefined)
45
-
46
- /** `KyselyPikkuDB`'s own table-name → interface-name mapping. */
47
- const tableInterfaces = new Map(
48
- interfaceBody('KyselyPikkuDB')
49
- .split('\n')
50
- .map((line) => /^ {2}(\w+): (\w+)$/.exec(line))
51
- .filter((match): match is RegExpExecArray => match !== null)
52
- .map((match) => [match[1]!, match[2]!])
53
- )
54
-
55
- /** The schema as declared, materialized and read back. */
56
- const declaredSchema = async (): Promise<Map<string, Set<string>>> => {
57
- const db = new Kysely<any>({
58
- dialect: new SqliteDialect({ database: new Database(':memory:') }),
59
- })
60
- try {
61
- // The scope schema's grants reference it, so it has to be there first —
62
- // the same prerequisite a real project satisfies with Better Auth.
63
- await db.schema
64
- .createTable('user')
65
- .addColumn('id', 'text', (col) => col.primaryKey())
66
- .execute()
67
- await applyPikkuSchemas(db)
68
-
69
- const tables = await db.introspection.getTables()
70
- return new Map(
71
- tables
72
- .filter((table) => table.name !== 'user')
73
- .map((table) => [
74
- table.name,
75
- new Set(table.columns.map((column) => column.name)),
76
- ])
77
- )
78
- } finally {
79
- await db.destroy()
80
- }
81
- }
82
-
83
- describe('KyselyPikkuDB tracks the schema declaration', () => {
84
- test('names every table the declaration creates, and no others', async () => {
85
- const declared = await declaredSchema()
86
-
87
- const typed = new Set([...tableInterfaces.keys()].map(physical))
88
- assert.deepEqual(
89
- [...typed].sort(),
90
- [...declared.keys()].sort(),
91
- 'a table in one and not the other means the two have drifted apart'
92
- )
93
- })
94
-
95
- test('names every column the declaration creates, and no others', async () => {
96
- const declared = await declaredSchema()
97
-
98
- for (const [key, interfaceName] of tableInterfaces) {
99
- const table = physical(key)
100
- const columns = declared.get(table)
101
- assert.ok(columns, `${table} is declared nowhere`)
102
-
103
- assert.deepEqual(
104
- fieldsOf(interfaceName).map(physical).sort(),
105
- [...columns].sort(),
106
- `${interfaceName} and the '${table}' declaration disagree`
107
- )
108
- }
109
- })
110
- })