@dawn-ai/memory 0.8.3

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,6 @@
1
+ export { type MemoryScopeTuple, serializeNamespace } from "./namespace.js";
2
+ export { classifyWrite, type WriteOp } from "./reconcile.js";
3
+ export { sqliteMemoryStore } from "./sqlite-store.js";
4
+ export { tokenize } from "./tokenize.js";
5
+ export type { MemoryKind, MemoryQuery, MemoryRecord, MemorySource, MemoryStatus, MemoryStore, } from "./types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { serializeNamespace } from "./namespace.js";
2
+ export { classifyWrite } from "./reconcile.js";
3
+ export { sqliteMemoryStore } from "./sqlite-store.js";
4
+ export { tokenize } from "./tokenize.js";
@@ -0,0 +1,10 @@
1
+ export interface MemoryScopeTuple {
2
+ readonly workspace?: string;
3
+ readonly route?: string;
4
+ readonly tenant?: string;
5
+ readonly user?: string;
6
+ readonly agent?: string;
7
+ }
8
+ /** Serialize a scope tuple to a stable namespace string. Fail-closed on empty. */
9
+ export declare function serializeNamespace(tuple: MemoryScopeTuple): string;
10
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../src/namespace.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CASlE"}
@@ -0,0 +1,13 @@
1
+ const ORDER = ["workspace", "route", "tenant", "user", "agent"];
2
+ /** Serialize a scope tuple to a stable namespace string. Fail-closed on empty. */
3
+ export function serializeNamespace(tuple) {
4
+ const parts = [];
5
+ for (const key of ORDER) {
6
+ const value = tuple[key];
7
+ if (value !== undefined && value !== "")
8
+ parts.push(`${key}=${value}`);
9
+ }
10
+ if (parts.length === 0)
11
+ throw new Error("serializeNamespace: scope tuple must have at least one dimension");
12
+ return parts.join("|");
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { MemoryRecord } from "./types.js";
2
+ export type WriteOp = {
3
+ op: "add";
4
+ } | {
5
+ op: "update";
6
+ targetId: string;
7
+ } | {
8
+ op: "supersede";
9
+ targetId: string;
10
+ };
11
+ /** Deterministic write classification (no LLM): ADD if no identity match; UPDATE if identity+data equal; SUPERSEDE if identity matches but data differs. */
12
+ export declare function classifyWrite(incoming: MemoryRecord, candidates: readonly MemoryRecord[], identityKeys: readonly string[]): WriteOp;
13
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,MAAM,MAAM,OAAO,GACf;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,GACb;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAIzC,4JAA4J;AAC5J,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,SAAS,YAAY,EAAE,EACnC,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,OAAO,CAMT"}
@@ -0,0 +1,12 @@
1
+ function identityOf(data, keys) {
2
+ return keys.map((k) => JSON.stringify(data[k] ?? null)).join(" ");
3
+ }
4
+ /** Deterministic write classification (no LLM): ADD if no identity match; UPDATE if identity+data equal; SUPERSEDE if identity matches but data differs. */
5
+ export function classifyWrite(incoming, candidates, identityKeys) {
6
+ const incomingId = identityOf(incoming.data, identityKeys);
7
+ const match = candidates.find((c) => identityOf(c.data, identityKeys) === incomingId);
8
+ if (!match)
9
+ return { op: "add" };
10
+ const same = JSON.stringify(match.data) === JSON.stringify(incoming.data);
11
+ return same ? { op: "update", targetId: match.id } : { op: "supersede", targetId: match.id };
12
+ }
@@ -0,0 +1,5 @@
1
+ import type { MemoryStore } from "./types.js";
2
+ export declare function sqliteMemoryStore(opts: {
3
+ path: string;
4
+ }): MemoryStore;
5
+ //# sourceMappingURL=sqlite-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAA6B,WAAW,EAAE,MAAM,YAAY,CAAA;AAwGxE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAsGrE"}
@@ -0,0 +1,170 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { tokenize } from "./tokenize.js";
5
+ // ---------------------------------------------------------------------------
6
+ // Inline DB helpers (mirrors packages/sqlite-storage/src/internal — not
7
+ // re-exported from that package's public API, so we replicate the tiny shim).
8
+ // ---------------------------------------------------------------------------
9
+ function openDb(path) {
10
+ const isMemory = path === ":memory:";
11
+ if (!isMemory) {
12
+ mkdirSync(dirname(path), { recursive: true });
13
+ }
14
+ const db = new DatabaseSync(path);
15
+ if (!isMemory) {
16
+ db.exec("PRAGMA journal_mode = WAL");
17
+ }
18
+ db.exec("PRAGMA foreign_keys = ON");
19
+ db.exec("PRAGMA synchronous = NORMAL");
20
+ return db;
21
+ }
22
+ function runMigrations(db, migrations) {
23
+ db.exec("CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY)");
24
+ const row = db.prepare("SELECT max(version) AS v FROM schema_version").get();
25
+ const current = row?.v ?? 0;
26
+ const sorted = [...migrations].sort((a, b) => a.version - b.version);
27
+ for (const m of sorted) {
28
+ if (m.version <= current)
29
+ continue;
30
+ db.exec("BEGIN");
31
+ try {
32
+ db.exec(m.up);
33
+ db.prepare("INSERT INTO schema_version(version) VALUES (?)").run(m.version);
34
+ db.exec("COMMIT");
35
+ }
36
+ catch (err) {
37
+ db.exec("ROLLBACK");
38
+ throw err;
39
+ }
40
+ }
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Schema
44
+ // ---------------------------------------------------------------------------
45
+ const MIGRATIONS = [
46
+ {
47
+ version: 1,
48
+ up: `
49
+ CREATE TABLE memories (
50
+ id TEXT PRIMARY KEY, kind TEXT NOT NULL, namespace TEXT NOT NULL,
51
+ content TEXT NOT NULL, data TEXT NOT NULL, source TEXT NOT NULL,
52
+ confidence REAL NOT NULL, tags TEXT NOT NULL, status TEXT NOT NULL,
53
+ supersedes TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
54
+ effective_at TEXT, expires_at TEXT
55
+ );
56
+ CREATE INDEX idx_mem_ns_status_updated ON memories(namespace, status, updated_at DESC);
57
+ CREATE TABLE memory_tokens (
58
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE, token TEXT NOT NULL
59
+ );
60
+ CREATE INDEX idx_memtok_token ON memory_tokens(token);
61
+ CREATE INDEX idx_memtok_mem ON memory_tokens(memory_id);
62
+ `,
63
+ },
64
+ ];
65
+ // ---------------------------------------------------------------------------
66
+ // Row ↔ record conversion
67
+ // ---------------------------------------------------------------------------
68
+ function rowToRecord(row) {
69
+ return {
70
+ id: row.id,
71
+ kind: row.kind,
72
+ namespace: row.namespace,
73
+ content: row.content,
74
+ data: JSON.parse(row.data),
75
+ source: JSON.parse(row.source),
76
+ confidence: row.confidence,
77
+ tags: JSON.parse(row.tags),
78
+ status: row.status,
79
+ ...(row.supersedes ? { supersedes: JSON.parse(row.supersedes) } : {}),
80
+ createdAt: row.created_at,
81
+ updatedAt: row.updated_at,
82
+ ...(row.effective_at ? { effectiveAt: row.effective_at } : {}),
83
+ ...(row.expires_at ? { expiresAt: row.expires_at } : {}),
84
+ };
85
+ }
86
+ function tokensFor(rec) {
87
+ const values = Object.values(rec.data).filter((v) => typeof v === "string");
88
+ return tokenize([rec.content, rec.tags.join(" "), values.join(" ")].join(" "));
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // Factory
92
+ // ---------------------------------------------------------------------------
93
+ export function sqliteMemoryStore(opts) {
94
+ const db = openDb(opts.path);
95
+ runMigrations(db, MIGRATIONS);
96
+ function reindex(rec) {
97
+ db.prepare("DELETE FROM memory_tokens WHERE memory_id = ?").run(rec.id);
98
+ const ins = db.prepare("INSERT INTO memory_tokens(memory_id, token) VALUES (?, ?)");
99
+ for (const t of tokensFor(rec))
100
+ ins.run(rec.id, t);
101
+ }
102
+ function getById(id) {
103
+ const row = db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
104
+ return row ? rowToRecord(row) : null;
105
+ }
106
+ function putRecord(rec) {
107
+ db.prepare(`INSERT OR REPLACE INTO memories
108
+ (id,kind,namespace,content,data,source,confidence,tags,status,supersedes,created_at,updated_at,effective_at,expires_at)
109
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(rec.id, rec.kind, rec.namespace, rec.content, JSON.stringify(rec.data), JSON.stringify(rec.source), rec.confidence, JSON.stringify(rec.tags), rec.status, rec.supersedes ? JSON.stringify(rec.supersedes) : null, rec.createdAt, rec.updatedAt, rec.effectiveAt ?? null, rec.expiresAt ?? null);
110
+ reindex(rec);
111
+ }
112
+ return {
113
+ async put(rec) {
114
+ putRecord(rec);
115
+ },
116
+ async get(id) {
117
+ return getById(id);
118
+ },
119
+ async search(q) {
120
+ const status = q.status ?? "active";
121
+ const limit = q.limit ?? 8;
122
+ const terms = q.query ? tokenize(q.query) : [];
123
+ const params = [q.namespace, status];
124
+ let sql = `SELECT m.* FROM memories m WHERE m.namespace = ? AND m.status = ?`;
125
+ if (q.kind) {
126
+ sql += ` AND m.kind = ?`;
127
+ params.push(q.kind);
128
+ }
129
+ if (terms.length > 0) {
130
+ const placeholders = terms.map(() => "?").join(",");
131
+ sql += ` AND m.id IN (SELECT memory_id FROM memory_tokens WHERE token IN (${placeholders}))`;
132
+ params.push(...terms);
133
+ }
134
+ sql += ` ORDER BY m.updated_at DESC, m.id ASC LIMIT ?`;
135
+ params.push(limit);
136
+ const rows = db.prepare(sql).all(...params);
137
+ let records = rows.map(rowToRecord);
138
+ if (q.tags && q.tags.length > 0) {
139
+ const want = new Set(q.tags);
140
+ records = records.filter((r) => r.tags.some((t) => want.has(t)));
141
+ }
142
+ return records;
143
+ },
144
+ async update(id, patch) {
145
+ const current = getById(id);
146
+ if (!current)
147
+ throw new Error(`memory not found: ${id}`);
148
+ putRecord({ ...current, ...patch, id });
149
+ },
150
+ async supersede(id, bySupersedingId) {
151
+ if (!getById(id))
152
+ throw new Error(`memory not found: ${id}`);
153
+ db.prepare("UPDATE memories SET status = 'superseded' WHERE id = ?").run(id);
154
+ const superseding = getById(bySupersedingId);
155
+ if (superseding) {
156
+ const links = new Set([...(superseding.supersedes ?? []), id]);
157
+ db.prepare("UPDATE memories SET supersedes = ? WHERE id = ?").run(JSON.stringify([...links]), bySupersedingId);
158
+ }
159
+ },
160
+ async delete(id) {
161
+ db.prepare("DELETE FROM memories WHERE id = ?").run(id);
162
+ },
163
+ async listCandidates(namespacePrefix) {
164
+ const rows = db
165
+ .prepare("SELECT * FROM memories WHERE status = 'candidate' AND namespace LIKE ? ORDER BY created_at DESC")
166
+ .all(`${namespacePrefix}%`);
167
+ return rows.map(rowToRecord);
168
+ },
169
+ };
170
+ }
@@ -0,0 +1,3 @@
1
+ /** Lowercase, split on non-alphanumerics, drop 1-char tokens, dedupe (insertion order). */
2
+ export declare function tokenize(text: string): string[];
3
+ //# sourceMappingURL=tokenize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenize.d.ts","sourceRoot":"","sources":["../src/tokenize.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAU/C"}
@@ -0,0 +1,14 @@
1
+ /** Lowercase, split on non-alphanumerics, drop 1-char tokens, dedupe (insertion order). */
2
+ export function tokenize(text) {
3
+ const seen = new Set();
4
+ const out = [];
5
+ for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
6
+ if (raw.length < 2)
7
+ continue;
8
+ if (seen.has(raw))
9
+ continue;
10
+ seen.add(raw);
11
+ out.push(raw);
12
+ }
13
+ return out;
14
+ }
@@ -0,0 +1,40 @@
1
+ export type MemoryKind = "semantic" | "episodic" | "procedural" | "reflection";
2
+ export type MemoryStatus = "candidate" | "active" | "superseded";
3
+ export interface MemorySource {
4
+ readonly type: "run" | "user" | "tool" | "eval" | "human";
5
+ readonly id: string;
6
+ }
7
+ export interface MemoryRecord {
8
+ readonly id: string;
9
+ readonly kind: MemoryKind;
10
+ readonly namespace: string;
11
+ readonly content: string;
12
+ readonly data: Record<string, unknown>;
13
+ readonly source: MemorySource;
14
+ readonly confidence: number;
15
+ readonly tags: readonly string[];
16
+ readonly status: MemoryStatus;
17
+ readonly supersedes?: readonly string[];
18
+ readonly createdAt: string;
19
+ readonly updatedAt: string;
20
+ readonly effectiveAt?: string;
21
+ readonly expiresAt?: string;
22
+ }
23
+ export interface MemoryQuery {
24
+ readonly namespace: string;
25
+ readonly query?: string;
26
+ readonly kind?: MemoryKind;
27
+ readonly tags?: readonly string[];
28
+ readonly status?: MemoryStatus;
29
+ readonly limit?: number;
30
+ }
31
+ export interface MemoryStore {
32
+ put(rec: MemoryRecord): Promise<void>;
33
+ get(id: string): Promise<MemoryRecord | null>;
34
+ search(q: MemoryQuery): Promise<readonly MemoryRecord[]>;
35
+ update(id: string, patch: Partial<MemoryRecord>): Promise<void>;
36
+ supersede(id: string, bySupersedingId: string): Promise<void>;
37
+ delete(id: string): Promise<void>;
38
+ listCandidates(namespacePrefix: string): Promise<readonly MemoryRecord[]>;
39
+ }
40
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAA;AAC9E,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAA;AAChE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IACzD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AACD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AACD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AACD,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;IAC7C,MAAM,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAA;IACxD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,cAAc,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAA;CAC1E"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@dawn-ai/memory",
3
+ "version": "0.8.3",
4
+ "private": false,
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/cacheplane/dawnai/tree/main/packages/memory#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/cacheplane/dawnai.git",
11
+ "directory": "packages/memory"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/cacheplane/dawnai/issues"
15
+ },
16
+ "engines": {
17
+ "node": ">=22.13.0"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -b tsconfig.json",
34
+ "lint": "biome check --config-path ../config-biome/biome.json package.json src tsconfig.json vitest.config.ts",
35
+ "test": "vitest --run --config vitest.config.ts --passWithNoTests",
36
+ "typecheck": "tsc --noEmit"
37
+ },
38
+ "dependencies": {
39
+ "@dawn-ai/sqlite-storage": "workspace:*"
40
+ },
41
+ "devDependencies": {
42
+ "@dawn-ai/config-typescript": "workspace:*",
43
+ "@types/node": "25.6.0"
44
+ }
45
+ }