@amqp-contract/asyncapi 0.5.0 → 0.7.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/README.md +23 -14
- package/dist/index.cjs +44 -10
- package/dist/index.d.cts +4 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +4 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +44 -10
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +6 -6
- package/package.json +16 -10
package/README.md
CHANGED
|
@@ -19,11 +19,11 @@ pnpm add @amqp-contract/asyncapi
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
|
-
import { AsyncAPIGenerator } from
|
|
23
|
-
import { ZodToJsonSchemaConverter } from
|
|
24
|
-
import { writeFileSync } from
|
|
22
|
+
import { AsyncAPIGenerator } from "@amqp-contract/asyncapi";
|
|
23
|
+
import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
|
|
24
|
+
import { writeFileSync } from "fs";
|
|
25
25
|
|
|
26
|
-
import { contract } from
|
|
26
|
+
import { contract } from "./contract";
|
|
27
27
|
|
|
28
28
|
// Create generator with schema converters
|
|
29
29
|
const generator = new AsyncAPIGenerator({
|
|
@@ -33,20 +33,20 @@ const generator = new AsyncAPIGenerator({
|
|
|
33
33
|
// Generate AsyncAPI specification
|
|
34
34
|
const asyncAPISpec = await generator.generate(contract, {
|
|
35
35
|
info: {
|
|
36
|
-
title:
|
|
37
|
-
version:
|
|
38
|
-
description:
|
|
36
|
+
title: "My AMQP API",
|
|
37
|
+
version: "1.0.0",
|
|
38
|
+
description: "Type-safe AMQP messaging API",
|
|
39
39
|
},
|
|
40
40
|
servers: {
|
|
41
41
|
development: {
|
|
42
|
-
host:
|
|
43
|
-
protocol:
|
|
44
|
-
description:
|
|
42
|
+
host: "localhost:5672",
|
|
43
|
+
protocol: "amqp",
|
|
44
|
+
description: "Development RabbitMQ server",
|
|
45
45
|
},
|
|
46
46
|
production: {
|
|
47
|
-
host:
|
|
48
|
-
protocol:
|
|
49
|
-
description:
|
|
47
|
+
host: "rabbitmq.example.com:5672",
|
|
48
|
+
protocol: "amqp",
|
|
49
|
+
description: "Production RabbitMQ server",
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
52
|
});
|
|
@@ -55,9 +55,18 @@ const asyncAPISpec = await generator.generate(contract, {
|
|
|
55
55
|
console.log(JSON.stringify(asyncAPISpec, null, 2));
|
|
56
56
|
|
|
57
57
|
// Or write to file
|
|
58
|
-
writeFileSync(
|
|
58
|
+
writeFileSync("asyncapi.json", JSON.stringify(asyncAPISpec, null, 2));
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- ✅ **AsyncAPI 3.0 compliant** with proper AMQP bindings (v0.3.0)
|
|
64
|
+
- ✅ **Schema validation** - Converts Zod, Valibot, and ArkType schemas to JSON Schema
|
|
65
|
+
- ✅ **Queue-exchange binding documentation** in channel descriptions
|
|
66
|
+
- ✅ **Type-safe** with full TypeScript support
|
|
67
|
+
|
|
68
|
+
For examples and detailed guides, see the [documentation](https://btravers.github.io/amqp-contract/api/asyncapi).
|
|
69
|
+
|
|
61
70
|
## API
|
|
62
71
|
|
|
63
72
|
For complete API documentation, see the [AsyncAPI API Reference](https://btravers.github.io/amqp-contract/api/asyncapi).
|
package/dist/index.cjs
CHANGED
|
@@ -112,7 +112,8 @@ var AsyncAPIGenerator = class {
|
|
|
112
112
|
channelMessages[messageName] = await this.convertMessage(message);
|
|
113
113
|
convertedMessages[messageName] = await this.convertMessage(message);
|
|
114
114
|
}
|
|
115
|
-
const
|
|
115
|
+
const queueBindings = this.getQueueBindings(queue, contract);
|
|
116
|
+
const channel = { ...this.queueToChannel(queue, queueBindings) };
|
|
116
117
|
if (Object.keys(channelMessages).length > 0) channel.messages = channelMessages;
|
|
117
118
|
convertedChannels[queueName] = channel;
|
|
118
119
|
}
|
|
@@ -130,12 +131,23 @@ var AsyncAPIGenerator = class {
|
|
|
130
131
|
if (contract.publishers) for (const [publisherName, publisher] of Object.entries(contract.publishers)) {
|
|
131
132
|
const exchangeName = this.getExchangeName(publisher.exchange, contract);
|
|
132
133
|
const messageName = `${publisherName}Message`;
|
|
133
|
-
convertedOperations[publisherName] = {
|
|
134
|
+
if (publisher.routingKey) convertedOperations[publisherName] = {
|
|
134
135
|
action: "send",
|
|
135
136
|
channel: { $ref: `#/channels/${exchangeName}` },
|
|
136
137
|
messages: [{ $ref: `#/channels/${exchangeName}/messages/${messageName}` }],
|
|
137
138
|
summary: `Publish to ${publisher.exchange.name}`,
|
|
138
|
-
|
|
139
|
+
description: `Routing key: ${publisher.routingKey}`,
|
|
140
|
+
bindings: { amqp: {
|
|
141
|
+
cc: [publisher.routingKey],
|
|
142
|
+
deliveryMode: 2,
|
|
143
|
+
bindingVersion: "0.3.0"
|
|
144
|
+
} }
|
|
145
|
+
};
|
|
146
|
+
else convertedOperations[publisherName] = {
|
|
147
|
+
action: "send",
|
|
148
|
+
channel: { $ref: `#/channels/${exchangeName}` },
|
|
149
|
+
messages: [{ $ref: `#/channels/${exchangeName}/messages/${messageName}` }],
|
|
150
|
+
summary: `Publish to ${publisher.exchange.name}`
|
|
139
151
|
};
|
|
140
152
|
}
|
|
141
153
|
if (contract.consumers) for (const [consumerName, consumer] of Object.entries(contract.consumers)) {
|
|
@@ -145,7 +157,8 @@ var AsyncAPIGenerator = class {
|
|
|
145
157
|
action: "receive",
|
|
146
158
|
channel: { $ref: `#/channels/${queueName}` },
|
|
147
159
|
messages: [{ $ref: `#/channels/${queueName}/messages/${messageName}` }],
|
|
148
|
-
summary: `Consume from ${consumer.queue.name}
|
|
160
|
+
summary: `Consume from ${consumer.queue.name}`,
|
|
161
|
+
bindings: { amqp: { bindingVersion: "0.3.0" } }
|
|
149
162
|
};
|
|
150
163
|
}
|
|
151
164
|
return {
|
|
@@ -176,11 +189,20 @@ var AsyncAPIGenerator = class {
|
|
|
176
189
|
/**
|
|
177
190
|
* Convert a queue definition to AsyncAPI ChannelObject
|
|
178
191
|
*/
|
|
179
|
-
queueToChannel(queue) {
|
|
192
|
+
queueToChannel(queue, bindings = []) {
|
|
193
|
+
let description = `AMQP Queue: ${queue.name}`;
|
|
194
|
+
if (bindings.length > 0) {
|
|
195
|
+
const bindingDescriptions = bindings.map((binding) => {
|
|
196
|
+
const exchangeName = binding.exchange.name;
|
|
197
|
+
const routingKey = "routingKey" in binding ? binding.routingKey : void 0;
|
|
198
|
+
return routingKey ? `bound to exchange '${exchangeName}' with routing key '${routingKey}'` : `bound to exchange '${exchangeName}'`;
|
|
199
|
+
}).join(", ");
|
|
200
|
+
description += ` (${bindingDescriptions})`;
|
|
201
|
+
}
|
|
180
202
|
return {
|
|
181
203
|
address: queue.name,
|
|
182
204
|
title: queue.name,
|
|
183
|
-
description
|
|
205
|
+
description,
|
|
184
206
|
bindings: { amqp: {
|
|
185
207
|
is: "queue",
|
|
186
208
|
queue: {
|
|
@@ -188,8 +210,9 @@ var AsyncAPIGenerator = class {
|
|
|
188
210
|
durable: queue.durable ?? false,
|
|
189
211
|
exclusive: queue.exclusive ?? false,
|
|
190
212
|
autoDelete: queue.autoDelete ?? false,
|
|
191
|
-
|
|
192
|
-
}
|
|
213
|
+
vhost: "/"
|
|
214
|
+
},
|
|
215
|
+
bindingVersion: "0.3.0"
|
|
193
216
|
} }
|
|
194
217
|
};
|
|
195
218
|
}
|
|
@@ -208,8 +231,9 @@ var AsyncAPIGenerator = class {
|
|
|
208
231
|
type: exchange.type,
|
|
209
232
|
durable: exchange.durable ?? false,
|
|
210
233
|
autoDelete: exchange.autoDelete ?? false,
|
|
211
|
-
|
|
212
|
-
}
|
|
234
|
+
vhost: "/"
|
|
235
|
+
},
|
|
236
|
+
bindingVersion: "0.3.0"
|
|
213
237
|
} }
|
|
214
238
|
};
|
|
215
239
|
}
|
|
@@ -232,6 +256,16 @@ var AsyncAPIGenerator = class {
|
|
|
232
256
|
return queue.name;
|
|
233
257
|
}
|
|
234
258
|
/**
|
|
259
|
+
* Get all bindings for a queue from the contract
|
|
260
|
+
*/
|
|
261
|
+
getQueueBindings(queue, contract) {
|
|
262
|
+
const result = [];
|
|
263
|
+
if (contract.bindings) {
|
|
264
|
+
for (const binding of Object.values(contract.bindings)) if (binding.type === "queue" && binding.queue.name === queue.name) result.push(binding);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
235
269
|
* Convert a Standard Schema to JSON Schema using oRPC converters
|
|
236
270
|
*/
|
|
237
271
|
async convertSchema(schema, strategy) {
|
package/dist/index.d.cts
CHANGED
|
@@ -133,6 +133,10 @@ declare class AsyncAPIGenerator {
|
|
|
133
133
|
* Get the name/key of a queue from the contract
|
|
134
134
|
*/
|
|
135
135
|
private getQueueName;
|
|
136
|
+
/**
|
|
137
|
+
* Get all bindings for a queue from the contract
|
|
138
|
+
*/
|
|
139
|
+
private getQueueBindings;
|
|
136
140
|
/**
|
|
137
141
|
* Convert a Standard Schema to JSON Schema using oRPC converters
|
|
138
142
|
*/
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA+BA;AAYA;AAiDA;;;;;;;;;KA7DY,wBAAA;;;;;qBAKS;;;;;;KAOT,gCAAA,GAAmC,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiDvC,iBAAA;;;;;;;wBAQU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiCT,6BACD,mCACR,QAAQ"}
|
package/dist/index.d.mts
CHANGED
|
@@ -133,6 +133,10 @@ declare class AsyncAPIGenerator {
|
|
|
133
133
|
* Get the name/key of a queue from the contract
|
|
134
134
|
*/
|
|
135
135
|
private getQueueName;
|
|
136
|
+
/**
|
|
137
|
+
* Get all bindings for a queue from the contract
|
|
138
|
+
*/
|
|
139
|
+
private getQueueBindings;
|
|
136
140
|
/**
|
|
137
141
|
* Convert a Standard Schema to JSON Schema using oRPC converters
|
|
138
142
|
*/
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA+BA;AAYA;AAiDA;;;;;;;;;KA7DY,wBAAA;;;;;qBAKS;;;;;;KAOT,gCAAA,GAAmC,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiDvC,iBAAA;;;;;;;wBAQU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiCT,6BACD,mCACR,QAAQ"}
|
package/dist/index.mjs
CHANGED
|
@@ -111,7 +111,8 @@ var AsyncAPIGenerator = class {
|
|
|
111
111
|
channelMessages[messageName] = await this.convertMessage(message);
|
|
112
112
|
convertedMessages[messageName] = await this.convertMessage(message);
|
|
113
113
|
}
|
|
114
|
-
const
|
|
114
|
+
const queueBindings = this.getQueueBindings(queue, contract);
|
|
115
|
+
const channel = { ...this.queueToChannel(queue, queueBindings) };
|
|
115
116
|
if (Object.keys(channelMessages).length > 0) channel.messages = channelMessages;
|
|
116
117
|
convertedChannels[queueName] = channel;
|
|
117
118
|
}
|
|
@@ -129,12 +130,23 @@ var AsyncAPIGenerator = class {
|
|
|
129
130
|
if (contract.publishers) for (const [publisherName, publisher] of Object.entries(contract.publishers)) {
|
|
130
131
|
const exchangeName = this.getExchangeName(publisher.exchange, contract);
|
|
131
132
|
const messageName = `${publisherName}Message`;
|
|
132
|
-
convertedOperations[publisherName] = {
|
|
133
|
+
if (publisher.routingKey) convertedOperations[publisherName] = {
|
|
133
134
|
action: "send",
|
|
134
135
|
channel: { $ref: `#/channels/${exchangeName}` },
|
|
135
136
|
messages: [{ $ref: `#/channels/${exchangeName}/messages/${messageName}` }],
|
|
136
137
|
summary: `Publish to ${publisher.exchange.name}`,
|
|
137
|
-
|
|
138
|
+
description: `Routing key: ${publisher.routingKey}`,
|
|
139
|
+
bindings: { amqp: {
|
|
140
|
+
cc: [publisher.routingKey],
|
|
141
|
+
deliveryMode: 2,
|
|
142
|
+
bindingVersion: "0.3.0"
|
|
143
|
+
} }
|
|
144
|
+
};
|
|
145
|
+
else convertedOperations[publisherName] = {
|
|
146
|
+
action: "send",
|
|
147
|
+
channel: { $ref: `#/channels/${exchangeName}` },
|
|
148
|
+
messages: [{ $ref: `#/channels/${exchangeName}/messages/${messageName}` }],
|
|
149
|
+
summary: `Publish to ${publisher.exchange.name}`
|
|
138
150
|
};
|
|
139
151
|
}
|
|
140
152
|
if (contract.consumers) for (const [consumerName, consumer] of Object.entries(contract.consumers)) {
|
|
@@ -144,7 +156,8 @@ var AsyncAPIGenerator = class {
|
|
|
144
156
|
action: "receive",
|
|
145
157
|
channel: { $ref: `#/channels/${queueName}` },
|
|
146
158
|
messages: [{ $ref: `#/channels/${queueName}/messages/${messageName}` }],
|
|
147
|
-
summary: `Consume from ${consumer.queue.name}
|
|
159
|
+
summary: `Consume from ${consumer.queue.name}`,
|
|
160
|
+
bindings: { amqp: { bindingVersion: "0.3.0" } }
|
|
148
161
|
};
|
|
149
162
|
}
|
|
150
163
|
return {
|
|
@@ -175,11 +188,20 @@ var AsyncAPIGenerator = class {
|
|
|
175
188
|
/**
|
|
176
189
|
* Convert a queue definition to AsyncAPI ChannelObject
|
|
177
190
|
*/
|
|
178
|
-
queueToChannel(queue) {
|
|
191
|
+
queueToChannel(queue, bindings = []) {
|
|
192
|
+
let description = `AMQP Queue: ${queue.name}`;
|
|
193
|
+
if (bindings.length > 0) {
|
|
194
|
+
const bindingDescriptions = bindings.map((binding) => {
|
|
195
|
+
const exchangeName = binding.exchange.name;
|
|
196
|
+
const routingKey = "routingKey" in binding ? binding.routingKey : void 0;
|
|
197
|
+
return routingKey ? `bound to exchange '${exchangeName}' with routing key '${routingKey}'` : `bound to exchange '${exchangeName}'`;
|
|
198
|
+
}).join(", ");
|
|
199
|
+
description += ` (${bindingDescriptions})`;
|
|
200
|
+
}
|
|
179
201
|
return {
|
|
180
202
|
address: queue.name,
|
|
181
203
|
title: queue.name,
|
|
182
|
-
description
|
|
204
|
+
description,
|
|
183
205
|
bindings: { amqp: {
|
|
184
206
|
is: "queue",
|
|
185
207
|
queue: {
|
|
@@ -187,8 +209,9 @@ var AsyncAPIGenerator = class {
|
|
|
187
209
|
durable: queue.durable ?? false,
|
|
188
210
|
exclusive: queue.exclusive ?? false,
|
|
189
211
|
autoDelete: queue.autoDelete ?? false,
|
|
190
|
-
|
|
191
|
-
}
|
|
212
|
+
vhost: "/"
|
|
213
|
+
},
|
|
214
|
+
bindingVersion: "0.3.0"
|
|
192
215
|
} }
|
|
193
216
|
};
|
|
194
217
|
}
|
|
@@ -207,8 +230,9 @@ var AsyncAPIGenerator = class {
|
|
|
207
230
|
type: exchange.type,
|
|
208
231
|
durable: exchange.durable ?? false,
|
|
209
232
|
autoDelete: exchange.autoDelete ?? false,
|
|
210
|
-
|
|
211
|
-
}
|
|
233
|
+
vhost: "/"
|
|
234
|
+
},
|
|
235
|
+
bindingVersion: "0.3.0"
|
|
212
236
|
} }
|
|
213
237
|
};
|
|
214
238
|
}
|
|
@@ -231,6 +255,16 @@ var AsyncAPIGenerator = class {
|
|
|
231
255
|
return queue.name;
|
|
232
256
|
}
|
|
233
257
|
/**
|
|
258
|
+
* Get all bindings for a queue from the contract
|
|
259
|
+
*/
|
|
260
|
+
getQueueBindings(queue, contract) {
|
|
261
|
+
const result = [];
|
|
262
|
+
if (contract.bindings) {
|
|
263
|
+
for (const binding of Object.values(contract.bindings)) if (binding.type === "queue" && binding.queue.name === queue.name) result.push(binding);
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
234
268
|
* Convert a Standard Schema to JSON Schema using oRPC converters
|
|
235
269
|
*/
|
|
236
270
|
async convertSchema(schema, strategy) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["convertedChannels: ChannelsObject","convertedOperations: OperationsObject","convertedMessages: MessagesObject","channelMessages: MessagesObject","channel: ChannelObject","result: Record<string, unknown>"],"sources":["../src/index.ts"],"sourcesContent":["import {\n AsyncAPIObject,\n ChannelObject,\n ChannelsObject,\n MessageObject,\n MessagesObject,\n OperationsObject,\n} from \"@asyncapi/parser/esm/spec-types/v3\";\nimport { ConditionalSchemaConverter, JSONSchema } from \"@orpc/openapi\";\nimport type {\n ContractDefinition,\n ExchangeDefinition,\n MessageDefinition,\n QueueDefinition,\n} from \"@amqp-contract/contract\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Options for configuring the AsyncAPI generator.\n *\n * @example\n * ```typescript\n * import { AsyncAPIGenerator } from '@amqp-contract/asyncapi';\n * import { zodToJsonSchema } from '@orpc/zod';\n *\n * const generator = new AsyncAPIGenerator({\n * schemaConverters: [zodToJsonSchema]\n * });\n * ```\n */\nexport type AsyncAPIGeneratorOptions = {\n /**\n * Schema converters for transforming validation schemas to JSON Schema.\n * Supports Zod, Valibot, ArkType, and other Standard Schema v1 compatible libraries.\n */\n schemaConverters?: ConditionalSchemaConverter[];\n};\n\n/**\n * Options for generating an AsyncAPI document.\n * These correspond to the top-level AsyncAPI document fields.\n */\nexport type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, \"id\" | \"info\" | \"servers\">;\n\n/**\n * Generator for creating AsyncAPI 3.0 documentation from AMQP contracts.\n *\n * This class converts contract definitions into AsyncAPI 3.0 specification documents,\n * which can be used for API documentation, code generation, and tooling integration.\n *\n * @example\n * ```typescript\n * import { AsyncAPIGenerator } from '@amqp-contract/asyncapi';\n * import { zodToJsonSchema } from '@orpc/zod';\n * import { z } from 'zod';\n *\n * const contract = defineContract({\n * exchanges: {\n * orders: defineExchange('orders', 'topic', { durable: true })\n * },\n * publishers: {\n * orderCreated: definePublisher('orders', z.object({\n * orderId: z.string(),\n * amount: z.number()\n * }), {\n * routingKey: 'order.created'\n * })\n * }\n * });\n *\n * const generator = new AsyncAPIGenerator({\n * schemaConverters: [zodToJsonSchema]\n * });\n *\n * const asyncapi = await generator.generate(contract, {\n * id: 'urn:com:example:order-service',\n * info: {\n * title: 'Order Service API',\n * version: '1.0.0',\n * description: 'Async API for order processing'\n * },\n * servers: {\n * production: {\n * host: 'rabbitmq.example.com',\n * protocol: 'amqp',\n * protocolVersion: '0.9.1'\n * }\n * }\n * });\n * ```\n */\nexport class AsyncAPIGenerator {\n private readonly converters: ConditionalSchemaConverter[];\n\n /**\n * Create a new AsyncAPI generator instance.\n *\n * @param options - Configuration options including schema converters\n */\n constructor(options: AsyncAPIGeneratorOptions = {}) {\n this.converters = options.schemaConverters ?? [];\n }\n\n /**\n * Generate an AsyncAPI 3.0 document from a contract definition.\n *\n * Converts AMQP exchanges, queues, publishers, and consumers into\n * AsyncAPI channels, operations, and messages with proper JSON Schema\n * validation definitions.\n *\n * @param contract - The AMQP contract definition to convert\n * @param options - AsyncAPI document metadata (id, info, servers)\n * @returns Promise resolving to a complete AsyncAPI 3.0 document\n *\n * @example\n * ```typescript\n * const asyncapi = await generator.generate(contract, {\n * id: 'urn:com:example:api',\n * info: {\n * title: 'My API',\n * version: '1.0.0'\n * },\n * servers: {\n * dev: {\n * host: 'localhost:5672',\n * protocol: 'amqp'\n * }\n * }\n * });\n * ```\n */\n async generate(\n contract: ContractDefinition,\n options: AsyncAPIGeneratorGenerateOptions,\n ): Promise<AsyncAPIObject> {\n const convertedChannels: ChannelsObject = {};\n const convertedOperations: OperationsObject = {};\n const convertedMessages: MessagesObject = {};\n\n // First, collect all messages from publishers and consumers\n const publisherMessages = new Map<string, { message: MessageDefinition; channelKey: string }>();\n const consumerMessages = new Map<string, { message: MessageDefinition; channelKey: string }>();\n\n // Collect messages from publishers\n if (contract.publishers) {\n for (const [publisherName, publisher] of Object.entries(contract.publishers)) {\n const channelKey = this.getExchangeName(publisher.exchange, contract);\n publisherMessages.set(publisherName, { message: publisher.message, channelKey });\n }\n }\n\n // Collect messages from consumers\n if (contract.consumers) {\n for (const [consumerName, consumer] of Object.entries(contract.consumers)) {\n const channelKey = this.getQueueName(consumer.queue, contract);\n consumerMessages.set(consumerName, { message: consumer.message, channelKey });\n }\n }\n\n // Generate channels from queues with their messages\n if (contract.queues) {\n for (const [queueName, queue] of Object.entries(contract.queues)) {\n const channelMessages: MessagesObject = {};\n\n // Add messages from consumers that reference this queue\n for (const [consumerName, { message, channelKey }] of consumerMessages) {\n if (channelKey === queueName) {\n const messageName = `${consumerName}Message`;\n channelMessages[messageName] = await this.convertMessage(message);\n convertedMessages[messageName] = await this.convertMessage(message);\n }\n }\n\n const channel: ChannelObject = {\n ...this.queueToChannel(queue),\n };\n\n if (Object.keys(channelMessages).length > 0) {\n channel.messages = channelMessages;\n }\n\n convertedChannels[queueName] = channel;\n }\n }\n\n // Generate channels from exchanges with their messages\n if (contract.exchanges) {\n for (const [exchangeName, exchange] of Object.entries(contract.exchanges)) {\n const channelMessages: MessagesObject = {};\n\n // Add messages from publishers that reference this exchange\n for (const [publisherName, { message, channelKey }] of publisherMessages) {\n if (channelKey === exchangeName) {\n const messageName = `${publisherName}Message`;\n channelMessages[messageName] = await this.convertMessage(message);\n convertedMessages[messageName] = await this.convertMessage(message);\n }\n }\n\n const channel: ChannelObject = {\n ...this.exchangeToChannel(exchange),\n };\n\n if (Object.keys(channelMessages).length > 0) {\n channel.messages = channelMessages;\n }\n\n convertedChannels[exchangeName] = channel;\n }\n }\n\n // Generate publish operations from publishers\n if (contract.publishers) {\n for (const [publisherName, publisher] of Object.entries(contract.publishers)) {\n const exchangeName = this.getExchangeName(publisher.exchange, contract);\n const messageName = `${publisherName}Message`;\n\n convertedOperations[publisherName] = {\n action: \"send\",\n channel: { $ref: `#/channels/${exchangeName}` },\n messages: [{ $ref: `#/channels/${exchangeName}/messages/${messageName}` }],\n summary: `Publish to ${publisher.exchange.name}`,\n ...(publisher.routingKey && {\n description: `Routing key: ${publisher.routingKey}`,\n }),\n };\n }\n }\n\n // Generate receive operations from consumers\n if (contract.consumers) {\n for (const [consumerName, consumer] of Object.entries(contract.consumers)) {\n const queueName = this.getQueueName(consumer.queue, contract);\n const messageName = `${consumerName}Message`;\n\n convertedOperations[consumerName] = {\n action: \"receive\",\n channel: { $ref: `#/channels/${queueName}` },\n messages: [{ $ref: `#/channels/${queueName}/messages/${messageName}` }],\n summary: `Consume from ${consumer.queue.name}`,\n };\n }\n }\n\n return {\n ...options,\n asyncapi: \"3.0.0\",\n channels: convertedChannels,\n operations: convertedOperations,\n components: {\n messages: convertedMessages,\n },\n };\n }\n\n /**\n * Convert a message definition to AsyncAPI MessageObject\n */\n private async convertMessage(message: MessageDefinition): Promise<MessageObject> {\n const payload = message.payload;\n\n // Convert payload schema\n const payloadJsonSchema = await this.convertSchema(payload, \"input\");\n\n // Build result with required properties\n const result: Record<string, unknown> = {\n payload: payloadJsonSchema,\n contentType: \"application/json\",\n };\n\n // Add optional properties only if they exist\n if (message.headers) {\n const headersJsonSchema = await this.convertSchema(message.headers, \"input\");\n if (headersJsonSchema) {\n result[\"headers\"] = headersJsonSchema;\n }\n }\n\n if (message.summary) {\n result[\"summary\"] = message.summary;\n }\n\n if (message.description) {\n result[\"description\"] = message.description;\n }\n\n return result as MessageObject;\n }\n\n /**\n * Convert a queue definition to AsyncAPI ChannelObject\n */\n private queueToChannel(queue: QueueDefinition): ChannelObject {\n const result: Record<string, unknown> = {\n address: queue.name,\n title: queue.name,\n description: `AMQP Queue: ${queue.name}`,\n bindings: {\n amqp: {\n is: \"queue\",\n queue: {\n name: queue.name,\n durable: queue.durable ?? false,\n exclusive: queue.exclusive ?? false,\n autoDelete: queue.autoDelete ?? false,\n ...(queue.arguments && { vhost: \"/\" }),\n },\n },\n },\n };\n\n return result as ChannelObject;\n }\n\n /**\n * Convert an exchange definition to AsyncAPI ChannelObject\n */\n private exchangeToChannel(exchange: ExchangeDefinition): ChannelObject {\n const result: Record<string, unknown> = {\n address: exchange.name,\n title: exchange.name,\n description: `AMQP Exchange: ${exchange.name} (${exchange.type})`,\n bindings: {\n amqp: {\n is: \"routingKey\",\n exchange: {\n name: exchange.name,\n type: exchange.type,\n durable: exchange.durable ?? false,\n autoDelete: exchange.autoDelete ?? false,\n ...(exchange.arguments && { vhost: \"/\" }),\n },\n },\n },\n };\n\n return result as ChannelObject;\n }\n\n /**\n * Get the name/key of an exchange from the contract\n */\n private getExchangeName(exchange: ExchangeDefinition, contract: ContractDefinition): string {\n if (contract.exchanges) {\n for (const [name, ex] of Object.entries(contract.exchanges)) {\n if (ex === exchange || ex.name === exchange.name) {\n return name;\n }\n }\n }\n return exchange.name;\n }\n\n /**\n * Get the name/key of a queue from the contract\n */\n private getQueueName(queue: QueueDefinition, contract: ContractDefinition): string {\n if (contract.queues) {\n for (const [name, q] of Object.entries(contract.queues)) {\n if (q === queue || q.name === queue.name) {\n return name;\n }\n }\n }\n return queue.name;\n }\n\n /**\n * Convert a Standard Schema to JSON Schema using oRPC converters\n */\n private async convertSchema(\n schema: StandardSchemaV1,\n strategy: \"input\" | \"output\",\n ): Promise<JSONSchema> {\n // Try each converter until one matches\n for (const converter of this.converters) {\n const matches = await converter.condition(schema, { strategy });\n if (matches) {\n const [_required, jsonSchema] = await converter.convert(schema, { strategy });\n return jsonSchema;\n }\n }\n\n // If no converter matches, return a generic object schema\n // This allows the contract to still be generated even without schema converters\n return { type: \"object\" };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["convertedChannels: ChannelsObject","convertedOperations: OperationsObject","convertedMessages: MessagesObject","channelMessages: MessagesObject","channel: ChannelObject","result: Record<string, unknown>","result: QueueBindingDefinition[]"],"sources":["../src/index.ts"],"sourcesContent":["import {\n AsyncAPIObject,\n ChannelObject,\n ChannelsObject,\n MessageObject,\n MessagesObject,\n OperationsObject,\n} from \"@asyncapi/parser/esm/spec-types/v3\";\nimport { ConditionalSchemaConverter, JSONSchema } from \"@orpc/openapi\";\nimport type {\n ContractDefinition,\n ExchangeDefinition,\n MessageDefinition,\n QueueBindingDefinition,\n QueueDefinition,\n} from \"@amqp-contract/contract\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Options for configuring the AsyncAPI generator.\n *\n * @example\n * ```typescript\n * import { AsyncAPIGenerator } from '@amqp-contract/asyncapi';\n * import { zodToJsonSchema } from '@orpc/zod';\n *\n * const generator = new AsyncAPIGenerator({\n * schemaConverters: [zodToJsonSchema]\n * });\n * ```\n */\nexport type AsyncAPIGeneratorOptions = {\n /**\n * Schema converters for transforming validation schemas to JSON Schema.\n * Supports Zod, Valibot, ArkType, and other Standard Schema v1 compatible libraries.\n */\n schemaConverters?: ConditionalSchemaConverter[];\n};\n\n/**\n * Options for generating an AsyncAPI document.\n * These correspond to the top-level AsyncAPI document fields.\n */\nexport type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, \"id\" | \"info\" | \"servers\">;\n\n/**\n * Generator for creating AsyncAPI 3.0 documentation from AMQP contracts.\n *\n * This class converts contract definitions into AsyncAPI 3.0 specification documents,\n * which can be used for API documentation, code generation, and tooling integration.\n *\n * @example\n * ```typescript\n * import { AsyncAPIGenerator } from '@amqp-contract/asyncapi';\n * import { zodToJsonSchema } from '@orpc/zod';\n * import { z } from 'zod';\n *\n * const contract = defineContract({\n * exchanges: {\n * orders: defineExchange('orders', 'topic', { durable: true })\n * },\n * publishers: {\n * orderCreated: definePublisher('orders', z.object({\n * orderId: z.string(),\n * amount: z.number()\n * }), {\n * routingKey: 'order.created'\n * })\n * }\n * });\n *\n * const generator = new AsyncAPIGenerator({\n * schemaConverters: [zodToJsonSchema]\n * });\n *\n * const asyncapi = await generator.generate(contract, {\n * id: 'urn:com:example:order-service',\n * info: {\n * title: 'Order Service API',\n * version: '1.0.0',\n * description: 'Async API for order processing'\n * },\n * servers: {\n * production: {\n * host: 'rabbitmq.example.com',\n * protocol: 'amqp',\n * protocolVersion: '0.9.1'\n * }\n * }\n * });\n * ```\n */\nexport class AsyncAPIGenerator {\n private readonly converters: ConditionalSchemaConverter[];\n\n /**\n * Create a new AsyncAPI generator instance.\n *\n * @param options - Configuration options including schema converters\n */\n constructor(options: AsyncAPIGeneratorOptions = {}) {\n this.converters = options.schemaConverters ?? [];\n }\n\n /**\n * Generate an AsyncAPI 3.0 document from a contract definition.\n *\n * Converts AMQP exchanges, queues, publishers, and consumers into\n * AsyncAPI channels, operations, and messages with proper JSON Schema\n * validation definitions.\n *\n * @param contract - The AMQP contract definition to convert\n * @param options - AsyncAPI document metadata (id, info, servers)\n * @returns Promise resolving to a complete AsyncAPI 3.0 document\n *\n * @example\n * ```typescript\n * const asyncapi = await generator.generate(contract, {\n * id: 'urn:com:example:api',\n * info: {\n * title: 'My API',\n * version: '1.0.0'\n * },\n * servers: {\n * dev: {\n * host: 'localhost:5672',\n * protocol: 'amqp'\n * }\n * }\n * });\n * ```\n */\n async generate(\n contract: ContractDefinition,\n options: AsyncAPIGeneratorGenerateOptions,\n ): Promise<AsyncAPIObject> {\n const convertedChannels: ChannelsObject = {};\n const convertedOperations: OperationsObject = {};\n const convertedMessages: MessagesObject = {};\n\n // First, collect all messages from publishers and consumers\n const publisherMessages = new Map<string, { message: MessageDefinition; channelKey: string }>();\n const consumerMessages = new Map<string, { message: MessageDefinition; channelKey: string }>();\n\n // Collect messages from publishers\n if (contract.publishers) {\n for (const [publisherName, publisher] of Object.entries(contract.publishers)) {\n const channelKey = this.getExchangeName(publisher.exchange, contract);\n publisherMessages.set(publisherName, { message: publisher.message, channelKey });\n }\n }\n\n // Collect messages from consumers\n if (contract.consumers) {\n for (const [consumerName, consumer] of Object.entries(contract.consumers)) {\n const channelKey = this.getQueueName(consumer.queue, contract);\n consumerMessages.set(consumerName, { message: consumer.message, channelKey });\n }\n }\n\n // Generate channels from queues with their messages\n if (contract.queues) {\n for (const [queueName, queue] of Object.entries(contract.queues)) {\n const channelMessages: MessagesObject = {};\n\n // Add messages from consumers that reference this queue\n for (const [consumerName, { message, channelKey }] of consumerMessages) {\n if (channelKey === queueName) {\n const messageName = `${consumerName}Message`;\n channelMessages[messageName] = await this.convertMessage(message);\n convertedMessages[messageName] = await this.convertMessage(message);\n }\n }\n\n // Find bindings for this queue\n const queueBindings = this.getQueueBindings(queue, contract);\n const channel: ChannelObject = {\n ...this.queueToChannel(queue, queueBindings),\n };\n\n if (Object.keys(channelMessages).length > 0) {\n channel.messages = channelMessages;\n }\n\n convertedChannels[queueName] = channel;\n }\n }\n\n // Generate channels from exchanges with their messages\n if (contract.exchanges) {\n for (const [exchangeName, exchange] of Object.entries(contract.exchanges)) {\n const channelMessages: MessagesObject = {};\n\n // Add messages from publishers that reference this exchange\n for (const [publisherName, { message, channelKey }] of publisherMessages) {\n if (channelKey === exchangeName) {\n const messageName = `${publisherName}Message`;\n channelMessages[messageName] = await this.convertMessage(message);\n convertedMessages[messageName] = await this.convertMessage(message);\n }\n }\n\n const channel: ChannelObject = {\n ...this.exchangeToChannel(exchange),\n };\n\n if (Object.keys(channelMessages).length > 0) {\n channel.messages = channelMessages;\n }\n\n convertedChannels[exchangeName] = channel;\n }\n }\n\n // Generate publish operations from publishers\n if (contract.publishers) {\n for (const [publisherName, publisher] of Object.entries(contract.publishers)) {\n const exchangeName = this.getExchangeName(publisher.exchange, contract);\n const messageName = `${publisherName}Message`;\n\n // Build operation without type assertion\n if (publisher.routingKey) {\n convertedOperations[publisherName] = {\n action: \"send\",\n channel: { $ref: `#/channels/${exchangeName}` },\n messages: [{ $ref: `#/channels/${exchangeName}/messages/${messageName}` }],\n summary: `Publish to ${publisher.exchange.name}`,\n description: `Routing key: ${publisher.routingKey}`,\n bindings: {\n amqp: {\n cc: [publisher.routingKey],\n deliveryMode: 2, // Persistent by default\n bindingVersion: \"0.3.0\",\n } as Record<string, unknown>,\n },\n };\n } else {\n convertedOperations[publisherName] = {\n action: \"send\",\n channel: { $ref: `#/channels/${exchangeName}` },\n messages: [{ $ref: `#/channels/${exchangeName}/messages/${messageName}` }],\n summary: `Publish to ${publisher.exchange.name}`,\n };\n }\n }\n }\n\n // Generate receive operations from consumers\n if (contract.consumers) {\n for (const [consumerName, consumer] of Object.entries(contract.consumers)) {\n const queueName = this.getQueueName(consumer.queue, contract);\n const messageName = `${consumerName}Message`;\n\n convertedOperations[consumerName] = {\n action: \"receive\",\n channel: { $ref: `#/channels/${queueName}` },\n messages: [{ $ref: `#/channels/${queueName}/messages/${messageName}` }],\n summary: `Consume from ${consumer.queue.name}`,\n bindings: {\n amqp: {\n bindingVersion: \"0.3.0\",\n } as Record<string, unknown>,\n },\n };\n }\n }\n\n return {\n ...options,\n asyncapi: \"3.0.0\",\n channels: convertedChannels,\n operations: convertedOperations,\n components: {\n messages: convertedMessages,\n },\n };\n }\n\n /**\n * Convert a message definition to AsyncAPI MessageObject\n */\n private async convertMessage(message: MessageDefinition): Promise<MessageObject> {\n const payload = message.payload;\n\n // Convert payload schema\n const payloadJsonSchema = await this.convertSchema(payload, \"input\");\n\n // Build result with required properties\n const result: Record<string, unknown> = {\n payload: payloadJsonSchema,\n contentType: \"application/json\",\n };\n\n // Add optional properties only if they exist\n if (message.headers) {\n const headersJsonSchema = await this.convertSchema(message.headers, \"input\");\n if (headersJsonSchema) {\n result[\"headers\"] = headersJsonSchema;\n }\n }\n\n if (message.summary) {\n result[\"summary\"] = message.summary;\n }\n\n if (message.description) {\n result[\"description\"] = message.description;\n }\n\n return result as MessageObject;\n }\n\n /**\n * Convert a queue definition to AsyncAPI ChannelObject\n */\n private queueToChannel(\n queue: QueueDefinition,\n bindings: QueueBindingDefinition[] = [],\n ): ChannelObject {\n // Build description with binding information\n let description = `AMQP Queue: ${queue.name}`;\n if (bindings.length > 0) {\n const bindingDescriptions = bindings\n .map((binding) => {\n const exchangeName = binding.exchange.name;\n const routingKey = \"routingKey\" in binding ? binding.routingKey : undefined;\n return routingKey\n ? `bound to exchange '${exchangeName}' with routing key '${routingKey}'`\n : `bound to exchange '${exchangeName}'`;\n })\n .join(\", \");\n description += ` (${bindingDescriptions})`;\n }\n\n const result: Record<string, unknown> = {\n address: queue.name,\n title: queue.name,\n description,\n bindings: {\n amqp: {\n is: \"queue\",\n queue: {\n name: queue.name,\n durable: queue.durable ?? false,\n exclusive: queue.exclusive ?? false,\n autoDelete: queue.autoDelete ?? false,\n vhost: \"/\",\n },\n bindingVersion: \"0.3.0\",\n },\n },\n };\n\n return result as ChannelObject;\n }\n\n /**\n * Convert an exchange definition to AsyncAPI ChannelObject\n */\n private exchangeToChannel(exchange: ExchangeDefinition): ChannelObject {\n const result: Record<string, unknown> = {\n address: exchange.name,\n title: exchange.name,\n description: `AMQP Exchange: ${exchange.name} (${exchange.type})`,\n bindings: {\n amqp: {\n is: \"routingKey\",\n exchange: {\n name: exchange.name,\n type: exchange.type,\n durable: exchange.durable ?? false,\n autoDelete: exchange.autoDelete ?? false,\n vhost: \"/\",\n },\n bindingVersion: \"0.3.0\",\n },\n },\n };\n\n return result as ChannelObject;\n }\n\n /**\n * Get the name/key of an exchange from the contract\n */\n private getExchangeName(exchange: ExchangeDefinition, contract: ContractDefinition): string {\n if (contract.exchanges) {\n for (const [name, ex] of Object.entries(contract.exchanges)) {\n if (ex === exchange || ex.name === exchange.name) {\n return name;\n }\n }\n }\n return exchange.name;\n }\n\n /**\n * Get the name/key of a queue from the contract\n */\n private getQueueName(queue: QueueDefinition, contract: ContractDefinition): string {\n if (contract.queues) {\n for (const [name, q] of Object.entries(contract.queues)) {\n if (q === queue || q.name === queue.name) {\n return name;\n }\n }\n }\n return queue.name;\n }\n\n /**\n * Get all bindings for a queue from the contract\n */\n private getQueueBindings(\n queue: QueueDefinition,\n contract: ContractDefinition,\n ): QueueBindingDefinition[] {\n const result: QueueBindingDefinition[] = [];\n\n if (contract.bindings) {\n for (const binding of Object.values(contract.bindings)) {\n if (binding.type === \"queue\" && binding.queue.name === queue.name) {\n result.push(binding);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Convert a Standard Schema to JSON Schema using oRPC converters\n */\n private async convertSchema(\n schema: StandardSchemaV1,\n strategy: \"input\" | \"output\",\n ): Promise<JSONSchema> {\n // Try each converter until one matches\n for (const converter of this.converters) {\n const matches = await converter.condition(schema, { strategy });\n if (matches) {\n const [_required, jsonSchema] = await converter.convert(schema, { strategy });\n return jsonSchema;\n }\n }\n\n // If no converter matches, return a generic object schema\n // This allows the contract to still be generated even without schema converters\n return { type: \"object\" };\n }\n}\n"],"mappings":"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"}
|
package/docs/index.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
### AsyncAPIGenerator
|
|
10
10
|
|
|
11
|
-
Defined in: [index.ts:
|
|
11
|
+
Defined in: [index.ts:93](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/asyncapi/src/index.ts#L93)
|
|
12
12
|
|
|
13
13
|
Generator for creating AsyncAPI 3.0 documentation from AMQP contracts.
|
|
14
14
|
|
|
@@ -65,7 +65,7 @@ const asyncapi = await generator.generate(contract, {
|
|
|
65
65
|
new AsyncAPIGenerator(options): AsyncAPIGenerator;
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Defined in: [index.ts:
|
|
68
|
+
Defined in: [index.ts:101](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/asyncapi/src/index.ts#L101)
|
|
69
69
|
|
|
70
70
|
Create a new AsyncAPI generator instance.
|
|
71
71
|
|
|
@@ -87,7 +87,7 @@ Create a new AsyncAPI generator instance.
|
|
|
87
87
|
generate(contract, options): Promise<AsyncAPIObject>;
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
Defined in: [index.ts:
|
|
90
|
+
Defined in: [index.ts:133](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/asyncapi/src/index.ts#L133)
|
|
91
91
|
|
|
92
92
|
Generate an AsyncAPI 3.0 document from a contract definition.
|
|
93
93
|
|
|
@@ -134,7 +134,7 @@ const asyncapi = await generator.generate(contract, {
|
|
|
134
134
|
type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, "id" | "info" | "servers">;
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Defined in: [index.ts:
|
|
137
|
+
Defined in: [index.ts:44](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/asyncapi/src/index.ts#L44)
|
|
138
138
|
|
|
139
139
|
Options for generating an AsyncAPI document.
|
|
140
140
|
These correspond to the top-level AsyncAPI document fields.
|
|
@@ -147,7 +147,7 @@ These correspond to the top-level AsyncAPI document fields.
|
|
|
147
147
|
type AsyncAPIGeneratorOptions = object;
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
Defined in: [index.ts:
|
|
150
|
+
Defined in: [index.ts:32](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/asyncapi/src/index.ts#L32)
|
|
151
151
|
|
|
152
152
|
Options for configuring the AsyncAPI generator.
|
|
153
153
|
|
|
@@ -166,4 +166,4 @@ const generator = new AsyncAPIGenerator({
|
|
|
166
166
|
|
|
167
167
|
| Property | Type | Description | Defined in |
|
|
168
168
|
| ------ | ------ | ------ | ------ |
|
|
169
|
-
| <a id="schemaconverters"></a> `schemaConverters?` | `ConditionalSchemaConverter`[] | Schema converters for transforming validation schemas to JSON Schema. Supports Zod, Valibot, ArkType, and other Standard Schema v1 compatible libraries. | [index.ts:
|
|
169
|
+
| <a id="schemaconverters"></a> `schemaConverters?` | `ConditionalSchemaConverter`[] | Schema converters for transforming validation schemas to JSON Schema. Supports Zod, Valibot, ArkType, and other Standard Schema v1 compatible libraries. | [index.ts:37](https://github.com/btravers/amqp-contract/blob/382c6d2fbfc563c9f6f16cb71cb33fd4a3d41d77/packages/asyncapi/src/index.ts#L37) |
|
package/package.json
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amqp-contract/asyncapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AsyncAPI specification generator for amqp-contract",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"amqp",
|
|
7
7
|
"asyncapi",
|
|
8
8
|
"contract",
|
|
9
9
|
"rabbitmq",
|
|
10
|
-
"typescript"
|
|
10
|
+
"typescript",
|
|
11
|
+
"nodejs",
|
|
12
|
+
"messaging",
|
|
13
|
+
"type-safe",
|
|
14
|
+
"schema",
|
|
15
|
+
"documentation",
|
|
16
|
+
"api-specification",
|
|
17
|
+
"message-broker"
|
|
11
18
|
],
|
|
12
19
|
"homepage": "https://github.com/btravers/amqp-contract#readme",
|
|
13
20
|
"bugs": {
|
|
@@ -42,25 +49,25 @@
|
|
|
42
49
|
"docs"
|
|
43
50
|
],
|
|
44
51
|
"dependencies": {
|
|
45
|
-
"@orpc/openapi": "1.13.
|
|
52
|
+
"@orpc/openapi": "1.13.2",
|
|
46
53
|
"@standard-schema/spec": "1.1.0",
|
|
47
|
-
"@amqp-contract/contract": "0.
|
|
54
|
+
"@amqp-contract/contract": "0.7.0"
|
|
48
55
|
},
|
|
49
56
|
"devDependencies": {
|
|
50
57
|
"@asyncapi/parser": "3.4.0",
|
|
51
|
-
"@orpc/arktype": "1.13.
|
|
52
|
-
"@orpc/valibot": "1.13.
|
|
53
|
-
"@orpc/zod": "1.13.
|
|
58
|
+
"@orpc/arktype": "1.13.2",
|
|
59
|
+
"@orpc/valibot": "1.13.2",
|
|
60
|
+
"@orpc/zod": "1.13.2",
|
|
54
61
|
"@types/node": "25.0.3",
|
|
55
62
|
"@vitest/coverage-v8": "4.0.16",
|
|
56
63
|
"arktype": "2.1.29",
|
|
57
|
-
"tsdown": "0.18.
|
|
64
|
+
"tsdown": "0.18.4",
|
|
58
65
|
"typedoc": "0.28.15",
|
|
59
66
|
"typedoc-plugin-markdown": "4.9.0",
|
|
60
67
|
"typescript": "5.9.3",
|
|
61
68
|
"valibot": "1.2.0",
|
|
62
69
|
"vitest": "4.0.16",
|
|
63
|
-
"zod": "4.
|
|
70
|
+
"zod": "4.3.5",
|
|
64
71
|
"@amqp-contract/tsconfig": "0.0.0",
|
|
65
72
|
"@amqp-contract/typedoc": "0.0.1"
|
|
66
73
|
},
|
|
@@ -69,7 +76,6 @@
|
|
|
69
76
|
"build:docs": "typedoc",
|
|
70
77
|
"dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
|
|
71
78
|
"test": "vitest run",
|
|
72
|
-
"test:watch": "vitest",
|
|
73
79
|
"typecheck": "tsc --noEmit"
|
|
74
80
|
}
|
|
75
81
|
}
|