···1111 expect(typeof id1).toBe('number');
1212 expect(id2).toBe(id1 + 1);
13131414- // Register a symbol with selector
1414+ // Register a symbol with meta
1515 const symbol1 = registry.register({
1616+ meta: {
1717+ foo: 'bar',
1818+ },
1619 placeholder: 'Foo',
1717- selector: ['foo'],
1820 });
1921 expect(symbol1).toEqual({
2022 exportFrom: [],
2123 id: expect.any(Number),
2424+ meta: {
2525+ foo: 'bar',
2626+ },
2227 placeholder: 'Foo',
2323- selector: ['foo'],
2428 });
25292626- // get by id and selector
3030+ // get by id and meta
2731 expect(registry.get(symbol1.id)).toEqual(symbol1);
2828- expect(registry.get(['foo'])).toEqual(symbol1);
3232+ expect(registry.get({ foo: 'bar' })).toEqual(symbol1);
29333034 // isRegistered should be true for explicitly registered symbols
3135 expect(registry.isRegistered(symbol1.id)).toBe(true);
3232- expect(registry.isRegistered(['foo'])).toBe(true);
3636+ expect(registry.isRegistered({ foo: 'bar' })).toBe(true);
33373434- // Registering again with same selector returns same symbol
3535- const symbol1b = registry.register({ selector: ['foo'] });
3636- expect(symbol1b).toEqual(symbol1);
3838+ // Registering again with same meta creates a new symbol
3939+ const symbol1b = registry.register({ meta: { foo: 'bar' } });
4040+ expect(symbol1b).not.toEqual(symbol1);
37413838- // Registering with id returns same symbol
4242+ // Registering with id overrides the symbol
3943 const symbol1c = registry.register({ id: symbol1.id });
4040- expect(symbol1c).toEqual(symbol1);
4444+ expect(symbol1c).not.toEqual(symbol1);
4545+ expect(symbol1c.id).toBe(symbol1.id);
41464242- // Reference by id returns same symbol
4343- const ref1 = registry.reference(symbol1.id);
4444- expect(ref1).toEqual(symbol1);
4747+ // Reference returns same symbol
4848+ const ref1 = registry.reference({ foo: 'bar' });
4949+ expect(ref1).toEqual(symbol1c);
45504646- // Reference by selector returns same symbol
4747- const ref1b = registry.reference(['foo']);
4848- expect(ref1b).toEqual(symbol1);
4949-5050- // Register a new symbol with a different selector
5151+ // Register a new symbol with different meta
5152 const symbol2 = registry.register({
5253 exportFrom: ['x'],
5454+ meta: { bar: 'baz' },
5355 placeholder: 'Bar',
5454- selector: ['bar'],
5556 });
5657 expect(symbol2).toEqual({
5758 exportFrom: ['x'],
5859 id: expect.any(Number),
6060+ meta: { bar: 'baz' },
5961 placeholder: 'Bar',
6060- selector: ['bar'],
6162 });
62636363- // Registering with same selector and extra exportFrom merges exportFrom
6464- const symbol2b = registry.register({
6565- exportFrom: ['y'],
6666- selector: ['bar'],
6767- });
6868- expect(symbol2b.exportFrom).toEqual(['x', 'y']);
6969-7064 // Registered symbols are yielded in order
7165 const registered = Array.from(registry.registered());
7266 expect(registered).toEqual([
7373- expect.objectContaining({ selector: ['foo'] }),
7474- expect.objectContaining({ selector: ['bar'] }),
6767+ expect.objectContaining({ id: 2 }),
6868+ expect.objectContaining({ meta: { foo: 'bar' } }),
6969+ expect.objectContaining({ meta: { bar: 'baz' } }),
7570 ]);
76717772 // setValue, getValue, hasValue
···8075 expect(registry.hasValue(symbol1.id)).toBe(true);
8176 expect(registry.getValue(symbol1.id)).toBe(42);
82778383- // referenced-only symbol should not be registered until register() with data
8484- const symRef = registry.reference(['qux']);
7878+ // referenced-only symbol should not be registered until register()
7979+ const symRef = registry.reference({ qux: true });
8580 expect(registry.isRegistered(symRef.id)).toBe(false);
8681 const symRegistered = registry.register({
8282+ meta: { qux: true },
8783 placeholder: 'Qux',
8888- selector: ['qux'],
8984 });
9085 expect(registry.isRegistered(symRegistered.id)).toBe(true);
9186 });
···134129 expect(symC).not.toEqual(refAD);
135130 expect(symC).not.toEqual(refB);
136131 expect(symC.meta).toEqual({ a: 0, b: 0, c: 0 });
137137- });
138138-139139- it('throws on invalid register or reference', () => {
140140- const registry = new SymbolRegistry();
141141- // Register with id that does not exist
142142- expect(() => registry.register({ id: 9999 })).toThrow(
143143- 'Symbol with ID 9999 not found. To register a new symbol, leave the ID undefined.',
144144- );
145145- // Register with selector that maps to missing id
146146- // Simulate by manually setting selectorToId
147147- registry['selectorToId'].set(JSON.stringify(['missing']), 42);
148148- expect(() => registry.register({ selector: ['missing'] })).toThrow(
149149- 'Symbol with ID 42 not found. The selector ["missing"] matched an ID, but there was no result. This is likely an issue with the application logic.',
150150- );
151132 });
152133153134 it('caches query results and invalidates on relevant updates', () => {
···11import type { IBiMap } from '../bimap/types';
22-import type { ISelector } from '../selectors/types';
22+33+/**
44+ * Selector array used to reference files.
55+ *
66+ * @example ["foo", "bar"]
77+ */
88+export type IFileSelector = ReadonlyArray<string>;
99+1010+export type IFileIdentifier = number | IFileSelector;
311412export interface IFileIn {
513 /**
···3240 */
3341 readonly path?: string;
3442 /**
3535- * Selector array used to select this file. It doesn't have to be
3636- * unique, but in practice it might be desirable.
4343+ * Selector array used to select this file.
3744 *
3838- * @example ["zod", "#/components/schemas/Foo"]
4545+ * @example ["foo", "bar"]
3946 */
4040- readonly selector?: ISelector;
4747+ readonly selector?: IFileSelector;
4148}
42494350export interface IFileOut extends IFileIn {
···70777178export interface IFileRegistry {
7279 /**
7373- * Get a file by its ID.
8080+ * Get a file.
7481 *
7575- * @param fileIdOrSelector File ID or selector to reference.
8282+ * @param identifier File identifier to reference.
7683 * @returns The file, or undefined if not found.
7784 */
7878- get(fileIdOrSelector: number | ISelector): IFileOut | undefined;
8585+ get(identifier: IFileIdentifier): IFileOut | undefined;
7986 /**
8087 * Returns the current file ID and increments it.
8188 *
···8592 /**
8693 * Returns whether a file is registered in the registry.
8794 *
8888- * @param fileIdOrSelector File ID or selector to check.
9595+ * @param identifier File identifier to check.
8996 * @returns True if the file is registered, false otherwise.
9097 */
9191- isRegistered(fileIdOrSelector: number | ISelector): boolean;
9898+ isRegistered(identifier: IFileIdentifier): boolean;
9299 /**
9393- * Returns a file by ID or selector, registering it if it doesn't exist.
100100+ * Returns a file by identifier, registering it if it doesn't exist.
94101 *
9595- * @param fileIdOrSelector File ID or selector to reference.
102102+ * @param identifier File identifier to reference.
96103 * @returns The referenced or newly registered file.
97104 */
9898- reference(fileIdOrSelector: number | ISelector): IFileOut;
105105+ reference(identifier: IFileIdentifier): IFileOut;
99106 /**
100107 * Get all unregistered files in the order they were referenced.
101108 *
+5-2
packages/codegen-core/src/index.ts
···55 IProjectRenderMeta as ProjectRenderMeta,
66 ISymbolMeta as SymbolMeta,
77} from './extensions/types';
88-export type { IFileOut as File, IFileIn as FileIn } from './files/types';
88+export type {
99+ IFileOut as File,
1010+ IFileIdentifier as FileIdentifier,
1111+ IFileIn as FileIn,
1212+} from './files/types';
913export type { IOutput as Output } from './output/types';
1014export { Project } from './project/project';
1115export type { IProject } from './project/types';
1216export type { IRenderer as Renderer } from './renderer/types';
1317export { renderIds } from './renderer/utils';
1414-export type { ISelector as Selector } from './selectors/types';
1518export type {
1619 ISymbolOut as Symbol,
1720 ISymbolIdentifier as SymbolIdentifier,
+2-3
packages/codegen-core/src/project/project.ts
···2233import type { IProjectRenderMeta } from '../extensions/types';
44import { FileRegistry } from '../files/registry';
55-import type { IFileOut } from '../files/types';
55+import type { IFileOut, IFileSelector } from '../files/types';
66import type { IOutput } from '../output/types';
77import type { IRenderer } from '../renderer/types';
88-import type { ISelector } from '../selectors/types';
98import { SymbolRegistry } from '../symbols/registry';
109import type { ISymbolOut } from '../symbols/types';
1110import type { IProject } from './types';
···132131 return Array.from(fileIds ?? []).map((fileId) => this.files.get(fileId)!);
133132 }
134133135135- private symbolToFileSelector(symbol: ISymbolOut): ISelector {
134134+ private symbolToFileSelector(symbol: ISymbolOut): IFileSelector {
136135 if (symbol.external) {
137136 return [externalSourceSymbol, symbol.external];
138137 }
-8
packages/codegen-core/src/selectors/types.d.ts
···11-/**
22- * Selector array used to reference resources. We don't enforce
33- * uniqueness, but in practice it's desirable.
44- *
55- * @deprecated
66- * @example ["zod", "#/components/schemas/Foo"]
77- */
88-export type ISelector = ReadonlyArray<string>;
+12-110
packages/codegen-core/src/symbols/registry.ts
···2121 private queryCacheDependencies: Map<QueryCacheKey, Set<QueryCacheKey>> =
2222 new Map();
2323 private registerOrder: Set<SymbolId> = new Set();
2424- // TODO: remove after removing selectors
2525- private selectorToId: Map<string, SymbolId> = new Map();
2624 private stubCache: Map<QueryCacheKey, SymbolId> = new Map();
2725 private stubs: Set<SymbolId> = new Set();
2826 private values: Map<SymbolId, ISymbolOut> = new Map();
29273028 get(identifier: ISymbolIdentifier): ISymbolOut | undefined {
3131- const symbol = this.identifierToSymbol(identifier);
3232-3333- if (symbol.id !== undefined) {
3434- return this.values.get(symbol.id);
3535- }
3636-3737- // TODO: remove after removing selectors
3838- const selector =
3939- symbol.selector !== undefined
4040- ? JSON.stringify(symbol.selector)
4141- : undefined;
4242-4343- // TODO: remove after removing selectors
4444- if (selector) {
4545- const id = this.selectorToId.get(selector);
4646- if (id !== undefined) {
4747- return this.values.get(id);
4848- }
4949- }
5050-5151- if (symbol.meta) {
5252- return this.query(symbol.meta)[0];
5353- }
5454-5555- return;
2929+ return typeof identifier === 'number'
3030+ ? this.values.get(identifier)
3131+ : this.query(identifier)[0];
5632 }
57335834 getValue(symbolId: SymbolId): unknown {
···11187 return resultIds.map((symbolId) => this.values.get(symbolId)!);
11288 }
11389114114- reference(identifier: ISymbolIdentifier): ISymbolOut {
115115- const symbol = this.identifierToSymbol(identifier);
116116- if (!symbol.meta) {
117117- // TODO: remove/refactor after removing selectors
118118- return this.register(symbol);
119119- }
120120- const [registered] = this.query(symbol.meta);
9090+ reference(meta: ISymbolMeta): ISymbolOut {
9191+ const [registered] = this.query(meta);
12192 if (registered) return registered;
122122- const cacheKey = this.buildCacheKey(symbol.meta);
9393+ const cacheKey = this.buildCacheKey(meta);
12394 const cachedId = this.stubCache.get(cacheKey);
12495 if (cachedId !== undefined) return this.values.get(cachedId)!;
12596 const id = this.id;
12697 const stub: ISymbolOut = {
12798 exportFrom: [],
12899 id,
129129- meta: symbol.meta,
100100+ meta,
130101 placeholder: wrapId(String(id)),
131102 };
132103 this.values.set(stub.id, stub);
···136107 }
137108138109 register(symbol: ISymbolIn): ISymbolOut {
139139- // TODO: refactor after removing selectors
140140- if (symbol.id !== undefined) {
141141- const result = this.values.get(symbol.id);
142142- if (!result) {
143143- throw new Error(
144144- `Symbol with ID ${symbol.id} not found. To register a new symbol, leave the ID undefined.`,
145145- );
146146- }
147147- return result;
148148- }
149149-150150- // TODO: refactor after removing selectors
151151- const hasOtherKeys = Object.keys(symbol).some(
152152- (key) => !['id', 'meta', 'selector'].includes(key),
153153- );
154154-155155- let result: ISymbolOut | undefined;
156156-157157- // TODO: remove after removing selectors
158158- const selector =
159159- symbol.selector !== undefined
160160- ? JSON.stringify(symbol.selector)
161161- : undefined;
162162- if (selector) {
163163- const id = this.selectorToId.get(selector);
164164- if (id !== undefined) {
165165- result = this.values.get(id);
166166- if (!result) {
167167- throw new Error(
168168- `Symbol with ID ${id} not found. The selector ${selector} matched an ID, but there was no result. This is likely an issue with the application logic.`,
169169- );
170170- }
171171- if (!hasOtherKeys) {
172172- return result;
173173- }
174174- }
175175- }
176176-177177- const id = result?.id !== undefined ? result.id : this.id;
178178- const exportFrom: Array<string> = result?.exportFrom
179179- ? [...result.exportFrom]
180180- : [];
181181- if (symbol.exportFrom) {
182182- exportFrom.push(...symbol.exportFrom);
183183- }
184184- result = {
185185- ...result,
110110+ const id = symbol.id !== undefined ? symbol.id : this.id;
111111+ const result: ISymbolOut = {
186112 ...symbol, // clone to avoid mutation
187187- exportFrom,
113113+ exportFrom: symbol.exportFrom ?? [],
188114 id,
189189- placeholder:
190190- result?.placeholder ?? symbol.placeholder ?? wrapId(String(id)),
115115+ placeholder: symbol.placeholder ?? wrapId(String(id)),
191116 };
192117 this.values.set(result.id, result);
193193-194194- if (hasOtherKeys) {
195195- this.registerOrder.add(result.id);
196196- }
197197-198198- if (selector) {
199199- // TODO: remove after removing selectors
200200- this.selectorToId.set(selector, result.id);
201201- }
202202-118118+ this.registerOrder.add(result.id);
203119 if (result.meta) {
204120 const indexKeySpace = this.buildIndexKeySpace(result.meta);
205121 this.indexSymbol(result.id, indexKeySpace);
206122 this.invalidateCache(indexKeySpace);
207123 this.replaceStubs(result, indexKeySpace);
208124 }
209209-210125 return result;
211126 }
212127···239154 }
240155 }
241156 return entries;
242242- }
243243-244244- private identifierToSymbol(
245245- identifier: ISymbolIdentifier,
246246- ): Pick<ISymbolIn, 'id' | 'meta' | 'selector'> {
247247- if (typeof identifier === 'number') {
248248- return { id: identifier };
249249- }
250250- if (identifier instanceof Array) {
251251- // TODO: remove after removing selectors
252252- return { selector: identifier };
253253- }
254254- return { meta: identifier };
255157 }
256158257159 private indexSymbol(symbolId: SymbolId, indexKeySpace: IndexKeySpace): void {
+5-14
packages/codegen-core/src/symbols/types.d.ts
···11import type { ISymbolMeta } from '../extensions/types';
22-import type { ISelector } from '../selectors/types';
3244-export type ISymbolIdentifier = number | ISymbolMeta | ISelector;
33+export type ISymbolIdentifier = number | ISymbolMeta;
5465export interface ISymbolIn {
76 /**
···6059 * @example "_heyapi_31_"
6160 */
6261 readonly placeholder?: string;
6363- /**
6464- * Selector array used to select this symbol. It doesn't have to be
6565- * unique, but in practice it might be desirable.
6666- *
6767- * @deprecated
6868- * @example ["zod", "#/components/schemas/Foo"]
6969- */
7070- readonly selector?: ISelector;
7162}
72637364export interface ISymbolOut extends ISymbolIn {
···130121 */
131122 query(filter: ISymbolMeta): ReadonlyArray<ISymbolOut>;
132123 /**
133133- * Returns a symbol, registers it if it doesn't exist.
124124+ * References a symbol.
134125 *
135135- * @param identifier Symbol identifier to reference.
136136- * @returns The referenced or newly registered symbol.
126126+ * @param meta Metadata filter to reference symbol by.
127127+ * @returns The referenced symbol.
137128 */
138138- reference(identifier: ISymbolIdentifier): ISymbolOut;
129129+ reference(meta: ISymbolMeta): ISymbolOut;
139130 /**
140131 * Register a symbol globally.
141132 *