@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.
Files changed (153) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/.turbo/turbo-check-types.log +36 -0
  3. package/CHANGELOG.md +41 -0
  4. package/README.md +124 -0
  5. package/dist/__tests__/integration.test.js +81 -1
  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 +62 -0
  106. package/dist/storage.d.ts.map +1 -1
  107. package/dist/storage.js +55 -10
  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 +7 -3
  112. package/dist/thread/store.d.ts.map +1 -1
  113. package/dist/thread/store.js +46 -105
  114. package/package.json +8 -5
  115. package/src/__tests__/integration.test.ts +114 -15
  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 +102 -11
  151. package/src/thread/sql.ts +159 -0
  152. package/src/thread/store.ts +58 -127
  153. package/tsconfig.tsbuildinfo +1 -0
package/dist/storage.js CHANGED
@@ -1,19 +1,64 @@
1
1
  import assert from "assert";
2
- import { SCHEMA_NAME, TABLE_MIGRATIONS } from "@kernl-sdk/storage";
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
- import { migrations } from "./migrations.js";
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 "${SCHEMA_NAME}"`);
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 "${SCHEMA_NAME}".migrations ORDER BY applied_at ASC`);
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 = migrations.filter((m) => !applied.has(m.id));
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 "${SCHEMA_NAME}".migrations (id, applied_at) VALUES ($1, $2)`, [migration.id, Date.now()]);
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 "${SCHEMA_NAME}"."${col._fk.table}" ("${col._fk.column}")`;
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 "${SCHEMA_NAME}"."${table.name}" (
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 "${SCHEMA_NAME}"."${tableName}" (${columns})
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
+ };
@@ -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
- constructor(db: Pool | PoolClient);
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,EAIL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,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;AAEf;;GAEG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,UAAU,CAA0D;gBAEhE,EAAE,EAAE,IAAI,GAAG,UAAU;IAKjC;;;;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;IAqGvE;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiG1D;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BhD;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IA6C/D;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC;;OAEG;IACG,OAAO,CACX,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,WAAW,EAAE,CAAC;IAsCzB;;;;;;;;;OASG;IACG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkClD;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,YAAY,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;KAAE,GAAG,MAAM;CAkC1E"}
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"}
@@ -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
- constructor(db) {
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 ${SCHEMA_NAME}.threads t
54
- LEFT JOIN ${SCHEMA_NAME}.thread_events e ON t.id = e.tid${eventFilter}
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 ${SCHEMA_NAME}.threads WHERE id = $1`, [tid]);
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
- let query = `SELECT * FROM ${SCHEMA_NAME}.threads`;
114
- const values = [];
115
- let paramIndex = 1;
116
- // build WHERE clause
117
- const conditions = [];
118
- if (options?.filter) {
119
- const { state, agentId, parentTaskId, createdAfter, createdBefore, namespace, } = options.filter;
120
- if (namespace) {
121
- conditions.push(`namespace = $${paramIndex++}`);
122
- values.push(namespace);
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 $${paramIndex++}`;
171
- values.push(options.limit);
131
+ query += ` LIMIT $${idx++}`;
132
+ params.push(options.limit);
172
133
  }
173
134
  if (options?.offset) {
174
- query += ` OFFSET $${paramIndex++}`;
175
- values.push(options.offset);
135
+ query += ` OFFSET $${idx++}`;
136
+ params.push(options.offset);
176
137
  }
177
- const result = await this.db.query(query, values);
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 ${SCHEMA_NAME}.threads
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
- const updates = [];
220
- const values = [];
221
- let paramIndex = 1;
222
- if (patch.tick !== undefined) {
223
- updates.push(`tick = $${paramIndex++}`);
224
- values.push(patch.tick);
225
- }
226
- if (patch.state !== undefined) {
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.db.query(`DELETE FROM ${SCHEMA_NAME}.threads WHERE id = $1`, [
256
- tid,
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
- let query = `SELECT * FROM ${SCHEMA_NAME}.thread_events WHERE tid = $1`;
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
- // Normalize BIGINT (string) to number for zod schema
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 ${SCHEMA_NAME}.thread_events
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.10",
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.1",
43
- "@kernl-sdk/storage": "0.1.10",
44
- "@kernl-sdk/shared": "^0.1.6"
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",