Relevant source files
The following files were used as context for generating this wiki page:
- packages/asyncapi/src/utils/document/dereference.ts
- packages/asyncapi/src/server/index.tsx
- packages/asyncapi/src/utils/document/load.ts
- packages/asyncapi/src/ui/index.tsx
- packages/openapi/src/server/index.tsx
- packages/openapi/src/utils/document/load.ts
- packages/openapi/src/utils/document/dereference.ts
- packages/asyncapi/src/generate-file.ts
- packages/asyncapi/src/utils/schema.ts
- packages/asyncapi/src/utils/pages/builder.ts
- packages/asyncapi/src/utils/pages/preset-auto.ts
- packages/python/src/convert.ts
- packages/api-docs/src/components/schema/index.tsx
- packages/asyncapi/src/index.ts
- packages/asyncapi/src/utils/pages/to-text.ts
- packages/openapi/src/ui/base.tsx
- packages/mdx/src/runtime/server.ts
- packages/api-docs/src/schema/dereference.ts
- packages/openapi/src/generate-file.ts
- packages/preview/src/pages/...slugs.tsx
- packages/content-collections/src/configuration.ts
- packages/openapi/src/utils/pages/builder.ts
- packages/mdx/src/core.ts
- packages/api-docs/src/schema/bundle.ts
- packages/core/src/source/loader.ts
- packages/asyncapi/src/utils/pages/to-static-data.ts
- packages/asyncapi/package.json
- packages/python/fumapy/mksource/document_module.py
- packages/doc-gen/src/remark-docgen.ts
- packages/asyncapi/src/utils/traits.ts
Overview
The AsyncAPI document processing pipeline ingest, bundles, and transforms AsyncAPI specifications into structured Fumadocs virtual files, navigation hierarchies, and standalone documentation pages. By combining reference bundling, shallow and deep schema dereferencing, automated page generation presets, and trait merging utilities, the framework standardizes event-driven API documentation for message channels and operations.
Sources: packages/asyncapi/src/utils/document/load.ts:7-16, packages/asyncapi/src/server/index.tsx:110-203, packages/asyncapi/src/utils/pages/preset-auto.ts:56-279
Document Loading and Bundling
Ingestion and Bundling Pipeline
AsyncAPI documents are ingested and prepared for processing via loadDocument, which accepts either a file path, a URL, or an inline AsyncAPIObject specification. The ingestion utility invokes bundle from @apidevtools/json-schema-ref-parser to resolve external and internal $ref pointers into a single unified JSON schema structure.
export async function loadDocument(input: string | AsyncAPIObject): Promise<{
bundled: AsyncAPIObject;
}> {
try {
const bundled = await bundle<AsyncAPIObject>(input);
if (!bundled.asyncapi) {
throw new Error('Missing required `asyncapi` field.');
}
return { bundled };
} catch (e) {
throw new Error(`[AsyncAPI] Failed to resolve input: ${input}`, {
cause: e,
});
}Sources: packages/asyncapi/src/utils/document/load.ts:7-21, packages/api-docs/src/schema/bundle.ts:1-1
Warning
If a loaded specification lacks a root asyncapi field after bundling, loadDocument throws an explicit error indicating a missing required property.
Sources: packages/asyncapi/src/utils/document/load.ts:12-14
Server Options and Document Caching
The createAsyncAPI server manages inputs supplied either as an array of path strings or as a SchemaRecord mapping identifiers to static objects or async loader functions. Caching behavior can be controlled using the disableCache option.
type SchemaRecord = Record<
string,
string | AsyncAPIObject | (() => Awaitable<string | AsyncAPIObject>)
>;
interface LoadedDocument {
bundled: AsyncAPIObject;
}
export interface AsyncAPIOptions {
input?: string[] | SchemaRecord;
disableCache?: boolean;
}Sources: packages/asyncapi/src/server/index.tsx:28-39
When caching is active, loaded document promises are stored in a local schemaMap to prevent redundant disk reads or network requests across schema queries.
export function createAsyncAPI(options: AsyncAPIOptions = {}): AsyncAPIServer {
const { disableCache = false } = options;
const schemaMap = new Map<string, Promise<LoadedDocument>>();
let resolvedInput: SchemaRecord = {};
if (Array.isArray(options.input)) {
for (const item of options.input) resolvedInput[item] = item;
} else if (options.input) {
resolvedInput = options.input;
}
function getSchema(schemaId: string): Promise<LoadedDocument> {
if (!(schemaId in resolvedInput)) {
console.warn(
`[Fumadocs AsyncAPI] the document "${schemaId}" is not listed in the input array, this may be unexpected and won't be cached properly.`,
);
return loadDocument(schemaId);
}
if (!disableCache) {
const cached = schemaMap.get(schemaId);
if (cached) return cached;
}
const raw = resolvedInput[schemaId];
const output = Promise.resolve(typeof raw === 'function' ? raw() : raw).then(loadDocument);
if (!disableCache) schemaMap.set(schemaId, output);
return output;
}Sources: packages/asyncapi/src/server/index.tsx:73-101
Note
If a requested schemaId is not pre-registered in resolvedInput, getSchema emits a console warning that the document will not be cached properly before falling back to direct execution of loadDocument(schemaId).
Sources: packages/asyncapi/src/server/index.tsx:85-90
Document Dereferencing Architecture
Synchronized and Shallow Dereferencing Mechanisms
The framework provides specialized routines to resolve $ref pointers within bundled AsyncAPI and JSON schema documents. The dereferenceBundledDocument utility wraps dereferenceSync from API Docs Primitives, constructing a DereferencedDocument container that holds the original bundled document, the fully dereferenced specification tree, and a getRawRef lookup function that retrieves original reference pointers for objects.
export function dereferenceBundledDocument(bundled: AsyncAPIObject): DereferencedDocument {
const dereferenceMap = new Map<object, string>();
return {
bundled,
dereferenced: dereferenceSync(bundled as JSONSchema, {
setOriginalRef(schema, ref) {
dereferenceMap.set(schema as object, ref);
},
}) as NoReference<AsyncAPIObject>,
getRawRef(obj: object) {
return dereferenceMap.get(obj);
},
};Sources: packages/asyncapi/src/utils/document/dereference.ts:20-33
Note
The dereferenceBundledDocument function registers a setOriginalRef callback during synchronous traversal, mapping both resolved target schemas and modified parent objects back to their original $ref strings in an internal Map.
Sources: packages/asyncapi/src/utils/document/dereference.ts:25-32
Dereference Options and Resolution Flow
The core synchronization engine operates via dereferenceSync, which clones the input schema structure using structuredClone and traverses nodes recursively with pointer path tracking via appendInternalRefPath. It supports options to control preservation and original reference tracking during traversal.
| Option | Type | Default | Purpose |
|---|---|---|---|
preserveRef | (pointer: string) => boolean | undefined | Returns true to keep the Reference Object at a specific pointer without inlining. |
setOriginalRef | (schema: JSONSchema, ref: string) => void | () => {} | Callback invoked with the resolved schema and its original reference pointer. |
export interface DereferenceOptions {
/**
* Return `true` to keep the Reference Object at `pointer` without inlining.
*/
preserveRef?: (pointer: string) => boolean;
setOriginalRef?: (schema: JSONSchema, ref: string) => void;
}Sources: packages/api-docs/src/schema/dereference.ts:7-13
For non-recursive or single-level resolution, dereferenceShallow inspects individual plain objects, extracts local properties while removing $ref, and resolves referenced definitions against a full document root using resolveRefSync.
export function dereferenceShallow<T>(schema: T, full: unknown): NoReferenceShallow<T> {
if (isPlainObject(schema)) {
if (typeof schema.$ref !== 'string') return schema as never;
const { $ref, ...rest } = schema;
const resolved = dereferenceShallow(resolveRefSync($ref, full), full);
if (typeof resolved !== 'object') throw new Error(`invalid schema referenced via "${$ref}"`);
return { ...resolved, ...rest } as never;
}
return schema as never;Sources: packages/api-docs/src/schema/dereference.ts:67-78
Caution
If a referenced target resolves to a boolean instead of an object during deep synchronization, dereferenceSync throws an explicit 'invalid schema' error.
Sources: packages/api-docs/src/schema/dereference.ts:48-48
Virtual File and Route Generation
Virtual File Construction Pipeline
The transformation of loaded AsyncAPI specifications into Fumadocs virtual files is driven by a precise execution sequence. The framework coordinates file generation starting from server initialization, traversing schemas, processing entries, and constructing page properties.
createAsyncAPI— Initializes server configurations, schema maps, and resolves input sources.getVirtualFiles— Asynchronously retrieves all bundled schemas and initializes builder configurations viacreateAutoPreset.onEntries— Recursively evaluates generated output entries, handling directory groups and meta configuration files whenmetaoptions are enabled.onEntry— Processes individual page and operation entries, generating page props and assigning static table of contents and structured data.getPageProps— Extracts generated metadata, document identifiers, and operations associated with the specific entry item.
async function getVirtualFiles(server: AsyncAPIServer, options: AsyncAPISourceOptions) {
const { baseDir = '', meta = false } = options;
const { createAutoPreset } = await import('@/utils/pages/preset-auto');
const { fromSchema } = await import('@/utils/pages/builder');
const files: VirtualFile<{
pageData: AsyncAPIPageData;
metaData: MetaData;
}>[] = [];
const schemas = await server.getSchemas();
const builderOptions = createAutoPreset(options);
for (const [id, schema] of Object.entries(schemas)) {
const list = fromSchema(id, schema.bundled, builderOptions);
onEntries(list);
function onEntry(entry: PageOutput | OperationOutput) {
const props = getPageProps(entry);
files.push({
type: 'page',
path: `${baseDir}/${entry.path}`,
data: {
...entry.info,
getAsyncAPIPageProps() {
return {
payload: {
bundled: schema.bundled,
},
...props,
};
},
getSchema() {
return {
id,
bundled: schema.bundled,
};
},
...toStaticData(props, schema.bundled),
_asyncapi: {
action: entry.type === 'operation' ? entry.item.action : undefined,
deprecated: entry.info.deprecated,
},
},
});
}Sources: packages/asyncapi/src/server/index.tsx:110-156
Note
During onEntries execution, path normalization uses PathUtils.slash to compute relative paths between parent groups and child entries.
Sources: packages/asyncapi/src/server/index.tsx:174-177
Schema Builders and Static Data Mapping
The fromSchema builder function accepts a schema identifier, a bundled AsyncAPIObject, and a configuration object containing a toPages callback. It exposes a PagesBuilder interface that executes custom page generation logic and provides a routePathToFilePath utility function to clean route paths into filesystem-compatible slugs.
| Property / Method | Type | Purpose |
|---|---|---|
id | string | The unique schema identifier. |
document | AsyncAPIObject | The bundled AsyncAPI specification object. |
dereferenceShallow | <T>(schema: T) => NoReferenceSwallow<T> | Resolves shallow references against the document root. |
create | (entry: OutputEntry) => void | Appends a generated entry to the output list. |
routePathToFilePath | (path: string) => string | Sanitizes route paths into filesystem paths. |
export interface PagesBuilder {
id: string;
document: AsyncAPIObject;
dereferenceShallow: <T>(schema: T) => NoReferenceSwallow<T>;
create: (entry: OutputEntry) => void;
routePathToFilePath: (path: string) => string;
}
export function fromSchema(
schemaId: string,
bundled: AsyncAPIObject,
config: PagesBuilderConfig,
): OutputEntry[] {
const files: OutputEntry[] = [];
const { toPages } = config;
toPages({
id: schemaId,
document: bundled,
dereferenceShallow: (s) => dereferenceShallow(s, bundled),
create(entry) {
files.push(entry);
},
routePathToFilePath(path) {
return path
.toLowerCase()
.replaceAll('.', '-')
.split('/')
.flatMap((v) => {
if (v.startsWith('{') && v.endsWith('}')) return v.slice(1, -1);
if (v.length === 0) return [];
return v;
})
.join('/');
},
});Sources: packages/asyncapi/src/utils/pages/builder.ts:49-84
Caution
The routePathToFilePath utility converts paths to lowercase, replaces periods (.) with hyphens (-), strips curly brace parameters like {id} to their inner values, and filters out empty path segments.
Sources: packages/asyncapi/src/utils/pages/builder.ts:72-82
Once entries are mapped, toStaticData iterates over page operations, generating slugged table of contents items and structured data headings using github-slugger.
export function toStaticData(
page: GeneratedPageProps,
doc: AsyncAPIObject,
): {
toc: TOCItemType[];
structuredData: StructuredData;
} {
const slugger = new Slugger();
const toc: TOCItemType[] = [];
const structuredData: StructuredData = { headings: [], contents: [] };
for (const item of page.operations ?? []) {
const operation = dereferenceShallow(doc.operations?.[item.id], doc);
if (!operation) continue;
if (page.showTitle) {
const title = getOperationDisplayName(item.id, operation);
const id = slugger.slug(title);
toc.push({
depth: 2,
title,
url: `#${id}`,
});
structuredData.headings.push({
content: title,
id,
});
}
if (operation.description) {
structuredData.contents.push({
content: operation.description,
heading: structuredData.headings.at(-1)?.id,
});
}
}
return { toc, structuredData };Sources: packages/asyncapi/src/utils/pages/to-static-data.ts:9-47
Automated Page Structuring Presets
Configuration Options and Preset Types
The automatic page structuring pipeline is configured via createAutoPreset, which accepts SchemaToPagesOptions to determine how AsyncAPI documents are parsed into pages, tags, files, or custom groupings. By default, slugification replaces whitespace characters with hyphens and converts strings to lowercase.
| Option | Type | Default | Purpose |
|---|---|---|---|
per | 'file' | 'tag' | 'operation' | 'custom' | 'operation' | Determines the partitioning granularity for generated documentation entries. |
groupBy | 'tag' | 'channel' | 'none' | function | 'none' | Groups operation entries by tag, channel address, or a custom mapper function. |
name | NameFn | NameFnOptions | { algorithm: 'v2' } | Customizes file naming using algorithm version 'v1' or 'v2', or a custom name function. |
slugify | (name: string) => string | s => s.replace(/\s+/g, '-').toLowerCase() | Converts titles or identifiers into URL-safe slugs. |
interface OperationConfig extends BaseConfig {
per?: 'operation';
groupBy?:
| 'tag'
| 'channel'
| 'none'
| ((entry: DistributiveOmit<OperationOutput, 'path'>) => string);
name?: NameFn<OperationOutput> | NameFnOptions;
}
interface TagConfig extends BaseConfig {
per: 'tag';
name?: NameFn<PageOutput> | NameFnOptions;
}
interface SchemaConfig extends BaseConfig {
per: 'file';
name?: NameFn<PageOutput> | NameFnOptions;
}
export type SchemaToPagesOptions =
| SchemaConfig
| TagConfig
| OperationConfig
| ({
per: 'custom';
} & PagesBuilderConfig);
type NameFn<Entry extends OperationOutput | PageOutput = OperationOutput | PageOutput> = (
this: PagesBuilder,
output: DistributiveOmit<Entry, 'path'>,
) => string;
interface NameFnOptions {
algorithm?: 'v2' | 'v1';
}
interface BaseConfig {
slugify?: (name: string) => string;
}Sources: packages/asyncapi/src/utils/pages/preset-auto.ts:15-54
Note
When per is set to 'custom', createAutoPreset bypasses internal generation logic and returns the user-provided PagesBuilderConfig object directly.
Sources: packages/asyncapi/src/utils/pages/preset-auto.ts:56-57
Generation Algorithms and Grouping Logic
When processing documents under an operation-level preset, the group function iterates through operations and organizes them based on the groupBy parameter. If groupBy is set to 'channel', operations resolve their associated channel address to construct group titles and file paths. If set to 'tag', operations inspect their tags array; if an operation lacks tags, it logs a warning and assigns an 'unknown' tag fallback before registering entries under corresponding tag directories.
function group(
builder: PagesBuilder,
entries: DistributiveOmit<OperationOutput, 'path'>[],
): OutputEntry[] {
const groups = new Map<string, OutputGroup>();
const rest: OutputEntry[] = [];
const doc = builder.document;
const { groupBy = 'none' } = options as OperationConfig;
for (const entry of entries) {
switch (groupBy) {
case 'channel': {
const operation = dereferenceShallow(doc.operations?.[entry.item.id], doc);
const channel = operation ? dereferenceShallow(operation.channel, doc) : undefined;
const groupName = slugify(channel?.address || entry.item.id);
let group = groups.get(groupName);
if (!group) {
group = {
type: 'group',
info: {
title: channel?.title || channel?.address || groupName,
description: channel?.description,
},
entries: [],
schemaId: builder.id,
path: groupName,
};
groups.set(groupName, group);
}
group.entries.push({
...entry,
path: path.join(groupName, `${entry.item.action}.mdx`),
});
break;
}
case 'tag': {
const operation = dereferenceShallow(doc.operations?.[entry.item.id], doc)!;
let tags = operation.tags?.map((t) => dereferenceShallow(t, doc));
if (!tags || tags.length === 0) {
console.warn(
'When `groupBy` is set to `tag`, make sure a `tags` is defined for every operation schema.',
);
tags = [{ name: 'unknown' }];
}
for (const tag of tags) {
const groupId = slugify(tag.name);
let group = groups.get(groupId);
if (!group) {
group = {
type: 'group',
info: { title: getTagDisplayName(tag), description: tag.description },
tag,
entries: [],
schemaId: builder.id,
path: groupId,
};
groups.set(groupId, group);
}
group.entries.push({
...entry,
path: path.join(groupId, `${nameFn.call(builder, entry)}.mdx`),
});
}
break;
}Sources: packages/asyncapi/src/utils/pages/preset-auto.ts:88-158
Warning
When groupBy is configured as 'tag', ensure that every operation schema defines a tags array; otherwise, operations fall back to an 'unknown' group with a console warning.
Sources: packages/asyncapi/src/utils/pages/preset-auto.ts:129-134
Name resolution algorithms distinguish between file-level and operation-level outputs. Page outputs default to their tag name or extract the base name of the schema file, whereas operation outputs use algorithm 'v2' to return the operation identifier directly, or algorithm 'v1' to join the sanitized route path with the operation action (send or receive).
let nameFn: NameFn;
if (typeof options.name === 'function') {
nameFn = options.name as NameFn;
} else {
const { algorithm = 'v2' } = options.name ?? {};
nameFn = function (result) {
if (result.type === 'page') {
if (result.tag) return slugify(result.tag.name);
const schemaId = result.schemaId;
return schemaId.startsWith('http://') || schemaId.startsWith('https://')
? 'index'
: path.basename(schemaId, path.extname(schemaId));
}
if (algorithm === 'v2') {
return result.item.id;
}
return path.join(this.routePathToFilePath(result.item.id), result.item.action);
};
}Sources: packages/asyncapi/src/utils/pages/preset-auto.ts:63-86
Static File and MDX Output
Execution Flow and Disk Generation Walkthrough
The disk generation pipeline transforms generated page structures into serialized MDX files on disk, supporting optional file watching, metadata generation, and custom pre-write hooks. The process begins by calling generateFiles, which delegates the core generation work and handles asynchronous file writing or sets up a file watcher via chokidar.
Sources: packages/asyncapi/src/generate-file.ts:56-134
generateFilesOnlyextracts input schemas, invokescreateAutoPreset, and iterates through entries usingfromSchemato produce virtual files and metadata objects. Sources: packages/asyncapi/src/generate-file.ts:83-117writeIndexFilesconstructs index or overview files if an index configuration is specified, mapping relative paths and checking available schema keys. Sources: packages/asyncapi/src/generate-file.ts:183-209fileContentbuilds the card layout containing hyperlinks and descriptions for output entries before passing the accumulated content togenerateDocument. Sources: packages/asyncapi/src/generate-file.ts:210-252
Note
When options.watch is enabled, generateFiles recursively invokes itself with watch: false and registers a chokidar watcher on input._getWatchPaths() to trigger regeneration on file changes.
Sources: packages/asyncapi/src/generate-file.ts:57-68
YAML Frontmatter Serialization and MDX Generation
Document serialization is handled by toText and generateDocument, which serialize metadata into YAML frontmatter and format the resulting MDX document. Frontmatter properties are processed using js-yaml's dump function alongside removeUndefined, while optional comments warn consumers against manual file modification.
| Option Key | Type | Default | Purpose |
|---|---|---|---|
imports | Array<{ names: string[]; from: string }> | undefined | Custom import statements prepended to the generated MDX document. |
frontmatter | (title: string, description: string | undefined, context: DocumentContext) => Record<string, unknown> | undefined | Extends or customizes generated YAML frontmatter attributes. |
includeDescription | boolean | false | Controls whether descriptions appear in frontmatter or within page props. |
addGeneratedComment | boolean | string | true | Appends an automated generation warning comment or a custom string to the file banner. |
export interface PagesToTextOptions {
imports?: {
names: string[];
from: string;
}[];
frontmatter?: (
title: string,
description: string | undefined,
context: DocumentContext,
) => Record<string, unknown>;
includeDescription?: boolean;
addGeneratedComment?: boolean | string;
}Sources: packages/asyncapi/src/utils/pages/to-text.ts:15-27
export function generateDocument(
frontmatter: unknown,
content: string,
options: PagesToTextOptions,
): string {
const { addGeneratedComment = true, imports } = options;
const out: string[] = [];
const banner = dump(removeUndefined(frontmatter as object)).trimEnd();
if (banner.length > 0) out.push(`---\n${banner}\n---`);
if (addGeneratedComment) {
let commentContent =
'This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again.';
if (typeof addGeneratedComment === 'string') {
commentContent = addGeneratedComment;
}
commentContent = commentContent.replaceAll('/', '\\/');
out.push(`{/* ${commentContent} */}`);
}
if (imports) {
out.push(
...imports
.map((item) => `import { ${item.names.join(', ')} } from ${doubleQuote(item.from)};`)
.join('\n'),
);
}
out.push(content);
return out.join('\n\n');
}Sources: packages/asyncapi/src/utils/pages/to-text.ts:76-108
Warning
Generated comment strings automatically replace all forward slashes with escaped backslashes (replaceAll('/', '\\/')) to preserve valid comment syntax in MDX outputs.
Sources: packages/asyncapi/src/utils/pages/to-text.ts:94-94
Operation Traits and Schema Merging
AsyncAPI definitions often use reusable traits to share common attributes, metadata, and configurations across operations and messages. The trait merging implementation processes these reusable layers according to the AsyncAPI specification, flattening trait arrays into base objects while omitting the traits property itself from the final merged output.
The merging engine is driven by mergeTraits and mergeTraitLayer, which iterate through an array of trait dictionaries and shallow-copy or deep-merge plain objects property by property. When an operation or message object contains a traits array, applyOperationTraits and applyMessageTraits extract the base properties by destructuring away the traits field and passing the remaining properties along with the trait layers into mergeTraits.
| Function Name | Input Parameters | Return Type | Description |
|---|---|---|---|
mergeTraits | target: T, traits: Record<string, unknown>[] | T | Sequentially folds multiple trait object layers into a target record. |
mergeTraitLayer | target: Record<string, unknown>, trait: Record<string, unknown> | Record<string, unknown> | Recursively merges a single trait layer into a target record, ignoring the traits key. |
applyOperationTraits | operation: NoReference<OperationObject> | NoReference<OperationObject> | Strips the traits property from an operation object and merges any declared traits. |
applyMessageTraits | message: NoReference<MessageObject> | NoReference<MessageObject> | Strips the traits property from a message object and merges any declared traits. |
export function mergeTraits<T extends Record<string, unknown>>(
target: T,
traits: Record<string, unknown>[],
): T {
let result: Record<string, unknown> = { ...target };
for (const trait of traits) {
result = mergeTraitLayer(result, trait);
}
return result as T;
}
function mergeTraitLayer(
target: Record<string, unknown>,
trait: Record<string, unknown>,
): Record<string, unknown> {
const result: Record<string, unknown> = { ...target };
for (const [key, traitValue] of Object.entries(trait)) {
if (key === 'traits') continue;
const targetValue = result[key];
if (targetValue === undefined) {
result[key] = traitValue;
} else if (isPlainObject(targetValue) && isPlainObject(traitValue)) {
result[key] = mergeTraitLayer(targetValue, traitValue);
}
}
return result;
}Sources: packages/asyncapi/src/utils/traits.ts:9-38
export function applyOperationTraits(
operation: NoReference<OperationObject>,
): NoReference<OperationObject> {
const traits = operation.traits;
if (!traits || traits.length === 0) return operation;
const { traits: _, ...base } = operation;
return mergeTraits(base, traits);
}
export function applyMessageTraits(
message: NoReference<MessageObject>,
): NoReference<MessageObject> {
const traits = message.traits;
if (!traits || traits.length === 0) return message;
const { traits: _, ...base } = message;
return mergeTraits(base, traits);
}Sources: packages/asyncapi/src/utils/traits.ts:40-58
Note
During trait layer merging, the algorithm explicitly checks if (key === 'traits') continue; to prevent nested or recursive trait declarations from re-triggering trait application loops.
Sources: packages/asyncapi/src/utils/traits.ts:26-28
Warning
If an operation or message object defines no traits or an empty traits array (traits.length === 0), applyOperationTraits and applyMessageTraits return the original object instance without modification.
Sources: packages/asyncapi/src/utils/traits.ts:43-44, packages/asyncapi/src/utils/traits.ts:53-54
Related
On This Page
OverviewDocument Loading and BundlingIngestion and Bundling PipelineServer Options and Document CachingDocument Dereferencing ArchitectureSynchronized and Shallow Dereferencing MechanismsDereference Options and Resolution FlowVirtual File and Route GenerationVirtual File Construction PipelineSchema Builders and Static Data MappingAutomated Page Structuring PresetsConfiguration Options and Preset TypesGeneration Algorithms and Grouping LogicStatic File and MDX OutputExecution Flow and Disk Generation WalkthroughYAML Frontmatter Serialization and MDX GenerationOperation Traits and Schema MergingRelated