@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
@@ -0,0 +1,428 @@
1
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
2
+ import { Pool } from "pg";
3
+
4
+ import { PGStorage } from "@/storage";
5
+
6
+ const TEST_DB_URL = process.env.KERNL_PG_TEST_URL;
7
+
8
+ describe.sequential("PGMemoryStore", () => {
9
+ if (!TEST_DB_URL) {
10
+ it.skip("requires KERNL_PG_TEST_URL environment variable", () => {});
11
+ return;
12
+ }
13
+
14
+ let pool: Pool;
15
+ let storage: PGStorage;
16
+
17
+ beforeAll(async () => {
18
+ pool = new Pool({ connectionString: TEST_DB_URL });
19
+ storage = new PGStorage({ pool });
20
+
21
+ await pool.query('DROP SCHEMA IF EXISTS "kernl" CASCADE');
22
+ await storage.init();
23
+ });
24
+
25
+ afterAll(async () => {
26
+ await storage.close();
27
+ });
28
+
29
+ beforeEach(async () => {
30
+ await pool.query('TRUNCATE "kernl"."memories" CASCADE');
31
+ });
32
+
33
+ describe("create", () => {
34
+ it("should create a memory with text content", async () => {
35
+ const memory = await storage.memories.create({
36
+ id: "mem-1",
37
+ scope: { namespace: "test", entityId: "user-1" },
38
+ kind: "semantic",
39
+ collection: "facts",
40
+ content: { text: "The user likes coffee" },
41
+ });
42
+
43
+ expect(memory.id).toBe("mem-1");
44
+ expect(memory.scope.namespace).toBe("test");
45
+ expect(memory.scope.entityId).toBe("user-1");
46
+ expect(memory.collection).toBe("facts");
47
+ expect(memory.content).toEqual({ text: "The user likes coffee" });
48
+ expect(memory.wmem).toBe(false);
49
+ expect(memory.smem.expiresAt).toBeNull();
50
+ expect(memory.createdAt).toBeTypeOf("number");
51
+ expect(memory.updatedAt).toBeTypeOf("number");
52
+ });
53
+
54
+ it("should create a memory with object content", async () => {
55
+ const memory = await storage.memories.create({
56
+ id: "mem-2",
57
+ scope: { namespace: "test" },
58
+ kind: "semantic",
59
+ collection: "preferences",
60
+ content: {
61
+ object: { theme: "dark", language: "en" },
62
+ },
63
+ metadata: { source: "settings" },
64
+ });
65
+
66
+ expect(memory.content).toEqual({
67
+ object: { theme: "dark", language: "en" },
68
+ });
69
+ expect(memory.metadata).toEqual({ source: "settings" });
70
+ });
71
+
72
+ it("should create a working memory (wmem=true)", async () => {
73
+ const memory = await storage.memories.create({
74
+ id: "mem-3",
75
+ scope: { namespace: "test" },
76
+ kind: "episodic",
77
+ collection: "working",
78
+ content: { text: "Current task context" },
79
+ wmem: true,
80
+ });
81
+
82
+ expect(memory.wmem).toBe(true);
83
+ });
84
+
85
+ it("should create a short-term memory with expiration", async () => {
86
+ const expiresAt = Date.now() + 3600000; // 1 hour from now
87
+
88
+ const memory = await storage.memories.create({
89
+ id: "mem-4",
90
+ scope: { namespace: "test" },
91
+ kind: "episodic",
92
+ collection: "short-term",
93
+ content: { text: "Temporary information" },
94
+ smem: { expiresAt },
95
+ });
96
+
97
+ expect(memory.smem.expiresAt).toBe(expiresAt);
98
+ });
99
+ });
100
+
101
+ describe("get", () => {
102
+ it("should get a memory by id", async () => {
103
+ await storage.memories.create({
104
+ id: "mem-get-1",
105
+ scope: { namespace: "test" },
106
+ kind: "semantic",
107
+ collection: "facts",
108
+ content: { text: "Test memory" },
109
+ });
110
+
111
+ const memory = await storage.memories.get("mem-get-1");
112
+
113
+ expect(memory).not.toBeNull();
114
+ expect(memory!.id).toBe("mem-get-1");
115
+ expect(memory!.content).toEqual({ text: "Test memory" });
116
+ });
117
+
118
+ it("should return null for non-existent memory", async () => {
119
+ const memory = await storage.memories.get("non-existent");
120
+ expect(memory).toBeNull();
121
+ });
122
+ });
123
+
124
+ describe("list", () => {
125
+ beforeEach(async () => {
126
+ // seed test data
127
+ await storage.memories.create({
128
+ id: "list-1",
129
+ scope: { namespace: "ns1", entityId: "user-1" },
130
+ kind: "semantic",
131
+ collection: "facts",
132
+ content: { text: "Fact 1" },
133
+ wmem: true,
134
+ timestamp: 1000,
135
+ });
136
+ await storage.memories.create({
137
+ id: "list-2",
138
+ scope: { namespace: "ns1", entityId: "user-1" },
139
+ kind: "semantic",
140
+ collection: "preferences",
141
+ content: { text: "Pref 1" },
142
+ wmem: false,
143
+ timestamp: 2000,
144
+ });
145
+ await storage.memories.create({
146
+ id: "list-3",
147
+ scope: { namespace: "ns2", entityId: "user-2" },
148
+ kind: "semantic",
149
+ collection: "facts",
150
+ content: { text: "Fact 2" },
151
+ wmem: false,
152
+ timestamp: 3000,
153
+ });
154
+ });
155
+
156
+ it("should list all memories", async () => {
157
+ const memories = await storage.memories.list();
158
+ expect(memories).toHaveLength(3);
159
+ });
160
+
161
+ it("should filter by namespace", async () => {
162
+ const memories = await storage.memories.list({
163
+ filter: { scope: { namespace: "ns1" } },
164
+ });
165
+
166
+ expect(memories).toHaveLength(2);
167
+ expect(memories.every((m) => m.scope.namespace === "ns1")).toBe(true);
168
+ });
169
+
170
+ it("should filter by entityId", async () => {
171
+ const memories = await storage.memories.list({
172
+ filter: { scope: { entityId: "user-1" } },
173
+ });
174
+
175
+ expect(memories).toHaveLength(2);
176
+ expect(memories.every((m) => m.scope.entityId === "user-1")).toBe(true);
177
+ });
178
+
179
+ it("should filter by collection", async () => {
180
+ const memories = await storage.memories.list({
181
+ filter: { collections: ["facts"] },
182
+ });
183
+
184
+ expect(memories).toHaveLength(2);
185
+ expect(memories.every((m) => m.collection === "facts")).toBe(true);
186
+ });
187
+
188
+ it("should filter by wmem", async () => {
189
+ const memories = await storage.memories.list({
190
+ filter: { wmem: true },
191
+ });
192
+
193
+ expect(memories).toHaveLength(1);
194
+ expect(memories[0].id).toBe("list-1");
195
+ });
196
+
197
+ it("should order by timestamp desc (default)", async () => {
198
+ const memories = await storage.memories.list();
199
+
200
+ expect(memories[0].id).toBe("list-3");
201
+ expect(memories[1].id).toBe("list-2");
202
+ expect(memories[2].id).toBe("list-1");
203
+ });
204
+
205
+ it("should order by timestamp asc", async () => {
206
+ const memories = await storage.memories.list({ order: "asc" });
207
+
208
+ expect(memories[0].id).toBe("list-1");
209
+ expect(memories[1].id).toBe("list-2");
210
+ expect(memories[2].id).toBe("list-3");
211
+ });
212
+
213
+ it("should apply limit", async () => {
214
+ const memories = await storage.memories.list({ limit: 2 });
215
+ expect(memories).toHaveLength(2);
216
+ });
217
+
218
+ it("should apply offset", async () => {
219
+ const memories = await storage.memories.list({ limit: 2, offset: 1 });
220
+
221
+ expect(memories).toHaveLength(2);
222
+ expect(memories[0].id).toBe("list-2");
223
+ });
224
+
225
+ it("should filter by timestamp range", async () => {
226
+ const memories = await storage.memories.list({
227
+ filter: { after: 1500, before: 2500 },
228
+ });
229
+
230
+ expect(memories).toHaveLength(1);
231
+ expect(memories[0].id).toBe("list-2");
232
+ });
233
+ });
234
+
235
+ describe("update", () => {
236
+ it("should update content", async () => {
237
+ await storage.memories.create({
238
+ id: "update-1",
239
+ scope: { namespace: "test" },
240
+ kind: "semantic",
241
+ collection: "facts",
242
+ content: { text: "Original" },
243
+ });
244
+
245
+ // Small delay to ensure timestamp changes
246
+ await new Promise((resolve) => setTimeout(resolve, 10));
247
+
248
+ const updated = await storage.memories.update("update-1", {
249
+ id: "update-1",
250
+ content: { text: "Updated" },
251
+ });
252
+
253
+ expect(updated.content).toEqual({ text: "Updated" });
254
+ expect(updated.updatedAt).toBeGreaterThan(updated.createdAt);
255
+ });
256
+
257
+ it("should update wmem flag", async () => {
258
+ await storage.memories.create({
259
+ id: "update-2",
260
+ scope: { namespace: "test" },
261
+ kind: "semantic",
262
+ collection: "facts",
263
+ content: { text: "Test" },
264
+ wmem: false,
265
+ });
266
+
267
+ const updated = await storage.memories.update("update-2", {
268
+ id: "update-2",
269
+ wmem: true,
270
+ });
271
+ expect(updated.wmem).toBe(true);
272
+ });
273
+
274
+ it("should update smem expiration", async () => {
275
+ await storage.memories.create({
276
+ id: "update-3",
277
+ scope: { namespace: "test" },
278
+ kind: "episodic",
279
+ collection: "facts",
280
+ content: { text: "Test" },
281
+ });
282
+
283
+ const expiresAt = Date.now() + 7200000;
284
+ const updated = await storage.memories.update("update-3", {
285
+ id: "update-3",
286
+ smem: { expiresAt },
287
+ });
288
+
289
+ expect(updated.smem.expiresAt).toBe(expiresAt);
290
+ });
291
+
292
+ it("should update metadata", async () => {
293
+ await storage.memories.create({
294
+ id: "update-4",
295
+ scope: { namespace: "test" },
296
+ kind: "semantic",
297
+ collection: "facts",
298
+ content: { text: "Test" },
299
+ });
300
+
301
+ const updated = await storage.memories.update("update-4", {
302
+ id: "update-4",
303
+ metadata: { updated: true, count: 1 },
304
+ });
305
+
306
+ expect(updated.metadata).toEqual({ updated: true, count: 1 });
307
+ });
308
+
309
+ it("should throw for non-existent memory", async () => {
310
+ await expect(
311
+ storage.memories.update("non-existent", { id: "non-existent", wmem: true }),
312
+ ).rejects.toThrow("memory not found");
313
+ });
314
+ });
315
+
316
+ describe("delete", () => {
317
+ it("should delete a memory", async () => {
318
+ await storage.memories.create({
319
+ id: "delete-1",
320
+ scope: { namespace: "test" },
321
+ kind: "semantic",
322
+ collection: "facts",
323
+ content: { text: "To be deleted" },
324
+ });
325
+
326
+ await storage.memories.delete("delete-1");
327
+
328
+ const memory = await storage.memories.get("delete-1");
329
+ expect(memory).toBeNull();
330
+ });
331
+
332
+ it("should not throw for non-existent memory", async () => {
333
+ await expect(storage.memories.delete("non-existent")).resolves.not.toThrow();
334
+ });
335
+ });
336
+
337
+ describe("mdelete", () => {
338
+ it("should delete multiple memories", async () => {
339
+ await storage.memories.create({
340
+ id: "mdelete-1",
341
+ scope: { namespace: "test" },
342
+ kind: "semantic",
343
+ collection: "facts",
344
+ content: { text: "Memory 1" },
345
+ });
346
+ await storage.memories.create({
347
+ id: "mdelete-2",
348
+ scope: { namespace: "test" },
349
+ kind: "semantic",
350
+ collection: "facts",
351
+ content: { text: "Memory 2" },
352
+ });
353
+ await storage.memories.create({
354
+ id: "mdelete-3",
355
+ scope: { namespace: "test" },
356
+ kind: "semantic",
357
+ collection: "facts",
358
+ content: { text: "Memory 3" },
359
+ });
360
+
361
+ await storage.memories.mdelete(["mdelete-1", "mdelete-2"]);
362
+
363
+ const remaining = await storage.memories.list();
364
+ expect(remaining).toHaveLength(1);
365
+ expect(remaining[0].id).toBe("mdelete-3");
366
+ });
367
+
368
+ it("should handle empty array", async () => {
369
+ await expect(storage.memories.mdelete([])).resolves.not.toThrow();
370
+ });
371
+ });
372
+
373
+ describe("smem filter", () => {
374
+ beforeEach(async () => {
375
+ const now = Date.now();
376
+
377
+ // active short-term memory
378
+ await storage.memories.create({
379
+ id: "smem-active",
380
+ scope: { namespace: "test" },
381
+ kind: "episodic",
382
+ collection: "short-term",
383
+ content: { text: "Active" },
384
+ smem: { expiresAt: now + 3600000 }, // expires in 1 hour
385
+ });
386
+
387
+ // expired short-term memory
388
+ await storage.memories.create({
389
+ id: "smem-expired",
390
+ scope: { namespace: "test" },
391
+ kind: "episodic",
392
+ collection: "short-term",
393
+ content: { text: "Expired" },
394
+ smem: { expiresAt: now - 3600000 }, // expired 1 hour ago
395
+ });
396
+
397
+ // long-term memory (no expiration)
398
+ await storage.memories.create({
399
+ id: "lmem",
400
+ scope: { namespace: "test" },
401
+ kind: "semantic",
402
+ collection: "long-term",
403
+ content: { text: "Long term" },
404
+ smem: { expiresAt: null },
405
+ });
406
+ });
407
+
408
+ it("should filter for active short-term memories (smem=true)", async () => {
409
+ const memories = await storage.memories.list({
410
+ filter: { smem: true },
411
+ });
412
+
413
+ expect(memories).toHaveLength(1);
414
+ expect(memories[0].id).toBe("smem-active");
415
+ });
416
+
417
+ it("should filter for non-short-term memories (smem=false)", async () => {
418
+ const memories = await storage.memories.list({
419
+ filter: { smem: false },
420
+ });
421
+
422
+ expect(memories).toHaveLength(2);
423
+ const ids = memories.map((m) => m.id);
424
+ expect(ids).toContain("smem-expired");
425
+ expect(ids).toContain("lmem");
426
+ });
427
+ });
428
+ });
package/src/index.ts CHANGED
@@ -2,6 +2,22 @@
2
2
  * @kernl/pg - PostgreSQL storage adapter for Kernl
3
3
  */
4
4
 
5
- export { PGStorage, type PGStorageConfig } from "./storage";
6
- export { postgres, type PostgresConfig } from "./postgres";
7
- export { migrations, REQUIRED_SCHEMA_VERSION } from "./migrations";
5
+ export {
6
+ PGStorage,
7
+ type PGStorageConfig,
8
+ type PGVectorConfig,
9
+ type VectorSimilarity,
10
+ type ResolvedVectorConfig,
11
+ DEFAULT_VECTOR_CONFIG,
12
+ resolveVectorConfig,
13
+ } from "./storage";
14
+ export { PGMemoryStore } from "./memory/store";
15
+ export { postgres, pgvector, type PostgresConfig } from "./postgres";
16
+ export { MIGRATIONS, REQUIRED_SCHEMA_VERSION } from "./migrations";
17
+ export {
18
+ PGSearchIndex,
19
+ PGIndexHandle,
20
+ type PGSearchIndexConfig,
21
+ type PGIndexConfig,
22
+ type PGFieldBinding,
23
+ } from "./pgvector";
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Memory 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 { MemoryFilter, MemoryRecordUpdate } from "kernl";
9
+
10
+ export interface SQLClause {
11
+ sql: string;
12
+ params: unknown[];
13
+ }
14
+
15
+ export interface WhereInput {
16
+ filter?: MemoryFilter;
17
+ startIdx: number;
18
+ }
19
+
20
+ export const SQL_WHERE: Codec<WhereInput, SQLClause> = {
21
+ encode({ filter, startIdx }) {
22
+ if (!filter) {
23
+ return { sql: "", params: [] };
24
+ }
25
+
26
+ const conditions: string[] = [];
27
+ const params: unknown[] = [];
28
+ let idx = startIdx;
29
+
30
+ if (filter.scope?.namespace !== undefined) {
31
+ conditions.push(`namespace = $${idx++}`);
32
+ params.push(filter.scope.namespace);
33
+ }
34
+ if (filter.scope?.entityId !== undefined) {
35
+ conditions.push(`entity_id = $${idx++}`);
36
+ params.push(filter.scope.entityId);
37
+ }
38
+ if (filter.scope?.agentId !== undefined) {
39
+ conditions.push(`agent_id = $${idx++}`);
40
+ params.push(filter.scope.agentId);
41
+ }
42
+ if (filter.collections && filter.collections.length > 0) {
43
+ conditions.push(`collection = ANY($${idx++})`);
44
+ params.push(filter.collections);
45
+ }
46
+ if (filter.wmem !== undefined) {
47
+ conditions.push(`wmem = $${idx++}`);
48
+ params.push(filter.wmem);
49
+ }
50
+ if (filter.smem === true) {
51
+ conditions.push(
52
+ `(smem_expires_at IS NOT NULL AND smem_expires_at > $${idx++})`,
53
+ );
54
+ params.push(Date.now());
55
+ } else if (filter.smem === false) {
56
+ conditions.push(
57
+ `(smem_expires_at IS NULL OR smem_expires_at <= $${idx++})`,
58
+ );
59
+ params.push(Date.now());
60
+ }
61
+ if (filter.after !== undefined) {
62
+ conditions.push(`timestamp > $${idx++}`);
63
+ params.push(filter.after);
64
+ }
65
+ if (filter.before !== undefined) {
66
+ conditions.push(`timestamp < $${idx++}`);
67
+ params.push(filter.before);
68
+ }
69
+
70
+ return {
71
+ sql: conditions.length > 0 ? conditions.join(" AND ") : "",
72
+ params,
73
+ };
74
+ },
75
+
76
+ decode() {
77
+ throw new Error("WHERE.decode not implemented");
78
+ },
79
+ };
80
+
81
+ type OrderDirection = "asc" | "desc";
82
+
83
+ export interface OrderInput {
84
+ order?: OrderDirection;
85
+ defaultColumn?: string;
86
+ defaultDirection?: OrderDirection;
87
+ }
88
+
89
+ export const ORDER: Codec<OrderInput, string> = {
90
+ encode({ order, defaultColumn = "timestamp", defaultDirection = "desc" }) {
91
+ const dir = (order ?? defaultDirection).toUpperCase();
92
+ return `${defaultColumn} ${dir}`;
93
+ },
94
+
95
+ decode() {
96
+ throw new Error("ORDER.decode not implemented");
97
+ },
98
+ };
99
+
100
+ export interface PatchInput {
101
+ patch: MemoryRecordUpdate;
102
+ startIdx: number;
103
+ }
104
+
105
+ export const SQL_UPDATE: Codec<PatchInput, SQLClause> = {
106
+ encode({ patch, startIdx }) {
107
+ const sets: string[] = [];
108
+ const params: unknown[] = [];
109
+ let idx = startIdx;
110
+
111
+ if (patch.content !== undefined) {
112
+ sets.push(`content = $${idx++}::jsonb`);
113
+ params.push(JSON.stringify(patch.content));
114
+ }
115
+ if (patch.wmem !== undefined) {
116
+ sets.push(`wmem = $${idx++}`);
117
+ params.push(patch.wmem);
118
+ }
119
+ if (patch.smem !== undefined) {
120
+ sets.push(`smem_expires_at = $${idx++}`);
121
+ params.push(patch.smem.expiresAt);
122
+ }
123
+ if (patch.metadata !== undefined) {
124
+ sets.push(`metadata = $${idx++}::jsonb`);
125
+ params.push(patch.metadata ? JSON.stringify(patch.metadata) : null);
126
+ }
127
+
128
+ // always update updated_at
129
+ sets.push(`updated_at = $${idx++}`);
130
+ params.push(Date.now());
131
+
132
+ return {
133
+ sql: sets.join(", "),
134
+ params,
135
+ };
136
+ },
137
+
138
+ decode() {
139
+ throw new Error("PATCH.decode not implemented");
140
+ },
141
+ };