@aitytech/agentkits-memory 1.0.0
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/README.md +250 -0
- package/dist/cache-manager.d.ts +134 -0
- package/dist/cache-manager.d.ts.map +1 -0
- package/dist/cache-manager.js +407 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/cli/save.d.ts +20 -0
- package/dist/cli/save.d.ts.map +1 -0
- package/dist/cli/save.js +94 -0
- package/dist/cli/save.js.map +1 -0
- package/dist/cli/setup.d.ts +18 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +163 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/viewer.d.ts +21 -0
- package/dist/cli/viewer.d.ts.map +1 -0
- package/dist/cli/viewer.js +182 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/hnsw-index.d.ts +111 -0
- package/dist/hnsw-index.d.ts.map +1 -0
- package/dist/hnsw-index.js +781 -0
- package/dist/hnsw-index.js.map +1 -0
- package/dist/hooks/cli.d.ts +20 -0
- package/dist/hooks/cli.d.ts.map +1 -0
- package/dist/hooks/cli.js +102 -0
- package/dist/hooks/cli.js.map +1 -0
- package/dist/hooks/context.d.ts +31 -0
- package/dist/hooks/context.d.ts.map +1 -0
- package/dist/hooks/context.js +64 -0
- package/dist/hooks/context.js.map +1 -0
- package/dist/hooks/index.d.ts +16 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +20 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/observation.d.ts +30 -0
- package/dist/hooks/observation.d.ts.map +1 -0
- package/dist/hooks/observation.js +79 -0
- package/dist/hooks/observation.js.map +1 -0
- package/dist/hooks/service.d.ts +102 -0
- package/dist/hooks/service.d.ts.map +1 -0
- package/dist/hooks/service.js +454 -0
- package/dist/hooks/service.js.map +1 -0
- package/dist/hooks/session-init.d.ts +30 -0
- package/dist/hooks/session-init.d.ts.map +1 -0
- package/dist/hooks/session-init.js +54 -0
- package/dist/hooks/session-init.js.map +1 -0
- package/dist/hooks/summarize.d.ts +30 -0
- package/dist/hooks/summarize.d.ts.map +1 -0
- package/dist/hooks/summarize.js +74 -0
- package/dist/hooks/summarize.js.map +1 -0
- package/dist/hooks/types.d.ts +193 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +137 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +564 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +368 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +14 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +110 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +100 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +9 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/migration.d.ts +77 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +457 -0
- package/dist/migration.js.map +1 -0
- package/dist/sqljs-backend.d.ts +128 -0
- package/dist/sqljs-backend.d.ts.map +1 -0
- package/dist/sqljs-backend.js +623 -0
- package/dist/sqljs-backend.js.map +1 -0
- package/dist/types.d.ts +481 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +73 -0
- package/dist/types.js.map +1 -0
- package/hooks.json +46 -0
- package/package.json +67 -0
- package/src/__tests__/index.test.ts +407 -0
- package/src/__tests__/sqljs-backend.test.ts +410 -0
- package/src/cache-manager.ts +515 -0
- package/src/cli/save.ts +109 -0
- package/src/cli/setup.ts +203 -0
- package/src/cli/viewer.ts +218 -0
- package/src/hnsw-index.ts +1013 -0
- package/src/hooks/__tests__/handlers.test.ts +298 -0
- package/src/hooks/__tests__/integration.test.ts +431 -0
- package/src/hooks/__tests__/service.test.ts +487 -0
- package/src/hooks/__tests__/types.test.ts +341 -0
- package/src/hooks/cli.ts +121 -0
- package/src/hooks/context.ts +77 -0
- package/src/hooks/index.ts +23 -0
- package/src/hooks/observation.ts +102 -0
- package/src/hooks/service.ts +582 -0
- package/src/hooks/session-init.ts +70 -0
- package/src/hooks/summarize.ts +89 -0
- package/src/hooks/types.ts +365 -0
- package/src/index.ts +755 -0
- package/src/mcp/__tests__/server.test.ts +181 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/server.ts +441 -0
- package/src/mcp/tools.ts +113 -0
- package/src/mcp/types.ts +109 -0
- package/src/migration.ts +574 -0
- package/src/sql.js.d.ts +70 -0
- package/src/sqljs-backend.ts +789 -0
- package/src/types.ts +715 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SqlJsBackend Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the SQLite backend using sql.js.
|
|
5
|
+
*
|
|
6
|
+
* @module @agentkits/memory/__tests__/sqljs-backend.test
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
10
|
+
import { SqlJsBackend } from '../sqljs-backend.js';
|
|
11
|
+
import { MemoryEntry, createDefaultEntry } from '../types.js';
|
|
12
|
+
|
|
13
|
+
describe('SqlJsBackend', () => {
|
|
14
|
+
let backend: SqlJsBackend;
|
|
15
|
+
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
backend = new SqlJsBackend({
|
|
18
|
+
databasePath: ':memory:',
|
|
19
|
+
verbose: false,
|
|
20
|
+
autoPersistInterval: 0, // Disable auto-persist for tests
|
|
21
|
+
});
|
|
22
|
+
await backend.initialize();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await backend.shutdown();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Initialization', () => {
|
|
30
|
+
it('should initialize successfully', async () => {
|
|
31
|
+
const newBackend = new SqlJsBackend({ databasePath: ':memory:' });
|
|
32
|
+
await newBackend.initialize();
|
|
33
|
+
|
|
34
|
+
const health = await newBackend.healthCheck();
|
|
35
|
+
expect(health.status).toBe('healthy');
|
|
36
|
+
|
|
37
|
+
await newBackend.shutdown();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should create schema on initialization', async () => {
|
|
41
|
+
const count = await backend.count();
|
|
42
|
+
expect(count).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle multiple initializations gracefully', async () => {
|
|
46
|
+
await backend.initialize();
|
|
47
|
+
await backend.initialize();
|
|
48
|
+
|
|
49
|
+
const health = await backend.healthCheck();
|
|
50
|
+
expect(health.status).toBe('healthy');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('Store Operations', () => {
|
|
55
|
+
it('should store a memory entry', async () => {
|
|
56
|
+
const entry = createDefaultEntry({
|
|
57
|
+
key: 'test-key',
|
|
58
|
+
content: 'Test content',
|
|
59
|
+
namespace: 'test',
|
|
60
|
+
tags: ['tag1', 'tag2'],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await backend.store(entry);
|
|
64
|
+
const retrieved = await backend.get(entry.id);
|
|
65
|
+
|
|
66
|
+
expect(retrieved).not.toBeNull();
|
|
67
|
+
expect(retrieved!.key).toBe('test-key');
|
|
68
|
+
expect(retrieved!.content).toBe('Test content');
|
|
69
|
+
expect(retrieved!.namespace).toBe('test');
|
|
70
|
+
expect(retrieved!.tags).toEqual(['tag1', 'tag2']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should update existing entry with same id', async () => {
|
|
74
|
+
const entry = createDefaultEntry({
|
|
75
|
+
key: 'update-test',
|
|
76
|
+
content: 'Original content',
|
|
77
|
+
namespace: 'test',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await backend.store(entry);
|
|
81
|
+
|
|
82
|
+
entry.content = 'Updated content';
|
|
83
|
+
await backend.store(entry);
|
|
84
|
+
|
|
85
|
+
const retrieved = await backend.get(entry.id);
|
|
86
|
+
expect(retrieved!.content).toBe('Updated content');
|
|
87
|
+
|
|
88
|
+
const count = await backend.count();
|
|
89
|
+
expect(count).toBe(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should store entry with metadata', async () => {
|
|
93
|
+
const entry = createDefaultEntry({
|
|
94
|
+
key: 'metadata-test',
|
|
95
|
+
content: 'Content with metadata',
|
|
96
|
+
namespace: 'test',
|
|
97
|
+
metadata: { importance: 'high', source: 'test' },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await backend.store(entry);
|
|
101
|
+
const retrieved = await backend.get(entry.id);
|
|
102
|
+
|
|
103
|
+
expect(retrieved!.metadata).toEqual({ importance: 'high', source: 'test' });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should store entry with embedding', async () => {
|
|
107
|
+
const embedding = new Float32Array([0.1, 0.2, 0.3, 0.4]);
|
|
108
|
+
const entry = createDefaultEntry({
|
|
109
|
+
key: 'embedding-test',
|
|
110
|
+
content: 'Content with embedding',
|
|
111
|
+
namespace: 'test',
|
|
112
|
+
});
|
|
113
|
+
entry.embedding = embedding;
|
|
114
|
+
|
|
115
|
+
await backend.store(entry);
|
|
116
|
+
const retrieved = await backend.get(entry.id);
|
|
117
|
+
|
|
118
|
+
expect(retrieved!.embedding).toBeDefined();
|
|
119
|
+
expect(retrieved!.embedding!.length).toBe(4);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Get Operations', () => {
|
|
124
|
+
it('should get entry by id', async () => {
|
|
125
|
+
const entry = createDefaultEntry({
|
|
126
|
+
key: 'get-by-id',
|
|
127
|
+
content: 'Test content',
|
|
128
|
+
namespace: 'test',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await backend.store(entry);
|
|
132
|
+
const retrieved = await backend.get(entry.id);
|
|
133
|
+
|
|
134
|
+
expect(retrieved).not.toBeNull();
|
|
135
|
+
expect(retrieved!.id).toBe(entry.id);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should return null for non-existent id', async () => {
|
|
139
|
+
const retrieved = await backend.get('non-existent-id');
|
|
140
|
+
expect(retrieved).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should get entry by namespace and key', async () => {
|
|
144
|
+
const entry = createDefaultEntry({
|
|
145
|
+
key: 'unique-key',
|
|
146
|
+
content: 'Test content',
|
|
147
|
+
namespace: 'unique-namespace',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await backend.store(entry);
|
|
151
|
+
const retrieved = await backend.getByKey('unique-namespace', 'unique-key');
|
|
152
|
+
|
|
153
|
+
expect(retrieved).not.toBeNull();
|
|
154
|
+
expect(retrieved!.key).toBe('unique-key');
|
|
155
|
+
expect(retrieved!.namespace).toBe('unique-namespace');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should return null for non-existent namespace/key', async () => {
|
|
159
|
+
const retrieved = await backend.getByKey('non-existent', 'non-existent');
|
|
160
|
+
expect(retrieved).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('Update Operations', () => {
|
|
165
|
+
it('should update entry content', async () => {
|
|
166
|
+
const entry = createDefaultEntry({
|
|
167
|
+
key: 'update-content',
|
|
168
|
+
content: 'Original',
|
|
169
|
+
namespace: 'test',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await backend.store(entry);
|
|
173
|
+
const updated = await backend.update(entry.id, { content: 'Updated' });
|
|
174
|
+
|
|
175
|
+
expect(updated).not.toBeNull();
|
|
176
|
+
expect(updated!.content).toBe('Updated');
|
|
177
|
+
expect(updated!.version).toBe(2);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should update entry tags', async () => {
|
|
181
|
+
const entry = createDefaultEntry({
|
|
182
|
+
key: 'update-tags',
|
|
183
|
+
content: 'Content',
|
|
184
|
+
namespace: 'test',
|
|
185
|
+
tags: ['old-tag'],
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await backend.store(entry);
|
|
189
|
+
const updated = await backend.update(entry.id, { tags: ['new-tag1', 'new-tag2'] });
|
|
190
|
+
|
|
191
|
+
expect(updated!.tags).toEqual(['new-tag1', 'new-tag2']);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should update entry metadata', async () => {
|
|
195
|
+
const entry = createDefaultEntry({
|
|
196
|
+
key: 'update-metadata',
|
|
197
|
+
content: 'Content',
|
|
198
|
+
namespace: 'test',
|
|
199
|
+
metadata: { old: true },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await backend.store(entry);
|
|
203
|
+
const updated = await backend.update(entry.id, { metadata: { new: true, version: 2 } });
|
|
204
|
+
|
|
205
|
+
expect(updated!.metadata).toEqual({ new: true, version: 2 });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should return null when updating non-existent entry', async () => {
|
|
209
|
+
const updated = await backend.update('non-existent', { content: 'New' });
|
|
210
|
+
expect(updated).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should increment version on update', async () => {
|
|
214
|
+
const entry = createDefaultEntry({
|
|
215
|
+
key: 'version-test',
|
|
216
|
+
content: 'Original',
|
|
217
|
+
namespace: 'test',
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await backend.store(entry);
|
|
221
|
+
expect(entry.version).toBe(1);
|
|
222
|
+
|
|
223
|
+
const updated1 = await backend.update(entry.id, { content: 'Update 1' });
|
|
224
|
+
expect(updated1!.version).toBe(2);
|
|
225
|
+
|
|
226
|
+
const updated2 = await backend.update(entry.id, { content: 'Update 2' });
|
|
227
|
+
expect(updated2!.version).toBe(3);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('Delete Operations', () => {
|
|
232
|
+
it('should delete entry by id', async () => {
|
|
233
|
+
const entry = createDefaultEntry({
|
|
234
|
+
key: 'delete-test',
|
|
235
|
+
content: 'To be deleted',
|
|
236
|
+
namespace: 'test',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await backend.store(entry);
|
|
240
|
+
expect(await backend.count()).toBe(1);
|
|
241
|
+
|
|
242
|
+
const deleted = await backend.delete(entry.id);
|
|
243
|
+
expect(deleted).toBe(true);
|
|
244
|
+
expect(await backend.count()).toBe(0);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should return false when deleting non-existent entry', async () => {
|
|
248
|
+
const deleted = await backend.delete('non-existent');
|
|
249
|
+
expect(deleted).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('Query Operations', () => {
|
|
254
|
+
beforeEach(async () => {
|
|
255
|
+
// Add test entries
|
|
256
|
+
const entries = [
|
|
257
|
+
createDefaultEntry({ key: 'pattern1', content: 'Auth pattern', namespace: 'patterns', tags: ['auth'] }),
|
|
258
|
+
createDefaultEntry({ key: 'pattern2', content: 'API pattern', namespace: 'patterns', tags: ['api'] }),
|
|
259
|
+
createDefaultEntry({ key: 'decision1', content: 'Use PostgreSQL', namespace: 'decisions', tags: ['database'] }),
|
|
260
|
+
createDefaultEntry({ key: 'error1', content: 'Build error', namespace: 'errors', tags: ['build'] }),
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
await backend.store(entry);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should query all entries with hybrid type', async () => {
|
|
269
|
+
const results = await backend.query({ type: 'hybrid', limit: 10 });
|
|
270
|
+
expect(results.length).toBe(4);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should query by namespace', async () => {
|
|
274
|
+
const results = await backend.query({ type: 'hybrid', namespace: 'patterns', limit: 10 });
|
|
275
|
+
expect(results.length).toBe(2);
|
|
276
|
+
expect(results.every(e => e.namespace === 'patterns')).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should query by tags', async () => {
|
|
280
|
+
const results = await backend.query({ type: 'hybrid', tags: ['auth'], limit: 10 });
|
|
281
|
+
expect(results.length).toBe(1);
|
|
282
|
+
expect(results[0].key).toBe('pattern1');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should limit results', async () => {
|
|
286
|
+
const results = await backend.query({ type: 'hybrid', limit: 2 });
|
|
287
|
+
expect(results.length).toBe(2);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should query by exact key', async () => {
|
|
291
|
+
const results = await backend.query({ type: 'exact', key: 'pattern1', limit: 10 });
|
|
292
|
+
expect(results.length).toBe(1);
|
|
293
|
+
expect(results[0].key).toBe('pattern1');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should query by key prefix', async () => {
|
|
297
|
+
const results = await backend.query({ type: 'prefix', keyPrefix: 'pattern', limit: 10 });
|
|
298
|
+
expect(results.length).toBe(2);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Namespace Operations', () => {
|
|
303
|
+
beforeEach(async () => {
|
|
304
|
+
const entries = [
|
|
305
|
+
createDefaultEntry({ key: 'e1', content: 'C1', namespace: 'ns1' }),
|
|
306
|
+
createDefaultEntry({ key: 'e2', content: 'C2', namespace: 'ns1' }),
|
|
307
|
+
createDefaultEntry({ key: 'e3', content: 'C3', namespace: 'ns2' }),
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
await backend.store(entry);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should list all namespaces', async () => {
|
|
316
|
+
const namespaces = await backend.listNamespaces();
|
|
317
|
+
expect(namespaces).toContain('ns1');
|
|
318
|
+
expect(namespaces).toContain('ns2');
|
|
319
|
+
expect(namespaces.length).toBe(2);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should count entries by namespace', async () => {
|
|
323
|
+
const ns1Count = await backend.count('ns1');
|
|
324
|
+
const ns2Count = await backend.count('ns2');
|
|
325
|
+
|
|
326
|
+
expect(ns1Count).toBe(2);
|
|
327
|
+
expect(ns2Count).toBe(1);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should clear namespace', async () => {
|
|
331
|
+
const deleted = await backend.clearNamespace('ns1');
|
|
332
|
+
expect(deleted).toBe(2);
|
|
333
|
+
|
|
334
|
+
const ns1Count = await backend.count('ns1');
|
|
335
|
+
const ns2Count = await backend.count('ns2');
|
|
336
|
+
|
|
337
|
+
expect(ns1Count).toBe(0);
|
|
338
|
+
expect(ns2Count).toBe(1);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('Bulk Operations', () => {
|
|
343
|
+
it('should bulk insert entries', async () => {
|
|
344
|
+
const entries = [
|
|
345
|
+
createDefaultEntry({ key: 'bulk1', content: 'C1', namespace: 'bulk' }),
|
|
346
|
+
createDefaultEntry({ key: 'bulk2', content: 'C2', namespace: 'bulk' }),
|
|
347
|
+
createDefaultEntry({ key: 'bulk3', content: 'C3', namespace: 'bulk' }),
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
await backend.bulkInsert(entries);
|
|
351
|
+
const count = await backend.count('bulk');
|
|
352
|
+
|
|
353
|
+
expect(count).toBe(3);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should bulk delete entries', async () => {
|
|
357
|
+
const entries = [
|
|
358
|
+
createDefaultEntry({ key: 'bd1', content: 'C1', namespace: 'bd' }),
|
|
359
|
+
createDefaultEntry({ key: 'bd2', content: 'C2', namespace: 'bd' }),
|
|
360
|
+
createDefaultEntry({ key: 'bd3', content: 'C3', namespace: 'bd' }),
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
await backend.bulkInsert(entries);
|
|
364
|
+
expect(await backend.count('bd')).toBe(3);
|
|
365
|
+
|
|
366
|
+
const deleted = await backend.bulkDelete([entries[0].id, entries[1].id]);
|
|
367
|
+
expect(deleted).toBe(2);
|
|
368
|
+
expect(await backend.count('bd')).toBe(1);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('Statistics', () => {
|
|
373
|
+
it('should return stats', async () => {
|
|
374
|
+
const entries = [
|
|
375
|
+
createDefaultEntry({ key: 's1', content: 'C1', namespace: 'ns1', type: 'semantic' }),
|
|
376
|
+
createDefaultEntry({ key: 's2', content: 'C2', namespace: 'ns2', type: 'episodic' }),
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
for (const entry of entries) {
|
|
380
|
+
await backend.store(entry);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const stats = await backend.getStats();
|
|
384
|
+
|
|
385
|
+
expect(stats.totalEntries).toBe(2);
|
|
386
|
+
expect(stats.entriesByNamespace['ns1']).toBe(1);
|
|
387
|
+
expect(stats.entriesByNamespace['ns2']).toBe(1);
|
|
388
|
+
expect(stats.entriesByType['semantic']).toBe(1);
|
|
389
|
+
expect(stats.entriesByType['episodic']).toBe(1);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('Health Check', () => {
|
|
394
|
+
it('should return healthy status', async () => {
|
|
395
|
+
const health = await backend.healthCheck();
|
|
396
|
+
|
|
397
|
+
expect(health.status).toBe('healthy');
|
|
398
|
+
expect(health.components.storage.status).toBe('healthy');
|
|
399
|
+
expect(health.timestamp).toBeDefined();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe('Error Handling', () => {
|
|
404
|
+
it('should throw error when not initialized', async () => {
|
|
405
|
+
const uninitBackend = new SqlJsBackend({ databasePath: ':memory:' });
|
|
406
|
+
|
|
407
|
+
await expect(uninitBackend.get('test')).rejects.toThrow('not initialized');
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|