v0.11.6 #65
Closed
sergeliatko
announced in
Announcements
v0.11.6
#65
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
ADR: Response Schema $ref Complexity Limit for fast-json-stringify
Date: 2026-02-18
Status: Implemented
Affects: Gateway code generation (
emitOperationSchemas,emitRouteFiles,emitRouteFilesWithHandlers)Related: v0.9.2 flattenAllOf (#46), #44 Circular type refs (#44)
Context
Fastify uses
fast-json-stringify(fjs) to compile JSON Schema response definitions into optimized serializer functions. During compilation, fjs recursively resolves every$refin the schema graph. For most SOAP or WSDL-derived schemas this works fine, but OTA (OpenTravel Alliance) specification types have deeply nested, wide type hierarchies where a single response type can transitively reference hundreds of sub-schemas.The failure
When generating a Fastify gateway from the Escapia EVRN WSDL (15 operations, 368 compiled types), three operations caused
RangeError: Maximum call stack size exceededduring Fastify'sapp.ready().The stack overflow occurs during fjs schema compilation, not during data serialization. Even an empty response object
{}triggers it because fjs compiles the full schema graph eagerly at route registration time.History of fjs compatibility fixes in this project
This is the third time
fast-json-stringifylimitations have required gateway generator adaptations.v0.8.17 to v0.8.18, Issue #44
Compiler stack overflow on circular type references during WSDL compilation. Fixed by moving cycle detection before
pascal()ingetOrCompileComplex(). This affected the compiler subsystem, not the gateway runtime.v0.9.2, PR #46
allOfcompositions in gateway model schemas caused fjs compilation failures. Fixed by addingflattenAllOf()insrc/gateway/helpers.ts, which merges inherited schemas into flat objects with direct properties. This function runs duringemitModelSchemas()and eliminates allallOffrom generated gateway schemas.This fix
Pure
$refchain depth and breadth exceeding fjs stack limits.flattenAllOfalready ran correctly (zeroallOfin gateway schemas), so the issue is solely about the number of distinct schemas reachable through$refresolution.Decision
When a response schema's
$refgraph references 150 or more unique schema components, strip theresponsekey from the Fastify route registration. The operation schema JSON file retains the full response definition for documentation and tooling. Only the generated route TypeScript code omits it, causing Fastify to fall back toJSON.stringifyfor response serialization.Implementation
Three files changed.
src/gateway/helpers.tsNew
measureSchemaRefComplexity(startSchemaName, allSchemas, limit)function. Walks the$refgraph from a starting OpenAPI component schema, counting unique referenced schemas. Stops early once the limit is reached. This is O(n) with early termination and no repeated visits.src/gateway/generators.tsemitOperationSchemas()calls the measurement for each operation's response schemas. If any response status code's schema exceeds the threshold,skipResponseSchema: trueis set onOperationMetadata. BothemitRouteFiles()(stub handlers) andemitRouteFilesWithHandlers()(full handlers) check this flag and emit a destructured schema binding that stripsresponse.OperationMetadatainterfaceNew optional
skipResponseSchemaboolean field.Threshold rationale
The threshold of 150 unique refs was chosen based on empirical testing.
150 provides a safety margin of approximately 3x above the largest known working schema (55) and catches all observed failures. The threshold is a named constant (
REF_COMPLEXITY_LIMIT) that can be adjusted if needed.Alternatives considered
Increase Node.js stack size (
--stack-size)Approach: run Node.js with
--stack-size=4194304(4 MB) or similar.Pros:
Rejected because:
Inline all
$refchains into a single self-contained schemaApproach: resolve all
$refin the response schema to produce a single JSON Schema document with$defsor fully inlined definitions. fjs would not need to do recursive$refresolution because everything is already inline.Pros:
Rejected because:
EVRN_UnitResRSinlined would be tens of thousands of lines since many types are referenced multiple times, for exampleAddressTypeappears in dozens of contexts.$reflookups.$reffor Fastify'saddSchema()store, which is the documented Fastify pattern.emitModelSchemas(), adding build-time complexity.Custom
serializerCompilerper routeApproach: use Fastify's
serializerCompilerroute option to provide a custom serializer for complex routes, for example one based onajvstandalone mode or a depth-limited fjs compilation.Pros:
Rejected because:
fast-json-stringifydoes not expose a depth-limit or iterative compilation mode. The stack overflow is an internal implementation detail.ajvstandalone serialization would add a new dependency and significant complexity to the generated code.Strip response schema for all operations unconditionally
Approach: never include
responsein any Fastify route schema. Always useJSON.stringify.Pros:
Rejected because:
Use fjs v6 or Fastify v5 improvements
Approach: wait for upstream fixes. fjs has had ongoing work on iterative schema compilation.
Investigated:
fast-json-stringifychangelog and open issues. As of Fastify 5.x (which the generated gateway targets), fjs v6 is included. The recursive$refresolution remains fundamentally recursive.$refresolution was found.Rejected because:
Consequences
Positive
JSON.stringifyinstead of fjs. Response data integrity is unchanged.Negative
buildSuccessEnvelope()wrapper enforces envelope structure, and AJV request validation still runs.JSON.stringifyis approximately 2x to 5x slower than fjs for large objects. Mitigated by: SOAP backend latency (100ms+) dominates total response time, making serialization overhead negligible.Neutral
response. Only the TypeScript route registration strips it. Tools that read the JSON files (OpenAPI viewers, test generators) see the full schema.OperationMetadatainterface gains an optionalskipResponseSchemaboolean. Consumers that do not check it are unaffected.Verification
unit-resrouteunit-readrouteunit-descriptive-inforouterouteSchemausedresponseobject preservedFiles changed
src/gateway/helpers.tsmeasureSchemaRefComplexity()src/gateway/generators.tsemitOperationSchemas(), conditional schema stripping inemitRouteFiles()andemitRouteFilesWithHandlers(), addedskipResponseSchematoOperationMetadataRelated work
This fix is part of a larger effort to fix the
--test-dirgenerator (v0.11.1+) for complex real-world WSDLs. The full set of bugs discovered and fixed during the EVRN WSDL testing is listed in the table.Version, in mock dataschemaCompiler.ts(attrType),mockData.tscatalogMeta.ts(flattenMockPayload), pipeline threadingsrc/util/catalogMeta.tsvitest.config.tsuses wrong APIgenerators.ts(defineProject)mockData.tsschemaCompiler.tsmockData.tsgenerators.ts(runtime template)catalogMeta.tsfast-json-stringifystack overflow on complex schemashelpers.ts,generators.ts(this ADR)Full Changelog: v0.11.5...v0.11.6
This discussion was created from the release v0.11.6.
All reactions