Skip to content

Commit 1cfc8f2

Browse files
committed
refactor: replace plugin references with queries
1 parent a71d840 commit 1cfc8f2

File tree

47 files changed

+678
-401
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+678
-401
lines changed

.changeset/chubby-ends-follow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': patch
3+
---
4+
5+
refactor: replace plugin references with queries

.changeset/lucky-results-like.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/codegen-core': patch
3+
---
4+
5+
feat: add `.query()` method to symbol registry

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"cwd": "${workspaceFolder}/dev",
1717
"runtimeExecutable": "node",
1818
"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"
2020
}
2121
]
2222
}

dev/openapi-ts.config.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ export default defineConfig(() => {
138138
// if (!symbol.external && !symbol.meta?.resourceType) {
139139
// console.log(`[${plugin.name}]:`, symbol.name);
140140
// }
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);
143143
}
144144
// if (symbol.meta?.tags && symbol.meta?.tags.size > 0) {
145145
// console.log(
@@ -213,11 +213,11 @@ export default defineConfig(() => {
213213
// myClientPlugin(),
214214
{
215215
// baseUrl: false,
216-
exportFromIndex: true,
217-
// name: '@hey-api/client-fetch',
216+
// exportFromIndex: true,
217+
name: '@hey-api/client-fetch',
218218
// name: 'legacy/angular',
219219
// runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'),
220-
runtimeConfigPath: './src/hey-api.ts',
220+
// runtimeConfigPath: './src/hey-api.ts',
221221
// strictBaseUrl: true,
222222
// throwOnError: true,
223223
},
@@ -269,10 +269,10 @@ export default defineConfig(() => {
269269
// signature: 'object',
270270
// transformer: '@hey-api/transformers',
271271
// transformer: true,
272-
validator: {
273-
request: 'zod',
274-
response: 'zod',
275-
},
272+
// validator: {
273+
// request: 'zod',
274+
// response: 'zod',
275+
// },
276276
'~hooks': {
277277
symbols: {
278278
// getFilePath: (symbol) => {
@@ -346,7 +346,7 @@ export default defineConfig(() => {
346346
// definitions: 'z{{name}}Definition',
347347
exportFromIndex: true,
348348
// metadata: true,
349-
name: 'valibot',
349+
// name: 'valibot',
350350
// requests: {
351351
// case: 'PascalCase',
352352
// name: '{{name}}Data',
@@ -396,7 +396,7 @@ export default defineConfig(() => {
396396
},
397397
exportFromIndex: true,
398398
// metadata: true,
399-
name: 'zod',
399+
// name: 'zod',
400400
// requests: {
401401
// // case: 'SCREAMING_SNAKE_CASE',
402402
// // name: 'z{{name}}TestData',

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export default tseslint.config(
6464
'**/dist/',
6565
'**/node_modules/',
6666
'temp/',
67+
'dev/.gen/',
6768
'examples/openapi-ts-openai/src/client/**/*.ts',
6869
'packages/openapi-ts/src/legacy/handlebars/compiled/**/*.js',
6970
'packages/openapi-ts/src/legacy/handlebars/templates/**/*.hbs',

packages/codegen-core/src/__tests__/bindings.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,18 @@ const makeSymbol = (
6565
placeholder: string,
6666
meta: ISymbolMeta = {},
6767
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+
};
7580

7681
describe('createBinding', () => {
7782
it('creates a named binding by default', () => {

packages/codegen-core/src/__tests__/symbols.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,52 @@ describe('SymbolRegistry', () => {
9090
expect(registry.isRegistered(symRegistered.id)).toBe(true);
9191
});
9292

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+
93139
it('throws on invalid register or reference', () => {
94140
const registry = new SymbolRegistry();
95141
// Register with id that does not exist
@@ -103,4 +149,58 @@ describe('SymbolRegistry', () => {
103149
'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.',
104150
);
105151
});
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);
205+
});
106206
});

packages/codegen-core/src/bindings/utils.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@ export const createBinding = ({
1919
aliases: {},
2020
from: modulePath,
2121
};
22-
if (symbol.meta?.importKind) {
23-
if (symbol.meta.importKind === 'default') {
22+
if (symbol.importKind) {
23+
if (symbol.importKind === 'default') {
2424
binding.defaultBinding = symbol.placeholder;
25-
if (symbol.meta.kind === 'type') {
25+
if (symbol.kind === 'type') {
2626
binding.typeDefaultBinding = true;
2727
}
28-
} else if (symbol.meta.importKind === 'namespace') {
28+
} else if (symbol.importKind === 'namespace') {
2929
binding.namespaceBinding = symbol.placeholder;
30-
if (symbol.meta.kind === 'type') {
30+
if (symbol.kind === 'type') {
3131
binding.typeNamespaceBinding = true;
3232
}
3333
}
3434
}
3535
// default to named binding
3636
if (
37-
symbol.meta?.importKind === 'named' ||
37+
symbol.importKind === 'named' ||
3838
(!names.length && !binding.defaultBinding && !binding.namespaceBinding)
3939
) {
4040
let name = symbol.placeholder;
@@ -52,7 +52,7 @@ export const createBinding = ({
5252
}
5353
}
5454
names.push(name);
55-
if (symbol.meta?.kind === 'type') {
55+
if (symbol.kind === 'type') {
5656
typeNames.push(name);
5757
}
5858
}

packages/codegen-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ export { renderIds } from './renderer/utils';
1414
export type { ISelector as Selector } from './selectors/types';
1515
export type {
1616
ISymbolOut as Symbol,
17+
ISymbolIdentifier as SymbolIdentifier,
1718
ISymbolIn as SymbolIn,
1819
} from './symbols/types';

packages/codegen-core/src/selectors/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Selector array used to reference resources. We don't enforce
33
* uniqueness, but in practice it's desirable.
44
*
5+
* @deprecated
56
* @example ["zod", "#/components/schemas/Foo"]
67
*/
78
export type ISelector = ReadonlyArray<string>;

0 commit comments

Comments
 (0)