@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.
- package/CHANGELOG.md +235 -0
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.js +3 -1
- package/dist/src/kysely-analytics-service.d.ts +30 -0
- package/dist/src/kysely-analytics-service.js +80 -0
- package/dist/src/kysely-feature-flag-store.d.ts +37 -0
- package/dist/src/kysely-feature-flag-store.js +239 -0
- package/dist/src/kysely-tables.d.ts +45 -0
- package/dist/src/schema/analytics.schema.d.ts +17 -0
- package/dist/src/schema/analytics.schema.js +48 -0
- package/dist/src/schema/flag.schema.d.ts +16 -0
- package/dist/src/schema/flag.schema.js +62 -0
- package/dist/src/schema/index.d.ts +2 -0
- package/dist/src/schema/index.js +6 -0
- package/package.json +11 -6
- package/run-tests.sh +9 -1
- package/bin/pikku-kysely-pure.ts +0 -59
- package/dist/src/kysely-lock-vault.d.ts +0 -34
- package/dist/src/kysely-lock-vault.js +0 -77
- package/dist/src/schema/data-lock.schema.d.ts +0 -15
- package/dist/src/schema/data-lock.schema.js +0 -27
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/classification-crypto.test.ts +0 -143
- package/src/classification-crypto.ts +0 -163
- package/src/classification-plugin.pglite.test.ts +0 -226
- package/src/classification-plugin.test.ts +0 -265
- package/src/classification-plugin.ts +0 -249
- package/src/coercion-plugin.test.ts +0 -160
- package/src/coercion-plugin.ts +0 -187
- package/src/create-audited-kysely.test.ts +0 -560
- package/src/create-audited-kysely.ts +0 -254
- package/src/index.ts +0 -81
- package/src/kysely-agent-run-scores.ts +0 -47
- package/src/kysely-agent-run-service.ts +0 -244
- package/src/kysely-agent-run-state-service.ts +0 -200
- package/src/kysely-agent-storage-service.ts +0 -632
- package/src/kysely-audit-service.test.ts +0 -352
- package/src/kysely-audit-service.ts +0 -237
- package/src/kysely-channel-store.ts +0 -107
- package/src/kysely-credential-service.ts +0 -286
- package/src/kysely-deployment-service.ts +0 -163
- package/src/kysely-eventhub-store.ts +0 -53
- package/src/kysely-json.ts +0 -5
- package/src/kysely-scope-service.test.ts +0 -418
- package/src/kysely-scope-service.ts +0 -425
- package/src/kysely-secret-service.ts +0 -242
- package/src/kysely-services.test.ts +0 -405
- package/src/kysely-session-store.ts +0 -57
- package/src/kysely-tables.test.ts +0 -110
- package/src/kysely-tables.ts +0 -434
- package/src/kysely-virtual-user-run-store.test.ts +0 -369
- package/src/kysely-virtual-user-run-store.ts +0 -235
- package/src/kysely-virtual-user-schedule-store.test.ts +0 -187
- package/src/kysely-virtual-user-schedule-store.ts +0 -187
- package/src/kysely-webhook-service.test.ts +0 -153
- package/src/kysely-webhook-service.ts +0 -139
- package/src/kysely-workflow-mirror.test.ts +0 -259
- package/src/kysely-workflow-mirror.ts +0 -374
- package/src/kysely-workflow-run-service.ts +0 -305
- package/src/kysely-workflow-service.test.ts +0 -544
- package/src/kysely-workflow-service.ts +0 -820
- package/src/schema/agent.schema.ts +0 -162
- package/src/schema/audit.schema.ts +0 -57
- package/src/schema/channel.schema.ts +0 -38
- package/src/schema/credential.schema.ts +0 -58
- package/src/schema/deployment.schema.ts +0 -47
- package/src/schema/index.ts +0 -441
- package/src/schema/pikku-schema.types.ts +0 -169
- package/src/schema/schema.test.ts +0 -591
- package/src/schema/scope.schema.ts +0 -110
- package/src/schema/secret.schema.ts +0 -49
- package/src/schema/session.schema.ts +0 -20
- package/src/schema/virtual-user-schedule.schema.ts +0 -48
- package/src/schema/virtual-user.schema.ts +0 -106
- package/src/schema/webhook.schema.ts +0 -58
- package/src/schema/workflow.schema.ts +0 -150
- package/src/serialize-plugin.ts +0 -233
- package/tsconfig.json +0 -17
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely'
|
|
2
|
-
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Encrypted secrets and their audit trail.
|
|
6
|
-
*
|
|
7
|
-
* The audit table is declared unconditionally, where `init()` created it only
|
|
8
|
-
* when the service was constructed with auditing on. A migration cannot depend
|
|
9
|
-
* on runtime configuration, and the asymmetry favours declaring it: an unused
|
|
10
|
-
* table costs nothing, while turning auditing on against a database that never
|
|
11
|
-
* got the table fails on the first write.
|
|
12
|
-
*/
|
|
13
|
-
export const secretSchema: PikkuSchema = {
|
|
14
|
-
name: 'secret',
|
|
15
|
-
statements: [
|
|
16
|
-
(db) =>
|
|
17
|
-
db.schema
|
|
18
|
-
.createTable('secrets')
|
|
19
|
-
.addColumn('key', 'varchar(255)', (col) => col.primaryKey())
|
|
20
|
-
.addColumn('ciphertext', 'text', (col) => col.notNull())
|
|
21
|
-
.addColumn('wrappedDek', 'text', (col) => col.notNull())
|
|
22
|
-
.addColumn('keyVersion', 'integer', (col) => col.notNull())
|
|
23
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
24
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
25
|
-
)
|
|
26
|
-
.addColumn('updatedAt', 'timestamp', (col) =>
|
|
27
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
28
|
-
),
|
|
29
|
-
|
|
30
|
-
(db) =>
|
|
31
|
-
db.schema
|
|
32
|
-
.createTable('secretKekSalts')
|
|
33
|
-
.addColumn('keyVersion', 'integer', (col) => col.primaryKey())
|
|
34
|
-
.addColumn('salt', 'varchar(64)', (col) => col.notNull())
|
|
35
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
36
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
37
|
-
),
|
|
38
|
-
|
|
39
|
-
(db) =>
|
|
40
|
-
db.schema
|
|
41
|
-
.createTable('secretsAudit')
|
|
42
|
-
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
43
|
-
.addColumn('secretKey', 'varchar(255)', (col) => col.notNull())
|
|
44
|
-
.addColumn('action', 'varchar(20)', (col) => col.notNull())
|
|
45
|
-
.addColumn('performedAt', 'timestamp', (col) =>
|
|
46
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
47
|
-
),
|
|
48
|
-
],
|
|
49
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely'
|
|
2
|
-
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
3
|
-
|
|
4
|
-
/** Server-side session storage. */
|
|
5
|
-
export const sessionSchema: PikkuSchema = {
|
|
6
|
-
name: 'session',
|
|
7
|
-
statements: [
|
|
8
|
-
(db) =>
|
|
9
|
-
db.schema
|
|
10
|
-
.createTable('pikkuUserSessions')
|
|
11
|
-
.addColumn('pikkuUserId', 'text', (col) => col.primaryKey())
|
|
12
|
-
.addColumn('session', 'text', (col) => col.notNull())
|
|
13
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
14
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
15
|
-
)
|
|
16
|
-
.addColumn('updatedAt', 'timestamp', (col) =>
|
|
17
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
18
|
-
),
|
|
19
|
-
],
|
|
20
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The `virtualUserSchedule` table {@link KyselyVirtualUserScheduleStore} writes
|
|
5
|
-
* to: one row per persona that keeps using the app on its own.
|
|
6
|
-
*
|
|
7
|
-
* Its own schema rather than a third table in {@link virtualUserSchema}, and
|
|
8
|
-
* owned by its own store: recording what a persona did and deciding that it
|
|
9
|
-
* keeps going on its own are separate opt-ins, and a project that only wants
|
|
10
|
-
* the first should not be handed a cadence table it never writes to.
|
|
11
|
-
*
|
|
12
|
-
* `persona` is the primary key: a persona has one cadence, and a second row for
|
|
13
|
-
* the same one would mean two copies of the same user acting at once — not a
|
|
14
|
-
* heavier test, a different and unreproducible one.
|
|
15
|
-
*/
|
|
16
|
-
export const virtualUserScheduleSchema: PikkuSchema = {
|
|
17
|
-
name: 'virtual-user-schedule',
|
|
18
|
-
ownedBy: ['virtualUserScheduleStore'],
|
|
19
|
-
statements: [
|
|
20
|
-
(db) =>
|
|
21
|
-
db.schema
|
|
22
|
-
.createTable('virtualUserSchedule')
|
|
23
|
-
.addColumn('persona', 'varchar(255)', (col) => col.primaryKey())
|
|
24
|
-
// 0 or 1 rather than a boolean, matching the run tables: a bare sqlite
|
|
25
|
-
// driver cannot bind one, and `SerializePlugin` is not installed
|
|
26
|
-
// everywhere. Off by default, so creating the table spends nothing.
|
|
27
|
-
.addColumn('enabled', 'integer', (col) => col.defaultTo(0).notNull())
|
|
28
|
-
.addColumn('disposition', 'varchar(50)', (col) => col.notNull())
|
|
29
|
-
.addColumn('goals', 'text', (col) => col.defaultTo('[]').notNull())
|
|
30
|
-
.addColumn('budget', 'text')
|
|
31
|
-
// The gap to the next run is drawn between these rather than fixed, so
|
|
32
|
-
// the persona does not keep an appointment.
|
|
33
|
-
.addColumn('minIntervalMs', 'bigint', (col) => col.notNull())
|
|
34
|
-
.addColumn('maxIntervalMs', 'bigint', (col) => col.notNull())
|
|
35
|
-
// The entire schedule. Written before a run is dispatched, so a tick
|
|
36
|
-
// that dies halfway cannot leave the row due for the next one.
|
|
37
|
-
.addColumn('nextRunAt', 'timestamp', (col) => col.notNull())
|
|
38
|
-
.addColumn('lastRunId', 'varchar(36)')
|
|
39
|
-
.addColumn('lastRunAt', 'timestamp'),
|
|
40
|
-
|
|
41
|
-
// The only read that matters at speed: which rows are due, on every tick.
|
|
42
|
-
(db) =>
|
|
43
|
-
db.schema
|
|
44
|
-
.createIndex('idx_virtual_user_schedule_due')
|
|
45
|
-
.on('virtualUserSchedule')
|
|
46
|
-
.columns(['enabled', 'nextRunAt']),
|
|
47
|
-
],
|
|
48
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely'
|
|
2
|
-
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The `virtualUserRun` and `virtualUserRunStep` tables
|
|
6
|
-
* {@link KyselyVirtualUserRunStore} writes to.
|
|
7
|
-
*
|
|
8
|
-
* Deliberately not in `pikkuSchemas`: the runtime never needs this table, only
|
|
9
|
-
* a project that runs virtual users does, and creating it for everyone would
|
|
10
|
-
* put an empty table in every database. `KyselyVirtualUserRunStore.init()`
|
|
11
|
-
* applies it, so it arrives exactly when the thing that fills it does.
|
|
12
|
-
*
|
|
13
|
-
* `persona` is text rather than a foreign key: personas are declared in code
|
|
14
|
-
* with `definePersonas()`, not rows, and a run has to outlive the deletion of
|
|
15
|
-
* the persona it ran as.
|
|
16
|
-
*
|
|
17
|
-
* The JSON columns are text on every engine, like the audit table's, because
|
|
18
|
-
* the store serialises them itself — a project on SQLite and one on Postgres
|
|
19
|
-
* then hold the same bytes, and comparing this week's findings against last
|
|
20
|
-
* week's does not depend on which database they were found in.
|
|
21
|
-
*/
|
|
22
|
-
export const virtualUserSchema: PikkuSchema = {
|
|
23
|
-
name: 'virtual-user',
|
|
24
|
-
ownedBy: ['virtualUserRunStore'],
|
|
25
|
-
statements: [
|
|
26
|
-
(db) =>
|
|
27
|
-
db.schema
|
|
28
|
-
.createTable('virtualUserRun')
|
|
29
|
-
.addColumn('runId', 'varchar(36)', (col) => col.primaryKey())
|
|
30
|
-
.addColumn('persona', 'varchar(255)', (col) => col.notNull())
|
|
31
|
-
.addColumn('disposition', 'varchar(50)', (col) => col.notNull())
|
|
32
|
-
.addColumn('seed', 'bigint', (col) => col.notNull())
|
|
33
|
-
// running | completed | failed. Not derived from `finishedAt` being
|
|
34
|
-
// null: a crashed run has no finish time either, and "crashed" and
|
|
35
|
-
// "still going" are not the same answer.
|
|
36
|
-
.addColumn('status', 'varchar(50)', (col) =>
|
|
37
|
-
col.defaultTo('running').notNull()
|
|
38
|
-
)
|
|
39
|
-
.addColumn('goals', 'text', (col) => col.defaultTo('[]').notNull())
|
|
40
|
-
.addColumn('memory', 'text', (col) => col.defaultTo('{}').notNull())
|
|
41
|
-
.addColumn('findings', 'text', (col) => col.defaultTo('[]').notNull())
|
|
42
|
-
// What the user set out to do and how far each one got. On the run row
|
|
43
|
-
// rather than beside the steps: there are a handful of them, and every
|
|
44
|
-
// read of the run wants them.
|
|
45
|
-
.addColumn('intents', 'text', (col) => col.defaultTo('[]').notNull())
|
|
46
|
-
// Counts the engine kept: steps, calls, mutations, tokens, elapsed.
|
|
47
|
-
.addColumn('tally', 'text')
|
|
48
|
-
.addColumn('stoppedBy', 'varchar(255)')
|
|
49
|
-
// Why the run itself failed, as opposed to what it found.
|
|
50
|
-
.addColumn('error', 'text')
|
|
51
|
-
.addColumn('startedBy', 'varchar(255)')
|
|
52
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
53
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
54
|
-
)
|
|
55
|
-
.addColumn('finishedAt', 'timestamp'),
|
|
56
|
-
|
|
57
|
-
// The two reads: the run list newest-first, and one persona's history when
|
|
58
|
-
// a finding needs comparing against what that persona found before.
|
|
59
|
-
(db) =>
|
|
60
|
-
db.schema
|
|
61
|
-
.createIndex('idx_virtual_user_run_created')
|
|
62
|
-
.on('virtualUserRun')
|
|
63
|
-
.column('createdAt'),
|
|
64
|
-
|
|
65
|
-
(db) =>
|
|
66
|
-
db.schema
|
|
67
|
-
.createIndex('idx_virtual_user_run_persona')
|
|
68
|
-
.on('virtualUserRun')
|
|
69
|
-
.columns(['persona', 'createdAt']),
|
|
70
|
-
|
|
71
|
-
// One row per turn, kept out of `virtualUserRun` so that listing runs does
|
|
72
|
-
// not drag a budget's worth of transcript along with it. No foreign key
|
|
73
|
-
// onto the run for the same reason `persona` is not one: this table is
|
|
74
|
-
// created by the store at boot on whatever engine the project uses, and a
|
|
75
|
-
// cascade that half the engines enforce differently is worse than the read
|
|
76
|
-
// rule that a step with no run is orphaned.
|
|
77
|
-
(db) =>
|
|
78
|
-
db.schema
|
|
79
|
-
.createTable('virtualUserRunStep')
|
|
80
|
-
.addColumn('runId', 'varchar(36)', (col) => col.notNull())
|
|
81
|
-
// The engine's own step number, which is what the intent records point
|
|
82
|
-
// at — not a surrogate key, so a transcript reads the same after paging.
|
|
83
|
-
// Spelled `stepIndex` because `index` is a reserved word in mysql.
|
|
84
|
-
.addColumn('stepIndex', 'integer', (col) => col.notNull())
|
|
85
|
-
.addColumn('intentId', 'varchar(255)')
|
|
86
|
-
// The action as the engine scheduled it, including the `invalid` shape
|
|
87
|
-
// for a turn the model got wrong — that turn is the interesting one.
|
|
88
|
-
.addColumn('action', 'text', (col) => col.notNull())
|
|
89
|
-
.addColumn('status', 'integer')
|
|
90
|
-
// 0 or 1 rather than a boolean, because a bare sqlite driver cannot
|
|
91
|
-
// bind one at all and `SerializePlugin` is not installed everywhere.
|
|
92
|
-
.addColumn('ok', 'integer')
|
|
93
|
-
// Already truncated by the engine, and stored JSON-encoded rather than
|
|
94
|
-
// raw: a truncated API response usually starts with a brace, which
|
|
95
|
-
// `SerializePlugin` would read back as an object rather than the string
|
|
96
|
-
// the engine actually saw.
|
|
97
|
-
.addColumn('response', 'text')
|
|
98
|
-
.addColumn('findingKinds', 'text')
|
|
99
|
-
.addColumn('tokensIn', 'integer', (col) => col.defaultTo(0).notNull())
|
|
100
|
-
.addColumn('tokensOut', 'integer', (col) => col.defaultTo(0).notNull())
|
|
101
|
-
.addPrimaryKeyConstraint('pk_virtual_user_run_step', [
|
|
102
|
-
'runId',
|
|
103
|
-
'stepIndex',
|
|
104
|
-
]),
|
|
105
|
-
],
|
|
106
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely'
|
|
2
|
-
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
3
|
-
|
|
4
|
-
/** Webhook deliveries and one row per delivery attempt. */
|
|
5
|
-
export const webhookSchema: PikkuSchema = {
|
|
6
|
-
name: 'webhook',
|
|
7
|
-
ownedBy: ['webhookService'],
|
|
8
|
-
statements: [
|
|
9
|
-
(db) =>
|
|
10
|
-
db.schema
|
|
11
|
-
.createTable('webhookDelivery')
|
|
12
|
-
.addColumn('deliveryId', 'text', (col) => col.primaryKey())
|
|
13
|
-
.addColumn('organizationId', 'text')
|
|
14
|
-
.addColumn('url', 'text', (col) => col.notNull())
|
|
15
|
-
.addColumn('event', 'text')
|
|
16
|
-
.addColumn('status', 'text', (col) =>
|
|
17
|
-
col.defaultTo('pending').notNull()
|
|
18
|
-
)
|
|
19
|
-
.addColumn('attempts', 'integer', (col) => col.defaultTo(0).notNull())
|
|
20
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
21
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
22
|
-
)
|
|
23
|
-
.addColumn('updatedAt', 'timestamp', (col) =>
|
|
24
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
25
|
-
)
|
|
26
|
-
.addColumn('deliveredAt', 'timestamp'),
|
|
27
|
-
|
|
28
|
-
(db) =>
|
|
29
|
-
db.schema
|
|
30
|
-
.createTable('webhookDeliveryAttempt')
|
|
31
|
-
.addColumn('attemptId', 'text', (col) => col.primaryKey())
|
|
32
|
-
.addColumn('deliveryId', 'text', (col) =>
|
|
33
|
-
col
|
|
34
|
-
.notNull()
|
|
35
|
-
.references('webhookDelivery.deliveryId')
|
|
36
|
-
.onDelete('cascade')
|
|
37
|
-
)
|
|
38
|
-
.addColumn('attemptNumber', 'integer', (col) => col.notNull())
|
|
39
|
-
.addColumn('statusCode', 'integer')
|
|
40
|
-
.addColumn('responseBody', 'text')
|
|
41
|
-
.addColumn('error', 'text')
|
|
42
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
43
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
44
|
-
),
|
|
45
|
-
|
|
46
|
-
(db) =>
|
|
47
|
-
db.schema
|
|
48
|
-
.createIndex('idx_webhook_delivery_org')
|
|
49
|
-
.on('webhookDelivery')
|
|
50
|
-
.column('organizationId'),
|
|
51
|
-
|
|
52
|
-
(db) =>
|
|
53
|
-
db.schema
|
|
54
|
-
.createIndex('idx_webhook_delivery_attempt_delivery')
|
|
55
|
-
.on('webhookDeliveryAttempt')
|
|
56
|
-
.column('deliveryId'),
|
|
57
|
-
],
|
|
58
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely'
|
|
2
|
-
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Workflow runs, their steps, per-step history and the versioned graphs.
|
|
6
|
-
*
|
|
7
|
-
* Two things from the boot-time DDL are deliberately not carried over. The
|
|
8
|
-
* primary keys had `defaultTo(sql.raw("'" + crypto.randomUUID() + "'"))`, which
|
|
9
|
-
* evaluates once while the statement is built — every row would have taken the
|
|
10
|
-
* same default, so it was never a generator; the services supply the id. And
|
|
11
|
-
* `workflowStep.fromStepName`, `workflowStep.currentAttempt` and
|
|
12
|
-
* `workflowStepHistory.attempt` were each backfilled by an
|
|
13
|
-
* `alterTable(...).catch(() => {})` bolted on after the fact, which is the job a
|
|
14
|
-
* migration does — they are declared here and nowhere else.
|
|
15
|
-
*
|
|
16
|
-
* `KyselyWorkflowMirror` created these same four tables separately. There is one
|
|
17
|
-
* declaration now, and both services read it.
|
|
18
|
-
*/
|
|
19
|
-
export const workflowSchema: PikkuSchema = {
|
|
20
|
-
name: 'workflow',
|
|
21
|
-
ownedBy: ['workflowService', 'workflowRunService'],
|
|
22
|
-
statements: [
|
|
23
|
-
(db) =>
|
|
24
|
-
db.schema
|
|
25
|
-
.createTable('workflowRuns')
|
|
26
|
-
.addColumn('workflowRunId', 'text', (col) => col.primaryKey())
|
|
27
|
-
.addColumn('workflow', 'text', (col) => col.notNull())
|
|
28
|
-
.addColumn('status', 'text', (col) => col.notNull())
|
|
29
|
-
.addColumn('input', 'text', (col) => col.notNull())
|
|
30
|
-
.addColumn('output', 'text')
|
|
31
|
-
.addColumn('error', 'text')
|
|
32
|
-
.addColumn('state', 'text', (col) => col.defaultTo('{}'))
|
|
33
|
-
.addColumn('inline', 'boolean', (col) => col.defaultTo(false))
|
|
34
|
-
.addColumn('graphHash', 'text')
|
|
35
|
-
.addColumn('deterministic', 'boolean', (col) => col.defaultTo(false))
|
|
36
|
-
.addColumn('plannedSteps', 'text')
|
|
37
|
-
.addColumn('wire', 'text')
|
|
38
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
39
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
40
|
-
)
|
|
41
|
-
.addColumn('updatedAt', 'timestamp', (col) =>
|
|
42
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
43
|
-
),
|
|
44
|
-
|
|
45
|
-
(db) =>
|
|
46
|
-
db.schema
|
|
47
|
-
.createTable('workflowStep')
|
|
48
|
-
.addColumn('workflowStepId', 'text', (col) => col.primaryKey())
|
|
49
|
-
.addColumn('workflowRunId', 'text', (col) =>
|
|
50
|
-
col
|
|
51
|
-
.notNull()
|
|
52
|
-
.references('workflowRuns.workflowRunId')
|
|
53
|
-
.onDelete('cascade')
|
|
54
|
-
)
|
|
55
|
-
.addColumn('stepName', 'text', (col) => col.notNull())
|
|
56
|
-
.addColumn('rpcName', 'text')
|
|
57
|
-
.addColumn('data', 'text')
|
|
58
|
-
.addColumn('status', 'text', (col) =>
|
|
59
|
-
col.notNull().defaultTo('pending')
|
|
60
|
-
)
|
|
61
|
-
.addColumn('result', 'text')
|
|
62
|
-
.addColumn('error', 'text')
|
|
63
|
-
.addColumn('childRunId', 'text')
|
|
64
|
-
.addColumn('branchTaken', 'text')
|
|
65
|
-
.addColumn('retries', 'integer')
|
|
66
|
-
.addColumn('retryDelay', 'text')
|
|
67
|
-
.addColumn('fromStepName', 'text')
|
|
68
|
-
.addColumn('currentAttempt', 'integer')
|
|
69
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
70
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
71
|
-
)
|
|
72
|
-
.addColumn('updatedAt', 'timestamp', (col) =>
|
|
73
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
74
|
-
)
|
|
75
|
-
.addUniqueConstraint('workflow_step_run_name_unique', [
|
|
76
|
-
'workflowRunId',
|
|
77
|
-
'stepName',
|
|
78
|
-
]),
|
|
79
|
-
|
|
80
|
-
(db) =>
|
|
81
|
-
db.schema
|
|
82
|
-
.createTable('workflowStepHistory')
|
|
83
|
-
.addColumn('historyId', 'text', (col) => col.primaryKey())
|
|
84
|
-
.addColumn('workflowStepId', 'text', (col) =>
|
|
85
|
-
col
|
|
86
|
-
.notNull()
|
|
87
|
-
.references('workflowStep.workflowStepId')
|
|
88
|
-
.onDelete('cascade')
|
|
89
|
-
)
|
|
90
|
-
.addColumn('status', 'text', (col) => col.notNull())
|
|
91
|
-
.addColumn('result', 'text')
|
|
92
|
-
.addColumn('error', 'text')
|
|
93
|
-
.addColumn('attempt', 'integer')
|
|
94
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
95
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
96
|
-
)
|
|
97
|
-
.addColumn('runningAt', 'timestamp')
|
|
98
|
-
.addColumn('scheduledAt', 'timestamp')
|
|
99
|
-
.addColumn('succeededAt', 'timestamp')
|
|
100
|
-
.addColumn('failedAt', 'timestamp'),
|
|
101
|
-
|
|
102
|
-
(db) =>
|
|
103
|
-
db.schema
|
|
104
|
-
.createTable('workflowVersions')
|
|
105
|
-
.addColumn('workflowName', 'text', (col) => col.notNull())
|
|
106
|
-
.addColumn('graphHash', 'text', (col) => col.notNull())
|
|
107
|
-
.addColumn('graph', 'text', (col) => col.notNull())
|
|
108
|
-
.addColumn('source', 'text', (col) => col.notNull())
|
|
109
|
-
.addColumn('status', 'text', (col) => col.notNull().defaultTo('active'))
|
|
110
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
111
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
112
|
-
)
|
|
113
|
-
.addPrimaryKeyConstraint('workflow_versions_pk', [
|
|
114
|
-
'workflowName',
|
|
115
|
-
'graphHash',
|
|
116
|
-
]),
|
|
117
|
-
|
|
118
|
-
// The indexes trail the tables so that a table is never indexed before it
|
|
119
|
-
// exists, whatever order the declarations are read in.
|
|
120
|
-
(db) =>
|
|
121
|
-
db.schema
|
|
122
|
-
.createIndex('idx_workflow_runs_status_created')
|
|
123
|
-
.on('workflowRuns')
|
|
124
|
-
.columns(['status', 'createdAt']),
|
|
125
|
-
|
|
126
|
-
(db) =>
|
|
127
|
-
db.schema
|
|
128
|
-
.createIndex('idx_workflow_runs_workflow_created')
|
|
129
|
-
.on('workflowRuns')
|
|
130
|
-
.columns(['workflow', 'createdAt']),
|
|
131
|
-
|
|
132
|
-
(db) =>
|
|
133
|
-
db.schema
|
|
134
|
-
.createIndex('idx_workflow_step_run_status')
|
|
135
|
-
.on('workflowStep')
|
|
136
|
-
.columns(['workflowRunId', 'status']),
|
|
137
|
-
|
|
138
|
-
(db) =>
|
|
139
|
-
db.schema
|
|
140
|
-
.createIndex('idx_workflow_step_history_step')
|
|
141
|
-
.on('workflowStepHistory')
|
|
142
|
-
.columns(['workflowStepId', 'createdAt']),
|
|
143
|
-
|
|
144
|
-
(db) =>
|
|
145
|
-
db.schema
|
|
146
|
-
.createIndex('idx_workflow_versions_source_status')
|
|
147
|
-
.on('workflowVersions')
|
|
148
|
-
.columns(['source', 'status']),
|
|
149
|
-
],
|
|
150
|
-
}
|
package/src/serialize-plugin.ts
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Serialize / deserialize plugin for Kysely.
|
|
3
|
-
*
|
|
4
|
-
* Adapted from `kysely-plugin-serialize` (MIT, by subframe7536 —
|
|
5
|
-
* https://github.com/subframe7536/kysely-plugin-serialize), which is
|
|
6
|
-
* unmaintained and ships no `exports` map, so modern Node16/ESM resolution
|
|
7
|
-
* falls back to its CommonJS build and cannot import ESM-only kysely (>= 0.29).
|
|
8
|
-
* Maintained here directly as part of @pikku/kysely so we publish it ourselves.
|
|
9
|
-
*
|
|
10
|
-
* Serializes object/array/Date/boolean parameters to strings on the way into
|
|
11
|
-
* SQLite-style dialects, and deserializes them back on the way out.
|
|
12
|
-
*/
|
|
13
|
-
import {
|
|
14
|
-
OperationNodeTransformer,
|
|
15
|
-
type ColumnUpdateNode,
|
|
16
|
-
type KyselyPlugin,
|
|
17
|
-
type PluginTransformQueryArgs,
|
|
18
|
-
type PluginTransformResultArgs,
|
|
19
|
-
type PrimitiveValueListNode,
|
|
20
|
-
type QueryId,
|
|
21
|
-
type QueryResult,
|
|
22
|
-
type RootOperationNode,
|
|
23
|
-
type UnknownRow,
|
|
24
|
-
type ValueNode,
|
|
25
|
-
} from 'kysely'
|
|
26
|
-
|
|
27
|
-
export type Serializer = (parameter: unknown) => unknown
|
|
28
|
-
export type Deserializer = (parameter: unknown) => unknown
|
|
29
|
-
|
|
30
|
-
class SerializeParametersTransformer extends OperationNodeTransformer {
|
|
31
|
-
private readonly serializer: Serializer
|
|
32
|
-
|
|
33
|
-
constructor(serializer: Serializer) {
|
|
34
|
-
super()
|
|
35
|
-
this.serializer = serializer
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
protected override transformPrimitiveValueList(
|
|
39
|
-
node: PrimitiveValueListNode
|
|
40
|
-
): PrimitiveValueListNode {
|
|
41
|
-
return {
|
|
42
|
-
...node,
|
|
43
|
-
values: node.values.map(this.serializer),
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// https://www.npmjs.com/package/zodsql
|
|
48
|
-
protected override transformColumnUpdate(
|
|
49
|
-
node: ColumnUpdateNode
|
|
50
|
-
): ColumnUpdateNode {
|
|
51
|
-
const { value: valueNode } = node
|
|
52
|
-
if (valueNode.kind !== 'ValueNode') {
|
|
53
|
-
return super.transformColumnUpdate(node)
|
|
54
|
-
}
|
|
55
|
-
const { value, ...item } = valueNode as ValueNode
|
|
56
|
-
const serializedValue = this.serializer(value)
|
|
57
|
-
if (value === serializedValue) {
|
|
58
|
-
return super.transformColumnUpdate(node)
|
|
59
|
-
}
|
|
60
|
-
const serializedNode: ValueNode = { ...item, value: serializedValue }
|
|
61
|
-
return super.transformColumnUpdate({ ...node, value: serializedNode })
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
protected override transformValue(node: ValueNode): ValueNode {
|
|
65
|
-
return {
|
|
66
|
-
...node,
|
|
67
|
-
value: this.serializer(node.value),
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export const defaultSerializer: Serializer = (parameter) => {
|
|
73
|
-
if (skipTransform(parameter) || typeof parameter === 'string') {
|
|
74
|
-
return parameter
|
|
75
|
-
} else if (typeof parameter === 'boolean') {
|
|
76
|
-
return '' + parameter
|
|
77
|
-
} else if (parameter instanceof Date) {
|
|
78
|
-
return parameter.toISOString()
|
|
79
|
-
} else {
|
|
80
|
-
try {
|
|
81
|
-
return JSON.stringify(parameter)
|
|
82
|
-
} catch {
|
|
83
|
-
return parameter
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export const dateRegex = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/
|
|
89
|
-
|
|
90
|
-
export const defaultDeserializer: Deserializer = (parameter) => {
|
|
91
|
-
if (skipTransform(parameter)) {
|
|
92
|
-
return parameter
|
|
93
|
-
}
|
|
94
|
-
if (typeof parameter === 'string') {
|
|
95
|
-
if (parameter === 'true') {
|
|
96
|
-
return true
|
|
97
|
-
} else if (parameter === 'false') {
|
|
98
|
-
return false
|
|
99
|
-
} else if (dateRegex.test(parameter)) {
|
|
100
|
-
return new Date(parameter)
|
|
101
|
-
} else if (maybeJson(parameter)) {
|
|
102
|
-
try {
|
|
103
|
-
return JSON.parse(parameter)
|
|
104
|
-
} catch {
|
|
105
|
-
// not valid JSON — fall through and return the raw string
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return parameter
|
|
109
|
-
}
|
|
110
|
-
return parameter
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Checks if a given string parameter is a JSON-like string.
|
|
115
|
-
*
|
|
116
|
-
* Determines whether the input starts and ends with curly braces `{}` or
|
|
117
|
-
* square brackets `[]`, the typical indicators of JSON objects and arrays.
|
|
118
|
-
*/
|
|
119
|
-
export function maybeJson(parameter: string): boolean {
|
|
120
|
-
return (
|
|
121
|
-
(parameter.startsWith('{') && parameter.endsWith('}')) ||
|
|
122
|
-
(parameter.startsWith('[') && parameter.endsWith(']'))
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Determines whether a given parameter should be skipped during transformation.
|
|
128
|
-
*
|
|
129
|
-
* Skipped when it is `undefined`, `null`, a `bigint`, a `number`, or an object
|
|
130
|
-
* that exposes a `buffer` property (e.g. a typed array / Buffer).
|
|
131
|
-
*/
|
|
132
|
-
export function skipTransform(parameter: unknown): boolean {
|
|
133
|
-
return (
|
|
134
|
-
parameter === undefined ||
|
|
135
|
-
parameter === null ||
|
|
136
|
-
typeof parameter === 'bigint' ||
|
|
137
|
-
typeof parameter === 'number' ||
|
|
138
|
-
(typeof parameter === 'object' && 'buffer' in parameter)
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export class BaseSerializePlugin implements KyselyPlugin {
|
|
143
|
-
private readonly transformer: SerializeParametersTransformer
|
|
144
|
-
private readonly deserializer: Deserializer
|
|
145
|
-
private readonly skipNodeSet?: Set<RootOperationNode['kind']>
|
|
146
|
-
private readonly ctx?: WeakSet<QueryId>
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Base class for {@link SerializePlugin}, without default options.
|
|
150
|
-
*/
|
|
151
|
-
constructor(
|
|
152
|
-
serializer: Serializer,
|
|
153
|
-
deserializer: Deserializer,
|
|
154
|
-
skipNodeKind: Array<RootOperationNode['kind']>
|
|
155
|
-
) {
|
|
156
|
-
this.transformer = new SerializeParametersTransformer(serializer)
|
|
157
|
-
this.deserializer = deserializer
|
|
158
|
-
if (skipNodeKind.length) {
|
|
159
|
-
this.skipNodeSet = new Set(skipNodeKind)
|
|
160
|
-
this.ctx = new WeakSet()
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
transformQuery({
|
|
165
|
-
node,
|
|
166
|
-
queryId,
|
|
167
|
-
}: PluginTransformQueryArgs): RootOperationNode {
|
|
168
|
-
if (this.skipNodeSet?.has(node.kind)) {
|
|
169
|
-
this.ctx?.add(queryId)
|
|
170
|
-
return node
|
|
171
|
-
}
|
|
172
|
-
return this.transformer.transformNode(node)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async transformResult({
|
|
176
|
-
result,
|
|
177
|
-
queryId,
|
|
178
|
-
}: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>> {
|
|
179
|
-
return this.ctx?.has(queryId)
|
|
180
|
-
? result
|
|
181
|
-
: { ...result, rows: this.parseRows(result.rows) }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private parseRows(rows: UnknownRow[]): UnknownRow[] {
|
|
185
|
-
const result: UnknownRow[] = []
|
|
186
|
-
for (const row of rows) {
|
|
187
|
-
if (!row) {
|
|
188
|
-
continue
|
|
189
|
-
}
|
|
190
|
-
const parsedRow: UnknownRow = {}
|
|
191
|
-
for (const [key, value] of Object.entries(row)) {
|
|
192
|
-
parsedRow[key] = this.deserializer(value)
|
|
193
|
-
}
|
|
194
|
-
result.push(parsedRow)
|
|
195
|
-
}
|
|
196
|
-
return result
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export interface SerializePluginOptions {
|
|
201
|
-
/**
|
|
202
|
-
* serialize params
|
|
203
|
-
*/
|
|
204
|
-
serializer?: Serializer
|
|
205
|
-
/**
|
|
206
|
-
* deserialize params
|
|
207
|
-
*/
|
|
208
|
-
deserializer?: Deserializer
|
|
209
|
-
/**
|
|
210
|
-
* node kind to skip transform
|
|
211
|
-
*/
|
|
212
|
-
skipNodeKind?: Array<RootOperationNode['kind']>
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export class SerializePlugin extends BaseSerializePlugin {
|
|
216
|
-
/**
|
|
217
|
-
* _**THIS PLUGIN SHOULD BE PLACED AT THE END OF PLUGINS ARRAY !!!**_
|
|
218
|
-
*
|
|
219
|
-
* reference from https://github.com/koskimas/kysely/pull/138
|
|
220
|
-
*
|
|
221
|
-
* Serializes object/array/Date/boolean column values to strings for
|
|
222
|
-
* SQLite-style dialects and deserializes them on the way back out. Pass a
|
|
223
|
-
* custom `serializer`/`deserializer` to override the default behaviour.
|
|
224
|
-
*/
|
|
225
|
-
constructor(options: SerializePluginOptions = {}) {
|
|
226
|
-
const {
|
|
227
|
-
deserializer = defaultDeserializer,
|
|
228
|
-
serializer = defaultSerializer,
|
|
229
|
-
skipNodeKind = [],
|
|
230
|
-
} = options
|
|
231
|
-
super(serializer, deserializer, skipNodeKind)
|
|
232
|
-
}
|
|
233
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": ".",
|
|
5
|
-
"module": "Node18",
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"target": "esnext",
|
|
8
|
-
"declaration": true
|
|
9
|
-
},
|
|
10
|
-
"include": ["src/**/*.ts", "bin/**/*.ts", "bin/pikku-kysely-pure.ts"],
|
|
11
|
-
"exclude": ["**/*.test.ts", "node_modules", "bin/dist"],
|
|
12
|
-
"references": [
|
|
13
|
-
{
|
|
14
|
-
"path": "../../core/tsconfig.json"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|