@seanhogg/builderforce-memory 2026.9.34 → 2026.10.2
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/dist/errors/SSMError.d.ts +1 -1
- package/dist/errors/SSMError.d.ts.map +1 -1
- package/dist/errors/SSMError.js.map +1 -1
- package/dist/handoff/index.d.ts +5 -5
- package/dist/handoff/index.js +5 -5
- package/dist/handoff/note.d.ts +1 -1
- package/dist/handoff/note.js +1 -1
- package/dist/handoff/pack.d.ts +13 -4
- package/dist/handoff/pack.d.ts.map +1 -1
- package/dist/handoff/pack.js +31 -10
- package/dist/handoff/pack.js.map +1 -1
- package/dist/memory/MemoryStore.d.ts +120 -181
- package/dist/memory/MemoryStore.d.ts.map +1 -1
- package/dist/memory/MemoryStore.js +201 -389
- package/dist/memory/MemoryStore.js.map +1 -1
- package/dist/memory/SemanticRecall.d.ts +64 -0
- package/dist/memory/SemanticRecall.d.ts.map +1 -0
- package/dist/memory/SemanticRecall.js +188 -0
- package/dist/memory/SemanticRecall.js.map +1 -0
- package/dist/memory/records.d.ts +26 -0
- package/dist/memory/records.d.ts.map +1 -0
- package/dist/memory/records.js +71 -0
- package/dist/memory/records.js.map +1 -0
- package/dist/memory/ttlSweeper.d.ts +12 -0
- package/dist/memory/ttlSweeper.d.ts.map +1 -0
- package/dist/memory/ttlSweeper.js +28 -0
- package/dist/memory/ttlSweeper.js.map +1 -0
- package/dist/memory/types.d.ts +142 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +6 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/storage/ScopedFactRepository.d.ts +156 -0
- package/dist/storage/ScopedFactRepository.d.ts.map +1 -0
- package/dist/storage/ScopedFactRepository.js +278 -0
- package/dist/storage/ScopedFactRepository.js.map +1 -0
- package/dist/storage/factImport.d.ts +21 -0
- package/dist/storage/factImport.d.ts.map +1 -0
- package/dist/storage/factImport.js +61 -0
- package/dist/storage/factImport.js.map +1 -0
- package/dist/storage/factMutations.d.ts +46 -0
- package/dist/storage/factMutations.d.ts.map +1 -0
- package/dist/storage/factMutations.js +73 -0
- package/dist/storage/factMutations.js.map +1 -0
- package/dist/storage/factQueries.d.ts +16 -0
- package/dist/storage/factQueries.d.ts.map +1 -0
- package/dist/storage/factQueries.js +36 -0
- package/dist/storage/factQueries.js.map +1 -0
- package/dist/storage/factRetention.d.ts +30 -0
- package/dist/storage/factRetention.d.ts.map +1 -0
- package/dist/storage/factRetention.js +65 -0
- package/dist/storage/factRetention.js.map +1 -0
- package/dist/storage/fileLock.d.ts +56 -0
- package/dist/storage/fileLock.d.ts.map +1 -0
- package/dist/storage/fileLock.js +138 -0
- package/dist/storage/fileLock.js.map +1 -0
- package/dist/storage/fileTransaction.d.ts +33 -0
- package/dist/storage/fileTransaction.d.ts.map +1 -0
- package/dist/storage/fileTransaction.js +108 -0
- package/dist/storage/fileTransaction.js.map +1 -0
- package/dist/storage/idbAdapter.d.ts +95 -0
- package/dist/storage/idbAdapter.d.ts.map +1 -0
- package/dist/storage/idbAdapter.js +311 -0
- package/dist/storage/idbAdapter.js.map +1 -0
- package/dist/storage/nodeFileAdapter.d.ts +127 -0
- package/dist/storage/nodeFileAdapter.d.ts.map +1 -0
- package/dist/storage/nodeFileAdapter.js +275 -0
- package/dist/storage/nodeFileAdapter.js.map +1 -0
- package/dist/storage/syncJournal.d.ts +19 -0
- package/dist/storage/syncJournal.d.ts.map +1 -0
- package/dist/storage/syncJournal.js +46 -0
- package/dist/storage/syncJournal.js.map +1 -0
- package/dist/storage/types.d.ts +207 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +90 -0
- package/dist/storage/types.js.map +1 -0
- package/package.json +3 -3
- package/src/errors/SSMError.ts +1 -0
- package/src/handoff/handoff.test.ts +52 -2
- package/src/handoff/index.ts +5 -5
- package/src/handoff/note.ts +1 -1
- package/src/handoff/pack.ts +32 -9
- package/src/memory/MemoryStore.ts +245 -525
- package/src/memory/SemanticRecall.ts +204 -0
- package/src/memory/records.ts +79 -0
- package/src/memory/ttlSweeper.ts +29 -0
- package/src/memory/types.ts +149 -0
- package/src/storage/ScopedFactRepository.ts +373 -0
- package/src/storage/factImport.ts +73 -0
- package/src/storage/factMutations.ts +106 -0
- package/src/storage/factQueries.ts +41 -0
- package/src/storage/factRetention.ts +82 -0
- package/src/storage/fileLock.ts +162 -0
- package/src/storage/fileTransaction.ts +116 -0
- package/src/storage/idbAdapter.ts +373 -0
- package/src/storage/nodeFileAdapter.ts +331 -0
- package/src/storage/syncJournal.ts +47 -0
- package/src/storage/types.ts +262 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storage/nodeFileAdapter.ts – Durable Node.js implementation of the storage
|
|
3
|
+
* port, backed by a single atomically-replaced JSON document.
|
|
4
|
+
*
|
|
5
|
+
* This is the VSIX extension-host / MCP adapter the offline-memory scope (M01)
|
|
6
|
+
* requires: real durable storage on Node WITHOUT an in-memory IndexedDB shim.
|
|
7
|
+
* fake-indexeddb evaporates with the process; this adapter fsyncs a temp file
|
|
8
|
+
* and atomically renames it over the database file, so a crash mid-write leaves
|
|
9
|
+
* either the old or the new document — never a torn one.
|
|
10
|
+
*
|
|
11
|
+
* Transaction semantics (M02/M05):
|
|
12
|
+
* - All transactions are serialized through one promise queue, so concurrent
|
|
13
|
+
* writers in this process interleave at transaction granularity.
|
|
14
|
+
* - A read-write transaction holds an exclusive `<file>.lock` (see
|
|
15
|
+
* `fileLock.ts`: atomic create, stale-lock reclamation by PID liveness,
|
|
16
|
+
* bounded wait) across refresh + mutate + persist, so two processes can
|
|
17
|
+
* never both build on the same stale document and lose a write.
|
|
18
|
+
* - Under that lock, the file is re-read when another process changed it
|
|
19
|
+
* (mtime/size freshness), so cross-process writers build on each other's
|
|
20
|
+
* commits instead of blindly overwriting.
|
|
21
|
+
* - If the persist fails, the in-memory document is rolled back to the
|
|
22
|
+
* pre-transaction snapshot and the transaction rejects — no partial commits.
|
|
23
|
+
*
|
|
24
|
+
* Cross-process consistency: a polling watcher (default 1s, injectable)
|
|
25
|
+
* republishes foreign changes to subscribers, the Node analogue of the IDB
|
|
26
|
+
* adapter's BroadcastChannel.
|
|
27
|
+
*
|
|
28
|
+
* Node builtins are loaded indirectly (via a non-static import) so a browser
|
|
29
|
+
* bundle of this package never statically pulls in `node:fs`.
|
|
30
|
+
*/
|
|
31
|
+
import { FileLock } from './fileLock.js';
|
|
32
|
+
import { FileTransaction, emptyDocument } from './fileTransaction.js';
|
|
33
|
+
import { MemoryStorageError, } from './types.js';
|
|
34
|
+
/** Load a module without a static specifier, so bundlers skip Node builtins. */
|
|
35
|
+
function dynamicImport(m) {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
37
|
+
return new Function('m', 'return import(m)')(m);
|
|
38
|
+
}
|
|
39
|
+
export class NodeFileStorageAdapter {
|
|
40
|
+
_filePath;
|
|
41
|
+
kind = 'node-file';
|
|
42
|
+
_fs;
|
|
43
|
+
_path;
|
|
44
|
+
_contextId;
|
|
45
|
+
_doc;
|
|
46
|
+
_stamp = null;
|
|
47
|
+
/** Transaction queue tail — all transacts serialize through this. */
|
|
48
|
+
_queue = Promise.resolve();
|
|
49
|
+
_watchTimer = null;
|
|
50
|
+
_listeners = new Set();
|
|
51
|
+
_watchIntervalMs;
|
|
52
|
+
_lock;
|
|
53
|
+
constructor(_filePath, fs, path, opts) {
|
|
54
|
+
this._filePath = _filePath;
|
|
55
|
+
this._fs = fs;
|
|
56
|
+
this._path = path;
|
|
57
|
+
this._contextId = opts.contextId ?? `node-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
|
|
58
|
+
this._watchIntervalMs = opts.watchIntervalMs ?? 1000;
|
|
59
|
+
this._lock = new FileLock(fs, `${_filePath}.lock`, opts.lock);
|
|
60
|
+
this._doc = this._loadFromDisk();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Opens (creating if necessary) the durable file at `filePath`. Async only
|
|
64
|
+
* because Node builtins load indirectly; the document itself is read
|
|
65
|
+
* synchronously before this resolves.
|
|
66
|
+
*/
|
|
67
|
+
static async open(filePath, opts = {}) {
|
|
68
|
+
const fs = (opts.fs ?? (await dynamicImport('node:fs')));
|
|
69
|
+
const path = (opts.path ?? (await dynamicImport('node:path')));
|
|
70
|
+
return new NodeFileStorageAdapter(filePath, fs, path, opts);
|
|
71
|
+
}
|
|
72
|
+
/** Identity of this context, stamped onto change notes and opIds. */
|
|
73
|
+
get contextId() {
|
|
74
|
+
return this._contextId;
|
|
75
|
+
}
|
|
76
|
+
/** The durable file backing this adapter. */
|
|
77
|
+
get filePath() {
|
|
78
|
+
return this._filePath;
|
|
79
|
+
}
|
|
80
|
+
// ── Boot / crash recovery ─────────────────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Reads the document from disk. Recovery order on a corrupt main file
|
|
83
|
+
* (killed mid-rename is impossible, but a torn fsync on exotic filesystems
|
|
84
|
+
* is not): try `<file>.tmp` (the last fully-fsynced candidate), then give up
|
|
85
|
+
* with the corrupt file preserved at `<file>.corrupt` for forensics rather
|
|
86
|
+
* than silently starting empty.
|
|
87
|
+
*/
|
|
88
|
+
_loadFromDisk() {
|
|
89
|
+
const fs = this._fs;
|
|
90
|
+
const dir = this._path.dirname(this._filePath);
|
|
91
|
+
if (dir && !fs.existsSync(dir))
|
|
92
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
93
|
+
const parsed = this._readDocument(this._filePath);
|
|
94
|
+
if (parsed)
|
|
95
|
+
return parsed;
|
|
96
|
+
if (fs.existsSync(this._filePath)) {
|
|
97
|
+
const tmp = this._readDocument(`${this._filePath}.tmp`);
|
|
98
|
+
if (tmp)
|
|
99
|
+
return tmp;
|
|
100
|
+
try {
|
|
101
|
+
fs.copyFileSync(this._filePath, `${this._filePath}.corrupt`);
|
|
102
|
+
fs.unlinkSync(this._filePath);
|
|
103
|
+
}
|
|
104
|
+
catch { /* best-effort forensics */ }
|
|
105
|
+
}
|
|
106
|
+
return emptyDocument();
|
|
107
|
+
}
|
|
108
|
+
_readDocument(path) {
|
|
109
|
+
const fs = this._fs;
|
|
110
|
+
if (!fs.existsSync(path))
|
|
111
|
+
return null;
|
|
112
|
+
let raw;
|
|
113
|
+
try {
|
|
114
|
+
raw = JSON.parse(fs.readFileSync(path, 'utf8'));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (!raw || typeof raw !== 'object')
|
|
120
|
+
return null;
|
|
121
|
+
const doc = raw;
|
|
122
|
+
if (doc.formatVersion !== 1 || !Array.isArray(doc.facts))
|
|
123
|
+
return null;
|
|
124
|
+
const full = {
|
|
125
|
+
formatVersion: 1,
|
|
126
|
+
facts: doc.facts,
|
|
127
|
+
tombstones: Array.isArray(doc.tombstones) ? doc.tombstones : [],
|
|
128
|
+
journal: Array.isArray(doc.journal) ? doc.journal : [],
|
|
129
|
+
meta: doc.meta?.key === 'counters'
|
|
130
|
+
? doc.meta
|
|
131
|
+
: { key: 'counters', seq: 0, revision: 0, opSeq: 0 },
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
const st = fs.statSync(path);
|
|
135
|
+
this._stamp = { mtimeMs: st.mtimeMs, size: st.size };
|
|
136
|
+
}
|
|
137
|
+
catch { /* stamp stays null; next write re-checks */ }
|
|
138
|
+
return full;
|
|
139
|
+
}
|
|
140
|
+
/** Re-reads the document when another process rewrote the file. */
|
|
141
|
+
_refreshIfChanged() {
|
|
142
|
+
const fs = this._fs;
|
|
143
|
+
if (!fs.existsSync(this._filePath)) {
|
|
144
|
+
// Deleted underneath us: an empty document is the truth now.
|
|
145
|
+
if (this._stamp) {
|
|
146
|
+
this._stamp = null;
|
|
147
|
+
this._doc = emptyDocument();
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
let st;
|
|
152
|
+
try {
|
|
153
|
+
st = fs.statSync(this._filePath);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (this._stamp && st.mtimeMs === this._stamp.mtimeMs && st.size === this._stamp.size)
|
|
159
|
+
return;
|
|
160
|
+
const doc = this._readDocument(this._filePath);
|
|
161
|
+
if (doc)
|
|
162
|
+
this._doc = doc;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Atomic durable write: fsync the full document to `<file>.tmp`, then rename
|
|
166
|
+
* over the live file. A crash leaves the old file intact; the tmp file is
|
|
167
|
+
* the recovery source on next boot.
|
|
168
|
+
*/
|
|
169
|
+
_persist() {
|
|
170
|
+
const fs = this._fs;
|
|
171
|
+
const text = JSON.stringify(this._doc);
|
|
172
|
+
const tmp = `${this._filePath}.tmp`;
|
|
173
|
+
const fd = fs.openSync(tmp, 'w');
|
|
174
|
+
try {
|
|
175
|
+
fs.writeFileSync(fd, text);
|
|
176
|
+
fs.fsyncSync(fd);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
fs.closeSync(fd);
|
|
180
|
+
}
|
|
181
|
+
fs.renameSync(tmp, this._filePath);
|
|
182
|
+
try {
|
|
183
|
+
const st = fs.statSync(this._filePath);
|
|
184
|
+
this._stamp = { mtimeMs: st.mtimeMs, size: st.size };
|
|
185
|
+
}
|
|
186
|
+
catch { /* re-stamped on next change check */ }
|
|
187
|
+
}
|
|
188
|
+
// ── Port ──────────────────────────────────────────────────────────────────
|
|
189
|
+
/**
|
|
190
|
+
* Serialized unit of work. Read-write transactions take the cross-process
|
|
191
|
+
* lock, refresh from disk (so we build on other processes' commits), run
|
|
192
|
+
* `fn` against the live document, then persist atomically before
|
|
193
|
+
* releasing. Any failure rolls the in-memory document back to the
|
|
194
|
+
* pre-transaction snapshot — no partial commits, in memory or on disk.
|
|
195
|
+
*/
|
|
196
|
+
transact(_stores, mode, fn) {
|
|
197
|
+
const run = this._queue.then(() => mode === 'readwrite'
|
|
198
|
+
? this._lock.run(() => this._readWrite(fn))
|
|
199
|
+
: Promise.resolve(fn(new FileTransaction(this._doc))));
|
|
200
|
+
// Keep the queue alive across rejections.
|
|
201
|
+
this._queue = run.catch(() => undefined);
|
|
202
|
+
return run;
|
|
203
|
+
}
|
|
204
|
+
async _readWrite(fn) {
|
|
205
|
+
this._refreshIfChanged();
|
|
206
|
+
const snapshot = JSON.stringify(this._doc);
|
|
207
|
+
let result;
|
|
208
|
+
try {
|
|
209
|
+
result = await fn(new FileTransaction(this._doc));
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
// A unit of work that threw leaves nothing behind, in memory either.
|
|
213
|
+
this._doc = JSON.parse(snapshot);
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
this._persist();
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
// Roll back: the caller must not observe a committed-looking state
|
|
221
|
+
// that never reached disk.
|
|
222
|
+
this._doc = JSON.parse(snapshot);
|
|
223
|
+
throw new MemoryStorageError('STORAGE_ABORTED', `Failed to durably persist "${this._filePath}": ${err instanceof Error ? err.message : String(err)}`, err);
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
// ── Cross-process consistency seam (M05) ──────────────────────────────────
|
|
228
|
+
publish(note) {
|
|
229
|
+
// Co-process subscribers get the note synchronously (subscribers filter
|
|
230
|
+
// their own origin); foreign processes learn via the watcher.
|
|
231
|
+
for (const l of this._listeners)
|
|
232
|
+
l(note);
|
|
233
|
+
}
|
|
234
|
+
subscribe(listener) {
|
|
235
|
+
this._listeners.add(listener);
|
|
236
|
+
if (this._watchIntervalMs > 0 && !this._watchTimer) {
|
|
237
|
+
let lastRevision = this._doc.meta.revision;
|
|
238
|
+
const timer = setInterval(() => {
|
|
239
|
+
const before = this._doc.meta.revision;
|
|
240
|
+
this._refreshIfChanged();
|
|
241
|
+
if (this._doc.meta.revision !== before && this._doc.meta.revision !== lastRevision) {
|
|
242
|
+
lastRevision = this._doc.meta.revision;
|
|
243
|
+
const note = {
|
|
244
|
+
kind: 'facts-changed',
|
|
245
|
+
revision: this._doc.meta.revision,
|
|
246
|
+
origin: 'foreign',
|
|
247
|
+
};
|
|
248
|
+
for (const l of this._listeners)
|
|
249
|
+
l(note);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
lastRevision = this._doc.meta.revision;
|
|
253
|
+
}
|
|
254
|
+
}, this._watchIntervalMs);
|
|
255
|
+
const maybeUnref = timer;
|
|
256
|
+
if (typeof maybeUnref.unref === 'function')
|
|
257
|
+
maybeUnref.unref();
|
|
258
|
+
this._watchTimer = timer;
|
|
259
|
+
}
|
|
260
|
+
return () => {
|
|
261
|
+
this._listeners.delete(listener);
|
|
262
|
+
if (this._listeners.size === 0 && this._watchTimer) {
|
|
263
|
+
clearInterval(this._watchTimer);
|
|
264
|
+
this._watchTimer = null;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
close() {
|
|
269
|
+
if (this._watchTimer) {
|
|
270
|
+
clearInterval(this._watchTimer);
|
|
271
|
+
this._watchTimer = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=nodeFileAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeFileAdapter.js","sourceRoot":"","sources":["../../src/storage/nodeFileAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storage/syncJournal.ts – The sync-acknowledgement half of the journal (M04/M06).
|
|
3
|
+
*
|
|
4
|
+
* The journal holds only operations still owed to a sync target: entries are
|
|
5
|
+
* written unsynced (and only when sync is enabled — see `factMutations.journal`)
|
|
6
|
+
* and are DELETED once acknowledged, so the journal stays bounded by the
|
|
7
|
+
* outstanding work instead of growing with every write ever made.
|
|
8
|
+
*/
|
|
9
|
+
import type { FactStoragePort, JournalEntry } from './types.js';
|
|
10
|
+
/** Journal entries not yet acknowledged by sync — the pending operation set. */
|
|
11
|
+
export declare function pendingOps(storage: FactStoragePort): Promise<JournalEntry[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Acknowledges synced operations: their journal entries are pruned, and the
|
|
14
|
+
* facts/tombstones they produced clear `pendingSync`, making cap eviction
|
|
15
|
+
* eligible again. Rows already marked synced (written by older versions,
|
|
16
|
+
* which journaled every write even without sync) are pruned too.
|
|
17
|
+
*/
|
|
18
|
+
export declare function markSynced(storage: FactStoragePort, opIds: string[]): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=syncJournal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncJournal.d.ts","sourceRoot":"","sources":["../../src/storage/syncJournal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAqC,MAAM,YAAY,CAAC;AAEnG,gFAAgF;AAChF,wBAAsB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAKlF;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBzF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storage/syncJournal.ts – The sync-acknowledgement half of the journal (M04/M06).
|
|
3
|
+
*
|
|
4
|
+
* The journal holds only operations still owed to a sync target: entries are
|
|
5
|
+
* written unsynced (and only when sync is enabled — see `factMutations.journal`)
|
|
6
|
+
* and are DELETED once acknowledged, so the journal stays bounded by the
|
|
7
|
+
* outstanding work instead of growing with every write ever made.
|
|
8
|
+
*/
|
|
9
|
+
/** Journal entries not yet acknowledged by sync — the pending operation set. */
|
|
10
|
+
export async function pendingOps(storage) {
|
|
11
|
+
return storage.transact(['journal'], 'readonly', async (tx) => {
|
|
12
|
+
const all = await tx.getAll('journal');
|
|
13
|
+
return all.filter((j) => !j.synced);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Acknowledges synced operations: their journal entries are pruned, and the
|
|
18
|
+
* facts/tombstones they produced clear `pendingSync`, making cap eviction
|
|
19
|
+
* eligible again. Rows already marked synced (written by older versions,
|
|
20
|
+
* which journaled every write even without sync) are pruned too.
|
|
21
|
+
*/
|
|
22
|
+
export async function markSynced(storage, opIds) {
|
|
23
|
+
if (opIds.length === 0)
|
|
24
|
+
return;
|
|
25
|
+
const acked = new Set(opIds);
|
|
26
|
+
await storage.transact(['journal', 'facts', 'tombstones'], 'readwrite', async (tx) => {
|
|
27
|
+
const journal = await tx.getAll('journal');
|
|
28
|
+
for (const entry of journal) {
|
|
29
|
+
if (entry.synced || acked.has(entry.opId))
|
|
30
|
+
await tx.delete('journal', entry.opId);
|
|
31
|
+
}
|
|
32
|
+
const facts = await tx.getAll('facts');
|
|
33
|
+
for (const record of facts) {
|
|
34
|
+
if (acked.has(record.opId) && record.pendingSync) {
|
|
35
|
+
await tx.put('facts', { ...record, pendingSync: false });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const tombstones = await tx.getAll('tombstones');
|
|
39
|
+
for (const tomb of tombstones) {
|
|
40
|
+
if (acked.has(tomb.opId) && tomb.pendingSync) {
|
|
41
|
+
await tx.put('tombstones', { ...tomb, pendingSync: false });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=syncJournal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncJournal.js","sourceRoot":"","sources":["../../src/storage/syncJournal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAwB;IACrD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC1D,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,MAAM,CAAe,SAAS,CAAC,CAAC;QACrD,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAwB,EAAE,KAAe;IACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC/B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QACjF,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAe,SAAS,CAAC,CAAC;QACzD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,MAAM,CAAmB,OAAO,CAAC,CAAC;QACzD,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBAC/C,MAAM,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7D,CAAC;QACL,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,MAAM,CAAkB,YAAY,CAAC,CAAC;QAClE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC3C,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;YAChE,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storage/types.ts – Narrow storage/transaction ports for durable memory.
|
|
3
|
+
*
|
|
4
|
+
* This module is the seam the offline-memory scope (M01–M05) requires:
|
|
5
|
+
* - MemoryStore no longer owns IndexedDB directly; it speaks to a
|
|
6
|
+
* {@link FactStoragePort} with REAL transaction semantics (a write resolves
|
|
7
|
+
* only after the transaction commits, and rejects on abort — M02).
|
|
8
|
+
* - Facts carry explicit scope partitions (account/tenant/project/repository/
|
|
9
|
+
* chat — or the explicit unbound scope for personal, never-connected
|
|
10
|
+
* memory), durable revisions, operation identity and provenance (M03/M04).
|
|
11
|
+
* - Deletes write tombstones, and every mutation appends to a sync journal in
|
|
12
|
+
* the SAME transaction as the fact change, so a crash can never acknowledge
|
|
13
|
+
* a fact whose journal entry was lost (M02/M04).
|
|
14
|
+
*
|
|
15
|
+
* Two durable adapters implement the port:
|
|
16
|
+
* - `IdbStorageAdapter` (browser / VSIX webview — IndexedDB)
|
|
17
|
+
* - `NodeFileStorageAdapter` (VSIX extension host / MCP — atomic JSON file)
|
|
18
|
+
* An in-memory IndexedDB shim (fake-indexeddb) is a TEST double, never a
|
|
19
|
+
* durability claim.
|
|
20
|
+
*/
|
|
21
|
+
import { SSMError } from '../errors/SSMError.js';
|
|
22
|
+
/** Explicit ownership partition for a fact. All fields optional; absent = unbound. */
|
|
23
|
+
export interface MemoryScope {
|
|
24
|
+
account?: string;
|
|
25
|
+
tenant?: string;
|
|
26
|
+
project?: string;
|
|
27
|
+
repository?: string;
|
|
28
|
+
chat?: string;
|
|
29
|
+
}
|
|
30
|
+
/** The explicit "no account, never connected" partition — NOT the absence of a scope. */
|
|
31
|
+
export declare const UNBOUND_SCOPE: MemoryScope;
|
|
32
|
+
/**
|
|
33
|
+
* Canonical partition string for a scope. The unbound scope partitions to the
|
|
34
|
+
* empty string, so legacy records (written before scopes existed) land in the
|
|
35
|
+
* unbound partition and stay readable. Two scopes that differ in any field are
|
|
36
|
+
* different partitions — callers can never widen a recall by omitting a field
|
|
37
|
+
* unless they genuinely target the unbound partition.
|
|
38
|
+
*/
|
|
39
|
+
export declare function scopePartitionKey(scope: MemoryScope | undefined): string;
|
|
40
|
+
/**
|
|
41
|
+
* Physical record id for a (scope, key) pair. Unbound-scope ids are the bare
|
|
42
|
+
* key — byte-identical to pre-scope records, so existing databases migrate
|
|
43
|
+
* without rewriting keys.
|
|
44
|
+
*/
|
|
45
|
+
export declare function scopedRecordId(scope: MemoryScope | undefined, key: string): string;
|
|
46
|
+
/** Where a fact came from. Survives export/import so source time is never rewritten. */
|
|
47
|
+
export interface FactProvenance {
|
|
48
|
+
/** Origin surface/agent, e.g. 'vscode', 'mcp', 'web', 'cognition'. */
|
|
49
|
+
source?: string;
|
|
50
|
+
/** Snapshot/store this record was imported from. */
|
|
51
|
+
importedFrom?: string;
|
|
52
|
+
/** The timestamp the source store recorded; preserved across import (M06). */
|
|
53
|
+
originalTimestamp?: number;
|
|
54
|
+
}
|
|
55
|
+
export type StorageStoreName = 'facts' | 'tombstones' | 'journal' | 'meta';
|
|
56
|
+
/**
|
|
57
|
+
* Secondary index on {@link StoredFactRecord.key}. Key lookup uses this (or the
|
|
58
|
+
* primary key) — never a full scan — when the adapter has built it.
|
|
59
|
+
*/
|
|
60
|
+
export declare const FACT_KEY_INDEX = "byKey";
|
|
61
|
+
/**
|
|
62
|
+
* Secondary multi-entry index on {@link StoredFactRecord.tags}. Tag lookup uses
|
|
63
|
+
* this when present and falls back to a scan only when the index is missing.
|
|
64
|
+
*/
|
|
65
|
+
export declare const FACT_TAG_INDEX = "byTag";
|
|
66
|
+
/**
|
|
67
|
+
* The durable fact record. Extends the legacy MemoryEntry shape with identity,
|
|
68
|
+
* revision and sync state. `pendingSync` is only ever true when the owning
|
|
69
|
+
* store was created with sync enabled — without a sync target there is nothing
|
|
70
|
+
* to be pending FOR, and eviction must behave exactly as before (M06).
|
|
71
|
+
*/
|
|
72
|
+
export interface StoredFactRecord {
|
|
73
|
+
/** Physical primary key: {@link scopedRecordId}. */
|
|
74
|
+
id: string;
|
|
75
|
+
/** Caller-facing key within the scope. */
|
|
76
|
+
key: string;
|
|
77
|
+
/** Ownership partition. */
|
|
78
|
+
scope: MemoryScope;
|
|
79
|
+
content: string;
|
|
80
|
+
/** Wall-clock write time (source time; preserved by import). */
|
|
81
|
+
timestamp: number;
|
|
82
|
+
/** Durable monotonic tiebreaker for same-millisecond writes. */
|
|
83
|
+
seq: number;
|
|
84
|
+
/** Durable revision: the store generation that last wrote this record. */
|
|
85
|
+
revision: number;
|
|
86
|
+
/** Durable identity of the operation that produced this record. */
|
|
87
|
+
opId: string;
|
|
88
|
+
ttlMs?: number;
|
|
89
|
+
type?: 'text' | 'json' | 'number' | 'boolean';
|
|
90
|
+
tags?: string[];
|
|
91
|
+
importance?: number;
|
|
92
|
+
provenance?: FactProvenance;
|
|
93
|
+
/** True until a sync acknowledgement clears it; such facts are NEVER evicted by caps. */
|
|
94
|
+
pendingSync: boolean;
|
|
95
|
+
}
|
|
96
|
+
/** A durable deletion marker. Deletes propagate through sync; tombstones prevent resurrection. */
|
|
97
|
+
export interface TombstoneRecord {
|
|
98
|
+
id: string;
|
|
99
|
+
key: string;
|
|
100
|
+
scope: MemoryScope;
|
|
101
|
+
deletedAt: number;
|
|
102
|
+
/** Store generation of the delete; always greater than the fact's last revision. */
|
|
103
|
+
revision: number;
|
|
104
|
+
opId: string;
|
|
105
|
+
pendingSync: boolean;
|
|
106
|
+
}
|
|
107
|
+
export type JournalOpKind = 'remember' | 'forget';
|
|
108
|
+
/** One durable operation in the sync journal — written atomically with its fact change. */
|
|
109
|
+
export interface JournalEntry {
|
|
110
|
+
opId: string;
|
|
111
|
+
kind: JournalOpKind;
|
|
112
|
+
id: string;
|
|
113
|
+
key: string;
|
|
114
|
+
scope: MemoryScope;
|
|
115
|
+
/** Store generation after this op committed. */
|
|
116
|
+
revision: number;
|
|
117
|
+
/** The revision the writer based its write on (CAS); undefined = blind write. */
|
|
118
|
+
baseRevision?: number;
|
|
119
|
+
timestamp: number;
|
|
120
|
+
synced: boolean;
|
|
121
|
+
}
|
|
122
|
+
/** Counters kept durably in the meta store. */
|
|
123
|
+
export interface MetaCounters {
|
|
124
|
+
key: 'counters';
|
|
125
|
+
/** Durable write sequence (replaces the process-local `_writeSeq`). */
|
|
126
|
+
seq: number;
|
|
127
|
+
/** Durable store generation, bumped by every committed write transaction. */
|
|
128
|
+
revision: number;
|
|
129
|
+
/** Durable operation counter; opIds are `${contextId}:${opSeq}`. */
|
|
130
|
+
opSeq: number;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The minimal surface a transaction offers. Adapters MUST keep the underlying
|
|
134
|
+
* transaction alive across the awaited operations (request-chained awaits only)
|
|
135
|
+
* and MUST NOT resolve {@link FactStoragePort.transact} before commit.
|
|
136
|
+
*/
|
|
137
|
+
export interface StorageTransaction {
|
|
138
|
+
get<T>(store: StorageStoreName, id: string): Promise<T | undefined>;
|
|
139
|
+
getAll<T>(store: StorageStoreName): Promise<T[]>;
|
|
140
|
+
put(store: StorageStoreName, value: unknown): Promise<void>;
|
|
141
|
+
delete(store: StorageStoreName, id: string): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Lookup via a named index. `undefined` means this store has no such index
|
|
144
|
+
* (legacy schema, or an adapter that has not built it) — the caller MUST
|
|
145
|
+
* fall back to a primary-key get or a scan. An empty array means the index
|
|
146
|
+
* exists and matched nothing; do not scan in that case.
|
|
147
|
+
*/
|
|
148
|
+
getByIndex?<T>(store: StorageStoreName, indexName: string, query: string): Promise<T[] | undefined>;
|
|
149
|
+
}
|
|
150
|
+
/** A change notice for cross-context invalidation (M05). */
|
|
151
|
+
export interface ConsistencyNote {
|
|
152
|
+
kind: 'facts-changed';
|
|
153
|
+
/** Scope partition that changed; undefined = whole database. */
|
|
154
|
+
scope?: MemoryScope;
|
|
155
|
+
/** Durable generation after the change. */
|
|
156
|
+
revision: number;
|
|
157
|
+
/** Identity of the committing context; a context ignores its own notes. */
|
|
158
|
+
origin: string;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The narrow durable-storage port. One transaction = one unit of work:
|
|
162
|
+
* fact mutation + provenance + tombstone/event + journal commit together or
|
|
163
|
+
* not at all.
|
|
164
|
+
*/
|
|
165
|
+
export interface FactStoragePort {
|
|
166
|
+
/** Which durable backend this is (for honest capability reporting). */
|
|
167
|
+
readonly kind: 'indexedDB' | 'node-file';
|
|
168
|
+
/**
|
|
169
|
+
* Runs `fn` inside one real transaction over the named stores. Resolves with
|
|
170
|
+
* `fn`'s result ONLY after the transaction has committed durably; rejects if
|
|
171
|
+
* the transaction aborts, `fn` throws, or the persist fails. A rejected
|
|
172
|
+
* transact leaves NO partial writes behind.
|
|
173
|
+
*/
|
|
174
|
+
transact<T>(stores: StorageStoreName[], mode: 'readonly' | 'readwrite', fn: (tx: StorageTransaction) => Promise<T> | T): Promise<T>;
|
|
175
|
+
/**
|
|
176
|
+
* Publishes a change notice to other contexts sharing this storage, when the
|
|
177
|
+
* adapter supports cross-context notification. Optional.
|
|
178
|
+
*/
|
|
179
|
+
publish?(note: ConsistencyNote): void;
|
|
180
|
+
/**
|
|
181
|
+
* Subscribes to change notices from OTHER contexts (tabs, extension windows,
|
|
182
|
+
* MCP processes) sharing this storage. Returns an unsubscribe function.
|
|
183
|
+
* Optional: an adapter without it simply offers no cross-context seam.
|
|
184
|
+
*/
|
|
185
|
+
subscribe?(listener: (note: ConsistencyNote) => void): () => void;
|
|
186
|
+
close?(): void | Promise<void>;
|
|
187
|
+
}
|
|
188
|
+
/** Detailed error codes surfaced by the storage layer (`MemoryStorageError.storageCode`). */
|
|
189
|
+
export type StorageErrorCode =
|
|
190
|
+
/** The durable backend is unavailable or faulted. */
|
|
191
|
+
'STORAGE_UNAVAILABLE'
|
|
192
|
+
/** A compare-and-set write found a newer revision than its base. */
|
|
193
|
+
| 'STORAGE_CONFLICT'
|
|
194
|
+
/** A transaction aborted; no partial writes were committed. */
|
|
195
|
+
| 'STORAGE_ABORTED';
|
|
196
|
+
/**
|
|
197
|
+
* A storage-layer failure. It IS an {@link SSMError} whose `code` is the public
|
|
198
|
+
* contract (`MEMORY_UNAVAILABLE` / `MEMORY_CONFLICT`); `storageCode` keeps the
|
|
199
|
+
* detailed storage code for callers that need it.
|
|
200
|
+
*/
|
|
201
|
+
export declare class MemoryStorageError extends SSMError {
|
|
202
|
+
readonly storageCode: StorageErrorCode;
|
|
203
|
+
constructor(storageCode: StorageErrorCode, message: string, cause?: unknown);
|
|
204
|
+
}
|
|
205
|
+
/** Type guard for conflict errors so callers can reconcile instead of blindly retrying. */
|
|
206
|
+
export declare function isStorageConflict(err: unknown): err is MemoryStorageError;
|
|
207
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"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"}
|