@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.
Files changed (137) hide show
  1. package/.turbo/turbo-build.log +5 -4
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +225 -0
  4. package/dist/__tests__/constraints.test.d.ts +2 -0
  5. package/dist/__tests__/constraints.test.d.ts.map +1 -0
  6. package/dist/__tests__/constraints.test.js +97 -0
  7. package/dist/__tests__/helpers.d.ts +36 -0
  8. package/dist/__tests__/helpers.d.ts.map +1 -0
  9. package/dist/__tests__/helpers.js +80 -0
  10. package/dist/__tests__/memory.create-get.test.d.ts +2 -0
  11. package/dist/__tests__/memory.create-get.test.d.ts.map +1 -0
  12. package/dist/__tests__/memory.create-get.test.js +8 -0
  13. package/dist/__tests__/memory.delete.test.d.ts +2 -0
  14. package/dist/__tests__/memory.delete.test.d.ts.map +1 -0
  15. package/dist/__tests__/memory.delete.test.js +6 -0
  16. package/dist/__tests__/memory.list.test.d.ts +2 -0
  17. package/dist/__tests__/memory.list.test.d.ts.map +1 -0
  18. package/dist/__tests__/memory.list.test.js +8 -0
  19. package/dist/__tests__/memory.update.test.d.ts +2 -0
  20. package/dist/__tests__/memory.update.test.d.ts.map +1 -0
  21. package/dist/__tests__/memory.update.test.js +8 -0
  22. package/dist/__tests__/migrations.test.d.ts +2 -0
  23. package/dist/__tests__/migrations.test.d.ts.map +1 -0
  24. package/dist/__tests__/migrations.test.js +68 -0
  25. package/dist/__tests__/row-codecs.test.d.ts +2 -0
  26. package/dist/__tests__/row-codecs.test.d.ts.map +1 -0
  27. package/dist/__tests__/row-codecs.test.js +175 -0
  28. package/dist/__tests__/sql-utils.test.d.ts +2 -0
  29. package/dist/__tests__/sql-utils.test.d.ts.map +1 -0
  30. package/dist/__tests__/sql-utils.test.js +45 -0
  31. package/dist/__tests__/storage.init.test.d.ts +2 -0
  32. package/dist/__tests__/storage.init.test.d.ts.map +1 -0
  33. package/dist/__tests__/storage.init.test.js +63 -0
  34. package/dist/__tests__/thread.lifecycle.test.d.ts +2 -0
  35. package/dist/__tests__/thread.lifecycle.test.d.ts.map +1 -0
  36. package/dist/__tests__/thread.lifecycle.test.js +172 -0
  37. package/dist/__tests__/transaction.test.d.ts +2 -0
  38. package/dist/__tests__/transaction.test.d.ts.map +1 -0
  39. package/dist/__tests__/transaction.test.js +16 -0
  40. package/dist/__tests__/utils.test.d.ts +2 -0
  41. package/dist/__tests__/utils.test.d.ts.map +1 -0
  42. package/dist/__tests__/utils.test.js +31 -0
  43. package/dist/client.d.ts +46 -0
  44. package/dist/client.d.ts.map +1 -0
  45. package/dist/client.js +46 -0
  46. package/dist/index.d.ts +5 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +5 -1
  49. package/dist/memory/__tests__/create-get.test.d.ts +2 -0
  50. package/dist/memory/__tests__/create-get.test.d.ts.map +1 -0
  51. package/dist/memory/__tests__/create-get.test.js +126 -0
  52. package/dist/memory/__tests__/delete.test.d.ts +2 -0
  53. package/dist/memory/__tests__/delete.test.d.ts.map +1 -0
  54. package/dist/memory/__tests__/delete.test.js +96 -0
  55. package/dist/memory/__tests__/list.test.d.ts +2 -0
  56. package/dist/memory/__tests__/list.test.d.ts.map +1 -0
  57. package/dist/memory/__tests__/list.test.js +168 -0
  58. package/dist/memory/__tests__/sql.test.d.ts +2 -0
  59. package/dist/memory/__tests__/sql.test.d.ts.map +1 -0
  60. package/dist/memory/__tests__/sql.test.js +159 -0
  61. package/dist/memory/__tests__/update.test.d.ts +2 -0
  62. package/dist/memory/__tests__/update.test.d.ts.map +1 -0
  63. package/dist/memory/__tests__/update.test.js +113 -0
  64. package/dist/memory/row.d.ts +11 -0
  65. package/dist/memory/row.d.ts.map +1 -0
  66. package/dist/memory/row.js +29 -0
  67. package/dist/memory/sql.d.ts +34 -0
  68. package/dist/memory/sql.d.ts.map +1 -0
  69. package/dist/memory/sql.js +109 -0
  70. package/dist/memory/store.d.ts +41 -0
  71. package/dist/memory/store.d.ts.map +1 -0
  72. package/dist/memory/store.js +132 -0
  73. package/dist/migrations.d.ts +32 -0
  74. package/dist/migrations.d.ts.map +1 -0
  75. package/dist/migrations.js +157 -0
  76. package/dist/sql.d.ts +28 -0
  77. package/dist/sql.d.ts.map +1 -0
  78. package/dist/sql.js +22 -0
  79. package/dist/storage.d.ts +75 -0
  80. package/dist/storage.d.ts.map +1 -0
  81. package/dist/storage.js +123 -0
  82. package/dist/thread/__tests__/append.test.d.ts +2 -0
  83. package/dist/thread/__tests__/append.test.d.ts.map +1 -0
  84. package/dist/thread/__tests__/append.test.js +141 -0
  85. package/dist/thread/__tests__/history.test.d.ts +2 -0
  86. package/dist/thread/__tests__/history.test.d.ts.map +1 -0
  87. package/dist/thread/__tests__/history.test.js +146 -0
  88. package/dist/thread/__tests__/sql.test.d.ts +2 -0
  89. package/dist/thread/__tests__/sql.test.d.ts.map +1 -0
  90. package/dist/thread/__tests__/sql.test.js +129 -0
  91. package/dist/thread/__tests__/store.test.d.ts +2 -0
  92. package/dist/thread/__tests__/store.test.d.ts.map +1 -0
  93. package/dist/thread/__tests__/store.test.js +170 -0
  94. package/dist/thread/row.d.ts +19 -0
  95. package/dist/thread/row.d.ts.map +1 -0
  96. package/dist/thread/row.js +65 -0
  97. package/dist/thread/sql.d.ts +33 -0
  98. package/dist/thread/sql.d.ts.map +1 -0
  99. package/dist/thread/sql.js +112 -0
  100. package/dist/thread/store.d.ts +67 -0
  101. package/dist/thread/store.d.ts.map +1 -0
  102. package/dist/thread/store.js +282 -0
  103. package/dist/utils.d.ts +10 -0
  104. package/dist/utils.d.ts.map +1 -0
  105. package/dist/utils.js +21 -0
  106. package/package.json +15 -11
  107. package/src/__tests__/constraints.test.ts +123 -0
  108. package/src/__tests__/helpers.ts +98 -0
  109. package/src/__tests__/migrations.test.ts +114 -0
  110. package/src/__tests__/row-codecs.test.ts +201 -0
  111. package/src/__tests__/sql-utils.test.ts +52 -0
  112. package/src/__tests__/storage.init.test.ts +92 -0
  113. package/src/__tests__/thread.lifecycle.test.ts +234 -0
  114. package/src/__tests__/transaction.test.ts +25 -0
  115. package/src/__tests__/utils.test.ts +38 -0
  116. package/src/client.ts +71 -0
  117. package/src/index.ts +10 -0
  118. package/src/memory/__tests__/create-get.test.ts +161 -0
  119. package/src/memory/__tests__/delete.test.ts +124 -0
  120. package/src/memory/__tests__/list.test.ts +198 -0
  121. package/src/memory/__tests__/sql.test.ts +186 -0
  122. package/src/memory/__tests__/update.test.ts +148 -0
  123. package/src/memory/row.ts +36 -0
  124. package/src/memory/sql.ts +142 -0
  125. package/src/memory/store.ts +173 -0
  126. package/src/migrations.ts +206 -0
  127. package/src/sql.ts +35 -0
  128. package/src/storage.ts +170 -0
  129. package/src/thread/__tests__/append.test.ts +201 -0
  130. package/src/thread/__tests__/history.test.ts +198 -0
  131. package/src/thread/__tests__/sql.test.ts +154 -0
  132. package/src/thread/__tests__/store.test.ts +219 -0
  133. package/src/thread/row.ts +77 -0
  134. package/src/thread/sql.ts +153 -0
  135. package/src/thread/store.ts +381 -0
  136. package/src/utils.ts +20 -0
  137. 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
+ });