@pikku/kysely 0.13.19 → 0.13.20
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 +54 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/kysely-agent-run-state-service.js +2 -2
- package/dist/src/kysely-agent-storage-service.js +2 -2
- package/dist/src/kysely-audit-service.js +2 -2
- package/dist/src/kysely-channel-store.js +2 -2
- package/dist/src/kysely-credential-service.js +2 -2
- package/dist/src/kysely-deployment-service.js +2 -2
- package/dist/src/kysely-scope-service.js +2 -2
- package/dist/src/kysely-secret-service.js +2 -2
- package/dist/src/kysely-session-store.js +2 -2
- package/dist/src/kysely-tables.d.ts +28 -0
- package/dist/src/kysely-virtual-user-run-store.js +2 -2
- package/dist/src/kysely-webhook-service.js +2 -2
- package/dist/src/kysely-workflow-mirror.d.ts +1 -1
- package/dist/src/kysely-workflow-mirror.js +3 -3
- package/dist/src/kysely-workflow-service.js +2 -2
- package/dist/src/schema/agent.schema.js +1 -0
- package/dist/src/schema/audit.schema.d.ts +5 -4
- package/dist/src/schema/audit.schema.js +6 -4
- package/dist/src/schema/channel.schema.js +1 -0
- package/dist/src/schema/credential.schema.js +1 -0
- package/dist/src/schema/index.d.ts +47 -26
- package/dist/src/schema/index.js +56 -46
- package/dist/src/schema/pikku-schema.types.d.ts +20 -0
- package/dist/src/schema/scope.schema.js +1 -0
- package/dist/src/schema/virtual-user.schema.js +2 -1
- package/dist/src/schema/webhook.schema.js +1 -0
- package/dist/src/schema/workflow.schema.js +1 -0
- package/package.json +2 -2
- package/src/index.ts +12 -2
- package/src/kysely-agent-run-state-service.ts +2 -2
- package/src/kysely-agent-storage-service.ts +2 -2
- package/src/kysely-audit-service.test.ts +2 -10
- package/src/kysely-audit-service.ts +2 -2
- package/src/kysely-channel-store.ts +2 -2
- package/src/kysely-credential-service.ts +2 -2
- package/src/kysely-deployment-service.ts +2 -2
- package/src/kysely-scope-service.test.ts +2 -0
- package/src/kysely-scope-service.ts +2 -2
- package/src/kysely-secret-service.ts +2 -2
- package/src/kysely-services.test.ts +26 -0
- package/src/kysely-session-store.ts +2 -2
- package/src/kysely-tables.test.ts +1 -12
- package/src/kysely-tables.ts +29 -0
- package/src/kysely-virtual-user-run-store.test.ts +22 -6
- package/src/kysely-virtual-user-run-store.ts +2 -2
- package/src/kysely-webhook-service.test.ts +4 -0
- package/src/kysely-webhook-service.ts +2 -2
- package/src/kysely-workflow-mirror.test.ts +2 -0
- package/src/kysely-workflow-mirror.ts +3 -3
- package/src/kysely-workflow-service.test.ts +2 -0
- package/src/kysely-workflow-service.ts +2 -2
- package/src/schema/agent.schema.ts +1 -0
- package/src/schema/audit.schema.ts +6 -4
- package/src/schema/channel.schema.ts +1 -0
- package/src/schema/credential.schema.ts +1 -0
- package/src/schema/index.ts +67 -52
- package/src/schema/pikku-schema.types.ts +20 -0
- package/src/schema/schema.test.ts +86 -29
- package/src/schema/scope.schema.ts +1 -0
- package/src/schema/virtual-user.schema.ts +2 -1
- package/src/schema/webhook.schema.ts +1 -0
- package/src/schema/workflow.schema.ts +1 -0
package/dist/src/schema/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CamelCasePlugin } from 'kysely';
|
|
2
2
|
import { schemaContext, } from './pikku-schema.types.js';
|
|
3
3
|
import { agentSchema } from './agent.schema.js';
|
|
4
|
+
import { auditSchema } from './audit.schema.js';
|
|
4
5
|
import { channelSchema } from './channel.schema.js';
|
|
5
6
|
import { credentialSchema } from './credential.schema.js';
|
|
6
7
|
import { deploymentSchema } from './deployment.schema.js';
|
|
@@ -9,19 +10,21 @@ import { secretSchema } from './secret.schema.js';
|
|
|
9
10
|
import { sessionSchema } from './session.schema.js';
|
|
10
11
|
import { webhookSchema } from './webhook.schema.js';
|
|
11
12
|
import { workflowSchema } from './workflow.schema.js';
|
|
13
|
+
import { virtualUserSchema } from './virtual-user.schema.js';
|
|
12
14
|
export { rawStatement, requiredType, schemaContext, unqualifiedContext, } from './pikku-schema.types.js';
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
* runtime works without an audit table, and `KyselyAuditService.init()` applies
|
|
16
|
-
* this for the projects that wire the sink.
|
|
17
|
-
*/
|
|
18
|
-
export { auditSchema } from './audit.schema.js';
|
|
19
|
-
/**
|
|
20
|
-
* Every table the pikku runtime needs, in dependency order.
|
|
16
|
+
* Every table any part of the pikku runtime can need, in dependency order.
|
|
21
17
|
*
|
|
22
18
|
* Order is load-bearing, not cosmetic: `scope` has foreign keys onto Better
|
|
23
19
|
* Auth's `user`, and the tables within each schema reference each other. Sort
|
|
24
20
|
* this list alphabetically and it stops applying.
|
|
21
|
+
*
|
|
22
|
+
* `audit` and `virtualUser` are in the list now. They used to be left out and
|
|
23
|
+
* created by their own services at boot, which was the only way they ever came
|
|
24
|
+
* into existence — so with the runtime no longer creating anything, leaving
|
|
25
|
+
* them out would mean a wired audit sink starting up against a table that
|
|
26
|
+
* nothing writes a migration for. Both are gated by `ownedBy`, so a project
|
|
27
|
+
* that wires neither still carries neither.
|
|
25
28
|
*/
|
|
26
29
|
export const pikkuSchemas = [
|
|
27
30
|
channelSchema,
|
|
@@ -33,7 +36,31 @@ export const pikkuSchemas = [
|
|
|
33
36
|
workflowSchema,
|
|
34
37
|
agentSchema,
|
|
35
38
|
scopeSchema,
|
|
39
|
+
auditSchema,
|
|
40
|
+
virtualUserSchema,
|
|
36
41
|
];
|
|
42
|
+
export { agentSchema } from './agent.schema.js';
|
|
43
|
+
export { auditSchema } from './audit.schema.js';
|
|
44
|
+
export { channelSchema } from './channel.schema.js';
|
|
45
|
+
export { credentialSchema } from './credential.schema.js';
|
|
46
|
+
export { deploymentSchema } from './deployment.schema.js';
|
|
47
|
+
export { scopeSchema } from './scope.schema.js';
|
|
48
|
+
export { secretSchema } from './secret.schema.js';
|
|
49
|
+
export { sessionSchema } from './session.schema.js';
|
|
50
|
+
export { virtualUserSchema } from './virtual-user.schema.js';
|
|
51
|
+
export { webhookSchema } from './webhook.schema.js';
|
|
52
|
+
export { workflowSchema } from './workflow.schema.js';
|
|
53
|
+
/**
|
|
54
|
+
* The schemas a project needs, given the services its code actually reaches.
|
|
55
|
+
*
|
|
56
|
+
* `services` is the inspector's `requiredServices` — the same set that drives
|
|
57
|
+
* service tree-shaking, so a schema is written for exactly the projects whose
|
|
58
|
+
* functions, middleware or wirings reach the service that owns its tables.
|
|
59
|
+
*
|
|
60
|
+
* The gate is one-sided: a schema declaring no `ownedBy` is always in, because
|
|
61
|
+
* nothing a project writes implies it and leaving it out would be a guess.
|
|
62
|
+
*/
|
|
63
|
+
export const requiredPikkuSchemas = (services) => pikkuSchemas.filter((schema) => !schema.ownedBy || schema.ownedBy.some((name) => services.has(name)));
|
|
37
64
|
/**
|
|
38
65
|
* Bind a declaration to a database.
|
|
39
66
|
*
|
|
@@ -237,53 +264,36 @@ const isPresent = (existing, table) => table.schema && existing.schemaAware
|
|
|
237
264
|
? existing.qualified.has(`${table.schema}.${table.name}`)
|
|
238
265
|
: existing.bare.has(table.name);
|
|
239
266
|
/**
|
|
240
|
-
*
|
|
267
|
+
* Refuse to start unless one schema's tables are already there.
|
|
241
268
|
*
|
|
242
|
-
* What a service calls at boot
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
269
|
+
* What a service calls at boot, and it never issues DDL. The runtime is not an
|
|
270
|
+
* author of the schema: `pikku db generate` writes the declaration down as a
|
|
271
|
+
* migration and `pikku db migrate` applies it, and those two are the only way
|
|
272
|
+
* these tables come into existence. A service that finds them missing says so
|
|
273
|
+
* and stops.
|
|
246
274
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
275
|
+
* This is the whole point of gating what `db generate` writes. While boot could
|
|
276
|
+
* still create, a schema that generation left out was created silently at
|
|
277
|
+
* startup instead — two authorities over one set of tables, which is the
|
|
278
|
+
* condition all of this exists to end. With creation gone, a schema generation
|
|
279
|
+
* missed is a sentence at startup naming the command that fixes it.
|
|
252
280
|
*
|
|
253
|
-
*
|
|
254
|
-
* the
|
|
255
|
-
* `present` path and this never issues DDL at all.
|
|
281
|
+
* Half-present is not a distinct case any more. Every missing table is reported
|
|
282
|
+
* the same way, because the remedy is the same one either way.
|
|
256
283
|
*/
|
|
257
|
-
export const
|
|
284
|
+
export const requirePikkuSchema = async (db, schema) => {
|
|
258
285
|
const existing = await tablesOf(db);
|
|
259
286
|
const declared = declaredTables(schema, db);
|
|
260
287
|
const missing = declared.filter((table) => !isPresent(existing, table));
|
|
261
288
|
if (missing.length === 0)
|
|
262
|
-
return
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
'`pikku db generate` writes the declaration down — rather than creating the rest at boot.');
|
|
271
|
-
}
|
|
272
|
-
try {
|
|
273
|
-
await applyPikkuSchemas(db, [schema]);
|
|
274
|
-
return 'created';
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
// Two instances booting cold against one database can both see it empty
|
|
278
|
-
// before either has finished creating. If the other one got there, say so;
|
|
279
|
-
// anything else is a real failure and still throws. This is a courtesy, not
|
|
280
|
-
// a guarantee — a boot race is only fully answered by having run the
|
|
281
|
-
// migration, which is what `pikku db generate` is for.
|
|
282
|
-
const after = await tablesOf(db);
|
|
283
|
-
if (declared.every((table) => isPresent(after, table)))
|
|
284
|
-
return 'present';
|
|
285
|
-
throw error;
|
|
286
|
-
}
|
|
289
|
+
return;
|
|
290
|
+
const present = declared.filter((table) => isPresent(existing, table));
|
|
291
|
+
throw new Error(`The '${schema.name}' schema is not in this database: ${missing.map(displayName).join(', ')} missing` +
|
|
292
|
+
(present.length > 0
|
|
293
|
+
? `, ${present.map(displayName).join(', ')} already there`
|
|
294
|
+
: '') +
|
|
295
|
+
'. The runtime does not create these — run `pikku db generate` to write the ' +
|
|
296
|
+
'migration, then `pikku db migrate` to apply it.');
|
|
287
297
|
};
|
|
288
298
|
/**
|
|
289
299
|
* Render the declaration as SQL for the dialect `db` was built with.
|
|
@@ -80,6 +80,26 @@ export interface PikkuSchema {
|
|
|
80
80
|
* database — or, worse, a stub table conjured up to make the error go away.
|
|
81
81
|
*/
|
|
82
82
|
requires?: SchemaRequirement[];
|
|
83
|
+
/**
|
|
84
|
+
* The singleton services whose `init()` requires these tables, or absent for
|
|
85
|
+
* a schema every project needs.
|
|
86
|
+
*
|
|
87
|
+
* Named as services rather than as wirings because a service is what the
|
|
88
|
+
* tables actually belong to: one `requirePikkuSchema` call site per service,
|
|
89
|
+
* and `pikku db generate` writes the schema for a project whose functions
|
|
90
|
+
* reach any of them. A wiring is a poor proxy — a project can construct
|
|
91
|
+
* `KyselyWorkflowService` and wire no workflows at all.
|
|
92
|
+
*
|
|
93
|
+
* Absent means unconditional. `secrets` is on every project and the session
|
|
94
|
+
* and deployment stores are reached by generated code rather than by
|
|
95
|
+
* functions, so nothing a project writes implies them and their absence
|
|
96
|
+
* cannot be inferred.
|
|
97
|
+
*
|
|
98
|
+
* Under-naming is safe now that the runtime creates nothing: a schema left
|
|
99
|
+
* out of the migration is a sentence at startup, not a table conjured behind
|
|
100
|
+
* your back. Over-naming costs an unused table. Prefer over-naming.
|
|
101
|
+
*/
|
|
102
|
+
ownedBy?: string[];
|
|
83
103
|
}
|
|
84
104
|
/**
|
|
85
105
|
* A column another source owns, which one of these schemas depends on.
|
|
@@ -17,7 +17,8 @@ import { sql } from 'kysely';
|
|
|
17
17
|
* week's does not depend on which database they were found in.
|
|
18
18
|
*/
|
|
19
19
|
export const virtualUserSchema = {
|
|
20
|
-
name: '
|
|
20
|
+
name: 'virtual-user',
|
|
21
|
+
ownedBy: ['virtualUserRunStore'],
|
|
21
22
|
statements: [
|
|
22
23
|
(db) => db.schema
|
|
23
24
|
.createTable('virtualUserRun')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/kysely",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.20",
|
|
4
4
|
"description": "Kysely-backed Pikku services — workflow state, secrets, sessions, scopes and audit",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"prepublishOnly": "yarn build"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@pikku/core": "^0.12.
|
|
24
|
+
"@pikku/core": "^0.12.89"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"kysely": "^0.29.5"
|
package/src/index.ts
CHANGED
|
@@ -32,12 +32,22 @@ export {
|
|
|
32
32
|
type CreateCoercionPluginOptions,
|
|
33
33
|
} from './coercion-plugin.js'
|
|
34
34
|
export {
|
|
35
|
+
agentSchema,
|
|
35
36
|
auditSchema,
|
|
37
|
+
channelSchema,
|
|
38
|
+
credentialSchema,
|
|
39
|
+
deploymentSchema,
|
|
40
|
+
scopeSchema,
|
|
41
|
+
secretSchema,
|
|
42
|
+
sessionSchema,
|
|
43
|
+
virtualUserSchema,
|
|
44
|
+
webhookSchema,
|
|
45
|
+
workflowSchema,
|
|
36
46
|
pikkuSchemas,
|
|
47
|
+
requiredPikkuSchemas,
|
|
37
48
|
applyPikkuSchemas,
|
|
38
49
|
compilePikkuSchemas,
|
|
39
|
-
|
|
40
|
-
type EnsureOutcome,
|
|
50
|
+
requirePikkuSchema,
|
|
41
51
|
resolveRequirements,
|
|
42
52
|
type PikkuSchema,
|
|
43
53
|
type RequiredTypes,
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
SaveScoreInput,
|
|
10
10
|
} from '@pikku/core/services'
|
|
11
11
|
import type { PendingApproval } from '@pikku/core/agent'
|
|
12
|
-
import {
|
|
12
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
13
13
|
import { agentSchema } from './schema/agent.schema.js'
|
|
14
14
|
import { getRunScores, saveRunScore } from './kysely-agent-run-scores.js'
|
|
15
15
|
|
|
@@ -20,7 +20,7 @@ export class KyselyAgentRunStateService implements AgentRunStateService {
|
|
|
20
20
|
|
|
21
21
|
async init(): Promise<void> {
|
|
22
22
|
if (this.initialized) return
|
|
23
|
-
await
|
|
23
|
+
await requirePikkuSchema(this.db, agentSchema)
|
|
24
24
|
this.initialized = true
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -14,7 +14,7 @@ import type { Kysely } from 'kysely'
|
|
|
14
14
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
15
15
|
import { parseJson } from './kysely-json.js'
|
|
16
16
|
import { getRunScores, saveRunScore } from './kysely-agent-run-scores.js'
|
|
17
|
-
import {
|
|
17
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
18
18
|
import { agentSchema } from './schema/agent.schema.js'
|
|
19
19
|
|
|
20
20
|
export class KyselyAgentStorageService
|
|
@@ -26,7 +26,7 @@ export class KyselyAgentStorageService
|
|
|
26
26
|
|
|
27
27
|
public async init(): Promise<void> {
|
|
28
28
|
if (this.initialized) return
|
|
29
|
-
await
|
|
29
|
+
await requirePikkuSchema(this.db, agentSchema)
|
|
30
30
|
this.initialized = true
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -309,21 +309,13 @@ describe('KyselyAuditService against a CamelCasePlugin connection', () => {
|
|
|
309
309
|
})
|
|
310
310
|
|
|
311
311
|
describe('KyselyAuditService.init', () => {
|
|
312
|
-
test('
|
|
312
|
+
test('refuses to boot against a database that has none', async () => {
|
|
313
313
|
const fresh = new Kysely<any>({
|
|
314
314
|
dialect: new SqliteDialect({ database: new Database(':memory:') }),
|
|
315
315
|
plugins: [new CamelCasePlugin()],
|
|
316
316
|
})
|
|
317
317
|
const freshService = new KyselyAuditService(fresh)
|
|
318
|
-
await freshService.init()
|
|
319
|
-
await freshService.write([
|
|
320
|
-
event({ eventId: 'e1', type: 't', occurredAt: '2026-01-01' }),
|
|
321
|
-
])
|
|
322
|
-
const { events } = await freshService.query()
|
|
323
|
-
assert.deepEqual(
|
|
324
|
-
events.map((e) => e.eventId),
|
|
325
|
-
['e1']
|
|
326
|
-
)
|
|
318
|
+
await assert.rejects(freshService.init(), /pikku db generate/)
|
|
327
319
|
})
|
|
328
320
|
|
|
329
321
|
// Two boots against one database, and the second must not wipe the first.
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
AuditQueryResult,
|
|
7
7
|
} from '@pikku/core/services'
|
|
8
8
|
import type { Kysely } from 'kysely'
|
|
9
|
-
import {
|
|
9
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
10
10
|
import { auditSchema } from './schema/audit.schema.js'
|
|
11
11
|
|
|
12
12
|
const jsonOrNull = (v: unknown): string | null =>
|
|
@@ -103,7 +103,7 @@ export class KyselyAuditService implements AuditService {
|
|
|
103
103
|
*/
|
|
104
104
|
public async init(): Promise<void> {
|
|
105
105
|
if (this.initialized) return
|
|
106
|
-
await
|
|
106
|
+
await requirePikkuSchema(this.db, auditSchema)
|
|
107
107
|
this.initialized = true
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -3,7 +3,7 @@ import type { Channel } from '@pikku/core/channel'
|
|
|
3
3
|
import type { Kysely } from 'kysely'
|
|
4
4
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
5
5
|
import { parseJson } from './kysely-json.js'
|
|
6
|
-
import {
|
|
6
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
7
7
|
import { channelSchema } from './schema/channel.schema.js'
|
|
8
8
|
|
|
9
9
|
export class KyselyChannelStore extends ChannelStore {
|
|
@@ -15,7 +15,7 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
15
15
|
|
|
16
16
|
public async init(): Promise<void> {
|
|
17
17
|
if (this.initialized) return
|
|
18
|
-
await
|
|
18
|
+
await requirePikkuSchema(this.db, channelSchema)
|
|
19
19
|
this.initialized = true
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import type { CredentialService } from '@pikku/core/services'
|
|
9
9
|
import type { Kysely } from 'kysely'
|
|
10
10
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
11
|
-
import {
|
|
11
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
12
12
|
import { credentialSchema } from './schema/credential.schema.js'
|
|
13
13
|
|
|
14
14
|
export interface KyselyCredentialServiceConfig {
|
|
@@ -42,7 +42,7 @@ export class KyselyCredentialService implements CredentialService {
|
|
|
42
42
|
|
|
43
43
|
public async init(): Promise<void> {
|
|
44
44
|
if (this.initialized) return
|
|
45
|
-
await
|
|
45
|
+
await requirePikkuSchema(this.db, credentialSchema)
|
|
46
46
|
this.initialized = true
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -9,7 +9,7 @@ import type { SecretService } from '@pikku/core/services'
|
|
|
9
9
|
import { getAllFunctionNames } from '@pikku/core/function'
|
|
10
10
|
import type { Kysely } from 'kysely'
|
|
11
11
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
12
|
-
import {
|
|
12
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
13
13
|
import { deploymentSchema } from './schema/deployment.schema.js'
|
|
14
14
|
|
|
15
15
|
export class KyselyDeploymentService implements DeploymentService {
|
|
@@ -31,7 +31,7 @@ export class KyselyDeploymentService implements DeploymentService {
|
|
|
31
31
|
|
|
32
32
|
public async init(): Promise<void> {
|
|
33
33
|
if (this.initialized) return
|
|
34
|
-
await
|
|
34
|
+
await requirePikkuSchema(this.db, deploymentSchema)
|
|
35
35
|
this.initialized = true
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -5,6 +5,7 @@ import Database from 'better-sqlite3'
|
|
|
5
5
|
import { SerializePlugin } from './serialize-plugin.js'
|
|
6
6
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
7
7
|
import { KyselyScopeService } from './kysely-scope-service.js'
|
|
8
|
+
import { applyPikkuSchemas, scopeSchema } from './schema/index.js'
|
|
8
9
|
|
|
9
10
|
const SCOPES = [
|
|
10
11
|
{ id: 'admin' },
|
|
@@ -41,6 +42,7 @@ beforeEach(async () => {
|
|
|
41
42
|
plugins: [new CamelCasePlugin(), new SerializePlugin()],
|
|
42
43
|
})
|
|
43
44
|
await createUserTable(db)
|
|
45
|
+
await applyPikkuSchemas(db, [scopeSchema])
|
|
44
46
|
service = new KyselyScopeService(db)
|
|
45
47
|
await service.init()
|
|
46
48
|
await service.syncScopes(SCOPES)
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '@pikku/core/errors'
|
|
9
9
|
import type { Kysely } from 'kysely'
|
|
10
10
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
11
|
-
import {
|
|
11
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
12
12
|
import { scopeSchema } from './schema/scope.schema.js'
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -30,7 +30,7 @@ export class KyselyScopeService implements ScopeService {
|
|
|
30
30
|
|
|
31
31
|
public async init(): Promise<void> {
|
|
32
32
|
if (this.initialized) return
|
|
33
|
-
await
|
|
33
|
+
await requirePikkuSchema(this.db, scopeSchema)
|
|
34
34
|
this.initialized = true
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -10,7 +10,7 @@ import type { SecretService, SecretValues } from '@pikku/core/services'
|
|
|
10
10
|
import type { SecretValue } from '@pikku/core/classification'
|
|
11
11
|
import type { Kysely } from 'kysely'
|
|
12
12
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
13
|
-
import {
|
|
13
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
14
14
|
import { secretSchema } from './schema/secret.schema.js'
|
|
15
15
|
|
|
16
16
|
export interface KyselySecretServiceConfig {
|
|
@@ -44,7 +44,7 @@ export class KyselySecretService implements SecretService {
|
|
|
44
44
|
|
|
45
45
|
public async init(): Promise<void> {
|
|
46
46
|
if (this.initialized) return
|
|
47
|
-
await
|
|
47
|
+
await requirePikkuSchema(this.db, secretSchema)
|
|
48
48
|
this.initialized = true
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -18,6 +18,30 @@ import { KyselyAgentRunService } from './kysely-agent-run-service.js'
|
|
|
18
18
|
import { KyselySecretService } from './kysely-secret-service.js'
|
|
19
19
|
import { KyselyCredentialService } from './kysely-credential-service.js'
|
|
20
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
|
+
]
|
|
21
45
|
|
|
22
46
|
function createSqliteDb(): Kysely<KyselyPikkuDB> {
|
|
23
47
|
return new Kysely<KyselyPikkuDB>({
|
|
@@ -345,6 +369,7 @@ describe('Kysely Services - SQLite', () => {
|
|
|
345
369
|
|
|
346
370
|
before(async () => {
|
|
347
371
|
db = createSqliteDb()
|
|
372
|
+
await applyPikkuSchemas(db, SERVICE_SCHEMAS)
|
|
348
373
|
})
|
|
349
374
|
|
|
350
375
|
after(async () => {
|
|
@@ -365,6 +390,7 @@ describe(
|
|
|
365
390
|
before(async () => {
|
|
366
391
|
db = createPostgresDb()!
|
|
367
392
|
await dropAllTables(db)
|
|
393
|
+
await applyPikkuSchemas(db, SERVICE_SCHEMAS)
|
|
368
394
|
})
|
|
369
395
|
|
|
370
396
|
after(async () => {
|
|
@@ -3,7 +3,7 @@ import type { CoreUserSession } from '@pikku/core/types'
|
|
|
3
3
|
import type { Kysely } from 'kysely'
|
|
4
4
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
5
5
|
import { parseJson } from './kysely-json.js'
|
|
6
|
-
import {
|
|
6
|
+
import { requirePikkuSchema } from './schema/index.js'
|
|
7
7
|
import { sessionSchema } from './schema/session.schema.js'
|
|
8
8
|
|
|
9
9
|
export class KyselySessionStore implements SessionStore {
|
|
@@ -13,7 +13,7 @@ export class KyselySessionStore implements SessionStore {
|
|
|
13
13
|
|
|
14
14
|
public async init(): Promise<void> {
|
|
15
15
|
if (this.initialized) return
|
|
16
|
-
await
|
|
16
|
+
await requirePikkuSchema(this.db, sessionSchema)
|
|
17
17
|
this.initialized = true
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -5,15 +5,7 @@ import { join } from 'node:path'
|
|
|
5
5
|
import { Kysely, SqliteDialect } from 'kysely'
|
|
6
6
|
import Database from 'better-sqlite3'
|
|
7
7
|
|
|
8
|
-
import { applyPikkuSchemas
|
|
9
|
-
import { virtualUserSchema } from './schema/virtual-user.schema.js'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Opt-in schemas that still have an interface in `KyselyPikkuDB`, so the drift
|
|
13
|
-
* check covers them too. A table being opt-in says when it is created, not
|
|
14
|
-
* whether its type is allowed to disagree with its DDL.
|
|
15
|
-
*/
|
|
16
|
-
const OPTIONAL_SCHEMAS = [virtualUserSchema]
|
|
8
|
+
import { applyPikkuSchemas } from './schema/index.js'
|
|
17
9
|
|
|
18
10
|
/**
|
|
19
11
|
* `KyselyPikkuDB` and the schema declaration describe the same tables, and
|
|
@@ -73,9 +65,6 @@ const declaredSchema = async (): Promise<Map<string, Set<string>>> => {
|
|
|
73
65
|
.addColumn('id', 'text', (col) => col.primaryKey())
|
|
74
66
|
.execute()
|
|
75
67
|
await applyPikkuSchemas(db)
|
|
76
|
-
for (const schema of OPTIONAL_SCHEMAS) {
|
|
77
|
-
await ensurePikkuSchema(db, schema)
|
|
78
|
-
}
|
|
79
68
|
|
|
80
69
|
const tables = await db.introspection.getTables()
|
|
81
70
|
return new Map(
|
package/src/kysely-tables.ts
CHANGED
|
@@ -324,6 +324,34 @@ export interface VirtualUserRunTable {
|
|
|
324
324
|
finishedAt: Date | string | null
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
/**
|
|
328
|
+
* One audit event. Every column is text on every engine so a locally-run
|
|
329
|
+
* project and a deployed stage write rows the same reader can read; the JSON
|
|
330
|
+
* columns (`tables`, `changedCols`, `event`, `old`, `data`) are serialised by
|
|
331
|
+
* {@link KyselyAuditService} rather than by a plugin.
|
|
332
|
+
*/
|
|
333
|
+
export interface AuditTable {
|
|
334
|
+
auditId: string
|
|
335
|
+
/** ISO 8601, because string ordering is chronological ordering. */
|
|
336
|
+
occurredAt: string
|
|
337
|
+
type: string
|
|
338
|
+
source: Generated<string>
|
|
339
|
+
outcome: string | null
|
|
340
|
+
functionId: string | null
|
|
341
|
+
wireType: string | null
|
|
342
|
+
traceId: string | null
|
|
343
|
+
transactionId: string | null
|
|
344
|
+
queryId: string | null
|
|
345
|
+
userId: string | null
|
|
346
|
+
orgId: string | null
|
|
347
|
+
pikkuUserId: string | null
|
|
348
|
+
tables: string | null
|
|
349
|
+
changedCols: string | null
|
|
350
|
+
event: string | null
|
|
351
|
+
old: string | null
|
|
352
|
+
data: string | null
|
|
353
|
+
}
|
|
354
|
+
|
|
327
355
|
export interface KyselyPikkuDB {
|
|
328
356
|
pikkuScopes: PikkuScopesTable
|
|
329
357
|
pikkuRoles: PikkuRolesTable
|
|
@@ -354,4 +382,5 @@ export interface KyselyPikkuDB {
|
|
|
354
382
|
webhookDelivery: WebhookDeliveryTable
|
|
355
383
|
webhookDeliveryAttempt: WebhookDeliveryAttemptTable
|
|
356
384
|
virtualUserRun: VirtualUserRunTable
|
|
385
|
+
audit: AuditTable
|
|
357
386
|
}
|
|
@@ -6,6 +6,7 @@ import { CamelCasePlugin, Kysely, SqliteDialect } from 'kysely'
|
|
|
6
6
|
import { KyselyVirtualUserRunStore } from './kysely-virtual-user-run-store.js'
|
|
7
7
|
import { SerializePlugin } from './serialize-plugin.js'
|
|
8
8
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
9
|
+
import { applyPikkuSchemas, virtualUserSchema } from './schema/index.js'
|
|
9
10
|
|
|
10
11
|
// SerializePlugin is here because most projects in this package run with it, and
|
|
11
12
|
// it is the harder case: it deserialises JSON columns the store has already
|
|
@@ -16,6 +17,12 @@ const newDb = () =>
|
|
|
16
17
|
plugins: [new CamelCasePlugin(), new SerializePlugin()],
|
|
17
18
|
})
|
|
18
19
|
|
|
20
|
+
const migratedDb = async () => {
|
|
21
|
+
const db = newDb()
|
|
22
|
+
await applyPikkuSchemas(db, [virtualUserSchema])
|
|
23
|
+
return db
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
const TALLY = {
|
|
20
27
|
steps: 12,
|
|
21
28
|
calls: 30,
|
|
@@ -36,15 +43,23 @@ describe('KyselyVirtualUserRunStore', () => {
|
|
|
36
43
|
let db: Kysely<KyselyPikkuDB>
|
|
37
44
|
let store: KyselyVirtualUserRunStore
|
|
38
45
|
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
db =
|
|
46
|
+
beforeEach(async () => {
|
|
47
|
+
db = await migratedDb()
|
|
41
48
|
store = new KyselyVirtualUserRunStore(db)
|
|
42
49
|
})
|
|
43
50
|
|
|
44
|
-
// The runtime never
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
test('
|
|
51
|
+
// The runtime never creates this table, so a project that skipped the
|
|
52
|
+
// migration has to be told which command writes it rather than quietly
|
|
53
|
+
// getting one at first use.
|
|
54
|
+
test('refuses to start against a database that never migrated it', async () => {
|
|
55
|
+
const store = new KyselyVirtualUserRunStore(newDb())
|
|
56
|
+
await assert.rejects(
|
|
57
|
+
store.start({ persona: 'susan', disposition: 'realistic', seed: 42 }),
|
|
58
|
+
/pikku db generate/
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('writes against a migrated table', async () => {
|
|
48
63
|
const runId = await store.start({
|
|
49
64
|
persona: 'susan',
|
|
50
65
|
disposition: 'realistic',
|
|
@@ -184,6 +199,7 @@ describe('KyselyVirtualUserRunStore', () => {
|
|
|
184
199
|
dialect: new SqliteDialect({ database: new Database(':memory:') }),
|
|
185
200
|
plugins: [new CamelCasePlugin()],
|
|
186
201
|
})
|
|
202
|
+
await applyPikkuSchemas(bare, [virtualUserSchema])
|
|
187
203
|
const bareStore = new KyselyVirtualUserRunStore(bare)
|
|
188
204
|
|
|
189
205
|
const runId = await bareStore.start({
|