Description
When the same shared JSON Schema (with its own $id) is referenced from two sibling subschemas inside a payload, the payloads preset inlines two literal copies of that schema — both keeping the same $id — into the parent payload's static theCodeGenSchema. AJV rejects the resulting schema at ajv.compile(...) time with reference "<id>" resolves to more than one schema, which makes the generated createValidator() throw. Any consumer that calls <Payload>.createValidator() (including the channels preset's subscription setup) crashes on first invocation.
Environment
- CLI Version:
@the-codegen-project/cli@0.72.0 (current latest)
- Node Version: v22.14.0
- OS: Windows 11
Minimal Reproduction
input.asyncapi.json
{
"asyncapi": "3.0.0",
"info": { "title": "Duplicate $id repro", "version": "0.1.0" },
"channels": {
"StartRunCommand": {
"address": "runs.command.start",
"messages": { "startRun": { "$ref": "#/components/messages/startRun" } }
}
},
"operations": {
"startRun": {
"action": "receive",
"channel": { "$ref": "#/channels/StartRunCommand" },
"messages": [{ "$ref": "#/channels/StartRunCommand/messages/startRun" }]
}
},
"components": {
"messages": {
"startRun": {
"name": "startRun",
"payload": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"generatorA": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "GeneratorA",
"type": "object",
"properties": {
"documentContext": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "DocumentContext",
"type": "object",
"properties": { "specificationDocument": { "type": "string" } }
}
}
},
"generatorB": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "GeneratorB",
"type": "object",
"properties": {
"documentContext": {
"$ref": "#/channels/StartRunCommand/messages/startRun/payload/properties/generatorA/properties/documentContext"
}
}
}
}
}
}
}
}
}
Note the asymmetry: generatorA.documentContext is the canonical inline subschema ($id: "DocumentContext"), and generatorB.documentContext is an internal JSON-pointer $ref to it. This is what an AsyncAPI bundler produces when two siblings both originally $ref-ed the same shared schema file.
codegen.ts
import { TheCodegenConfiguration } from '@the-codegen-project/cli'
const config: TheCodegenConfiguration = {
inputType: 'asyncapi',
inputPath: './input.asyncapi.json',
language: 'typescript',
generators: [
{
preset: 'payloads',
outputPath: './out/payloads',
serializationType: 'json',
language: 'typescript',
},
],
}
export default config
Command
codegen generate ./codegen.ts
Expected Output
Any one of the following would let AJV compile the generated theCodeGenSchema:
- Keep one inline definition and use a JSON-pointer
$ref for subsequent occurrences (matching the input shape).
- Hoist the shared schema into a
$defs entry on the root of theCodeGenSchema and $ref it from each site.
- If full dereferencing is intentional, strip
$id from the inlined duplicates so AJV only sees one anchor for that schema.
Example (option 1):
public static theCodeGenSchema = {
// ...
"properties": {
"generatorA": { /* ... */ "documentContext": { "$id": "DocumentContext", /* ... */ } },
"generatorB": { /* ... */ "documentContext": { "$ref": "#/properties/generatorA/properties/documentContext" } }
},
"$id": "startRun"
};
Actual Output
out/payloads/StartRun.ts (relevant excerpt, formatted):
public static theCodeGenSchema = {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"generatorA": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "GeneratorA",
"type": "object",
"properties": {
"documentContext": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "DocumentContext", // copy 1
"type": "object",
"properties": { "specificationDocument": { "type": "string" } }
}
}
},
"generatorB": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "GeneratorB",
"type": "object",
"properties": {
"documentContext": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "DocumentContext", // copy 2 — same $id, different position
"type": "object",
"properties": { "specificationDocument": { "type": "string" } }
}
}
}
},
"$id": "startRun"
};
public static createValidator(...): ValidateFunction {
// ...
const validate = ajvInstance.compile(this.theCodeGenSchema); // throws
return validate;
}
Calling StartRun.createValidator() (or feeding the generated theCodeGenSchema directly into new Ajv().compile(...)) throws:
Error: reference "DocumentContext" resolves to more than one schema
at ambiguos (.../ajv/lib/compile/resolve.ts:147:12)
at Ajv.addRef (.../ajv/lib/compile/resolve.ts:115:38)
...
at Ajv.compile (.../ajv/lib/core.ts:388:22)
Impact
Any service consuming payloads runtime validation on a payload whose schema tree contains a shared $id-bearing schema referenced from two siblings will crash at startup the first time <Payload>.createValidator() is called. The channels preset calls createValidator() eagerly inside subscription setup, so the failure surfaces immediately — not only when a message arrives.
The same shape also produces duplicate $id: "npm-release" etc. in real specs (with multiple shared schemas referenced from multiple generator subschemas); AJV simply reports the first conflict it hits.
Additional Context
- I searched the repo for
DocumentContext, duplicate $id, resolves to more than one schema, ajv duplicate, theCodeGenSchema, inline schema duplicate, and payloads validate. No existing issue covers this.
- Tested with
@the-codegen-project/cli@0.72.0 (latest). The behavior reproduces; nothing in v0.64.x→v0.72.0 release notes mentions related fixes.
- Verified the AJV failure independently by extracting the generated
theCodeGenSchema literal and passing it to new Ajv().compile(...).
Description
When the same shared JSON Schema (with its own
$id) is referenced from two sibling subschemas inside a payload, thepayloadspreset inlines two literal copies of that schema — both keeping the same$id— into the parent payload's statictheCodeGenSchema. AJV rejects the resulting schema atajv.compile(...)time withreference "<id>" resolves to more than one schema, which makes the generatedcreateValidator()throw. Any consumer that calls<Payload>.createValidator()(including thechannelspreset's subscription setup) crashes on first invocation.Environment
@the-codegen-project/cli@0.72.0(currentlatest)Minimal Reproduction
input.asyncapi.json{ "asyncapi": "3.0.0", "info": { "title": "Duplicate $id repro", "version": "0.1.0" }, "channels": { "StartRunCommand": { "address": "runs.command.start", "messages": { "startRun": { "$ref": "#/components/messages/startRun" } } } }, "operations": { "startRun": { "action": "receive", "channel": { "$ref": "#/channels/StartRunCommand" }, "messages": [{ "$ref": "#/channels/StartRunCommand/messages/startRun" }] } }, "components": { "messages": { "startRun": { "name": "startRun", "payload": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "generatorA": { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "GeneratorA", "type": "object", "properties": { "documentContext": { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "DocumentContext", "type": "object", "properties": { "specificationDocument": { "type": "string" } } } } }, "generatorB": { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "GeneratorB", "type": "object", "properties": { "documentContext": { "$ref": "#/channels/StartRunCommand/messages/startRun/payload/properties/generatorA/properties/documentContext" } } } } } } } } }Note the asymmetry:
generatorA.documentContextis the canonical inline subschema ($id: "DocumentContext"), andgeneratorB.documentContextis an internal JSON-pointer$refto it. This is what an AsyncAPI bundler produces when two siblings both originally$ref-ed the same shared schema file.codegen.tsCommand
Expected Output
Any one of the following would let AJV compile the generated
theCodeGenSchema:$reffor subsequent occurrences (matching the input shape).$defsentry on the root oftheCodeGenSchemaand$refit from each site.$idfrom the inlined duplicates so AJV only sees one anchor for that schema.Example (option 1):
Actual Output
out/payloads/StartRun.ts(relevant excerpt, formatted):Calling
StartRun.createValidator()(or feeding the generatedtheCodeGenSchemadirectly intonew Ajv().compile(...)) throws:Impact
Any service consuming
payloadsruntime validation on a payload whose schema tree contains a shared$id-bearing schema referenced from two siblings will crash at startup the first time<Payload>.createValidator()is called. Thechannelspreset callscreateValidator()eagerly inside subscription setup, so the failure surfaces immediately — not only when a message arrives.The same shape also produces duplicate
$id: "npm-release"etc. in real specs (with multiple shared schemas referenced from multiple generator subschemas); AJV simply reports the first conflict it hits.Additional Context
DocumentContext,duplicate $id,resolves to more than one schema,ajv duplicate,theCodeGenSchema,inline schema duplicate, andpayloads validate. No existing issue covers this.@the-codegen-project/cli@0.72.0(latest). The behavior reproduces; nothing in v0.64.x→v0.72.0 release notes mentions related fixes.theCodeGenSchemaliteral and passing it tonew Ajv().compile(...).