@kernl-sdk/pg 0.1.10 → 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 +4 -5
- package/.turbo/turbo-check-types.log +36 -0
- package/CHANGELOG.md +41 -0
- package/README.md +124 -0
- package/dist/__tests__/integration.test.js +81 -1
- 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 +62 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +55 -10
- 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 +7 -3
- package/dist/thread/store.d.ts.map +1 -1
- package/dist/thread/store.js +46 -105
- package/package.json +8 -5
- package/src/__tests__/integration.test.ts +114 -15
- 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 +102 -11
- package/src/thread/sql.ts +159 -0
- package/src/thread/store.ts +58 -127
- package/tsconfig.tsbuildinfo +1 -0
package/dist/storage.js
CHANGED
|
@@ -1,19 +1,64 @@
|
|
|
1
1
|
import assert from "assert";
|
|
2
|
-
import {
|
|
2
|
+
import { KERNL_SCHEMA_NAME, TABLE_MIGRATIONS } from "@kernl-sdk/storage";
|
|
3
3
|
import { UnimplementedError } from "@kernl-sdk/shared/lib";
|
|
4
4
|
/* pg */
|
|
5
5
|
import { PGThreadStore } from "./thread/store.js";
|
|
6
|
+
import { PGMemoryStore } from "./memory/store.js";
|
|
7
|
+
import { MIGRATIONS } from "./migrations.js";
|
|
6
8
|
import { SQL_IDENTIFIER_REGEX } from "./sql.js";
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Default vector configuration.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_VECTOR_CONFIG = {
|
|
13
|
+
dimensions: 1536,
|
|
14
|
+
similarity: "cosine",
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Resolve vector config, applying defaults.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveVectorConfig(config) {
|
|
20
|
+
if (!config)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (config === true)
|
|
23
|
+
return DEFAULT_VECTOR_CONFIG;
|
|
24
|
+
return {
|
|
25
|
+
dimensions: config.dimensions ?? DEFAULT_VECTOR_CONFIG.dimensions,
|
|
26
|
+
similarity: config.similarity ?? DEFAULT_VECTOR_CONFIG.similarity,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
8
29
|
/**
|
|
9
30
|
* PostgreSQL storage adapter.
|
|
31
|
+
*
|
|
32
|
+
* Storage is lazily initialized on first use via `ensureInit()`. This means
|
|
33
|
+
* callers don't need to explicitly call `init()` - it happens automatically.
|
|
34
|
+
*
|
|
35
|
+
* NOTE: If the number of store methods grows significantly, consider replacing
|
|
36
|
+
* the manual `ensureInit()` calls with a Proxy-based wrapper for foolproof
|
|
37
|
+
* auto-initialization.
|
|
10
38
|
*/
|
|
11
39
|
export class PGStorage {
|
|
12
40
|
pool;
|
|
41
|
+
initPromise = null;
|
|
13
42
|
threads;
|
|
43
|
+
memories;
|
|
14
44
|
constructor(config) {
|
|
15
45
|
this.pool = config.pool;
|
|
16
|
-
this.threads = new PGThreadStore(this.pool);
|
|
46
|
+
this.threads = new PGThreadStore(this.pool, () => this.ensureInit());
|
|
47
|
+
this.memories = new PGMemoryStore(this.pool, () => this.ensureInit());
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Ensure storage is initialized before any operation.
|
|
51
|
+
*
|
|
52
|
+
* Safe to call multiple times - initialization only runs once.
|
|
53
|
+
*/
|
|
54
|
+
async ensureInit() {
|
|
55
|
+
if (!this.initPromise) {
|
|
56
|
+
this.initPromise = this.init().catch((err) => {
|
|
57
|
+
this.initPromise = null;
|
|
58
|
+
throw err;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return this.initPromise;
|
|
17
62
|
}
|
|
18
63
|
/**
|
|
19
64
|
* Bind runtime registries to storage.
|
|
@@ -31,7 +76,7 @@ export class PGStorage {
|
|
|
31
76
|
* Initialize the storage backend.
|
|
32
77
|
*/
|
|
33
78
|
async init() {
|
|
34
|
-
await this.pool.query(`CREATE SCHEMA IF NOT EXISTS "${
|
|
79
|
+
await this.pool.query(`CREATE SCHEMA IF NOT EXISTS "${KERNL_SCHEMA_NAME}"`);
|
|
35
80
|
await this.createTable(TABLE_MIGRATIONS);
|
|
36
81
|
await this.migrate();
|
|
37
82
|
}
|
|
@@ -49,10 +94,10 @@ export class PGStorage {
|
|
|
49
94
|
try {
|
|
50
95
|
await client.query("BEGIN");
|
|
51
96
|
// read applied migration IDs
|
|
52
|
-
const result = await client.query(`SELECT id FROM "${
|
|
97
|
+
const result = await client.query(`SELECT id FROM "${KERNL_SCHEMA_NAME}".migrations ORDER BY applied_at ASC`);
|
|
53
98
|
const applied = new Set(result.rows.map((row) => row.id));
|
|
54
99
|
// filter pending migrations
|
|
55
|
-
const pending =
|
|
100
|
+
const pending = MIGRATIONS.filter((m) => !applied.has(m.id));
|
|
56
101
|
if (pending.length === 0) {
|
|
57
102
|
await client.query("COMMIT");
|
|
58
103
|
return;
|
|
@@ -65,7 +110,7 @@ export class PGStorage {
|
|
|
65
110
|
await this._createTable(client, table);
|
|
66
111
|
},
|
|
67
112
|
});
|
|
68
|
-
await client.query(`INSERT INTO "${
|
|
113
|
+
await client.query(`INSERT INTO "${KERNL_SCHEMA_NAME}".migrations (id, applied_at) VALUES ($1, $2)`, [migration.id, Date.now()]);
|
|
69
114
|
}
|
|
70
115
|
await client.query("COMMIT");
|
|
71
116
|
}
|
|
@@ -105,7 +150,7 @@ export class PGStorage {
|
|
|
105
150
|
}
|
|
106
151
|
// foreign key reference
|
|
107
152
|
if (col._fk) {
|
|
108
|
-
let ref = `REFERENCES "${
|
|
153
|
+
let ref = `REFERENCES "${KERNL_SCHEMA_NAME}"."${col._fk.table}" ("${col._fk.column}")`;
|
|
109
154
|
if (col._onDelete) {
|
|
110
155
|
ref += ` ON DELETE ${col._onDelete}`;
|
|
111
156
|
}
|
|
@@ -149,7 +194,7 @@ export class PGStorage {
|
|
|
149
194
|
}
|
|
150
195
|
const constraints = [...columns, ...tableConstraints];
|
|
151
196
|
const sql = `
|
|
152
|
-
CREATE TABLE IF NOT EXISTS "${
|
|
197
|
+
CREATE TABLE IF NOT EXISTS "${KERNL_SCHEMA_NAME}"."${table.name}" (
|
|
153
198
|
${constraints.join(",\n ")}
|
|
154
199
|
)
|
|
155
200
|
`.trim();
|
|
@@ -187,7 +232,7 @@ export class PGStorage {
|
|
|
187
232
|
const indexName = `idx_${tableName}_${index.columns.join("_")}`;
|
|
188
233
|
const sql = `
|
|
189
234
|
CREATE ${uniqueKeyword} INDEX IF NOT EXISTS "${indexName}"
|
|
190
|
-
ON "${
|
|
235
|
+
ON "${KERNL_SCHEMA_NAME}"."${tableName}" (${columns})
|
|
191
236
|
`.trim();
|
|
192
237
|
await client.query(sql);
|
|
193
238
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread SQL conversion codecs.
|
|
3
|
+
*
|
|
4
|
+
* TODO: generalize object -> SQL conversion into a shared utility
|
|
5
|
+
*/
|
|
6
|
+
import type { Codec } from "@kernl-sdk/shared/lib";
|
|
7
|
+
import type { ThreadFilter, ThreadUpdate, SortOrder } from "kernl";
|
|
8
|
+
export interface SQLClause {
|
|
9
|
+
sql: string;
|
|
10
|
+
params: unknown[];
|
|
11
|
+
}
|
|
12
|
+
export interface WhereInput {
|
|
13
|
+
filter?: ThreadFilter;
|
|
14
|
+
startIdx: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Encode ThreadFilter to SQL WHERE clause.
|
|
18
|
+
*/
|
|
19
|
+
export declare const SQL_WHERE: Codec<WhereInput, SQLClause>;
|
|
20
|
+
export interface OrderInput {
|
|
21
|
+
order?: {
|
|
22
|
+
createdAt?: SortOrder;
|
|
23
|
+
updatedAt?: SortOrder;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Encode order options to SQL ORDER BY clause.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SQL_ORDER: Codec<OrderInput, string>;
|
|
30
|
+
export interface UpdateInput {
|
|
31
|
+
patch: ThreadUpdate;
|
|
32
|
+
startIdx: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Encode ThreadUpdate to SQL SET clause.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SQL_UPDATE: Codec<UpdateInput, SQLClause>;
|
|
38
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/thread/sql.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEnE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,SAAS,CAsDlD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,SAAS,CAAC,EAAE,SAAS,CAAC;KACvB,CAAC;CACH;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,MAAM,CAqB/C,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,WAAW,EAAE,SAAS,CAuCpD,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread SQL conversion codecs.
|
|
3
|
+
*
|
|
4
|
+
* TODO: generalize object -> SQL conversion into a shared utility
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Encode ThreadFilter to SQL WHERE clause.
|
|
8
|
+
*/
|
|
9
|
+
export const SQL_WHERE = {
|
|
10
|
+
encode({ filter, startIdx }) {
|
|
11
|
+
if (!filter) {
|
|
12
|
+
return { sql: "", params: [] };
|
|
13
|
+
}
|
|
14
|
+
const conditions = [];
|
|
15
|
+
const params = [];
|
|
16
|
+
let idx = startIdx;
|
|
17
|
+
if (filter.namespace !== undefined) {
|
|
18
|
+
conditions.push(`namespace = $${idx++}`);
|
|
19
|
+
params.push(filter.namespace);
|
|
20
|
+
}
|
|
21
|
+
if (filter.state !== undefined) {
|
|
22
|
+
if (Array.isArray(filter.state)) {
|
|
23
|
+
conditions.push(`state = ANY($${idx++})`);
|
|
24
|
+
params.push(filter.state);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
conditions.push(`state = $${idx++}`);
|
|
28
|
+
params.push(filter.state);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (filter.agentId !== undefined) {
|
|
32
|
+
conditions.push(`agent_id = $${idx++}`);
|
|
33
|
+
params.push(filter.agentId);
|
|
34
|
+
}
|
|
35
|
+
if (filter.parentTaskId !== undefined) {
|
|
36
|
+
conditions.push(`parent_task_id = $${idx++}`);
|
|
37
|
+
params.push(filter.parentTaskId);
|
|
38
|
+
}
|
|
39
|
+
if (filter.createdAfter !== undefined) {
|
|
40
|
+
conditions.push(`created_at > $${idx++}`);
|
|
41
|
+
params.push(filter.createdAfter.getTime());
|
|
42
|
+
}
|
|
43
|
+
if (filter.createdBefore !== undefined) {
|
|
44
|
+
conditions.push(`created_at < $${idx++}`);
|
|
45
|
+
params.push(filter.createdBefore.getTime());
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "",
|
|
49
|
+
params,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
decode() {
|
|
53
|
+
throw new Error("SQL_WHERE.decode not implemented");
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Encode order options to SQL ORDER BY clause.
|
|
58
|
+
*/
|
|
59
|
+
export const SQL_ORDER = {
|
|
60
|
+
encode({ order }) {
|
|
61
|
+
const clauses = [];
|
|
62
|
+
if (order?.createdAt) {
|
|
63
|
+
clauses.push(`created_at ${order.createdAt.toUpperCase()}`);
|
|
64
|
+
}
|
|
65
|
+
if (order?.updatedAt) {
|
|
66
|
+
clauses.push(`updated_at ${order.updatedAt.toUpperCase()}`);
|
|
67
|
+
}
|
|
68
|
+
if (clauses.length === 0) {
|
|
69
|
+
return "created_at DESC";
|
|
70
|
+
}
|
|
71
|
+
return clauses.join(", ");
|
|
72
|
+
},
|
|
73
|
+
decode() {
|
|
74
|
+
throw new Error("SQL_ORDER.decode not implemented");
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Encode ThreadUpdate to SQL SET clause.
|
|
79
|
+
*/
|
|
80
|
+
export const SQL_UPDATE = {
|
|
81
|
+
encode({ patch, startIdx }) {
|
|
82
|
+
const sets = [];
|
|
83
|
+
const params = [];
|
|
84
|
+
let idx = startIdx;
|
|
85
|
+
if (patch.tick !== undefined) {
|
|
86
|
+
sets.push(`tick = $${idx++}`);
|
|
87
|
+
params.push(patch.tick);
|
|
88
|
+
}
|
|
89
|
+
if (patch.state !== undefined) {
|
|
90
|
+
sets.push(`state = $${idx++}`);
|
|
91
|
+
params.push(patch.state);
|
|
92
|
+
}
|
|
93
|
+
if (patch.context !== undefined) {
|
|
94
|
+
sets.push(`context = $${idx++}`);
|
|
95
|
+
params.push(JSON.stringify(patch.context.context));
|
|
96
|
+
}
|
|
97
|
+
if (patch.metadata !== undefined) {
|
|
98
|
+
sets.push(`metadata = $${idx++}`);
|
|
99
|
+
params.push(patch.metadata ? JSON.stringify(patch.metadata) : null);
|
|
100
|
+
}
|
|
101
|
+
// always update updated_at
|
|
102
|
+
sets.push(`updated_at = $${idx++}`);
|
|
103
|
+
params.push(Date.now());
|
|
104
|
+
return {
|
|
105
|
+
sql: sets.join(", "),
|
|
106
|
+
params,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
decode() {
|
|
110
|
+
throw new Error("SQL_UPDATE.decode not implemented");
|
|
111
|
+
},
|
|
112
|
+
};
|
package/dist/thread/store.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { Pool, PoolClient } from "pg";
|
|
2
|
-
import { type ThreadRecord } from "@kernl-sdk/storage";
|
|
3
|
-
import { Thread, type ThreadEvent } from "kernl/internal";
|
|
4
2
|
import { type AgentRegistry, type ModelRegistry, type ThreadStore, type NewThread, type ThreadUpdate, type ThreadInclude, type ThreadListOptions, type ThreadHistoryOptions } from "kernl";
|
|
3
|
+
import { Thread, type ThreadEvent } from "kernl/internal";
|
|
4
|
+
import { type ThreadRecord } from "@kernl-sdk/storage";
|
|
5
5
|
/**
|
|
6
6
|
* PostgreSQL Thread store implementation.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: All async methods must call `await this.ensureInit()` before
|
|
9
|
+
* any database operations. This ensures schema/tables exist.
|
|
7
10
|
*/
|
|
8
11
|
export declare class PGThreadStore implements ThreadStore {
|
|
9
12
|
private db;
|
|
10
13
|
private registries;
|
|
11
|
-
|
|
14
|
+
private ensureInit;
|
|
15
|
+
constructor(db: Pool | PoolClient, ensureInit: () => Promise<void>);
|
|
12
16
|
/**
|
|
13
17
|
* Bind runtime registries for hydrating Thread instances.
|
|
14
18
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/thread/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAE3C,OAAO,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/thread/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAE3C,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAIL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAI5B;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,UAAU,CAA0D;IAC5E,OAAO,CAAC,UAAU,CAAsB;gBAE5B,EAAE,EAAE,IAAI,GAAG,UAAU,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;IAMlE;;;;OAIG;IACH,IAAI,CAAC,UAAU,EAAE;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI;IAIxE;;OAEG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuGvE;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuC1D;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BhD;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAkB/D;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxC;;OAEG;IACG,OAAO,CACX,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,WAAW,EAAE,CAAC;IAuCzB;;;;;;;;;OASG;IACG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmClD;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,YAAY,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;KAAE,GAAG,MAAM;CAmC1E"}
|
package/dist/thread/store.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import assert from "assert";
|
|
2
|
-
import { SCHEMA_NAME, NewThreadCodec, ThreadEventRecordCodec, } from "@kernl-sdk/storage";
|
|
3
|
-
import { Thread } from "kernl/internal";
|
|
4
2
|
import { Context, } from "kernl";
|
|
3
|
+
import { Thread } from "kernl/internal";
|
|
4
|
+
import { KERNL_SCHEMA_NAME, NewThreadCodec, ThreadEventRecordCodec, } from "@kernl-sdk/storage";
|
|
5
|
+
import { SQL_WHERE, SQL_ORDER, SQL_UPDATE } from "./sql.js";
|
|
5
6
|
/**
|
|
6
7
|
* PostgreSQL Thread store implementation.
|
|
8
|
+
*
|
|
9
|
+
* IMPORTANT: All async methods must call `await this.ensureInit()` before
|
|
10
|
+
* any database operations. This ensures schema/tables exist.
|
|
7
11
|
*/
|
|
8
12
|
export class PGThreadStore {
|
|
9
13
|
db;
|
|
10
14
|
registries;
|
|
11
|
-
|
|
15
|
+
ensureInit;
|
|
16
|
+
constructor(db, ensureInit) {
|
|
12
17
|
this.db = db;
|
|
18
|
+
this.ensureInit = ensureInit;
|
|
13
19
|
this.registries = null;
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
@@ -24,6 +30,7 @@ export class PGThreadStore {
|
|
|
24
30
|
* Get a thread by id.
|
|
25
31
|
*/
|
|
26
32
|
async get(tid, include) {
|
|
33
|
+
await this.ensureInit();
|
|
27
34
|
// JOIN with thread_events if include.history
|
|
28
35
|
if (include?.history) {
|
|
29
36
|
const opts = typeof include.history === "object" ? include.history : undefined;
|
|
@@ -50,8 +57,8 @@ export class PGThreadStore {
|
|
|
50
57
|
e.timestamp,
|
|
51
58
|
e.data,
|
|
52
59
|
e.metadata as event_metadata
|
|
53
|
-
FROM ${
|
|
54
|
-
LEFT JOIN ${
|
|
60
|
+
FROM ${KERNL_SCHEMA_NAME}.threads t
|
|
61
|
+
LEFT JOIN ${KERNL_SCHEMA_NAME}.thread_events e ON t.id = e.tid${eventFilter}
|
|
55
62
|
WHERE t.id = $1
|
|
56
63
|
ORDER BY e.seq ${order.toUpperCase()}
|
|
57
64
|
${limit}
|
|
@@ -95,7 +102,7 @@ export class PGThreadStore {
|
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
104
|
// simple query without events
|
|
98
|
-
const result = await this.db.query(`SELECT * FROM ${
|
|
105
|
+
const result = await this.db.query(`SELECT * FROM ${KERNL_SCHEMA_NAME}.threads WHERE id = $1`, [tid]);
|
|
99
106
|
if (result.rows.length === 0) {
|
|
100
107
|
return null;
|
|
101
108
|
}
|
|
@@ -110,71 +117,25 @@ export class PGThreadStore {
|
|
|
110
117
|
* List threads matching the filter.
|
|
111
118
|
*/
|
|
112
119
|
async list(options) {
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (state) {
|
|
125
|
-
if (Array.isArray(state)) {
|
|
126
|
-
conditions.push(`state = ANY($${paramIndex++})`);
|
|
127
|
-
values.push(state);
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
conditions.push(`state = $${paramIndex++}`);
|
|
131
|
-
values.push(state);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (agentId) {
|
|
135
|
-
conditions.push(`agent_id = $${paramIndex++}`);
|
|
136
|
-
values.push(agentId);
|
|
137
|
-
}
|
|
138
|
-
if (parentTaskId) {
|
|
139
|
-
conditions.push(`parent_task_id = $${paramIndex++}`);
|
|
140
|
-
values.push(parentTaskId);
|
|
141
|
-
}
|
|
142
|
-
if (createdAfter) {
|
|
143
|
-
conditions.push(`created_at > $${paramIndex++}`);
|
|
144
|
-
values.push(createdAfter.getTime());
|
|
145
|
-
}
|
|
146
|
-
if (createdBefore) {
|
|
147
|
-
conditions.push(`created_at < $${paramIndex++}`);
|
|
148
|
-
values.push(createdBefore.getTime());
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (conditions.length > 0) {
|
|
152
|
-
query += ` WHERE ${conditions.join(" AND ")}`;
|
|
153
|
-
}
|
|
154
|
-
// build ORDER BY clause
|
|
155
|
-
const orderClauses = [];
|
|
156
|
-
if (options?.order?.createdAt) {
|
|
157
|
-
orderClauses.push(`created_at ${options.order.createdAt.toUpperCase()}`);
|
|
158
|
-
}
|
|
159
|
-
if (options?.order?.updatedAt) {
|
|
160
|
-
orderClauses.push(`updated_at ${options.order.updatedAt.toUpperCase()}`);
|
|
161
|
-
}
|
|
162
|
-
if (orderClauses.length > 0) {
|
|
163
|
-
query += ` ORDER BY ${orderClauses.join(", ")}`;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
// default: most recent first
|
|
167
|
-
query += ` ORDER BY created_at DESC`;
|
|
168
|
-
}
|
|
120
|
+
await this.ensureInit();
|
|
121
|
+
const { sql: where, params } = SQL_WHERE.encode({
|
|
122
|
+
filter: options?.filter,
|
|
123
|
+
startIdx: 1,
|
|
124
|
+
});
|
|
125
|
+
let idx = params.length + 1;
|
|
126
|
+
let query = `SELECT * FROM ${KERNL_SCHEMA_NAME}.threads`;
|
|
127
|
+
if (where)
|
|
128
|
+
query += ` WHERE ${where}`;
|
|
129
|
+
query += ` ORDER BY ${SQL_ORDER.encode({ order: options?.order })}`;
|
|
169
130
|
if (options?.limit) {
|
|
170
|
-
query += ` LIMIT $${
|
|
171
|
-
|
|
131
|
+
query += ` LIMIT $${idx++}`;
|
|
132
|
+
params.push(options.limit);
|
|
172
133
|
}
|
|
173
134
|
if (options?.offset) {
|
|
174
|
-
query += ` OFFSET $${
|
|
175
|
-
|
|
135
|
+
query += ` OFFSET $${idx++}`;
|
|
136
|
+
params.push(options.offset);
|
|
176
137
|
}
|
|
177
|
-
const result = await this.db.query(query,
|
|
138
|
+
const result = await this.db.query(query, params);
|
|
178
139
|
return result.rows
|
|
179
140
|
.map((record) => {
|
|
180
141
|
try {
|
|
@@ -193,8 +154,9 @@ export class PGThreadStore {
|
|
|
193
154
|
* Insert a new thread into the store.
|
|
194
155
|
*/
|
|
195
156
|
async insert(thread) {
|
|
157
|
+
await this.ensureInit();
|
|
196
158
|
const record = NewThreadCodec.encode(thread);
|
|
197
|
-
const result = await this.db.query(`INSERT INTO ${
|
|
159
|
+
const result = await this.db.query(`INSERT INTO ${KERNL_SCHEMA_NAME}.threads
|
|
198
160
|
(id, namespace, agent_id, model, context, tick, state, parent_task_id, metadata, created_at, updated_at)
|
|
199
161
|
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7, $8, $9::jsonb, $10, $11)
|
|
200
162
|
RETURNING *`, [
|
|
@@ -216,51 +178,29 @@ export class PGThreadStore {
|
|
|
216
178
|
* Update thread runtime state.
|
|
217
179
|
*/
|
|
218
180
|
async update(tid, patch) {
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
updates.push(`state = $${paramIndex++}`);
|
|
228
|
-
values.push(patch.state);
|
|
229
|
-
}
|
|
230
|
-
if (patch.context !== undefined) {
|
|
231
|
-
updates.push(`context = $${paramIndex++}`);
|
|
232
|
-
// NOTE: Store the raw context value, not the Context wrapper.
|
|
233
|
-
//
|
|
234
|
-
// THis may change in the future depending on Context implementation.
|
|
235
|
-
values.push(JSON.stringify(patch.context.context));
|
|
236
|
-
}
|
|
237
|
-
if (patch.metadata !== undefined) {
|
|
238
|
-
updates.push(`metadata = $${paramIndex++}`);
|
|
239
|
-
values.push(patch.metadata ? JSON.stringify(patch.metadata) : null);
|
|
240
|
-
}
|
|
241
|
-
// always update `updated_at`
|
|
242
|
-
updates.push(`updated_at = $${paramIndex++}`);
|
|
243
|
-
values.push(Date.now());
|
|
244
|
-
values.push(tid); // WHERE id = $N
|
|
245
|
-
const result = await this.db.query(`UPDATE ${SCHEMA_NAME}.threads
|
|
246
|
-
SET ${updates.join(", ")}
|
|
247
|
-
WHERE id = $${paramIndex}
|
|
248
|
-
RETURNING *`, values);
|
|
181
|
+
await this.ensureInit();
|
|
182
|
+
const { sql: updates, params } = SQL_UPDATE.encode({ patch, startIdx: 1 });
|
|
183
|
+
const idx = params.length + 1;
|
|
184
|
+
params.push(tid);
|
|
185
|
+
const result = await this.db.query(`UPDATE ${KERNL_SCHEMA_NAME}.threads
|
|
186
|
+
SET ${updates}
|
|
187
|
+
WHERE id = $${idx}
|
|
188
|
+
RETURNING *`, params);
|
|
249
189
|
return this.hydrate({ record: result.rows[0] });
|
|
250
190
|
}
|
|
251
191
|
/**
|
|
252
192
|
* Delete a thread and cascade to thread_events.
|
|
253
193
|
*/
|
|
254
194
|
async delete(tid) {
|
|
255
|
-
await this.
|
|
256
|
-
|
|
257
|
-
]);
|
|
195
|
+
await this.ensureInit();
|
|
196
|
+
await this.db.query(`DELETE FROM ${KERNL_SCHEMA_NAME}.threads WHERE id = $1`, [tid]);
|
|
258
197
|
}
|
|
259
198
|
/**
|
|
260
199
|
* Get the event history for a thread.
|
|
261
200
|
*/
|
|
262
201
|
async history(tid, opts) {
|
|
263
|
-
|
|
202
|
+
await this.ensureInit();
|
|
203
|
+
let query = `SELECT * FROM ${KERNL_SCHEMA_NAME}.thread_events WHERE tid = $1`;
|
|
264
204
|
const values = [tid];
|
|
265
205
|
let paramIndex = 2;
|
|
266
206
|
// - filter:seq -
|
|
@@ -284,8 +224,7 @@ export class PGThreadStore {
|
|
|
284
224
|
const result = await this.db.query(query, values);
|
|
285
225
|
return result.rows.map((record) => ThreadEventRecordCodec.decode({
|
|
286
226
|
...record,
|
|
287
|
-
//
|
|
288
|
-
timestamp: Number(record.timestamp),
|
|
227
|
+
timestamp: Number(record.timestamp), // normalize BIGINT (string) to number for zod schema
|
|
289
228
|
}));
|
|
290
229
|
}
|
|
291
230
|
/**
|
|
@@ -301,6 +240,7 @@ export class PGThreadStore {
|
|
|
301
240
|
async append(events) {
|
|
302
241
|
if (events.length === 0)
|
|
303
242
|
return;
|
|
243
|
+
await this.ensureInit();
|
|
304
244
|
const records = events.map((e) => ThreadEventRecordCodec.encode(e));
|
|
305
245
|
const values = [];
|
|
306
246
|
const placeholders = [];
|
|
@@ -310,7 +250,7 @@ export class PGThreadStore {
|
|
|
310
250
|
values.push(record.id, record.tid, record.seq, record.kind, record.timestamp, record.data, record.metadata);
|
|
311
251
|
}
|
|
312
252
|
// insert with ON CONFLICT DO NOTHING for idempotency
|
|
313
|
-
await this.db.query(`INSERT INTO ${
|
|
253
|
+
await this.db.query(`INSERT INTO ${KERNL_SCHEMA_NAME}.thread_events
|
|
314
254
|
(id, tid, seq, kind, timestamp, data, metadata)
|
|
315
255
|
VALUES ${placeholders.join(", ")}
|
|
316
256
|
ON CONFLICT (tid, id) DO NOTHING`, values);
|
|
@@ -323,6 +263,7 @@ export class PGThreadStore {
|
|
|
323
263
|
const { record, events = [] } = thread;
|
|
324
264
|
const agent = this.registries.agents.get(record.agent_id);
|
|
325
265
|
const model = this.registries.models.get(record.model);
|
|
266
|
+
// (TODO): we might want to allow this in the future, unclear how it would look though..
|
|
326
267
|
if (!agent || !model) {
|
|
327
268
|
throw new Error(`Thread ${record.id} references non-existent agent/model (agent: ${record.agent_id}, model: ${record.model})`);
|
|
328
269
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kernl-sdk/pg",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "PostgreSQL storage adapter for kernl",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kernl",
|
|
@@ -35,15 +35,18 @@
|
|
|
35
35
|
"tsc-alias": "^1.8.10",
|
|
36
36
|
"typescript": "5.9.2",
|
|
37
37
|
"vitest": "^4.0.8",
|
|
38
|
-
"@kernl-sdk/protocol": "^0.2.5"
|
|
38
|
+
"@kernl-sdk/protocol": "^0.2.5",
|
|
39
|
+
"@kernl-sdk/ai": "^0.2.7"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"pg": "^8.16.3",
|
|
42
|
-
"kernl": "^0.6
|
|
43
|
-
"
|
|
44
|
-
"@kernl-sdk/
|
|
43
|
+
"@kernl-sdk/shared": "^0.1.6",
|
|
44
|
+
"kernl": "^0.6.3",
|
|
45
|
+
"@kernl-sdk/retrieval": "^0.1.0",
|
|
46
|
+
"@kernl-sdk/storage": "0.1.12"
|
|
45
47
|
},
|
|
46
48
|
"scripts": {
|
|
49
|
+
"clean": "rm -rf dist",
|
|
47
50
|
"build": "tsc && tsc-alias --resolve-full-paths",
|
|
48
51
|
"dev": "tsc --watch",
|
|
49
52
|
"check-types": "tsc --noEmit",
|