@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.
Files changed (153) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-check-types.log +36 -0
  3. package/CHANGELOG.md +32 -0
  4. package/README.md +124 -0
  5. package/dist/__tests__/integration.test.js +2 -2
  6. package/dist/__tests__/memory-integration.test.d.ts +2 -0
  7. package/dist/__tests__/memory-integration.test.d.ts.map +1 -0
  8. package/dist/__tests__/memory-integration.test.js +287 -0
  9. package/dist/__tests__/memory.test.d.ts +2 -0
  10. package/dist/__tests__/memory.test.d.ts.map +1 -0
  11. package/dist/__tests__/memory.test.js +357 -0
  12. package/dist/index.d.ts +5 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +5 -3
  15. package/dist/memory/sql.d.ts +30 -0
  16. package/dist/memory/sql.d.ts.map +1 -0
  17. package/dist/memory/sql.js +100 -0
  18. package/dist/memory/store.d.ts +41 -0
  19. package/dist/memory/store.d.ts.map +1 -0
  20. package/dist/memory/store.js +114 -0
  21. package/dist/migrations.d.ts +1 -1
  22. package/dist/migrations.d.ts.map +1 -1
  23. package/dist/migrations.js +9 -3
  24. package/dist/pgvector/__tests__/handle.test.d.ts +2 -0
  25. package/dist/pgvector/__tests__/handle.test.d.ts.map +1 -0
  26. package/dist/pgvector/__tests__/handle.test.js +277 -0
  27. package/dist/pgvector/__tests__/hit.test.d.ts +2 -0
  28. package/dist/pgvector/__tests__/hit.test.d.ts.map +1 -0
  29. package/dist/pgvector/__tests__/hit.test.js +134 -0
  30. package/dist/pgvector/__tests__/integration/document.integration.test.d.ts +7 -0
  31. package/dist/pgvector/__tests__/integration/document.integration.test.d.ts.map +1 -0
  32. package/dist/pgvector/__tests__/integration/document.integration.test.js +587 -0
  33. package/dist/pgvector/__tests__/integration/edge.integration.test.d.ts +8 -0
  34. package/dist/pgvector/__tests__/integration/edge.integration.test.d.ts.map +1 -0
  35. package/dist/pgvector/__tests__/integration/edge.integration.test.js +663 -0
  36. package/dist/pgvector/__tests__/integration/filters.integration.test.d.ts +8 -0
  37. package/dist/pgvector/__tests__/integration/filters.integration.test.d.ts.map +1 -0
  38. package/dist/pgvector/__tests__/integration/filters.integration.test.js +609 -0
  39. package/dist/pgvector/__tests__/integration/lifecycle.integration.test.d.ts +8 -0
  40. package/dist/pgvector/__tests__/integration/lifecycle.integration.test.d.ts.map +1 -0
  41. package/dist/pgvector/__tests__/integration/lifecycle.integration.test.js +449 -0
  42. package/dist/pgvector/__tests__/integration/query.integration.test.d.ts +8 -0
  43. package/dist/pgvector/__tests__/integration/query.integration.test.d.ts.map +1 -0
  44. package/dist/pgvector/__tests__/integration/query.integration.test.js +544 -0
  45. package/dist/pgvector/__tests__/search.test.d.ts +2 -0
  46. package/dist/pgvector/__tests__/search.test.d.ts.map +1 -0
  47. package/dist/pgvector/__tests__/search.test.js +279 -0
  48. package/dist/pgvector/handle.d.ts +60 -0
  49. package/dist/pgvector/handle.d.ts.map +1 -0
  50. package/dist/pgvector/handle.js +213 -0
  51. package/dist/pgvector/hit.d.ts +10 -0
  52. package/dist/pgvector/hit.d.ts.map +1 -0
  53. package/dist/pgvector/hit.js +44 -0
  54. package/dist/pgvector/index.d.ts +7 -0
  55. package/dist/pgvector/index.d.ts.map +1 -0
  56. package/dist/pgvector/index.js +5 -0
  57. package/dist/pgvector/search.d.ts +60 -0
  58. package/dist/pgvector/search.d.ts.map +1 -0
  59. package/dist/pgvector/search.js +227 -0
  60. package/dist/pgvector/sql/__tests__/limit.test.d.ts +2 -0
  61. package/dist/pgvector/sql/__tests__/limit.test.d.ts.map +1 -0
  62. package/dist/pgvector/sql/__tests__/limit.test.js +161 -0
  63. package/dist/pgvector/sql/__tests__/order.test.d.ts +2 -0
  64. package/dist/pgvector/sql/__tests__/order.test.d.ts.map +1 -0
  65. package/dist/pgvector/sql/__tests__/order.test.js +218 -0
  66. package/dist/pgvector/sql/__tests__/query.test.d.ts +2 -0
  67. package/dist/pgvector/sql/__tests__/query.test.d.ts.map +1 -0
  68. package/dist/pgvector/sql/__tests__/query.test.js +392 -0
  69. package/dist/pgvector/sql/__tests__/select.test.d.ts +2 -0
  70. package/dist/pgvector/sql/__tests__/select.test.d.ts.map +1 -0
  71. package/dist/pgvector/sql/__tests__/select.test.js +293 -0
  72. package/dist/pgvector/sql/__tests__/where.test.d.ts +2 -0
  73. package/dist/pgvector/sql/__tests__/where.test.d.ts.map +1 -0
  74. package/dist/pgvector/sql/__tests__/where.test.js +488 -0
  75. package/dist/pgvector/sql/index.d.ts +7 -0
  76. package/dist/pgvector/sql/index.d.ts.map +1 -0
  77. package/dist/pgvector/sql/index.js +6 -0
  78. package/dist/pgvector/sql/limit.d.ts +8 -0
  79. package/dist/pgvector/sql/limit.d.ts.map +1 -0
  80. package/dist/pgvector/sql/limit.js +20 -0
  81. package/dist/pgvector/sql/order.d.ts +9 -0
  82. package/dist/pgvector/sql/order.d.ts.map +1 -0
  83. package/dist/pgvector/sql/order.js +47 -0
  84. package/dist/pgvector/sql/query.d.ts +46 -0
  85. package/dist/pgvector/sql/query.d.ts.map +1 -0
  86. package/dist/pgvector/sql/query.js +54 -0
  87. package/dist/pgvector/sql/schema.d.ts +16 -0
  88. package/dist/pgvector/sql/schema.d.ts.map +1 -0
  89. package/dist/pgvector/sql/schema.js +47 -0
  90. package/dist/pgvector/sql/select.d.ts +11 -0
  91. package/dist/pgvector/sql/select.d.ts.map +1 -0
  92. package/dist/pgvector/sql/select.js +87 -0
  93. package/dist/pgvector/sql/where.d.ts +8 -0
  94. package/dist/pgvector/sql/where.d.ts.map +1 -0
  95. package/dist/pgvector/sql/where.js +137 -0
  96. package/dist/pgvector/types.d.ts +20 -0
  97. package/dist/pgvector/types.d.ts.map +1 -0
  98. package/dist/pgvector/types.js +1 -0
  99. package/dist/pgvector/utils.d.ts +18 -0
  100. package/dist/pgvector/utils.d.ts.map +1 -0
  101. package/dist/pgvector/utils.js +22 -0
  102. package/dist/postgres.d.ts +19 -26
  103. package/dist/postgres.d.ts.map +1 -1
  104. package/dist/postgres.js +15 -27
  105. package/dist/storage.d.ts +48 -0
  106. package/dist/storage.d.ts.map +1 -1
  107. package/dist/storage.js +32 -9
  108. package/dist/thread/sql.d.ts +38 -0
  109. package/dist/thread/sql.d.ts.map +1 -0
  110. package/dist/thread/sql.js +112 -0
  111. package/dist/thread/store.d.ts +2 -2
  112. package/dist/thread/store.d.ts.map +1 -1
  113. package/dist/thread/store.js +32 -102
  114. package/package.json +7 -4
  115. package/src/__tests__/integration.test.ts +15 -17
  116. package/src/__tests__/memory-integration.test.ts +355 -0
  117. package/src/__tests__/memory.test.ts +428 -0
  118. package/src/index.ts +19 -3
  119. package/src/memory/sql.ts +141 -0
  120. package/src/memory/store.ts +166 -0
  121. package/src/migrations.ts +13 -3
  122. package/src/pgvector/README.md +50 -0
  123. package/src/pgvector/__tests__/handle.test.ts +335 -0
  124. package/src/pgvector/__tests__/hit.test.ts +165 -0
  125. package/src/pgvector/__tests__/integration/document.integration.test.ts +717 -0
  126. package/src/pgvector/__tests__/integration/edge.integration.test.ts +835 -0
  127. package/src/pgvector/__tests__/integration/filters.integration.test.ts +721 -0
  128. package/src/pgvector/__tests__/integration/lifecycle.integration.test.ts +570 -0
  129. package/src/pgvector/__tests__/integration/query.integration.test.ts +667 -0
  130. package/src/pgvector/__tests__/search.test.ts +366 -0
  131. package/src/pgvector/handle.ts +285 -0
  132. package/src/pgvector/hit.ts +56 -0
  133. package/src/pgvector/index.ts +7 -0
  134. package/src/pgvector/search.ts +330 -0
  135. package/src/pgvector/sql/__tests__/limit.test.ts +180 -0
  136. package/src/pgvector/sql/__tests__/order.test.ts +248 -0
  137. package/src/pgvector/sql/__tests__/query.test.ts +548 -0
  138. package/src/pgvector/sql/__tests__/select.test.ts +367 -0
  139. package/src/pgvector/sql/__tests__/where.test.ts +554 -0
  140. package/src/pgvector/sql/index.ts +14 -0
  141. package/src/pgvector/sql/limit.ts +29 -0
  142. package/src/pgvector/sql/order.ts +55 -0
  143. package/src/pgvector/sql/query.ts +112 -0
  144. package/src/pgvector/sql/schema.ts +61 -0
  145. package/src/pgvector/sql/select.ts +100 -0
  146. package/src/pgvector/sql/where.ts +152 -0
  147. package/src/pgvector/types.ts +21 -0
  148. package/src/pgvector/utils.ts +24 -0
  149. package/src/postgres.ts +31 -33
  150. package/src/storage.ts +77 -9
  151. package/src/thread/sql.ts +159 -0
  152. package/src/thread/store.ts +40 -127
  153. 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 { SCHEMA_NAME, TABLE_MIGRATIONS } from "@kernl-sdk/storage";
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 { migrations } from "./migrations";
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 "${SCHEMA_NAME}"`);
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 "${SCHEMA_NAME}".migrations ORDER BY applied_at ASC`,
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 = migrations.filter((m) => !applied.has(m.id));
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 "${SCHEMA_NAME}".migrations (id, applied_at) VALUES ($1, $2)`,
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 "${SCHEMA_NAME}"."${col._fk.table}" ("${col._fk.column}")`;
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 "${SCHEMA_NAME}"."${table.name}" (
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 "${SCHEMA_NAME}"."${tableName}" (${columns})
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
+ };
@@ -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 ${SCHEMA_NAME}.threads t
89
- LEFT JOIN ${SCHEMA_NAME}.thread_events e ON t.id = e.tid${eventFilter}
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 ${SCHEMA_NAME}.threads WHERE id = $1`,
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
- let query = `SELECT * FROM ${SCHEMA_NAME}.threads`;
163
- const values: any[] = [];
164
- let paramIndex = 1;
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
- if (createdBefore) {
209
- conditions.push(`created_at < $${paramIndex++}`);
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 (conditions.length > 0) {
215
- query += ` WHERE ${conditions.join(" AND ")}`;
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 $${paramIndex++}`;
235
- values.push(options.limit);
176
+ query += ` LIMIT $${idx++}`;
177
+ params.push(options.limit);
236
178
  }
237
179
 
238
180
  if (options?.offset) {
239
- query += ` OFFSET $${paramIndex++}`;
240
- values.push(options.offset);
181
+ query += ` OFFSET $${idx++}`;
182
+ params.push(options.offset);
241
183
  }
242
184
 
243
- const result = await this.db.query<ThreadRecord>(query, values);
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 ${SCHEMA_NAME}.threads
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 updates: string[] = [];
296
- const values: any[] = [];
297
- let paramIndex = 1;
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 ${SCHEMA_NAME}.threads
330
- SET ${updates.join(", ")}
331
- WHERE id = $${paramIndex}
242
+ `UPDATE ${KERNL_SCHEMA_NAME}.threads
243
+ SET ${updates}
244
+ WHERE id = $${idx}
332
245
  RETURNING *`,
333
- values,
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
- await this.db.query(`DELETE FROM ${SCHEMA_NAME}.threads WHERE id = $1`, [
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 ${SCHEMA_NAME}.thread_events WHERE tid = $1`;
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 ${SCHEMA_NAME}.thread_events
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"}