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 #2481 from hey-api/fix/graph-perf-2

fix(parser): cache parent to children nodes

authored by

Lubos and committed by
GitHub
091ca65f 6e88c9cd

+173 -143
+5
.changeset/khaki-rabbits-explain.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(parser): cache parent to children nodes
+3 -3
packages/openapi-ts-tests/main/test/openapi-ts.config.ts
··· 54 54 // }, 55 55 }, 56 56 logs: { 57 - // level: 'debug', 57 + level: 'debug', 58 58 path: './logs', 59 59 }, 60 60 // name: 'foo', ··· 80 80 // // '/^[A-Z]+ /v1//', 81 81 // ], 82 82 }, 83 - // orphans: false, 83 + // orphans: true, 84 84 // preserveOrder: true, 85 85 // schemas: { 86 86 // include: ['Foo'], ··· 190 190 // mutationOptions: { 191 191 // name: '{{name}}MO', 192 192 // }, 193 - // name: '@tanstack/react-query', 193 + name: '@tanstack/react-query', 194 194 // queryKeys: { 195 195 // name: '{{name}}QK', 196 196 // },
+46 -42
packages/openapi-ts-tests/main/test/performance.test.ts
··· 1 1 import path from 'node:path'; 2 2 3 - import { createClient } from '@hey-api/openapi-ts'; 4 - import { describe, expect, it } from 'vitest'; 3 + import { createClient, Logger } from '@hey-api/openapi-ts'; 4 + import { beforeEach, describe, expect, it } from 'vitest'; 5 5 6 - import { Performance } from '../../../openapi-ts/src/utils/logger'; 7 6 import { getSpecsPath } from '../../utils'; 8 7 9 8 const V3_SPEC_PATH = path.resolve(getSpecsPath(), 'v3.json'); ··· 13 12 path.resolve(__dirname, 'generated', name); 14 13 15 14 describe('performance', () => { 16 - it('creates client under 1500ms', async () => { 17 - Performance.clear(); 15 + beforeEach(() => { 16 + performance.clearMarks(); 17 + performance.clearMeasures(); 18 + }); 18 19 19 - await createClient({ 20 - input: V3_SPEC_PATH, 21 - logs: { 22 - level: 'silent', 20 + it('creates client under 1500ms', async () => { 21 + const logger = new Logger(); 22 + await createClient( 23 + { 24 + input: V3_SPEC_PATH, 25 + logs: { 26 + level: 'silent', 27 + }, 28 + output: toOutputPath('perf'), 29 + plugins: ['@hey-api/client-fetch'], 23 30 }, 24 - output: toOutputPath('perf'), 25 - plugins: ['@hey-api/client-fetch'], 26 - }); 27 - 28 - Performance.measure('createClient'); 29 - const measures = Performance.getEntriesByName('createClient'); 31 + logger, 32 + ); 30 33 31 - expect(measures[0]!.duration).toBeLessThanOrEqual(1500); 34 + const duration = logger.report()?.duration ?? 9999; 35 + expect(duration).toBeLessThanOrEqual(1500); 32 36 }); 33 37 34 38 it('parses spec under 1500ms', async () => { 35 - Performance.clear(); 36 - 37 - await createClient({ 38 - input: V3_SPEC_PATH, 39 - logs: { 40 - level: 'silent', 39 + const logger = new Logger(); 40 + await createClient( 41 + { 42 + input: V3_SPEC_PATH, 43 + logs: { 44 + level: 'silent', 45 + }, 46 + output: toOutputPath('perf'), 47 + plugins: ['@hey-api/client-fetch'], 41 48 }, 42 - output: toOutputPath('perf'), 43 - plugins: ['@hey-api/client-fetch'], 44 - }); 45 - 46 - Performance.measure('parser'); 47 - const measures = Performance.getEntriesByName('parser'); 49 + logger, 50 + ); 48 51 49 - expect(measures[0]!.duration).toBeLessThanOrEqual(1500); 52 + const duration = logger.report()?.duration ?? 9999; 53 + expect(duration).toBeLessThanOrEqual(1500); 50 54 }); 51 55 52 56 it('parses spec under 1500ms (experimental)', async () => { 53 - Performance.clear(); 54 - 55 - await createClient({ 56 - input: V3_1_X_SPEC_PATH, 57 - logs: { 58 - level: 'silent', 57 + const logger = new Logger(); 58 + await createClient( 59 + { 60 + input: V3_1_X_SPEC_PATH, 61 + logs: { 62 + level: 'silent', 63 + }, 64 + output: toOutputPath('perf'), 65 + plugins: ['@hey-api/client-fetch'], 59 66 }, 60 - output: toOutputPath('perf'), 61 - plugins: ['@hey-api/client-fetch'], 62 - }); 67 + logger, 68 + ); 63 69 64 - Performance.measure('parser'); 65 - const measures = Performance.getEntriesByName('parser'); 66 - 67 - expect(measures[0]!.duration).toBeLessThanOrEqual(1500); 70 + const duration = logger.report()?.duration ?? 9999; 71 + expect(duration).toBeLessThanOrEqual(1500); 68 72 }); 69 73 });
+3 -4
packages/openapi-ts/src/index.ts
··· 29 29 */ 30 30 export const createClient = async ( 31 31 userConfig?: Configs, 32 + logger = new Logger(), 32 33 ): Promise<ReadonlyArray<Client | IR.Context>> => { 33 34 const resolvedConfig = 34 35 typeof userConfig === 'function' ? await userConfig() : userConfig; ··· 38 39 try { 39 40 checkNodeVersion(); 40 41 41 - const logger = new Logger(); 42 42 const eventCreateClient = logger.timeEvent('createClient'); 43 43 44 44 const eventConfig = logger.timeEvent('config'); ··· 72 72 eventCreateClient.timeEnd(); 73 73 74 74 const config = configs[0]; 75 - if (config && config.logs.level === 'debug') { 76 - logger.report(); 77 - } 75 + logger.report(config && config.logs.level === 'debug'); 78 76 79 77 return result; 80 78 } catch (error) { ··· 134 132 export type { UserConfig } from './types/config'; 135 133 export type { LegacyIR } from './types/types'; 136 134 export { utils } from './utils/exports'; 135 + export { Logger } from './utils/logger';
+37 -58
packages/openapi-ts/src/openApi/shared/utils/graph.ts
··· 75 75 } 76 76 }; 77 77 78 + interface Cache { 79 + allDependencies: Map<string, Set<string>>; 80 + childDependencies: Map<string, Set<string>>; 81 + parentToChildren: Map<string, Array<string>>; 82 + } 83 + 78 84 /** 79 85 * Recursively collects all $ref dependencies in the subtree rooted at `pointer`. 80 86 */ ··· 84 90 pointer, 85 91 visited, 86 92 }: { 87 - cache: Map<string, Set<string>>; 93 + cache: Cache; 88 94 graph: Graph; 89 95 pointer: string; 90 96 visited: Set<string>; 91 97 }): Set<string> => { 92 - const cached = cache.get(pointer); 98 + const cached = cache.allDependencies.get(pointer); 93 99 if (cached) { 94 100 return cached; 95 101 } ··· 128 134 } 129 135 130 136 // Recursively collect dependencies of all children 131 - for (const [childPointer, childInfo] of graph.nodes) { 132 - if (childInfo.parentPointer === pointer) { 133 - const transitiveDependencies = collectAllDependenciesForPointer({ 134 - cache, 135 - graph, 136 - pointer: childPointer, 137 - visited, 138 - }); 137 + const children = cache.parentToChildren.get(pointer); 138 + if (children) { 139 + for (const childPointer of children) { 140 + let transitiveDependencies = cache.childDependencies.get(childPointer); 141 + if (!transitiveDependencies) { 142 + transitiveDependencies = collectAllDependenciesForPointer({ 143 + cache, 144 + graph, 145 + pointer: childPointer, 146 + visited, 147 + }); 148 + cache.childDependencies.set(childPointer, transitiveDependencies); 149 + } 139 150 for (const dep of transitiveDependencies) { 140 151 allDependencies.add(dep); 141 152 } 142 153 } 143 154 } 144 155 145 - cache.set(pointer, allDependencies); 156 + cache.allDependencies.set(pointer, allDependencies); 146 157 return allDependencies; 147 158 }; 148 159 ··· 462 473 path: [], 463 474 }); 464 475 476 + const cache: Cache = { 477 + allDependencies: new Map(), 478 + childDependencies: new Map(), 479 + parentToChildren: new Map(), 480 + }; 481 + 482 + for (const [pointer, nodeInfo] of graph.nodes) { 483 + const parent = nodeInfo.parentPointer; 484 + if (!parent) continue; 485 + if (!cache.parentToChildren.has(parent)) { 486 + cache.parentToChildren.set(parent, []); 487 + } 488 + cache.parentToChildren.get(parent)!.push(pointer); 489 + } 490 + 465 491 for (const [pointerFrom, pointers] of graph.dependencies) { 466 492 for (const pointerTo of pointers) { 467 493 if (!graph.reverseDependencies.has(pointerTo)) { ··· 475 501 propagateScopes(graph); 476 502 annotateChildScopes(graph.nodes); 477 503 478 - const cache = new Map<string, Set<string>>(); 479 504 for (const pointer of graph.nodes.keys()) { 480 505 const allDependencies = collectAllDependenciesForPointer({ 481 506 cache, ··· 495 520 496 521 return { graph }; 497 522 }; 498 - 499 - export const analyzeGraphStructure = (graph: Graph) => { 500 - let maxDepth = 0; 501 - let maxChildren = 0; 502 - 503 - const computeDepth = (pointer: string, depth: number): void => { 504 - maxDepth = Math.max(maxDepth, depth); 505 - 506 - const children = Array.from(graph.nodes.entries()) 507 - .filter(([, nodeInfo]) => nodeInfo.parentPointer === pointer) 508 - .map(([childPointer]) => childPointer); 509 - 510 - maxChildren = Math.max(maxChildren, children.length); 511 - 512 - for (const childPointer of children) { 513 - computeDepth(childPointer, depth + 1); 514 - } 515 - }; 516 - 517 - const totalNodes = graph.nodes.size; 518 - if (graph.nodes.has('#')) { 519 - computeDepth('#', 1); 520 - } 521 - 522 - return { maxChildren, maxDepth, totalNodes }; 523 - }; 524 - 525 - export const exportGraphForVisualization = (graph: Graph) => { 526 - const childrenMap = new Map<string, string[]>(); 527 - 528 - for (const [pointer, nodeInfo] of graph.nodes) { 529 - if (!nodeInfo.parentPointer) continue; 530 - if (!childrenMap.has(nodeInfo.parentPointer)) { 531 - childrenMap.set(nodeInfo.parentPointer, []); 532 - } 533 - childrenMap.get(nodeInfo.parentPointer)!.push(pointer); 534 - } 535 - 536 - const nodes = Array.from(graph.nodes.keys()).map((pointer) => ({ 537 - children: childrenMap.get(pointer)?.length ?? 0, 538 - childrenPointers: childrenMap.get(pointer) || [], 539 - pointer, 540 - })); 541 - 542 - return nodes; 543 - };
+47
packages/openapi-ts/src/openApi/shared/utils/graphDebug.ts
··· 1 + import type { Graph } from './graph'; 2 + 3 + export const analyzeGraphStructure = (graph: Graph) => { 4 + let maxDepth = 0; 5 + let maxChildren = 0; 6 + 7 + const computeDepth = (pointer: string, depth: number): void => { 8 + maxDepth = Math.max(maxDepth, depth); 9 + 10 + const children = Array.from(graph.nodes.entries()) 11 + .filter(([, nodeInfo]) => nodeInfo.parentPointer === pointer) 12 + .map(([childPointer]) => childPointer); 13 + 14 + maxChildren = Math.max(maxChildren, children.length); 15 + 16 + for (const childPointer of children) { 17 + computeDepth(childPointer, depth + 1); 18 + } 19 + }; 20 + 21 + const totalNodes = graph.nodes.size; 22 + if (graph.nodes.has('#')) { 23 + computeDepth('#', 1); 24 + } 25 + 26 + return { maxChildren, maxDepth, totalNodes }; 27 + }; 28 + 29 + export const exportGraphForVisualization = (graph: Graph) => { 30 + const childrenMap = new Map<string, string[]>(); 31 + 32 + for (const [pointer, nodeInfo] of graph.nodes) { 33 + if (!nodeInfo.parentPointer) continue; 34 + if (!childrenMap.has(nodeInfo.parentPointer)) { 35 + childrenMap.set(nodeInfo.parentPointer, []); 36 + } 37 + childrenMap.get(nodeInfo.parentPointer)!.push(pointer); 38 + } 39 + 40 + const nodes = Array.from(graph.nodes.keys()).map((pointer) => ({ 41 + children: childrenMap.get(pointer)?.length ?? 0, 42 + childrenPointers: childrenMap.get(pointer) || [], 43 + pointer, 44 + })); 45 + 46 + return nodes; 47 + };
+32 -36
packages/openapi-ts/src/utils/logger.ts
··· 3 3 interface LoggerEvent { 4 4 end?: PerformanceMark; 5 5 events: Array<LoggerEvent>; 6 + id: string; // unique internal key 6 7 name: string; 7 8 start: PerformanceMark; 8 9 } ··· 16 17 position: ReadonlyArray<number>; 17 18 } 18 19 20 + let loggerCounter = 0; 21 + const nameToId = (name: string) => `${name}-${loggerCounter++}`; 19 22 const idEnd = (id: string) => `${id}-end`; 20 - 21 23 const idLength = (id: string) => `${id}-length`; 22 - 23 24 const idStart = (id: string) => `${id}-start`; 24 25 25 - export const Performance = { 26 - clear: (): void => { 27 - performance.clearMarks(); 28 - performance.clearMeasures(); 29 - }, 30 - end: (id: string): PerformanceMark => performance.mark(idEnd(id)), 31 - getEntriesByName: (id: string): PerformanceEntryList => 32 - performance.getEntriesByName(idLength(id)), 33 - measure: (id: string): PerformanceMeasure => 34 - performance.measure(idLength(id), idStart(id), idEnd(id)), 35 - start: (id: string): PerformanceMark => performance.mark(idStart(id)), 36 - }; 37 - 38 26 const getSeverity = ( 39 27 duration: number, 40 28 percentage: number, ··· 79 67 } 80 68 } 81 69 if (event && !event.end) { 82 - event.end = performance.mark(idEnd(event.name)); 70 + event.end = performance.mark(idEnd(event.id)); 83 71 } 84 72 } 85 73 86 - report() { 74 + report(print: boolean = true): PerformanceMeasure | undefined { 87 75 const firstEvent = this.events[0]; 88 76 if (!firstEvent) return; 89 77 const lastEvent = this.events[this.events.length - 1]!; 78 + const name = 'root'; 79 + const id = nameToId(name); 90 80 const measure = performance.measure( 91 - idLength('root'), 92 - idStart(firstEvent.name), 93 - idEnd(lastEvent.name), 81 + idLength(id), 82 + idStart(firstEvent.id), 83 + idEnd(lastEvent.id), 94 84 ); 95 - this.reportEvent({ 96 - end: lastEvent.end, 97 - events: this.events, 98 - indent: 0, 99 - measure, 100 - name: 'root', 101 - start: firstEvent!.start, 102 - }); 85 + if (print) { 86 + this.reportEvent({ 87 + end: lastEvent.end, 88 + events: this.events, 89 + id, 90 + indent: 0, 91 + measure, 92 + name, 93 + start: firstEvent!.start, 94 + }); 95 + } 96 + return measure; 103 97 } 104 98 105 99 private reportEvent({ ··· 114 108 115 109 parent.events.forEach((event, index) => { 116 110 const measure = performance.measure( 117 - idLength(event.name), 118 - idStart(event.name), 119 - idEnd(event.name), 111 + idLength(event.id), 112 + idStart(event.id), 113 + idEnd(event.id), 120 114 ); 121 115 const duration = Math.ceil(measure.duration * 100) / 100; 122 116 const percentage = ··· 131 125 132 126 const branch = index === lastIndex ? '└─ ' : '├─ '; 133 127 const prefix = !indent ? '' : '│ '.repeat(indent - 1) + branch; 134 - const maxLength = 30 - prefix.length; 128 + const maxLength = 38 - prefix.length; 135 129 136 130 const percentageBranch = !indent ? '' : '↳ '; 137 131 const percentagePrefix = indent ··· 151 145 }); 152 146 } 153 147 154 - private start(name: string): PerformanceMark { 155 - return performance.mark(idStart(name)); 148 + private start(id: string): PerformanceMark { 149 + return performance.mark(idStart(id)); 156 150 } 157 151 158 152 private storeEvent({ 159 153 result, 160 154 ...event 161 - }: Pick<LoggerEvent, 'events' | 'name' | 'start'> & { 155 + }: Pick<LoggerEvent, 'events' | 'id' | 'name' | 'start'> & { 162 156 result: StoredEventResult; 163 157 }): void { 164 158 const lastEventIndex = event.events.length - 1; ··· 173 167 } 174 168 175 169 timeEvent(name: string) { 176 - const start = this.start(name); 170 + const id = nameToId(name); 171 + const start = this.start(id); 177 172 const event: LoggerEvent = { 178 173 events: this.events, 174 + id, 179 175 name, 180 176 start, 181 177 };