fork
Configure Feed
Select the types of activity you want to include in your feed.
fork of hey-api/openapi-ts because I need some additional things
fork
Configure Feed
Select the types of activity you want to include in your feed.
1import type { ISymbolMeta } from '../extensions';
2import { Symbol } from './symbol';
3import type { ISymbolIdentifier, ISymbolIn, ISymbolRegistry } from './types';
4
5type IndexEntry = [string, unknown];
6type IndexKeySpace = ReadonlyArray<IndexEntry>;
7type QueryCacheKey = string;
8type SymbolId = number;
9
10export class SymbolRegistry implements ISymbolRegistry {
11 private _id: SymbolId = 0;
12 private _indices: Map<IndexEntry[0], Map<IndexEntry[1], Set<SymbolId>>> = new Map();
13 private _queryCache: Map<QueryCacheKey, ReadonlyArray<SymbolId>> = new Map();
14 private _queryCacheDependencies: Map<QueryCacheKey, Set<QueryCacheKey>> = new Map();
15 private _registered: Set<SymbolId> = new Set();
16 private _stubs: Set<SymbolId> = new Set();
17 private _stubCache: Map<QueryCacheKey, SymbolId> = new Map();
18 private _values: Map<SymbolId, Symbol> = new Map();
19
20 get(identifier: ISymbolIdentifier): Symbol | undefined {
21 return typeof identifier === 'number'
22 ? this._values.get(identifier)
23 : this.query(identifier)[0];
24 }
25
26 isRegistered(identifier: ISymbolIdentifier): boolean {
27 const symbol = this.get(identifier);
28 return symbol ? this._registered.has(symbol.id) : false;
29 }
30
31 get nextId(): SymbolId {
32 return this._id++;
33 }
34
35 query(filter: ISymbolMeta): ReadonlyArray<Symbol> {
36 const cacheKey = this.buildCacheKey(filter);
37 const cachedIds = this._queryCache.get(cacheKey);
38 if (cachedIds) {
39 return cachedIds.map((symbolId) => this._values.get(symbolId)!);
40 }
41 const sets: Array<Set<SymbolId>> = [];
42 const indexKeySpace = this.buildIndexKeySpace(filter);
43 const cacheDependencies = new Set<QueryCacheKey>();
44 let missed = false;
45 for (const indexEntry of indexKeySpace) {
46 cacheDependencies.add(this.serializeIndexEntry(indexEntry));
47 const values = this._indices.get(indexEntry[0]);
48 if (!values) {
49 missed = true;
50 break;
51 }
52 const set = values.get(indexEntry[1]);
53 if (!set) {
54 missed = true;
55 break;
56 }
57 sets.push(set);
58 }
59 if (missed || !sets.length) {
60 this._queryCacheDependencies.set(cacheKey, cacheDependencies);
61 this._queryCache.set(cacheKey, []);
62 return [];
63 }
64 let result = new Set(sets[0]);
65 for (const set of sets.slice(1)) {
66 result = new Set([...result].filter((symbolId) => set.has(symbolId)));
67 }
68 const resultIds = [...result];
69 this._queryCacheDependencies.set(cacheKey, cacheDependencies);
70 this._queryCache.set(cacheKey, resultIds);
71 return resultIds.map((symbolId) => this._values.get(symbolId)!);
72 }
73
74 reference(meta: ISymbolMeta): Symbol {
75 const [registered] = this.query(meta);
76 if (registered) return registered;
77
78 const cacheKey = this.buildCacheKey(meta);
79 const cachedId = this._stubCache.get(cacheKey);
80 if (cachedId !== undefined) return this._values.get(cachedId)!;
81
82 const stub = new Symbol({ meta, name: '' }, this.nextId);
83
84 this._values.set(stub.id, stub);
85 this._stubs.add(stub.id);
86 this._stubCache.set(cacheKey, stub.id);
87 return stub;
88 }
89
90 register(symbol: ISymbolIn): Symbol {
91 const result = new Symbol(symbol, this.nextId);
92
93 this._values.set(result.id, result);
94 this._registered.add(result.id);
95
96 if (result.meta) {
97 const indexKeySpace = this.buildIndexKeySpace(result.meta);
98 this.indexSymbol(result.id, indexKeySpace);
99 this.invalidateCache(indexKeySpace);
100 this.replaceStubs(result, indexKeySpace);
101 }
102
103 return result;
104 }
105
106 *registered(): IterableIterator<Symbol> {
107 for (const id of this._registered.values()) {
108 yield this._values.get(id)!;
109 }
110 }
111
112 private buildCacheKey(filter: ISymbolMeta): QueryCacheKey {
113 const indexKeySpace = this.buildIndexKeySpace(filter);
114 return indexKeySpace
115 .map((indexEntry) => this.serializeIndexEntry(indexEntry))
116 .sort() // ensure order-insensitivity
117 .join('|');
118 }
119
120 private buildIndexKeySpace(meta: ISymbolMeta, prefix = ''): IndexKeySpace {
121 const entries: Array<IndexEntry> = [];
122 for (const [key, value] of Object.entries(meta)) {
123 const path = prefix ? `${prefix}.${key}` : key;
124 if (value && typeof value === 'object' && !Array.isArray(value)) {
125 entries.push(...this.buildIndexKeySpace(value as ISymbolMeta, path));
126 } else {
127 entries.push([path, value]);
128 }
129 }
130 return entries;
131 }
132
133 private indexSymbol(symbolId: SymbolId, indexKeySpace: IndexKeySpace): void {
134 for (const [key, value] of indexKeySpace) {
135 if (!this._indices.has(key)) this._indices.set(key, new Map());
136 const values = this._indices.get(key)!;
137 const set = values.get(value) ?? new Set();
138 set.add(symbolId);
139 values.set(value, set);
140 }
141 }
142
143 private invalidateCache(indexKeySpace: IndexKeySpace): void {
144 const changed = indexKeySpace.map((indexEntry) => this.serializeIndexEntry(indexEntry));
145 for (const [cacheKey, cacheDependencies] of this._queryCacheDependencies.entries()) {
146 for (const key of changed) {
147 if (cacheDependencies.has(key)) {
148 this._queryCacheDependencies.delete(cacheKey);
149 this._queryCache.delete(cacheKey);
150 break;
151 }
152 }
153 }
154 }
155
156 private isSubset(sub: IndexKeySpace, sup: IndexKeySpace): boolean {
157 const supMap = new Map(sup);
158 for (const [key, value] of sub) {
159 if (!supMap.has(key) || supMap.get(key) !== value) {
160 return false;
161 }
162 }
163 return true;
164 }
165
166 private replaceStubs(symbol: Symbol, indexKeySpace: IndexKeySpace): void {
167 for (const stubId of this._stubs.values()) {
168 const stub = this._values.get(stubId);
169 if (stub?.meta && this.isSubset(this.buildIndexKeySpace(stub.meta), indexKeySpace)) {
170 const cacheKey = this.buildCacheKey(stub.meta);
171 this._stubCache.delete(cacheKey);
172 this._stubs.delete(stubId);
173 stub.setCanonical(symbol);
174 }
175 }
176 }
177
178 private serializeIndexEntry(indexEntry: IndexEntry): string {
179 return `${indexEntry[0]}:${JSON.stringify(indexEntry[1])}`;
180 }
181}