@@ -13,6 +13,8 @@ export class SymbolRegistry implements ISymbolRegistry {
1313 private _queryCache : Map < QueryCacheKey , ReadonlyArray < Symbol > > = new Map ( ) ;
1414 /** Reverse index: serialized index entry → set of cache keys that depend on it. */
1515 private _dependencyToCache : Map < QueryCacheKey , Set < QueryCacheKey > > = new Map ( ) ;
16+ /** Forward index: cache key → serialized entries it depends on, for clean eviction. */
17+ private _cacheDependencies : Map < QueryCacheKey , ReadonlyArray < string > > = new Map ( ) ;
1618 private _registered : Set < SymbolId > = new Set ( ) ;
1719 private _stubs : Set < SymbolId > = new Set ( ) ;
1820 private _stubCache : Map < QueryCacheKey , SymbolId > = new Map ( ) ;
@@ -36,6 +38,52 @@ export class SymbolRegistry implements ISymbolRegistry {
3638 query ( filter : ISymbolMeta ) : ReadonlyArray < Symbol > {
3739 const indexKeySpace = this . buildIndexKeySpace ( filter ) ;
3840 const cacheKey = this . cacheKeyFromKeySpace ( indexKeySpace ) ;
41+ return this . queryByKeySpace ( indexKeySpace , cacheKey ) ;
42+ }
43+
44+ reference ( meta : ISymbolMeta ) : Symbol {
45+ const indexKeySpace = this . buildIndexKeySpace ( meta ) ;
46+ const cacheKey = this . cacheKeyFromKeySpace ( indexKeySpace ) ;
47+ const [ registered ] = this . queryByKeySpace ( indexKeySpace , cacheKey ) ;
48+ if ( registered ) return registered ;
49+
50+ const cachedId = this . _stubCache . get ( cacheKey ) ;
51+ if ( cachedId !== undefined ) return this . _values . get ( cachedId ) ! ;
52+
53+ const stub = new Symbol ( { meta, name : '' } , this . nextId ) ;
54+
55+ this . _values . set ( stub . id , stub ) ;
56+ this . _stubs . add ( stub . id ) ;
57+ this . _stubCache . set ( cacheKey , stub . id ) ;
58+ return stub ;
59+ }
60+
61+ register ( symbol : ISymbolIn ) : Symbol {
62+ const result = new Symbol ( symbol , this . nextId ) ;
63+
64+ this . _values . set ( result . id , result ) ;
65+ this . _registered . add ( result . id ) ;
66+
67+ if ( result . meta ) {
68+ const indexKeySpace = this . buildIndexKeySpace ( result . meta ) ;
69+ this . indexSymbol ( result . id , indexKeySpace ) ;
70+ this . invalidateCache ( indexKeySpace ) ;
71+ this . replaceStubs ( result , indexKeySpace ) ;
72+ }
73+
74+ return result ;
75+ }
76+
77+ * registered ( ) : IterableIterator < Symbol > {
78+ for ( const id of this . _registered . values ( ) ) {
79+ yield this . _values . get ( id ) ! ;
80+ }
81+ }
82+
83+ private queryByKeySpace (
84+ indexKeySpace : IndexKeySpace ,
85+ cacheKey : QueryCacheKey ,
86+ ) : ReadonlyArray < Symbol > {
3987 const cached = this . _queryCache . get ( cacheKey ) ;
4088 if ( cached ) {
4189 return cached ;
@@ -84,45 +132,6 @@ export class SymbolRegistry implements ISymbolRegistry {
84132 return symbols ;
85133 }
86134
87- reference ( meta : ISymbolMeta ) : Symbol {
88- const [ registered ] = this . query ( meta ) ;
89- if ( registered ) return registered ;
90-
91- const indexKeySpace = this . buildIndexKeySpace ( meta ) ;
92- const cacheKey = this . cacheKeyFromKeySpace ( indexKeySpace ) ;
93- const cachedId = this . _stubCache . get ( cacheKey ) ;
94- if ( cachedId !== undefined ) return this . _values . get ( cachedId ) ! ;
95-
96- const stub = new Symbol ( { meta, name : '' } , this . nextId ) ;
97-
98- this . _values . set ( stub . id , stub ) ;
99- this . _stubs . add ( stub . id ) ;
100- this . _stubCache . set ( cacheKey , stub . id ) ;
101- return stub ;
102- }
103-
104- register ( symbol : ISymbolIn ) : Symbol {
105- const result = new Symbol ( symbol , this . nextId ) ;
106-
107- this . _values . set ( result . id , result ) ;
108- this . _registered . add ( result . id ) ;
109-
110- if ( result . meta ) {
111- const indexKeySpace = this . buildIndexKeySpace ( result . meta ) ;
112- this . indexSymbol ( result . id , indexKeySpace ) ;
113- this . invalidateCache ( indexKeySpace ) ;
114- this . replaceStubs ( result , indexKeySpace ) ;
115- }
116-
117- return result ;
118- }
119-
120- * registered ( ) : IterableIterator < Symbol > {
121- for ( const id of this . _registered . values ( ) ) {
122- yield this . _values . get ( id ) ! ;
123- }
124- }
125-
126135 /**
127136 * Derives a stable, order-insensitive cache key from a pre-built key space.
128137 * Avoids rebuilding the key space when it's already available.
@@ -139,15 +148,18 @@ export class SymbolRegistry implements ISymbolRegistry {
139148 * are invalidated, the cache key is evicted in O(1) per entry.
140149 */
141150 private registerCacheDependencies ( cacheKey : QueryCacheKey , indexKeySpace : IndexKeySpace ) : void {
151+ const serializedEntries : Array < string > = [ ] ;
142152 for ( const indexEntry of indexKeySpace ) {
143153 const serialized = this . serializeIndexEntry ( indexEntry ) ;
154+ serializedEntries . push ( serialized ) ;
144155 let cacheKeys = this . _dependencyToCache . get ( serialized ) ;
145156 if ( ! cacheKeys ) {
146157 cacheKeys = new Set ( ) ;
147158 this . _dependencyToCache . set ( serialized , cacheKeys ) ;
148159 }
149160 cacheKeys . add ( cacheKey ) ;
150161 }
162+ this . _cacheDependencies . set ( cacheKey , serializedEntries ) ;
151163 }
152164
153165 private buildIndexKeySpace ( meta : ISymbolMeta , prefix = '' ) : IndexKeySpace {
@@ -180,6 +192,17 @@ export class SymbolRegistry implements ISymbolRegistry {
180192 if ( ! cacheKeys ) continue ;
181193 for ( const cacheKey of cacheKeys ) {
182194 this . _queryCache . delete ( cacheKey ) ;
195+ // Clean up stale reverse-index references so _dependencyToCache doesn't
196+ // accumulate orphaned entries for evicted cache keys.
197+ const deps = this . _cacheDependencies . get ( cacheKey ) ;
198+ if ( deps ) {
199+ for ( const dep of deps ) {
200+ if ( dep !== serialized ) {
201+ this . _dependencyToCache . get ( dep ) ?. delete ( cacheKey ) ;
202+ }
203+ }
204+ this . _cacheDependencies . delete ( cacheKey ) ;
205+ }
183206 }
184207 this . _dependencyToCache . delete ( serialized ) ;
185208 }
0 commit comments