@wisemen/nestjs-async-api 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintcache +1 -1
- package/.turbo/turbo-build.log +9 -0
- package/.turbo/turbo-lint.log +5 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/dist/lib/generate-async-api-html.d.ts +1 -1
- package/dist/lib/generate-async-api-html.js +682 -11
- package/dist/lib/generate-async-api-html.js.map +1 -1
- package/lib/generate-async-api-html.ts +853 -21
- package/package.json +18 -16
- package/.release-it.json +0 -13
|
@@ -1,15 +1,686 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import YAML from 'yaml';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function escapeHTML(value) {
|
|
6
|
+
let normalized = '';
|
|
7
|
+
if (value === null || value === undefined) {
|
|
8
|
+
normalized = '';
|
|
9
|
+
}
|
|
10
|
+
else if (typeof value === 'string') {
|
|
11
|
+
normalized = value;
|
|
12
|
+
}
|
|
13
|
+
else if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
14
|
+
normalized = `${value}`;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
normalized = JSON.stringify(value) ?? '';
|
|
18
|
+
}
|
|
19
|
+
return normalized
|
|
20
|
+
.replaceAll('&', '&')
|
|
21
|
+
.replaceAll('<', '<')
|
|
22
|
+
.replaceAll('>', '>')
|
|
23
|
+
.replaceAll('"', '"')
|
|
24
|
+
.replaceAll('\'', ''');
|
|
25
|
+
}
|
|
26
|
+
function valueToCodeBlock(value) {
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
return '<p class="muted">No data available.</p>';
|
|
29
|
+
}
|
|
30
|
+
const formatted = typeof value === 'string'
|
|
31
|
+
? value
|
|
32
|
+
: JSON.stringify(value, null, 2) ?? 'No serializable data available.';
|
|
33
|
+
return `<pre>${escapeHTML(formatted)}</pre>`;
|
|
34
|
+
}
|
|
35
|
+
function parseAsyncApiDocument(yaml) {
|
|
36
|
+
const parsedDocument = YAML.parse(yaml);
|
|
37
|
+
if (!isRecord(parsedDocument)) {
|
|
38
|
+
throw new Error('Invalid AsyncAPI document: YAML did not parse to an object');
|
|
39
|
+
}
|
|
40
|
+
if (typeof parsedDocument.asyncapi !== 'string') {
|
|
41
|
+
throw new Error('Invalid AsyncAPI document: asyncapi must be a string');
|
|
42
|
+
}
|
|
43
|
+
if (!isRecord(parsedDocument.info)) {
|
|
44
|
+
throw new Error('Invalid AsyncAPI document: info must be an object');
|
|
45
|
+
}
|
|
46
|
+
if (typeof parsedDocument.info.title !== 'string' || typeof parsedDocument.info.version !== 'string') {
|
|
47
|
+
throw new Error('Invalid AsyncAPI document: info.title and info.version are required');
|
|
48
|
+
}
|
|
49
|
+
if (!isRecord(parsedDocument.channels)) {
|
|
50
|
+
throw new Error('Invalid AsyncAPI document: channels must be an object');
|
|
51
|
+
}
|
|
52
|
+
return parsedDocument;
|
|
53
|
+
}
|
|
54
|
+
function toAnchorId(prefix, name) {
|
|
55
|
+
const normalized = name
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
59
|
+
.replace(/^-+|-+$/g, '');
|
|
60
|
+
return `${prefix}-${normalized || 'item'}`;
|
|
61
|
+
}
|
|
62
|
+
function resolveChannelNameFromRef(ref) {
|
|
63
|
+
if (!ref.startsWith('#/channels/')) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return ref.replace('#/channels/', '');
|
|
67
|
+
}
|
|
68
|
+
function resolveMessageNameFromChannelRef(ref) {
|
|
69
|
+
if (!ref.startsWith('#/channels/')) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const segments = ref.split('/');
|
|
73
|
+
const messageName = segments[segments.length - 1];
|
|
74
|
+
return messageName || undefined;
|
|
75
|
+
}
|
|
76
|
+
function resolveMessageNameFromComponentRef(ref) {
|
|
77
|
+
if (!ref.startsWith('#/components/messages/')) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return ref.replace('#/components/messages/', '');
|
|
81
|
+
}
|
|
82
|
+
function resolveSchemaNameFromRef(ref) {
|
|
83
|
+
if (!ref.startsWith('#/components/schemas/')) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return ref.replace('#/components/schemas/', '');
|
|
87
|
+
}
|
|
88
|
+
function collectOperationMessages(operation) {
|
|
89
|
+
if (operation.messages === undefined) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
return operation.messages
|
|
93
|
+
.map(messageRef => resolveMessageNameFromChannelRef(messageRef.$ref))
|
|
94
|
+
.filter((messageName) => messageName !== undefined);
|
|
95
|
+
}
|
|
96
|
+
function buildLookups(document) {
|
|
97
|
+
const operations = document.operations ?? {};
|
|
98
|
+
const messages = document.components?.messages ?? {};
|
|
99
|
+
const schemas = document.components?.schemas ?? {};
|
|
100
|
+
const operationsByChannel = new Map();
|
|
101
|
+
const operationLookups = [];
|
|
102
|
+
for (const [operationName, operation] of Object.entries(operations)) {
|
|
103
|
+
const channelName = resolveChannelNameFromRef(operation.channel.$ref);
|
|
104
|
+
const operationMessages = collectOperationMessages(operation);
|
|
105
|
+
operationLookups.push({
|
|
106
|
+
name: operationName,
|
|
107
|
+
channelName,
|
|
108
|
+
action: operation.action,
|
|
109
|
+
summary: operation.summary,
|
|
110
|
+
description: operation.description,
|
|
111
|
+
messageNames: operationMessages
|
|
112
|
+
});
|
|
113
|
+
if (channelName !== undefined) {
|
|
114
|
+
const channelOperations = operationsByChannel.get(channelName) ?? [];
|
|
115
|
+
channelOperations.push(operationName);
|
|
116
|
+
operationsByChannel.set(channelName, channelOperations);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const messageLookups = {};
|
|
120
|
+
for (const [messageName, messageValue] of Object.entries(messages)) {
|
|
121
|
+
const payloadRef = typeof messageValue.payload === 'object'
|
|
122
|
+
&& messageValue.payload !== null
|
|
123
|
+
&& '$ref' in messageValue.payload
|
|
124
|
+
? String(messageValue.payload.$ref)
|
|
125
|
+
: undefined;
|
|
126
|
+
const schemaName = payloadRef !== undefined
|
|
127
|
+
? resolveSchemaNameFromRef(payloadRef)
|
|
128
|
+
: undefined;
|
|
129
|
+
messageLookups[messageName] = {
|
|
130
|
+
name: messageName,
|
|
131
|
+
message: messageValue,
|
|
132
|
+
schemaName
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const channelLookups = [];
|
|
136
|
+
for (const [channelName, channelValue] of Object.entries(document.channels)) {
|
|
137
|
+
const channel = channelValue;
|
|
138
|
+
const channelMessageNames = new Set();
|
|
139
|
+
if (channel.messages !== undefined) {
|
|
140
|
+
for (const [messageName, messageRef] of Object.entries(channel.messages)) {
|
|
141
|
+
channelMessageNames.add(messageName);
|
|
142
|
+
const componentMessageName = resolveMessageNameFromComponentRef(messageRef.$ref);
|
|
143
|
+
if (componentMessageName !== undefined) {
|
|
144
|
+
channelMessageNames.add(componentMessageName);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const operationNames = operationsByChannel.get(channelName) ?? [];
|
|
149
|
+
const operationMessages = operationNames
|
|
150
|
+
.map(operationName => collectOperationMessages(operations[operationName]))
|
|
151
|
+
.flat();
|
|
152
|
+
for (const operationMessage of operationMessages) {
|
|
153
|
+
channelMessageNames.add(operationMessage);
|
|
11
154
|
}
|
|
155
|
+
const schemaNames = new Set();
|
|
156
|
+
for (const messageName of channelMessageNames) {
|
|
157
|
+
const schemaName = messageLookups[messageName]?.schemaName;
|
|
158
|
+
if (schemaName !== undefined) {
|
|
159
|
+
schemaNames.add(schemaName);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
channelLookups.push({
|
|
163
|
+
name: channelName,
|
|
164
|
+
description: channel.description,
|
|
165
|
+
address: channel.address,
|
|
166
|
+
messageNames: Array.from(channelMessageNames).sort(),
|
|
167
|
+
schemaNames: Array.from(schemaNames).sort(),
|
|
168
|
+
operationNames: [...operationNames].sort()
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
operations: operationLookups.sort((a, b) => a.name.localeCompare(b.name)),
|
|
173
|
+
operationByName: new Map(operationLookups.map(operation => [operation.name, operation])),
|
|
174
|
+
channels: channelLookups.sort((a, b) => a.name.localeCompare(b.name)),
|
|
175
|
+
messages: Object.values(messageLookups).sort((a, b) => a.name.localeCompare(b.name)),
|
|
176
|
+
messageByName: new Map(Object.values(messageLookups).map(message => [message.name, message])),
|
|
177
|
+
schemas: Object.keys(schemas).sort(),
|
|
178
|
+
schemaEntries: schemas
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function renderLinkedList(items, prefix, emptyLabel) {
|
|
182
|
+
if (items.length === 0) {
|
|
183
|
+
return `<p class="muted">${escapeHTML(emptyLabel)}</p>`;
|
|
184
|
+
}
|
|
185
|
+
const links = items
|
|
186
|
+
.map(item => `<a href="#${toAnchorId(prefix, item)}">${escapeHTML(item)}</a>`)
|
|
187
|
+
.join(', ');
|
|
188
|
+
return `<p class="links">${links}</p>`;
|
|
189
|
+
}
|
|
190
|
+
function resolveSchemaRef(schema, schemaEntries, seenRefs) {
|
|
191
|
+
if (!isRecord(schema)) {
|
|
192
|
+
return { schema };
|
|
193
|
+
}
|
|
194
|
+
if (!('$ref' in schema)) {
|
|
195
|
+
return { schema };
|
|
196
|
+
}
|
|
197
|
+
const refValue = String(schema.$ref);
|
|
198
|
+
const schemaName = resolveSchemaNameFromRef(refValue);
|
|
199
|
+
if (schemaName === undefined) {
|
|
200
|
+
return { schema };
|
|
201
|
+
}
|
|
202
|
+
if (seenRefs.has(schemaName)) {
|
|
203
|
+
return { schema, refName: schemaName, circular: true };
|
|
204
|
+
}
|
|
205
|
+
const resolved = schemaEntries[schemaName];
|
|
206
|
+
if (resolved === undefined) {
|
|
207
|
+
return { schema, refName: schemaName };
|
|
208
|
+
}
|
|
209
|
+
const nextSeen = new Set(seenRefs);
|
|
210
|
+
nextSeen.add(schemaName);
|
|
211
|
+
return { schema: resolved, refName: schemaName, circular: false };
|
|
212
|
+
}
|
|
213
|
+
function schemaToExample(schema, schemaEntries, seenRefs) {
|
|
214
|
+
const resolved = resolveSchemaRef(schema, schemaEntries, seenRefs);
|
|
215
|
+
if (resolved.circular === true && resolved.refName !== undefined) {
|
|
216
|
+
return `[Circular:${resolved.refName}]`;
|
|
217
|
+
}
|
|
218
|
+
if (!isRecord(resolved.schema)) {
|
|
219
|
+
return resolved.schema;
|
|
220
|
+
}
|
|
221
|
+
const record = resolved.schema;
|
|
222
|
+
if (record.example !== undefined) {
|
|
223
|
+
return record.example;
|
|
224
|
+
}
|
|
225
|
+
if (record.default !== undefined) {
|
|
226
|
+
return record.default;
|
|
227
|
+
}
|
|
228
|
+
if (Array.isArray(record.enum)) {
|
|
229
|
+
return record.enum.length > 0 ? record.enum[0] : null;
|
|
230
|
+
}
|
|
231
|
+
if (Array.isArray(record.oneOf)) {
|
|
232
|
+
return schemaToExample(record.oneOf[0], schemaEntries, seenRefs);
|
|
233
|
+
}
|
|
234
|
+
if (Array.isArray(record.anyOf)) {
|
|
235
|
+
return schemaToExample(record.anyOf[0], schemaEntries, seenRefs);
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(record.allOf)) {
|
|
238
|
+
return schemaToExample(record.allOf[0], schemaEntries, seenRefs);
|
|
239
|
+
}
|
|
240
|
+
if (record.type === 'array') {
|
|
241
|
+
const itemExample = record.items !== undefined
|
|
242
|
+
? schemaToExample(record.items, schemaEntries, seenRefs)
|
|
243
|
+
: null;
|
|
244
|
+
return [itemExample];
|
|
245
|
+
}
|
|
246
|
+
if (record.type === 'object' || isRecord(record.properties)) {
|
|
247
|
+
const properties = isRecord(record.properties) ? record.properties : {};
|
|
248
|
+
const example = {};
|
|
249
|
+
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
250
|
+
example[propertyName] = schemaToExample(propertySchema, schemaEntries, seenRefs);
|
|
251
|
+
}
|
|
252
|
+
if (record.additionalProperties === true) {
|
|
253
|
+
example.additionalProperty = 'string';
|
|
254
|
+
}
|
|
255
|
+
else if (isRecord(record.additionalProperties)) {
|
|
256
|
+
example.additionalProperty = schemaToExample(record.additionalProperties, schemaEntries, seenRefs);
|
|
257
|
+
}
|
|
258
|
+
return example;
|
|
259
|
+
}
|
|
260
|
+
if (record.type === 'integer' || record.type === 'number') {
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
if (record.type === 'boolean') {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (record.type === 'string') {
|
|
267
|
+
return 'string';
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
function renderSchemaView(schema, schemaEntries) {
|
|
272
|
+
const seenRefs = new Set();
|
|
273
|
+
const example = schemaToExample(schema, schemaEntries, seenRefs);
|
|
274
|
+
return `
|
|
275
|
+
<div class="schema-block">
|
|
276
|
+
<h4>Example</h4>
|
|
277
|
+
${valueToCodeBlock(example)}
|
|
278
|
+
</div>
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
function renderMessageView(message, schemaEntries) {
|
|
282
|
+
if (message === undefined) {
|
|
283
|
+
return '<p class="muted">No message details available.</p>';
|
|
284
|
+
}
|
|
285
|
+
const contentType = typeof message.contentType === 'string' ? message.contentType : '';
|
|
286
|
+
const summary = typeof message.summary === 'string' ? message.summary : '';
|
|
287
|
+
const description = typeof message.description === 'string' ? message.description : '';
|
|
288
|
+
return `
|
|
289
|
+
<div class="meta-block">
|
|
290
|
+
${contentType !== '' ? `<p><strong>Content Type:</strong> ${escapeHTML(contentType)}</p>` : ''}
|
|
291
|
+
${summary !== '' ? `<p>${escapeHTML(summary)}</p>` : ''}
|
|
292
|
+
${description !== '' ? `<p>${escapeHTML(description)}</p>` : ''}
|
|
293
|
+
</div>
|
|
294
|
+
<div class="payload-block">
|
|
295
|
+
<h4>Payload</h4>
|
|
296
|
+
${renderSchemaView(message.payload, schemaEntries)}
|
|
297
|
+
</div>
|
|
298
|
+
`;
|
|
299
|
+
}
|
|
300
|
+
function renderMessageInlineDetails(messageNames, messageByName, schemaEntries) {
|
|
301
|
+
const messageBlocks = messageNames.map((messageName) => {
|
|
302
|
+
const message = messageByName.get(messageName);
|
|
303
|
+
if (message === undefined) {
|
|
304
|
+
return '';
|
|
305
|
+
}
|
|
306
|
+
const messageSchema = message.schemaName ?? '';
|
|
307
|
+
return `
|
|
308
|
+
<details class="sub-item">
|
|
309
|
+
<summary>
|
|
310
|
+
<span class="title">${escapeHTML(message.name)}</span>
|
|
311
|
+
${messageSchema !== '' ? `<span class="muted">Schema: ${escapeHTML(messageSchema)}</span>` : ''}
|
|
312
|
+
</summary>
|
|
313
|
+
<div class="detail-grid">
|
|
314
|
+
<div>
|
|
315
|
+
<h4>Schema</h4>
|
|
316
|
+
${messageSchema !== '' ? renderLinkedList([messageSchema], 'schema', 'No schema linked.') : '<p class="muted">No schema linked.</p>'}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
${renderMessageView(message.message, schemaEntries)}
|
|
320
|
+
</details>
|
|
321
|
+
`;
|
|
12
322
|
});
|
|
13
|
-
|
|
323
|
+
return messageBlocks.filter(Boolean).join('');
|
|
324
|
+
}
|
|
325
|
+
export function generateAsyncAPIHTML(yaml) {
|
|
326
|
+
const document = parseAsyncApiDocument(yaml);
|
|
327
|
+
const info = document.info;
|
|
328
|
+
const lookups = buildLookups(document);
|
|
329
|
+
const title = escapeHTML(info.title);
|
|
330
|
+
const version = escapeHTML(info.version);
|
|
331
|
+
const description = escapeHTML(info.description ?? 'No description provided.');
|
|
332
|
+
const asyncApiVersion = escapeHTML(document.asyncapi);
|
|
333
|
+
const channelBlocks = lookups.channels.map((channel) => {
|
|
334
|
+
const channelAddress = channel.address ?? '';
|
|
335
|
+
const channelDescription = channel.description ?? '';
|
|
336
|
+
const inlineOperations = channel.operationNames
|
|
337
|
+
.map((operationName) => {
|
|
338
|
+
const operation = lookups.operationByName.get(operationName);
|
|
339
|
+
if (operation === undefined) {
|
|
340
|
+
return '';
|
|
341
|
+
}
|
|
342
|
+
const operationAction = operation.action ?? '';
|
|
343
|
+
const operationSummary = operation.summary ?? '';
|
|
344
|
+
const operationDescription = operation.description ?? '';
|
|
345
|
+
const operationChannel = operation.channelName ?? '';
|
|
346
|
+
const operationMessageDetails = renderMessageInlineDetails(operation.messageNames, lookups.messageByName, lookups.schemaEntries);
|
|
347
|
+
return `
|
|
348
|
+
<details class="sub-item">
|
|
349
|
+
<summary>
|
|
350
|
+
<span class="title">${escapeHTML(operation.name)}</span>
|
|
351
|
+
${operationAction !== '' ? `<span class="badge">${escapeHTML(operationAction)}</span>` : ''}
|
|
352
|
+
</summary>
|
|
353
|
+
${operationSummary !== '' ? `<p>${escapeHTML(operationSummary)}</p>` : ''}
|
|
354
|
+
${operationDescription !== '' ? `<p>${escapeHTML(operationDescription)}</p>` : ''}
|
|
355
|
+
<div class="detail-grid">
|
|
356
|
+
<div>
|
|
357
|
+
<h4>Channel</h4>
|
|
358
|
+
${operationChannel !== '' ? renderLinkedList([operationChannel], 'channel', 'No channel linked.') : '<p class="muted">No channel linked.</p>'}
|
|
359
|
+
</div>
|
|
360
|
+
<div>
|
|
361
|
+
<h4>Messages</h4>
|
|
362
|
+
${renderLinkedList(operation.messageNames, 'message', 'No messages linked.')}
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="inline-stack">
|
|
366
|
+
<h4>Message Details</h4>
|
|
367
|
+
${operationMessageDetails || '<p class="muted">No message details available.</p>'}
|
|
368
|
+
</div>
|
|
369
|
+
</details>
|
|
370
|
+
`;
|
|
371
|
+
})
|
|
372
|
+
.filter(Boolean)
|
|
373
|
+
.join('');
|
|
374
|
+
return `
|
|
375
|
+
<details class="item" id="${toAnchorId('channel', channel.name)}">
|
|
376
|
+
<summary>
|
|
377
|
+
<span class="title">${escapeHTML(channel.name)}</span>
|
|
378
|
+
${channelAddress !== '' ? `<span class="muted">${escapeHTML(channelAddress)}</span>` : ''}
|
|
379
|
+
</summary>
|
|
380
|
+
${channelDescription !== '' ? `<p>${escapeHTML(channelDescription)}</p>` : '<p class="muted">No description provided.</p>'}
|
|
381
|
+
<div class="detail-grid">
|
|
382
|
+
<div>
|
|
383
|
+
<h4>Operations</h4>
|
|
384
|
+
${renderLinkedList(channel.operationNames, 'operation', 'No operations linked.')}
|
|
385
|
+
</div>
|
|
386
|
+
<div>
|
|
387
|
+
<h4>Messages</h4>
|
|
388
|
+
${renderLinkedList(channel.messageNames, 'message', 'No messages linked.')}
|
|
389
|
+
</div>
|
|
390
|
+
<div>
|
|
391
|
+
<h4>Schemas</h4>
|
|
392
|
+
${renderLinkedList(channel.schemaNames, 'schema', 'No schemas linked.')}
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="inline-stack">
|
|
396
|
+
<h4>Operation Details</h4>
|
|
397
|
+
${inlineOperations || '<p class="muted">No operation details available.</p>'}
|
|
398
|
+
</div>
|
|
399
|
+
</details>
|
|
400
|
+
`;
|
|
401
|
+
}).join('');
|
|
402
|
+
const operationBlocks = lookups.operations.map((operation) => {
|
|
403
|
+
const operationAction = operation.action ?? '';
|
|
404
|
+
const operationSummary = operation.summary ?? '';
|
|
405
|
+
const operationDescription = operation.description ?? '';
|
|
406
|
+
const operationChannel = operation.channelName ?? '';
|
|
407
|
+
return `
|
|
408
|
+
<details class="item" id="${toAnchorId('operation', operation.name)}">
|
|
409
|
+
<summary>
|
|
410
|
+
<span class="title">${escapeHTML(operation.name)}</span>
|
|
411
|
+
${operationAction !== '' ? `<span class="badge">${escapeHTML(operationAction)}</span>` : ''}
|
|
412
|
+
</summary>
|
|
413
|
+
${operationSummary !== '' ? `<p>${escapeHTML(operationSummary)}</p>` : ''}
|
|
414
|
+
${operationDescription !== '' ? `<p>${escapeHTML(operationDescription)}</p>` : ''}
|
|
415
|
+
<div class="detail-grid">
|
|
416
|
+
<div>
|
|
417
|
+
<h4>Channel</h4>
|
|
418
|
+
${operationChannel !== '' ? renderLinkedList([operationChannel], 'channel', 'No channel linked.') : '<p class="muted">No channel linked.</p>'}
|
|
419
|
+
</div>
|
|
420
|
+
<div>
|
|
421
|
+
<h4>Messages</h4>
|
|
422
|
+
${renderLinkedList(operation.messageNames, 'message', 'No messages linked.')}
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
</details>
|
|
426
|
+
`;
|
|
427
|
+
}).join('');
|
|
428
|
+
const messageBlocks = lookups.messages.map((message) => {
|
|
429
|
+
const messageSchema = message.schemaName ?? '';
|
|
430
|
+
return `
|
|
431
|
+
<details class="item" id="${toAnchorId('message', message.name)}">
|
|
432
|
+
<summary>
|
|
433
|
+
<span class="title">${escapeHTML(message.name)}</span>
|
|
434
|
+
${messageSchema !== '' ? `<span class="muted">Schema: ${escapeHTML(messageSchema)}</span>` : ''}
|
|
435
|
+
</summary>
|
|
436
|
+
<div class="detail-grid">
|
|
437
|
+
<div>
|
|
438
|
+
<h4>Schema</h4>
|
|
439
|
+
${messageSchema !== '' ? renderLinkedList([messageSchema], 'schema', 'No schema linked.') : '<p class="muted">No schema linked.</p>'}
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
${renderMessageView(message.message, lookups.schemaEntries)}
|
|
443
|
+
</details>
|
|
444
|
+
`;
|
|
445
|
+
}).join('');
|
|
446
|
+
const schemaBlocks = lookups.schemas.map((schemaName) => {
|
|
447
|
+
const schemaValue = lookups.schemaEntries[schemaName];
|
|
448
|
+
const description = isRecord(schemaValue) && typeof schemaValue.description === 'string'
|
|
449
|
+
? schemaValue.description
|
|
450
|
+
: '';
|
|
451
|
+
return `
|
|
452
|
+
<details class="item" id="${toAnchorId('schema', schemaName)}">
|
|
453
|
+
<summary>
|
|
454
|
+
<span class="title">${escapeHTML(schemaName)}</span>
|
|
455
|
+
</summary>
|
|
456
|
+
${description !== '' ? `<p>${escapeHTML(description)}</p>` : ''}
|
|
457
|
+
${renderSchemaView(schemaValue, lookups.schemaEntries)}
|
|
458
|
+
</details>
|
|
459
|
+
`;
|
|
460
|
+
}).join('');
|
|
461
|
+
const html = `<!doctype html>
|
|
462
|
+
<html lang="en">
|
|
463
|
+
<head>
|
|
464
|
+
<meta charset="utf-8" />
|
|
465
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
466
|
+
<title>${title}</title>
|
|
467
|
+
<style>
|
|
468
|
+
:root {
|
|
469
|
+
color-scheme: dark;
|
|
470
|
+
--bg: #0f1115;
|
|
471
|
+
--fg: #eef2f7;
|
|
472
|
+
--muted: #9aa4b2;
|
|
473
|
+
--border: #2b3139;
|
|
474
|
+
--panel: #171b21;
|
|
475
|
+
--accent: #4cc9f0;
|
|
476
|
+
--accent-soft: #1f2a33;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
@media (prefers-color-scheme: dark) {
|
|
480
|
+
:root {
|
|
481
|
+
--bg: #0f1115;
|
|
482
|
+
--fg: #eef2f7;
|
|
483
|
+
--muted: #9aa4b2;
|
|
484
|
+
--border: #2b3139;
|
|
485
|
+
--panel: #171b21;
|
|
486
|
+
--accent: #4cc9f0;
|
|
487
|
+
--accent-soft: #1f2a33;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
body {
|
|
492
|
+
margin: 0;
|
|
493
|
+
padding: 32px;
|
|
494
|
+
background: var(--bg);
|
|
495
|
+
color: var(--fg);
|
|
496
|
+
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
497
|
+
line-height: 1.6;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
main {
|
|
501
|
+
width: 100%;
|
|
502
|
+
margin: 0;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
section {
|
|
506
|
+
margin: 24px 0 40px;
|
|
507
|
+
background: var(--panel);
|
|
508
|
+
border: 1px solid var(--border);
|
|
509
|
+
border-radius: 16px;
|
|
510
|
+
padding: 20px;
|
|
511
|
+
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.08);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
h1, h2, h3, h4 {
|
|
515
|
+
margin: 0 0 12px;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
h1 {
|
|
519
|
+
font-size: 2.4rem;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
nav {
|
|
523
|
+
display: flex;
|
|
524
|
+
gap: 12px;
|
|
525
|
+
flex-wrap: wrap;
|
|
526
|
+
margin: 16px 0 0;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
nav a {
|
|
530
|
+
background: var(--accent-soft);
|
|
531
|
+
color: var(--accent);
|
|
532
|
+
text-decoration: none;
|
|
533
|
+
padding: 6px 12px;
|
|
534
|
+
border-radius: 999px;
|
|
535
|
+
font-weight: 600;
|
|
536
|
+
border: 1px solid transparent;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
nav a:hover {
|
|
540
|
+
border-color: var(--accent);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.muted {
|
|
544
|
+
color: var(--muted);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.item {
|
|
548
|
+
border: 1px solid var(--border);
|
|
549
|
+
border-radius: 12px;
|
|
550
|
+
padding: 12px 14px;
|
|
551
|
+
margin-bottom: 12px;
|
|
552
|
+
background: color-mix(in srgb, var(--panel) 85%, var(--accent-soft));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
summary {
|
|
556
|
+
cursor: pointer;
|
|
557
|
+
display: flex;
|
|
558
|
+
justify-content: space-between;
|
|
559
|
+
align-items: center;
|
|
560
|
+
gap: 12px;
|
|
561
|
+
font-weight: 600;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
summary::-webkit-details-marker {
|
|
565
|
+
display: none;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.title {
|
|
569
|
+
font-size: 1.1rem;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.badge {
|
|
573
|
+
background: var(--accent-soft);
|
|
574
|
+
color: var(--accent);
|
|
575
|
+
padding: 2px 10px;
|
|
576
|
+
border-radius: 999px;
|
|
577
|
+
font-size: 0.85rem;
|
|
578
|
+
font-weight: 600;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.detail-grid {
|
|
582
|
+
display: grid;
|
|
583
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
584
|
+
gap: 16px;
|
|
585
|
+
margin: 12px 0;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.sub-item {
|
|
589
|
+
border: 1px dashed var(--border);
|
|
590
|
+
border-radius: 10px;
|
|
591
|
+
padding: 10px 12px;
|
|
592
|
+
margin: 10px 0 0;
|
|
593
|
+
background: color-mix(in srgb, var(--panel) 92%, var(--accent-soft));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.sub-item summary {
|
|
597
|
+
font-size: 0.98rem;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.inline-stack {
|
|
601
|
+
margin-top: 16px;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.meta-block {
|
|
605
|
+
margin: 8px 0 12px;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.payload-block {
|
|
609
|
+
margin-top: 12px;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.links {
|
|
613
|
+
margin: 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.links a {
|
|
617
|
+
color: var(--accent);
|
|
618
|
+
text-decoration: none;
|
|
619
|
+
font-weight: 600;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.links a:hover {
|
|
623
|
+
text-decoration: underline;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
pre {
|
|
627
|
+
overflow-x: auto;
|
|
628
|
+
background: #111827;
|
|
629
|
+
color: #e2e8f0;
|
|
630
|
+
border-left: 4px solid var(--accent);
|
|
631
|
+
box-shadow: inset 0 0 0 1px #1f2937;
|
|
632
|
+
tab-size: 2;
|
|
633
|
+
border-radius: 10px;
|
|
634
|
+
padding: 12px;
|
|
635
|
+
font-size: 0.85rem;
|
|
636
|
+
margin: 12px 0 0;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@media (max-width: 720px) {
|
|
640
|
+
body {
|
|
641
|
+
padding: 20px;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
</style>
|
|
645
|
+
</head>
|
|
646
|
+
<body>
|
|
647
|
+
<main>
|
|
648
|
+
<section id="overview">
|
|
649
|
+
<h1>${title}</h1>
|
|
650
|
+
<p class="muted">AsyncAPI version: ${asyncApiVersion}</p>
|
|
651
|
+
<p><strong>Version:</strong> ${version}</p>
|
|
652
|
+
<p>${description}</p>
|
|
653
|
+
<nav>
|
|
654
|
+
<a href="#channels">Channels</a>
|
|
655
|
+
<a href="#operations">Operations</a>
|
|
656
|
+
<a href="#messages">Messages</a>
|
|
657
|
+
<a href="#schemas">Schemas</a>
|
|
658
|
+
</nav>
|
|
659
|
+
</section>
|
|
660
|
+
|
|
661
|
+
<section id="channels">
|
|
662
|
+
<h2>Channels</h2>
|
|
663
|
+
${channelBlocks || '<p class="muted">No channels found.</p>'}
|
|
664
|
+
</section>
|
|
665
|
+
|
|
666
|
+
<section id="operations">
|
|
667
|
+
<h2>Operations</h2>
|
|
668
|
+
${operationBlocks || '<p class="muted">No operations found.</p>'}
|
|
669
|
+
</section>
|
|
670
|
+
|
|
671
|
+
<section id="messages">
|
|
672
|
+
<h2>Messages</h2>
|
|
673
|
+
${messageBlocks || '<p class="muted">No messages found.</p>'}
|
|
674
|
+
</section>
|
|
675
|
+
|
|
676
|
+
<section id="schemas">
|
|
677
|
+
<h2>Schemas</h2>
|
|
678
|
+
${schemaBlocks || '<p class="muted">No schemas found.</p>'}
|
|
679
|
+
</section>
|
|
680
|
+
</main>
|
|
681
|
+
</body>
|
|
682
|
+
</html>
|
|
683
|
+
`;
|
|
684
|
+
return html;
|
|
14
685
|
}
|
|
15
686
|
//# sourceMappingURL=generate-async-api-html.js.map
|