@fgv/ts-agent-memory 5.1.0-49 → 5.1.0-50
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/packlets/index/memoryIndex.js +23 -16
- package/dist/packlets/index/memoryIndex.js.map +1 -1
- package/dist/packlets/ingest/orchestrator.js +13 -1
- package/dist/packlets/ingest/orchestrator.js.map +1 -1
- package/dist/packlets/retrieve/linkTraversalRetriever.js +12 -26
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/dist/packlets/retrieve/recencyRetriever.js +7 -7
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/dist/packlets/retrieve/retriever.js +91 -10
- package/dist/packlets/retrieve/retriever.js.map +1 -1
- package/dist/packlets/retrieve/semanticRetriever.js +16 -16
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/dist/packlets/retrieve/structuredFilterRetriever.js +7 -7
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/dist/packlets/retrieve/tagRetriever.js +7 -7
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
- package/dist/packlets/retrieve/temporalRetrievers.js +23 -20
- package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/dist/packlets/store/coverage.js +6 -0
- package/dist/packlets/store/coverage.js.map +1 -0
- package/dist/packlets/store/fileTreeMemoryStore.js +221 -79
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/store/index.js +4 -0
- package/dist/packlets/store/index.js.map +1 -1
- package/dist/packlets/store/listSelection.js +36 -0
- package/dist/packlets/store/listSelection.js.map +1 -0
- package/dist/packlets/store/memoryStore.js +6 -0
- package/dist/packlets/store/memoryStore.js.map +1 -0
- package/dist/packlets/store/reconcile.js +6 -0
- package/dist/packlets/store/reconcile.js.map +1 -0
- package/dist/packlets/store/storeCoverage.js +102 -0
- package/dist/packlets/store/storeCoverage.js.map +1 -0
- package/dist/packlets/store/storeReconcile.js +122 -0
- package/dist/packlets/store/storeReconcile.js.map +1 -0
- package/dist/packlets/store/vectorMaintenance.js +116 -8
- package/dist/packlets/store/vectorMaintenance.js.map +1 -1
- package/dist/packlets/store/vectorRecordSource.js +44 -0
- package/dist/packlets/store/vectorRecordSource.js.map +1 -0
- package/dist/packlets/tools/memoryTools.js +25 -2
- package/dist/packlets/tools/memoryTools.js.map +1 -1
- package/dist/packlets/types/envelope.js +25 -0
- package/dist/packlets/types/envelope.js.map +1 -1
- package/dist/packlets/types/index.js +1 -0
- package/dist/packlets/types/index.js.map +1 -1
- package/dist/packlets/types/recordResolver.js +6 -0
- package/dist/packlets/types/recordResolver.js.map +1 -0
- package/dist/packlets/types/temporal.js.map +1 -1
- package/dist/packlets/vector/inMemoryCosineIndex.js +39 -18
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +67 -12
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/dist/packlets/vector/rebuildHelpers.js +38 -0
- package/dist/packlets/vector/rebuildHelpers.js.map +1 -0
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +1035 -106
- package/lib/packlets/index/memoryIndex.d.ts +118 -27
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
- package/lib/packlets/index/memoryIndex.js +23 -16
- package/lib/packlets/index/memoryIndex.js.map +1 -1
- package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
- package/lib/packlets/ingest/orchestrator.js +13 -1
- package/lib/packlets/ingest/orchestrator.js.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +3 -10
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.js +11 -25
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.js +6 -6
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/lib/packlets/retrieve/retriever.d.ts +88 -7
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/retriever.js +94 -9
- package/lib/packlets/retrieve/retriever.js.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.d.ts +3 -5
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.js +15 -15
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.js +6 -6
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.js +6 -6
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.d.ts +7 -5
- package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.js +22 -19
- package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/lib/packlets/store/coverage.d.ts +102 -0
- package/lib/packlets/store/coverage.d.ts.map +1 -0
- package/lib/packlets/store/coverage.js +7 -0
- package/lib/packlets/store/coverage.js.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +53 -166
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +221 -79
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/store/index.d.ts +4 -0
- package/lib/packlets/store/index.d.ts.map +1 -1
- package/lib/packlets/store/index.js +4 -0
- package/lib/packlets/store/index.js.map +1 -1
- package/lib/packlets/store/listSelection.d.ts +101 -0
- package/lib/packlets/store/listSelection.d.ts.map +1 -0
- package/lib/packlets/store/listSelection.js +40 -0
- package/lib/packlets/store/listSelection.js.map +1 -0
- package/lib/packlets/store/memoryStore.d.ts +237 -0
- package/lib/packlets/store/memoryStore.d.ts.map +1 -0
- package/lib/packlets/store/memoryStore.js +7 -0
- package/lib/packlets/store/memoryStore.js.map +1 -0
- package/lib/packlets/store/reconcile.d.ts +82 -0
- package/lib/packlets/store/reconcile.d.ts.map +1 -0
- package/lib/packlets/store/reconcile.js +7 -0
- package/lib/packlets/store/reconcile.js.map +1 -0
- package/lib/packlets/store/storeCoverage.d.ts +45 -0
- package/lib/packlets/store/storeCoverage.d.ts.map +1 -0
- package/lib/packlets/store/storeCoverage.js +105 -0
- package/lib/packlets/store/storeCoverage.js.map +1 -0
- package/lib/packlets/store/storeReconcile.d.ts +41 -0
- package/lib/packlets/store/storeReconcile.d.ts.map +1 -0
- package/lib/packlets/store/storeReconcile.js +125 -0
- package/lib/packlets/store/storeReconcile.js.map +1 -0
- package/lib/packlets/store/vectorMaintenance.d.ts +74 -0
- package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -1
- package/lib/packlets/store/vectorMaintenance.js +117 -8
- package/lib/packlets/store/vectorMaintenance.js.map +1 -1
- package/lib/packlets/store/vectorRecordSource.d.ts +36 -0
- package/lib/packlets/store/vectorRecordSource.d.ts.map +1 -0
- package/lib/packlets/store/vectorRecordSource.js +47 -0
- package/lib/packlets/store/vectorRecordSource.js.map +1 -0
- package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
- package/lib/packlets/tools/memoryTools.js +25 -2
- package/lib/packlets/tools/memoryTools.js.map +1 -1
- package/lib/packlets/types/envelope.d.ts +24 -2
- package/lib/packlets/types/envelope.d.ts.map +1 -1
- package/lib/packlets/types/envelope.js +26 -0
- package/lib/packlets/types/envelope.js.map +1 -1
- package/lib/packlets/types/index.d.ts +1 -0
- package/lib/packlets/types/index.d.ts.map +1 -1
- package/lib/packlets/types/index.js +1 -0
- package/lib/packlets/types/index.js.map +1 -1
- package/lib/packlets/types/recordResolver.d.ts +39 -0
- package/lib/packlets/types/recordResolver.d.ts.map +1 -0
- package/lib/packlets/types/recordResolver.js +7 -0
- package/lib/packlets/types/recordResolver.js.map +1 -0
- package/lib/packlets/types/temporal.d.ts +26 -6
- package/lib/packlets/types/temporal.d.ts.map +1 -1
- package/lib/packlets/types/temporal.js.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -2
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.js +40 -19
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +6 -3
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +66 -11
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/lib/packlets/vector/rebuildHelpers.d.ts +30 -0
- package/lib/packlets/vector/rebuildHelpers.d.ts.map +1 -0
- package/lib/packlets/vector/rebuildHelpers.js +42 -0
- package/lib/packlets/vector/rebuildHelpers.js.map +1 -0
- package/lib/packlets/vector/vectorIndex.d.ts +270 -15
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/package.json +7 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
|
-
import { IEdgeTarget, IMemoryRecord, Kind, MemoryScopeKey, Tag } from '../types';
|
|
2
|
+
import { IEdgeTarget, IMemoryEnvelope, IMemoryRecord, Kind, MemoryScopeKey, Tag } from '../types';
|
|
3
3
|
/**
|
|
4
4
|
* The mutation a {@link IMemoryIndex.patch | patch} applies: a record was
|
|
5
5
|
* written (`'put'`) or removed (`'delete'`).
|
|
@@ -21,6 +21,32 @@ export interface IIndexedMemoryRecord {
|
|
|
21
21
|
/** The memory record itself. */
|
|
22
22
|
readonly record: IMemoryRecord<unknown>;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* What the index HOLDS and what every index read returns: a record's scope and
|
|
26
|
+
* its {@link IMemoryEnvelope}, and **no body**.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* The index is a derived *selection* structure, and selection has never needed a
|
|
30
|
+
* body — every filter the store and the retrievers apply reads envelope fields
|
|
31
|
+
* (`scope` / `kind` / `tags` / `contentHash` / `provenance` / `links` /
|
|
32
|
+
* `temporal` / `updated` / `seq` / `rank`). Returning whole records made every
|
|
33
|
+
* conforming index hold every body by construction, which was the store's
|
|
34
|
+
* resident-memory ceiling; returning envelopes removes it from the contract
|
|
35
|
+
* rather than from one implementation.
|
|
36
|
+
*
|
|
37
|
+
* A caller that needs the body **materializes it explicitly** — through
|
|
38
|
+
* {@link IMemoryStore.getById}, or an `IMemoryRecordResolver` where one is
|
|
39
|
+
* wired. That is deliberately visible: a lazy `body` getter would have kept
|
|
40
|
+
* every call site compiling while turning a memory read into a file read behind
|
|
41
|
+
* an unchanged type, which is a silent performance cliff rather than a migration.
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export interface IIndexedMemoryEntry {
|
|
45
|
+
/** The scope the record is stored under. */
|
|
46
|
+
readonly scope: MemoryScopeKey;
|
|
47
|
+
/** The record's envelope. No body — see the remarks. */
|
|
48
|
+
readonly envelope: IMemoryEnvelope;
|
|
49
|
+
}
|
|
24
50
|
/**
|
|
25
51
|
* The derived, in-memory secondary indexes the store maintains over its
|
|
26
52
|
* records. Never the source of truth — the FileTree is. The index is fully
|
|
@@ -28,40 +54,98 @@ export interface IIndexedMemoryRecord {
|
|
|
28
54
|
* patched incrementally on every write ({@link IMemoryIndex.patch}).
|
|
29
55
|
*
|
|
30
56
|
* @remarks
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
57
|
+
* Every read returns the projected {@link IIndexedMemoryEntry} — scope and
|
|
58
|
+
* envelope, no body. See that type for why.
|
|
59
|
+
*
|
|
60
|
+
* **The conformance rule: an index is a derived, COMPLETE, FAITHFUL projection
|
|
61
|
+
* of the vault.** An implementation may change *where* entries are stored and
|
|
62
|
+
* *how* they are looked up; it may not change *which* entries exist or *what any
|
|
63
|
+
* envelope says*. Concretely, {@link IMemoryIndex.entries} must return exactly
|
|
64
|
+
* one entry per record the store has written and not deleted, and each entry's
|
|
65
|
+
* envelope must be the one the store patched in.
|
|
66
|
+
*
|
|
67
|
+
* An index that filters, truncates, deduplicates, or synthesizes entries is not
|
|
68
|
+
* a conforming implementation, and the reason is not tidiness: **the store's
|
|
69
|
+
* write path derives from these reads** — content-hash dedup, write-policy
|
|
70
|
+
* admission cohorts, and temporal version histories all read the index. An index
|
|
71
|
+
* that hides an entry does not merely hide it from queries; it changes what the
|
|
72
|
+
* next write does. That is why the previous guidance said only a faithful
|
|
73
|
+
* delegating decorator was safe to inject. This invariant is what that guidance
|
|
74
|
+
* was reaching for, stated so that a genuinely different implementation (a
|
|
75
|
+
* SQLite-backed index, a lazily-paged one) is permitted while the reshaping that
|
|
76
|
+
* was the actual hazard stays out.
|
|
77
|
+
*
|
|
78
|
+
* **Ordering is NOT part of the contract.** {@link IMemoryIndex.entries} may
|
|
79
|
+
* return entries in any order and callers that need one sort explicitly. Note
|
|
80
|
+
* this is a *behavioural* freedom the compiler cannot police: the bundled
|
|
81
|
+
* {@link MemoryIndex} iterates a `Map` and so returns insertion order, which is
|
|
82
|
+
* stable and observable, so code that came to rely on it keeps compiling and
|
|
83
|
+
* changes results. The ordered accessors ({@link IMemoryIndex.byRecency},
|
|
84
|
+
* {@link IMemoryIndex.byRank}, and the recency-ordered `byKind` / `byTag`) are
|
|
85
|
+
* the supported way to ask for an order.
|
|
34
86
|
* @public
|
|
35
87
|
*/
|
|
36
88
|
export interface IMemoryIndex {
|
|
37
89
|
/**
|
|
38
|
-
* Replace the entire index from a full set of
|
|
39
|
-
*
|
|
90
|
+
* Replace the entire index from a full set of entries (a store walk).
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* Takes the **projected** {@link IIndexedMemoryEntry} form, not whole records,
|
|
94
|
+
* and the distinction is load-bearing rather than cosmetic: **`patch` writes,
|
|
95
|
+
* `rebuild` reads.** A rebuild is a whole-vault read that happens to terminate
|
|
96
|
+
* in the index, so requiring whole records here would force every caller — the
|
|
97
|
+
* store's own open path included — to materialize N bodies purely to feed a
|
|
98
|
+
* structure that projects the envelope back out and discards them. `patch`
|
|
99
|
+
* keeps whole records because it carries exactly one, which its caller already
|
|
100
|
+
* holds.
|
|
101
|
+
*
|
|
102
|
+
* @returns The number of entries indexed.
|
|
40
103
|
*/
|
|
41
|
-
rebuild(entries: ReadonlyArray<
|
|
104
|
+
rebuild(entries: ReadonlyArray<IIndexedMemoryEntry>): Result<number>;
|
|
42
105
|
/**
|
|
43
106
|
* Apply a single incremental change. `'put'` inserts or replaces the entry
|
|
44
107
|
* at its `(scope, id)` key (removing any prior associations first); `'delete'`
|
|
45
108
|
* removes it.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* Takes the whole record — see {@link IMemoryIndex.rebuild} for why this one
|
|
112
|
+
* does and that one does not. It costs nothing (the caller is mid-write and
|
|
113
|
+
* holds the record already) and it is the single point at which an index
|
|
114
|
+
* maintaining a body-derived view could observe content without a re-read.
|
|
115
|
+
* What is *held* is still only the projection.
|
|
116
|
+
*
|
|
46
117
|
* @returns The entry that was applied.
|
|
47
118
|
*/
|
|
48
119
|
patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord>;
|
|
49
|
-
/** Every indexed entry (scope +
|
|
50
|
-
entries(): ReadonlyArray<
|
|
51
|
-
/** Records of the given kind, in recency order (most-recently-updated first). */
|
|
52
|
-
byKind(kind: Kind): ReadonlyArray<IMemoryRecord<unknown>>;
|
|
53
|
-
/** Records carrying the given tag, in recency order. */
|
|
54
|
-
byTag(tag: Tag): ReadonlyArray<IMemoryRecord<unknown>>;
|
|
55
|
-
/** All records in recency order (most-recently-updated first). */
|
|
56
|
-
byRecency(): ReadonlyArray<IMemoryRecord<unknown>>;
|
|
120
|
+
/** Every indexed entry (scope + envelope). Primary read surface for the store. */
|
|
121
|
+
entries(): ReadonlyArray<IIndexedMemoryEntry>;
|
|
57
122
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
123
|
+
* The entry at a scope-qualified address, or `undefined` if none.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* On the contract because its absence made every caller that wanted **one**
|
|
127
|
+
* entry rebuild a map of **all** of them: both `SemanticRetriever` (resolving
|
|
128
|
+
* at most `topK` hits) and `LinkTraversalRetriever` (resolving a BFS frontier)
|
|
129
|
+
* built a full-index `Map` per query for want of this. The index already keys
|
|
130
|
+
* on `(scope, id)` internally, so this exposes a lookup it was doing anyway.
|
|
131
|
+
*/
|
|
132
|
+
get(target: IEdgeTarget): IIndexedMemoryEntry | undefined;
|
|
133
|
+
/** Entries of the given kind, in recency order (most-recently-updated first). */
|
|
134
|
+
byKind(kind: Kind): ReadonlyArray<IIndexedMemoryEntry>;
|
|
135
|
+
/** Entries carrying the given tag, in recency order. */
|
|
136
|
+
byTag(tag: Tag): ReadonlyArray<IIndexedMemoryEntry>;
|
|
137
|
+
/** All entries in recency order (most-recently-updated first). */
|
|
138
|
+
byRecency(): ReadonlyArray<IIndexedMemoryEntry>;
|
|
139
|
+
/**
|
|
140
|
+
* All entries ordered by store-computed {@link IMemoryEnvelope.rank} descending,
|
|
141
|
+
* with recency (most-recently-updated, then `seq`) as a tiebreak. Entries with
|
|
142
|
+
* an absent `rank` sort LAST (after every ranked entry), then by recency among
|
|
61
143
|
* themselves. Serves a bounded top-M ({@link IMemoryEnvelope.rank}-ordered) page
|
|
62
|
-
* from the in-memory index with no full-vault (filesystem) scan
|
|
144
|
+
* from the in-memory index with no full-vault (filesystem) scan — and since the
|
|
145
|
+
* page is envelope-only, a caller taking the top M materializes M bodies rather
|
|
146
|
+
* than the vault.
|
|
63
147
|
*/
|
|
64
|
-
byRank(): ReadonlyArray<
|
|
148
|
+
byRank(): ReadonlyArray<IIndexedMemoryEntry>;
|
|
65
149
|
/**
|
|
66
150
|
* The scope-qualified sources of records whose `links` point AT `target`
|
|
67
151
|
* (inbound edges), keyed on the target's `(scope, id)` address. The seed map
|
|
@@ -78,7 +162,10 @@ export interface IMemoryIndex {
|
|
|
78
162
|
* @public
|
|
79
163
|
*/
|
|
80
164
|
export declare class MemoryIndex implements IMemoryIndex {
|
|
81
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* Primary store: `(scope, id)` composite key → indexed entry. Holds the
|
|
167
|
+
* PROJECTED form, so the index never retains a body.
|
|
168
|
+
*/
|
|
82
169
|
private readonly _byKey;
|
|
83
170
|
/** kind → set of composite keys. */
|
|
84
171
|
private readonly _byKind;
|
|
@@ -105,19 +192,21 @@ export declare class MemoryIndex implements IMemoryIndex {
|
|
|
105
192
|
*/
|
|
106
193
|
private static _keyOf;
|
|
107
194
|
/** {@inheritDoc IMemoryIndex.rebuild} */
|
|
108
|
-
rebuild(entries: ReadonlyArray<
|
|
195
|
+
rebuild(entries: ReadonlyArray<IIndexedMemoryEntry>): Result<number>;
|
|
109
196
|
/** {@inheritDoc IMemoryIndex.patch} */
|
|
110
197
|
patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord>;
|
|
111
198
|
/** {@inheritDoc IMemoryIndex.entries} */
|
|
112
|
-
entries(): ReadonlyArray<
|
|
199
|
+
entries(): ReadonlyArray<IIndexedMemoryEntry>;
|
|
200
|
+
/** {@inheritDoc IMemoryIndex.get} */
|
|
201
|
+
get(target: IEdgeTarget): IIndexedMemoryEntry | undefined;
|
|
113
202
|
/** {@inheritDoc IMemoryIndex.byKind} */
|
|
114
|
-
byKind(kind: Kind): ReadonlyArray<
|
|
203
|
+
byKind(kind: Kind): ReadonlyArray<IIndexedMemoryEntry>;
|
|
115
204
|
/** {@inheritDoc IMemoryIndex.byTag} */
|
|
116
|
-
byTag(tag: Tag): ReadonlyArray<
|
|
205
|
+
byTag(tag: Tag): ReadonlyArray<IIndexedMemoryEntry>;
|
|
117
206
|
/** {@inheritDoc IMemoryIndex.byRecency} */
|
|
118
|
-
byRecency(): ReadonlyArray<
|
|
207
|
+
byRecency(): ReadonlyArray<IIndexedMemoryEntry>;
|
|
119
208
|
/** {@inheritDoc IMemoryIndex.byRank} */
|
|
120
|
-
byRank(): ReadonlyArray<
|
|
209
|
+
byRank(): ReadonlyArray<IIndexedMemoryEntry>;
|
|
121
210
|
/** {@inheritDoc IMemoryIndex.backlinks} */
|
|
122
211
|
backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget>;
|
|
123
212
|
/**
|
|
@@ -135,6 +224,8 @@ export declare class MemoryIndex implements IMemoryIndex {
|
|
|
135
224
|
* approach; the sort is over the in-memory index, never a filesystem walk.
|
|
136
225
|
*/
|
|
137
226
|
private _rankOrdered;
|
|
227
|
+
/** Resolve composite keys to their entries, skipping any that are absent. */
|
|
228
|
+
private static _resolve;
|
|
138
229
|
/**
|
|
139
230
|
* Rank-descending comparator with an absent-`rank`-last rule and a recency
|
|
140
231
|
* (`updated`, then `seq`) tiebreak. Duplicated from the retrieve packlet's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memoryIndex.d.ts","sourceRoot":"","sources":["../../../src/packlets/index/memoryIndex.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAW,MAAM,eAAe,CAAC;AAChD,OAAO,
|
|
1
|
+
{"version":3,"file":"memoryIndex.d.ts","sourceRoot":"","sources":["../../../src/packlets/index/memoryIndex.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,10 @@ const ts_utils_1 = require("@fgv/ts-utils");
|
|
|
9
9
|
const types_1 = require("../types");
|
|
10
10
|
/** Shared empty key set for `byKind` / `byTag` misses (avoids per-call allocation). */
|
|
11
11
|
const EMPTY_KEY_SET = new Set();
|
|
12
|
+
/** Project the write-side whole-record form onto the held/read entry form. */
|
|
13
|
+
function toEntry(entry) {
|
|
14
|
+
return { scope: entry.scope, envelope: entry.record.envelope };
|
|
15
|
+
}
|
|
12
16
|
/**
|
|
13
17
|
* Default in-memory {@link IMemoryIndex}. Maintains the derived `byKind` /
|
|
14
18
|
* `byTag` / `byRecency` / `backlinks` views incrementally; a `'put'` for an
|
|
@@ -54,7 +58,9 @@ class MemoryIndex {
|
|
|
54
58
|
// changes kind/tags/links cannot strand a stale reference.
|
|
55
59
|
this._remove(key);
|
|
56
60
|
if (op === 'put') {
|
|
57
|
-
|
|
61
|
+
// Projected on the way in: the caller's record is used for its envelope and
|
|
62
|
+
// the body is not retained.
|
|
63
|
+
this._add(toEntry(entry));
|
|
58
64
|
}
|
|
59
65
|
return (0, ts_utils_1.succeed)(entry);
|
|
60
66
|
}
|
|
@@ -62,6 +68,10 @@ class MemoryIndex {
|
|
|
62
68
|
entries() {
|
|
63
69
|
return Array.from(this._byKey.values());
|
|
64
70
|
}
|
|
71
|
+
/** {@inheritDoc IMemoryIndex.get} */
|
|
72
|
+
get(target) {
|
|
73
|
+
return this._byKey.get((0, types_1.edgeTargetKey)(target));
|
|
74
|
+
}
|
|
65
75
|
/** {@inheritDoc IMemoryIndex.byKind} */
|
|
66
76
|
byKind(kind) {
|
|
67
77
|
var _a;
|
|
@@ -91,14 +101,7 @@ class MemoryIndex {
|
|
|
91
101
|
* records sort deterministically).
|
|
92
102
|
*/
|
|
93
103
|
_recencyOrdered(keys) {
|
|
94
|
-
|
|
95
|
-
for (const key of keys) {
|
|
96
|
-
const entry = this._byKey.get(key);
|
|
97
|
-
if (entry !== undefined) {
|
|
98
|
-
records.push(entry.record);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return records.sort((a, b) => {
|
|
104
|
+
return MemoryIndex._resolve(this._byKey, keys).sort((a, b) => {
|
|
102
105
|
const byUpdated = b.envelope.updated - a.envelope.updated;
|
|
103
106
|
return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;
|
|
104
107
|
});
|
|
@@ -112,14 +115,18 @@ class MemoryIndex {
|
|
|
112
115
|
* approach; the sort is over the in-memory index, never a filesystem walk.
|
|
113
116
|
*/
|
|
114
117
|
_rankOrdered(keys) {
|
|
115
|
-
|
|
118
|
+
return MemoryIndex._resolve(this._byKey, keys).sort(MemoryIndex._compareByRank);
|
|
119
|
+
}
|
|
120
|
+
/** Resolve composite keys to their entries, skipping any that are absent. */
|
|
121
|
+
static _resolve(byKey, keys) {
|
|
122
|
+
const entries = [];
|
|
116
123
|
for (const key of keys) {
|
|
117
|
-
const entry =
|
|
124
|
+
const entry = byKey.get(key);
|
|
118
125
|
if (entry !== undefined) {
|
|
119
|
-
|
|
126
|
+
entries.push(entry);
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
|
-
return
|
|
129
|
+
return entries;
|
|
123
130
|
}
|
|
124
131
|
/**
|
|
125
132
|
* Rank-descending comparator with an absent-`rank`-last rule and a recency
|
|
@@ -146,8 +153,8 @@ class MemoryIndex {
|
|
|
146
153
|
}
|
|
147
154
|
/** Insert an entry and register all its derived associations. */
|
|
148
155
|
_add(entry) {
|
|
149
|
-
const key = MemoryIndex._keyOf(entry.scope, entry.
|
|
150
|
-
const envelope = entry.
|
|
156
|
+
const key = MemoryIndex._keyOf(entry.scope, entry.envelope.id);
|
|
157
|
+
const envelope = entry.envelope;
|
|
151
158
|
this._byKey.set(key, entry);
|
|
152
159
|
this._addToSetMap(this._byKind, envelope.kind, key);
|
|
153
160
|
for (const tag of envelope.tags) {
|
|
@@ -163,7 +170,7 @@ class MemoryIndex {
|
|
|
163
170
|
if (entry === undefined) {
|
|
164
171
|
return;
|
|
165
172
|
}
|
|
166
|
-
const envelope = entry.
|
|
173
|
+
const envelope = entry.envelope;
|
|
167
174
|
this._byKey.delete(key);
|
|
168
175
|
this._removeFromSetMap(this._byKind, envelope.kind, key);
|
|
169
176
|
for (const tag of envelope.tags) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memoryIndex.js","sourceRoot":"","sources":["../../../src/packlets/index/memoryIndex.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,4CAAgD;AAChD,oCAA0G;AAS1G,uFAAuF;AACvF,MAAM,aAAa,GAAwB,IAAI,GAAG,EAAU,CAAC;AA2E7D;;;;;;GAMG;AACH,MAAa,WAAW;IAkBtB;QACE,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAgC,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,GAAG,EAAoC,CAAC;IAChE,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM;QAClB,OAAO,IAAA,kBAAO,EAAC,IAAI,WAAW,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,MAAM,CAAC,KAAqB,EAAE,EAAY;QACvD,OAAO,IAAA,qBAAa,EAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,yCAAyC;IAClC,OAAO,CAAC,OAA4C;QACzD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACxB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,IAAA,kBAAO,EAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,uCAAuC;IAChC,KAAK,CAAC,EAAsB,EAAE,KAA2B;QAC9D,MAAM,GAAG,GAAW,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC9E,yEAAyE;QACzE,2DAA2D;QAC3D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClB,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,IAAA,kBAAO,EAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,yCAAyC;IAClC,OAAO;QACZ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,wCAAwC;IACjC,MAAM,CAAC,IAAU;;QACtB,OAAO,IAAI,CAAC,eAAe,CAAC,MAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAI,aAAa,CAAC,CAAC;IACvE,CAAC;IAED,uCAAuC;IAChC,KAAK,CAAC,GAAQ;;QACnB,OAAO,IAAI,CAAC,eAAe,CAAC,MAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,mCAAI,aAAa,CAAC,CAAC;IACrE,CAAC;IAED,2CAA2C;IACpC,SAAS;QACd,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,wCAAwC;IACjC,MAAM;QACX,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,2CAA2C;IACpC,SAAS,CAAC,MAAmB;QAClC,MAAM,OAAO,GAAyC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC,CAAC;QACjG,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnE,CAAC;IAED;;;;OAIG;IACK,eAAe,CAAC,IAAsB;QAC5C,MAAM,OAAO,GAA6B,EAAE,CAAC;QAC7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,KAAK,GAAqC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC3B,MAAM,SAAS,GAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YAClE,OAAO,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACK,YAAY,CAAC,IAAsB;QACzC,MAAM,OAAO,GAA6B,EAAE,CAAC;QAC7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,KAAK,GAAqC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,cAAc,CAAC,CAAyB,EAAE,CAAyB;QAChF,MAAM,EAAE,GAAuB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC/C,MAAM,EAAE,GAAuB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC/C,iFAAiF;QACjF,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACzC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACzC,OAAO,CAAC,CAAC,CAAC;QACZ,CAAC;QACD,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACtD,OAAO,EAAE,GAAG,EAAE,CAAC;QACjB,CAAC;QACD,MAAM,SAAS,GAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;QAClE,OAAO,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;IACvE,CAAC;IAED,iEAAiE;IACzD,IAAI,CAAC,KAA2B;QACtC,MAAM,GAAG,GAAW,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC9E,MAAM,QAAQ,GAAuC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC3E,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACpD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC3C,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,+EAA+E;IACvE,OAAO,CAAC,GAAW;QACzB,MAAM,KAAK,GAAqC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,MAAM,QAAQ,GAAuC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC3E,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACzD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAChD,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,qFAAqF;IAC7E,YAAY,CAAC,MAAmB,EAAE,SAAiB,EAAE,MAAmB;QAC9E,MAAM,SAAS,GAAW,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAyC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,GAAG,CAAsB,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACtF,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,0FAA0F;IAClF,eAAe,CAAC,MAAmB,EAAE,SAAiB;QAC5D,MAAM,SAAS,GAAW,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAyC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3B,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,0EAA0E;IAClE,YAAY,CAAO,GAAmB,EAAE,MAAS,EAAE,MAAS;QAClE,MAAM,QAAQ,GAAuB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,CAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,6EAA6E;IACrE,iBAAiB,CAAO,GAAmB,EAAE,MAAS,EAAE,MAAS;QACvE,MAAM,QAAQ,GAAuB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACxB,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;CACF;AAzOD,kCAyOC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, succeed } from '@fgv/ts-utils';\nimport { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey, Tag, edgeTargetKey } from '../types';\n\n/**\n * The mutation a {@link IMemoryIndex.patch | patch} applies: a record was\n * written (`'put'`) or removed (`'delete'`).\n * @public\n */\nexport type MemoryIndexPatchOp = 'put' | 'delete';\n\n/** Shared empty key set for `byKind` / `byTag` misses (avoids per-call allocation). */\nconst EMPTY_KEY_SET: ReadonlySet<string> = new Set<string>();\n\n/**\n * A memory record paired with the {@link MemoryScopeKey | scope} it lives\n * under. The scope is not carried on the {@link IMemoryEnvelope} (it is derived\n * from the entity id by the codec), so the store threads it alongside the\n * record when patching or rebuilding the index. The `(scope, id)` pair is the\n * index's primary key, keeping records distinct across scopes that reuse a\n * filename stem (e.g. `turn-0` under different conversations in Phase C).\n * @public\n */\nexport interface IIndexedMemoryRecord {\n /** The scope the record is stored under. */\n readonly scope: MemoryScopeKey;\n /** The memory record itself. */\n readonly record: IMemoryRecord<unknown>;\n}\n\n/**\n * The derived, in-memory secondary indexes the store maintains over its\n * records. Never the source of truth — the FileTree is. The index is fully\n * rebuildable from a walk of the store ({@link IMemoryIndex.rebuild}) and is\n * patched incrementally on every write ({@link IMemoryIndex.patch}).\n *\n * @remarks\n * B1 builds the maps; link-traversal BFS over {@link IMemoryIndex.backlinks}\n * is B2. The accessors return records (not bare ids) so the B2 retrievers can\n * consume them directly.\n * @public\n */\nexport interface IMemoryIndex {\n /**\n * Replace the entire index from a full set of records (a store walk).\n * @returns The number of records indexed.\n */\n rebuild(entries: ReadonlyArray<IIndexedMemoryRecord>): Result<number>;\n\n /**\n * Apply a single incremental change. `'put'` inserts or replaces the entry\n * at its `(scope, id)` key (removing any prior associations first); `'delete'`\n * removes it.\n * @returns The entry that was applied.\n */\n patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord>;\n\n /** Every indexed entry (scope + record). Primary read surface for the store. */\n entries(): ReadonlyArray<IIndexedMemoryRecord>;\n\n /** Records of the given kind, in recency order (most-recently-updated first). */\n byKind(kind: Kind): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /** Records carrying the given tag, in recency order. */\n byTag(tag: Tag): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /** All records in recency order (most-recently-updated first). */\n byRecency(): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /**\n * All records ordered by store-computed {@link IMemoryEnvelope.rank} descending,\n * with recency (most-recently-updated, then `seq`) as a tiebreak. Records with\n * an absent `rank` sort LAST (after every ranked record), then by recency among\n * themselves. Serves a bounded top-M ({@link IMemoryEnvelope.rank}-ordered) page\n * from the in-memory index with no full-vault (filesystem) scan.\n */\n byRank(): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /**\n * The scope-qualified sources of records whose `links` point AT `target`\n * (inbound edges), keyed on the target's `(scope, id)` address. The seed map\n * for B2 link-traversal; results are {@link IEdgeTarget}s so a caller can feed\n * them straight back in as further traversal seeds.\n */\n backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget>;\n}\n\n/**\n * Default in-memory {@link IMemoryIndex}. Maintains the derived `byKind` /\n * `byTag` / `byRecency` / `backlinks` views incrementally; a `'put'` for an\n * already-indexed key first removes the prior entry's associations so a changed\n * kind / tag / link set never leaves a stale reference behind.\n * @public\n */\nexport class MemoryIndex implements IMemoryIndex {\n /** Primary store: `(scope, id)` composite key → indexed entry. */\n private readonly _byKey: Map<string, IIndexedMemoryRecord>;\n /** kind → set of composite keys. */\n private readonly _byKind: Map<Kind, Set<string>>;\n /** tag → set of composite keys. */\n private readonly _byTag: Map<Tag, Set<string>>;\n /**\n * canonical target key (`edgeTargetKey`) → (source composite key → source\n * {@link IEdgeTarget}). The OUTER map is keyed on the scope-qualified target's\n * canonical `(scope, id)` string — NOT the target's bare id — so an edge to\n * `turn-3` in one conversation is tracked separately from `turn-3` in another.\n * The INNER map is keyed by the source's `(scope, id)` composite so two distinct\n * source records that share an id across scopes are tracked independently and\n * removing one never drops the other's edge.\n */\n private readonly _backlinks: Map<string, Map<string, IEdgeTarget>>;\n\n private constructor() {\n this._byKey = new Map<string, IIndexedMemoryRecord>();\n this._byKind = new Map<Kind, Set<string>>();\n this._byTag = new Map<Tag, Set<string>>();\n this._backlinks = new Map<string, Map<string, IEdgeTarget>>();\n }\n\n /** Family-convention factory. */\n public static create(): Result<MemoryIndex> {\n return succeed(new MemoryIndex());\n }\n\n /**\n * The composite primary key for an entry: scope + id, NUL-separated. NUL\n * is excluded from both components (scope segments pass\n * `assertPortableFilenameStem`; `MemoryId` is portable-filename-safe), so it\n * is a collision-proof separator across every scope/id pair the codecs produce.\n */\n private static _keyOf(scope: MemoryScopeKey, id: MemoryId): string {\n return edgeTargetKey({ scope, id });\n }\n\n /** {@inheritDoc IMemoryIndex.rebuild} */\n public rebuild(entries: ReadonlyArray<IIndexedMemoryRecord>): Result<number> {\n this._byKey.clear();\n this._byKind.clear();\n this._byTag.clear();\n this._backlinks.clear();\n for (const entry of entries) {\n this._add(entry);\n }\n return succeed(this._byKey.size);\n }\n\n /** {@inheritDoc IMemoryIndex.patch} */\n public patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord> {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);\n // Always drop any prior associations for this key first, so a 'put' that\n // changes kind/tags/links cannot strand a stale reference.\n this._remove(key);\n if (op === 'put') {\n this._add(entry);\n }\n return succeed(entry);\n }\n\n /** {@inheritDoc IMemoryIndex.entries} */\n public entries(): ReadonlyArray<IIndexedMemoryRecord> {\n return Array.from(this._byKey.values());\n }\n\n /** {@inheritDoc IMemoryIndex.byKind} */\n public byKind(kind: Kind): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byKind.get(kind) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byTag} */\n public byTag(tag: Tag): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byTag.get(tag) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byRecency} */\n public byRecency(): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byKey.keys());\n }\n\n /** {@inheritDoc IMemoryIndex.byRank} */\n public byRank(): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._rankOrdered(this._byKey.keys());\n }\n\n /** {@inheritDoc IMemoryIndex.backlinks} */\n public backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget> {\n const sources: Map<string, IEdgeTarget> | undefined = this._backlinks.get(edgeTargetKey(target));\n return sources === undefined ? [] : Array.from(sources.values());\n }\n\n /**\n * Resolve a set of composite keys to their records, ordered\n * most-recently-updated first (with a `seq` tiebreak so equal-`updated`\n * records sort deterministically).\n */\n private _recencyOrdered(keys: Iterable<string>): ReadonlyArray<IMemoryRecord<unknown>> {\n const records: IMemoryRecord<unknown>[] = [];\n for (const key of keys) {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry !== undefined) {\n records.push(entry.record);\n }\n }\n return records.sort((a, b) => {\n const byUpdated: number = b.envelope.updated - a.envelope.updated;\n return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;\n });\n }\n\n /**\n * Resolve a set of composite keys to their records, ordered by\n * {@link IMemoryEnvelope.rank} descending with recency (`updated`, then `seq`)\n * as the tiebreak. Records with an absent `rank` sort LAST, then by recency\n * among themselves. Computed on call (mirrors {@link MemoryIndex._recencyOrdered}) —\n * no incremental rank-ordered view is maintained, matching the recency view's\n * approach; the sort is over the in-memory index, never a filesystem walk.\n */\n private _rankOrdered(keys: Iterable<string>): ReadonlyArray<IMemoryRecord<unknown>> {\n const records: IMemoryRecord<unknown>[] = [];\n for (const key of keys) {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry !== undefined) {\n records.push(entry.record);\n }\n }\n return records.sort(MemoryIndex._compareByRank);\n }\n\n /**\n * Rank-descending comparator with an absent-`rank`-last rule and a recency\n * (`updated`, then `seq`) tiebreak. Duplicated from the retrieve packlet's\n * `rankCompare` deliberately: the index must not depend on `retrieve` (that\n * package depends on the index), mirroring how `_recencyOrdered` inlines the\n * recency ordering rather than importing `recencyCompare`.\n */\n private static _compareByRank(a: IMemoryRecord<unknown>, b: IMemoryRecord<unknown>): number {\n const ra: number | undefined = a.envelope.rank;\n const rb: number | undefined = b.envelope.rank;\n // Absent rank sorts last; two absent ranks fall through to the recency tiebreak.\n if (ra === undefined && rb !== undefined) {\n return 1;\n }\n if (rb === undefined && ra !== undefined) {\n return -1;\n }\n if (ra !== undefined && rb !== undefined && ra !== rb) {\n return rb - ra;\n }\n const byUpdated: number = b.envelope.updated - a.envelope.updated;\n return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;\n }\n\n /** Insert an entry and register all its derived associations. */\n private _add(entry: IIndexedMemoryRecord): void {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);\n const envelope: IMemoryRecord<unknown>['envelope'] = entry.record.envelope;\n this._byKey.set(key, entry);\n this._addToSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._addToSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._addBacklink(edge.target, key, { scope: entry.scope, id: envelope.id });\n }\n }\n\n /** Remove the entry at `key` (if present) and all its derived associations. */\n private _remove(key: string): void {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry === undefined) {\n return;\n }\n const envelope: IMemoryRecord<unknown>['envelope'] = entry.record.envelope;\n this._byKey.delete(key);\n this._removeFromSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._removeFromSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._removeBacklink(edge.target, key);\n }\n }\n\n /** Register `source` (keyed by its composite `sourceKey`) as linking at `target`. */\n private _addBacklink(target: IEdgeTarget, sourceKey: string, source: IEdgeTarget): void {\n const targetKey: string = edgeTargetKey(target);\n const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);\n if (existing === undefined) {\n this._backlinks.set(targetKey, new Map<string, IEdgeTarget>([[sourceKey, source]]));\n } else {\n existing.set(sourceKey, source);\n }\n }\n\n /** Drop the backlink from `sourceKey` to `target`, removing the target map when empty. */\n private _removeBacklink(target: IEdgeTarget, sourceKey: string): void {\n const targetKey: string = edgeTargetKey(target);\n const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);\n if (existing === undefined) {\n return;\n }\n existing.delete(sourceKey);\n if (existing.size === 0) {\n this._backlinks.delete(targetKey);\n }\n }\n\n /** Add `member` to the set at `mapKey`, creating the set on first use. */\n private _addToSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n map.set(mapKey, new Set<M>([member]));\n } else {\n existing.add(member);\n }\n }\n\n /** Remove `member` from the set at `mapKey`, dropping the set when empty. */\n private _removeFromSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n return;\n }\n existing.delete(member);\n if (existing.size === 0) {\n map.delete(mapKey);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"memoryIndex.js","sourceRoot":"","sources":["../../../src/packlets/index/memoryIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, succeed } from '@fgv/ts-utils';\nimport {\n IEdgeTarget,\n IMemoryEnvelope,\n IMemoryRecord,\n Kind,\n MemoryId,\n MemoryScopeKey,\n Tag,\n edgeTargetKey\n} from '../types';\n\n/**\n * The mutation a {@link IMemoryIndex.patch | patch} applies: a record was\n * written (`'put'`) or removed (`'delete'`).\n * @public\n */\nexport type MemoryIndexPatchOp = 'put' | 'delete';\n\n/** Shared empty key set for `byKind` / `byTag` misses (avoids per-call allocation). */\nconst EMPTY_KEY_SET: ReadonlySet<string> = new Set<string>();\n\n/**\n * A memory record paired with the {@link MemoryScopeKey | scope} it lives\n * under. The scope is not carried on the {@link IMemoryEnvelope} (it is derived\n * from the entity id by the codec), so the store threads it alongside the\n * record when patching or rebuilding the index. The `(scope, id)` pair is the\n * index's primary key, keeping records distinct across scopes that reuse a\n * filename stem (e.g. `turn-0` under different conversations in Phase C).\n * @public\n */\nexport interface IIndexedMemoryRecord {\n /** The scope the record is stored under. */\n readonly scope: MemoryScopeKey;\n /** The memory record itself. */\n readonly record: IMemoryRecord<unknown>;\n}\n\n/**\n * What the index HOLDS and what every index read returns: a record's scope and\n * its {@link IMemoryEnvelope}, and **no body**.\n *\n * @remarks\n * The index is a derived *selection* structure, and selection has never needed a\n * body — every filter the store and the retrievers apply reads envelope fields\n * (`scope` / `kind` / `tags` / `contentHash` / `provenance` / `links` /\n * `temporal` / `updated` / `seq` / `rank`). Returning whole records made every\n * conforming index hold every body by construction, which was the store's\n * resident-memory ceiling; returning envelopes removes it from the contract\n * rather than from one implementation.\n *\n * A caller that needs the body **materializes it explicitly** — through\n * {@link IMemoryStore.getById}, or an `IMemoryRecordResolver` where one is\n * wired. That is deliberately visible: a lazy `body` getter would have kept\n * every call site compiling while turning a memory read into a file read behind\n * an unchanged type, which is a silent performance cliff rather than a migration.\n * @public\n */\nexport interface IIndexedMemoryEntry {\n /** The scope the record is stored under. */\n readonly scope: MemoryScopeKey;\n /** The record's envelope. No body — see the remarks. */\n readonly envelope: IMemoryEnvelope;\n}\n\n/** Project the write-side whole-record form onto the held/read entry form. */\nfunction toEntry(entry: IIndexedMemoryRecord): IIndexedMemoryEntry {\n return { scope: entry.scope, envelope: entry.record.envelope };\n}\n\n/**\n * The derived, in-memory secondary indexes the store maintains over its\n * records. Never the source of truth — the FileTree is. The index is fully\n * rebuildable from a walk of the store ({@link IMemoryIndex.rebuild}) and is\n * patched incrementally on every write ({@link IMemoryIndex.patch}).\n *\n * @remarks\n * Every read returns the projected {@link IIndexedMemoryEntry} — scope and\n * envelope, no body. See that type for why.\n *\n * **The conformance rule: an index is a derived, COMPLETE, FAITHFUL projection\n * of the vault.** An implementation may change *where* entries are stored and\n * *how* they are looked up; it may not change *which* entries exist or *what any\n * envelope says*. Concretely, {@link IMemoryIndex.entries} must return exactly\n * one entry per record the store has written and not deleted, and each entry's\n * envelope must be the one the store patched in.\n *\n * An index that filters, truncates, deduplicates, or synthesizes entries is not\n * a conforming implementation, and the reason is not tidiness: **the store's\n * write path derives from these reads** — content-hash dedup, write-policy\n * admission cohorts, and temporal version histories all read the index. An index\n * that hides an entry does not merely hide it from queries; it changes what the\n * next write does. That is why the previous guidance said only a faithful\n * delegating decorator was safe to inject. This invariant is what that guidance\n * was reaching for, stated so that a genuinely different implementation (a\n * SQLite-backed index, a lazily-paged one) is permitted while the reshaping that\n * was the actual hazard stays out.\n *\n * **Ordering is NOT part of the contract.** {@link IMemoryIndex.entries} may\n * return entries in any order and callers that need one sort explicitly. Note\n * this is a *behavioural* freedom the compiler cannot police: the bundled\n * {@link MemoryIndex} iterates a `Map` and so returns insertion order, which is\n * stable and observable, so code that came to rely on it keeps compiling and\n * changes results. The ordered accessors ({@link IMemoryIndex.byRecency},\n * {@link IMemoryIndex.byRank}, and the recency-ordered `byKind` / `byTag`) are\n * the supported way to ask for an order.\n * @public\n */\nexport interface IMemoryIndex {\n /**\n * Replace the entire index from a full set of entries (a store walk).\n *\n * @remarks\n * Takes the **projected** {@link IIndexedMemoryEntry} form, not whole records,\n * and the distinction is load-bearing rather than cosmetic: **`patch` writes,\n * `rebuild` reads.** A rebuild is a whole-vault read that happens to terminate\n * in the index, so requiring whole records here would force every caller — the\n * store's own open path included — to materialize N bodies purely to feed a\n * structure that projects the envelope back out and discards them. `patch`\n * keeps whole records because it carries exactly one, which its caller already\n * holds.\n *\n * @returns The number of entries indexed.\n */\n rebuild(entries: ReadonlyArray<IIndexedMemoryEntry>): Result<number>;\n\n /**\n * Apply a single incremental change. `'put'` inserts or replaces the entry\n * at its `(scope, id)` key (removing any prior associations first); `'delete'`\n * removes it.\n *\n * @remarks\n * Takes the whole record — see {@link IMemoryIndex.rebuild} for why this one\n * does and that one does not. It costs nothing (the caller is mid-write and\n * holds the record already) and it is the single point at which an index\n * maintaining a body-derived view could observe content without a re-read.\n * What is *held* is still only the projection.\n *\n * @returns The entry that was applied.\n */\n patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord>;\n\n /** Every indexed entry (scope + envelope). Primary read surface for the store. */\n entries(): ReadonlyArray<IIndexedMemoryEntry>;\n\n /**\n * The entry at a scope-qualified address, or `undefined` if none.\n *\n * @remarks\n * On the contract because its absence made every caller that wanted **one**\n * entry rebuild a map of **all** of them: both `SemanticRetriever` (resolving\n * at most `topK` hits) and `LinkTraversalRetriever` (resolving a BFS frontier)\n * built a full-index `Map` per query for want of this. The index already keys\n * on `(scope, id)` internally, so this exposes a lookup it was doing anyway.\n */\n get(target: IEdgeTarget): IIndexedMemoryEntry | undefined;\n\n /** Entries of the given kind, in recency order (most-recently-updated first). */\n byKind(kind: Kind): ReadonlyArray<IIndexedMemoryEntry>;\n\n /** Entries carrying the given tag, in recency order. */\n byTag(tag: Tag): ReadonlyArray<IIndexedMemoryEntry>;\n\n /** All entries in recency order (most-recently-updated first). */\n byRecency(): ReadonlyArray<IIndexedMemoryEntry>;\n\n /**\n * All entries ordered by store-computed {@link IMemoryEnvelope.rank} descending,\n * with recency (most-recently-updated, then `seq`) as a tiebreak. Entries with\n * an absent `rank` sort LAST (after every ranked entry), then by recency among\n * themselves. Serves a bounded top-M ({@link IMemoryEnvelope.rank}-ordered) page\n * from the in-memory index with no full-vault (filesystem) scan — and since the\n * page is envelope-only, a caller taking the top M materializes M bodies rather\n * than the vault.\n */\n byRank(): ReadonlyArray<IIndexedMemoryEntry>;\n\n /**\n * The scope-qualified sources of records whose `links` point AT `target`\n * (inbound edges), keyed on the target's `(scope, id)` address. The seed map\n * for B2 link-traversal; results are {@link IEdgeTarget}s so a caller can feed\n * them straight back in as further traversal seeds.\n */\n backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget>;\n}\n\n/**\n * Default in-memory {@link IMemoryIndex}. Maintains the derived `byKind` /\n * `byTag` / `byRecency` / `backlinks` views incrementally; a `'put'` for an\n * already-indexed key first removes the prior entry's associations so a changed\n * kind / tag / link set never leaves a stale reference behind.\n * @public\n */\nexport class MemoryIndex implements IMemoryIndex {\n /**\n * Primary store: `(scope, id)` composite key → indexed entry. Holds the\n * PROJECTED form, so the index never retains a body.\n */\n private readonly _byKey: Map<string, IIndexedMemoryEntry>;\n /** kind → set of composite keys. */\n private readonly _byKind: Map<Kind, Set<string>>;\n /** tag → set of composite keys. */\n private readonly _byTag: Map<Tag, Set<string>>;\n /**\n * canonical target key (`edgeTargetKey`) → (source composite key → source\n * {@link IEdgeTarget}). The OUTER map is keyed on the scope-qualified target's\n * canonical `(scope, id)` string — NOT the target's bare id — so an edge to\n * `turn-3` in one conversation is tracked separately from `turn-3` in another.\n * The INNER map is keyed by the source's `(scope, id)` composite so two distinct\n * source records that share an id across scopes are tracked independently and\n * removing one never drops the other's edge.\n */\n private readonly _backlinks: Map<string, Map<string, IEdgeTarget>>;\n\n private constructor() {\n this._byKey = new Map<string, IIndexedMemoryEntry>();\n this._byKind = new Map<Kind, Set<string>>();\n this._byTag = new Map<Tag, Set<string>>();\n this._backlinks = new Map<string, Map<string, IEdgeTarget>>();\n }\n\n /** Family-convention factory. */\n public static create(): Result<MemoryIndex> {\n return succeed(new MemoryIndex());\n }\n\n /**\n * The composite primary key for an entry: scope + id, NUL-separated. NUL\n * is excluded from both components (scope segments pass\n * `assertPortableFilenameStem`; `MemoryId` is portable-filename-safe), so it\n * is a collision-proof separator across every scope/id pair the codecs produce.\n */\n private static _keyOf(scope: MemoryScopeKey, id: MemoryId): string {\n return edgeTargetKey({ scope, id });\n }\n\n /** {@inheritDoc IMemoryIndex.rebuild} */\n public rebuild(entries: ReadonlyArray<IIndexedMemoryEntry>): Result<number> {\n this._byKey.clear();\n this._byKind.clear();\n this._byTag.clear();\n this._backlinks.clear();\n for (const entry of entries) {\n this._add(entry);\n }\n return succeed(this._byKey.size);\n }\n\n /** {@inheritDoc IMemoryIndex.patch} */\n public patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord> {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);\n // Always drop any prior associations for this key first, so a 'put' that\n // changes kind/tags/links cannot strand a stale reference.\n this._remove(key);\n if (op === 'put') {\n // Projected on the way in: the caller's record is used for its envelope and\n // the body is not retained.\n this._add(toEntry(entry));\n }\n return succeed(entry);\n }\n\n /** {@inheritDoc IMemoryIndex.entries} */\n public entries(): ReadonlyArray<IIndexedMemoryEntry> {\n return Array.from(this._byKey.values());\n }\n\n /** {@inheritDoc IMemoryIndex.get} */\n public get(target: IEdgeTarget): IIndexedMemoryEntry | undefined {\n return this._byKey.get(edgeTargetKey(target));\n }\n\n /** {@inheritDoc IMemoryIndex.byKind} */\n public byKind(kind: Kind): ReadonlyArray<IIndexedMemoryEntry> {\n return this._recencyOrdered(this._byKind.get(kind) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byTag} */\n public byTag(tag: Tag): ReadonlyArray<IIndexedMemoryEntry> {\n return this._recencyOrdered(this._byTag.get(tag) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byRecency} */\n public byRecency(): ReadonlyArray<IIndexedMemoryEntry> {\n return this._recencyOrdered(this._byKey.keys());\n }\n\n /** {@inheritDoc IMemoryIndex.byRank} */\n public byRank(): ReadonlyArray<IIndexedMemoryEntry> {\n return this._rankOrdered(this._byKey.keys());\n }\n\n /** {@inheritDoc IMemoryIndex.backlinks} */\n public backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget> {\n const sources: Map<string, IEdgeTarget> | undefined = this._backlinks.get(edgeTargetKey(target));\n return sources === undefined ? [] : Array.from(sources.values());\n }\n\n /**\n * Resolve a set of composite keys to their records, ordered\n * most-recently-updated first (with a `seq` tiebreak so equal-`updated`\n * records sort deterministically).\n */\n private _recencyOrdered(keys: Iterable<string>): ReadonlyArray<IIndexedMemoryEntry> {\n return MemoryIndex._resolve(this._byKey, keys).sort((a, b) => {\n const byUpdated: number = b.envelope.updated - a.envelope.updated;\n return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;\n });\n }\n\n /**\n * Resolve a set of composite keys to their records, ordered by\n * {@link IMemoryEnvelope.rank} descending with recency (`updated`, then `seq`)\n * as the tiebreak. Records with an absent `rank` sort LAST, then by recency\n * among themselves. Computed on call (mirrors {@link MemoryIndex._recencyOrdered}) —\n * no incremental rank-ordered view is maintained, matching the recency view's\n * approach; the sort is over the in-memory index, never a filesystem walk.\n */\n private _rankOrdered(keys: Iterable<string>): ReadonlyArray<IIndexedMemoryEntry> {\n return MemoryIndex._resolve(this._byKey, keys).sort(MemoryIndex._compareByRank);\n }\n\n /** Resolve composite keys to their entries, skipping any that are absent. */\n private static _resolve(\n byKey: ReadonlyMap<string, IIndexedMemoryEntry>,\n keys: Iterable<string>\n ): IIndexedMemoryEntry[] {\n const entries: IIndexedMemoryEntry[] = [];\n for (const key of keys) {\n const entry: IIndexedMemoryEntry | undefined = byKey.get(key);\n if (entry !== undefined) {\n entries.push(entry);\n }\n }\n return entries;\n }\n\n /**\n * Rank-descending comparator with an absent-`rank`-last rule and a recency\n * (`updated`, then `seq`) tiebreak. Duplicated from the retrieve packlet's\n * `rankCompare` deliberately: the index must not depend on `retrieve` (that\n * package depends on the index), mirroring how `_recencyOrdered` inlines the\n * recency ordering rather than importing `recencyCompare`.\n */\n private static _compareByRank(a: IIndexedMemoryEntry, b: IIndexedMemoryEntry): number {\n const ra: number | undefined = a.envelope.rank;\n const rb: number | undefined = b.envelope.rank;\n // Absent rank sorts last; two absent ranks fall through to the recency tiebreak.\n if (ra === undefined && rb !== undefined) {\n return 1;\n }\n if (rb === undefined && ra !== undefined) {\n return -1;\n }\n if (ra !== undefined && rb !== undefined && ra !== rb) {\n return rb - ra;\n }\n const byUpdated: number = b.envelope.updated - a.envelope.updated;\n return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;\n }\n\n /** Insert an entry and register all its derived associations. */\n private _add(entry: IIndexedMemoryEntry): void {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.envelope.id);\n const envelope: IMemoryEnvelope = entry.envelope;\n this._byKey.set(key, entry);\n this._addToSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._addToSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._addBacklink(edge.target, key, { scope: entry.scope, id: envelope.id });\n }\n }\n\n /** Remove the entry at `key` (if present) and all its derived associations. */\n private _remove(key: string): void {\n const entry: IIndexedMemoryEntry | undefined = this._byKey.get(key);\n if (entry === undefined) {\n return;\n }\n const envelope: IMemoryEnvelope = entry.envelope;\n this._byKey.delete(key);\n this._removeFromSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._removeFromSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._removeBacklink(edge.target, key);\n }\n }\n\n /** Register `source` (keyed by its composite `sourceKey`) as linking at `target`. */\n private _addBacklink(target: IEdgeTarget, sourceKey: string, source: IEdgeTarget): void {\n const targetKey: string = edgeTargetKey(target);\n const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);\n if (existing === undefined) {\n this._backlinks.set(targetKey, new Map<string, IEdgeTarget>([[sourceKey, source]]));\n } else {\n existing.set(sourceKey, source);\n }\n }\n\n /** Drop the backlink from `sourceKey` to `target`, removing the target map when empty. */\n private _removeBacklink(target: IEdgeTarget, sourceKey: string): void {\n const targetKey: string = edgeTargetKey(target);\n const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);\n if (existing === undefined) {\n return;\n }\n existing.delete(sourceKey);\n if (existing.size === 0) {\n this._backlinks.delete(targetKey);\n }\n }\n\n /** Add `member` to the set at `mapKey`, creating the set on first use. */\n private _addToSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n map.set(mapKey, new Set<M>([member]));\n } else {\n existing.add(member);\n }\n }\n\n /** Remove `member` from the set at `mapKey`, dropping the set when empty. */\n private _removeFromSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n return;\n }\n existing.delete(member);\n if (existing.size === 0) {\n map.delete(mapKey);\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/orchestrator.ts"],"names":[],"mappings":"AAKA,OAAO,EAAQ,OAAO,EAAE,MAAM,EAA6B,MAAM,eAAe,CAAC;AACjF,OAAO,EAML,cAAc,EAKd,IAAI,EACJ,QAAQ,EAMT,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.MemoryIngestOrchestrator = exports.DEFAULT_SIMILARITY_TOP_K = exports.DEFAULT_SIMILARITY_THRESHOLD = exports.CONTRADICTS_LINK_TYPE = exports.HOST_INGEST_PROVENANCE_SOURCE = void 0;
|
|
8
8
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
9
|
const types_1 = require("../types");
|
|
10
|
+
const store_1 = require("../store");
|
|
10
11
|
const cycleGuard_1 = require("./cycleGuard");
|
|
11
12
|
/**
|
|
12
13
|
* The provenance source stamped on every record the ingest pipeline writes.
|
|
@@ -92,7 +93,18 @@ class MemoryIngestOrchestrator {
|
|
|
92
93
|
async _processCandidates(item, candidates) {
|
|
93
94
|
// Snapshot the store once; stage-4 resolution and the cycle guard reason over
|
|
94
95
|
// the pre-ingest state.
|
|
95
|
-
|
|
96
|
+
//
|
|
97
|
+
// DECLARED, not optimized. This reads every body in the vault on EVERY
|
|
98
|
+
// ingested item, and `scanEveryRecord()` is how the store now makes a caller
|
|
99
|
+
// say so out loud. Most of what the snapshot is used for downstream — edge
|
|
100
|
+
// target existence, the cycle guard's link walk, scoped-address keying — is
|
|
101
|
+
// envelope-only work that `listEntries()` would serve without reading a
|
|
102
|
+
// single file; only stage-4 layer-1's exact `{ kind, body }` match needs
|
|
103
|
+
// bodies, and it needs them for candidates rather than for the whole vault.
|
|
104
|
+
// Converting this is its own stream (`agent-memory-ingest-snapshot`), because
|
|
105
|
+
// it is on the write path with its own risk surface. Until then the cost is
|
|
106
|
+
// at least visible and greppable rather than implied.
|
|
107
|
+
const snapshotResult = await this._store.list((0, store_1.scanEveryRecord)());
|
|
96
108
|
if (snapshotResult.isFailure()) {
|
|
97
109
|
return (0, ts_utils_1.fail)(`ingest '${item.id}': failed to snapshot store: ${snapshotResult.message}`);
|
|
98
110
|
}
|