@ai2070/memex 0.9.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.
Files changed (46) hide show
  1. package/.github/workflows/ci.yml +31 -0
  2. package/.github/workflows/release.yml +35 -0
  3. package/API.md +1078 -0
  4. package/LICENSE +190 -0
  5. package/README.md +574 -0
  6. package/package.json +30 -0
  7. package/src/bulk.ts +128 -0
  8. package/src/envelope.ts +52 -0
  9. package/src/errors.ts +27 -0
  10. package/src/graph.ts +15 -0
  11. package/src/helpers.ts +51 -0
  12. package/src/index.ts +142 -0
  13. package/src/integrity.ts +378 -0
  14. package/src/intent.ts +311 -0
  15. package/src/query.ts +357 -0
  16. package/src/reducer.ts +177 -0
  17. package/src/replay.ts +32 -0
  18. package/src/retrieval.ts +306 -0
  19. package/src/serialization.ts +34 -0
  20. package/src/stats.ts +62 -0
  21. package/src/task.ts +373 -0
  22. package/src/transplant.ts +488 -0
  23. package/src/types.ts +248 -0
  24. package/tests/bugfix-and-coverage.test.ts +958 -0
  25. package/tests/bugfix-holes.test.ts +856 -0
  26. package/tests/bulk.test.ts +256 -0
  27. package/tests/edge-cases-v2.test.ts +355 -0
  28. package/tests/edge-cases.test.ts +661 -0
  29. package/tests/envelope.test.ts +92 -0
  30. package/tests/graph.test.ts +41 -0
  31. package/tests/helpers.test.ts +120 -0
  32. package/tests/integrity.test.ts +371 -0
  33. package/tests/intent.test.ts +276 -0
  34. package/tests/query-advanced.test.ts +252 -0
  35. package/tests/query.test.ts +623 -0
  36. package/tests/reducer.test.ts +342 -0
  37. package/tests/replay.test.ts +145 -0
  38. package/tests/retrieval.test.ts +691 -0
  39. package/tests/serialization.test.ts +118 -0
  40. package/tests/setup.test.ts +7 -0
  41. package/tests/stats.test.ts +163 -0
  42. package/tests/task.test.ts +322 -0
  43. package/tests/transplant.test.ts +385 -0
  44. package/tests/types.test.ts +231 -0
  45. package/tsconfig.json +18 -0
  46. package/vitest.config.ts +7 -0
@@ -0,0 +1,342 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { applyCommand } from "../src/reducer.js";
3
+ import { createGraphState } from "../src/graph.js";
4
+ import {
5
+ DuplicateMemoryError,
6
+ MemoryNotFoundError,
7
+ DuplicateEdgeError,
8
+ EdgeNotFoundError,
9
+ } from "../src/errors.js";
10
+ import type { MemoryItem, Edge, GraphState } from "../src/types.js";
11
+
12
+ const makeItem = (overrides: Partial<MemoryItem> = {}): MemoryItem => ({
13
+ id: "m1",
14
+ scope: "test",
15
+ kind: "observation",
16
+ content: { key: "value", nested: 1 },
17
+ author: "user:laz",
18
+ source_kind: "observed",
19
+ authority: 0.9,
20
+ ...overrides,
21
+ });
22
+
23
+ const makeEdge = (overrides: Partial<Edge> = {}): Edge => ({
24
+ edge_id: "e1",
25
+ from: "m1",
26
+ to: "m2",
27
+ kind: "SUPPORTS",
28
+ author: "system:rule",
29
+ source_kind: "derived_deterministic",
30
+ authority: 0.8,
31
+ active: true,
32
+ ...overrides,
33
+ });
34
+
35
+ function stateWith(items: MemoryItem[] = [], edges: Edge[] = []): GraphState {
36
+ const s = createGraphState();
37
+ for (const i of items) s.items.set(i.id, i);
38
+ for (const e of edges) s.edges.set(e.edge_id, e);
39
+ return s;
40
+ }
41
+
42
+ describe("memory.create", () => {
43
+ it("creates an item in empty state", () => {
44
+ const item = makeItem();
45
+ const { state, events } = applyCommand(createGraphState(), {
46
+ type: "memory.create",
47
+ item,
48
+ });
49
+ expect(state.items.get("m1")).toEqual(item);
50
+ expect(events).toHaveLength(1);
51
+ expect(events[0]).toEqual({
52
+ namespace: "memory",
53
+ type: "memory.created",
54
+ item,
55
+ cause_type: "memory.create",
56
+ });
57
+ });
58
+
59
+ it("returns a new state object", () => {
60
+ const original = createGraphState();
61
+ const { state } = applyCommand(original, {
62
+ type: "memory.create",
63
+ item: makeItem(),
64
+ });
65
+ expect(state).not.toBe(original);
66
+ });
67
+
68
+ it("does not mutate original state", () => {
69
+ const original = createGraphState();
70
+ applyCommand(original, { type: "memory.create", item: makeItem() });
71
+ expect(original.items.size).toBe(0);
72
+ });
73
+
74
+ it("throws DuplicateMemoryError on duplicate id", () => {
75
+ const state = stateWith([makeItem()]);
76
+ expect(() =>
77
+ applyCommand(state, { type: "memory.create", item: makeItem() }),
78
+ ).toThrow(DuplicateMemoryError);
79
+ });
80
+ });
81
+
82
+ describe("memory.update", () => {
83
+ it("updates authority on an existing item", () => {
84
+ const state = stateWith([makeItem()]);
85
+ const { state: next, events } = applyCommand(state, {
86
+ type: "memory.update",
87
+ item_id: "m1",
88
+ partial: { authority: 0.5 },
89
+ author: "system:tuner",
90
+ });
91
+ expect(next.items.get("m1")!.authority).toBe(0.5);
92
+ expect(events[0].type).toBe("memory.updated");
93
+ });
94
+
95
+ it("shallow-merges content", () => {
96
+ const state = stateWith([makeItem()]);
97
+ const { state: next } = applyCommand(state, {
98
+ type: "memory.update",
99
+ item_id: "m1",
100
+ partial: { content: { newKey: "added" } },
101
+ author: "test",
102
+ });
103
+ expect(next.items.get("m1")!.content).toEqual({
104
+ key: "value",
105
+ nested: 1,
106
+ newKey: "added",
107
+ });
108
+ });
109
+
110
+ it("overwrites existing content keys", () => {
111
+ const state = stateWith([makeItem()]);
112
+ const { state: next } = applyCommand(state, {
113
+ type: "memory.update",
114
+ item_id: "m1",
115
+ partial: { content: { key: "updated" } },
116
+ author: "test",
117
+ });
118
+ expect(next.items.get("m1")!.content.key).toBe("updated");
119
+ expect(next.items.get("m1")!.content.nested).toBe(1);
120
+ });
121
+
122
+ it("ignores id in partial (cannot change item id)", () => {
123
+ const state = stateWith([makeItem()]);
124
+ const { state: next } = applyCommand(state, {
125
+ type: "memory.update",
126
+ item_id: "m1",
127
+ partial: { id: "sneaky-new-id" } as any,
128
+ author: "test",
129
+ });
130
+ expect(next.items.get("m1")!.id).toBe("m1");
131
+ expect(next.items.has("sneaky-new-id")).toBe(false);
132
+ });
133
+
134
+ it("shallow-merges meta without losing existing fields", () => {
135
+ const state = stateWith([
136
+ makeItem({ meta: { agent_id: "agent:x", session_id: "s1" } }),
137
+ ]);
138
+ const { state: next } = applyCommand(state, {
139
+ type: "memory.update",
140
+ item_id: "m1",
141
+ partial: { meta: { tagged: true } },
142
+ author: "test",
143
+ });
144
+ const meta = next.items.get("m1")!.meta!;
145
+ expect(meta.tagged).toBe(true);
146
+ expect(meta.agent_id).toBe("agent:x");
147
+ expect(meta.session_id).toBe("s1");
148
+ });
149
+
150
+ it("throws MemoryNotFoundError for non-existent item_id", () => {
151
+ expect(() =>
152
+ applyCommand(createGraphState(), {
153
+ type: "memory.update",
154
+ item_id: "nope",
155
+ partial: { authority: 0.1 },
156
+ author: "test",
157
+ }),
158
+ ).toThrow(MemoryNotFoundError);
159
+ });
160
+
161
+ it("emits memory.updated with fully merged item", () => {
162
+ const state = stateWith([makeItem()]);
163
+ const { events } = applyCommand(state, {
164
+ type: "memory.update",
165
+ item_id: "m1",
166
+ partial: { authority: 0.3, importance: 0.7 },
167
+ author: "test",
168
+ });
169
+ expect(events[0].item!.authority).toBe(0.3);
170
+ expect(events[0].item!.importance).toBe(0.7);
171
+ });
172
+
173
+ it("does not mutate original state", () => {
174
+ const state = stateWith([makeItem()]);
175
+ applyCommand(state, {
176
+ type: "memory.update",
177
+ item_id: "m1",
178
+ partial: { authority: 0.1 },
179
+ author: "test",
180
+ });
181
+ expect(state.items.get("m1")!.authority).toBe(0.9);
182
+ });
183
+ });
184
+
185
+ describe("memory.retract", () => {
186
+ it("removes item from state", () => {
187
+ const state = stateWith([makeItem()]);
188
+ const { state: next, events } = applyCommand(state, {
189
+ type: "memory.retract",
190
+ item_id: "m1",
191
+ author: "user:laz",
192
+ });
193
+ expect(next.items.has("m1")).toBe(false);
194
+ expect(events[0].type).toBe("memory.retracted");
195
+ });
196
+
197
+ it("throws MemoryNotFoundError for non-existent item_id", () => {
198
+ expect(() =>
199
+ applyCommand(createGraphState(), {
200
+ type: "memory.retract",
201
+ item_id: "nope",
202
+ author: "test",
203
+ }),
204
+ ).toThrow(MemoryNotFoundError);
205
+ });
206
+
207
+ it("does not mutate original state", () => {
208
+ const state = stateWith([makeItem()]);
209
+ applyCommand(state, {
210
+ type: "memory.retract",
211
+ item_id: "m1",
212
+ author: "test",
213
+ });
214
+ expect(state.items.has("m1")).toBe(true);
215
+ });
216
+ });
217
+
218
+ describe("edge.create", () => {
219
+ it("creates an edge", () => {
220
+ const edge = makeEdge();
221
+ const { state, events } = applyCommand(createGraphState(), {
222
+ type: "edge.create",
223
+ edge,
224
+ });
225
+ expect(state.edges.get("e1")).toEqual(edge);
226
+ expect(events[0]).toEqual({
227
+ namespace: "memory",
228
+ type: "edge.created",
229
+ edge,
230
+ cause_type: "edge.create",
231
+ });
232
+ });
233
+
234
+ it("throws DuplicateEdgeError on duplicate edge_id", () => {
235
+ const state = stateWith([], [makeEdge()]);
236
+ expect(() =>
237
+ applyCommand(state, { type: "edge.create", edge: makeEdge() }),
238
+ ).toThrow(DuplicateEdgeError);
239
+ });
240
+ });
241
+
242
+ describe("edge.update", () => {
243
+ it("updates weight on existing edge", () => {
244
+ const state = stateWith([], [makeEdge()]);
245
+ const { state: next, events } = applyCommand(state, {
246
+ type: "edge.update",
247
+ edge_id: "e1",
248
+ partial: { weight: 0.5 },
249
+ author: "test",
250
+ });
251
+ expect(next.edges.get("e1")!.weight).toBe(0.5);
252
+ expect(events[0].type).toBe("edge.updated");
253
+ });
254
+
255
+ it("throws EdgeNotFoundError for non-existent edge_id", () => {
256
+ expect(() =>
257
+ applyCommand(createGraphState(), {
258
+ type: "edge.update",
259
+ edge_id: "nope",
260
+ partial: { weight: 0.5 },
261
+ author: "test",
262
+ }),
263
+ ).toThrow(EdgeNotFoundError);
264
+ });
265
+ });
266
+
267
+ describe("edge.retract", () => {
268
+ it("removes edge from state", () => {
269
+ const state = stateWith([], [makeEdge()]);
270
+ const { state: next, events } = applyCommand(state, {
271
+ type: "edge.retract",
272
+ edge_id: "e1",
273
+ author: "test",
274
+ });
275
+ expect(next.edges.has("e1")).toBe(false);
276
+ expect(events[0].type).toBe("edge.retracted");
277
+ });
278
+
279
+ it("throws EdgeNotFoundError for non-existent edge_id", () => {
280
+ expect(() =>
281
+ applyCommand(createGraphState(), {
282
+ type: "edge.retract",
283
+ edge_id: "nope",
284
+ author: "test",
285
+ }),
286
+ ).toThrow(EdgeNotFoundError);
287
+ });
288
+ });
289
+
290
+ describe("sequential operations", () => {
291
+ it("create -> update -> retract produces empty state with 3 events", () => {
292
+ const allEvents: unknown[] = [];
293
+ let state: GraphState = createGraphState();
294
+ let result = applyCommand(state, {
295
+ type: "memory.create",
296
+ item: makeItem(),
297
+ });
298
+ state = result.state;
299
+ allEvents.push(...result.events);
300
+ result = applyCommand(state, {
301
+ type: "memory.update",
302
+ item_id: "m1",
303
+ partial: { authority: 0.5 },
304
+ author: "test",
305
+ });
306
+ state = result.state;
307
+ allEvents.push(...result.events);
308
+ result = applyCommand(state, {
309
+ type: "memory.retract",
310
+ item_id: "m1",
311
+ author: "test",
312
+ });
313
+ state = result.state;
314
+ allEvents.push(...result.events);
315
+ expect(state.items.size).toBe(0);
316
+ expect(allEvents).toHaveLength(3);
317
+ });
318
+
319
+ it("two items + edge; retract one; edge and other item remain", () => {
320
+ let state: GraphState = createGraphState();
321
+ state = applyCommand(state, {
322
+ type: "memory.create",
323
+ item: makeItem({ id: "m1" }),
324
+ }).state;
325
+ state = applyCommand(state, {
326
+ type: "memory.create",
327
+ item: makeItem({ id: "m2", scope: "other" }),
328
+ }).state;
329
+ state = applyCommand(state, {
330
+ type: "edge.create",
331
+ edge: makeEdge({ edge_id: "e1", from: "m1", to: "m2" }),
332
+ }).state;
333
+ state = applyCommand(state, {
334
+ type: "memory.retract",
335
+ item_id: "m1",
336
+ author: "test",
337
+ }).state;
338
+ expect(state.items.size).toBe(1);
339
+ expect(state.items.has("m2")).toBe(true);
340
+ expect(state.edges.size).toBe(1);
341
+ });
342
+ });
@@ -0,0 +1,145 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { replayCommands, replayFromEnvelopes } from "../src/replay.js";
3
+ import { MemoryNotFoundError } from "../src/errors.js";
4
+ import type {
5
+ MemoryItem,
6
+ Edge,
7
+ MemoryCommand,
8
+ EventEnvelope,
9
+ } from "../src/types.js";
10
+
11
+ const item1: MemoryItem = {
12
+ id: "m1",
13
+ scope: "test",
14
+ kind: "observation",
15
+ content: { key: "v1" },
16
+ author: "user:laz",
17
+ source_kind: "observed",
18
+ authority: 0.9,
19
+ };
20
+
21
+ const item2: MemoryItem = {
22
+ id: "m2",
23
+ scope: "test",
24
+ kind: "assertion",
25
+ content: { key: "v2" },
26
+ author: "user:laz",
27
+ source_kind: "user_explicit",
28
+ authority: 0.8,
29
+ };
30
+
31
+ const edge1: Edge = {
32
+ edge_id: "e1",
33
+ from: "m1",
34
+ to: "m2",
35
+ kind: "SUPPORTS",
36
+ author: "system:rule",
37
+ source_kind: "derived_deterministic",
38
+ authority: 0.7,
39
+ active: true,
40
+ };
41
+
42
+ describe("replayCommands", () => {
43
+ it("replays empty command list to empty state", () => {
44
+ const { state, events } = replayCommands([]);
45
+ expect(state.items.size).toBe(0);
46
+ expect(events).toHaveLength(0);
47
+ });
48
+
49
+ it("replays create -> update -> retract to empty state with 3 events", () => {
50
+ const commands: MemoryCommand[] = [
51
+ { type: "memory.create", item: item1 },
52
+ {
53
+ type: "memory.update",
54
+ item_id: "m1",
55
+ partial: { authority: 0.5 },
56
+ author: "system:tuner",
57
+ },
58
+ { type: "memory.retract", item_id: "m1", author: "user:laz" },
59
+ ];
60
+ const { state, events } = replayCommands(commands);
61
+ expect(state.items.size).toBe(0);
62
+ expect(events).toHaveLength(3);
63
+ expect(events[0].type).toBe("memory.created");
64
+ expect(events[1].type).toBe("memory.updated");
65
+ expect(events[2].type).toBe("memory.retracted");
66
+ });
67
+
68
+ it("replays create A, create B, edge A->B to 2 items + 1 edge", () => {
69
+ const commands: MemoryCommand[] = [
70
+ { type: "memory.create", item: item1 },
71
+ { type: "memory.create", item: item2 },
72
+ { type: "edge.create", edge: edge1 },
73
+ ];
74
+ const { state } = replayCommands(commands);
75
+ expect(state.items.size).toBe(2);
76
+ expect(state.edges.size).toBe(1);
77
+ });
78
+
79
+ it("throws at point of failure for invalid command", () => {
80
+ const commands: MemoryCommand[] = [
81
+ { type: "memory.create", item: item1 },
82
+ {
83
+ type: "memory.update",
84
+ item_id: "nonexistent",
85
+ partial: { authority: 0.1 },
86
+ author: "test",
87
+ },
88
+ ];
89
+ expect(() => replayCommands(commands)).toThrow(MemoryNotFoundError);
90
+ });
91
+ });
92
+
93
+ describe("replayFromEnvelopes", () => {
94
+ it("sorts by timestamp before replaying", () => {
95
+ const envelopes: EventEnvelope<MemoryCommand>[] = [
96
+ {
97
+ id: "ev2",
98
+ namespace: "memory",
99
+ type: "memory.create",
100
+ ts: "2026-04-10T19:30:00.000Z",
101
+ payload: { type: "memory.create", item: item2 },
102
+ },
103
+ {
104
+ id: "ev1",
105
+ namespace: "memory",
106
+ type: "memory.create",
107
+ ts: "2026-04-10T19:20:00.000Z",
108
+ payload: { type: "memory.create", item: item1 },
109
+ },
110
+ {
111
+ id: "ev3",
112
+ namespace: "memory",
113
+ type: "edge.create",
114
+ ts: "2026-04-10T19:40:00.000Z",
115
+ payload: { type: "edge.create", edge: edge1 },
116
+ },
117
+ ];
118
+ const { state, events } = replayFromEnvelopes(envelopes);
119
+ expect(state.items.size).toBe(2);
120
+ expect(state.edges.size).toBe(1);
121
+ expect(events[0].item?.id).toBe("m1");
122
+ });
123
+
124
+ it("does not mutate the input array", () => {
125
+ const envelopes: EventEnvelope<MemoryCommand>[] = [
126
+ {
127
+ id: "ev2",
128
+ namespace: "memory",
129
+ type: "memory.create",
130
+ ts: "2026-04-10T19:30:00.000Z",
131
+ payload: { type: "memory.create", item: item2 },
132
+ },
133
+ {
134
+ id: "ev1",
135
+ namespace: "memory",
136
+ type: "memory.create",
137
+ ts: "2026-04-10T19:20:00.000Z",
138
+ payload: { type: "memory.create", item: item1 },
139
+ },
140
+ ];
141
+ const firstId = envelopes[0].id;
142
+ replayFromEnvelopes(envelopes);
143
+ expect(envelopes[0].id).toBe(firstId);
144
+ });
145
+ });