@pikku/kysely 0.13.24 → 0.13.27

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 (78) hide show
  1. package/CHANGELOG.md +235 -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-tables.d.ts +45 -0
  9. package/dist/src/schema/analytics.schema.d.ts +17 -0
  10. package/dist/src/schema/analytics.schema.js +48 -0
  11. package/dist/src/schema/flag.schema.d.ts +16 -0
  12. package/dist/src/schema/flag.schema.js +62 -0
  13. package/dist/src/schema/index.d.ts +2 -0
  14. package/dist/src/schema/index.js +6 -0
  15. package/package.json +11 -6
  16. package/run-tests.sh +9 -1
  17. package/bin/pikku-kysely-pure.ts +0 -59
  18. package/dist/src/kysely-lock-vault.d.ts +0 -34
  19. package/dist/src/kysely-lock-vault.js +0 -77
  20. package/dist/src/schema/data-lock.schema.d.ts +0 -15
  21. package/dist/src/schema/data-lock.schema.js +0 -27
  22. package/dist/tsconfig.tsbuildinfo +0 -1
  23. package/src/classification-crypto.test.ts +0 -143
  24. package/src/classification-crypto.ts +0 -163
  25. package/src/classification-plugin.pglite.test.ts +0 -226
  26. package/src/classification-plugin.test.ts +0 -265
  27. package/src/classification-plugin.ts +0 -249
  28. package/src/coercion-plugin.test.ts +0 -160
  29. package/src/coercion-plugin.ts +0 -187
  30. package/src/create-audited-kysely.test.ts +0 -560
  31. package/src/create-audited-kysely.ts +0 -254
  32. package/src/index.ts +0 -81
  33. package/src/kysely-agent-run-scores.ts +0 -47
  34. package/src/kysely-agent-run-service.ts +0 -244
  35. package/src/kysely-agent-run-state-service.ts +0 -200
  36. package/src/kysely-agent-storage-service.ts +0 -632
  37. package/src/kysely-audit-service.test.ts +0 -352
  38. package/src/kysely-audit-service.ts +0 -237
  39. package/src/kysely-channel-store.ts +0 -107
  40. package/src/kysely-credential-service.ts +0 -286
  41. package/src/kysely-deployment-service.ts +0 -163
  42. package/src/kysely-eventhub-store.ts +0 -53
  43. package/src/kysely-json.ts +0 -5
  44. package/src/kysely-scope-service.test.ts +0 -418
  45. package/src/kysely-scope-service.ts +0 -425
  46. package/src/kysely-secret-service.ts +0 -242
  47. package/src/kysely-services.test.ts +0 -405
  48. package/src/kysely-session-store.ts +0 -57
  49. package/src/kysely-tables.test.ts +0 -110
  50. package/src/kysely-tables.ts +0 -434
  51. package/src/kysely-virtual-user-run-store.test.ts +0 -369
  52. package/src/kysely-virtual-user-run-store.ts +0 -235
  53. package/src/kysely-virtual-user-schedule-store.test.ts +0 -187
  54. package/src/kysely-virtual-user-schedule-store.ts +0 -187
  55. package/src/kysely-webhook-service.test.ts +0 -153
  56. package/src/kysely-webhook-service.ts +0 -139
  57. package/src/kysely-workflow-mirror.test.ts +0 -259
  58. package/src/kysely-workflow-mirror.ts +0 -374
  59. package/src/kysely-workflow-run-service.ts +0 -305
  60. package/src/kysely-workflow-service.test.ts +0 -544
  61. package/src/kysely-workflow-service.ts +0 -820
  62. package/src/schema/agent.schema.ts +0 -162
  63. package/src/schema/audit.schema.ts +0 -57
  64. package/src/schema/channel.schema.ts +0 -38
  65. package/src/schema/credential.schema.ts +0 -58
  66. package/src/schema/deployment.schema.ts +0 -47
  67. package/src/schema/index.ts +0 -441
  68. package/src/schema/pikku-schema.types.ts +0 -169
  69. package/src/schema/schema.test.ts +0 -591
  70. package/src/schema/scope.schema.ts +0 -110
  71. package/src/schema/secret.schema.ts +0 -49
  72. package/src/schema/session.schema.ts +0 -20
  73. package/src/schema/virtual-user-schedule.schema.ts +0 -48
  74. package/src/schema/virtual-user.schema.ts +0 -106
  75. package/src/schema/webhook.schema.ts +0 -58
  76. package/src/schema/workflow.schema.ts +0 -150
  77. package/src/serialize-plugin.ts +0 -233
  78. 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
- })