@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
package/src/client.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LibSQL client configuration and factory.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { KernlStorage } from "kernl";
|
|
6
|
+
import { createClient, type Client, type Config } from "@libsql/client";
|
|
7
|
+
|
|
8
|
+
import { LibSQLStorage } from "./storage";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* LibSQL connection configuration.
|
|
12
|
+
*
|
|
13
|
+
* Supports:
|
|
14
|
+
* - Local SQLite files: `file:./data.db` or `file:/path/to/data.db`
|
|
15
|
+
* - In-memory: `:memory:`
|
|
16
|
+
* - Remote Turso: `libsql://your-db.turso.io`
|
|
17
|
+
*/
|
|
18
|
+
export type LibSQLConnectionConfig =
|
|
19
|
+
| { client: Client }
|
|
20
|
+
| { url: string; authToken?: string };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* LibSQL storage configuration.
|
|
24
|
+
*/
|
|
25
|
+
export type LibSQLConfig = LibSQLConnectionConfig;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a LibSQL storage adapter for Kernl.
|
|
29
|
+
*
|
|
30
|
+
* @example Local file
|
|
31
|
+
* ```ts
|
|
32
|
+
* const storage = libsql({ url: 'file:./kernl.db' });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example In-memory (for testing)
|
|
36
|
+
* ```ts
|
|
37
|
+
* const storage = libsql({ url: ':memory:' });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example Remote Turso
|
|
41
|
+
* ```ts
|
|
42
|
+
* const storage = libsql({
|
|
43
|
+
* url: 'libsql://your-db.turso.io',
|
|
44
|
+
* authToken: process.env.TURSO_AUTH_TOKEN
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function libsql(config: LibSQLConfig): KernlStorage {
|
|
49
|
+
const client = createLibSQLClient(config);
|
|
50
|
+
const url = "url" in config ? config.url : undefined;
|
|
51
|
+
return new LibSQLStorage({ client, url });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a LibSQL client from configuration.
|
|
56
|
+
*/
|
|
57
|
+
function createLibSQLClient(config: LibSQLConnectionConfig): Client {
|
|
58
|
+
if ("client" in config) {
|
|
59
|
+
return config.client;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const clientConfig: Config = {
|
|
63
|
+
url: config.url,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (config.authToken) {
|
|
67
|
+
clientConfig.authToken = config.authToken;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return createClient(clientConfig);
|
|
71
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @kernl/libsql - LibSQL storage adapter for Kernl
|
|
3
3
|
*/
|
|
4
|
+
|
|
5
|
+
export { LibSQLStorage, type LibSQLStorageConfig } from "./storage";
|
|
6
|
+
export { LibSQLThreadStore } from "./thread/store";
|
|
7
|
+
export { LibSQLMemoryStore } from "./memory/store";
|
|
8
|
+
export {
|
|
9
|
+
libsql,
|
|
10
|
+
type LibSQLConfig,
|
|
11
|
+
type LibSQLConnectionConfig,
|
|
12
|
+
} from "./client";
|
|
13
|
+
export { MIGRATIONS, REQUIRED_SCHEMA_VERSION } from "./migrations";
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import type { Client } from "@libsql/client";
|
|
3
|
+
|
|
4
|
+
import { create_client, create_storage, testid } from "../../__tests__/helpers";
|
|
5
|
+
import { LibSQLStorage } from "../../storage";
|
|
6
|
+
|
|
7
|
+
describe("LibSQLMemoryStore create/get", () => {
|
|
8
|
+
let client: Client;
|
|
9
|
+
let storage: LibSQLStorage;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
client = create_client();
|
|
13
|
+
storage = create_storage(client);
|
|
14
|
+
await storage.memories.list(); // init
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
client.close();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("creates and reads text memory", async () => {
|
|
22
|
+
const id = testid("mem");
|
|
23
|
+
|
|
24
|
+
const created = await storage.memories.create({
|
|
25
|
+
id,
|
|
26
|
+
scope: { namespace: "default", entityId: "user-1", agentId: "agent-1" },
|
|
27
|
+
kind: "semantic",
|
|
28
|
+
collection: "facts",
|
|
29
|
+
content: { text: "User prefers dark mode" },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(created.id).toBe(id);
|
|
33
|
+
expect(created.content).toEqual({ text: "User prefers dark mode" });
|
|
34
|
+
expect(created.kind).toBe("semantic");
|
|
35
|
+
|
|
36
|
+
const found = await storage.memories.get(id);
|
|
37
|
+
expect(found).not.toBeNull();
|
|
38
|
+
expect(found?.content).toEqual({ text: "User prefers dark mode" });
|
|
39
|
+
expect(found?.scope.namespace).toBe("default");
|
|
40
|
+
expect(found?.scope.entityId).toBe("user-1");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("creates and reads object memory", async () => {
|
|
44
|
+
const id = testid("mem");
|
|
45
|
+
const objectContent = {
|
|
46
|
+
object: {
|
|
47
|
+
preferences: {
|
|
48
|
+
theme: "dark",
|
|
49
|
+
language: "en",
|
|
50
|
+
notifications: true,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const created = await storage.memories.create({
|
|
56
|
+
id,
|
|
57
|
+
scope: { namespace: "default" },
|
|
58
|
+
kind: "semantic",
|
|
59
|
+
content: objectContent,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(created.content).toEqual(objectContent);
|
|
63
|
+
|
|
64
|
+
const found = await storage.memories.get(id);
|
|
65
|
+
expect(found?.content).toEqual(objectContent);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("creates working memory (wmem=true)", async () => {
|
|
69
|
+
const id = testid("mem");
|
|
70
|
+
|
|
71
|
+
const created = await storage.memories.create({
|
|
72
|
+
id,
|
|
73
|
+
scope: { namespace: "default" },
|
|
74
|
+
kind: "episodic",
|
|
75
|
+
content: { text: "Current task context" },
|
|
76
|
+
wmem: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(created.wmem).toBe(true);
|
|
80
|
+
|
|
81
|
+
const found = await storage.memories.get(id);
|
|
82
|
+
expect(found?.wmem).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("creates short-term memory with expiration", async () => {
|
|
86
|
+
const id = testid("mem");
|
|
87
|
+
const expiresAt = Date.now() + 3600000; // 1 hour from now
|
|
88
|
+
|
|
89
|
+
const created = await storage.memories.create({
|
|
90
|
+
id,
|
|
91
|
+
scope: { namespace: "default" },
|
|
92
|
+
kind: "episodic",
|
|
93
|
+
content: { text: "Temporary note" },
|
|
94
|
+
smem: { expiresAt },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(created.smem.expiresAt).toBe(expiresAt);
|
|
98
|
+
|
|
99
|
+
const found = await storage.memories.get(id);
|
|
100
|
+
expect(found?.smem.expiresAt).toBe(expiresAt);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("gets by id and returns null when missing", async () => {
|
|
104
|
+
const found = await storage.memories.get("nonexistent");
|
|
105
|
+
expect(found).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("handles null scope fields", async () => {
|
|
109
|
+
const id = testid("mem");
|
|
110
|
+
|
|
111
|
+
const created = await storage.memories.create({
|
|
112
|
+
id,
|
|
113
|
+
scope: {}, // All scope fields null
|
|
114
|
+
kind: "semantic",
|
|
115
|
+
content: { text: "Global memory" },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(created.scope.namespace).toBeUndefined();
|
|
119
|
+
expect(created.scope.entityId).toBeUndefined();
|
|
120
|
+
expect(created.scope.agentId).toBeUndefined();
|
|
121
|
+
|
|
122
|
+
const found = await storage.memories.get(id);
|
|
123
|
+
expect(found?.scope.namespace).toBeUndefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("stores and retrieves metadata", async () => {
|
|
127
|
+
const id = testid("mem");
|
|
128
|
+
const metadata = { confidence: 0.95, source: "user_input", version: 2 };
|
|
129
|
+
|
|
130
|
+
const created = await storage.memories.create({
|
|
131
|
+
id,
|
|
132
|
+
scope: { namespace: "default" },
|
|
133
|
+
kind: "semantic",
|
|
134
|
+
content: { text: "Test" },
|
|
135
|
+
metadata,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(created.metadata).toEqual(metadata);
|
|
139
|
+
|
|
140
|
+
const found = await storage.memories.get(id);
|
|
141
|
+
expect(found?.metadata).toEqual(metadata);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("sets timestamps on create", async () => {
|
|
145
|
+
const before = Date.now();
|
|
146
|
+
|
|
147
|
+
const created = await storage.memories.create({
|
|
148
|
+
id: testid("mem"),
|
|
149
|
+
scope: { namespace: "default" },
|
|
150
|
+
kind: "semantic",
|
|
151
|
+
content: { text: "Test" },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const after = Date.now();
|
|
155
|
+
|
|
156
|
+
expect(created.timestamp).toBeGreaterThanOrEqual(before);
|
|
157
|
+
expect(created.timestamp).toBeLessThanOrEqual(after);
|
|
158
|
+
expect(created.createdAt).toBeGreaterThanOrEqual(before);
|
|
159
|
+
expect(created.updatedAt).toBeGreaterThanOrEqual(before);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import type { Client } from "@libsql/client";
|
|
3
|
+
|
|
4
|
+
import { create_client, create_storage, testid } from "../../__tests__/helpers";
|
|
5
|
+
import { LibSQLStorage } from "../../storage";
|
|
6
|
+
|
|
7
|
+
describe("LibSQLMemoryStore delete", () => {
|
|
8
|
+
let client: Client;
|
|
9
|
+
let storage: LibSQLStorage;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
client = create_client();
|
|
13
|
+
storage = create_storage(client);
|
|
14
|
+
await storage.memories.list(); // init
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
client.close();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("deletes by id", async () => {
|
|
22
|
+
const id = testid("mem");
|
|
23
|
+
|
|
24
|
+
await storage.memories.create({
|
|
25
|
+
id,
|
|
26
|
+
scope: { namespace: "default" },
|
|
27
|
+
kind: "semantic",
|
|
28
|
+
content: { text: "To be deleted" },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Verify exists
|
|
32
|
+
const before = await storage.memories.get(id);
|
|
33
|
+
expect(before).not.toBeNull();
|
|
34
|
+
|
|
35
|
+
// Delete
|
|
36
|
+
await storage.memories.delete(id);
|
|
37
|
+
|
|
38
|
+
// Verify gone
|
|
39
|
+
const after = await storage.memories.get(id);
|
|
40
|
+
expect(after).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("delete is idempotent (no error for missing)", async () => {
|
|
44
|
+
// Should not throw
|
|
45
|
+
await storage.memories.delete("nonexistent");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("mdelete removes multiple ids", async () => {
|
|
49
|
+
const ids = [testid("m1"), testid("m2"), testid("m3")];
|
|
50
|
+
|
|
51
|
+
// Create memories
|
|
52
|
+
for (const id of ids) {
|
|
53
|
+
await storage.memories.create({
|
|
54
|
+
id,
|
|
55
|
+
scope: { namespace: "default" },
|
|
56
|
+
kind: "semantic",
|
|
57
|
+
content: { text: `Memory ${id}` },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Verify all exist
|
|
62
|
+
for (const id of ids) {
|
|
63
|
+
expect(await storage.memories.get(id)).not.toBeNull();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Delete all
|
|
67
|
+
await storage.memories.mdelete(ids);
|
|
68
|
+
|
|
69
|
+
// Verify all gone
|
|
70
|
+
for (const id of ids) {
|
|
71
|
+
expect(await storage.memories.get(id)).toBeNull();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("mdelete handles partial matches", async () => {
|
|
76
|
+
const existing = testid("existing");
|
|
77
|
+
|
|
78
|
+
await storage.memories.create({
|
|
79
|
+
id: existing,
|
|
80
|
+
scope: { namespace: "default" },
|
|
81
|
+
kind: "semantic",
|
|
82
|
+
content: { text: "Exists" },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Delete mix of existing and non-existing
|
|
86
|
+
await storage.memories.mdelete([existing, "nonexistent-1", "nonexistent-2"]);
|
|
87
|
+
|
|
88
|
+
// Existing should be deleted
|
|
89
|
+
expect(await storage.memories.get(existing)).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("mdelete handles empty array", async () => {
|
|
93
|
+
// Create a memory to ensure it's not affected
|
|
94
|
+
const id = testid("mem");
|
|
95
|
+
await storage.memories.create({
|
|
96
|
+
id,
|
|
97
|
+
scope: { namespace: "default" },
|
|
98
|
+
kind: "semantic",
|
|
99
|
+
content: { text: "Should remain" },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Should not throw and not affect anything
|
|
103
|
+
await storage.memories.mdelete([]);
|
|
104
|
+
|
|
105
|
+
// Memory should still exist
|
|
106
|
+
const found = await storage.memories.get(id);
|
|
107
|
+
expect(found).not.toBeNull();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("mdelete with single id", async () => {
|
|
111
|
+
const id = testid("mem");
|
|
112
|
+
|
|
113
|
+
await storage.memories.create({
|
|
114
|
+
id,
|
|
115
|
+
scope: { namespace: "default" },
|
|
116
|
+
kind: "semantic",
|
|
117
|
+
content: { text: "Single" },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await storage.memories.mdelete([id]);
|
|
121
|
+
|
|
122
|
+
expect(await storage.memories.get(id)).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import type { Client } from "@libsql/client";
|
|
3
|
+
|
|
4
|
+
import { create_client, create_storage, testid } from "../../__tests__/helpers";
|
|
5
|
+
import { LibSQLStorage } from "../../storage";
|
|
6
|
+
|
|
7
|
+
describe("LibSQLMemoryStore list", () => {
|
|
8
|
+
let client: Client;
|
|
9
|
+
let storage: LibSQLStorage;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
client = create_client();
|
|
13
|
+
storage = create_storage(client);
|
|
14
|
+
await storage.memories.list(); // init
|
|
15
|
+
|
|
16
|
+
// Seed test data
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
await storage.memories.create({
|
|
19
|
+
id: "m1",
|
|
20
|
+
scope: { namespace: "ns1", entityId: "user-1", agentId: "agent-1" },
|
|
21
|
+
kind: "semantic",
|
|
22
|
+
collection: "facts",
|
|
23
|
+
content: { text: "Fact 1" },
|
|
24
|
+
wmem: true,
|
|
25
|
+
timestamp: now,
|
|
26
|
+
});
|
|
27
|
+
await storage.memories.create({
|
|
28
|
+
id: "m2",
|
|
29
|
+
scope: { namespace: "ns1", entityId: "user-1", agentId: "agent-1" },
|
|
30
|
+
kind: "semantic",
|
|
31
|
+
collection: "preferences",
|
|
32
|
+
content: { text: "Preference 1" },
|
|
33
|
+
wmem: false,
|
|
34
|
+
smem: { expiresAt: now + 3600000 },
|
|
35
|
+
timestamp: now + 100,
|
|
36
|
+
});
|
|
37
|
+
await storage.memories.create({
|
|
38
|
+
id: "m3",
|
|
39
|
+
scope: { namespace: "ns1", entityId: "user-2", agentId: "agent-1" },
|
|
40
|
+
kind: "episodic",
|
|
41
|
+
collection: "facts",
|
|
42
|
+
content: { text: "Fact 2" },
|
|
43
|
+
wmem: true,
|
|
44
|
+
timestamp: now + 200,
|
|
45
|
+
});
|
|
46
|
+
await storage.memories.create({
|
|
47
|
+
id: "m4",
|
|
48
|
+
scope: { namespace: "ns2", entityId: "user-1", agentId: "agent-2" },
|
|
49
|
+
kind: "semantic",
|
|
50
|
+
collection: "facts",
|
|
51
|
+
content: { text: "Fact 3" },
|
|
52
|
+
timestamp: now + 300,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
client.close();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("lists all memories without filter", async () => {
|
|
61
|
+
const memories = await storage.memories.list();
|
|
62
|
+
expect(memories.length).toBe(4);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("filters by namespace", async () => {
|
|
66
|
+
const memories = await storage.memories.list({
|
|
67
|
+
filter: { scope: { namespace: "ns1" } },
|
|
68
|
+
});
|
|
69
|
+
expect(memories.length).toBe(3);
|
|
70
|
+
expect(memories.every((m) => m.scope.namespace === "ns1")).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("filters by entityId", async () => {
|
|
74
|
+
const memories = await storage.memories.list({
|
|
75
|
+
filter: { scope: { entityId: "user-1" } },
|
|
76
|
+
});
|
|
77
|
+
expect(memories.length).toBe(3);
|
|
78
|
+
expect(memories.every((m) => m.scope.entityId === "user-1")).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("filters by agentId", async () => {
|
|
82
|
+
const memories = await storage.memories.list({
|
|
83
|
+
filter: { scope: { agentId: "agent-1" } },
|
|
84
|
+
});
|
|
85
|
+
expect(memories.length).toBe(3);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("filters by multiple scope fields", async () => {
|
|
89
|
+
const memories = await storage.memories.list({
|
|
90
|
+
filter: {
|
|
91
|
+
scope: { namespace: "ns1", entityId: "user-1" },
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
expect(memories.length).toBe(2);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("filters by collection", async () => {
|
|
98
|
+
const memories = await storage.memories.list({
|
|
99
|
+
filter: { collections: ["facts"] },
|
|
100
|
+
});
|
|
101
|
+
expect(memories.length).toBe(3);
|
|
102
|
+
expect(memories.every((m) => m.collection === "facts")).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("filters by multiple collections", async () => {
|
|
106
|
+
const memories = await storage.memories.list({
|
|
107
|
+
filter: { collections: ["facts", "preferences"] },
|
|
108
|
+
});
|
|
109
|
+
expect(memories.length).toBe(4);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("filters by wmem", async () => {
|
|
113
|
+
const wmemTrue = await storage.memories.list({
|
|
114
|
+
filter: { wmem: true },
|
|
115
|
+
});
|
|
116
|
+
expect(wmemTrue.length).toBe(2);
|
|
117
|
+
expect(wmemTrue.every((m) => m.wmem === true)).toBe(true);
|
|
118
|
+
|
|
119
|
+
const wmemFalse = await storage.memories.list({
|
|
120
|
+
filter: { wmem: false },
|
|
121
|
+
});
|
|
122
|
+
expect(wmemFalse.length).toBe(2);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("filters by smem (has expiration)", async () => {
|
|
126
|
+
const hasSmem = await storage.memories.list({
|
|
127
|
+
filter: { smem: true },
|
|
128
|
+
});
|
|
129
|
+
expect(hasSmem.length).toBe(1);
|
|
130
|
+
expect(hasSmem[0].id).toBe("m2");
|
|
131
|
+
|
|
132
|
+
const noSmem = await storage.memories.list({
|
|
133
|
+
filter: { smem: false },
|
|
134
|
+
});
|
|
135
|
+
expect(noSmem.length).toBe(3);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("filters by timestamp range", async () => {
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const m1 = await storage.memories.get("m1");
|
|
141
|
+
const m2 = await storage.memories.get("m2");
|
|
142
|
+
|
|
143
|
+
const memories = await storage.memories.list({
|
|
144
|
+
filter: {
|
|
145
|
+
after: m1!.timestamp - 1,
|
|
146
|
+
before: m2!.timestamp + 1,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(memories.length).toBe(2);
|
|
151
|
+
expect(memories.map((m) => m.id).sort()).toEqual(["m1", "m2"]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("orders by timestamp desc (default)", async () => {
|
|
155
|
+
const memories = await storage.memories.list();
|
|
156
|
+
|
|
157
|
+
// Should be in desc order by default
|
|
158
|
+
for (let i = 1; i < memories.length; i++) {
|
|
159
|
+
expect(memories[i - 1].timestamp).toBeGreaterThanOrEqual(memories[i].timestamp);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("orders by timestamp asc", async () => {
|
|
164
|
+
const memories = await storage.memories.list({ order: "asc" });
|
|
165
|
+
|
|
166
|
+
for (let i = 1; i < memories.length; i++) {
|
|
167
|
+
expect(memories[i - 1].timestamp).toBeLessThanOrEqual(memories[i].timestamp);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("applies limit", async () => {
|
|
172
|
+
const memories = await storage.memories.list({ limit: 2 });
|
|
173
|
+
expect(memories.length).toBe(2);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("applies offset", async () => {
|
|
177
|
+
const all = await storage.memories.list({ order: "asc" });
|
|
178
|
+
const offset = await storage.memories.list({ order: "asc", offset: 2 });
|
|
179
|
+
|
|
180
|
+
expect(offset.length).toBe(2);
|
|
181
|
+
expect(offset[0].id).toBe(all[2].id);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("combines filters, order, limit, and offset", async () => {
|
|
185
|
+
const memories = await storage.memories.list({
|
|
186
|
+
filter: {
|
|
187
|
+
scope: { namespace: "ns1" },
|
|
188
|
+
collections: ["facts"],
|
|
189
|
+
},
|
|
190
|
+
order: "asc",
|
|
191
|
+
limit: 1,
|
|
192
|
+
offset: 1,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(memories.length).toBe(1);
|
|
196
|
+
expect(memories[0].id).toBe("m3");
|
|
197
|
+
});
|
|
198
|
+
});
|