From 28db76f25ba1290198b3e51e70cd926023ea685a Mon Sep 17 00:00:00 2001 From: SukkaW Date: Tue, 19 May 2026 23:06:17 +0800 Subject: [PATCH 01/11] perf(shared): simplify object iteration --- .../shared/src/openApi/2.0.x/parser/schema.ts | 8 ++- .../shared/src/openApi/3.0.x/parser/schema.ts | 11 ++-- .../shared/src/openApi/3.1.x/parser/schema.ts | 52 +++++++++++-------- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/packages/shared/src/openApi/2.0.x/parser/schema.ts b/packages/shared/src/openApi/2.0.x/parser/schema.ts index 2c0c366437..2fec2aa27c 100644 --- a/packages/shared/src/openApi/2.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/2.0.x/parser/schema.ts @@ -200,7 +200,11 @@ function parseObject({ const schemaProperties: Record = {}; + let isSchemaPropertiesEmpty = true; + for (const name in schema.properties) { + isSchemaPropertiesEmpty = false; + const property = schema.properties[name]!; if (typeof property === 'boolean') { // TODO: parser - handle boolean properties @@ -214,7 +218,7 @@ function parseObject({ } } - if (Object.keys(schemaProperties).length) { + if (!isSchemaPropertiesEmpty) { irSchema.properties = schemaProperties; } @@ -230,7 +234,7 @@ function parseObject({ const isEmptyObjectInAllOf = state.inAllOf && schema.additionalProperties === false && - (!schema.properties || !Object.keys(schema.properties).length); + (!schema.properties || isSchemaPropertiesEmpty); if (!isEmptyObjectInAllOf) { irSchema.additionalProperties = { diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index 14d19ce4dd..0ec2c3ca06 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -142,7 +142,8 @@ function getAllDiscriminatorValues({ const values: Array = []; // Check each entry in the discriminator mapping - for (const [value, mappedSchemaRef] of Object.entries(discriminator.mapping || {})) { + for (const value in discriminator.mapping) { + const mappedSchemaRef = discriminator.mapping[value]!; if (mappedSchemaRef === schemaRef) { // This is the current schema's own value values.push(value); @@ -330,7 +331,11 @@ function parseObject({ const schemaProperties: Record = {}; + let isSchemaPropertiesEmpty = true; + for (const name in schema.properties) { + isSchemaPropertiesEmpty = false; + const property = schema.properties[name]!; if (typeof property === 'boolean') { // TODO: parser - handle boolean properties @@ -343,7 +348,7 @@ function parseObject({ } } - if (Object.keys(schemaProperties).length) { + if (!isSchemaPropertiesEmpty) { irSchema.properties = schemaProperties; } @@ -359,7 +364,7 @@ function parseObject({ const isEmptyObjectInAllOf = state.inAllOf && schema.additionalProperties === false && - (!schema.properties || !Object.keys(schema.properties).length); + (!schema.properties || isSchemaPropertiesEmpty); if (!isEmptyObjectInAllOf) { irSchema.additionalProperties = { diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index 5ad794f63a..bbcce9ef9a 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -156,7 +156,8 @@ function getAllDiscriminatorValues({ const values: Array = []; // Check each entry in the discriminator mapping - for (const [value, mappedSchemaRef] of Object.entries(discriminator.mapping || {})) { + for (const value in discriminator.mapping) { + const mappedSchemaRef = discriminator.mapping[value]!; if (mappedSchemaRef === schemaRef) { // This is the current schema's own value values.push(value); @@ -398,7 +399,11 @@ function parseObject({ const schemaProperties: Record = {}; + let isSchemaPropertiesEmpty = true; + for (const name in schema.properties) { + isSchemaPropertiesEmpty = false; + const property = schema.properties[name]!; if (typeof property === 'boolean') { // TODO: parser - handle boolean properties @@ -412,10 +417,31 @@ function parseObject({ } } - if (Object.keys(schemaProperties).length) { + if (!isSchemaPropertiesEmpty) { irSchema.properties = schemaProperties; } + let isPatternPropertiesEmpty = true; + + if (schema.patternProperties) { + const patternProperties: Record = {}; + + for (const pattern in schema.patternProperties) { + const patternSchema = schema.patternProperties[pattern]!; + const irPatternSchema = schemaToIrSchema({ + context, + schema: patternSchema, + state, + }); + patternProperties[pattern] = irPatternSchema; + isPatternPropertiesEmpty = false; + } + + if (!isPatternPropertiesEmpty) { + irSchema.patternProperties = patternProperties; + } + } + if (schema.additionalProperties === undefined) { if (!irSchema.properties) { irSchema.additionalProperties = { @@ -428,8 +454,8 @@ function parseObject({ const isEmptyObjectInAllOf = state.inAllOf && schema.additionalProperties === false && - (!schema.properties || !Object.keys(schema.properties).length) && - (!schema.patternProperties || !Object.keys(schema.patternProperties).length); + (!schema.properties || isSchemaPropertiesEmpty) && + (!schema.patternProperties || isPatternPropertiesEmpty); if (!isEmptyObjectInAllOf) { irSchema.additionalProperties = { @@ -445,24 +471,6 @@ function parseObject({ irSchema.additionalProperties = irAdditionalPropertiesSchema; } - if (schema.patternProperties) { - const patternProperties: Record = {}; - - for (const pattern in schema.patternProperties) { - const patternSchema = schema.patternProperties[pattern]!; - const irPatternSchema = schemaToIrSchema({ - context, - schema: patternSchema, - state, - }); - patternProperties[pattern] = irPatternSchema; - } - - if (Object.keys(patternProperties).length) { - irSchema.patternProperties = patternProperties; - } - } - if (schema.propertyNames) { irSchema.propertyNames = schemaToIrSchema({ context, From 4943cd204d034f817bb2b6d9cd88f282926d3f0c Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 00:42:48 +0800 Subject: [PATCH 02/11] perf(shared): hoist ir pattern regexps --- packages/shared/src/ir/graph.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/ir/graph.ts b/packages/shared/src/ir/graph.ts index b2df820046..3e55667554 100644 --- a/packages/shared/src/ir/graph.ts +++ b/packages/shared/src/ir/graph.ts @@ -11,6 +11,15 @@ export const irTopLevelKinds = [ export type IrTopLevelKind = (typeof irTopLevelKinds)[number]; +const irPatterns: Record = { + operation: /^#\/paths\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, + parameter: /^#\/components\/parameters\/[^/]+$/, + requestBody: /^#\/components\/requestBodies\/[^/]+$/, + schema: /^#\/components\/schemas\/[^/]+$/, + server: /^#\/servers\/(\d+|[^/]+)$/, + webhook: /^#\/webhooks\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, +}; + /** * Checks if a pointer matches a known top-level IR component (schema, parameter, etc) and returns match info. * @@ -19,21 +28,12 @@ export type IrTopLevelKind = (typeof irTopLevelKinds)[number]; * @returns { matched: true, kind: IrTopLevelKind } | { matched: false } - Whether it matched, and the matched kind if so */ export const matchIrPointerToGroup: MatchPointerToGroupFn = (pointer, kind) => { - const patterns: Record = { - operation: /^#\/paths\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, - parameter: /^#\/components\/parameters\/[^/]+$/, - requestBody: /^#\/components\/requestBodies\/[^/]+$/, - schema: /^#\/components\/schemas\/[^/]+$/, - server: /^#\/servers\/(\d+|[^/]+)$/, - webhook: /^#\/webhooks\/[^/]+\/(get|put|post|delete|options|head|patch|trace)$/, - }; if (kind) { - return patterns[kind].test(pointer) ? { kind, matched: true } : { matched: false }; + return irPatterns[kind].test(pointer) ? { kind, matched: true } : { matched: false }; } - for (const key of Object.keys(patterns)) { - const kind = key as IrTopLevelKind; - if (patterns[kind].test(pointer)) { - return { kind, matched: true }; + for (const key of irTopLevelKinds) { + if (irPatterns[key].test(pointer)) { + return { kind: key, matched: true }; } } return { matched: false }; From ec0928f53aef9f1158717c94555980ec48560617 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 00:46:05 +0800 Subject: [PATCH 03/11] perf(shared): avoid iterators in `parseEnum` --- packages/shared/src/openApi/2.0.x/parser/schema.ts | 10 +++++++--- packages/shared/src/openApi/3.0.x/parser/schema.ts | 10 +++++++--- packages/shared/src/openApi/3.1.x/parser/schema.ts | 10 +++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/shared/src/openApi/2.0.x/parser/schema.ts b/packages/shared/src/openApi/2.0.x/parser/schema.ts index 2fec2aa27c..7aeb334f7c 100644 --- a/packages/shared/src/openApi/2.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/2.0.x/parser/schema.ts @@ -463,8 +463,12 @@ function parseEnum({ irSchema.type = 'enum'; const schemaItems: Array = []; + const xEnumDescriptions = schema['x-enum-descriptions']; + const xEnumVarnames = schema['x-enum-varnames']; + const xEnumNames = schema['x-enumNames']; - for (const [index, enumValue] of schema.enum.entries()) { + for (let index = 0, len = schema.enum.length; index < len; index++) { + const enumValue = schema.enum[index]; const typeOfEnumValue = typeof enumValue; let enumType: SchemaType | 'null' | undefined; @@ -496,8 +500,8 @@ function parseEnum({ const irTypeSchema = parseOneType({ context, schema: { - description: schema['x-enum-descriptions']?.[index], - title: schema['x-enum-varnames']?.[index] ?? schema['x-enumNames']?.[index], + description: xEnumDescriptions?.[index], + title: xEnumVarnames?.[index] ?? xEnumNames?.[index], // cast enum to string temporarily type: enumType === 'null' ? 'string' : enumType, }, diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index 0ec2c3ca06..873ef7e635 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -876,8 +876,12 @@ function parseEnum({ irSchema.type = 'enum'; const schemaItems: Array = []; + const xEnumDescriptions = schema['x-enum-descriptions']; + const xEnumVarnames = schema['x-enum-varnames']; + const xEnumNames = schema['x-enumNames']; - for (const [index, enumValue] of schema.enum.entries()) { + for (let index = 0, len = schema.enum.length; index < len; index++) { + const enumValue = schema.enum[index]; const typeOfEnumValue = typeof enumValue; let enumType: SchemaType | 'null' | undefined; @@ -909,8 +913,8 @@ function parseEnum({ const irTypeSchema = parseOneType({ context, schema: { - description: schema['x-enum-descriptions']?.[index], - title: schema['x-enum-varnames']?.[index] ?? schema['x-enumNames']?.[index], + description: xEnumDescriptions?.[index], + title: xEnumVarnames?.[index] ?? xEnumNames?.[index], // cast enum to string temporarily type: enumType === 'null' ? 'string' : enumType, }, diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index bbcce9ef9a..1132710c58 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -964,8 +964,12 @@ function parseEnum({ const schemaItems: Array = []; const schemaTypes = getSchemaTypes({ schema }); + const xEnumDescriptions = schema['x-enum-descriptions']; + const xEnumVarnames = schema['x-enum-varnames']; + const xEnumNames = schema['x-enumNames']; - for (const [index, enumValue] of schema.enum.entries()) { + for (let index = 0, len = schema.enum.length; index < len; index++) { + const enumValue = schema.enum[index]; const typeOfEnumValue = typeof enumValue; let enumType: SchemaType | undefined; @@ -998,8 +1002,8 @@ function parseEnum({ context, schema: { const: enumValue, - description: schema['x-enum-descriptions']?.[index], - title: schema['x-enum-varnames']?.[index] ?? schema['x-enumNames']?.[index], + description: xEnumDescriptions?.[index], + title: xEnumVarnames?.[index] ?? xEnumNames?.[index], type: enumType, }, state, From 29b7d1506224918327efeeb90d53539bd6890a93 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 00:48:11 +0800 Subject: [PATCH 04/11] perf(shared): fast path for is top level check --- packages/shared/src/utils/ref.ts | 35 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/shared/src/utils/ref.ts b/packages/shared/src/utils/ref.ts index f8a6d806e1..b20b856d2a 100644 --- a/packages/shared/src/utils/ref.ts +++ b/packages/shared/src/utils/ref.ts @@ -98,18 +98,35 @@ export function pathToJsonPointer(path: ReadonlyArray): string * @returns true if the ref points to a top-level component, false otherwise */ export function isTopLevelComponent(refOrPath: string | ReadonlyArray): boolean { - const path = refOrPath instanceof Array ? refOrPath : jsonPointerToPath(refOrPath); - - // OpenAPI 3.x: #/components/{type}/{name} = 3 segments - if (path[0] === 'components') { - return path.length === 3; + if (typeof refOrPath !== 'string') { + // OpenAPI 3.x: #/components/{type}/{name} = 3 segments + if (refOrPath[0] === 'components') { + return refOrPath.length === 3; + } + // OpenAPI 2.0: #/definitions/{name} = 2 segments + if (refOrPath[0] === 'definitions') { + return refOrPath.length === 2; + } + return false; } - // OpenAPI 2.0: #/definitions/{name} = 2 segments - if (path[0] === 'definitions') { - return path.length === 2; + // OpenAPI 3.x: #/components/{type}/{name} — exactly one slash after the type segment + if (refOrPath.startsWith('#/components/')) { + // '#/components/'.length === 13 + const typeEnd = refOrPath.indexOf('/', 13); + if (typeEnd === -1) { + // there is no slash after the type segment, missing name segment + return false; + } + const nameStart = typeEnd + 1; + return nameStart < refOrPath.length && refOrPath.indexOf('/', nameStart) === -1; + } + // OpenAPI 2.0: #/definitions/{name} — no slash after the name + if (refOrPath.startsWith('#/definitions/')) { + // '#/definitions/'.length === 14 + const nameStart = 14; + return nameStart < refOrPath.length && refOrPath.indexOf('/', nameStart) === -1; } - return false; } From 0353bd5e684190f0861ffc34afa75b818d3aeb28 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 01:04:01 +0800 Subject: [PATCH 05/11] perf(codegen-core): replace copy via spread w/ inline recursive --- packages/codegen-core/src/symbols/registry.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/codegen-core/src/symbols/registry.ts b/packages/codegen-core/src/symbols/registry.ts index 57f40d3f81..8d8e826b57 100644 --- a/packages/codegen-core/src/symbols/registry.ts +++ b/packages/codegen-core/src/symbols/registry.ts @@ -80,12 +80,15 @@ export class SymbolRegistry implements ISymbolRegistry { } } - private buildIndexKeySpace(meta: ISymbolMeta, prefix = ''): IndexKeySpace { - const entries: Array = []; + private buildIndexKeySpace( + meta: ISymbolMeta, + prefix = '', + entries: Array = [], + ): IndexKeySpace { for (const [key, value] of Object.entries(meta)) { const path = prefix ? `${prefix}.${key}` : key; if (value && typeof value === 'object' && !Array.isArray(value)) { - entries.push(...this.buildIndexKeySpace(value as ISymbolMeta, path)); + this.buildIndexKeySpace(value as ISymbolMeta, path, entries); } else { entries.push([path, value, `${path}:${JSON.stringify(value)}`]); } From 846bf59cd5786aea80323cf46c86164033dd22f2 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 01:04:35 +0800 Subject: [PATCH 06/11] perf(shared): fast path for `additionalProperties` only object --- .../shared/src/openApi/2.0.x/parser/schema.ts | 36 ++++++++++--------- .../shared/src/openApi/3.0.x/parser/schema.ts | 34 +++++++++--------- .../shared/src/openApi/3.1.x/parser/schema.ts | 36 ++++++++++--------- 3 files changed, 56 insertions(+), 50 deletions(-) diff --git a/packages/shared/src/openApi/2.0.x/parser/schema.ts b/packages/shared/src/openApi/2.0.x/parser/schema.ts index 7aeb334f7c..55f539d04c 100644 --- a/packages/shared/src/openApi/2.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/2.0.x/parser/schema.ts @@ -198,28 +198,30 @@ function parseObject({ }): IR.SchemaObject { irSchema.type = 'object'; - const schemaProperties: Record = {}; - let isSchemaPropertiesEmpty = true; - for (const name in schema.properties) { - isSchemaPropertiesEmpty = false; + if (schema.properties) { + const schemaProperties: Record = {}; - const property = schema.properties[name]!; - if (typeof property === 'boolean') { - // TODO: parser - handle boolean properties - } else { - const irPropertySchema = schemaToIrSchema({ - context, - schema: property, - state, - }); - schemaProperties[name] = irPropertySchema; + for (const name in schema.properties) { + isSchemaPropertiesEmpty = false; + + const property = schema.properties[name]!; + if (typeof property === 'boolean') { + // TODO: parser - handle boolean properties + } else { + const irPropertySchema = schemaToIrSchema({ + context, + schema: property, + state, + }); + schemaProperties[name] = irPropertySchema; + } } - } - if (!isSchemaPropertiesEmpty) { - irSchema.properties = schemaProperties; + if (!isSchemaPropertiesEmpty) { + irSchema.properties = schemaProperties; + } } if (schema.additionalProperties === undefined) { diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index 873ef7e635..c3c8266eab 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -329,27 +329,29 @@ function parseObject({ }): IR.SchemaObject { irSchema.type = 'object'; - const schemaProperties: Record = {}; - let isSchemaPropertiesEmpty = true; - for (const name in schema.properties) { - isSchemaPropertiesEmpty = false; + if (schema.properties) { + const schemaProperties: Record = {}; - const property = schema.properties[name]!; - if (typeof property === 'boolean') { - // TODO: parser - handle boolean properties - } else { - schemaProperties[name] = schemaToIrSchema({ - context, - schema: property, - state, - }); + for (const name in schema.properties) { + isSchemaPropertiesEmpty = false; + + const property = schema.properties[name]!; + if (typeof property === 'boolean') { + // TODO: parser - handle boolean properties + } else { + schemaProperties[name] = schemaToIrSchema({ + context, + schema: property, + state, + }); + } } - } - if (!isSchemaPropertiesEmpty) { - irSchema.properties = schemaProperties; + if (!isSchemaPropertiesEmpty) { + irSchema.properties = schemaProperties; + } } if (schema.additionalProperties === undefined) { diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index 1132710c58..0ae5468cb2 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -397,28 +397,30 @@ function parseObject({ }): IR.SchemaObject { irSchema.type = 'object'; - const schemaProperties: Record = {}; - let isSchemaPropertiesEmpty = true; - for (const name in schema.properties) { - isSchemaPropertiesEmpty = false; + if (schema.properties) { + const schemaProperties: Record = {}; - const property = schema.properties[name]!; - if (typeof property === 'boolean') { - // TODO: parser - handle boolean properties - } else { - const irPropertySchema = schemaToIrSchema({ - context, - schema: property, - state, - }); - schemaProperties[name] = irPropertySchema; + for (const name in schema.properties) { + isSchemaPropertiesEmpty = false; + + const property = schema.properties[name]!; + if (typeof property === 'boolean') { + // TODO: parser - handle boolean properties + } else { + const irPropertySchema = schemaToIrSchema({ + context, + schema: property, + state, + }); + schemaProperties[name] = irPropertySchema; + } } - } - if (!isSchemaPropertiesEmpty) { - irSchema.properties = schemaProperties; + if (!isSchemaPropertiesEmpty) { + irSchema.properties = schemaProperties; + } } let isPatternPropertiesEmpty = true; From bd39845a79b1a15e523a23547bc06b0721eeb6c1 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 01:05:38 +0800 Subject: [PATCH 07/11] perf(shared): avoid `Set` allocation when possible --- .../shared/src/openApi/shared/utils/graph.ts | 78 +++++++++---------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/packages/shared/src/openApi/shared/utils/graph.ts b/packages/shared/src/openApi/shared/utils/graph.ts index d768dd4881..369f63ff92 100644 --- a/packages/shared/src/openApi/shared/utils/graph.ts +++ b/packages/shared/src/openApi/shared/utils/graph.ts @@ -32,13 +32,13 @@ export const annotateChildScopes = (nodes: Graph['nodes']): void => { interface Cache { parentToChildren: Map>; - subtreeDependencies: Map>; - transitiveDependencies: Map>; + subtreeDependencies: Map | null>; + transitiveDependencies: Map | null>; } type PointerDependenciesResult = { - subtreeDependencies: Set; - transitiveDependencies: Set; + subtreeDependencies: Set | null; + transitiveDependencies: Set | null; }; /** @@ -55,32 +55,25 @@ const collectPointerDependencies = ({ pointer: string; visited: Set; }): PointerDependenciesResult => { - const cached = cache.transitiveDependencies.get(pointer); - if (cached) { + if (cache.transitiveDependencies.has(pointer)) { return { - subtreeDependencies: cache.subtreeDependencies.get(pointer)!, - transitiveDependencies: cached, + subtreeDependencies: cache.subtreeDependencies.get(pointer) ?? null, + transitiveDependencies: cache.transitiveDependencies.get(pointer) ?? null, }; } if (visited.has(pointer)) { - return { - subtreeDependencies: new Set(), - transitiveDependencies: new Set(), - }; + return { subtreeDependencies: null, transitiveDependencies: null }; } visited.add(pointer); const nodeInfo = graph.nodes.get(pointer); if (!nodeInfo) { - return { - subtreeDependencies: new Set(), - transitiveDependencies: new Set(), - }; + return { subtreeDependencies: null, transitiveDependencies: null }; } - const transitiveDependencies = new Set(); - const subtreeDependencies = new Set(); + let transitiveDependencies: Set | null = null; + let subtreeDependencies: Set | null = null; // Add direct $ref dependencies for this node // (from the dependencies map, or by checking nodeInfo.node directly) @@ -88,8 +81,8 @@ const collectPointerDependencies = ({ const nodeDependencies = graph.nodeDependencies.get(pointer); if (nodeDependencies) { for (const depPointer of nodeDependencies) { - transitiveDependencies.add(depPointer); - subtreeDependencies.add(depPointer); + (transitiveDependencies ??= new Set()).add(depPointer); + (subtreeDependencies ??= new Set()).add(depPointer); // Recursively collect dependencies of the referenced node const depResult = collectPointerDependencies({ cache, @@ -97,42 +90,43 @@ const collectPointerDependencies = ({ pointer: depPointer, visited, }); - for (const dependency of depResult.transitiveDependencies) { - transitiveDependencies.add(dependency); + if (depResult.transitiveDependencies) { + for (const dependency of depResult.transitiveDependencies) { + transitiveDependencies.add(dependency); + } } } } const children = cache.parentToChildren.get(pointer) ?? []; for (const childPointer of children) { - let childResult: Partial = { - subtreeDependencies: cache.subtreeDependencies.get(childPointer), - transitiveDependencies: cache.transitiveDependencies.get(childPointer), - }; - if (!childResult.subtreeDependencies || !childResult.transitiveDependencies) { - childResult = collectPointerDependencies({ + if (!cache.transitiveDependencies.has(childPointer)) { + const childResult = collectPointerDependencies({ cache, graph, pointer: childPointer, visited, }); - cache.transitiveDependencies.set(childPointer, childResult.transitiveDependencies!); - cache.subtreeDependencies.set(childPointer, childResult.subtreeDependencies!); + cache.transitiveDependencies.set(childPointer, childResult.transitiveDependencies); + cache.subtreeDependencies.set(childPointer, childResult.subtreeDependencies); } - for (const dependency of childResult.transitiveDependencies!) { - transitiveDependencies.add(dependency); + const childTransitive = cache.transitiveDependencies.get(childPointer) ?? null; + const childSubtree = cache.subtreeDependencies.get(childPointer) ?? null; + if (childTransitive) { + for (const dependency of childTransitive) { + (transitiveDependencies ??= new Set()).add(dependency); + } } - for (const dependency of childResult.subtreeDependencies!) { - subtreeDependencies.add(dependency); + if (childSubtree) { + for (const dependency of childSubtree) { + (subtreeDependencies ??= new Set()).add(dependency); + } } } cache.transitiveDependencies.set(pointer, transitiveDependencies); cache.subtreeDependencies.set(pointer, subtreeDependencies); - return { - subtreeDependencies, - transitiveDependencies, - }; + return { subtreeDependencies, transitiveDependencies }; }; /** @@ -360,8 +354,12 @@ export function buildGraph( pointer, visited: new Set(), }); - graph.transitiveDependencies.set(pointer, result.transitiveDependencies); - graph.subtreeDependencies.set(pointer, result.subtreeDependencies); + if (result.transitiveDependencies) { + graph.transitiveDependencies.set(pointer, result.transitiveDependencies); + } + if (result.subtreeDependencies) { + graph.subtreeDependencies.set(pointer, result.subtreeDependencies); + } } eventBuildGraph.timeEnd(); From a6b13e39b04439944a89d5eb459268a5e8d33bdd Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 01:41:43 +0800 Subject: [PATCH 08/11] perf(shared): fast path for hot path `jsonPointerToPath` --- packages/shared/src/utils/ref.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/shared/src/utils/ref.ts b/packages/shared/src/utils/ref.ts index b20b856d2a..7b10034b73 100644 --- a/packages/shared/src/utils/ref.ts +++ b/packages/shared/src/utils/ref.ts @@ -45,6 +45,10 @@ export function jsonPointerToPath(pointer: string): ReadonlyArray { if (!clean) { return []; } + // fast path: if the pointer doesn't contain '~', we can skip the decoding step entirely + if (!clean.includes('~')) { + return clean.split('/'); + } return clean.split('/').map((part) => part.replaceAll('~1', '/').replaceAll('~0', '~')); } From 538b8563cf7e83212fded9c432393196057dc109 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 03:52:42 +0800 Subject: [PATCH 09/11] perf(shared): avoid delete and spread --- packages/shared/src/ir/schema.ts | 19 +++++++++---------- .../shared/src/openApi/2.0.x/parser/schema.ts | 3 --- .../shared/src/openApi/3.0.x/parser/schema.ts | 3 --- .../shared/src/openApi/3.1.x/parser/schema.ts | 3 --- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/packages/shared/src/ir/schema.ts b/packages/shared/src/ir/schema.ts index d252adf2e1..c4f5dcb02d 100644 --- a/packages/shared/src/ir/schema.ts +++ b/packages/shared/src/ir/schema.ts @@ -64,23 +64,22 @@ export function deduplicateSchema({ uniqueItems.push(item); } - let result = { ...schema }; - result.items = uniqueItems; + const result = { ...schema }; if ( - result.items.length <= 1 && + uniqueItems.length <= 1 && result.type !== 'array' && result.type !== 'enum' && result.type !== 'tuple' ) { // bring the only item up to clean up the schema - const liftedSchema = result.items[0]; - delete result.logicalOperator; - delete result.items; - result = { - ...result, - ...liftedSchema, - }; + const liftedSchema = uniqueItems[0]; + result.items = undefined; + result.logicalOperator = undefined; + + Object.assign(result, liftedSchema); + } else { + result.items = uniqueItems; } // exclude unknown if it's the only type left diff --git a/packages/shared/src/openApi/2.0.x/parser/schema.ts b/packages/shared/src/openApi/2.0.x/parser/schema.ts index 55f539d04c..ea24b05724 100644 --- a/packages/shared/src/openApi/2.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/2.0.x/parser/schema.ts @@ -324,9 +324,6 @@ function parseAllOf({ state, }); state.inAllOf = originalInAllOf; - if (state.inAllOf === undefined) { - delete state.inAllOf; - } if (schema.required) { if (irCompositionSchema.required) { diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index c3c8266eab..ad130584aa 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -493,9 +493,6 @@ function parseAllOf({ state, }); state.inAllOf = originalInAllOf; - if (state.inAllOf === undefined) { - delete state.inAllOf; - } if (schema.required) { if (irCompositionSchema.required) { diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index 0ae5468cb2..626dd8546a 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -592,9 +592,6 @@ function parseAllOf({ state, }); state.inAllOf = originalInAllOf; - if (state.inAllOf === undefined) { - delete state.inAllOf; - } if (schema.required) { if (irCompositionSchema.required) { From 355525245f2a847eb3b46a22fcdc36f7c058ecc9 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 04:13:25 +0800 Subject: [PATCH 10/11] perf(shared): prefer in-place mutate when possible --- packages/shared/src/ir/utils.ts | 5 +---- packages/shared/src/openApi/2.0.x/parser/schema.ts | 7 ++----- packages/shared/src/openApi/3.0.x/parser/schema.ts | 9 +++------ packages/shared/src/openApi/3.1.x/parser/schema.ts | 9 +++------ 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/packages/shared/src/ir/utils.ts b/packages/shared/src/ir/utils.ts index bb0e641a79..d404601d2b 100644 --- a/packages/shared/src/ir/utils.ts +++ b/packages/shared/src/ir/utils.ts @@ -32,10 +32,7 @@ export function addItemsToSchema({ if (mutateSchemaOneItem) { // bring composition up to avoid extraneous brackets - schema = { - ...schema, - ...items[0], - }; + Object.assign(schema, items[0]); return schema; } diff --git a/packages/shared/src/openApi/2.0.x/parser/schema.ts b/packages/shared/src/openApi/2.0.x/parser/schema.ts index ea24b05724..54998afc33 100644 --- a/packages/shared/src/openApi/2.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/2.0.x/parser/schema.ts @@ -139,10 +139,7 @@ function parseArray({ const ofArray = schema.items.allOf; if (ofArray && ofArray.length > 1 && !schema.items['x-nullable']) { // bring composition up to avoid incorrectly nested arrays - irSchema = { - ...irSchema, - ...irItemsSchema, - }; + Object.assign(irSchema, irItemsSchema); } else { schemaItems.push(irItemsSchema); } @@ -327,7 +324,7 @@ function parseAllOf({ if (schema.required) { if (irCompositionSchema.required) { - irCompositionSchema.required = [...irCompositionSchema.required, ...schema.required]; + irCompositionSchema.required.push(...schema.required); } else { irCompositionSchema.required = schema.required; } diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index ad130584aa..c071434db4 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -270,10 +270,7 @@ function parseArray({ const ofArray = schema.items.allOf || schema.items.anyOf || schema.items.oneOf; if (ofArray && ofArray.length > 1 && !schema.items.nullable) { // bring composition up to avoid incorrectly nested arrays - irSchema = { - ...irSchema, - ...irItemsSchema, - }; + Object.assign(irSchema, irItemsSchema); } else { schemaItems.push(irItemsSchema); } @@ -496,7 +493,7 @@ function parseAllOf({ if (schema.required) { if (irCompositionSchema.required) { - irCompositionSchema.required = [...irCompositionSchema.required, ...schema.required]; + irCompositionSchema.required.push(...schema.required); } else { irCompositionSchema.required = schema.required; } @@ -662,7 +659,7 @@ function parseAllOf({ inlineSchema.required = []; } if (!inlineSchema.required.includes(discriminator.propertyName)) { - inlineSchema.required = [...inlineSchema.required, discriminator.propertyName]; + inlineSchema.required.push(discriminator.propertyName); } } } else { diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index 626dd8546a..e08669bcb4 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -327,10 +327,7 @@ function parseArray({ !getSchemaTypes({ schema: schema.items }).includes('null') ) { // bring composition up to avoid incorrectly nested arrays - irSchema = { - ...irSchema, - ...irItemsSchema, - }; + Object.assign(irSchema, irItemsSchema); } else { schemaItems.push(irItemsSchema); } @@ -595,7 +592,7 @@ function parseAllOf({ if (schema.required) { if (irCompositionSchema.required) { - irCompositionSchema.required = [...irCompositionSchema.required, ...schema.required]; + irCompositionSchema.required.push(...schema.required); } else { irCompositionSchema.required = schema.required; } @@ -761,7 +758,7 @@ function parseAllOf({ inlineSchema.required = []; } if (!inlineSchema.required.includes(discriminator.propertyName)) { - inlineSchema.required = [...inlineSchema.required, discriminator.propertyName]; + inlineSchema.required.push(discriminator.propertyName); } } } else { From 1dc3fa33699eb4857e131894411a0585853e9ebf Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 20 May 2026 04:19:15 +0800 Subject: [PATCH 11/11] perf(shared): avoid object allocation --- .../src/openApi/2.0.x/parser/pagination.ts | 2 +- .../shared/src/openApi/2.0.x/parser/schema.ts | 14 +++++----- .../src/openApi/3.0.x/parser/pagination.ts | 2 +- .../shared/src/openApi/3.0.x/parser/schema.ts | 18 ++++++------- .../src/openApi/3.1.x/parser/pagination.ts | 6 ++--- .../shared/src/openApi/3.1.x/parser/schema.ts | 26 +++++++------------ 6 files changed, 28 insertions(+), 40 deletions(-) diff --git a/packages/shared/src/openApi/2.0.x/parser/pagination.ts b/packages/shared/src/openApi/2.0.x/parser/pagination.ts index 254c5fd45f..4e9d1ef08b 100644 --- a/packages/shared/src/openApi/2.0.x/parser/pagination.ts +++ b/packages/shared/src/openApi/2.0.x/parser/pagination.ts @@ -87,7 +87,7 @@ export const paginationField = ({ const property = schema.properties[name]!; if (typeof property !== 'boolean' && !('$ref' in property)) { - const schemaType = getSchemaType({ schema: property }); + const schemaType = getSchemaType(property); // TODO: resolve deeper references if (isPaginationType(schemaType)) { diff --git a/packages/shared/src/openApi/2.0.x/parser/schema.ts b/packages/shared/src/openApi/2.0.x/parser/schema.ts index 54998afc33..ab1c3cea41 100644 --- a/packages/shared/src/openApi/2.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/2.0.x/parser/schema.ts @@ -11,11 +11,9 @@ import type { import { discriminatorValues } from '../../../openApi/shared/utils/discriminator'; import { isTopLevelComponent, refToName } from '../../../utils/ref'; -export function getSchemaType({ - schema, -}: { - schema: OpenAPIV2.SchemaObject; -}): SchemaType | undefined { +export function getSchemaType( + schema: OpenAPIV2.SchemaObject, +): SchemaType | undefined { if (schema.type) { return schema.type; } @@ -305,7 +303,7 @@ function parseAllOf({ let irSchema = initIrSchema({ schema }); const schemaItems: Array = []; - const schemaType = getSchemaType({ schema }); + const schemaType = getSchemaType(schema); const compositionSchemas = schema.allOf; @@ -380,7 +378,7 @@ function parseAllOf({ ? context.resolveRef(compositionSchema.$ref) : compositionSchema; - if (getSchemaType({ schema: finalCompositionSchema }) === 'object') { + if (getSchemaType(finalCompositionSchema) === 'object') { const irCompositionSchema = parseOneType({ context, schema: { @@ -634,7 +632,7 @@ function parseType({ parseSchemaMeta({ irSchema, schema }); - const type = getSchemaType({ schema }); + const type = getSchemaType(schema); if (!type) { return irSchema; diff --git a/packages/shared/src/openApi/3.0.x/parser/pagination.ts b/packages/shared/src/openApi/3.0.x/parser/pagination.ts index 80a8c768d2..66e264b626 100644 --- a/packages/shared/src/openApi/3.0.x/parser/pagination.ts +++ b/packages/shared/src/openApi/3.0.x/parser/pagination.ts @@ -74,7 +74,7 @@ export const paginationField = ({ const property = schema.properties[name]!; if (typeof property !== 'boolean' && !('$ref' in property)) { - const schemaType = getSchemaType({ schema: property }); + const schemaType = getSchemaType(property); // TODO: resolve deeper references if (isPaginationType(schemaType)) { diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index c071434db4..9c3b59b4ee 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -15,11 +15,9 @@ import { } from '../../../openApi/shared/utils/discriminator'; import { isTopLevelComponent, refToName } from '../../../utils/ref'; -export function getSchemaType({ - schema, -}: { - schema: OpenAPIV3.SchemaObject; -}): SchemaType | undefined { +export function getSchemaType( + schema: OpenAPIV3.SchemaObject, +): SchemaType | undefined { if (schema.type) { return schema.type; } @@ -465,7 +463,7 @@ function parseAllOf({ let irSchema = initIrSchema({ schema }); const schemaItems: Array = []; - const schemaType = getSchemaType({ schema }); + const schemaType = getSchemaType(schema); const compositionSchemas = schema.allOf; @@ -698,7 +696,7 @@ function parseAllOf({ ? context.resolveRef(compositionSchema.$ref) : compositionSchema; - if (getSchemaType({ schema: finalCompositionSchema }) === 'object') { + if (getSchemaType(finalCompositionSchema) === 'object') { const irCompositionSchema = parseOneType({ context, schema: { @@ -773,7 +771,7 @@ function parseAnyOf({ let irSchema = initIrSchema({ schema }); const schemaItems: Array = []; - const schemaType = getSchemaType({ schema }); + const schemaType = getSchemaType(schema); const compositionSchemas = schema.anyOf; @@ -951,7 +949,7 @@ function parseOneOf({ let irSchema = initIrSchema({ schema }); let schemaItems: Array = []; - const schemaType = getSchemaType({ schema }); + const schemaType = getSchemaType(schema); const compositionSchemas = schema.oneOf; @@ -1151,7 +1149,7 @@ function parseType({ parseSchemaMeta({ irSchema, schema }); - const type = getSchemaType({ schema }); + const type = getSchemaType(schema); if (!type) { return irSchema; diff --git a/packages/shared/src/openApi/3.1.x/parser/pagination.ts b/packages/shared/src/openApi/3.1.x/parser/pagination.ts index c3b08fb0e3..19375f1490 100644 --- a/packages/shared/src/openApi/3.1.x/parser/pagination.ts +++ b/packages/shared/src/openApi/3.1.x/parser/pagination.ts @@ -77,7 +77,7 @@ export const paginationField = ({ if (typeof property !== 'boolean') { // TODO: resolve deeper references - const schemaTypes = getSchemaTypes({ schema: property }); + const schemaTypes = getSchemaTypes(property); if (!schemaTypes.length) { const compositionSchemas = property.anyOf ?? property.oneOf; @@ -85,9 +85,7 @@ export const paginationField = ({ (schema) => schema.type !== 'null', ); if (nonNullCompositionSchemas.length === 1) { - const schemaTypes = getSchemaTypes({ - schema: nonNullCompositionSchemas[0]!, - }); + const schemaTypes = getSchemaTypes(nonNullCompositionSchemas[0]!); if (isPaginationType(schemaTypes)) { return name; } diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index e08669bcb4..97dc86b2c9 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -16,11 +16,9 @@ import { } from '../../../openApi/shared/utils/discriminator'; import { isTopLevelComponent, refToName } from '../../../utils/ref'; -export function getSchemaTypes({ - schema, -}: { - schema: OpenAPIV3_1.SchemaObject; -}): ReadonlyArray> { +export function getSchemaTypes( + schema: OpenAPIV3_1.SchemaObject, +): ReadonlyArray> { if (typeof schema.type === 'string') { return [schema.type]; } @@ -321,11 +319,7 @@ function parseArray({ schemaItems = Array(schema.maxItems).fill(irItemsSchema); } else { const ofArray = schema.items.allOf || schema.items.anyOf || schema.items.oneOf; - if ( - ofArray && - ofArray.length > 1 && - !getSchemaTypes({ schema: schema.items }).includes('null') - ) { + if (ofArray && ofArray.length > 1 && !getSchemaTypes(schema.items).includes('null')) { // bring composition up to avoid incorrectly nested arrays Object.assign(irSchema, irItemsSchema); } else { @@ -564,7 +558,7 @@ function parseAllOf({ parseSchemaMeta({ irSchema, schema }); const schemaItems: Array = []; - const schemaTypes = getSchemaTypes({ schema }); + const schemaTypes = getSchemaTypes(schema); const compositionSchemas = schema.allOf; @@ -796,7 +790,7 @@ function parseAllOf({ ? context.resolveRef(compositionSchema.$ref) : compositionSchema; - if (getSchemaTypes({ schema: finalCompositionSchema }).includes('object')) { + if (getSchemaTypes(finalCompositionSchema).includes('object')) { const irCompositionSchema = parseOneType({ context, schema: { @@ -860,7 +854,7 @@ function parseAnyOf({ parseSchemaMeta({ irSchema, schema }); const schemaItems: Array = []; - const schemaTypes = getSchemaTypes({ schema }); + const schemaTypes = getSchemaTypes(schema); const compositionSchemas = schema.anyOf; @@ -959,7 +953,7 @@ function parseEnum({ irSchema.type = 'enum'; const schemaItems: Array = []; - const schemaTypes = getSchemaTypes({ schema }); + const schemaTypes = getSchemaTypes(schema); const xEnumDescriptions = schema['x-enum-descriptions']; const xEnumVarnames = schema['x-enum-varnames']; const xEnumNames = schema['x-enumNames']; @@ -1029,7 +1023,7 @@ function parseOneOf({ parseSchemaMeta({ irSchema, schema }); let schemaItems: Array = []; - const schemaTypes = getSchemaTypes({ schema }); + const schemaTypes = getSchemaTypes(schema); const compositionSchemas = schema.oneOf; @@ -1321,7 +1315,7 @@ function parseType({ parseSchemaMeta({ irSchema, schema }); - const schemaTypes = getSchemaTypes({ schema }); + const schemaTypes = getSchemaTypes(schema); if (schemaTypes.length === 1) { return parseOneType({