@kernl-sdk/pg 0.1.11 → 0.1.12
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-check-types.log +36 -0
- package/CHANGELOG.md +32 -0
- package/README.md +124 -0
- package/dist/__tests__/integration.test.js +2 -2
- package/dist/__tests__/memory-integration.test.d.ts +2 -0
- package/dist/__tests__/memory-integration.test.d.ts.map +1 -0
- package/dist/__tests__/memory-integration.test.js +287 -0
- package/dist/__tests__/memory.test.d.ts +2 -0
- package/dist/__tests__/memory.test.d.ts.map +1 -0
- package/dist/__tests__/memory.test.js +357 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/memory/sql.d.ts +30 -0
- package/dist/memory/sql.d.ts.map +1 -0
- package/dist/memory/sql.js +100 -0
- package/dist/memory/store.d.ts +41 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +114 -0
- package/dist/migrations.d.ts +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +9 -3
- package/dist/pgvector/__tests__/handle.test.d.ts +2 -0
- package/dist/pgvector/__tests__/handle.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/handle.test.js +277 -0
- package/dist/pgvector/__tests__/hit.test.d.ts +2 -0
- package/dist/pgvector/__tests__/hit.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/hit.test.js +134 -0
- package/dist/pgvector/__tests__/integration/document.integration.test.d.ts +7 -0
- package/dist/pgvector/__tests__/integration/document.integration.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/integration/document.integration.test.js +587 -0
- package/dist/pgvector/__tests__/integration/edge.integration.test.d.ts +8 -0
- package/dist/pgvector/__tests__/integration/edge.integration.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/integration/edge.integration.test.js +663 -0
- package/dist/pgvector/__tests__/integration/filters.integration.test.d.ts +8 -0
- package/dist/pgvector/__tests__/integration/filters.integration.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/integration/filters.integration.test.js +609 -0
- package/dist/pgvector/__tests__/integration/lifecycle.integration.test.d.ts +8 -0
- package/dist/pgvector/__tests__/integration/lifecycle.integration.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/integration/lifecycle.integration.test.js +449 -0
- package/dist/pgvector/__tests__/integration/query.integration.test.d.ts +8 -0
- package/dist/pgvector/__tests__/integration/query.integration.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/integration/query.integration.test.js +544 -0
- package/dist/pgvector/__tests__/search.test.d.ts +2 -0
- package/dist/pgvector/__tests__/search.test.d.ts.map +1 -0
- package/dist/pgvector/__tests__/search.test.js +279 -0
- package/dist/pgvector/handle.d.ts +60 -0
- package/dist/pgvector/handle.d.ts.map +1 -0
- package/dist/pgvector/handle.js +213 -0
- package/dist/pgvector/hit.d.ts +10 -0
- package/dist/pgvector/hit.d.ts.map +1 -0
- package/dist/pgvector/hit.js +44 -0
- package/dist/pgvector/index.d.ts +7 -0
- package/dist/pgvector/index.d.ts.map +1 -0
- package/dist/pgvector/index.js +5 -0
- package/dist/pgvector/search.d.ts +60 -0
- package/dist/pgvector/search.d.ts.map +1 -0
- package/dist/pgvector/search.js +227 -0
- package/dist/pgvector/sql/__tests__/limit.test.d.ts +2 -0
- package/dist/pgvector/sql/__tests__/limit.test.d.ts.map +1 -0
- package/dist/pgvector/sql/__tests__/limit.test.js +161 -0
- package/dist/pgvector/sql/__tests__/order.test.d.ts +2 -0
- package/dist/pgvector/sql/__tests__/order.test.d.ts.map +1 -0
- package/dist/pgvector/sql/__tests__/order.test.js +218 -0
- package/dist/pgvector/sql/__tests__/query.test.d.ts +2 -0
- package/dist/pgvector/sql/__tests__/query.test.d.ts.map +1 -0
- package/dist/pgvector/sql/__tests__/query.test.js +392 -0
- package/dist/pgvector/sql/__tests__/select.test.d.ts +2 -0
- package/dist/pgvector/sql/__tests__/select.test.d.ts.map +1 -0
- package/dist/pgvector/sql/__tests__/select.test.js +293 -0
- package/dist/pgvector/sql/__tests__/where.test.d.ts +2 -0
- package/dist/pgvector/sql/__tests__/where.test.d.ts.map +1 -0
- package/dist/pgvector/sql/__tests__/where.test.js +488 -0
- package/dist/pgvector/sql/index.d.ts +7 -0
- package/dist/pgvector/sql/index.d.ts.map +1 -0
- package/dist/pgvector/sql/index.js +6 -0
- package/dist/pgvector/sql/limit.d.ts +8 -0
- package/dist/pgvector/sql/limit.d.ts.map +1 -0
- package/dist/pgvector/sql/limit.js +20 -0
- package/dist/pgvector/sql/order.d.ts +9 -0
- package/dist/pgvector/sql/order.d.ts.map +1 -0
- package/dist/pgvector/sql/order.js +47 -0
- package/dist/pgvector/sql/query.d.ts +46 -0
- package/dist/pgvector/sql/query.d.ts.map +1 -0
- package/dist/pgvector/sql/query.js +54 -0
- package/dist/pgvector/sql/schema.d.ts +16 -0
- package/dist/pgvector/sql/schema.d.ts.map +1 -0
- package/dist/pgvector/sql/schema.js +47 -0
- package/dist/pgvector/sql/select.d.ts +11 -0
- package/dist/pgvector/sql/select.d.ts.map +1 -0
- package/dist/pgvector/sql/select.js +87 -0
- package/dist/pgvector/sql/where.d.ts +8 -0
- package/dist/pgvector/sql/where.d.ts.map +1 -0
- package/dist/pgvector/sql/where.js +137 -0
- package/dist/pgvector/types.d.ts +20 -0
- package/dist/pgvector/types.d.ts.map +1 -0
- package/dist/pgvector/types.js +1 -0
- package/dist/pgvector/utils.d.ts +18 -0
- package/dist/pgvector/utils.d.ts.map +1 -0
- package/dist/pgvector/utils.js +22 -0
- package/dist/postgres.d.ts +19 -26
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +15 -27
- package/dist/storage.d.ts +48 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +32 -9
- package/dist/thread/sql.d.ts +38 -0
- package/dist/thread/sql.d.ts.map +1 -0
- package/dist/thread/sql.js +112 -0
- package/dist/thread/store.d.ts +2 -2
- package/dist/thread/store.d.ts.map +1 -1
- package/dist/thread/store.js +32 -102
- package/package.json +7 -4
- package/src/__tests__/integration.test.ts +15 -17
- package/src/__tests__/memory-integration.test.ts +355 -0
- package/src/__tests__/memory.test.ts +428 -0
- package/src/index.ts +19 -3
- package/src/memory/sql.ts +141 -0
- package/src/memory/store.ts +166 -0
- package/src/migrations.ts +13 -3
- package/src/pgvector/README.md +50 -0
- package/src/pgvector/__tests__/handle.test.ts +335 -0
- package/src/pgvector/__tests__/hit.test.ts +165 -0
- package/src/pgvector/__tests__/integration/document.integration.test.ts +717 -0
- package/src/pgvector/__tests__/integration/edge.integration.test.ts +835 -0
- package/src/pgvector/__tests__/integration/filters.integration.test.ts +721 -0
- package/src/pgvector/__tests__/integration/lifecycle.integration.test.ts +570 -0
- package/src/pgvector/__tests__/integration/query.integration.test.ts +667 -0
- package/src/pgvector/__tests__/search.test.ts +366 -0
- package/src/pgvector/handle.ts +285 -0
- package/src/pgvector/hit.ts +56 -0
- package/src/pgvector/index.ts +7 -0
- package/src/pgvector/search.ts +330 -0
- package/src/pgvector/sql/__tests__/limit.test.ts +180 -0
- package/src/pgvector/sql/__tests__/order.test.ts +248 -0
- package/src/pgvector/sql/__tests__/query.test.ts +548 -0
- package/src/pgvector/sql/__tests__/select.test.ts +367 -0
- package/src/pgvector/sql/__tests__/where.test.ts +554 -0
- package/src/pgvector/sql/index.ts +14 -0
- package/src/pgvector/sql/limit.ts +29 -0
- package/src/pgvector/sql/order.ts +55 -0
- package/src/pgvector/sql/query.ts +112 -0
- package/src/pgvector/sql/schema.ts +61 -0
- package/src/pgvector/sql/select.ts +100 -0
- package/src/pgvector/sql/where.ts +152 -0
- package/src/pgvector/types.ts +21 -0
- package/src/pgvector/utils.ts +24 -0
- package/src/postgres.ts +31 -33
- package/src/storage.ts +77 -9
- package/src/thread/sql.ts +159 -0
- package/src/thread/store.ts +40 -127
- package/tsconfig.tsbuildinfo +1 -0
package/src/storage.ts
CHANGED
|
@@ -9,14 +9,67 @@ import type {
|
|
|
9
9
|
Transaction,
|
|
10
10
|
} from "kernl";
|
|
11
11
|
import type { Table, Column, IndexConstraint } from "@kernl-sdk/storage";
|
|
12
|
-
import {
|
|
12
|
+
import { KERNL_SCHEMA_NAME, TABLE_MIGRATIONS } from "@kernl-sdk/storage";
|
|
13
13
|
import { UnimplementedError } from "@kernl-sdk/shared/lib";
|
|
14
14
|
|
|
15
15
|
/* pg */
|
|
16
16
|
import { PGThreadStore } from "./thread/store";
|
|
17
|
-
import {
|
|
17
|
+
import { PGMemoryStore } from "./memory/store";
|
|
18
|
+
import { MIGRATIONS } from "./migrations";
|
|
18
19
|
import { SQL_IDENTIFIER_REGEX } from "./sql";
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Vector similarity metric for pgvector.
|
|
23
|
+
*/
|
|
24
|
+
export type VectorSimilarity = "cosine" | "euclidean" | "dot_product";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* pgvector configuration options.
|
|
28
|
+
*/
|
|
29
|
+
export interface PGVectorConfig {
|
|
30
|
+
/**
|
|
31
|
+
* Vector dimensions.
|
|
32
|
+
* @default 1536 (OpenAI text-embedding-3-small)
|
|
33
|
+
*/
|
|
34
|
+
dimensions?: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Distance metric for similarity search.
|
|
38
|
+
* @default "cosine"
|
|
39
|
+
*/
|
|
40
|
+
similarity?: VectorSimilarity;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolved vector configuration with defaults applied.
|
|
45
|
+
*/
|
|
46
|
+
export interface ResolvedVectorConfig {
|
|
47
|
+
dimensions: number;
|
|
48
|
+
similarity: VectorSimilarity;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default vector configuration.
|
|
53
|
+
*/
|
|
54
|
+
export const DEFAULT_VECTOR_CONFIG: ResolvedVectorConfig = {
|
|
55
|
+
dimensions: 1536,
|
|
56
|
+
similarity: "cosine",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve vector config, applying defaults.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveVectorConfig(
|
|
63
|
+
config: boolean | PGVectorConfig | undefined,
|
|
64
|
+
): ResolvedVectorConfig | undefined {
|
|
65
|
+
if (!config) return undefined;
|
|
66
|
+
if (config === true) return DEFAULT_VECTOR_CONFIG;
|
|
67
|
+
return {
|
|
68
|
+
dimensions: config.dimensions ?? DEFAULT_VECTOR_CONFIG.dimensions,
|
|
69
|
+
similarity: config.similarity ?? DEFAULT_VECTOR_CONFIG.similarity,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
20
73
|
/**
|
|
21
74
|
* PostgreSQL storage configuration.
|
|
22
75
|
*/
|
|
@@ -25,6 +78,19 @@ export interface PGStorageConfig {
|
|
|
25
78
|
* Pool instance for database connections.
|
|
26
79
|
*/
|
|
27
80
|
pool: Pool;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Enable pgvector support for semantic search.
|
|
84
|
+
*
|
|
85
|
+
* - `true`: Use default config (1536 dimensions, cosine similarity)
|
|
86
|
+
* - `PGVectorConfig`: Custom dimensions and similarity metric
|
|
87
|
+
*
|
|
88
|
+
* Requires pgvector extension to be installed by superuser:
|
|
89
|
+
* ```sql
|
|
90
|
+
* CREATE EXTENSION IF NOT EXISTS vector;
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
vector?: boolean | PGVectorConfig;
|
|
28
94
|
}
|
|
29
95
|
|
|
30
96
|
/**
|
|
@@ -42,10 +108,12 @@ export class PGStorage implements KernlStorage {
|
|
|
42
108
|
private initPromise: Promise<void> | null = null;
|
|
43
109
|
|
|
44
110
|
threads: PGThreadStore;
|
|
111
|
+
memories: PGMemoryStore;
|
|
45
112
|
|
|
46
113
|
constructor(config: PGStorageConfig) {
|
|
47
114
|
this.pool = config.pool;
|
|
48
115
|
this.threads = new PGThreadStore(this.pool, () => this.ensureInit());
|
|
116
|
+
this.memories = new PGMemoryStore(this.pool, () => this.ensureInit());
|
|
49
117
|
}
|
|
50
118
|
|
|
51
119
|
/**
|
|
@@ -81,7 +149,7 @@ export class PGStorage implements KernlStorage {
|
|
|
81
149
|
* Initialize the storage backend.
|
|
82
150
|
*/
|
|
83
151
|
async init(): Promise<void> {
|
|
84
|
-
await this.pool.query(`CREATE SCHEMA IF NOT EXISTS "${
|
|
152
|
+
await this.pool.query(`CREATE SCHEMA IF NOT EXISTS "${KERNL_SCHEMA_NAME}"`);
|
|
85
153
|
await this.createTable(TABLE_MIGRATIONS);
|
|
86
154
|
await this.migrate();
|
|
87
155
|
}
|
|
@@ -103,12 +171,12 @@ export class PGStorage implements KernlStorage {
|
|
|
103
171
|
|
|
104
172
|
// read applied migration IDs
|
|
105
173
|
const result = await client.query<{ id: string }>(
|
|
106
|
-
`SELECT id FROM "${
|
|
174
|
+
`SELECT id FROM "${KERNL_SCHEMA_NAME}".migrations ORDER BY applied_at ASC`,
|
|
107
175
|
);
|
|
108
176
|
const applied = new Set(result.rows.map((row) => row.id));
|
|
109
177
|
|
|
110
178
|
// filter pending migrations
|
|
111
|
-
const pending =
|
|
179
|
+
const pending = MIGRATIONS.filter((m) => !applied.has(m.id));
|
|
112
180
|
if (pending.length === 0) {
|
|
113
181
|
await client.query("COMMIT");
|
|
114
182
|
return;
|
|
@@ -123,7 +191,7 @@ export class PGStorage implements KernlStorage {
|
|
|
123
191
|
},
|
|
124
192
|
});
|
|
125
193
|
await client.query(
|
|
126
|
-
`INSERT INTO "${
|
|
194
|
+
`INSERT INTO "${KERNL_SCHEMA_NAME}".migrations (id, applied_at) VALUES ($1, $2)`,
|
|
127
195
|
[migration.id, Date.now()],
|
|
128
196
|
);
|
|
129
197
|
}
|
|
@@ -174,7 +242,7 @@ export class PGStorage implements KernlStorage {
|
|
|
174
242
|
|
|
175
243
|
// foreign key reference
|
|
176
244
|
if (col._fk) {
|
|
177
|
-
let ref = `REFERENCES "${
|
|
245
|
+
let ref = `REFERENCES "${KERNL_SCHEMA_NAME}"."${col._fk.table}" ("${col._fk.column}")`;
|
|
178
246
|
if (col._onDelete) {
|
|
179
247
|
ref += ` ON DELETE ${col._onDelete}`;
|
|
180
248
|
}
|
|
@@ -233,7 +301,7 @@ export class PGStorage implements KernlStorage {
|
|
|
233
301
|
const constraints = [...columns, ...tableConstraints];
|
|
234
302
|
|
|
235
303
|
const sql = `
|
|
236
|
-
CREATE TABLE IF NOT EXISTS "${
|
|
304
|
+
CREATE TABLE IF NOT EXISTS "${KERNL_SCHEMA_NAME}"."${table.name}" (
|
|
237
305
|
${constraints.join(",\n ")}
|
|
238
306
|
)
|
|
239
307
|
`.trim();
|
|
@@ -285,7 +353,7 @@ export class PGStorage implements KernlStorage {
|
|
|
285
353
|
|
|
286
354
|
const sql = `
|
|
287
355
|
CREATE ${uniqueKeyword} INDEX IF NOT EXISTS "${indexName}"
|
|
288
|
-
ON "${
|
|
356
|
+
ON "${KERNL_SCHEMA_NAME}"."${tableName}" (${columns})
|
|
289
357
|
`.trim();
|
|
290
358
|
|
|
291
359
|
await client.query(sql);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread SQL conversion codecs.
|
|
3
|
+
*
|
|
4
|
+
* TODO: generalize object -> SQL conversion into a shared utility
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Codec } from "@kernl-sdk/shared/lib";
|
|
8
|
+
import type { ThreadFilter, ThreadUpdate, SortOrder } from "kernl";
|
|
9
|
+
|
|
10
|
+
export interface SQLClause {
|
|
11
|
+
sql: string;
|
|
12
|
+
params: unknown[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WhereInput {
|
|
16
|
+
filter?: ThreadFilter;
|
|
17
|
+
startIdx: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Encode ThreadFilter to SQL WHERE clause.
|
|
22
|
+
*/
|
|
23
|
+
export const SQL_WHERE: Codec<WhereInput, SQLClause> = {
|
|
24
|
+
encode({ filter, startIdx }) {
|
|
25
|
+
if (!filter) {
|
|
26
|
+
return { sql: "", params: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const conditions: string[] = [];
|
|
30
|
+
const params: unknown[] = [];
|
|
31
|
+
let idx = startIdx;
|
|
32
|
+
|
|
33
|
+
if (filter.namespace !== undefined) {
|
|
34
|
+
conditions.push(`namespace = $${idx++}`);
|
|
35
|
+
params.push(filter.namespace);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (filter.state !== undefined) {
|
|
39
|
+
if (Array.isArray(filter.state)) {
|
|
40
|
+
conditions.push(`state = ANY($${idx++})`);
|
|
41
|
+
params.push(filter.state);
|
|
42
|
+
} else {
|
|
43
|
+
conditions.push(`state = $${idx++}`);
|
|
44
|
+
params.push(filter.state);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (filter.agentId !== undefined) {
|
|
49
|
+
conditions.push(`agent_id = $${idx++}`);
|
|
50
|
+
params.push(filter.agentId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (filter.parentTaskId !== undefined) {
|
|
54
|
+
conditions.push(`parent_task_id = $${idx++}`);
|
|
55
|
+
params.push(filter.parentTaskId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (filter.createdAfter !== undefined) {
|
|
59
|
+
conditions.push(`created_at > $${idx++}`);
|
|
60
|
+
params.push(filter.createdAfter.getTime());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (filter.createdBefore !== undefined) {
|
|
64
|
+
conditions.push(`created_at < $${idx++}`);
|
|
65
|
+
params.push(filter.createdBefore.getTime());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "",
|
|
70
|
+
params,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
decode() {
|
|
75
|
+
throw new Error("SQL_WHERE.decode not implemented");
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export interface OrderInput {
|
|
80
|
+
order?: {
|
|
81
|
+
createdAt?: SortOrder;
|
|
82
|
+
updatedAt?: SortOrder;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Encode order options to SQL ORDER BY clause.
|
|
88
|
+
*/
|
|
89
|
+
export const SQL_ORDER: Codec<OrderInput, string> = {
|
|
90
|
+
encode({ order }) {
|
|
91
|
+
const clauses: string[] = [];
|
|
92
|
+
|
|
93
|
+
if (order?.createdAt) {
|
|
94
|
+
clauses.push(`created_at ${order.createdAt.toUpperCase()}`);
|
|
95
|
+
}
|
|
96
|
+
if (order?.updatedAt) {
|
|
97
|
+
clauses.push(`updated_at ${order.updatedAt.toUpperCase()}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (clauses.length === 0) {
|
|
101
|
+
return "created_at DESC";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return clauses.join(", ");
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
decode() {
|
|
108
|
+
throw new Error("SQL_ORDER.decode not implemented");
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export interface UpdateInput {
|
|
113
|
+
patch: ThreadUpdate;
|
|
114
|
+
startIdx: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Encode ThreadUpdate to SQL SET clause.
|
|
119
|
+
*/
|
|
120
|
+
export const SQL_UPDATE: Codec<UpdateInput, SQLClause> = {
|
|
121
|
+
encode({ patch, startIdx }) {
|
|
122
|
+
const sets: string[] = [];
|
|
123
|
+
const params: unknown[] = [];
|
|
124
|
+
let idx = startIdx;
|
|
125
|
+
|
|
126
|
+
if (patch.tick !== undefined) {
|
|
127
|
+
sets.push(`tick = $${idx++}`);
|
|
128
|
+
params.push(patch.tick);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (patch.state !== undefined) {
|
|
132
|
+
sets.push(`state = $${idx++}`);
|
|
133
|
+
params.push(patch.state);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (patch.context !== undefined) {
|
|
137
|
+
sets.push(`context = $${idx++}`);
|
|
138
|
+
params.push(JSON.stringify(patch.context.context));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (patch.metadata !== undefined) {
|
|
142
|
+
sets.push(`metadata = $${idx++}`);
|
|
143
|
+
params.push(patch.metadata ? JSON.stringify(patch.metadata) : null);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// always update updated_at
|
|
147
|
+
sets.push(`updated_at = $${idx++}`);
|
|
148
|
+
params.push(Date.now());
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
sql: sets.join(", "),
|
|
152
|
+
params,
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
decode() {
|
|
157
|
+
throw new Error("SQL_UPDATE.decode not implemented");
|
|
158
|
+
},
|
|
159
|
+
};
|
package/src/thread/store.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import assert from "assert";
|
|
2
2
|
import type { Pool, PoolClient } from "pg";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
SCHEMA_NAME,
|
|
6
|
-
NewThreadCodec,
|
|
7
|
-
ThreadEventRecordCodec,
|
|
8
|
-
type ThreadRecord,
|
|
9
|
-
type ThreadEventRecord,
|
|
10
|
-
} from "@kernl-sdk/storage";
|
|
11
|
-
import { Thread, type ThreadEvent } from "kernl/internal";
|
|
12
4
|
import {
|
|
13
5
|
Context,
|
|
14
6
|
type AgentRegistry,
|
|
@@ -20,6 +12,16 @@ import {
|
|
|
20
12
|
type ThreadListOptions,
|
|
21
13
|
type ThreadHistoryOptions,
|
|
22
14
|
} from "kernl";
|
|
15
|
+
import { Thread, type ThreadEvent } from "kernl/internal";
|
|
16
|
+
import {
|
|
17
|
+
KERNL_SCHEMA_NAME,
|
|
18
|
+
NewThreadCodec,
|
|
19
|
+
ThreadEventRecordCodec,
|
|
20
|
+
type ThreadRecord,
|
|
21
|
+
type ThreadEventRecord,
|
|
22
|
+
} from "@kernl-sdk/storage";
|
|
23
|
+
|
|
24
|
+
import { SQL_WHERE, SQL_ORDER, SQL_UPDATE } from "./sql";
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* PostgreSQL Thread store implementation.
|
|
@@ -85,8 +87,8 @@ export class PGThreadStore implements ThreadStore {
|
|
|
85
87
|
e.timestamp,
|
|
86
88
|
e.data,
|
|
87
89
|
e.metadata as event_metadata
|
|
88
|
-
FROM ${
|
|
89
|
-
LEFT JOIN ${
|
|
90
|
+
FROM ${KERNL_SCHEMA_NAME}.threads t
|
|
91
|
+
LEFT JOIN ${KERNL_SCHEMA_NAME}.thread_events e ON t.id = e.tid${eventFilter}
|
|
90
92
|
WHERE t.id = $1
|
|
91
93
|
ORDER BY e.seq ${order.toUpperCase()}
|
|
92
94
|
${limit}
|
|
@@ -138,7 +140,7 @@ export class PGThreadStore implements ThreadStore {
|
|
|
138
140
|
|
|
139
141
|
// simple query without events
|
|
140
142
|
const result = await this.db.query<ThreadRecord>(
|
|
141
|
-
`SELECT * FROM ${
|
|
143
|
+
`SELECT * FROM ${KERNL_SCHEMA_NAME}.threads WHERE id = $1`,
|
|
142
144
|
[tid],
|
|
143
145
|
);
|
|
144
146
|
|
|
@@ -159,88 +161,28 @@ export class PGThreadStore implements ThreadStore {
|
|
|
159
161
|
async list(options?: ThreadListOptions): Promise<Thread[]> {
|
|
160
162
|
await this.ensureInit();
|
|
161
163
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// build WHERE clause
|
|
167
|
-
const conditions: string[] = [];
|
|
168
|
-
if (options?.filter) {
|
|
169
|
-
const {
|
|
170
|
-
state,
|
|
171
|
-
agentId,
|
|
172
|
-
parentTaskId,
|
|
173
|
-
createdAfter,
|
|
174
|
-
createdBefore,
|
|
175
|
-
namespace,
|
|
176
|
-
} = options.filter;
|
|
177
|
-
|
|
178
|
-
if (namespace) {
|
|
179
|
-
conditions.push(`namespace = $${paramIndex++}`);
|
|
180
|
-
values.push(namespace);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (state) {
|
|
184
|
-
if (Array.isArray(state)) {
|
|
185
|
-
conditions.push(`state = ANY($${paramIndex++})`);
|
|
186
|
-
values.push(state);
|
|
187
|
-
} else {
|
|
188
|
-
conditions.push(`state = $${paramIndex++}`);
|
|
189
|
-
values.push(state);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (agentId) {
|
|
194
|
-
conditions.push(`agent_id = $${paramIndex++}`);
|
|
195
|
-
values.push(agentId);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (parentTaskId) {
|
|
199
|
-
conditions.push(`parent_task_id = $${paramIndex++}`);
|
|
200
|
-
values.push(parentTaskId);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (createdAfter) {
|
|
204
|
-
conditions.push(`created_at > $${paramIndex++}`);
|
|
205
|
-
values.push(createdAfter.getTime());
|
|
206
|
-
}
|
|
164
|
+
const { sql: where, params } = SQL_WHERE.encode({
|
|
165
|
+
filter: options?.filter,
|
|
166
|
+
startIdx: 1,
|
|
167
|
+
});
|
|
207
168
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
values.push(createdBefore.getTime());
|
|
211
|
-
}
|
|
212
|
-
}
|
|
169
|
+
let idx = params.length + 1;
|
|
170
|
+
let query = `SELECT * FROM ${KERNL_SCHEMA_NAME}.threads`;
|
|
213
171
|
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// build ORDER BY clause
|
|
219
|
-
const orderClauses: string[] = [];
|
|
220
|
-
if (options?.order?.createdAt) {
|
|
221
|
-
orderClauses.push(`created_at ${options.order.createdAt.toUpperCase()}`);
|
|
222
|
-
}
|
|
223
|
-
if (options?.order?.updatedAt) {
|
|
224
|
-
orderClauses.push(`updated_at ${options.order.updatedAt.toUpperCase()}`);
|
|
225
|
-
}
|
|
226
|
-
if (orderClauses.length > 0) {
|
|
227
|
-
query += ` ORDER BY ${orderClauses.join(", ")}`;
|
|
228
|
-
} else {
|
|
229
|
-
// default: most recent first
|
|
230
|
-
query += ` ORDER BY created_at DESC`;
|
|
231
|
-
}
|
|
172
|
+
if (where) query += ` WHERE ${where}`;
|
|
173
|
+
query += ` ORDER BY ${SQL_ORDER.encode({ order: options?.order })}`;
|
|
232
174
|
|
|
233
175
|
if (options?.limit) {
|
|
234
|
-
query += ` LIMIT $${
|
|
235
|
-
|
|
176
|
+
query += ` LIMIT $${idx++}`;
|
|
177
|
+
params.push(options.limit);
|
|
236
178
|
}
|
|
237
179
|
|
|
238
180
|
if (options?.offset) {
|
|
239
|
-
query += ` OFFSET $${
|
|
240
|
-
|
|
181
|
+
query += ` OFFSET $${idx++}`;
|
|
182
|
+
params.push(options.offset);
|
|
241
183
|
}
|
|
242
184
|
|
|
243
|
-
const result = await this.db.query<ThreadRecord>(query,
|
|
185
|
+
const result = await this.db.query<ThreadRecord>(query, params);
|
|
244
186
|
return result.rows
|
|
245
187
|
.map((record) => {
|
|
246
188
|
try {
|
|
@@ -264,7 +206,7 @@ export class PGThreadStore implements ThreadStore {
|
|
|
264
206
|
const record = NewThreadCodec.encode(thread);
|
|
265
207
|
|
|
266
208
|
const result = await this.db.query<ThreadRecord>(
|
|
267
|
-
`INSERT INTO ${
|
|
209
|
+
`INSERT INTO ${KERNL_SCHEMA_NAME}.threads
|
|
268
210
|
(id, namespace, agent_id, model, context, tick, state, parent_task_id, metadata, created_at, updated_at)
|
|
269
211
|
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7, $8, $9::jsonb, $10, $11)
|
|
270
212
|
RETURNING *`,
|
|
@@ -292,45 +234,16 @@ export class PGThreadStore implements ThreadStore {
|
|
|
292
234
|
async update(tid: string, patch: ThreadUpdate): Promise<Thread> {
|
|
293
235
|
await this.ensureInit();
|
|
294
236
|
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (patch.tick !== undefined) {
|
|
300
|
-
updates.push(`tick = $${paramIndex++}`);
|
|
301
|
-
values.push(patch.tick);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (patch.state !== undefined) {
|
|
305
|
-
updates.push(`state = $${paramIndex++}`);
|
|
306
|
-
values.push(patch.state);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (patch.context !== undefined) {
|
|
310
|
-
updates.push(`context = $${paramIndex++}`);
|
|
311
|
-
// NOTE: Store the raw context value, not the Context wrapper.
|
|
312
|
-
//
|
|
313
|
-
// THis may change in the future depending on Context implementation.
|
|
314
|
-
values.push(JSON.stringify(patch.context.context));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (patch.metadata !== undefined) {
|
|
318
|
-
updates.push(`metadata = $${paramIndex++}`);
|
|
319
|
-
values.push(patch.metadata ? JSON.stringify(patch.metadata) : null);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// always update `updated_at`
|
|
323
|
-
updates.push(`updated_at = $${paramIndex++}`);
|
|
324
|
-
values.push(Date.now());
|
|
325
|
-
|
|
326
|
-
values.push(tid); // WHERE id = $N
|
|
237
|
+
const { sql: updates, params } = SQL_UPDATE.encode({ patch, startIdx: 1 });
|
|
238
|
+
const idx = params.length + 1;
|
|
239
|
+
params.push(tid);
|
|
327
240
|
|
|
328
241
|
const result = await this.db.query<ThreadRecord>(
|
|
329
|
-
`UPDATE ${
|
|
330
|
-
SET ${updates
|
|
331
|
-
WHERE id = $${
|
|
242
|
+
`UPDATE ${KERNL_SCHEMA_NAME}.threads
|
|
243
|
+
SET ${updates}
|
|
244
|
+
WHERE id = $${idx}
|
|
332
245
|
RETURNING *`,
|
|
333
|
-
|
|
246
|
+
params,
|
|
334
247
|
);
|
|
335
248
|
|
|
336
249
|
return this.hydrate({ record: result.rows[0] });
|
|
@@ -341,10 +254,10 @@ export class PGThreadStore implements ThreadStore {
|
|
|
341
254
|
*/
|
|
342
255
|
async delete(tid: string): Promise<void> {
|
|
343
256
|
await this.ensureInit();
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
tid,
|
|
347
|
-
|
|
257
|
+
await this.db.query(
|
|
258
|
+
`DELETE FROM ${KERNL_SCHEMA_NAME}.threads WHERE id = $1`,
|
|
259
|
+
[tid],
|
|
260
|
+
);
|
|
348
261
|
}
|
|
349
262
|
|
|
350
263
|
/**
|
|
@@ -356,7 +269,7 @@ export class PGThreadStore implements ThreadStore {
|
|
|
356
269
|
): Promise<ThreadEvent[]> {
|
|
357
270
|
await this.ensureInit();
|
|
358
271
|
|
|
359
|
-
let query = `SELECT * FROM ${
|
|
272
|
+
let query = `SELECT * FROM ${KERNL_SCHEMA_NAME}.thread_events WHERE tid = $1`;
|
|
360
273
|
const values: any[] = [tid];
|
|
361
274
|
let paramIndex = 2;
|
|
362
275
|
|
|
@@ -429,7 +342,7 @@ export class PGThreadStore implements ThreadStore {
|
|
|
429
342
|
|
|
430
343
|
// insert with ON CONFLICT DO NOTHING for idempotency
|
|
431
344
|
await this.db.query(
|
|
432
|
-
`INSERT INTO ${
|
|
345
|
+
`INSERT INTO ${KERNL_SCHEMA_NAME}.thread_events
|
|
433
346
|
(id, tid, seq, kind, timestamp, data, metadata)
|
|
434
347
|
VALUES ${placeholders.join(", ")}
|
|
435
348
|
ON CONFLICT (tid, id) DO NOTHING`,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts","./src/migrations.ts","./src/postgres.ts","./src/sql.ts","./src/storage.ts","./src/__tests__/integration.test.ts","./src/__tests__/memory.test.ts","./src/__tests__/thread.test.ts","./src/memory/sql.ts","./src/memory/store.ts","./src/thread/sql.ts","./src/thread/store.ts"],"version":"5.9.2"}
|