@kernl-sdk/libsql 0.1.38 → 0.1.39
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 +5 -4
- package/CHANGELOG.md +8 -0
- package/README.md +225 -0
- package/dist/__tests__/constraints.test.d.ts +2 -0
- package/dist/__tests__/constraints.test.d.ts.map +1 -0
- package/dist/__tests__/constraints.test.js +97 -0
- package/dist/__tests__/helpers.d.ts +36 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +80 -0
- package/dist/__tests__/memory.create-get.test.d.ts +2 -0
- package/dist/__tests__/memory.create-get.test.d.ts.map +1 -0
- package/dist/__tests__/memory.create-get.test.js +8 -0
- package/dist/__tests__/memory.delete.test.d.ts +2 -0
- package/dist/__tests__/memory.delete.test.d.ts.map +1 -0
- package/dist/__tests__/memory.delete.test.js +6 -0
- package/dist/__tests__/memory.list.test.d.ts +2 -0
- package/dist/__tests__/memory.list.test.d.ts.map +1 -0
- package/dist/__tests__/memory.list.test.js +8 -0
- package/dist/__tests__/memory.update.test.d.ts +2 -0
- package/dist/__tests__/memory.update.test.d.ts.map +1 -0
- package/dist/__tests__/memory.update.test.js +8 -0
- package/dist/__tests__/migrations.test.d.ts +2 -0
- package/dist/__tests__/migrations.test.d.ts.map +1 -0
- package/dist/__tests__/migrations.test.js +68 -0
- package/dist/__tests__/row-codecs.test.d.ts +2 -0
- package/dist/__tests__/row-codecs.test.d.ts.map +1 -0
- package/dist/__tests__/row-codecs.test.js +175 -0
- package/dist/__tests__/sql-utils.test.d.ts +2 -0
- package/dist/__tests__/sql-utils.test.d.ts.map +1 -0
- package/dist/__tests__/sql-utils.test.js +45 -0
- package/dist/__tests__/storage.init.test.d.ts +2 -0
- package/dist/__tests__/storage.init.test.d.ts.map +1 -0
- package/dist/__tests__/storage.init.test.js +63 -0
- package/dist/__tests__/thread.lifecycle.test.d.ts +2 -0
- package/dist/__tests__/thread.lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/thread.lifecycle.test.js +172 -0
- package/dist/__tests__/transaction.test.d.ts +2 -0
- package/dist/__tests__/transaction.test.d.ts.map +1 -0
- package/dist/__tests__/transaction.test.js +16 -0
- package/dist/__tests__/utils.test.d.ts +2 -0
- package/dist/__tests__/utils.test.d.ts.map +1 -0
- package/dist/__tests__/utils.test.js +31 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +46 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/memory/__tests__/create-get.test.d.ts +2 -0
- package/dist/memory/__tests__/create-get.test.d.ts.map +1 -0
- package/dist/memory/__tests__/create-get.test.js +126 -0
- package/dist/memory/__tests__/delete.test.d.ts +2 -0
- package/dist/memory/__tests__/delete.test.d.ts.map +1 -0
- package/dist/memory/__tests__/delete.test.js +96 -0
- package/dist/memory/__tests__/list.test.d.ts +2 -0
- package/dist/memory/__tests__/list.test.d.ts.map +1 -0
- package/dist/memory/__tests__/list.test.js +168 -0
- package/dist/memory/__tests__/sql.test.d.ts +2 -0
- package/dist/memory/__tests__/sql.test.d.ts.map +1 -0
- package/dist/memory/__tests__/sql.test.js +159 -0
- package/dist/memory/__tests__/update.test.d.ts +2 -0
- package/dist/memory/__tests__/update.test.d.ts.map +1 -0
- package/dist/memory/__tests__/update.test.js +113 -0
- package/dist/memory/row.d.ts +11 -0
- package/dist/memory/row.d.ts.map +1 -0
- package/dist/memory/row.js +29 -0
- package/dist/memory/sql.d.ts +34 -0
- package/dist/memory/sql.d.ts.map +1 -0
- package/dist/memory/sql.js +109 -0
- package/dist/memory/store.d.ts +41 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +132 -0
- package/dist/migrations.d.ts +32 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +157 -0
- package/dist/sql.d.ts +28 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +22 -0
- package/dist/storage.d.ts +75 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +123 -0
- package/dist/thread/__tests__/append.test.d.ts +2 -0
- package/dist/thread/__tests__/append.test.d.ts.map +1 -0
- package/dist/thread/__tests__/append.test.js +141 -0
- package/dist/thread/__tests__/history.test.d.ts +2 -0
- package/dist/thread/__tests__/history.test.d.ts.map +1 -0
- package/dist/thread/__tests__/history.test.js +146 -0
- package/dist/thread/__tests__/sql.test.d.ts +2 -0
- package/dist/thread/__tests__/sql.test.d.ts.map +1 -0
- package/dist/thread/__tests__/sql.test.js +129 -0
- package/dist/thread/__tests__/store.test.d.ts +2 -0
- package/dist/thread/__tests__/store.test.d.ts.map +1 -0
- package/dist/thread/__tests__/store.test.js +170 -0
- package/dist/thread/row.d.ts +19 -0
- package/dist/thread/row.d.ts.map +1 -0
- package/dist/thread/row.js +65 -0
- package/dist/thread/sql.d.ts +33 -0
- package/dist/thread/sql.d.ts.map +1 -0
- package/dist/thread/sql.js +112 -0
- package/dist/thread/store.d.ts +67 -0
- package/dist/thread/store.d.ts.map +1 -0
- package/dist/thread/store.js +282 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +21 -0
- package/package.json +15 -11
- package/src/__tests__/constraints.test.ts +123 -0
- package/src/__tests__/helpers.ts +98 -0
- package/src/__tests__/migrations.test.ts +114 -0
- package/src/__tests__/row-codecs.test.ts +201 -0
- package/src/__tests__/sql-utils.test.ts +52 -0
- package/src/__tests__/storage.init.test.ts +92 -0
- package/src/__tests__/thread.lifecycle.test.ts +234 -0
- package/src/__tests__/transaction.test.ts +25 -0
- package/src/__tests__/utils.test.ts +38 -0
- package/src/client.ts +71 -0
- package/src/index.ts +10 -0
- package/src/memory/__tests__/create-get.test.ts +161 -0
- package/src/memory/__tests__/delete.test.ts +124 -0
- package/src/memory/__tests__/list.test.ts +198 -0
- package/src/memory/__tests__/sql.test.ts +186 -0
- package/src/memory/__tests__/update.test.ts +148 -0
- package/src/memory/row.ts +36 -0
- package/src/memory/sql.ts +142 -0
- package/src/memory/store.ts +173 -0
- package/src/migrations.ts +206 -0
- package/src/sql.ts +35 -0
- package/src/storage.ts +170 -0
- package/src/thread/__tests__/append.test.ts +201 -0
- package/src/thread/__tests__/history.test.ts +198 -0
- package/src/thread/__tests__/sql.test.ts +154 -0
- package/src/thread/__tests__/store.test.ts +219 -0
- package/src/thread/row.ts +77 -0
- package/src/thread/sql.ts +153 -0
- package/src/thread/store.ts +381 -0
- package/src/utils.ts +20 -0
- package/LICENSE +0 -201
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-get.test.d.ts","sourceRoot":"","sources":["../../../src/memory/__tests__/create-get.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { create_client, create_storage, testid } from "../../__tests__/helpers.js";
|
|
3
|
+
describe("LibSQLMemoryStore create/get", () => {
|
|
4
|
+
let client;
|
|
5
|
+
let storage;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
client = create_client();
|
|
8
|
+
storage = create_storage(client);
|
|
9
|
+
await storage.memories.list(); // init
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
client.close();
|
|
13
|
+
});
|
|
14
|
+
it("creates and reads text memory", async () => {
|
|
15
|
+
const id = testid("mem");
|
|
16
|
+
const created = await storage.memories.create({
|
|
17
|
+
id,
|
|
18
|
+
scope: { namespace: "default", entityId: "user-1", agentId: "agent-1" },
|
|
19
|
+
kind: "semantic",
|
|
20
|
+
collection: "facts",
|
|
21
|
+
content: { text: "User prefers dark mode" },
|
|
22
|
+
});
|
|
23
|
+
expect(created.id).toBe(id);
|
|
24
|
+
expect(created.content).toEqual({ text: "User prefers dark mode" });
|
|
25
|
+
expect(created.kind).toBe("semantic");
|
|
26
|
+
const found = await storage.memories.get(id);
|
|
27
|
+
expect(found).not.toBeNull();
|
|
28
|
+
expect(found?.content).toEqual({ text: "User prefers dark mode" });
|
|
29
|
+
expect(found?.scope.namespace).toBe("default");
|
|
30
|
+
expect(found?.scope.entityId).toBe("user-1");
|
|
31
|
+
});
|
|
32
|
+
it("creates and reads object memory", async () => {
|
|
33
|
+
const id = testid("mem");
|
|
34
|
+
const objectContent = {
|
|
35
|
+
object: {
|
|
36
|
+
preferences: {
|
|
37
|
+
theme: "dark",
|
|
38
|
+
language: "en",
|
|
39
|
+
notifications: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const created = await storage.memories.create({
|
|
44
|
+
id,
|
|
45
|
+
scope: { namespace: "default" },
|
|
46
|
+
kind: "semantic",
|
|
47
|
+
content: objectContent,
|
|
48
|
+
});
|
|
49
|
+
expect(created.content).toEqual(objectContent);
|
|
50
|
+
const found = await storage.memories.get(id);
|
|
51
|
+
expect(found?.content).toEqual(objectContent);
|
|
52
|
+
});
|
|
53
|
+
it("creates working memory (wmem=true)", async () => {
|
|
54
|
+
const id = testid("mem");
|
|
55
|
+
const created = await storage.memories.create({
|
|
56
|
+
id,
|
|
57
|
+
scope: { namespace: "default" },
|
|
58
|
+
kind: "episodic",
|
|
59
|
+
content: { text: "Current task context" },
|
|
60
|
+
wmem: true,
|
|
61
|
+
});
|
|
62
|
+
expect(created.wmem).toBe(true);
|
|
63
|
+
const found = await storage.memories.get(id);
|
|
64
|
+
expect(found?.wmem).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it("creates short-term memory with expiration", async () => {
|
|
67
|
+
const id = testid("mem");
|
|
68
|
+
const expiresAt = Date.now() + 3600000; // 1 hour from now
|
|
69
|
+
const created = await storage.memories.create({
|
|
70
|
+
id,
|
|
71
|
+
scope: { namespace: "default" },
|
|
72
|
+
kind: "episodic",
|
|
73
|
+
content: { text: "Temporary note" },
|
|
74
|
+
smem: { expiresAt },
|
|
75
|
+
});
|
|
76
|
+
expect(created.smem.expiresAt).toBe(expiresAt);
|
|
77
|
+
const found = await storage.memories.get(id);
|
|
78
|
+
expect(found?.smem.expiresAt).toBe(expiresAt);
|
|
79
|
+
});
|
|
80
|
+
it("gets by id and returns null when missing", async () => {
|
|
81
|
+
const found = await storage.memories.get("nonexistent");
|
|
82
|
+
expect(found).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
it("handles null scope fields", async () => {
|
|
85
|
+
const id = testid("mem");
|
|
86
|
+
const created = await storage.memories.create({
|
|
87
|
+
id,
|
|
88
|
+
scope: {}, // All scope fields null
|
|
89
|
+
kind: "semantic",
|
|
90
|
+
content: { text: "Global memory" },
|
|
91
|
+
});
|
|
92
|
+
expect(created.scope.namespace).toBeUndefined();
|
|
93
|
+
expect(created.scope.entityId).toBeUndefined();
|
|
94
|
+
expect(created.scope.agentId).toBeUndefined();
|
|
95
|
+
const found = await storage.memories.get(id);
|
|
96
|
+
expect(found?.scope.namespace).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
it("stores and retrieves metadata", async () => {
|
|
99
|
+
const id = testid("mem");
|
|
100
|
+
const metadata = { confidence: 0.95, source: "user_input", version: 2 };
|
|
101
|
+
const created = await storage.memories.create({
|
|
102
|
+
id,
|
|
103
|
+
scope: { namespace: "default" },
|
|
104
|
+
kind: "semantic",
|
|
105
|
+
content: { text: "Test" },
|
|
106
|
+
metadata,
|
|
107
|
+
});
|
|
108
|
+
expect(created.metadata).toEqual(metadata);
|
|
109
|
+
const found = await storage.memories.get(id);
|
|
110
|
+
expect(found?.metadata).toEqual(metadata);
|
|
111
|
+
});
|
|
112
|
+
it("sets timestamps on create", async () => {
|
|
113
|
+
const before = Date.now();
|
|
114
|
+
const created = await storage.memories.create({
|
|
115
|
+
id: testid("mem"),
|
|
116
|
+
scope: { namespace: "default" },
|
|
117
|
+
kind: "semantic",
|
|
118
|
+
content: { text: "Test" },
|
|
119
|
+
});
|
|
120
|
+
const after = Date.now();
|
|
121
|
+
expect(created.timestamp).toBeGreaterThanOrEqual(before);
|
|
122
|
+
expect(created.timestamp).toBeLessThanOrEqual(after);
|
|
123
|
+
expect(created.createdAt).toBeGreaterThanOrEqual(before);
|
|
124
|
+
expect(created.updatedAt).toBeGreaterThanOrEqual(before);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.test.d.ts","sourceRoot":"","sources":["../../../src/memory/__tests__/delete.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { create_client, create_storage, testid } from "../../__tests__/helpers.js";
|
|
3
|
+
describe("LibSQLMemoryStore delete", () => {
|
|
4
|
+
let client;
|
|
5
|
+
let storage;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
client = create_client();
|
|
8
|
+
storage = create_storage(client);
|
|
9
|
+
await storage.memories.list(); // init
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
client.close();
|
|
13
|
+
});
|
|
14
|
+
it("deletes by id", async () => {
|
|
15
|
+
const id = testid("mem");
|
|
16
|
+
await storage.memories.create({
|
|
17
|
+
id,
|
|
18
|
+
scope: { namespace: "default" },
|
|
19
|
+
kind: "semantic",
|
|
20
|
+
content: { text: "To be deleted" },
|
|
21
|
+
});
|
|
22
|
+
// Verify exists
|
|
23
|
+
const before = await storage.memories.get(id);
|
|
24
|
+
expect(before).not.toBeNull();
|
|
25
|
+
// Delete
|
|
26
|
+
await storage.memories.delete(id);
|
|
27
|
+
// Verify gone
|
|
28
|
+
const after = await storage.memories.get(id);
|
|
29
|
+
expect(after).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
it("delete is idempotent (no error for missing)", async () => {
|
|
32
|
+
// Should not throw
|
|
33
|
+
await storage.memories.delete("nonexistent");
|
|
34
|
+
});
|
|
35
|
+
it("mdelete removes multiple ids", async () => {
|
|
36
|
+
const ids = [testid("m1"), testid("m2"), testid("m3")];
|
|
37
|
+
// Create memories
|
|
38
|
+
for (const id of ids) {
|
|
39
|
+
await storage.memories.create({
|
|
40
|
+
id,
|
|
41
|
+
scope: { namespace: "default" },
|
|
42
|
+
kind: "semantic",
|
|
43
|
+
content: { text: `Memory ${id}` },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Verify all exist
|
|
47
|
+
for (const id of ids) {
|
|
48
|
+
expect(await storage.memories.get(id)).not.toBeNull();
|
|
49
|
+
}
|
|
50
|
+
// Delete all
|
|
51
|
+
await storage.memories.mdelete(ids);
|
|
52
|
+
// Verify all gone
|
|
53
|
+
for (const id of ids) {
|
|
54
|
+
expect(await storage.memories.get(id)).toBeNull();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
it("mdelete handles partial matches", async () => {
|
|
58
|
+
const existing = testid("existing");
|
|
59
|
+
await storage.memories.create({
|
|
60
|
+
id: existing,
|
|
61
|
+
scope: { namespace: "default" },
|
|
62
|
+
kind: "semantic",
|
|
63
|
+
content: { text: "Exists" },
|
|
64
|
+
});
|
|
65
|
+
// Delete mix of existing and non-existing
|
|
66
|
+
await storage.memories.mdelete([existing, "nonexistent-1", "nonexistent-2"]);
|
|
67
|
+
// Existing should be deleted
|
|
68
|
+
expect(await storage.memories.get(existing)).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
it("mdelete handles empty array", async () => {
|
|
71
|
+
// Create a memory to ensure it's not affected
|
|
72
|
+
const id = testid("mem");
|
|
73
|
+
await storage.memories.create({
|
|
74
|
+
id,
|
|
75
|
+
scope: { namespace: "default" },
|
|
76
|
+
kind: "semantic",
|
|
77
|
+
content: { text: "Should remain" },
|
|
78
|
+
});
|
|
79
|
+
// Should not throw and not affect anything
|
|
80
|
+
await storage.memories.mdelete([]);
|
|
81
|
+
// Memory should still exist
|
|
82
|
+
const found = await storage.memories.get(id);
|
|
83
|
+
expect(found).not.toBeNull();
|
|
84
|
+
});
|
|
85
|
+
it("mdelete with single id", async () => {
|
|
86
|
+
const id = testid("mem");
|
|
87
|
+
await storage.memories.create({
|
|
88
|
+
id,
|
|
89
|
+
scope: { namespace: "default" },
|
|
90
|
+
kind: "semantic",
|
|
91
|
+
content: { text: "Single" },
|
|
92
|
+
});
|
|
93
|
+
await storage.memories.mdelete([id]);
|
|
94
|
+
expect(await storage.memories.get(id)).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.test.d.ts","sourceRoot":"","sources":["../../../src/memory/__tests__/list.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { create_client, create_storage } from "../../__tests__/helpers.js";
|
|
3
|
+
describe("LibSQLMemoryStore list", () => {
|
|
4
|
+
let client;
|
|
5
|
+
let storage;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
client = create_client();
|
|
8
|
+
storage = create_storage(client);
|
|
9
|
+
await storage.memories.list(); // init
|
|
10
|
+
// Seed test data
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
await storage.memories.create({
|
|
13
|
+
id: "m1",
|
|
14
|
+
scope: { namespace: "ns1", entityId: "user-1", agentId: "agent-1" },
|
|
15
|
+
kind: "semantic",
|
|
16
|
+
collection: "facts",
|
|
17
|
+
content: { text: "Fact 1" },
|
|
18
|
+
wmem: true,
|
|
19
|
+
timestamp: now,
|
|
20
|
+
});
|
|
21
|
+
await storage.memories.create({
|
|
22
|
+
id: "m2",
|
|
23
|
+
scope: { namespace: "ns1", entityId: "user-1", agentId: "agent-1" },
|
|
24
|
+
kind: "semantic",
|
|
25
|
+
collection: "preferences",
|
|
26
|
+
content: { text: "Preference 1" },
|
|
27
|
+
wmem: false,
|
|
28
|
+
smem: { expiresAt: now + 3600000 },
|
|
29
|
+
timestamp: now + 100,
|
|
30
|
+
});
|
|
31
|
+
await storage.memories.create({
|
|
32
|
+
id: "m3",
|
|
33
|
+
scope: { namespace: "ns1", entityId: "user-2", agentId: "agent-1" },
|
|
34
|
+
kind: "episodic",
|
|
35
|
+
collection: "facts",
|
|
36
|
+
content: { text: "Fact 2" },
|
|
37
|
+
wmem: true,
|
|
38
|
+
timestamp: now + 200,
|
|
39
|
+
});
|
|
40
|
+
await storage.memories.create({
|
|
41
|
+
id: "m4",
|
|
42
|
+
scope: { namespace: "ns2", entityId: "user-1", agentId: "agent-2" },
|
|
43
|
+
kind: "semantic",
|
|
44
|
+
collection: "facts",
|
|
45
|
+
content: { text: "Fact 3" },
|
|
46
|
+
timestamp: now + 300,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
client.close();
|
|
51
|
+
});
|
|
52
|
+
it("lists all memories without filter", async () => {
|
|
53
|
+
const memories = await storage.memories.list();
|
|
54
|
+
expect(memories.length).toBe(4);
|
|
55
|
+
});
|
|
56
|
+
it("filters by namespace", async () => {
|
|
57
|
+
const memories = await storage.memories.list({
|
|
58
|
+
filter: { scope: { namespace: "ns1" } },
|
|
59
|
+
});
|
|
60
|
+
expect(memories.length).toBe(3);
|
|
61
|
+
expect(memories.every((m) => m.scope.namespace === "ns1")).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it("filters by entityId", async () => {
|
|
64
|
+
const memories = await storage.memories.list({
|
|
65
|
+
filter: { scope: { entityId: "user-1" } },
|
|
66
|
+
});
|
|
67
|
+
expect(memories.length).toBe(3);
|
|
68
|
+
expect(memories.every((m) => m.scope.entityId === "user-1")).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it("filters by agentId", async () => {
|
|
71
|
+
const memories = await storage.memories.list({
|
|
72
|
+
filter: { scope: { agentId: "agent-1" } },
|
|
73
|
+
});
|
|
74
|
+
expect(memories.length).toBe(3);
|
|
75
|
+
});
|
|
76
|
+
it("filters by multiple scope fields", async () => {
|
|
77
|
+
const memories = await storage.memories.list({
|
|
78
|
+
filter: {
|
|
79
|
+
scope: { namespace: "ns1", entityId: "user-1" },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
expect(memories.length).toBe(2);
|
|
83
|
+
});
|
|
84
|
+
it("filters by collection", async () => {
|
|
85
|
+
const memories = await storage.memories.list({
|
|
86
|
+
filter: { collections: ["facts"] },
|
|
87
|
+
});
|
|
88
|
+
expect(memories.length).toBe(3);
|
|
89
|
+
expect(memories.every((m) => m.collection === "facts")).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it("filters by multiple collections", async () => {
|
|
92
|
+
const memories = await storage.memories.list({
|
|
93
|
+
filter: { collections: ["facts", "preferences"] },
|
|
94
|
+
});
|
|
95
|
+
expect(memories.length).toBe(4);
|
|
96
|
+
});
|
|
97
|
+
it("filters by wmem", async () => {
|
|
98
|
+
const wmemTrue = await storage.memories.list({
|
|
99
|
+
filter: { wmem: true },
|
|
100
|
+
});
|
|
101
|
+
expect(wmemTrue.length).toBe(2);
|
|
102
|
+
expect(wmemTrue.every((m) => m.wmem === true)).toBe(true);
|
|
103
|
+
const wmemFalse = await storage.memories.list({
|
|
104
|
+
filter: { wmem: false },
|
|
105
|
+
});
|
|
106
|
+
expect(wmemFalse.length).toBe(2);
|
|
107
|
+
});
|
|
108
|
+
it("filters by smem (has expiration)", async () => {
|
|
109
|
+
const hasSmem = await storage.memories.list({
|
|
110
|
+
filter: { smem: true },
|
|
111
|
+
});
|
|
112
|
+
expect(hasSmem.length).toBe(1);
|
|
113
|
+
expect(hasSmem[0].id).toBe("m2");
|
|
114
|
+
const noSmem = await storage.memories.list({
|
|
115
|
+
filter: { smem: false },
|
|
116
|
+
});
|
|
117
|
+
expect(noSmem.length).toBe(3);
|
|
118
|
+
});
|
|
119
|
+
it("filters by timestamp range", async () => {
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
const m1 = await storage.memories.get("m1");
|
|
122
|
+
const m2 = await storage.memories.get("m2");
|
|
123
|
+
const memories = await storage.memories.list({
|
|
124
|
+
filter: {
|
|
125
|
+
after: m1.timestamp - 1,
|
|
126
|
+
before: m2.timestamp + 1,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
expect(memories.length).toBe(2);
|
|
130
|
+
expect(memories.map((m) => m.id).sort()).toEqual(["m1", "m2"]);
|
|
131
|
+
});
|
|
132
|
+
it("orders by timestamp desc (default)", async () => {
|
|
133
|
+
const memories = await storage.memories.list();
|
|
134
|
+
// Should be in desc order by default
|
|
135
|
+
for (let i = 1; i < memories.length; i++) {
|
|
136
|
+
expect(memories[i - 1].timestamp).toBeGreaterThanOrEqual(memories[i].timestamp);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
it("orders by timestamp asc", async () => {
|
|
140
|
+
const memories = await storage.memories.list({ order: "asc" });
|
|
141
|
+
for (let i = 1; i < memories.length; i++) {
|
|
142
|
+
expect(memories[i - 1].timestamp).toBeLessThanOrEqual(memories[i].timestamp);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
it("applies limit", async () => {
|
|
146
|
+
const memories = await storage.memories.list({ limit: 2 });
|
|
147
|
+
expect(memories.length).toBe(2);
|
|
148
|
+
});
|
|
149
|
+
it("applies offset", async () => {
|
|
150
|
+
const all = await storage.memories.list({ order: "asc" });
|
|
151
|
+
const offset = await storage.memories.list({ order: "asc", offset: 2 });
|
|
152
|
+
expect(offset.length).toBe(2);
|
|
153
|
+
expect(offset[0].id).toBe(all[2].id);
|
|
154
|
+
});
|
|
155
|
+
it("combines filters, order, limit, and offset", async () => {
|
|
156
|
+
const memories = await storage.memories.list({
|
|
157
|
+
filter: {
|
|
158
|
+
scope: { namespace: "ns1" },
|
|
159
|
+
collections: ["facts"],
|
|
160
|
+
},
|
|
161
|
+
order: "asc",
|
|
162
|
+
limit: 1,
|
|
163
|
+
offset: 1,
|
|
164
|
+
});
|
|
165
|
+
expect(memories.length).toBe(1);
|
|
166
|
+
expect(memories[0].id).toBe("m3");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.test.d.ts","sourceRoot":"","sources":["../../../src/memory/__tests__/sql.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { SQL_WHERE, ORDER, SQL_UPDATE } from "../sql.js";
|
|
3
|
+
describe("LibSQL memory SQL codecs", () => {
|
|
4
|
+
describe("SQL_WHERE", () => {
|
|
5
|
+
it("returns empty clause when no filters", () => {
|
|
6
|
+
const result = SQL_WHERE.encode({ filter: undefined });
|
|
7
|
+
expect(result.sql).toBe("");
|
|
8
|
+
expect(result.params).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
it("encodes scope namespace filter", () => {
|
|
11
|
+
const result = SQL_WHERE.encode({
|
|
12
|
+
filter: { scope: { namespace: "default" } },
|
|
13
|
+
});
|
|
14
|
+
expect(result.sql).toBe("namespace = ?");
|
|
15
|
+
expect(result.params).toEqual(["default"]);
|
|
16
|
+
});
|
|
17
|
+
it("encodes scope entityId filter", () => {
|
|
18
|
+
const result = SQL_WHERE.encode({
|
|
19
|
+
filter: { scope: { entityId: "user-1" } },
|
|
20
|
+
});
|
|
21
|
+
expect(result.sql).toBe("entity_id = ?");
|
|
22
|
+
expect(result.params).toEqual(["user-1"]);
|
|
23
|
+
});
|
|
24
|
+
it("encodes scope agentId filter", () => {
|
|
25
|
+
const result = SQL_WHERE.encode({
|
|
26
|
+
filter: { scope: { agentId: "agent-1" } },
|
|
27
|
+
});
|
|
28
|
+
expect(result.sql).toBe("agent_id = ?");
|
|
29
|
+
expect(result.params).toEqual(["agent-1"]);
|
|
30
|
+
});
|
|
31
|
+
it("encodes full scope filter", () => {
|
|
32
|
+
const result = SQL_WHERE.encode({
|
|
33
|
+
filter: {
|
|
34
|
+
scope: {
|
|
35
|
+
namespace: "default",
|
|
36
|
+
entityId: "user-1",
|
|
37
|
+
agentId: "agent-1",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
expect(result.sql).toBe("namespace = ? AND entity_id = ? AND agent_id = ?");
|
|
42
|
+
expect(result.params).toEqual(["default", "user-1", "agent-1"]);
|
|
43
|
+
});
|
|
44
|
+
it("encodes collections filter with IN clause", () => {
|
|
45
|
+
const result = SQL_WHERE.encode({
|
|
46
|
+
filter: { collections: ["facts", "preferences"] },
|
|
47
|
+
});
|
|
48
|
+
expect(result.sql).toBe("collection IN (?, ?)");
|
|
49
|
+
expect(result.params).toEqual(["facts", "preferences"]);
|
|
50
|
+
});
|
|
51
|
+
it("encodes wmem filter", () => {
|
|
52
|
+
const result = SQL_WHERE.encode({ filter: { wmem: true } });
|
|
53
|
+
expect(result.sql).toBe("wmem = ?");
|
|
54
|
+
expect(result.params).toEqual([1]); // SQLite boolean
|
|
55
|
+
});
|
|
56
|
+
it("encodes smem filter (has valid expiration)", () => {
|
|
57
|
+
const before = Date.now();
|
|
58
|
+
const result = SQL_WHERE.encode({ filter: { smem: true } });
|
|
59
|
+
const after = Date.now();
|
|
60
|
+
// smem=true means has expiration AND not expired
|
|
61
|
+
expect(result.sql).toBe("(smem_expires_at IS NOT NULL AND smem_expires_at > ?)");
|
|
62
|
+
expect(result.params.length).toBe(1);
|
|
63
|
+
expect(result.params[0]).toBeGreaterThanOrEqual(before);
|
|
64
|
+
expect(result.params[0]).toBeLessThanOrEqual(after);
|
|
65
|
+
});
|
|
66
|
+
it("encodes smem=false filter (no expiration or expired)", () => {
|
|
67
|
+
const before = Date.now();
|
|
68
|
+
const result = SQL_WHERE.encode({ filter: { smem: false } });
|
|
69
|
+
const after = Date.now();
|
|
70
|
+
// smem=false means no expiration OR already expired
|
|
71
|
+
expect(result.sql).toBe("(smem_expires_at IS NULL OR smem_expires_at <= ?)");
|
|
72
|
+
expect(result.params.length).toBe(1);
|
|
73
|
+
expect(result.params[0]).toBeGreaterThanOrEqual(before);
|
|
74
|
+
expect(result.params[0]).toBeLessThanOrEqual(after);
|
|
75
|
+
});
|
|
76
|
+
it("encodes timestamp range filters", () => {
|
|
77
|
+
const result = SQL_WHERE.encode({
|
|
78
|
+
filter: { after: 1700000000000, before: 1700100000000 },
|
|
79
|
+
});
|
|
80
|
+
expect(result.sql).toBe("timestamp > ? AND timestamp < ?");
|
|
81
|
+
expect(result.params).toEqual([1700000000000, 1700100000000]);
|
|
82
|
+
});
|
|
83
|
+
it("combines multiple filters with AND", () => {
|
|
84
|
+
const result = SQL_WHERE.encode({
|
|
85
|
+
filter: {
|
|
86
|
+
scope: { namespace: "default" },
|
|
87
|
+
wmem: true,
|
|
88
|
+
collections: ["facts"],
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
expect(result.sql).toContain("namespace = ?");
|
|
92
|
+
expect(result.sql).toContain("wmem = ?");
|
|
93
|
+
expect(result.sql).toContain("collection IN (?)");
|
|
94
|
+
expect(result.sql.match(/AND/g)?.length).toBe(2);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe("ORDER", () => {
|
|
98
|
+
it("returns default ordering (desc)", () => {
|
|
99
|
+
const result = ORDER.encode({ order: undefined });
|
|
100
|
+
expect(result).toBe("timestamp DESC");
|
|
101
|
+
});
|
|
102
|
+
it("encodes asc order", () => {
|
|
103
|
+
const result = ORDER.encode({ order: "asc" });
|
|
104
|
+
expect(result).toBe("timestamp ASC");
|
|
105
|
+
});
|
|
106
|
+
it("encodes desc order", () => {
|
|
107
|
+
const result = ORDER.encode({ order: "desc" });
|
|
108
|
+
expect(result).toBe("timestamp DESC");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe("SQL_UPDATE", () => {
|
|
112
|
+
it("encodes content update with JSON stringify", () => {
|
|
113
|
+
const content = { text: "Updated content" };
|
|
114
|
+
const result = SQL_UPDATE.encode({ patch: { id: "m1", content } });
|
|
115
|
+
expect(result.sql).toContain("content = ?");
|
|
116
|
+
expect(result.params).toContain(JSON.stringify(content));
|
|
117
|
+
});
|
|
118
|
+
it("encodes wmem flag update", () => {
|
|
119
|
+
const result = SQL_UPDATE.encode({ patch: { id: "m1", wmem: true } });
|
|
120
|
+
expect(result.sql).toContain("wmem = ?");
|
|
121
|
+
expect(result.params).toContain(1); // SQLite boolean
|
|
122
|
+
});
|
|
123
|
+
it("encodes smem expiration update", () => {
|
|
124
|
+
const result = SQL_UPDATE.encode({
|
|
125
|
+
patch: { id: "m1", smem: { expiresAt: 1700100000000 } },
|
|
126
|
+
});
|
|
127
|
+
expect(result.sql).toContain("smem_expires_at = ?");
|
|
128
|
+
expect(result.params).toContain(1700100000000);
|
|
129
|
+
});
|
|
130
|
+
it("encodes metadata update", () => {
|
|
131
|
+
const metadata = { confidence: 0.95 };
|
|
132
|
+
const result = SQL_UPDATE.encode({ patch: { id: "m1", metadata } });
|
|
133
|
+
expect(result.sql).toContain("metadata = ?");
|
|
134
|
+
expect(result.params).toContain(JSON.stringify(metadata));
|
|
135
|
+
});
|
|
136
|
+
it("always includes updated_at", () => {
|
|
137
|
+
const before = Date.now();
|
|
138
|
+
const result = SQL_UPDATE.encode({ patch: { id: "m1", wmem: false } });
|
|
139
|
+
const after = Date.now();
|
|
140
|
+
expect(result.sql).toContain("updated_at = ?");
|
|
141
|
+
const updatedAt = result.params.find((p) => typeof p === "number" && p >= before && p <= after);
|
|
142
|
+
expect(updatedAt).toBeDefined();
|
|
143
|
+
});
|
|
144
|
+
it("combines multiple updates", () => {
|
|
145
|
+
const result = SQL_UPDATE.encode({
|
|
146
|
+
patch: {
|
|
147
|
+
id: "m1",
|
|
148
|
+
content: { text: "New" },
|
|
149
|
+
wmem: true,
|
|
150
|
+
metadata: { edited: true },
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
expect(result.sql).toContain("content = ?");
|
|
154
|
+
expect(result.sql).toContain("wmem = ?");
|
|
155
|
+
expect(result.sql).toContain("metadata = ?");
|
|
156
|
+
expect(result.sql).toContain("updated_at = ?");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.test.d.ts","sourceRoot":"","sources":["../../../src/memory/__tests__/update.test.ts"],"names":[],"mappings":""}
|