cozo-memory 1.2.6 → 1.2.10

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.
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const db_service_1 = require("./db-service");
4
+ describe("DatabaseService", () => {
5
+ let db;
6
+ beforeEach(async () => {
7
+ db = new db_service_1.DatabaseService(":memory:", "sqlite");
8
+ await db.initialize();
9
+ });
10
+ // ── Entity CRUD ──────────────────────────────────────────────
11
+ describe("Entity CRUD", () => {
12
+ const sampleEntity = {
13
+ id: "e1",
14
+ name: "Test Entity",
15
+ type: "Test",
16
+ embedding: [0.1, 0.2, 0.3],
17
+ name_embedding: [0.4, 0.5, 0.6],
18
+ metadata: { key: "value" },
19
+ created_at: 1000,
20
+ };
21
+ it("should create and get an entity", async () => {
22
+ await db.createEntity(sampleEntity);
23
+ const result = await db.getEntity("e1");
24
+ expect(result).not.toBeNull();
25
+ expect(result.name).toBe("Test Entity");
26
+ expect(result.type).toBe("Test");
27
+ expect(result.embedding).toEqual([0.1, 0.2, 0.3]);
28
+ expect(result.metadata).toEqual({ key: "value" });
29
+ });
30
+ it("should return null for non-existent entity", async () => {
31
+ const result = await db.getEntity("non_existent");
32
+ expect(result).toBeNull();
33
+ });
34
+ it("should update an entity partially", async () => {
35
+ await db.createEntity(sampleEntity);
36
+ await db.updateEntity("e1", { name: "Updated Entity", metadata: { new: "meta" } });
37
+ const result = await db.getEntity("e1");
38
+ expect(result).not.toBeNull();
39
+ expect(result.name).toBe("Updated Entity");
40
+ // metadata should be merged
41
+ expect(result.metadata).toEqual({ key: "value", new: "meta" });
42
+ });
43
+ it("should update embedding fields", async () => {
44
+ await db.createEntity(sampleEntity);
45
+ const newEmbedding = [0.9, 0.8, 0.7];
46
+ await db.updateEntity("e1", { embedding: newEmbedding });
47
+ const result = await db.getEntity("e1");
48
+ expect(result.embedding).toEqual(newEmbedding);
49
+ // name_embedding should remain unchanged
50
+ expect(result.name_embedding).toEqual([0.4, 0.5, 0.6]);
51
+ });
52
+ it("should not throw when updating non-existent entity", async () => {
53
+ await expect(db.updateEntity("ghost", { name: "X" })).resolves.not.toThrow();
54
+ });
55
+ it("should delete an entity and its observations", async () => {
56
+ await db.createEntity(sampleEntity);
57
+ await db.addObservation({
58
+ id: "obs1",
59
+ entity_id: "e1",
60
+ text: "test",
61
+ embedding: [],
62
+ metadata: {},
63
+ created_at: 1001,
64
+ });
65
+ await db.deleteEntity("e1");
66
+ const entity = await db.getEntity("e1");
67
+ expect(entity).toBeNull();
68
+ // Observation should also be deleted
69
+ const obs = await db.getObservationsForEntity("e1");
70
+ expect(obs).toHaveLength(0);
71
+ });
72
+ });
73
+ // ── Observations ─────────────────────────────────────────────
74
+ describe("Observation CRUD", () => {
75
+ beforeEach(async () => {
76
+ await db.createEntity({
77
+ id: "e2",
78
+ name: "Entity With Obs",
79
+ type: "Test",
80
+ embedding: [],
81
+ name_embedding: [],
82
+ metadata: {},
83
+ created_at: 2000,
84
+ });
85
+ });
86
+ it("should add and retrieve observations for an entity", async () => {
87
+ await db.addObservation({
88
+ id: "obs1",
89
+ entity_id: "e2",
90
+ text: "First observation",
91
+ embedding: [1.0, 2.0],
92
+ metadata: { source: "test" },
93
+ created_at: 2001,
94
+ });
95
+ await db.addObservation({
96
+ id: "obs2",
97
+ entity_id: "e2",
98
+ text: "Second observation",
99
+ embedding: [3.0, 4.0],
100
+ metadata: {},
101
+ created_at: 2002,
102
+ });
103
+ const obs = await db.getObservationsForEntity("e2");
104
+ expect(obs).toHaveLength(2);
105
+ expect(obs[0].text).toBe("First observation");
106
+ expect(obs[1].text).toBe("Second observation");
107
+ });
108
+ it("should return empty array for entity with no observations", async () => {
109
+ const obs = await db.getObservationsForEntity("e2");
110
+ expect(obs).toHaveLength(0);
111
+ });
112
+ it("should return empty array for non-existent entity", async () => {
113
+ const obs = await db.getObservationsForEntity("ghost");
114
+ expect(obs).toHaveLength(0);
115
+ });
116
+ });
117
+ // ── Relationships ────────────────────────────────────────────
118
+ describe("Relationship CRUD", () => {
119
+ beforeEach(async () => {
120
+ for (const id of ["a", "b", "c"]) {
121
+ await db.createEntity({
122
+ id,
123
+ name: `Entity ${id}`,
124
+ type: "Test",
125
+ embedding: [],
126
+ name_embedding: [],
127
+ metadata: {},
128
+ created_at: 3000,
129
+ });
130
+ }
131
+ });
132
+ it("should create and list all relations", async () => {
133
+ await db.createRelation({
134
+ from_id: "a", to_id: "b", relation_type: "knows",
135
+ strength: 0.8, metadata: {}, created_at: 3001,
136
+ });
137
+ await db.createRelation({
138
+ from_id: "b", to_id: "c", relation_type: "likes",
139
+ strength: 1.0, metadata: { since: "2024" }, created_at: 3002,
140
+ });
141
+ const all = await db.getRelations();
142
+ expect(all).toHaveLength(2);
143
+ });
144
+ it("should filter relations by from_id", async () => {
145
+ await db.createRelation({
146
+ from_id: "a", to_id: "b", relation_type: "knows",
147
+ strength: 0.5, metadata: {}, created_at: 3001,
148
+ });
149
+ await db.createRelation({
150
+ from_id: "a", to_id: "c", relation_type: "knows",
151
+ strength: 0.3, metadata: {}, created_at: 3002,
152
+ });
153
+ await db.createRelation({
154
+ from_id: "b", to_id: "c", relation_type: "likes",
155
+ strength: 0.9, metadata: {}, created_at: 3003,
156
+ });
157
+ const fromA = await db.getRelations("a");
158
+ expect(fromA).toHaveLength(2);
159
+ const fromB = await db.getRelations("b");
160
+ expect(fromB).toHaveLength(1);
161
+ expect(fromB[0].to_id).toBe("c");
162
+ });
163
+ it("should filter relations by to_id", async () => {
164
+ await db.createRelation({
165
+ from_id: "a", to_id: "c", relation_type: "knows",
166
+ strength: 0.5, metadata: {}, created_at: 3001,
167
+ });
168
+ await db.createRelation({
169
+ from_id: "b", to_id: "c", relation_type: "likes",
170
+ strength: 0.9, metadata: {}, created_at: 3002,
171
+ });
172
+ const toC = await db.getRelations(undefined, "c");
173
+ expect(toC).toHaveLength(2);
174
+ });
175
+ it("should delete entity and cascade its relations", async () => {
176
+ await db.createRelation({
177
+ from_id: "a", to_id: "b", relation_type: "knows",
178
+ strength: 0.5, metadata: {}, created_at: 3001,
179
+ });
180
+ await db.deleteEntity("a");
181
+ const all = await db.getRelations();
182
+ expect(all).toHaveLength(0);
183
+ });
184
+ });
185
+ // ── Vector Search ────────────────────────────────────────────
186
+ describe("Vector Search", () => {
187
+ beforeEach(async () => {
188
+ const entities = [
189
+ { id: "vec1", name: "Cat", embedding: [1.0, 0.0, 0.0] },
190
+ { id: "vec2", name: "Dog", embedding: [0.0, 1.0, 0.0] },
191
+ { id: "vec3", name: "Fish", embedding: [0.0, 0.0, 1.0] },
192
+ ];
193
+ for (const e of entities) {
194
+ await db.createEntity({
195
+ id: e.id, name: e.name, type: "Animal",
196
+ embedding: e.embedding, name_embedding: e.embedding,
197
+ metadata: {}, created_at: 4000,
198
+ });
199
+ }
200
+ });
201
+ it("should find closest entity by cosine similarity", async () => {
202
+ // Query vector closest to [1, 0, 0] → "Cat"
203
+ const results = await db.vectorSearchEntity([0.9, 0.1, 0.0], 1);
204
+ expect(results).toHaveLength(1);
205
+ expect(results[0][0]).toBe("vec1"); // id
206
+ expect(results[0][4]).toBeGreaterThan(0.9); // score
207
+ });
208
+ it("should return correct limit", async () => {
209
+ const results = await db.vectorSearchEntity([0.5, 0.5, 0.5], 2);
210
+ expect(results).toHaveLength(2);
211
+ });
212
+ it("should handle empty query vector gracefully (returns zero-score results)", async () => {
213
+ const results = await db.vectorSearchEntity([], 10);
214
+ // Empty vector produces cosine=0 for all entities, so all are returned with score 0
215
+ expect(results).toHaveLength(3);
216
+ expect(results[0][4]).toBe(0);
217
+ });
218
+ });
219
+ // ── Full-Text Search ─────────────────────────────────────────
220
+ describe("Full-Text Search", () => {
221
+ beforeEach(async () => {
222
+ await db.createEntity({
223
+ id: "fts1", name: "Alice Wonderland", type: "Person",
224
+ embedding: [], name_embedding: [], metadata: {}, created_at: 5000,
225
+ });
226
+ await db.createEntity({
227
+ id: "fts2", name: "Bob The Builder", type: "Person",
228
+ embedding: [], name_embedding: [], metadata: {}, created_at: 5001,
229
+ });
230
+ await db.addObservation({
231
+ id: "fobs1", entity_id: "fts1",
232
+ text: "Alice lives in a wonderland of dreams",
233
+ embedding: [], metadata: {}, created_at: 5002,
234
+ });
235
+ });
236
+ it("should find entity by name substring", async () => {
237
+ const results = await db.fullTextSearchEntity("alice");
238
+ expect(results).toHaveLength(1);
239
+ expect(results[0][0]).toBe("fts1");
240
+ });
241
+ it("should be case-insensitive", async () => {
242
+ const results = await db.fullTextSearchEntity("BOB");
243
+ expect(results).toHaveLength(1);
244
+ expect(results[0][0]).toBe("fts2");
245
+ });
246
+ it("should find observation by text substring", async () => {
247
+ const results = await db.fullTextSearchObservation("wonderland");
248
+ expect(results).toHaveLength(1);
249
+ expect(results[0][1]).toBe("fts1");
250
+ });
251
+ it("should return empty array for no match", async () => {
252
+ const results = await db.fullTextSearchEntity("nobody");
253
+ expect(results).toHaveLength(0);
254
+ });
255
+ });
256
+ // ── Export & Stats ───────────────────────────────────────────
257
+ describe("Export & Stats", () => {
258
+ it("should export empty database", async () => {
259
+ const exported = await db.exportRelations();
260
+ expect(exported).toHaveProperty("entity");
261
+ expect(exported).toHaveProperty("observation");
262
+ expect(exported).toHaveProperty("relationship");
263
+ expect(exported.entity).toHaveLength(0);
264
+ expect(exported.observation).toHaveLength(0);
265
+ expect(exported.relationship).toHaveLength(0);
266
+ });
267
+ it("should export correct counts", async () => {
268
+ await db.createEntity({
269
+ id: "exp1", name: "Export Test", type: "Test",
270
+ embedding: [], name_embedding: [], metadata: {}, created_at: 6000,
271
+ });
272
+ await db.addObservation({
273
+ id: "exp_obs1", entity_id: "exp1", text: "Obs",
274
+ embedding: [], metadata: {}, created_at: 6001,
275
+ });
276
+ await db.createRelation({
277
+ from_id: "exp1", to_id: "exp1", relation_type: "self",
278
+ strength: 1.0, metadata: {}, created_at: 6002,
279
+ });
280
+ const exported = await db.exportRelations();
281
+ expect(exported.entity).toHaveLength(1);
282
+ expect(exported.observation).toHaveLength(1);
283
+ expect(exported.relationship).toHaveLength(1);
284
+ });
285
+ it("should return correct stats", async () => {
286
+ await db.createEntity({
287
+ id: "stat1", name: "Stats", type: "T",
288
+ embedding: [], name_embedding: [], metadata: {}, created_at: 7000,
289
+ });
290
+ const stats = await db.getStats();
291
+ expect(stats.entities).toBe(1);
292
+ expect(stats.observations).toBe(0);
293
+ expect(stats.relationships).toBe(0);
294
+ });
295
+ });
296
+ // ── Lifecycle ────────────────────────────────────────────────
297
+ describe("Lifecycle", () => {
298
+ it("should initialize without error", async () => {
299
+ await expect(db.initialize()).resolves.not.toThrow();
300
+ });
301
+ it("should close without error", async () => {
302
+ await expect(db.close()).resolves.not.toThrow();
303
+ });
304
+ it("should backup and restore without error", async () => {
305
+ await expect(db.backup("/tmp/test_backup.cozo")).resolves.not.toThrow();
306
+ await expect(db.restore("/tmp/test_backup.cozo")).resolves.not.toThrow();
307
+ });
308
+ it("should run a query without error", async () => {
309
+ const result = await db.runQuery("SELECT 1");
310
+ expect(result).toEqual({ rows: [] });
311
+ });
312
+ });
313
+ });
@@ -7,8 +7,10 @@ exports.ExportImportService = void 0;
7
7
  const archiver_1 = __importDefault(require("archiver"));
8
8
  class ExportImportService {
9
9
  dbService;
10
- constructor(dbService) {
10
+ embeddingDim;
11
+ constructor(dbService, embeddingDim = 1024) {
11
12
  this.dbService = dbService;
13
+ this.embeddingDim = embeddingDim;
12
14
  }
13
15
  /**
14
16
  * Export memory to various formats
@@ -418,7 +420,7 @@ class ExportImportService {
418
420
  }
419
421
  async createEntityWithId(id, name, type, metadata) {
420
422
  const now = Date.now() * 1000;
421
- const zeroVec = new Array(1024).fill(0);
423
+ const zeroVec = new Array(this.embeddingDim).fill(0);
422
424
  // Escape strings properly for CozoDB
423
425
  const escapedName = name.replace(/"/g, '\\"');
424
426
  const escapedType = type.replace(/"/g, '\\"');
@@ -442,14 +444,16 @@ class ExportImportService {
442
444
  }
443
445
  async createObservationWithId(id, entityId, text, metadata) {
444
446
  const now = Date.now() * 1000;
445
- const zeroVec = new Array(1024).fill(0);
447
+ const zeroVec = new Array(this.embeddingDim).fill(0);
446
448
  const escapedText = text.replace(/"/g, '\\"').replace(/\n/g, '\\n');
447
449
  await this.dbService.run(`
448
- ?[id, entity_id, text, embedding, metadata, created_at] <- [[$id, $entity_id, $text, $embedding, $metadata, [${now}, true]]]
449
- :insert observation {id, entity_id, text, embedding, metadata, created_at}
450
+ ?[id, entity_id, session_id, task_id, text, embedding, metadata, created_at] <- [[$id, $entity_id, $session_id, $task_id, $text, $embedding, $metadata, [${now}, true]]]
451
+ :insert observation {id, entity_id, session_id, task_id, text, embedding, metadata, created_at}
450
452
  `, {
451
453
  id,
452
454
  entity_id: entityId,
455
+ session_id: "",
456
+ task_id: "",
453
457
  text: escapedText,
454
458
  embedding: zeroVec,
455
459
  metadata: metadata || {}