fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2873 from hey-api/feat/symbol-registry-query

authored by

Lubos and committed by
GitHub
afb4dfbe 672d96fe

+647 -367
+5
.changeset/chubby-ends-follow.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + refactor: replace plugin references with queries
+5
.changeset/lucky-results-like.md
··· 1 + --- 2 + '@hey-api/codegen-core': patch 3 + --- 4 + 5 + feat: add `.query()` method to symbol registry
+1 -1
.vscode/launch.json
··· 16 16 "cwd": "${workspaceFolder}/dev", 17 17 "runtimeExecutable": "node", 18 18 "runtimeArgs": ["-r", "ts-node/register/transpile-only"], 19 - "program": "${workspaceFolder}/packages/openapi-ts/src/cli.ts" 19 + "program": "${workspaceFolder}/packages/openapi-ts/src/run.ts" 20 20 } 21 21 ] 22 22 }
+11 -11
dev/openapi-ts.config.ts
··· 138 138 // if (!symbol.external && !symbol.meta?.resourceType) { 139 139 // console.log(`[${plugin.name}]:`, symbol.name); 140 140 // } 141 - if (!symbol.external && !symbol.meta?.path) { 142 - console.log(`[${plugin.name}]:`, symbol.name, symbol.meta); 141 + if (plugin && !symbol.external && !symbol.meta?.path) { 142 + // console.log(`[${plugin.name}]:`, symbol.name, symbol.meta); 143 143 } 144 144 // if (symbol.meta?.tags && symbol.meta?.tags.size > 0) { 145 145 // console.log( ··· 213 213 // myClientPlugin(), 214 214 { 215 215 // baseUrl: false, 216 - exportFromIndex: true, 217 - // name: '@hey-api/client-fetch', 216 + // exportFromIndex: true, 217 + name: '@hey-api/client-fetch', 218 218 // name: 'legacy/angular', 219 219 // runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'), 220 - runtimeConfigPath: './src/hey-api.ts', 220 + // runtimeConfigPath: './src/hey-api.ts', 221 221 // strictBaseUrl: true, 222 222 // throwOnError: true, 223 223 }, ··· 269 269 // signature: 'object', 270 270 // transformer: '@hey-api/transformers', 271 271 // transformer: true, 272 - validator: { 273 - request: 'zod', 274 - response: 'zod', 275 - }, 272 + // validator: { 273 + // request: 'zod', 274 + // response: 'zod', 275 + // }, 276 276 '~hooks': { 277 277 symbols: { 278 278 // getFilePath: (symbol) => { ··· 346 346 // definitions: 'z{{name}}Definition', 347 347 exportFromIndex: true, 348 348 // metadata: true, 349 - name: 'valibot', 349 + // name: 'valibot', 350 350 // requests: { 351 351 // case: 'PascalCase', 352 352 // name: '{{name}}Data', ··· 396 396 }, 397 397 exportFromIndex: true, 398 398 // metadata: true, 399 - name: 'zod', 399 + // name: 'zod', 400 400 // requests: { 401 401 // // case: 'SCREAMING_SNAKE_CASE', 402 402 // // name: 'z{{name}}TestData',
+1
eslint.config.js
··· 64 64 '**/dist/', 65 65 '**/node_modules/', 66 66 'temp/', 67 + 'dev/.gen/', 67 68 'examples/openapi-ts-openai/src/client/**/*.ts', 68 69 'packages/openapi-ts/src/legacy/handlebars/compiled/**/*.js', 69 70 'packages/openapi-ts/src/legacy/handlebars/templates/**/*.hbs',
+12 -7
packages/codegen-core/src/__tests__/bindings.test.ts
··· 65 65 placeholder: string, 66 66 meta: ISymbolMeta = {}, 67 67 name?: string, 68 - ): ISymbolOut => ({ 69 - exportFrom: [], 70 - id, 71 - meta, 72 - name, 73 - placeholder, 74 - }); 68 + ): ISymbolOut => { 69 + const { importKind, kind, ...restMeta } = meta as any; 70 + return { 71 + exportFrom: [], 72 + id, 73 + importKind, 74 + kind, 75 + meta: restMeta, 76 + name, 77 + placeholder, 78 + } as any; 79 + }; 75 80 76 81 describe('createBinding', () => { 77 82 it('creates a named binding by default', () => {
+100
packages/codegen-core/src/__tests__/symbols.test.ts
··· 90 90 expect(registry.isRegistered(symRegistered.id)).toBe(true); 91 91 }); 92 92 93 + it('indexes symbols and supports querying by meta', () => { 94 + const registry = new SymbolRegistry(); 95 + 96 + // register a couple of symbols with meta 97 + const symA = registry.register({ 98 + meta: { bar: 'type', foo: { bar: true } }, 99 + name: 'A', 100 + }); 101 + const symB = registry.register({ 102 + meta: { bar: 'value', foo: { bar: false } }, 103 + name: 'B', 104 + }); 105 + 106 + // query by top-level meta key 107 + const types = registry.query({ bar: 'type' }); 108 + expect(types).toEqual([symA]); 109 + 110 + // query by nested meta key 111 + const nestedTrue = registry.query({ foo: { bar: true } }); 112 + expect(nestedTrue).toEqual([symA]); 113 + 114 + const nestedFalse = registry.query({ foo: { bar: false } }); 115 + expect(nestedFalse).toEqual([symB]); 116 + }); 117 + 118 + it('replaces stubs after registering', () => { 119 + const registry = new SymbolRegistry(); 120 + 121 + const refA = registry.reference({ a: 0 }); 122 + const refAB = registry.reference({ a: 0, b: 0 }); 123 + const refB = registry.reference({ b: -1 }); 124 + const symC = registry.register({ 125 + meta: { a: 0, b: 0, c: 0 }, 126 + name: 'C', 127 + }); 128 + const refAD = registry.reference({ a: 0, d: 0 }); 129 + const refAC = registry.reference({ a: 0, c: 0 }); 130 + 131 + expect(symC).toEqual(refA); 132 + expect(symC).toEqual(refAB); 133 + expect(symC).toEqual(refAC); 134 + expect(symC).not.toEqual(refAD); 135 + expect(symC).not.toEqual(refB); 136 + expect(symC.meta).toEqual({ a: 0, b: 0, c: 0 }); 137 + }); 138 + 93 139 it('throws on invalid register or reference', () => { 94 140 const registry = new SymbolRegistry(); 95 141 // Register with id that does not exist ··· 102 148 expect(() => registry.register({ selector: ['missing'] })).toThrow( 103 149 '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.', 104 150 ); 151 + }); 152 + 153 + it('caches query results and invalidates on relevant updates', () => { 154 + const registry = new SymbolRegistry(); 155 + const symA = registry.register({ meta: { foo: 'bar' }, name: 'A' }); 156 + 157 + // first query populates cache 158 + const result1 = registry.query({ foo: 'bar' }); 159 + expect(result1).toEqual([symA]); 160 + expect(registry['queryCache'].size).toBe(1); 161 + 162 + // same query should hit cache, no change in cache size 163 + const result2 = registry.query({ foo: 'bar' }); 164 + expect(result2).toEqual([symA]); 165 + expect(registry['queryCache'].size).toBe(1); 166 + 167 + // register another symbol with matching key should invalidate cache 168 + registry.register({ meta: { foo: 'bar' }, name: 'B' }); 169 + expect(registry['queryCache'].size).toBe(0); 170 + 171 + // new query repopulates cache 172 + const result3 = registry.query({ foo: 'bar' }); 173 + expect(result3.map((r) => r.name).sort()).toEqual(['A', 'B']); 174 + expect(registry['queryCache'].size).toBe(1); 175 + }); 176 + 177 + it('invalidates only affected cache entries', () => { 178 + const registry = new SymbolRegistry(); 179 + const symA = registry.register({ meta: { foo: 'bar' }, name: 'A' }); 180 + const symX = registry.register({ meta: { x: 'y' }, name: 'X' }); 181 + 182 + // Seed multiple cache entries 183 + const resultFoo = registry.query({ foo: 'bar' }); 184 + const resultX = registry.query({ x: 'y' }); 185 + expect(resultFoo).toEqual([symA]); 186 + expect(resultX).toEqual([symX]); 187 + const initialCacheKeys = Array.from(registry['queryCache'].keys()); 188 + expect(initialCacheKeys.length).toBe(2); 189 + 190 + // Add new symbol that should only affect foo:bar queries 191 + registry.register({ meta: { foo: 'bar' }, name: 'B' }); 192 + 193 + // Cache entry for foo:bar should be invalidated, x:y should remain 194 + const cacheKeysAfter = Array.from(registry['queryCache'].keys()); 195 + expect(cacheKeysAfter.length).toBe(1); 196 + const remainingKey = cacheKeysAfter[0]; 197 + expect(remainingKey).toBe( 198 + initialCacheKeys.find((k) => k.includes('x:"y"')), 199 + ); 200 + 201 + // Query foo:bar again to repopulate it 202 + const resultFoo2 = registry.query({ foo: 'bar' }); 203 + expect(resultFoo2.map((r) => r.name).sort()).toEqual(['A', 'B']); 204 + expect(registry['queryCache'].size).toBe(2); 105 205 }); 106 206 });
+7 -7
packages/codegen-core/src/bindings/utils.ts
··· 19 19 aliases: {}, 20 20 from: modulePath, 21 21 }; 22 - if (symbol.meta?.importKind) { 23 - if (symbol.meta.importKind === 'default') { 22 + if (symbol.importKind) { 23 + if (symbol.importKind === 'default') { 24 24 binding.defaultBinding = symbol.placeholder; 25 - if (symbol.meta.kind === 'type') { 25 + if (symbol.kind === 'type') { 26 26 binding.typeDefaultBinding = true; 27 27 } 28 - } else if (symbol.meta.importKind === 'namespace') { 28 + } else if (symbol.importKind === 'namespace') { 29 29 binding.namespaceBinding = symbol.placeholder; 30 - if (symbol.meta.kind === 'type') { 30 + if (symbol.kind === 'type') { 31 31 binding.typeNamespaceBinding = true; 32 32 } 33 33 } 34 34 } 35 35 // default to named binding 36 36 if ( 37 - symbol.meta?.importKind === 'named' || 37 + symbol.importKind === 'named' || 38 38 (!names.length && !binding.defaultBinding && !binding.namespaceBinding) 39 39 ) { 40 40 let name = symbol.placeholder; ··· 52 52 } 53 53 } 54 54 names.push(name); 55 - if (symbol.meta?.kind === 'type') { 55 + if (symbol.kind === 'type') { 56 56 typeNames.push(name); 57 57 } 58 58 }
+1
packages/codegen-core/src/index.ts
··· 14 14 export type { ISelector as Selector } from './selectors/types'; 15 15 export type { 16 16 ISymbolOut as Symbol, 17 + ISymbolIdentifier as SymbolIdentifier, 17 18 ISymbolIn as SymbolIn, 18 19 } from './symbols/types';
+1
packages/codegen-core/src/selectors/types.d.ts
··· 2 2 * Selector array used to reference resources. We don't enforce 3 3 * uniqueness, but in practice it's desirable. 4 4 * 5 + * @deprecated 5 6 * @example ["zod", "#/components/schemas/Foo"] 6 7 */ 7 8 export type ISelector = ReadonlyArray<string>;
+187 -29
packages/codegen-core/src/symbols/registry.ts
··· 1 + import type { ISymbolMeta } from '../extensions/types'; 1 2 import { wrapId } from '../renderer/utils'; 2 - import type { ISelector } from '../selectors/types'; 3 - import type { ISymbolIn, ISymbolOut, ISymbolRegistry } from './types'; 3 + import type { 4 + ISymbolIdentifier, 5 + ISymbolIn, 6 + ISymbolOut, 7 + ISymbolRegistry, 8 + } from './types'; 9 + 10 + type IndexEntry = [string, unknown]; 11 + type IndexKeySpace = ReadonlyArray<IndexEntry>; 12 + type QueryCacheKey = string; 13 + type SymbolId = number; 4 14 5 15 export class SymbolRegistry implements ISymbolRegistry { 6 - private _id: number = 0; 7 - private nodes: Map<number, unknown> = new Map(); 8 - private registerOrder: Set<number> = new Set(); 9 - private selectorToId: Map<string, number> = new Map(); 10 - private values: Map<number, ISymbolOut> = new Map(); 16 + private _id: SymbolId = 0; 17 + private indices: Map<IndexEntry[0], Map<IndexEntry[1], Set<SymbolId>>> = 18 + new Map(); 19 + private nodes: Map<SymbolId, unknown> = new Map(); 20 + private queryCache: Map<QueryCacheKey, ReadonlyArray<SymbolId>> = new Map(); 21 + private queryCacheDependencies: Map<QueryCacheKey, Set<QueryCacheKey>> = 22 + new Map(); 23 + private registerOrder: Set<SymbolId> = new Set(); 24 + // TODO: remove after removing selectors 25 + private selectorToId: Map<string, SymbolId> = new Map(); 26 + private stubs: Set<SymbolId> = new Set(); 27 + private values: Map<SymbolId, ISymbolOut> = new Map(); 11 28 12 - get(symbolIdOrSelector: number | ISelector): ISymbolOut | undefined { 13 - const symbol = this.idOrSelector(symbolIdOrSelector); 29 + get(identifier: ISymbolIdentifier): ISymbolOut | undefined { 30 + const symbol = this.identifierToSymbol(identifier); 14 31 15 32 if (symbol.id !== undefined) { 16 33 return this.values.get(symbol.id); 17 34 } 18 35 36 + // TODO: remove after removing selectors 19 37 const selector = 20 38 symbol.selector !== undefined 21 39 ? JSON.stringify(symbol.selector) 22 40 : undefined; 23 41 42 + // TODO: remove after removing selectors 24 43 if (selector) { 25 44 const id = this.selectorToId.get(selector); 26 45 if (id !== undefined) { 27 46 return this.values.get(id); 28 47 } 48 + } 49 + 50 + if (symbol.meta) { 51 + return this.query(symbol.meta)[0]; 29 52 } 30 53 31 54 return; 32 55 } 33 56 34 - getValue(symbolId: number): unknown { 57 + getValue(symbolId: SymbolId): unknown { 35 58 return this.nodes.get(symbolId); 36 59 } 37 60 38 - hasValue(symbolId: number): boolean { 61 + hasValue(symbolId: SymbolId): boolean { 39 62 return this.nodes.has(symbolId); 40 63 } 41 64 42 - get id(): number { 65 + get id(): SymbolId { 43 66 return this._id++; 44 67 } 45 68 46 - private idOrSelector( 47 - symbolIdOrSelector: number | ISelector, 48 - ): Pick<ISymbolIn, 'id' | 'selector'> { 49 - return typeof symbolIdOrSelector === 'number' 50 - ? { id: symbolIdOrSelector } 51 - : { selector: symbolIdOrSelector }; 69 + isRegistered(identifier: ISymbolIdentifier): boolean { 70 + const symbol = this.get(identifier); 71 + return symbol ? this.registerOrder.has(symbol.id) : false; 52 72 } 53 73 54 - isRegistered(symbolIdOrSelector: number | ISelector): boolean { 55 - const symbol = this.get(symbolIdOrSelector); 56 - return symbol ? this.registerOrder.has(symbol.id) : false; 74 + query(filter: ISymbolMeta): ReadonlyArray<ISymbolOut> { 75 + const cacheKey = this.buildCacheKey(filter); 76 + const cachedIds = this.queryCache.get(cacheKey); 77 + if (cachedIds) { 78 + return cachedIds.map((symbolId) => this.values.get(symbolId)!); 79 + } 80 + const sets: Array<Set<SymbolId>> = []; 81 + const indexKeySpace = this.buildIndexKeySpace(filter); 82 + const cacheDependencies = new Set<QueryCacheKey>(); 83 + for (const indexEntry of indexKeySpace) { 84 + cacheDependencies.add(this.serializeIndexEntry(indexEntry)); 85 + const values = this.indices.get(indexEntry[0]); 86 + if (!values) return []; 87 + const set = values.get(indexEntry[1]); 88 + if (!set) return []; 89 + sets.push(set); 90 + } 91 + if (!sets.length) return []; 92 + let result = new Set(sets[0]); 93 + for (const set of sets.slice(1)) { 94 + result = new Set([...result].filter((symbolId) => set.has(symbolId))); 95 + } 96 + const resultIds = [...result]; 97 + this.queryCacheDependencies.set(cacheKey, cacheDependencies); 98 + this.queryCache.set(cacheKey, resultIds); 99 + return resultIds.map((symbolId) => this.values.get(symbolId)!); 57 100 } 58 101 59 - reference(symbolIdOrSelector: number | ISelector): ISymbolOut { 60 - const symbol = this.idOrSelector(symbolIdOrSelector); 61 - return this.register(symbol); 102 + reference(identifier: ISymbolIdentifier): ISymbolOut { 103 + const symbol = this.identifierToSymbol(identifier); 104 + if (!symbol.meta) { 105 + // TODO: remove/refactor after removing selectors 106 + return this.register(symbol); 107 + } 108 + const [registered] = this.query(symbol.meta); 109 + if (registered) return registered; 110 + const id = this.id; 111 + const stub: ISymbolOut = { 112 + exportFrom: [], 113 + id, 114 + meta: symbol.meta, 115 + placeholder: wrapId(String(id)), 116 + }; 117 + this.values.set(stub.id, stub); 118 + this.stubs.add(stub.id); 119 + return stub; 62 120 } 63 121 64 122 register(symbol: ISymbolIn): ISymbolOut { 123 + // TODO: refactor after removing selectors 65 124 if (symbol.id !== undefined) { 66 125 const result = this.values.get(symbol.id); 67 126 if (!result) { ··· 72 131 return result; 73 132 } 74 133 134 + // TODO: refactor after removing selectors 75 135 const hasOtherKeys = Object.keys(symbol).some( 76 - (key) => !['id', 'selector'].includes(key), 136 + (key) => !['id', 'meta', 'selector'].includes(key), 77 137 ); 78 138 79 139 let result: ISymbolOut | undefined; 80 140 141 + // TODO: remove after removing selectors 81 142 const selector = 82 143 symbol.selector !== undefined 83 144 ? JSON.stringify(symbol.selector) ··· 112 173 placeholder: 113 174 result?.placeholder ?? symbol.placeholder ?? wrapId(String(id)), 114 175 }; 115 - this.values.set(id, result); 176 + this.values.set(result.id, result); 116 177 117 178 if (hasOtherKeys) { 118 - this.registerOrder.add(id); 179 + this.registerOrder.add(result.id); 119 180 } 120 181 121 182 if (selector) { 122 - this.selectorToId.set(selector, id); 183 + // TODO: remove after removing selectors 184 + this.selectorToId.set(selector, result.id); 185 + } 186 + 187 + if (result.meta) { 188 + const indexKeySpace = this.buildIndexKeySpace(result.meta); 189 + this.indexSymbol(result.id, indexKeySpace); 190 + this.invalidateCache(indexKeySpace); 191 + this.replaceStubs(result, indexKeySpace); 123 192 } 124 193 125 194 return result; ··· 131 200 } 132 201 } 133 202 134 - setValue(symbolId: number, value: unknown): Map<number, unknown> { 203 + setValue(symbolId: SymbolId, value: unknown): Map<SymbolId, unknown> { 135 204 return this.nodes.set(symbolId, value); 205 + } 206 + 207 + private buildCacheKey(filter: ISymbolMeta): QueryCacheKey { 208 + const indexKeySpace = this.buildIndexKeySpace(filter); 209 + return indexKeySpace 210 + .map((indexEntry) => this.serializeIndexEntry(indexEntry)) 211 + .sort() // ensure order-insensitivity 212 + .join('|'); 213 + } 214 + 215 + private buildIndexKeySpace(meta: ISymbolMeta, prefix = ''): IndexKeySpace { 216 + const entries: Array<IndexEntry> = []; 217 + for (const [key, value] of Object.entries(meta)) { 218 + const path = prefix ? `${prefix}.${key}` : key; 219 + if (value && typeof value === 'object' && !Array.isArray(value)) { 220 + entries.push(...this.buildIndexKeySpace(value as ISymbolMeta, path)); 221 + } else { 222 + entries.push([path, value]); 223 + } 224 + } 225 + return entries; 226 + } 227 + 228 + private identifierToSymbol( 229 + identifier: ISymbolIdentifier, 230 + ): Pick<ISymbolIn, 'id' | 'meta' | 'selector'> { 231 + if (typeof identifier === 'number') { 232 + return { id: identifier }; 233 + } 234 + if (identifier instanceof Array) { 235 + // TODO: remove after removing selectors 236 + return { selector: identifier }; 237 + } 238 + return { meta: identifier }; 239 + } 240 + 241 + private indexSymbol(symbolId: SymbolId, indexKeySpace: IndexKeySpace): void { 242 + for (const [key, value] of indexKeySpace) { 243 + if (!this.indices.has(key)) this.indices.set(key, new Map()); 244 + const values = this.indices.get(key)!; 245 + const set = values.get(value) ?? new Set(); 246 + set.add(symbolId); 247 + values.set(value, set); 248 + } 249 + } 250 + 251 + private invalidateCache(indexKeySpace: IndexKeySpace): void { 252 + const changed = indexKeySpace.map((indexEntry) => 253 + this.serializeIndexEntry(indexEntry), 254 + ); 255 + for (const [ 256 + cacheKey, 257 + cacheDependencies, 258 + ] of this.queryCacheDependencies.entries()) { 259 + for (const key of changed) { 260 + if (cacheDependencies.has(key)) { 261 + this.queryCacheDependencies.delete(cacheKey); 262 + this.queryCache.delete(cacheKey); 263 + break; 264 + } 265 + } 266 + } 267 + } 268 + 269 + private isSubset(sub: IndexKeySpace, sup: IndexKeySpace): boolean { 270 + const supMap = new Map(sup); 271 + for (const [key, value] of sub) { 272 + if (!supMap.has(key) || supMap.get(key) !== value) { 273 + return false; 274 + } 275 + } 276 + return true; 277 + } 278 + 279 + private replaceStubs(symbol: ISymbolOut, indexKeySpace: IndexKeySpace): void { 280 + for (const stubId of this.stubs.values()) { 281 + const stub = this.values.get(stubId); 282 + if ( 283 + stub?.meta && 284 + this.isSubset(this.buildIndexKeySpace(stub.meta), indexKeySpace) 285 + ) { 286 + this.values.set(stubId, Object.assign(stub, symbol)); 287 + this.stubs.delete(stubId); 288 + } 289 + } 290 + } 291 + 292 + private serializeIndexEntry(indexEntry: IndexEntry): string { 293 + return `${indexEntry[0]}:${JSON.stringify(indexEntry[1])}`; 136 294 } 137 295 }
+27 -18
packages/codegen-core/src/symbols/types.d.ts
··· 1 1 import type { ISymbolMeta } from '../extensions/types'; 2 2 import type { ISelector } from '../selectors/types'; 3 3 4 + export type ISymbolIdentifier = number | ISymbolMeta | ISelector; 5 + 4 6 export interface ISymbolIn { 5 7 /** 6 8 * Array of file names (without extensions) from which this symbol is re-exported. ··· 32 34 */ 33 35 readonly id?: number; 34 36 /** 37 + * Kind of import if this symbol represents an import. 38 + */ 39 + readonly importKind?: 'namespace' | 'default' | 'named'; 40 + /** 41 + * Kind of symbol. 42 + */ 43 + readonly kind?: 'type'; 44 + /** 35 45 * Arbitrary metadata about the symbol. 36 46 * 37 47 * @default undefined 38 48 */ 39 - readonly meta?: ISymbolMeta & { 40 - /** 41 - * Kind of import if this symbol represents an import. 42 - */ 43 - importKind?: 'namespace' | 'default' | 'named'; 44 - /** 45 - * Kind of symbol. 46 - */ 47 - kind?: 'type'; 48 - }; 49 + readonly meta?: ISymbolMeta; 49 50 /** 50 51 * The desired name for the symbol within its file. If there are multiple symbols 51 52 * with the same desired name, this might not end up being the actual name. ··· 63 64 * Selector array used to select this symbol. It doesn't have to be 64 65 * unique, but in practice it might be desirable. 65 66 * 67 + * @deprecated 66 68 * @example ["zod", "#/components/schemas/Foo"] 67 69 */ 68 70 readonly selector?: ISelector; ··· 87 89 88 90 export interface ISymbolRegistry { 89 91 /** 90 - * Get a symbol by its ID. 92 + * Get a symbol. 91 93 * 92 - * @param symbolIdOrSelector Symbol ID or selector to reference. 94 + * @param identifier Symbol identifier to reference. 93 95 * @returns The symbol, or undefined if not found. 94 96 */ 95 - get(symbolIdOrSelector: number | ISelector): ISymbolOut | undefined; 97 + get(identifier: ISymbolIdentifier): ISymbolOut | undefined; 96 98 /** 97 99 * Returns the value associated with a symbol ID. 98 100 * ··· 116 118 /** 117 119 * Returns whether a symbol is registered in the registry. 118 120 * 119 - * @param symbolIdOrSelector Symbol ID or selector to check. 121 + * @param identifier Symbol identifier to check. 120 122 * @returns True if the symbol is registered, false otherwise. 121 123 */ 122 - isRegistered(symbolIdOrSelector: number | ISelector): boolean; 124 + isRegistered(identifier: ISymbolIdentifier): boolean; 125 + /** 126 + * Queries symbols by metadata filter. 127 + * 128 + * @param filter Metadata filter to query symbols by. 129 + * @returns Array of symbols matching the filter. 130 + */ 131 + query(filter: ISymbolMeta): ReadonlyArray<ISymbolOut>; 123 132 /** 124 - * Returns a symbol by ID or selector, registering it if it doesn't exist. 133 + * Returns a symbol, registers it if it doesn't exist. 125 134 * 126 - * @param symbolIdOrSelector Symbol ID or selector to reference. 135 + * @param identifier Symbol identifier to reference. 127 136 * @returns The referenced or newly registered symbol. 128 137 */ 129 - reference(symbolIdOrSelector: number | ISelector): ISymbolOut; 138 + reference(identifier: ISymbolIdentifier): ISymbolOut; 130 139 /** 131 140 * Register a symbol globally. 132 141 *
+2 -1
packages/openapi-ts/src/generate/__tests__/renderer.test.ts
··· 87 87 const typeSymbol: any = { 88 88 exportFrom: [], 89 89 id: typeSymbolId, 90 - meta: { kind: 'type' }, 90 + kind: 'type', 91 + meta: {}, 91 92 name: 'Foo', 92 93 placeholder: '_heyapi_1_', 93 94 };
+61 -62
packages/openapi-ts/src/generate/renderer.ts
··· 54 54 ]); 55 55 56 56 export class TypeScriptRenderer implements Renderer { 57 + renderFile( 58 + symbolsAndExports: string, 59 + file: File, 60 + project: IProject, 61 + meta?: ProjectRenderMeta, 62 + ): string { 63 + const imports: Map<string, Binding> = new Map(); 64 + symbolsAndExports = renderIds(symbolsAndExports, (symbolId) => { 65 + const symbol = project.symbols.get(symbolId); 66 + const replaced = this.replacerFn({ file, project, symbol }); 67 + if (symbol) { 68 + this.addBinding({ bindings: imports, file, meta, project, symbol }); 69 + } 70 + return replaced; 71 + }); 72 + if (!symbolsAndExports.length) return ''; 73 + let output = ''; 74 + const headerLines = this.getHeaderLines(); 75 + output += `${headerLines.join('\n')}${headerLines.length ? '\n\n' : ''}`; 76 + const importLines = this.getImportLines(imports, file, project); 77 + output += `${importLines.join('\n')}${importLines.length ? '\n\n' : ''}`; 78 + return `${output}${symbolsAndExports}`; 79 + } 80 + 81 + renderSymbols( 82 + file: File, 83 + project: IProject, 84 + meta?: ProjectRenderMeta, 85 + ): string { 86 + const exports: Map<string, Binding> = new Map(); 87 + let output = ''; 88 + const bodyLines = this.getBodyLines(file, project); 89 + output += `${bodyLines.join('\n\n')}${bodyLines.length ? '\n' : ''}`; 90 + output = renderIds(output, (symbolId) => { 91 + if (!file.symbols.body.includes(symbolId)) return; 92 + const symbol = project.symbols.get(symbolId); 93 + return this.replacerFn({ file, project, symbol }); 94 + }); 95 + for (const symbolId of file.symbols.exports) { 96 + const symbol = project.symbols.get(symbolId); 97 + if (symbol) { 98 + this.addBinding({ bindings: exports, file, meta, project, symbol }); 99 + } 100 + } 101 + // cast everything into namespace exports for now 102 + for (const binding of exports.values()) { 103 + binding.namespaceBinding = true; 104 + binding.typeNamespaceBinding = 105 + binding.names && 106 + binding.typeNames && 107 + binding.names.length === binding.typeNames.length && 108 + binding.names.every((name) => (binding.typeNames ?? []).includes(name)); 109 + } 110 + const exportLines = this.getExportLines(exports, file, project); 111 + output += `${exportLines.join('\n')}${exportLines.length ? '\n' : ''}`; 112 + return output; 113 + } 114 + 57 115 private addBinding({ 58 116 bindings, 59 117 file, ··· 72 130 } 73 131 74 132 const [symbolFile] = project.symbolIdToFiles(symbol.id); 75 - if (!symbolFile) return; 133 + if (!symbolFile || file === symbolFile) return; 76 134 77 135 const modulePath = this.getBindingPath(file, symbolFile, meta); 78 136 const existing = bindings.get(modulePath); ··· 405 463 return name; 406 464 } 407 465 408 - renderFile( 409 - symbolsAndExports: string, 410 - file: File, 411 - project: IProject, 412 - meta?: ProjectRenderMeta, 413 - ): string { 414 - const imports: Map<string, Binding> = new Map(); 415 - symbolsAndExports = renderIds(symbolsAndExports, (symbolId) => { 416 - const symbol = project.symbols.get(symbolId); 417 - const replaced = this.replacerFn({ file, project, symbol }); 418 - if (symbol) { 419 - this.addBinding({ bindings: imports, file, meta, project, symbol }); 420 - } 421 - return replaced; 422 - }); 423 - if (!symbolsAndExports.length) return ''; 424 - let output = ''; 425 - const headerLines = this.getHeaderLines(); 426 - output += `${headerLines.join('\n')}${headerLines.length ? '\n\n' : ''}`; 427 - const importLines = this.getImportLines(imports, file, project); 428 - output += `${importLines.join('\n')}${importLines.length ? '\n\n' : ''}`; 429 - return `${output}${symbolsAndExports}`; 430 - } 431 - 432 - renderSymbols( 433 - file: File, 434 - project: IProject, 435 - meta?: ProjectRenderMeta, 436 - ): string { 437 - const exports: Map<string, Binding> = new Map(); 438 - let output = ''; 439 - const bodyLines = this.getBodyLines(file, project); 440 - output += `${bodyLines.join('\n\n')}${bodyLines.length ? '\n' : ''}`; 441 - output = renderIds(output, (symbolId) => { 442 - if (!file.symbols.body.includes(symbolId)) return; 443 - const symbol = project.symbols.get(symbolId); 444 - return this.replacerFn({ file, project, symbol }); 445 - }); 446 - for (const symbolId of file.symbols.exports) { 447 - const symbol = project.symbols.get(symbolId); 448 - if (symbol) { 449 - this.addBinding({ bindings: exports, file, meta, project, symbol }); 450 - } 451 - } 452 - // cast everything into namespace exports for now 453 - for (const binding of exports.values()) { 454 - binding.namespaceBinding = true; 455 - binding.typeNamespaceBinding = 456 - binding.names && 457 - binding.typeNames && 458 - binding.names.length === binding.typeNames.length && 459 - binding.names.every((name) => (binding.typeNames ?? []).includes(name)); 460 - } 461 - const exportLines = this.getExportLines(exports, file, project); 462 - output += `${exportLines.join('\n')}${exportLines.length ? '\n' : ''}`; 463 - return output; 464 - } 465 - 466 466 private replacerFn({ 467 467 file, 468 468 project, ··· 483 483 if (conflictId !== undefined) { 484 484 const conflictSymbol = project.symbols.get(conflictId); 485 485 if ( 486 - (conflictSymbol?.meta?.kind === 'type' && 487 - symbol.meta?.kind === 'type') || 488 - (conflictSymbol?.meta?.kind !== 'type' && symbol.meta?.kind !== 'type') 486 + (conflictSymbol?.kind === 'type' && symbol.kind === 'type') || 487 + (conflictSymbol?.kind !== 'type' && symbol.kind !== 'type') 489 488 ) { 490 489 name = this.getUniqueName(name, file.resolvedNames); 491 490 }
+13
packages/openapi-ts/src/index.ts
··· 15 15 } 16 16 17 17 interface SymbolMeta { 18 + category?: 'sdk' | 'type' | (string & {}); 18 19 /** 19 20 * Path to the resource this symbol represents. 20 21 */ ··· 23 24 * Name of the plugin that registered this symbol. 24 25 */ 25 26 pluginName?: string; 27 + resource?: 'client' | 'definition' | 'operation' | (string & {}); 28 + resourceId?: string; 29 + role?: 30 + | 'data' 31 + | 'error' 32 + | 'errors' 33 + | 'options' 34 + | 'response' 35 + | 'responses' 36 + | (string & {}); 26 37 /** 27 38 * Tags associated with this symbol. 28 39 */ 29 40 tags?: Set<string>; 41 + tool?: string; 42 + variant?: 'container' | (string & {}); 30 43 } 31 44 } 32 45 // END OVERRIDES
+12 -10
packages/openapi-ts/src/plugins/@angular/common/httpRequests.ts
··· 278 278 operation: IR.OperationObject; 279 279 plugin: AngularCommonPlugin['Instance']; 280 280 }) => { 281 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 282 - 283 281 const symbolHttpRequest = plugin.referenceSymbol( 284 282 plugin.api.selector('HttpRequest'), 285 283 ); ··· 289 287 sdkPlugin.api.selector('Options'), 290 288 ); 291 289 292 - const symbolDataType = plugin.getSymbol( 293 - pluginTypeScript.api.selector('data', operation.id), 294 - ); 290 + const symbolDataType = plugin.querySymbol({ 291 + category: 'type', 292 + resource: 'operation', 293 + resourceId: operation.id, 294 + role: 'data', 295 + }); 295 296 const dataType = symbolDataType?.placeholder || 'unknown'; 296 297 297 298 return tsc.methodDeclaration({ ··· 335 336 plugin: AngularCommonPlugin['Instance']; 336 337 symbol: Symbol; 337 338 }) => { 338 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 339 - 340 339 const symbolHttpRequest = plugin.referenceSymbol( 341 340 plugin.api.selector('HttpRequest'), 342 341 ); ··· 346 345 sdkPlugin.api.selector('Options'), 347 346 ); 348 347 349 - const symbolDataType = plugin.getSymbol( 350 - pluginTypeScript.api.selector('data', operation.id), 351 - ); 348 + const symbolDataType = plugin.querySymbol({ 349 + category: 'type', 350 + resource: 'operation', 351 + resourceId: operation.id, 352 + role: 'data', 353 + }); 352 354 const dataType = symbolDataType?.placeholder || 'unknown'; 353 355 354 356 return tsc.constVariable({
+18 -14
packages/openapi-ts/src/plugins/@angular/common/httpResources.ts
··· 208 208 plugin: AngularCommonPlugin['Instance']; 209 209 }) => { 210 210 const sdkPlugin = plugin.getPluginOrThrow('@hey-api/sdk'); 211 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 212 211 213 212 const symbolHttpResource = plugin.referenceSymbol( 214 213 plugin.api.selector('httpResource'), 215 214 ); 216 215 217 - const symbolResponseType = plugin.getSymbol( 218 - pluginTypeScript.api.selector('response', operation.id), 219 - ); 216 + const symbolResponseType = plugin.querySymbol({ 217 + category: 'type', 218 + resource: 'operation', 219 + resourceId: operation.id, 220 + role: 'response', 221 + }); 220 222 const responseType = symbolResponseType?.placeholder || 'unknown'; 221 223 222 224 if (plugin.config.httpRequests.asClass) { ··· 363 365 operation: IR.OperationObject; 364 366 plugin: AngularCommonPlugin['Instance']; 365 367 }) => { 366 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 367 - 368 368 const sdkPlugin = plugin.getPluginOrThrow('@hey-api/sdk'); 369 369 const symbolOptions = plugin.referenceSymbol( 370 370 sdkPlugin.api.selector('Options'), 371 371 ); 372 372 373 - const symbolDataType = plugin.getSymbol( 374 - pluginTypeScript.api.selector('data', operation.id), 375 - ); 373 + const symbolDataType = plugin.querySymbol({ 374 + category: 'type', 375 + resource: 'operation', 376 + resourceId: operation.id, 377 + role: 'data', 378 + }); 376 379 const dataType = symbolDataType?.placeholder || 'unknown'; 377 380 378 381 return tsc.methodDeclaration({ ··· 416 419 plugin: AngularCommonPlugin['Instance']; 417 420 symbol: Symbol; 418 421 }) => { 419 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 420 - 421 422 const sdkPlugin = plugin.getPluginOrThrow('@hey-api/sdk'); 422 423 const symbolOptions = plugin.referenceSymbol( 423 424 sdkPlugin.api.selector('Options'), 424 425 ); 425 426 426 - const symbolDataType = plugin.getSymbol( 427 - pluginTypeScript.api.selector('data', operation.id), 428 - ); 427 + const symbolDataType = plugin.querySymbol({ 428 + category: 'type', 429 + resource: 'operation', 430 + resourceId: operation.id, 431 + role: 'data', 432 + }); 429 433 const dataType = symbolDataType?.placeholder || 'unknown'; 430 434 431 435 return tsc.constVariable({
+1 -3
packages/openapi-ts/src/plugins/@angular/common/plugin.ts
··· 5 5 export const handler: AngularCommonPlugin['Handler'] = ({ plugin }) => { 6 6 plugin.registerSymbol({ 7 7 external: '@angular/common/http', 8 - meta: { 9 - kind: 'type', 10 - }, 8 + kind: 'type', 11 9 name: 'HttpRequest', 12 10 selector: plugin.api.selector('HttpRequest'), 13 11 });
+5 -7
packages/openapi-ts/src/plugins/@hey-api/client-core/client.ts
··· 37 37 external: clientModule, 38 38 name: 'createConfig', 39 39 }); 40 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 41 - const symbolClientOptions = plugin.referenceSymbol( 42 - pluginTypeScript.api.selector('ClientOptions'), 43 - ); 40 + const symbolClientOptions = plugin.referenceSymbol({ 41 + category: 'type', 42 + resource: 'client', 43 + role: 'options', 44 + }); 44 45 45 46 const { runtimeConfigPath } = plugin.config; 46 47 const symbolCreateClientConfig = runtimeConfigPath ··· 93 94 ]; 94 95 95 96 const symbolClient = plugin.registerSymbol({ 96 - meta: { 97 - path: [], 98 - }, 99 97 name: 'client', 100 98 selector: plugin.api.selector('client'), 101 99 });
+8 -14
packages/openapi-ts/src/plugins/@hey-api/client-core/createClientConfig.ts
··· 7 7 plugin, 8 8 }: Parameters<PluginHandler>[0]) => { 9 9 const clientModule = clientFolderAbsolutePath(plugin.context.config); 10 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 11 - const symbolClientOptions = plugin.referenceSymbol( 12 - pluginTypeScript.api.selector('ClientOptions'), 13 - ); 10 + const symbolClientOptions = plugin.referenceSymbol({ 11 + category: 'type', 12 + resource: 'client', 13 + role: 'options', 14 + }); 14 15 const symbolConfig = plugin.registerSymbol({ 15 16 external: clientModule, 16 - meta: { 17 - kind: 'type', 18 - }, 17 + kind: 'type', 19 18 name: 'Config', 20 19 }); 21 20 const symbolDefaultClientOptions = plugin.registerSymbol({ 22 21 external: clientModule, 23 - meta: { 24 - kind: 'type', 25 - }, 22 + kind: 'type', 26 23 name: 'ClientOptions', 27 24 }); 28 25 const symbolCreateClientConfig = plugin.registerSymbol({ 29 26 exported: true, 30 - meta: { 31 - kind: 'type', 32 - path: [], 33 - }, 27 + kind: 'type', 34 28 name: 'CreateClientConfig', 35 29 }); 36 30
+6 -10
packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts
··· 144 144 context: plugin.context, 145 145 operation, 146 146 }); 147 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 148 147 const symbolResponse = isNuxtClient 149 - ? plugin.getSymbol( 150 - pluginTypeScript.api.selector('response', operation.id), 151 - ) 148 + ? plugin.querySymbol({ 149 + category: 'type', 150 + resource: 'operation', 151 + resourceId: operation.id, 152 + role: 'response', 153 + }) 152 154 : undefined; 153 155 154 156 const classes = operationClasses({ ··· 278 280 279 281 const symbolHeyApiClient = plugin.registerSymbol({ 280 282 exported: false, 281 - meta: { 282 - path: [], 283 - }, 284 283 name: '_HeyApiClient', 285 284 }); 286 285 ··· 333 332 334 333 const symbol = plugin.registerSymbol({ 335 334 exported: true, 336 - meta: { 337 - path: [], 338 - }, 339 335 name: currentClass.className, 340 336 selector: plugin.api.selector('class', currentClass.className), 341 337 });
+6 -4
packages/openapi-ts/src/plugins/@hey-api/sdk/shared/functions.ts
··· 26 26 context: plugin.context, 27 27 operation, 28 28 }); 29 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 30 29 const symbolResponse = isNuxtClient 31 - ? plugin.getSymbol( 32 - pluginTypeScript.api.selector('response', operation.id), 33 - ) 30 + ? plugin.querySymbol({ 31 + category: 'type', 32 + resource: 'operation', 33 + resourceId: operation.id, 34 + role: 'response', 35 + }) 34 36 : undefined; 35 37 const opParameters = operationParameters({ 36 38 isRequiredOptions,
+24 -22
packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts
··· 158 158 const client = getClientPlugin(plugin.context.config); 159 159 const isNuxtClient = client.name === '@hey-api/client-nuxt'; 160 160 161 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 162 - 163 - const symbolDataType = plugin.getSymbol( 164 - pluginTypeScript.api.selector('data', operation.id), 165 - ); 161 + const symbolDataType = plugin.querySymbol({ 162 + category: 'type', 163 + resource: 'operation', 164 + resourceId: operation.id, 165 + role: 'data', 166 + }); 166 167 const dataType = symbolDataType?.placeholder || 'unknown'; 167 168 168 169 const symbolOptions = plugin.referenceSymbol(plugin.api.selector('Options')); 169 170 170 171 if (isNuxtClient) { 171 - const symbolResponseType = plugin.getSymbol( 172 - pluginTypeScript.api.selector('response', operation.id), 173 - ); 172 + const symbolResponseType = plugin.querySymbol({ 173 + category: 'type', 174 + resource: 'operation', 175 + resourceId: operation.id, 176 + role: 'response', 177 + }); 174 178 const responseType = symbolResponseType?.placeholder || 'unknown'; 175 179 return `${symbolOptions.placeholder}<${nuxtTypeComposable}, ${dataType}, ${responseType}, ${nuxtTypeDefault}>`; 176 180 } ··· 369 373 const client = getClientPlugin(plugin.context.config); 370 374 const isNuxtClient = client.name === '@hey-api/client-nuxt'; 371 375 372 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 373 - 374 - const symbolResponseType = plugin.getSymbol( 375 - pluginTypeScript.api.selector( 376 - isNuxtClient ? 'response' : 'responses', 377 - operation.id, 378 - ), 379 - ); 376 + const symbolResponseType = plugin.querySymbol({ 377 + category: 'type', 378 + resource: 'operation', 379 + resourceId: operation.id, 380 + role: isNuxtClient ? 'response' : 'responses', 381 + }); 380 382 const responseType = symbolResponseType?.placeholder || 'unknown'; 381 383 382 - const symbolErrorType = plugin.getSymbol( 383 - pluginTypeScript.api.selector( 384 - isNuxtClient ? 'error' : 'errors', 385 - operation.id, 386 - ), 387 - ); 384 + const symbolErrorType = plugin.querySymbol({ 385 + category: 'type', 386 + resource: 'operation', 387 + resourceId: operation.id, 388 + role: isNuxtClient ? 'error' : 'errors', 389 + }); 388 390 const errorType = symbolErrorType?.placeholder || 'unknown'; 389 391 390 392 // TODO: transform parameters
+4 -16
packages/openapi-ts/src/plugins/@hey-api/sdk/shared/typeOptions.ts
··· 16 16 17 17 const symbolTDataShape = plugin.registerSymbol({ 18 18 external: clientModule, 19 - meta: { 20 - kind: 'type', 21 - path: [], 22 - }, 19 + kind: 'type', 23 20 name: 'TDataShape', 24 21 }); 25 22 const symbolClient = plugin.registerSymbol({ 26 23 external: clientModule, 27 - meta: { 28 - kind: 'type', 29 - path: [], 30 - }, 24 + kind: 'type', 31 25 name: 'Client', 32 26 selector: plugin.api.selector('Client'), 33 27 }); 34 28 const symbolClientOptions = plugin.registerSymbol({ 35 29 external: clientModule, 36 - meta: { 37 - kind: 'type', 38 - path: [], 39 - }, 30 + kind: 'type', 40 31 name: 'Options', 41 32 }); 42 33 const symbolOptions = plugin.registerSymbol({ 43 34 exported: true, 44 - meta: { 45 - kind: 'type', 46 - path: [], 47 - }, 35 + kind: 'type', 48 36 name: 'Options', 49 37 selector: plugin.api.selector('Options'), 50 38 });
+1 -3
packages/openapi-ts/src/plugins/@hey-api/sdk/v1/plugin.ts
··· 30 30 if (isNuxtClient) { 31 31 plugin.registerSymbol({ 32 32 external: clientModule, 33 - meta: { 34 - kind: 'type', 35 - }, 33 + kind: 'type', 36 34 name: 'Composable', 37 35 selector: plugin.api.selector('Composable'), 38 36 });
+6 -4
packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts
··· 334 334 return; 335 335 } 336 336 337 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 338 - const symbolResponse = plugin.getSymbol( 339 - pluginTypeScript.api.selector('response', operation.id), 340 - ); 337 + const symbolResponse = plugin.querySymbol({ 338 + category: 'type', 339 + resource: 'operation', 340 + resourceId: operation.id, 341 + role: 'response', 342 + }); 341 343 if (!symbolResponse) return; 342 344 343 345 // TODO: parser - consider handling simple string response which is also a date
-14
packages/openapi-ts/src/plugins/@hey-api/typescript/api.ts
··· 6 6 import { irSchemaToAstV1 } from './v1/api'; 7 7 8 8 type SelectorType = 9 - | 'ClientOptions' 10 - | 'data' 11 - | 'error' 12 - | 'errors' 13 - | 'ref' 14 - | 'response' 15 - | 'responses' 16 9 | 'TypeID' 17 10 | 'webhook-payload' 18 11 | 'webhook-request' ··· 23 16 /** 24 17 * @param type Selector type. 25 18 * @param value Depends on `type`: 26 - * - `ClientOptions`: never 27 - * - `data`: `operation.id` string 28 - * - `error`: `operation.id` string 29 - * - `errors`: `operation.id` string 30 - * - `ref`: `$ref` JSON pointer 31 - * - `response`: `operation.id` string 32 - * - `responses`: `operation.id` string 33 19 * - `TypeID`: `type` name string 34 20 * - `webhook-payload`: `operation.id` string 35 21 * - `webhook-request`: `operation.id` string
+25 -10
packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts
··· 122 122 123 123 const symbol = plugin.registerSymbol({ 124 124 exported: true, 125 + kind: 'type', 125 126 meta: { 126 - kind: 'type', 127 + category: 'type', 127 128 path: state.path.value, 129 + resource: 'operation', 130 + resourceId: operation.id, 131 + role: 'data', 128 132 tags: state.tags?.value, 129 133 }, 130 134 name: buildName({ 131 135 config: plugin.config.requests, 132 136 name: operation.id, 133 137 }), 134 - selector: plugin.api.selector('data', operation.id), 135 138 }); 136 139 const type = irSchemaToAst({ 137 140 plugin, ··· 161 164 if (errors) { 162 165 const symbolErrors = plugin.registerSymbol({ 163 166 exported: true, 167 + kind: 'type', 164 168 meta: { 165 - kind: 'type', 169 + category: 'type', 166 170 path: state.path.value, 171 + resource: 'operation', 172 + resourceId: operation.id, 173 + role: 'errors', 167 174 tags: state.tags?.value, 168 175 }, 169 176 name: buildName({ 170 177 config: plugin.config.errors, 171 178 name: operation.id, 172 179 }), 173 - selector: plugin.api.selector('errors', operation.id), 174 180 }); 175 181 const type = irSchemaToAst({ 176 182 plugin, ··· 187 193 if (error) { 188 194 const symbol = plugin.registerSymbol({ 189 195 exported: true, 196 + kind: 'type', 190 197 meta: { 191 - kind: 'type', 198 + category: 'type', 192 199 path: state.path.value, 200 + resource: 'operation', 201 + resourceId: operation.id, 202 + role: 'error', 193 203 tags: state.tags?.value, 194 204 }, 195 205 name: buildName({ ··· 199 209 }, 200 210 name: operation.id, 201 211 }), 202 - selector: plugin.api.selector('error', operation.id), 203 212 }); 204 213 const type = tsc.indexedAccessTypeNode({ 205 214 indexType: tsc.typeOperatorNode({ ··· 222 231 if (responses) { 223 232 const symbolResponses = plugin.registerSymbol({ 224 233 exported: true, 234 + kind: 'type', 225 235 meta: { 226 - kind: 'type', 236 + category: 'type', 227 237 path: state.path.value, 238 + resource: 'operation', 239 + resourceId: operation.id, 240 + role: 'responses', 228 241 tags: state.tags?.value, 229 242 }, 230 243 name: buildName({ 231 244 config: plugin.config.responses, 232 245 name: operation.id, 233 246 }), 234 - selector: plugin.api.selector('responses', operation.id), 235 247 }); 236 248 const type = irSchemaToAst({ 237 249 plugin, ··· 248 260 if (response) { 249 261 const symbol = plugin.registerSymbol({ 250 262 exported: true, 263 + kind: 'type', 251 264 meta: { 252 - kind: 'type', 265 + category: 'type', 253 266 path: state.path.value, 267 + resource: 'operation', 268 + resourceId: operation.id, 269 + role: 'response', 254 270 tags: state.tags?.value, 255 271 }, 256 272 name: buildName({ ··· 260 276 }, 261 277 name: operation.id, 262 278 }), 263 - selector: plugin.api.selector('response', operation.id), 264 279 }); 265 280 const type = tsc.indexedAccessTypeNode({ 266 281 indexType: tsc.typeOperatorNode({
+6 -4
packages/openapi-ts/src/plugins/@hey-api/typescript/shared/webhook.ts
··· 25 25 if (operation.body) { 26 26 const symbolWebhookPayload = plugin.registerSymbol({ 27 27 exported: true, 28 + kind: 'type', 28 29 meta: { 29 - kind: 'type', 30 30 path: state.path.value, 31 31 tags: state.tags?.value, 32 32 }, ··· 54 54 55 55 plugin.registerSymbol({ 56 56 exported: true, 57 + kind: 'type', 57 58 meta: { 58 - kind: 'type', 59 + category: 'type', 59 60 path: state.path.value, 61 + resource: 'definition', 62 + resourceId: symbolWebhookPayload.placeholder, 60 63 tags: state.tags?.value, 61 64 }, 62 65 name: symbolWebhookPayload.name, 63 66 placeholder: symbolWebhookPayload.placeholder, 64 - selector: plugin.api.selector('ref', symbolWebhookPayload.placeholder), 65 67 }); 66 68 data.properties.body = { $ref: symbolWebhookPayload.placeholder }; 67 69 dataRequired.push('body'); ··· 82 84 83 85 const symbolWebhookRequest = plugin.registerSymbol({ 84 86 exported: true, 87 + kind: 'type', 85 88 meta: { 86 - kind: 'type', 87 89 path: state.path.value, 88 90 tags: state.tags?.value, 89 91 },
+14 -12
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts
··· 25 25 schema: IR.SchemaObject; 26 26 }): ts.TypeNode => { 27 27 if (schema.$ref) { 28 - const symbol = plugin.referenceSymbol( 29 - plugin.api.selector('ref', schema.$ref), 30 - ); 28 + const symbol = plugin.referenceSymbol({ 29 + category: 'type', 30 + resource: 'definition', 31 + resourceId: schema.$ref, 32 + }); 31 33 return tsc.typeReferenceNode({ typeName: symbol.placeholder }); 32 34 } 33 35 ··· 82 84 const $ref = pathToJsonPointer(state.path.value); 83 85 const symbol = plugin.registerSymbol({ 84 86 exported: true, 87 + kind: isEnum ? undefined : 'type', 85 88 meta: { 86 - kind: isEnum ? undefined : 'type', 89 + category: 'type', 87 90 path: state.path.value, 91 + resource: 'definition', 92 + resourceId: $ref, 88 93 tags: state.tags?.value, 89 94 }, 90 95 name: buildName({ 91 96 config: plugin.config.definitions, 92 97 name: refToName($ref), 93 98 }), 94 - selector: plugin.api.selector('ref', $ref), 95 99 }); 96 100 exportType({ 97 101 plugin, ··· 105 109 // reserve identifier for ClientOptions 106 110 const symbolClientOptions = plugin.registerSymbol({ 107 111 exported: true, 112 + kind: 'type', 108 113 meta: { 109 - kind: 'type', 110 - path: [], 114 + category: 'type', 115 + resource: 'client', 116 + role: 'options', 111 117 }, 112 118 name: buildName({ 113 119 config: { ··· 115 121 }, 116 122 name: 'ClientOptions', 117 123 }), 118 - selector: plugin.api.selector('ClientOptions'), 119 124 }); 120 125 // reserve identifier for Webhooks 121 126 const symbolWebhooks = plugin.registerSymbol({ 122 127 exported: true, 123 - meta: { 124 - kind: 'type', 125 - path: [], 126 - }, 128 + kind: 'type', 127 129 name: buildName({ 128 130 config: { 129 131 case: plugin.config.case,
+2 -8
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts
··· 51 51 if (!plugin.getSymbol(selectorTypeId)) { 52 52 const symbolTypeId = plugin.registerSymbol({ 53 53 exported: true, 54 - meta: { 55 - kind: 'type', 56 - path: [], 57 - }, 54 + kind: 'type', 58 55 name: 'TypeID', 59 56 selector: selectorTypeId, 60 57 }); ··· 83 80 const symbolTypeId = plugin.referenceSymbol(selectorTypeId); 84 81 const symbolTypeName = plugin.registerSymbol({ 85 82 exported: true, 86 - meta: { 87 - kind: 'type', 88 - path: [], 89 - }, 83 + kind: 'type', 90 84 name: stringCase({ 91 85 case: plugin.config.case, 92 86 value: `${type}_id`,
+4 -12
packages/openapi-ts/src/plugins/@pinia/colada/plugin.ts
··· 8 8 export const handler: PiniaColadaPlugin['Handler'] = ({ plugin }) => { 9 9 plugin.registerSymbol({ 10 10 external: plugin.name, 11 - meta: { 12 - kind: 'type', 13 - }, 11 + kind: 'type', 14 12 name: 'UseMutationOptions', 15 13 selector: plugin.api.selector('UseMutationOptions'), 16 14 }); 17 15 plugin.registerSymbol({ 18 16 external: plugin.name, 19 - meta: { 20 - kind: 'type', 21 - }, 17 + kind: 'type', 22 18 name: 'UseQueryOptions', 23 19 selector: plugin.api.selector('UseQueryOptions'), 24 20 }); 25 21 plugin.registerSymbol({ 26 22 external: plugin.name, 27 - meta: { 28 - kind: 'type', 29 - }, 23 + kind: 'type', 30 24 name: '_JSONValue', 31 25 selector: plugin.api.selector('_JSONValue'), 32 26 }); 33 27 plugin.registerSymbol({ 34 28 external: 'axios', 35 - meta: { 36 - kind: 'type', 37 - }, 29 + kind: 'type', 38 30 name: 'AxiosError', 39 31 selector: plugin.api.selector('AxiosError'), 40 32 });
+1 -1
packages/openapi-ts/src/plugins/@pinia/colada/queryKey.ts
··· 338 338 ); 339 339 const symbolQueryKeyType = plugin.registerSymbol({ 340 340 exported: true, 341 - meta: { kind: 'type' }, 341 + kind: 'type', 342 342 name: 'QueryKey', 343 343 selector: plugin.api.selector('QueryKey'), 344 344 });
+12 -8
packages/openapi-ts/src/plugins/@pinia/colada/useType.ts
··· 24 24 plugin: PiniaColadaPlugin['Instance']; 25 25 }): string => { 26 26 const client = getClientPlugin(plugin.context.config); 27 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 28 27 29 - const symbolErrorType = plugin.getSymbol( 30 - pluginTypeScript.api.selector('error', operation.id), 31 - ); 28 + const symbolErrorType = plugin.querySymbol({ 29 + category: 'type', 30 + resource: 'operation', 31 + resourceId: operation.id, 32 + role: 'error', 33 + }); 32 34 33 35 let typeErrorName: string | undefined = symbolErrorType?.placeholder; 34 36 if (!typeErrorName) { ··· 48 50 operation: IR.OperationObject; 49 51 plugin: PiniaColadaPlugin['Instance']; 50 52 }): string => { 51 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 52 - const symbolResponseType = plugin.getSymbol( 53 - pluginTypeScript.api.selector('response', operation.id), 54 - ); 53 + const symbolResponseType = plugin.querySymbol({ 54 + category: 'type', 55 + resource: 'operation', 56 + resourceId: operation.id, 57 + role: 'response', 58 + }); 55 59 return symbolResponseType?.placeholder || 'unknown'; 56 60 };
+4 -12
packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts
··· 10 10 export const handler: PluginHandler = ({ plugin }) => { 11 11 plugin.registerSymbol({ 12 12 external: plugin.name, 13 - meta: { 14 - kind: 'type', 15 - }, 13 + kind: 'type', 16 14 name: 'DefaultError', 17 15 selector: plugin.api.selector('DefaultError'), 18 16 }); 19 17 plugin.registerSymbol({ 20 18 external: plugin.name, 21 - meta: { 22 - kind: 'type', 23 - }, 19 + kind: 'type', 24 20 name: 'InfiniteData', 25 21 selector: plugin.api.selector('InfiniteData'), 26 22 }); ··· 32 28 : 'UseMutationOptions'; 33 29 plugin.registerSymbol({ 34 30 external: plugin.name, 35 - meta: { 36 - kind: 'type', 37 - }, 31 + kind: 'type', 38 32 name: mutationsType, 39 33 selector: plugin.api.selector('MutationOptions'), 40 34 }); ··· 55 49 }); 56 50 plugin.registerSymbol({ 57 51 external: 'axios', 58 - meta: { 59 - kind: 'type', 60 - }, 52 + kind: 'type', 61 53 name: 'AxiosError', 62 54 selector: plugin.api.selector('AxiosError'), 63 55 });
+1 -3
packages/openapi-ts/src/plugins/@tanstack/query-core/queryKey.ts
··· 316 316 ); 317 317 const symbolQueryKeyType = plugin.registerSymbol({ 318 318 exported: true, 319 - meta: { 320 - kind: 'type', 321 - }, 319 + kind: 'type', 322 320 name: 'QueryKey', 323 321 selector: plugin.api.selector('QueryKey'), 324 322 });
+12 -8
packages/openapi-ts/src/plugins/@tanstack/query-core/useType.ts
··· 24 24 plugin: PluginInstance; 25 25 }): string => { 26 26 const client = getClientPlugin(plugin.context.config); 27 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 28 27 29 - const symbolErrorType = plugin.getSymbol( 30 - pluginTypeScript.api.selector('error', operation.id), 31 - ); 28 + const symbolErrorType = plugin.querySymbol({ 29 + category: 'type', 30 + resource: 'operation', 31 + resourceId: operation.id, 32 + role: 'error', 33 + }); 32 34 33 35 let typeErrorName: string | undefined = symbolErrorType?.placeholder; 34 36 if (!typeErrorName) { ··· 49 51 operation: IR.OperationObject; 50 52 plugin: PluginInstance; 51 53 }): string => { 52 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 53 - const symbolResponseType = plugin.getSymbol( 54 - pluginTypeScript.api.selector('response', operation.id), 55 - ); 54 + const symbolResponseType = plugin.querySymbol({ 55 + category: 'type', 56 + resource: 'operation', 57 + resourceId: operation.id, 58 + role: 'response', 59 + }); 56 60 return symbolResponseType?.placeholder || 'unknown'; 57 61 };
+1 -1
packages/openapi-ts/src/plugins/arktype/v2/plugin.ts
··· 259 259 const typeInferSymbol = plugin.config.definitions.types.infer.enabled 260 260 ? plugin.registerSymbol({ 261 261 exported: true, 262 + kind: 'type', 262 263 meta: { 263 - kind: 'type', 264 264 path: state.path.value, 265 265 }, 266 266 name: buildName({
+20 -16
packages/openapi-ts/src/plugins/fastify/plugin.ts
··· 16 16 }): Property | undefined => { 17 17 const properties: Array<Property> = []; 18 18 19 - const pluginTypeScript = plugin.getPluginOrThrow('@hey-api/typescript'); 20 - const symbolDataType = plugin.getSymbol( 21 - pluginTypeScript.api.selector('data', operation.id), 22 - ); 19 + const symbolDataType = plugin.querySymbol({ 20 + category: 'type', 21 + resource: 'operation', 22 + resourceId: operation.id, 23 + role: 'data', 24 + }); 23 25 if (symbolDataType) { 24 26 if (operation.body) { 25 27 properties.push({ ··· 65 67 const { errors, responses } = operationResponsesMap(operation); 66 68 67 69 let errorsTypeReference: ts.TypeReferenceNode | undefined = undefined; 68 - const symbolErrorType = plugin.getSymbol( 69 - pluginTypeScript.api.selector('errors', operation.id), 70 - ); 70 + const symbolErrorType = plugin.querySymbol({ 71 + category: 'type', 72 + resource: 'operation', 73 + resourceId: operation.id, 74 + role: 'errors', 75 + }); 71 76 if (symbolErrorType && errors && errors.properties) { 72 77 const keys = Object.keys(errors.properties); 73 78 if (keys.length) { ··· 92 97 } 93 98 94 99 let responsesTypeReference: ts.TypeReferenceNode | undefined = undefined; 95 - const symbolResponseType = plugin.getSymbol( 96 - pluginTypeScript.api.selector('responses', operation.id), 97 - ); 100 + const symbolResponseType = plugin.querySymbol({ 101 + category: 'type', 102 + resource: 'operation', 103 + resourceId: operation.id, 104 + role: 'responses', 105 + }); 98 106 if (symbolResponseType && responses && responses.properties) { 99 107 const keys = Object.keys(responses.properties); 100 108 if (keys.length) { ··· 155 163 export const handler: FastifyPlugin['Handler'] = ({ plugin }) => { 156 164 plugin.registerSymbol({ 157 165 external: 'fastify', 158 - meta: { 159 - kind: 'type', 160 - }, 166 + kind: 'type', 161 167 name: 'RouteHandler', 162 168 selector: plugin.api.selector('RouteHandler'), 163 169 }); 164 170 165 171 const symbolRouteHandlers = plugin.registerSymbol({ 166 172 exported: true, 167 - meta: { 168 - kind: 'type', 169 - }, 173 + kind: 'type', 170 174 name: 'RouteHandlers', 171 175 }); 172 176
+12 -7
packages/openapi-ts/src/plugins/shared/utils/instance.ts
··· 2 2 3 3 import type { 4 4 IProject, 5 - Selector, 6 5 Symbol, 6 + SymbolIdentifier, 7 7 SymbolIn, 8 + SymbolMeta, 8 9 } from '@hey-api/codegen-core'; 9 10 10 11 import { HeyApiError } from '~/error'; ··· 280 281 return plugin as any; 281 282 } 282 283 283 - getSymbol(symbolIdOrSelector: number | Selector): Symbol | undefined { 284 - return this.gen.symbols.get(symbolIdOrSelector); 284 + getSymbol(identifier: SymbolIdentifier): Symbol | undefined { 285 + return this.gen.symbols.get(identifier); 285 286 } 286 287 287 288 hooks = { ··· 293 294 }, 294 295 }; 295 296 296 - isSymbolRegistered(symbolIdOrSelector: number | Selector): boolean { 297 - return this.gen.symbols.isRegistered(symbolIdOrSelector); 297 + isSymbolRegistered(identifier: SymbolIdentifier): boolean { 298 + return this.gen.symbols.isRegistered(identifier); 298 299 } 299 300 300 - referenceSymbol(symbolIdOrSelector: number | Selector): Symbol { 301 - return this.gen.symbols.reference(symbolIdOrSelector); 301 + querySymbol(filter: SymbolMeta): Symbol | undefined { 302 + return this.gen.symbols.query(filter)[0]; 303 + } 304 + 305 + referenceSymbol(identifier: SymbolIdentifier): Symbol { 306 + return this.gen.symbols.reference(identifier); 302 307 } 303 308 304 309 registerSymbol(symbol: SymbolIn): Symbol {
+1 -1
packages/openapi-ts/src/plugins/valibot/v1/plugin.ts
··· 235 235 export const handlerV1: ValibotPlugin['Handler'] = ({ plugin }) => { 236 236 plugin.registerSymbol({ 237 237 external: 'valibot', 238 - meta: { importKind: 'namespace' }, 238 + importKind: 'namespace', 239 239 name: 'v', 240 240 selector: plugin.api.selector('external', 'valibot.v'), 241 241 });
+2 -2
packages/openapi-ts/src/plugins/zod/mini/plugin.ts
··· 250 250 const typeInferSymbol = plugin.config.definitions.types.infer.enabled 251 251 ? plugin.registerSymbol({ 252 252 exported: true, 253 + kind: 'type', 253 254 meta: { 254 - kind: 'type', 255 255 path: state.path.value, 256 256 tags: state.tags?.value, 257 257 }, ··· 274 274 export const handlerMini: ZodPlugin['Handler'] = ({ plugin }) => { 275 275 plugin.registerSymbol({ 276 276 external: getZodModule({ plugin }), 277 - meta: { importKind: 'namespace' }, 277 + importKind: 'namespace', 278 278 name: 'z', 279 279 selector: plugin.api.selector('external', 'zod.z'), 280 280 });
+2 -2
packages/openapi-ts/src/plugins/zod/shared/operation.ts
··· 131 131 const typeInferSymbol = plugin.config.requests.types.infer.enabled 132 132 ? plugin.registerSymbol({ 133 133 exported: true, 134 + kind: 'type', 134 135 meta: { 135 - kind: 'type', 136 136 path: state.path.value, 137 137 tags: state.tags?.value, 138 138 }, ··· 174 174 const typeInferSymbol = plugin.config.responses.types.infer.enabled 175 175 ? plugin.registerSymbol({ 176 176 exported: true, 177 + kind: 'type', 177 178 meta: { 178 - kind: 'type', 179 179 path, 180 180 tags: state.tags?.value, 181 181 },
+1 -1
packages/openapi-ts/src/plugins/zod/shared/webhook.ts
··· 130 130 const typeInferSymbol = plugin.config.webhooks.types.infer.enabled 131 131 ? plugin.registerSymbol({ 132 132 exported: true, 133 + kind: 'type', 133 134 meta: { 134 - kind: 'type', 135 135 path: state.path.value, 136 136 tags: state.tags?.value, 137 137 },
+1 -1
packages/openapi-ts/src/plugins/zod/v3/plugin.ts
··· 224 224 const typeInferSymbol = plugin.config.definitions.types.infer.enabled 225 225 ? plugin.registerSymbol({ 226 226 exported: true, 227 + kind: 'type', 227 228 meta: { 228 - kind: 'type', 229 229 path: state.path.value, 230 230 tags: state.tags?.value, 231 231 },
+1 -1
packages/openapi-ts/src/plugins/zod/v4/plugin.ts
··· 252 252 const typeInferSymbol = plugin.config.definitions.types.infer.enabled 253 253 ? plugin.registerSymbol({ 254 254 exported: true, 255 + kind: 'type', 255 256 meta: { 256 - kind: 'type', 257 257 path: state.path.value, 258 258 tags: state.tags?.value, 259 259 },