@pikku/kysely 0.13.2 → 0.13.4
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 +56 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/kysely-ai-run-state-service.js +3 -18
- package/dist/src/kysely-ai-storage-service.d.ts +0 -1
- package/dist/src/kysely-ai-storage-service.js +4 -101
- package/dist/src/kysely-channel-store.js +4 -24
- package/dist/src/kysely-credential-service.js +3 -24
- package/dist/src/kysely-deployment-service.d.ts +0 -1
- package/dist/src/kysely-deployment-service.js +4 -48
- package/dist/src/kysely-scope-service.js +4 -54
- package/dist/src/kysely-secret-service.js +3 -21
- package/dist/src/kysely-session-store.js +4 -11
- package/dist/src/kysely-tables.d.ts +2 -0
- package/dist/src/kysely-webhook-service.d.ts +0 -1
- package/dist/src/kysely-webhook-service.js +4 -51
- package/dist/src/kysely-workflow-mirror.d.ts +7 -3
- package/dist/src/kysely-workflow-mirror.js +10 -87
- package/dist/src/kysely-workflow-service.d.ts +2 -2
- package/dist/src/kysely-workflow-service.js +11 -95
- package/dist/src/schema/ai.schema.d.ts +12 -0
- package/dist/src/schema/ai.schema.js +89 -0
- package/dist/src/schema/channel.schema.d.ts +3 -0
- package/dist/src/schema/channel.schema.js +24 -0
- package/dist/src/schema/credential.schema.d.ts +11 -0
- package/dist/src/schema/credential.schema.js +33 -0
- package/dist/src/schema/deployment.schema.d.ts +3 -0
- package/dist/src/schema/deployment.schema.js +32 -0
- package/dist/src/schema/index.d.ts +82 -0
- package/dist/src/schema/index.js +187 -0
- package/dist/src/schema/pikku-schema.types.d.ts +75 -0
- package/dist/src/schema/pikku-schema.types.js +21 -0
- package/dist/src/schema/scope.schema.d.ts +18 -0
- package/dist/src/schema/scope.schema.js +61 -0
- package/dist/src/schema/secret.schema.d.ts +11 -0
- package/dist/src/schema/secret.schema.js +29 -0
- package/dist/src/schema/session.schema.d.ts +3 -0
- package/dist/src/schema/session.schema.js +13 -0
- package/dist/src/schema/webhook.schema.d.ts +3 -0
- package/dist/src/schema/webhook.schema.js +38 -0
- package/dist/src/schema/workflow.schema.d.ts +15 -0
- package/dist/src/schema/workflow.schema.js +86 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +18 -0
- package/src/kysely-ai-run-state-service.ts +7 -28
- package/src/kysely-ai-storage-service.ts +4 -152
- package/src/kysely-channel-store.ts +4 -34
- package/src/kysely-credential-service.ts +3 -36
- package/src/kysely-deployment-service.ts +4 -63
- package/src/kysely-scope-service.ts +4 -81
- package/src/kysely-secret-service.ts +6 -33
- package/src/kysely-session-store.ts +4 -18
- package/src/kysely-tables.test.ts +110 -0
- package/src/kysely-tables.ts +2 -0
- package/src/kysely-webhook-service.ts +4 -67
- package/src/kysely-workflow-mirror.test.ts +7 -7
- package/src/kysely-workflow-mirror.ts +10 -109
- package/src/kysely-workflow-service.ts +18 -125
- package/src/schema/ai.schema.ts +140 -0
- package/src/schema/channel.schema.ts +37 -0
- package/src/schema/credential.schema.ts +46 -0
- package/src/schema/deployment.schema.ts +47 -0
- package/src/schema/index.ts +249 -0
- package/src/schema/pikku-schema.types.ts +94 -0
- package/src/schema/schema.test.ts +273 -0
- package/src/schema/scope.schema.ts +95 -0
- package/src/schema/secret.schema.ts +40 -0
- package/src/schema/session.schema.ts +20 -0
- package/src/schema/webhook.schema.ts +57 -0
- package/src/schema/workflow.schema.ts +113 -0
|
@@ -3,6 +3,8 @@ import type { Kysely } from 'kysely'
|
|
|
3
3
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
4
4
|
import type { AIRunStateService, CreateRunInput } from '@pikku/core/services'
|
|
5
5
|
import type { AgentRunState, PendingApproval } from '@pikku/core/ai-agent'
|
|
6
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
7
|
+
import { aiSchema } from './schema/ai.schema.js'
|
|
6
8
|
|
|
7
9
|
export class KyselyAIRunStateService implements AIRunStateService {
|
|
8
10
|
private initialized = false
|
|
@@ -11,30 +13,7 @@ export class KyselyAIRunStateService implements AIRunStateService {
|
|
|
11
13
|
|
|
12
14
|
async init(): Promise<void> {
|
|
13
15
|
if (this.initialized) return
|
|
14
|
-
|
|
15
|
-
await this.db.schema
|
|
16
|
-
.createTable('aiRun')
|
|
17
|
-
.ifNotExists()
|
|
18
|
-
.addColumn('runId', 'text', (col) => col.primaryKey())
|
|
19
|
-
.addColumn('agentName', 'text', (col) => col.notNull())
|
|
20
|
-
.addColumn('threadId', 'text', (col) => col.notNull())
|
|
21
|
-
.addColumn('resourceId', 'text', (col) => col.notNull())
|
|
22
|
-
.addColumn('status', 'text', (col) => col.defaultTo('running').notNull())
|
|
23
|
-
.addColumn('errorMessage', 'text')
|
|
24
|
-
.addColumn('suspendReason', 'text')
|
|
25
|
-
.addColumn('missingRpcs', 'text')
|
|
26
|
-
.addColumn('pendingApprovals', 'text')
|
|
27
|
-
.addColumn('usageInputTokens', 'integer', (col) => col.defaultTo(0))
|
|
28
|
-
.addColumn('usageOutputTokens', 'integer', (col) => col.defaultTo(0))
|
|
29
|
-
.addColumn('usageModel', 'text', (col) => col.defaultTo(''))
|
|
30
|
-
.addColumn('createdAt', 'timestamp', (col) =>
|
|
31
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
32
|
-
)
|
|
33
|
-
.addColumn('updatedAt', 'timestamp', (col) =>
|
|
34
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
35
|
-
)
|
|
36
|
-
.execute()
|
|
37
|
-
|
|
16
|
+
await ensurePikkuSchema(this.db, aiSchema)
|
|
38
17
|
this.initialized = true
|
|
39
18
|
}
|
|
40
19
|
|
|
@@ -113,7 +92,7 @@ export class KyselyAIRunStateService implements AIRunStateService {
|
|
|
113
92
|
): Promise<void> {
|
|
114
93
|
const rows = await this.db
|
|
115
94
|
.selectFrom('aiRun')
|
|
116
|
-
.select(['runId', 'pendingApprovals'
|
|
95
|
+
.select(['runId', 'pendingApprovals'])
|
|
117
96
|
.where('status', '=', 'suspended')
|
|
118
97
|
.execute()
|
|
119
98
|
|
|
@@ -121,7 +100,7 @@ export class KyselyAIRunStateService implements AIRunStateService {
|
|
|
121
100
|
let approvals: PendingApproval[] = []
|
|
122
101
|
if (row.pendingApprovals) {
|
|
123
102
|
try {
|
|
124
|
-
approvals = JSON.parse(row.pendingApprovals
|
|
103
|
+
approvals = JSON.parse(row.pendingApprovals)
|
|
125
104
|
} catch {
|
|
126
105
|
console.warn(
|
|
127
106
|
`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`
|
|
@@ -159,9 +138,9 @@ export class KyselyAIRunStateService implements AIRunStateService {
|
|
|
159
138
|
|
|
160
139
|
for (const row of rows) {
|
|
161
140
|
let approvals: PendingApproval[] = []
|
|
162
|
-
if (
|
|
141
|
+
if (row.pendingApprovals) {
|
|
163
142
|
try {
|
|
164
|
-
approvals = JSON.parse(
|
|
143
|
+
approvals = JSON.parse(row.pendingApprovals)
|
|
165
144
|
} catch {
|
|
166
145
|
console.warn(
|
|
167
146
|
`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`
|
|
@@ -5,9 +5,10 @@ import type {
|
|
|
5
5
|
} from '@pikku/core/services'
|
|
6
6
|
import type { AIThread, AIMessage, AgentRunState } from '@pikku/core/ai-agent'
|
|
7
7
|
import type { Kysely } from 'kysely'
|
|
8
|
-
import { sql } from 'kysely'
|
|
9
8
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
10
9
|
import { parseJson } from './kysely-json.js'
|
|
10
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
11
|
+
import { aiSchema } from './schema/ai.schema.js'
|
|
11
12
|
|
|
12
13
|
export class KyselyAIStorageService
|
|
13
14
|
implements AIStorageService, AIRunStateService
|
|
@@ -16,158 +17,9 @@ export class KyselyAIStorageService
|
|
|
16
17
|
|
|
17
18
|
constructor(private db: Kysely<KyselyPikkuDB>) {}
|
|
18
19
|
|
|
19
|
-
private async createIndexSafe(builder: {
|
|
20
|
-
execute(): Promise<void>
|
|
21
|
-
}): Promise<void> {
|
|
22
|
-
try {
|
|
23
|
-
await builder.execute()
|
|
24
|
-
} catch (e: any) {
|
|
25
|
-
// Ignore "index already exists" errors across databases
|
|
26
|
-
// MySQL: ER_DUP_KEYNAME, Postgres: 42P07, SQLite: "already exists"
|
|
27
|
-
if (e?.code === 'ER_DUP_KEYNAME' || e?.errno === 1061) return
|
|
28
|
-
if (e?.code === '42P07') return
|
|
29
|
-
if (e?.message?.includes('already exists')) return
|
|
30
|
-
throw e
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
20
|
public async init(): Promise<void> {
|
|
35
|
-
if (this.initialized)
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
await this.db.schema
|
|
40
|
-
.createTable('ai_threads')
|
|
41
|
-
.ifNotExists()
|
|
42
|
-
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
43
|
-
.addColumn('resource_id', 'varchar(255)', (col) => col.notNull())
|
|
44
|
-
.addColumn('title', 'text')
|
|
45
|
-
.addColumn('metadata', 'text')
|
|
46
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
47
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
48
|
-
)
|
|
49
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
50
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
51
|
-
)
|
|
52
|
-
.execute()
|
|
53
|
-
|
|
54
|
-
await this.createIndexSafe(
|
|
55
|
-
this.db.schema
|
|
56
|
-
.createIndex('idx_ai_threads_resource')
|
|
57
|
-
.on('ai_threads')
|
|
58
|
-
.column('resource_id')
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
await this.db.schema
|
|
62
|
-
.createTable('ai_message')
|
|
63
|
-
.ifNotExists()
|
|
64
|
-
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
65
|
-
.addColumn('thread_id', 'varchar(36)', (col) =>
|
|
66
|
-
col.notNull().references('ai_threads.id').onDelete('cascade')
|
|
67
|
-
)
|
|
68
|
-
.addColumn('role', 'varchar(50)', (col) => col.notNull())
|
|
69
|
-
.addColumn('content', 'text')
|
|
70
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
71
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
72
|
-
)
|
|
73
|
-
.execute()
|
|
74
|
-
|
|
75
|
-
await this.createIndexSafe(
|
|
76
|
-
this.db.schema
|
|
77
|
-
.createIndex('idx_ai_message_thread')
|
|
78
|
-
.on('ai_message')
|
|
79
|
-
.columns(['thread_id', 'created_at'])
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
await this.db.schema
|
|
83
|
-
.createTable('ai_tool_call')
|
|
84
|
-
.ifNotExists()
|
|
85
|
-
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
86
|
-
.addColumn('thread_id', 'varchar(36)', (col) =>
|
|
87
|
-
col.notNull().references('ai_threads.id').onDelete('cascade')
|
|
88
|
-
)
|
|
89
|
-
.addColumn('message_id', 'varchar(36)', (col) =>
|
|
90
|
-
col.notNull().references('ai_message.id').onDelete('cascade')
|
|
91
|
-
)
|
|
92
|
-
.addColumn('run_id', 'varchar(36)')
|
|
93
|
-
.addColumn('tool_name', 'varchar(255)', (col) => col.notNull())
|
|
94
|
-
.addColumn('args', 'text', (col) => col.notNull())
|
|
95
|
-
.addColumn('result', 'text')
|
|
96
|
-
.addColumn('approval_status', 'varchar(50)')
|
|
97
|
-
.addColumn('approval_type', 'varchar(50)')
|
|
98
|
-
.addColumn('agent_run_id', 'varchar(36)')
|
|
99
|
-
.addColumn('display_tool_name', 'varchar(255)')
|
|
100
|
-
.addColumn('display_args', 'text')
|
|
101
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
102
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
103
|
-
)
|
|
104
|
-
.execute()
|
|
105
|
-
|
|
106
|
-
await this.createIndexSafe(
|
|
107
|
-
this.db.schema
|
|
108
|
-
.createIndex('idx_ai_tool_call_thread')
|
|
109
|
-
.on('ai_tool_call')
|
|
110
|
-
.column('thread_id')
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
await this.createIndexSafe(
|
|
114
|
-
this.db.schema
|
|
115
|
-
.createIndex('idx_ai_tool_call_message')
|
|
116
|
-
.on('ai_tool_call')
|
|
117
|
-
.column('message_id')
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
await this.db.schema
|
|
121
|
-
.createTable('ai_working_memory')
|
|
122
|
-
.ifNotExists()
|
|
123
|
-
.addColumn('id', 'varchar(255)', (col) => col.notNull())
|
|
124
|
-
.addColumn('scope', 'varchar(50)', (col) => col.notNull())
|
|
125
|
-
.addColumn('data', 'text', (col) => col.notNull())
|
|
126
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
127
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
128
|
-
)
|
|
129
|
-
.addPrimaryKeyConstraint('ai_working_memory_pk', ['id', 'scope'])
|
|
130
|
-
.execute()
|
|
131
|
-
|
|
132
|
-
await this.db.schema
|
|
133
|
-
.createTable('ai_run')
|
|
134
|
-
.ifNotExists()
|
|
135
|
-
.addColumn('run_id', 'varchar(36)', (col) => col.primaryKey())
|
|
136
|
-
.addColumn('agent_name', 'varchar(255)', (col) => col.notNull())
|
|
137
|
-
.addColumn('thread_id', 'varchar(36)', (col) =>
|
|
138
|
-
col.notNull().references('ai_threads.id').onDelete('cascade')
|
|
139
|
-
)
|
|
140
|
-
.addColumn('resource_id', 'varchar(255)', (col) => col.notNull())
|
|
141
|
-
.addColumn('status', 'varchar(50)', (col) =>
|
|
142
|
-
col.notNull().defaultTo('running')
|
|
143
|
-
)
|
|
144
|
-
.addColumn('error_message', 'text')
|
|
145
|
-
.addColumn('suspend_reason', 'text')
|
|
146
|
-
.addColumn('missing_rpcs', 'text')
|
|
147
|
-
.addColumn('usage_input_tokens', 'integer', (col) =>
|
|
148
|
-
col.notNull().defaultTo(0)
|
|
149
|
-
)
|
|
150
|
-
.addColumn('usage_output_tokens', 'integer', (col) =>
|
|
151
|
-
col.notNull().defaultTo(0)
|
|
152
|
-
)
|
|
153
|
-
.addColumn('usage_model', 'varchar(255)', (col) =>
|
|
154
|
-
col.notNull().defaultTo('')
|
|
155
|
-
)
|
|
156
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
157
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
158
|
-
)
|
|
159
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
160
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
161
|
-
)
|
|
162
|
-
.execute()
|
|
163
|
-
|
|
164
|
-
await this.createIndexSafe(
|
|
165
|
-
this.db.schema
|
|
166
|
-
.createIndex('idx_ai_run_thread')
|
|
167
|
-
.on('ai_run')
|
|
168
|
-
.columns(['thread_id', 'created_at'])
|
|
169
|
-
)
|
|
170
|
-
|
|
21
|
+
if (this.initialized) return
|
|
22
|
+
await ensurePikkuSchema(this.db, aiSchema)
|
|
171
23
|
this.initialized = true
|
|
172
24
|
}
|
|
173
25
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Channel } from '@pikku/core/channel'
|
|
2
2
|
import { ChannelStore } from '@pikku/core/channel'
|
|
3
3
|
import type { Kysely } from 'kysely'
|
|
4
|
-
import { sql } from 'kysely'
|
|
5
4
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
6
5
|
import { parseJson } from './kysely-json.js'
|
|
6
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
7
|
+
import { channelSchema } from './schema/channel.schema.js'
|
|
7
8
|
|
|
8
9
|
export class KyselyChannelStore extends ChannelStore {
|
|
9
10
|
private initialized = false
|
|
@@ -13,39 +14,8 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
public async init(): Promise<void> {
|
|
16
|
-
if (this.initialized)
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
await this.db.schema
|
|
21
|
-
.createTable('channels')
|
|
22
|
-
.ifNotExists()
|
|
23
|
-
.addColumn('channel_id', 'text', (col) => col.primaryKey())
|
|
24
|
-
.addColumn('channel_name', 'text', (col) => col.notNull())
|
|
25
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
26
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
27
|
-
)
|
|
28
|
-
.addColumn('opening_data', 'text', (col) => col.notNull().defaultTo('{}'))
|
|
29
|
-
.addColumn('pikku_user_id', 'text')
|
|
30
|
-
.addColumn('state', 'text')
|
|
31
|
-
.addColumn('last_wire', 'timestamp', (col) =>
|
|
32
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
33
|
-
)
|
|
34
|
-
.execute()
|
|
35
|
-
|
|
36
|
-
await this.db.schema
|
|
37
|
-
.createTable('channel_subscriptions')
|
|
38
|
-
.ifNotExists()
|
|
39
|
-
.addColumn('channel_id', 'text', (col) =>
|
|
40
|
-
col.notNull().references('channels.channel_id').onDelete('cascade')
|
|
41
|
-
)
|
|
42
|
-
.addColumn('topic', 'text', (col) => col.notNull())
|
|
43
|
-
.addPrimaryKeyConstraint('channel_subscriptions_pk', [
|
|
44
|
-
'channel_id',
|
|
45
|
-
'topic',
|
|
46
|
-
])
|
|
47
|
-
.execute()
|
|
48
|
-
|
|
17
|
+
if (this.initialized) return
|
|
18
|
+
await ensurePikkuSchema(this.db, channelSchema)
|
|
49
19
|
this.initialized = true
|
|
50
20
|
}
|
|
51
21
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { CredentialService } from '@pikku/core/services'
|
|
2
2
|
import type { Kysely } from 'kysely'
|
|
3
|
-
import { sql } from 'kysely'
|
|
4
3
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
5
4
|
import {
|
|
6
5
|
envelopeEncrypt,
|
|
7
6
|
envelopeDecrypt,
|
|
8
7
|
envelopeRewrap,
|
|
9
8
|
} from '@pikku/core/crypto-utils'
|
|
9
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
10
|
+
import { credentialSchema } from './schema/credential.schema.js'
|
|
10
11
|
|
|
11
12
|
export interface KyselyCredentialServiceConfig {
|
|
12
13
|
key: string
|
|
@@ -37,41 +38,7 @@ export class KyselyCredentialService implements CredentialService {
|
|
|
37
38
|
|
|
38
39
|
public async init(): Promise<void> {
|
|
39
40
|
if (this.initialized) return
|
|
40
|
-
|
|
41
|
-
await this.db.schema
|
|
42
|
-
.createTable('credentials')
|
|
43
|
-
.ifNotExists()
|
|
44
|
-
.addColumn('name', 'varchar(255)', (col) => col.notNull())
|
|
45
|
-
.addColumn('user_id', 'varchar(255)')
|
|
46
|
-
.addColumn('ciphertext', 'text', (col) => col.notNull())
|
|
47
|
-
.addColumn('wrapped_dek', 'text', (col) => col.notNull())
|
|
48
|
-
.addColumn('key_version', 'integer', (col) => col.notNull())
|
|
49
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
50
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
51
|
-
)
|
|
52
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
53
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
54
|
-
)
|
|
55
|
-
.execute()
|
|
56
|
-
|
|
57
|
-
await sql`CREATE UNIQUE INDEX IF NOT EXISTS credentials_name_user_id_unique ON credentials (name, COALESCE(user_id, ''))`.execute(
|
|
58
|
-
this.db
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
if (this.audit) {
|
|
62
|
-
await this.db.schema
|
|
63
|
-
.createTable('credentials_audit')
|
|
64
|
-
.ifNotExists()
|
|
65
|
-
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
66
|
-
.addColumn('credential_name', 'varchar(255)', (col) => col.notNull())
|
|
67
|
-
.addColumn('user_id', 'varchar(255)')
|
|
68
|
-
.addColumn('action', 'varchar(20)', (col) => col.notNull())
|
|
69
|
-
.addColumn('performed_at', 'timestamp', (col) =>
|
|
70
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
71
|
-
)
|
|
72
|
-
.execute()
|
|
73
|
-
}
|
|
74
|
-
|
|
41
|
+
await ensurePikkuSchema(this.db, credentialSchema)
|
|
75
42
|
this.initialized = true
|
|
76
43
|
}
|
|
77
44
|
|
|
@@ -7,8 +7,9 @@ import type {
|
|
|
7
7
|
import type { JWTService, SecretService } from '@pikku/core/services'
|
|
8
8
|
import { getAllFunctionNames } from '@pikku/core/function'
|
|
9
9
|
import type { Kysely } from 'kysely'
|
|
10
|
-
import { sql } from 'kysely'
|
|
11
10
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
11
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
12
|
+
import { deploymentSchema } from './schema/deployment.schema.js'
|
|
12
13
|
|
|
13
14
|
export class KyselyDeploymentService implements DeploymentService {
|
|
14
15
|
private initialized = false
|
|
@@ -28,55 +29,8 @@ export class KyselyDeploymentService implements DeploymentService {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
public async init(): Promise<void> {
|
|
31
|
-
if (this.initialized)
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
await this.db.schema
|
|
36
|
-
.createTable('pikku_deployments')
|
|
37
|
-
.ifNotExists()
|
|
38
|
-
.addColumn('deployment_id', 'text', (col) => col.primaryKey())
|
|
39
|
-
.addColumn('endpoint', 'text', (col) => col.notNull())
|
|
40
|
-
.addColumn('last_heartbeat', 'timestamp', (col) =>
|
|
41
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
42
|
-
)
|
|
43
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
44
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
45
|
-
)
|
|
46
|
-
.execute()
|
|
47
|
-
|
|
48
|
-
await this.db.schema
|
|
49
|
-
.createTable('pikku_deployment_functions')
|
|
50
|
-
.ifNotExists()
|
|
51
|
-
.addColumn('deployment_id', 'text', (col) =>
|
|
52
|
-
col
|
|
53
|
-
.notNull()
|
|
54
|
-
.references('pikku_deployments.deployment_id')
|
|
55
|
-
.onDelete('cascade')
|
|
56
|
-
)
|
|
57
|
-
.addColumn('function_name', 'text', (col) => col.notNull())
|
|
58
|
-
.addPrimaryKeyConstraint('pikku_deployment_functions_pk', [
|
|
59
|
-
'deployment_id',
|
|
60
|
-
'function_name',
|
|
61
|
-
])
|
|
62
|
-
.execute()
|
|
63
|
-
|
|
64
|
-
await this.createIndexSafe(
|
|
65
|
-
this.db.schema
|
|
66
|
-
.createIndex('idx_pikku_deployments_heartbeat')
|
|
67
|
-
.ifNotExists()
|
|
68
|
-
.on('pikku_deployments')
|
|
69
|
-
.column('last_heartbeat')
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
await this.createIndexSafe(
|
|
73
|
-
this.db.schema
|
|
74
|
-
.createIndex('idx_pikku_deployment_functions_name')
|
|
75
|
-
.ifNotExists()
|
|
76
|
-
.on('pikku_deployment_functions')
|
|
77
|
-
.column('function_name')
|
|
78
|
-
)
|
|
79
|
-
|
|
32
|
+
if (this.initialized) return
|
|
33
|
+
await ensurePikkuSchema(this.db, deploymentSchema)
|
|
80
34
|
this.initialized = true
|
|
81
35
|
}
|
|
82
36
|
|
|
@@ -192,19 +146,6 @@ export class KyselyDeploymentService implements DeploymentService {
|
|
|
192
146
|
return response.json()
|
|
193
147
|
}
|
|
194
148
|
|
|
195
|
-
private async createIndexSafe(builder: {
|
|
196
|
-
execute(): Promise<void>
|
|
197
|
-
}): Promise<void> {
|
|
198
|
-
try {
|
|
199
|
-
await builder.execute()
|
|
200
|
-
} catch (e: any) {
|
|
201
|
-
if (e?.code === 'ER_DUP_KEYNAME' || e?.errno === 1061) return
|
|
202
|
-
if (e?.code === '42P07') return
|
|
203
|
-
if (e?.message?.includes('already exists')) return
|
|
204
|
-
throw e
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
149
|
private async sendHeartbeat(): Promise<void> {
|
|
209
150
|
if (!this.deploymentConfig) return
|
|
210
151
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Role, ScopeService } from '@pikku/core/services'
|
|
2
2
|
import type { FlatScope } from '@pikku/core/scope'
|
|
3
3
|
import type { Kysely } from 'kysely'
|
|
4
|
-
import { sql } from 'kysely'
|
|
5
4
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
5
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
6
|
+
import { scopeSchema } from './schema/scope.schema.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Resolves and administers user scopes against four self-created tables.
|
|
@@ -22,86 +23,8 @@ export class KyselyScopeService implements ScopeService {
|
|
|
22
23
|
constructor(private db: Kysely<KyselyPikkuDB>) {}
|
|
23
24
|
|
|
24
25
|
public async init(): Promise<void> {
|
|
25
|
-
if (this.initialized)
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
await this.db.schema
|
|
30
|
-
.createTable('pikku_scopes')
|
|
31
|
-
.ifNotExists()
|
|
32
|
-
.addColumn('name', 'text', (col) => col.primaryKey())
|
|
33
|
-
.addColumn('description', 'text')
|
|
34
|
-
.addColumn('declared', 'boolean', (col) => col.defaultTo(true).notNull())
|
|
35
|
-
.execute()
|
|
36
|
-
|
|
37
|
-
await this.db.schema
|
|
38
|
-
.createTable('pikku_roles')
|
|
39
|
-
.ifNotExists()
|
|
40
|
-
.addColumn('name', 'text', (col) => col.primaryKey())
|
|
41
|
-
.addColumn('description', 'text')
|
|
42
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
43
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
44
|
-
)
|
|
45
|
-
.execute()
|
|
46
|
-
|
|
47
|
-
await this.db.schema
|
|
48
|
-
.createTable('pikku_role_scopes')
|
|
49
|
-
.ifNotExists()
|
|
50
|
-
.addColumn('role', 'text', (col) =>
|
|
51
|
-
col.notNull().references('pikku_roles.name').onDelete('cascade')
|
|
52
|
-
)
|
|
53
|
-
.addColumn('scope', 'text', (col) =>
|
|
54
|
-
col.notNull().references('pikku_scopes.name').onDelete('cascade')
|
|
55
|
-
)
|
|
56
|
-
.addPrimaryKeyConstraint('pikku_role_scopes_pk', ['role', 'scope'])
|
|
57
|
-
.execute()
|
|
58
|
-
|
|
59
|
-
await this.db.schema
|
|
60
|
-
.createTable('pikku_user_role')
|
|
61
|
-
.ifNotExists()
|
|
62
|
-
.addColumn('user_id', 'text', (col) =>
|
|
63
|
-
col.notNull().references('user.id').onDelete('cascade')
|
|
64
|
-
)
|
|
65
|
-
.addColumn('role', 'text', (col) =>
|
|
66
|
-
col.notNull().references('pikku_roles.name').onDelete('cascade')
|
|
67
|
-
)
|
|
68
|
-
.addColumn('granted_by', 'text')
|
|
69
|
-
.addColumn('granted_at', 'timestamp', (col) =>
|
|
70
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
71
|
-
)
|
|
72
|
-
.addPrimaryKeyConstraint('pikku_user_role_pk', ['user_id', 'role'])
|
|
73
|
-
.execute()
|
|
74
|
-
|
|
75
|
-
await this.db.schema
|
|
76
|
-
.createTable('pikku_user_scope')
|
|
77
|
-
.ifNotExists()
|
|
78
|
-
.addColumn('user_id', 'text', (col) =>
|
|
79
|
-
col.notNull().references('user.id').onDelete('cascade')
|
|
80
|
-
)
|
|
81
|
-
.addColumn('scope', 'text', (col) =>
|
|
82
|
-
col.notNull().references('pikku_scopes.name').onDelete('cascade')
|
|
83
|
-
)
|
|
84
|
-
.addColumn('granted_by', 'text')
|
|
85
|
-
.addColumn('granted_at', 'timestamp', (col) =>
|
|
86
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
87
|
-
)
|
|
88
|
-
.addPrimaryKeyConstraint('pikku_user_scope_pk', ['user_id', 'scope'])
|
|
89
|
-
.execute()
|
|
90
|
-
|
|
91
|
-
await this.db.schema
|
|
92
|
-
.createIndex('pikku_role_scopes_scope_idx')
|
|
93
|
-
.ifNotExists()
|
|
94
|
-
.on('pikku_role_scopes')
|
|
95
|
-
.column('scope')
|
|
96
|
-
.execute()
|
|
97
|
-
|
|
98
|
-
await this.db.schema
|
|
99
|
-
.createIndex('pikku_user_role_role_idx')
|
|
100
|
-
.ifNotExists()
|
|
101
|
-
.on('pikku_user_role')
|
|
102
|
-
.column('role')
|
|
103
|
-
.execute()
|
|
104
|
-
|
|
26
|
+
if (this.initialized) return
|
|
27
|
+
await ensurePikkuSchema(this.db, scopeSchema)
|
|
105
28
|
this.initialized = true
|
|
106
29
|
}
|
|
107
30
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { SecretService } from '@pikku/core/services'
|
|
2
2
|
import type { Kysely } from 'kysely'
|
|
3
|
-
import { sql } from 'kysely'
|
|
4
3
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
5
4
|
import {
|
|
6
5
|
envelopeEncrypt,
|
|
7
6
|
envelopeDecrypt,
|
|
8
7
|
envelopeRewrap,
|
|
9
8
|
} from '@pikku/core/crypto-utils'
|
|
9
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
10
|
+
import { secretSchema } from './schema/secret.schema.js'
|
|
10
11
|
|
|
11
12
|
export interface KyselySecretServiceConfig {
|
|
12
13
|
key: string
|
|
@@ -37,35 +38,7 @@ export class KyselySecretService implements SecretService {
|
|
|
37
38
|
|
|
38
39
|
public async init(): Promise<void> {
|
|
39
40
|
if (this.initialized) return
|
|
40
|
-
|
|
41
|
-
await this.db.schema
|
|
42
|
-
.createTable('secrets')
|
|
43
|
-
.ifNotExists()
|
|
44
|
-
.addColumn('key', 'varchar(255)', (col) => col.primaryKey())
|
|
45
|
-
.addColumn('ciphertext', 'text', (col) => col.notNull())
|
|
46
|
-
.addColumn('wrapped_dek', 'text', (col) => col.notNull())
|
|
47
|
-
.addColumn('key_version', 'integer', (col) => col.notNull())
|
|
48
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
49
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
50
|
-
)
|
|
51
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
52
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
53
|
-
)
|
|
54
|
-
.execute()
|
|
55
|
-
|
|
56
|
-
if (this.audit) {
|
|
57
|
-
await this.db.schema
|
|
58
|
-
.createTable('secrets_audit')
|
|
59
|
-
.ifNotExists()
|
|
60
|
-
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
61
|
-
.addColumn('secret_key', 'varchar(255)', (col) => col.notNull())
|
|
62
|
-
.addColumn('action', 'varchar(20)', (col) => col.notNull())
|
|
63
|
-
.addColumn('performed_at', 'timestamp', (col) =>
|
|
64
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
65
|
-
)
|
|
66
|
-
.execute()
|
|
67
|
-
}
|
|
68
|
-
|
|
41
|
+
await ensurePikkuSchema(this.db, secretSchema)
|
|
69
42
|
this.initialized = true
|
|
70
43
|
}
|
|
71
44
|
|
|
@@ -149,9 +122,9 @@ export class KyselySecretService implements SecretService {
|
|
|
149
122
|
await this.logAudit(key, 'delete')
|
|
150
123
|
}
|
|
151
124
|
|
|
152
|
-
async getSecrets<
|
|
153
|
-
|
|
154
|
-
|
|
125
|
+
async getSecrets<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
126
|
+
keys: (keyof T & string)[]
|
|
127
|
+
): Promise<T> {
|
|
155
128
|
const rows = await this.db
|
|
156
129
|
.selectFrom('secrets')
|
|
157
130
|
.select(['key', 'ciphertext', 'wrappedDek', 'keyVersion'])
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CoreUserSession } from '@pikku/core'
|
|
2
2
|
import type { SessionStore } from '@pikku/core/services'
|
|
3
3
|
import type { Kysely } from 'kysely'
|
|
4
|
-
import { sql } from 'kysely'
|
|
5
4
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
6
5
|
import { parseJson } from './kysely-json.js'
|
|
6
|
+
import { ensurePikkuSchema } from './schema/index.js'
|
|
7
|
+
import { sessionSchema } from './schema/session.schema.js'
|
|
7
8
|
|
|
8
9
|
export class KyselySessionStore implements SessionStore {
|
|
9
10
|
private initialized = false
|
|
@@ -11,23 +12,8 @@ export class KyselySessionStore implements SessionStore {
|
|
|
11
12
|
constructor(private db: Kysely<KyselyPikkuDB>) {}
|
|
12
13
|
|
|
13
14
|
public async init(): Promise<void> {
|
|
14
|
-
if (this.initialized)
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
await this.db.schema
|
|
19
|
-
.createTable('pikku_user_sessions')
|
|
20
|
-
.ifNotExists()
|
|
21
|
-
.addColumn('pikku_user_id', 'text', (col) => col.primaryKey())
|
|
22
|
-
.addColumn('session', 'text', (col) => col.notNull())
|
|
23
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
24
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
25
|
-
)
|
|
26
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
27
|
-
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
28
|
-
)
|
|
29
|
-
.execute()
|
|
30
|
-
|
|
15
|
+
if (this.initialized) return
|
|
16
|
+
await ensurePikkuSchema(this.db, sessionSchema)
|
|
31
17
|
this.initialized = true
|
|
32
18
|
}
|
|
33
19
|
|