@pikku/kysely 0.12.6 → 0.12.8
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 +24 -0
- package/dist/src/kysely-agent-run-service.js +59 -59
- package/dist/src/kysely-ai-storage-service.js +174 -174
- package/dist/src/kysely-channel-store.js +12 -12
- package/dist/src/kysely-credential-service.d.ts +2 -0
- package/dist/src/kysely-credential-service.js +47 -29
- package/dist/src/kysely-deployment-service.js +22 -22
- package/dist/src/kysely-eventhub-store.js +8 -8
- package/dist/src/kysely-secret-service.js +19 -19
- package/dist/src/kysely-tables.d.ts +91 -91
- package/dist/src/kysely-workflow-run-service.js +73 -73
- package/dist/src/kysely-workflow-service.js +127 -127
- package/package.json +2 -2
- package/src/kysely-agent-run-service.ts +59 -59
- package/src/kysely-ai-storage-service.ts +174 -174
- package/src/kysely-channel-store.ts +12 -12
- package/src/kysely-credential-service.ts +51 -29
- package/src/kysely-deployment-service.ts +24 -24
- package/src/kysely-eventhub-store.ts +8 -8
- package/src/kysely-secret-service.ts +19 -19
- package/src/kysely-services.test.ts +19 -18
- package/src/kysely-tables.ts +91 -91
- package/src/kysely-workflow-run-service.ts +76 -76
- package/src/kysely-workflow-service.ts +129 -129
|
@@ -38,9 +38,9 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
38
38
|
await this.db
|
|
39
39
|
.insertInto('channels')
|
|
40
40
|
.values({
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
channelId: channelId,
|
|
42
|
+
channelName: channelName,
|
|
43
|
+
openingData: JSON.stringify(openingData || {}),
|
|
44
44
|
})
|
|
45
45
|
.execute();
|
|
46
46
|
}
|
|
@@ -50,30 +50,30 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
50
50
|
}
|
|
51
51
|
await this.db
|
|
52
52
|
.deleteFrom('channels')
|
|
53
|
-
.where('
|
|
53
|
+
.where('channelId', 'in', channelIds)
|
|
54
54
|
.execute();
|
|
55
55
|
}
|
|
56
56
|
async setUserSession(channelId, session) {
|
|
57
57
|
await this.db
|
|
58
58
|
.updateTable('channels')
|
|
59
|
-
.set({
|
|
60
|
-
.where('
|
|
59
|
+
.set({ userSession: session ? JSON.stringify(session) : null })
|
|
60
|
+
.where('channelId', '=', channelId)
|
|
61
61
|
.execute();
|
|
62
62
|
}
|
|
63
63
|
async getChannelAndSession(channelId) {
|
|
64
64
|
const row = await this.db
|
|
65
65
|
.selectFrom('channels')
|
|
66
|
-
.select(['
|
|
67
|
-
.where('
|
|
66
|
+
.select(['channelId', 'channelName', 'openingData', 'userSession'])
|
|
67
|
+
.where('channelId', '=', channelId)
|
|
68
68
|
.executeTakeFirst();
|
|
69
69
|
if (!row) {
|
|
70
70
|
throw new Error(`Channel not found: ${channelId}`);
|
|
71
71
|
}
|
|
72
72
|
return {
|
|
73
|
-
channelId: row.
|
|
74
|
-
channelName: row.
|
|
75
|
-
openingData: parseJson(row.
|
|
76
|
-
session: (parseJson(row.
|
|
73
|
+
channelId: row.channelId,
|
|
74
|
+
channelName: row.channelName,
|
|
75
|
+
openingData: parseJson(row.openingData) ?? {},
|
|
76
|
+
session: (parseJson(row.userSession) ?? {}),
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
async close() { }
|
|
@@ -26,5 +26,7 @@ export declare class KyselyCredentialService implements CredentialService {
|
|
|
26
26
|
delete(name: string, userId?: string): Promise<void>;
|
|
27
27
|
has(name: string, userId?: string): Promise<boolean>;
|
|
28
28
|
getAll(userId: string): Promise<Record<string, unknown>>;
|
|
29
|
+
getUsersWithCredential(name: string): Promise<string[]>;
|
|
30
|
+
getAllUsers(): Promise<string[]>;
|
|
29
31
|
rotateKEK(): Promise<number>;
|
|
30
32
|
}
|
|
@@ -50,13 +50,13 @@ export class KyselyCredentialService {
|
|
|
50
50
|
if (action === 'read' && !this.auditReads)
|
|
51
51
|
return;
|
|
52
52
|
await this.db
|
|
53
|
-
.insertInto('
|
|
53
|
+
.insertInto('credentialsAudit')
|
|
54
54
|
.values({
|
|
55
55
|
id: crypto.randomUUID(),
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
credentialName: name,
|
|
57
|
+
userId: userId ?? null,
|
|
58
58
|
action,
|
|
59
|
-
|
|
59
|
+
performedAt: new Date().toISOString(),
|
|
60
60
|
})
|
|
61
61
|
.execute();
|
|
62
62
|
}
|
|
@@ -69,20 +69,20 @@ export class KyselyCredentialService {
|
|
|
69
69
|
}
|
|
70
70
|
whereUserId(qb, userId) {
|
|
71
71
|
return userId
|
|
72
|
-
? qb.where('
|
|
73
|
-
: qb.where('
|
|
72
|
+
? qb.where('userId', '=', userId)
|
|
73
|
+
: qb.where('userId', 'is', null);
|
|
74
74
|
}
|
|
75
75
|
async get(name, userId) {
|
|
76
76
|
let qb = this.db
|
|
77
77
|
.selectFrom('credentials')
|
|
78
|
-
.select(['ciphertext', '
|
|
78
|
+
.select(['ciphertext', 'wrappedDek', 'keyVersion'])
|
|
79
79
|
.where('name', '=', name);
|
|
80
80
|
qb = this.whereUserId(qb, userId);
|
|
81
81
|
const row = await qb.executeTakeFirst();
|
|
82
82
|
if (!row)
|
|
83
83
|
return null;
|
|
84
|
-
const kek = this.getKEK(row.
|
|
85
|
-
const plaintext = await envelopeDecrypt(kek, row.ciphertext, row.
|
|
84
|
+
const kek = this.getKEK(row.keyVersion);
|
|
85
|
+
const plaintext = await envelopeDecrypt(kek, row.ciphertext, row.wrappedDek);
|
|
86
86
|
await this.logAudit(name, userId, 'read');
|
|
87
87
|
try {
|
|
88
88
|
return JSON.parse(plaintext);
|
|
@@ -101,9 +101,9 @@ export class KyselyCredentialService {
|
|
|
101
101
|
.updateTable('credentials')
|
|
102
102
|
.set({
|
|
103
103
|
ciphertext,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
wrappedDek: wrappedDEK,
|
|
105
|
+
keyVersion: this.keyVersion,
|
|
106
|
+
updatedAt: now,
|
|
107
107
|
})
|
|
108
108
|
.where('name', '=', name);
|
|
109
109
|
qb = this.whereUserId(qb, userId);
|
|
@@ -114,12 +114,12 @@ export class KyselyCredentialService {
|
|
|
114
114
|
.insertInto('credentials')
|
|
115
115
|
.values({
|
|
116
116
|
name,
|
|
117
|
-
|
|
117
|
+
userId: userId ?? null,
|
|
118
118
|
ciphertext,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
wrappedDek: wrappedDEK,
|
|
120
|
+
keyVersion: this.keyVersion,
|
|
121
|
+
createdAt: now,
|
|
122
|
+
updatedAt: now,
|
|
123
123
|
})
|
|
124
124
|
.execute();
|
|
125
125
|
}
|
|
@@ -143,13 +143,13 @@ export class KyselyCredentialService {
|
|
|
143
143
|
async getAll(userId) {
|
|
144
144
|
const rows = await this.db
|
|
145
145
|
.selectFrom('credentials')
|
|
146
|
-
.select(['name', 'ciphertext', '
|
|
147
|
-
.where('
|
|
146
|
+
.select(['name', 'ciphertext', 'wrappedDek', 'keyVersion'])
|
|
147
|
+
.where('userId', '=', userId)
|
|
148
148
|
.execute();
|
|
149
149
|
const result = {};
|
|
150
150
|
for (const row of rows) {
|
|
151
|
-
const kek = this.getKEK(row.
|
|
152
|
-
const plaintext = await envelopeDecrypt(kek, row.ciphertext, row.
|
|
151
|
+
const kek = this.getKEK(row.keyVersion);
|
|
152
|
+
const plaintext = await envelopeDecrypt(kek, row.ciphertext, row.wrappedDek);
|
|
153
153
|
try {
|
|
154
154
|
result[row.name] = JSON.parse(plaintext);
|
|
155
155
|
}
|
|
@@ -160,28 +160,46 @@ export class KyselyCredentialService {
|
|
|
160
160
|
}
|
|
161
161
|
return result;
|
|
162
162
|
}
|
|
163
|
+
async getUsersWithCredential(name) {
|
|
164
|
+
const rows = await this.db
|
|
165
|
+
.selectFrom('credentials')
|
|
166
|
+
.select('userId')
|
|
167
|
+
.where('name', '=', name)
|
|
168
|
+
.where('userId', 'is not', null)
|
|
169
|
+
.execute();
|
|
170
|
+
return rows.map((row) => row.userId).filter(Boolean);
|
|
171
|
+
}
|
|
172
|
+
async getAllUsers() {
|
|
173
|
+
const rows = await this.db
|
|
174
|
+
.selectFrom('credentials')
|
|
175
|
+
.select('userId')
|
|
176
|
+
.distinct()
|
|
177
|
+
.where('userId', 'is not', null)
|
|
178
|
+
.execute();
|
|
179
|
+
return rows.map((row) => row.userId).filter(Boolean);
|
|
180
|
+
}
|
|
163
181
|
async rotateKEK() {
|
|
164
182
|
if (!this.previousKey) {
|
|
165
183
|
throw new Error('No previousKey configured — nothing to rotate from');
|
|
166
184
|
}
|
|
167
185
|
const rows = await this.db
|
|
168
186
|
.selectFrom('credentials')
|
|
169
|
-
.select(['name', '
|
|
170
|
-
.where('
|
|
187
|
+
.select(['name', 'userId', 'wrappedDek'])
|
|
188
|
+
.where('keyVersion', '<', this.keyVersion)
|
|
171
189
|
.execute();
|
|
172
190
|
for (const row of rows) {
|
|
173
|
-
const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.
|
|
191
|
+
const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.wrappedDek);
|
|
174
192
|
let qb = this.db
|
|
175
193
|
.updateTable('credentials')
|
|
176
194
|
.set({
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
195
|
+
wrappedDek: newWrappedDEK,
|
|
196
|
+
keyVersion: this.keyVersion,
|
|
197
|
+
updatedAt: new Date().toISOString(),
|
|
180
198
|
})
|
|
181
199
|
.where('name', '=', row.name);
|
|
182
|
-
qb = this.whereUserId(qb, row.
|
|
200
|
+
qb = this.whereUserId(qb, row.userId ?? undefined);
|
|
183
201
|
await qb.execute();
|
|
184
|
-
await this.logAudit(row.name, row.
|
|
202
|
+
await this.logAudit(row.name, row.userId ?? undefined, 'rotate');
|
|
185
203
|
}
|
|
186
204
|
return rows.length;
|
|
187
205
|
}
|
|
@@ -54,27 +54,27 @@ export class KyselyDeploymentService {
|
|
|
54
54
|
this.deploymentConfig = { ...config, functions };
|
|
55
55
|
await this.db.transaction().execute(async (trx) => {
|
|
56
56
|
await trx
|
|
57
|
-
.insertInto('
|
|
57
|
+
.insertInto('pikkuDeployments')
|
|
58
58
|
.values({
|
|
59
|
-
|
|
59
|
+
deploymentId: config.deploymentId,
|
|
60
60
|
endpoint: config.endpoint,
|
|
61
|
-
|
|
61
|
+
lastHeartbeat: new Date(),
|
|
62
62
|
})
|
|
63
|
-
.onConflict((oc) => oc.column('
|
|
63
|
+
.onConflict((oc) => oc.column('deploymentId').doUpdateSet({
|
|
64
64
|
endpoint: config.endpoint,
|
|
65
|
-
|
|
65
|
+
lastHeartbeat: new Date(),
|
|
66
66
|
}))
|
|
67
67
|
.execute();
|
|
68
68
|
await trx
|
|
69
|
-
.deleteFrom('
|
|
70
|
-
.where('
|
|
69
|
+
.deleteFrom('pikkuDeploymentFunctions')
|
|
70
|
+
.where('deploymentId', '=', config.deploymentId)
|
|
71
71
|
.execute();
|
|
72
72
|
if (functions.length > 0) {
|
|
73
73
|
await trx
|
|
74
|
-
.insertInto('
|
|
74
|
+
.insertInto('pikkuDeploymentFunctions')
|
|
75
75
|
.values(functions.map((fn) => ({
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
deploymentId: config.deploymentId,
|
|
77
|
+
functionName: fn,
|
|
78
78
|
})))
|
|
79
79
|
.execute();
|
|
80
80
|
}
|
|
@@ -88,8 +88,8 @@ export class KyselyDeploymentService {
|
|
|
88
88
|
}
|
|
89
89
|
if (this.deploymentConfig) {
|
|
90
90
|
await this.db
|
|
91
|
-
.deleteFrom('
|
|
92
|
-
.where('
|
|
91
|
+
.deleteFrom('pikkuDeployments')
|
|
92
|
+
.where('deploymentId', '=', this.deploymentConfig.deploymentId)
|
|
93
93
|
.execute();
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -97,15 +97,15 @@ export class KyselyDeploymentService {
|
|
|
97
97
|
const ttlMs = this.heartbeatTtl;
|
|
98
98
|
const cutoff = new Date(Date.now() - ttlMs);
|
|
99
99
|
const result = await this.db
|
|
100
|
-
.selectFrom('
|
|
101
|
-
.innerJoin('
|
|
102
|
-
.select(['d.
|
|
103
|
-
.where('f.
|
|
104
|
-
.where('d.
|
|
105
|
-
.orderBy('d.
|
|
100
|
+
.selectFrom('pikkuDeployments as d')
|
|
101
|
+
.innerJoin('pikkuDeploymentFunctions as f', 'f.deploymentId', 'd.deploymentId')
|
|
102
|
+
.select(['d.deploymentId', 'd.endpoint'])
|
|
103
|
+
.where('f.functionName', '=', name)
|
|
104
|
+
.where('d.lastHeartbeat', '>', cutoff)
|
|
105
|
+
.orderBy('d.lastHeartbeat', 'desc')
|
|
106
106
|
.execute();
|
|
107
107
|
return result.map((row) => ({
|
|
108
|
-
deploymentId: row.
|
|
108
|
+
deploymentId: row.deploymentId,
|
|
109
109
|
endpoint: row.endpoint,
|
|
110
110
|
}));
|
|
111
111
|
}
|
|
@@ -128,9 +128,9 @@ export class KyselyDeploymentService {
|
|
|
128
128
|
return;
|
|
129
129
|
try {
|
|
130
130
|
await this.db
|
|
131
|
-
.updateTable('
|
|
132
|
-
.set({
|
|
133
|
-
.where('
|
|
131
|
+
.updateTable('pikkuDeployments')
|
|
132
|
+
.set({ lastHeartbeat: new Date() })
|
|
133
|
+
.where('deploymentId', '=', this.deploymentConfig.deploymentId)
|
|
134
134
|
.execute();
|
|
135
135
|
}
|
|
136
136
|
catch {
|
|
@@ -14,18 +14,18 @@ export class KyselyEventHubStore extends EventHubStore {
|
|
|
14
14
|
}
|
|
15
15
|
async getChannelIdsForTopic(topic) {
|
|
16
16
|
const result = await this.db
|
|
17
|
-
.selectFrom('
|
|
18
|
-
.select('
|
|
17
|
+
.selectFrom('channelSubscriptions')
|
|
18
|
+
.select('channelId')
|
|
19
19
|
.where('topic', '=', topic)
|
|
20
20
|
.execute();
|
|
21
|
-
return result.map((row) => row.
|
|
21
|
+
return result.map((row) => row.channelId);
|
|
22
22
|
}
|
|
23
23
|
async subscribe(topic, channelId) {
|
|
24
24
|
try {
|
|
25
25
|
await this.db
|
|
26
|
-
.insertInto('
|
|
27
|
-
.values({
|
|
28
|
-
.onConflict((oc) => oc.columns(['
|
|
26
|
+
.insertInto('channelSubscriptions')
|
|
27
|
+
.values({ channelId: channelId, topic: topic })
|
|
28
|
+
.onConflict((oc) => oc.columns(['channelId', 'topic']).doNothing())
|
|
29
29
|
.execute();
|
|
30
30
|
return true;
|
|
31
31
|
}
|
|
@@ -35,8 +35,8 @@ export class KyselyEventHubStore extends EventHubStore {
|
|
|
35
35
|
}
|
|
36
36
|
async unsubscribe(topic, channelId) {
|
|
37
37
|
const result = await this.db
|
|
38
|
-
.deleteFrom('
|
|
39
|
-
.where('
|
|
38
|
+
.deleteFrom('channelSubscriptions')
|
|
39
|
+
.where('channelId', '=', channelId)
|
|
40
40
|
.where('topic', '=', topic)
|
|
41
41
|
.executeTakeFirst();
|
|
42
42
|
return BigInt(result.numDeletedRows) > 0n;
|
|
@@ -47,12 +47,12 @@ export class KyselySecretService {
|
|
|
47
47
|
if (action === 'read' && !this.auditReads)
|
|
48
48
|
return;
|
|
49
49
|
await this.db
|
|
50
|
-
.insertInto('
|
|
50
|
+
.insertInto('secretsAudit')
|
|
51
51
|
.values({
|
|
52
52
|
id: crypto.randomUUID(),
|
|
53
|
-
|
|
53
|
+
secretKey: secretKey,
|
|
54
54
|
action,
|
|
55
|
-
|
|
55
|
+
performedAt: new Date().toISOString(),
|
|
56
56
|
})
|
|
57
57
|
.execute();
|
|
58
58
|
}
|
|
@@ -66,13 +66,13 @@ export class KyselySecretService {
|
|
|
66
66
|
async getSecret(key) {
|
|
67
67
|
const row = await this.db
|
|
68
68
|
.selectFrom('secrets')
|
|
69
|
-
.select(['ciphertext', '
|
|
69
|
+
.select(['ciphertext', 'wrappedDek', 'keyVersion'])
|
|
70
70
|
.where('key', '=', key)
|
|
71
71
|
.executeTakeFirst();
|
|
72
72
|
if (!row)
|
|
73
73
|
throw new Error('Requested secret not found');
|
|
74
|
-
const kek = this.getKEK(row.
|
|
75
|
-
const result = await envelopeDecrypt(kek, row.ciphertext, row.
|
|
74
|
+
const kek = this.getKEK(row.keyVersion);
|
|
75
|
+
const result = await envelopeDecrypt(kek, row.ciphertext, row.wrappedDek);
|
|
76
76
|
await this.logAudit(key, 'read');
|
|
77
77
|
return result;
|
|
78
78
|
}
|
|
@@ -97,16 +97,16 @@ export class KyselySecretService {
|
|
|
97
97
|
.values({
|
|
98
98
|
key,
|
|
99
99
|
ciphertext,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
wrappedDek: wrappedDEK,
|
|
101
|
+
keyVersion: this.keyVersion,
|
|
102
|
+
createdAt: now,
|
|
103
|
+
updatedAt: now,
|
|
104
104
|
})
|
|
105
105
|
.onConflict((oc) => oc.column('key').doUpdateSet({
|
|
106
106
|
ciphertext,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
wrappedDek: wrappedDEK,
|
|
108
|
+
keyVersion: this.keyVersion,
|
|
109
|
+
updatedAt: now,
|
|
110
110
|
}))
|
|
111
111
|
.execute();
|
|
112
112
|
await this.logAudit(key, 'write');
|
|
@@ -121,17 +121,17 @@ export class KyselySecretService {
|
|
|
121
121
|
}
|
|
122
122
|
const rows = await this.db
|
|
123
123
|
.selectFrom('secrets')
|
|
124
|
-
.select(['key', '
|
|
125
|
-
.where('
|
|
124
|
+
.select(['key', 'wrappedDek'])
|
|
125
|
+
.where('keyVersion', '<', this.keyVersion)
|
|
126
126
|
.execute();
|
|
127
127
|
for (const row of rows) {
|
|
128
|
-
const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.
|
|
128
|
+
const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.wrappedDek);
|
|
129
129
|
await this.db
|
|
130
130
|
.updateTable('secrets')
|
|
131
131
|
.set({
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
wrappedDek: newWrappedDEK,
|
|
133
|
+
keyVersion: this.keyVersion,
|
|
134
|
+
updatedAt: new Date().toISOString(),
|
|
135
135
|
})
|
|
136
136
|
.where('key', '=', row.key)
|
|
137
137
|
.execute();
|