@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,560 +0,0 @@
1
- import { afterEach, describe, test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import Database from 'better-sqlite3'
4
- import { Kysely, SqliteDialect, type Generated } from 'kysely'
5
- import {
6
- createInvocationAudit,
7
- NoopAuditService,
8
- type AuditEvent,
9
- type AuditService,
10
- } from '@pikku/core/services'
11
-
12
- import { createAuditedKysely } from './create-audited-kysely.js'
13
-
14
- interface TodoTable {
15
- id: Generated<number>
16
- title: string
17
- done: number
18
- }
19
-
20
- interface TestDB {
21
- todos: TodoTable
22
- }
23
-
24
- interface AuditEventTable {
25
- eventId: string
26
- occurredAt: string
27
- type: string
28
- source: string
29
- outcome: string | null
30
- functionId: string | null
31
- wireType: string | null
32
- wireId: string | null
33
- traceId: string | null
34
- transactionId: string | null
35
- queryId: string | null
36
- userId: string | null
37
- orgId: string | null
38
- actorPikkuUserId: string | null
39
- metadataJson: string | null
40
- }
41
-
42
- interface AuditVerifierDB extends TestDB {
43
- audit_events: AuditEventTable
44
- }
45
-
46
- function createDb(): Kysely<TestDB> {
47
- return new Kysely<TestDB>({
48
- dialect: new SqliteDialect({
49
- database: new Database(':memory:'),
50
- }),
51
- })
52
- }
53
-
54
- async function createTodosTable(db: Kysely<TestDB>): Promise<void> {
55
- await db.schema
56
- .createTable('todos')
57
- .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
58
- .addColumn('title', 'text', (col) => col.notNull())
59
- .addColumn('done', 'integer', (col) => col.notNull().defaultTo(0))
60
- .execute()
61
- }
62
-
63
- async function createAuditEventsTable(
64
- db: Kysely<AuditVerifierDB>
65
- ): Promise<void> {
66
- await db.schema
67
- .createTable('audit_events')
68
- .addColumn('eventId', 'text', (col) => col.primaryKey())
69
- .addColumn('occurredAt', 'text', (col) => col.notNull())
70
- .addColumn('type', 'text', (col) => col.notNull())
71
- .addColumn('source', 'text', (col) => col.notNull())
72
- .addColumn('outcome', 'text')
73
- .addColumn('functionId', 'text')
74
- .addColumn('wireType', 'text')
75
- .addColumn('wireId', 'text')
76
- .addColumn('traceId', 'text')
77
- .addColumn('transactionId', 'text')
78
- .addColumn('queryId', 'text')
79
- .addColumn('userId', 'text')
80
- .addColumn('orgId', 'text')
81
- .addColumn('actorPikkuUserId', 'text')
82
- .addColumn('metadataJson', 'text')
83
- .execute()
84
- }
85
-
86
- class ImmediateAuditService implements AuditService {
87
- constructor(private readonly db: Kysely<AuditVerifierDB>) {}
88
-
89
- async audit(event: AuditEvent): Promise<void> {
90
- await this.insertEvent(event)
91
- }
92
-
93
- async write(batch: AuditEvent[]): Promise<void> {
94
- for (const event of batch) {
95
- await this.insertEvent(event)
96
- }
97
- }
98
-
99
- private async insertEvent(event: AuditEvent): Promise<void> {
100
- await this.db
101
- .insertInto('audit_events')
102
- .values({
103
- eventId:
104
- event.eventId ??
105
- `${event.traceId ?? 'trace'}:${event.queryId ?? 'single'}:${event.occurredAt}`,
106
- occurredAt: event.occurredAt,
107
- type: event.type,
108
- source: event.source,
109
- outcome: event.outcome ?? null,
110
- functionId: event.functionId ?? null,
111
- wireType: event.wireType ?? null,
112
- wireId: event.wireId ?? null,
113
- traceId: event.traceId ?? null,
114
- transactionId: event.transactionId ?? null,
115
- queryId: event.queryId ?? null,
116
- userId: event.userIdentity?.userId ?? null,
117
- orgId: event.userIdentity?.orgId ?? null,
118
- actorPikkuUserId: event.userIdentity?.pikkuUserId ?? null,
119
- metadataJson: event.metadata ? JSON.stringify(event.metadata) : null,
120
- })
121
- .execute()
122
- }
123
- }
124
-
125
- async function countPersistedEvents(
126
- db: Kysely<AuditVerifierDB>
127
- ): Promise<number> {
128
- const row = await db
129
- .selectFrom('audit_events')
130
- .select((eb) => eb.fn.countAll<number>().as('count'))
131
- .executeTakeFirstOrThrow()
132
-
133
- return Number(row.count)
134
- }
135
-
136
- async function listPersistedEvents(
137
- db: Kysely<AuditVerifierDB>
138
- ): Promise<
139
- Array<AuditEventTable & { metadata: Record<string, unknown> | null }>
140
- > {
141
- const rows = await db
142
- .selectFrom('audit_events')
143
- .selectAll()
144
- .orderBy('occurredAt', 'asc')
145
- .execute()
146
-
147
- return rows.map((row) => ({
148
- ...row,
149
- metadata: row.metadataJson
150
- ? (JSON.parse(row.metadataJson) as Record<string, unknown>)
151
- : null,
152
- }))
153
- }
154
-
155
- describe('createAuditedKysely', () => {
156
- const dbs: Kysely<TestDB>[] = []
157
-
158
- afterEach(async () => {
159
- while (dbs.length > 0) {
160
- const db = dbs.pop()!
161
- await db.destroy()
162
- }
163
- })
164
-
165
- test('captures insert queries through the invocation audit wrapper', async () => {
166
- const events: AuditEvent[] = []
167
- const db = createDb()
168
- dbs.push(db)
169
- await createTodosTable(db)
170
-
171
- const auditService: AuditService = {
172
- audit: async (event) => {
173
- events.push(event)
174
- },
175
- write: async (batch) => {
176
- events.push(...batch)
177
- },
178
- }
179
- const wire: any = {
180
- wireType: 'rpc',
181
- wireId: 'wire-1',
182
- traceId: 'trace-1',
183
- functionId: 'todos:create',
184
- session: { userId: 'user-1', orgId: 'org-1' },
185
- pikkuUserId: 'user-1',
186
- audit: { durability: 'best-effort' },
187
- logger: { warn: () => {} },
188
- }
189
- const audit = createInvocationAudit(auditService, wire)
190
- const auditedDb = createAuditedKysely(db, { audit })
191
-
192
- await auditedDb
193
- .insertInto('todos')
194
- .values({ title: 'write tests', done: 0 })
195
- .execute()
196
- await audit.close()
197
-
198
- assert.equal(events.length, 1)
199
- assert.equal(events[0]?.type, 'db.query')
200
- assert.equal(events[0]?.traceId, 'trace-1')
201
- assert.equal(events[0]?.functionId, 'todos:create')
202
- assert.equal(events[0]?.queryId, null)
203
- assert.deepEqual(events[0]?.metadata, {
204
- queryKind: 'insert',
205
- tables: ['todos'],
206
- changedColumns: ['title', 'done'],
207
- changes: { title: 'write tests', done: 0 },
208
- rowCount: 1,
209
- })
210
- assert.deepEqual(events[0]?.userIdentity, {
211
- userId: 'user-1',
212
- orgId: 'org-1',
213
- pikkuUserId: 'user-1',
214
- })
215
- })
216
-
217
- test('captures update changes from the query tree', async () => {
218
- const events: AuditEvent[] = []
219
- const db = createDb()
220
- dbs.push(db)
221
- await createTodosTable(db)
222
- await db.insertInto('todos').values({ title: 'before', done: 0 }).execute()
223
-
224
- const audit = createInvocationAudit(
225
- {
226
- audit: async (event) => {
227
- events.push(event)
228
- },
229
- write: async (batch) => {
230
- events.push(...batch)
231
- },
232
- },
233
- {
234
- wireType: 'rpc',
235
- wireId: 'wire-2',
236
- traceId: 'trace-2',
237
- functionId: 'todos:update',
238
- session: { userId: 'user-2' },
239
- pikkuUserId: 'user-2',
240
- audit: { durability: 'best-effort' },
241
- logger: { warn: () => {} },
242
- } as any
243
- )
244
- const auditedDb = createAuditedKysely(db, { audit })
245
-
246
- await auditedDb
247
- .updateTable('todos')
248
- .set({ title: 'after', done: 1 })
249
- .where('id', '=', 1)
250
- .execute()
251
- await audit.close()
252
-
253
- assert.equal(events.length, 1)
254
- assert.equal(events[0]?.metadata?.queryKind, 'update')
255
- assert.deepEqual(events[0]?.metadata?.changedColumns, ['title', 'done'])
256
- assert.deepEqual(events[0]?.metadata?.changes, {
257
- title: 'after',
258
- done: 1,
259
- })
260
- assert.deepEqual(events[0]?.userIdentity, {
261
- userId: 'user-2',
262
- orgId: undefined,
263
- pikkuUserId: 'user-2',
264
- })
265
- })
266
-
267
- test('keeps query ids when multiple queries are captured in one invocation', async () => {
268
- const events: AuditEvent[] = []
269
- const db = createDb()
270
- dbs.push(db)
271
- await createTodosTable(db)
272
-
273
- const audit = createInvocationAudit(
274
- {
275
- audit: async (event) => {
276
- events.push(event)
277
- },
278
- write: async (batch) => {
279
- events.push(...batch)
280
- },
281
- },
282
- {
283
- wireType: 'rpc',
284
- wireId: 'wire-3',
285
- traceId: 'trace-3',
286
- functionId: 'todos:multi',
287
- session: { userId: 'user-3' },
288
- pikkuUserId: 'user-3',
289
- audit: { durability: 'best-effort' },
290
- logger: { warn: () => {} },
291
- } as any
292
- )
293
- const auditedDb = createAuditedKysely(db, {
294
- audit,
295
- transactionId: 'tx-1',
296
- })
297
-
298
- await auditedDb
299
- .insertInto('todos')
300
- .values({ title: 'first', done: 0 })
301
- .execute()
302
- await auditedDb
303
- .insertInto('todos')
304
- .values({ title: 'second', done: 1 })
305
- .execute()
306
- await audit.close()
307
-
308
- assert.equal(events.length, 2)
309
- assert.equal(events[0]?.transactionId, 'tx-1')
310
- assert.equal(events[1]?.transactionId, 'tx-1')
311
- assert.equal(events[0]?.queryId, 'q-1')
312
- assert.equal(events[1]?.queryId, 'q-2')
313
- })
314
-
315
- test('captures select queries by default', async () => {
316
- const events: AuditEvent[] = []
317
- const db = createDb()
318
- dbs.push(db)
319
- await createTodosTable(db)
320
- await db.insertInto('todos').values({ title: 'read me', done: 0 }).execute()
321
-
322
- const audit = createInvocationAudit(
323
- {
324
- audit: async (event) => {
325
- events.push(event)
326
- },
327
- write: async (batch) => {
328
- events.push(...batch)
329
- },
330
- },
331
- {
332
- wireType: 'rpc',
333
- wireId: 'wire-4',
334
- audit: { durability: 'best-effort' },
335
- logger: { warn: () => {} },
336
- } as any
337
- )
338
- const auditedDb = createAuditedKysely(db, { audit })
339
-
340
- await auditedDb.selectFrom('todos').selectAll().execute()
341
- await audit.close()
342
-
343
- assert.equal(events.length, 1)
344
- assert.equal(events[0]?.metadata?.queryKind, 'select')
345
- assert.deepEqual(events[0]?.metadata?.tables, ['todos'])
346
- })
347
-
348
- test('can skip select auditing when auditReads is false', async () => {
349
- const events: AuditEvent[] = []
350
- const db = createDb()
351
- dbs.push(db)
352
- await createTodosTable(db)
353
- await db
354
- .insertInto('todos')
355
- .values({ title: 'skip read', done: 0 })
356
- .execute()
357
-
358
- const audit = createInvocationAudit(
359
- {
360
- audit: async (event) => {
361
- events.push(event)
362
- },
363
- write: async (batch) => {
364
- events.push(...batch)
365
- },
366
- },
367
- {
368
- wireType: 'rpc',
369
- wireId: 'wire-5',
370
- audit: { durability: 'best-effort' },
371
- logger: { warn: () => {} },
372
- } as any
373
- )
374
- const auditedDb = createAuditedKysely(db, {
375
- audit,
376
- auditReads: false,
377
- })
378
-
379
- await auditedDb.selectFrom('todos').selectAll().execute()
380
- await audit.close()
381
-
382
- assert.equal(events.length, 0)
383
- })
384
-
385
- test('noops when audit is not enabled on the wire', async () => {
386
- const db = createDb()
387
- dbs.push(db)
388
- await createTodosTable(db)
389
-
390
- const audit = createInvocationAudit(new NoopAuditService(), {
391
- wireType: 'rpc',
392
- wireId: 'wire-6',
393
- logger: { warn: () => {} },
394
- } as any)
395
- const auditedDb = createAuditedKysely(db, { audit })
396
-
397
- await auditedDb
398
- .insertInto('todos')
399
- .values({ title: 'noop', done: 0 })
400
- .execute()
401
- await audit.close()
402
-
403
- assert.ok(true)
404
- })
405
-
406
- test('persists read and update queries after best-effort flush', async () => {
407
- const db = createDb() as Kysely<AuditVerifierDB>
408
- dbs.push(db as unknown as Kysely<TestDB>)
409
- await createTodosTable(db)
410
- await createAuditEventsTable(db)
411
- await db.insertInto('todos').values({ title: 'seed', done: 0 }).execute()
412
-
413
- const audit = createInvocationAudit(new ImmediateAuditService(db), {
414
- wireType: 'rpc',
415
- wireId: 'wire-best-effort',
416
- traceId: 'trace-best-effort',
417
- functionId: 'todos:best-effort',
418
- session: { userId: 'user-best-effort' },
419
- pikkuUserId: 'user-best-effort',
420
- audit: { durability: 'best-effort' },
421
- logger: { warn: () => {} },
422
- } as any)
423
- const auditedDb = createAuditedKysely(db, { audit })
424
-
425
- const beforeCount = await countPersistedEvents(db)
426
- await auditedDb
427
- .selectFrom('todos')
428
- .selectAll()
429
- .where('id', '=', 1)
430
- .executeTakeFirstOrThrow()
431
- await auditedDb
432
- .updateTable('todos')
433
- .set({ done: 1 })
434
- .where('id', '=', 1)
435
- .execute()
436
- const insideCount = await countPersistedEvents(db)
437
-
438
- assert.equal(beforeCount, 0)
439
- assert.equal(insideCount, 0)
440
-
441
- await audit.close()
442
-
443
- const persisted = await listPersistedEvents(db)
444
- assert.equal(persisted.length, 2)
445
- assert.deepEqual(
446
- persisted.map((event) => event.metadata?.queryKind),
447
- ['select', 'update']
448
- )
449
- assert.deepEqual(
450
- persisted.map((event) => event.traceId),
451
- ['trace-best-effort', 'trace-best-effort']
452
- )
453
- assert.deepEqual(
454
- persisted.map((event) => event.functionId),
455
- ['todos:best-effort', 'todos:best-effort']
456
- )
457
- assert.deepEqual(
458
- persisted.map((event) => event.userId),
459
- ['user-best-effort', 'user-best-effort']
460
- )
461
- assert.equal(persisted[0]?.queryId, 'q-1')
462
- assert.equal(persisted[1]?.queryId, 'q-2')
463
- assert.deepEqual(persisted[1]?.metadata?.changedColumns, ['done'])
464
- assert.deepEqual(persisted[1]?.metadata?.changes, { done: 1 })
465
- })
466
-
467
- test('writes read and update audit events immediately in transactional mode', async () => {
468
- const db = createDb()
469
- dbs.push(db)
470
- await createTodosTable(db)
471
- await db.insertInto('todos').values({ title: 'seed', done: 0 }).execute()
472
-
473
- const events: AuditEvent[] = []
474
- const audit = createInvocationAudit(
475
- {
476
- audit: async (event) => {
477
- events.push(event)
478
- },
479
- },
480
- {
481
- wireType: 'rpc',
482
- wireId: 'wire-transactional',
483
- traceId: 'trace-transactional',
484
- functionId: 'todos:transactional',
485
- session: { userId: 'user-transactional' },
486
- pikkuUserId: 'user-transactional',
487
- audit: { durability: 'transactional' },
488
- logger: { warn: () => {} },
489
- } as any
490
- )
491
-
492
- const counts: number[] = []
493
-
494
- await db.transaction().execute(async (trx) => {
495
- const auditedTrx = createAuditedKysely(trx as Kysely<TestDB>, {
496
- audit,
497
- transactionId: 'tx-verifier-1',
498
- })
499
-
500
- await auditedTrx
501
- .selectFrom('todos')
502
- .selectAll()
503
- .where('id', '=', 1)
504
- .executeTakeFirstOrThrow()
505
- counts.push(events.length)
506
-
507
- await auditedTrx
508
- .updateTable('todos')
509
- .set({ done: 1 })
510
- .where('id', '=', 1)
511
- .execute()
512
- counts.push(events.length)
513
- })
514
-
515
- assert.deepEqual(counts, [1, 2])
516
- assert.equal(events.length, 2)
517
- assert.deepEqual(
518
- events.map((event) => event.transactionId),
519
- ['tx-verifier-1', 'tx-verifier-1']
520
- )
521
- assert.deepEqual(
522
- events.map((event) => event.metadata?.queryKind),
523
- ['select', 'update']
524
- )
525
- assert.deepEqual(events[1]?.metadata?.changes, { done: 1 })
526
- })
527
-
528
- test('does not persist audit rows when the invocation is not audited', async () => {
529
- const db = createDb() as Kysely<AuditVerifierDB>
530
- dbs.push(db as unknown as Kysely<TestDB>)
531
- await createTodosTable(db)
532
- await createAuditEventsTable(db)
533
- await db.insertInto('todos').values({ title: 'seed', done: 0 }).execute()
534
-
535
- const audit = createInvocationAudit(new ImmediateAuditService(db), {
536
- wireType: 'rpc',
537
- wireId: 'wire-plain',
538
- traceId: 'trace-plain',
539
- functionId: 'todos:plain',
540
- session: { userId: 'user-plain' },
541
- pikkuUserId: 'user-plain',
542
- logger: { warn: () => {} },
543
- } as any)
544
- const auditedDb = createAuditedKysely(db, { audit })
545
-
546
- await auditedDb
547
- .selectFrom('todos')
548
- .selectAll()
549
- .where('id', '=', 1)
550
- .executeTakeFirstOrThrow()
551
- await auditedDb
552
- .updateTable('todos')
553
- .set({ done: 1 })
554
- .where('id', '=', 1)
555
- .execute()
556
- await audit.close()
557
-
558
- assert.equal(await countPersistedEvents(db), 0)
559
- })
560
- })