@amqp-contract/asyncapi 0.6.0 → 0.8.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 CHANGED
@@ -19,11 +19,11 @@ pnpm add @amqp-contract/asyncapi
19
19
  ## Usage
20
20
 
21
21
  ```typescript
22
- import { AsyncAPIGenerator } from '@amqp-contract/asyncapi';
23
- import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4';
24
- import { writeFileSync } from 'fs';
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 './contract';
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: 'My AMQP API',
37
- version: '1.0.0',
38
- description: 'Type-safe AMQP messaging API',
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: 'localhost:5672',
43
- protocol: 'amqp',
44
- description: 'Development RabbitMQ server',
42
+ host: "localhost:5672",
43
+ protocol: "amqp",
44
+ description: "Development RabbitMQ server",
45
45
  },
46
46
  production: {
47
- host: 'rabbitmq.example.com:5672',
48
- protocol: 'amqp',
49
- description: 'Production RabbitMQ server',
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('asyncapi.json', JSON.stringify(asyncAPISpec, null, 2));
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 channel = { ...this.queueToChannel(queue) };
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
- ...publisher.routingKey && { description: `Routing key: ${publisher.routingKey}` }
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: `AMQP Queue: ${queue.name}`,
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
- ...queue.arguments && { vhost: "/" }
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
- ...exchange.arguments && { vhost: "/" }
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
@@ -1,4 +1,4 @@
1
- import { AsyncAPIObject } from "@asyncapi/parser/esm/spec-types/v3";
1
+ import { AsyncAPIObject } from "@asyncapi/parser/esm/spec-types/v3.js";
2
2
  import { ConditionalSchemaConverter } from "@orpc/openapi";
3
3
  import { ContractDefinition } from "@amqp-contract/contract";
4
4
 
@@ -28,7 +28,7 @@ type AsyncAPIGeneratorOptions = {
28
28
  * Options for generating an AsyncAPI document.
29
29
  * These correspond to the top-level AsyncAPI document fields.
30
30
  */
31
- type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, "id" | "info" | "servers">;
31
+ type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, "info"> & Partial<Pick<AsyncAPIObject, "id" | "servers">>;
32
32
  /**
33
33
  * Generator for creating AsyncAPI 3.0 documentation from AMQP contracts.
34
34
  *
@@ -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
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA8BA;AAYA;AAiDA;;;;;;;;;KA7DY,wBAAA;;;;;qBAKS;;;;;;KAOT,gCAAA,GAAmC,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiDvC,iBAAA;;;;;;;wBAQU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiCT,6BACD,mCACR,QAAQ"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA+BA;AAYA;;;;;;;AAkDA;;;AA0Ca,KAxGD,wBAAA,GAwGC;EACA;;;;qBApGQ;;;;;;KAOT,gCAAA,GAAmC,KAAK,0BAClD,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiDF,iBAAA;;;;;;;wBAQU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiCT,6BACD,mCACR,QAAQ"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AsyncAPIObject } from "@asyncapi/parser/esm/spec-types/v3";
1
+ import { AsyncAPIObject } from "@asyncapi/parser/esm/spec-types/v3.js";
2
2
  import { ConditionalSchemaConverter } from "@orpc/openapi";
3
3
  import { ContractDefinition } from "@amqp-contract/contract";
4
4
 
@@ -28,7 +28,7 @@ type AsyncAPIGeneratorOptions = {
28
28
  * Options for generating an AsyncAPI document.
29
29
  * These correspond to the top-level AsyncAPI document fields.
30
30
  */
31
- type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, "id" | "info" | "servers">;
31
+ type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, "info"> & Partial<Pick<AsyncAPIObject, "id" | "servers">>;
32
32
  /**
33
33
  * Generator for creating AsyncAPI 3.0 documentation from AMQP contracts.
34
34
  *
@@ -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
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA8BA;AAYA;AAiDA;;;;;;;;;KA7DY,wBAAA;;;;;qBAKS;;;;;;KAOT,gCAAA,GAAmC,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiDvC,iBAAA;;;;;;;wBAQU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiCT,6BACD,mCACR,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA+BA;AAYA;;;;;;;AAkDA;;;AA0Ca,KAxGD,wBAAA,GAwGC;EACA;;;;qBApGQ;;;;;;KAOT,gCAAA,GAAmC,KAAK,0BAClD,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiDF,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 channel = { ...this.queueToChannel(queue) };
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
- ...publisher.routingKey && { description: `Routing key: ${publisher.routingKey}` }
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: `AMQP Queue: ${queue.name}`,
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
- ...queue.arguments && { vhost: "/" }
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
- ...exchange.arguments && { vhost: "/" }
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) {
@@ -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":[],"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.js\";\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, \"info\"> &\n Partial<Pick<AsyncAPIObject, \"id\" | \"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:92](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/asyncapi/src/index.ts#L92)
11
+ Defined in: [index.ts:94](https://github.com/btravers/amqp-contract/blob/7897a0ef6745684800b11ec9bbc26bfab645f2a7/packages/asyncapi/src/index.ts#L94)
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:100](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/asyncapi/src/index.ts#L100)
68
+ Defined in: [index.ts:102](https://github.com/btravers/amqp-contract/blob/7897a0ef6745684800b11ec9bbc26bfab645f2a7/packages/asyncapi/src/index.ts#L102)
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:132](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/asyncapi/src/index.ts#L132)
90
+ Defined in: [index.ts:134](https://github.com/btravers/amqp-contract/blob/7897a0ef6745684800b11ec9bbc26bfab645f2a7/packages/asyncapi/src/index.ts#L134)
91
91
 
92
92
  Generate an AsyncAPI 3.0 document from a contract definition.
93
93
 
@@ -131,10 +131,10 @@ const asyncapi = await generator.generate(contract, {
131
131
  ### AsyncAPIGeneratorGenerateOptions
132
132
 
133
133
  ```ts
134
- type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, "id" | "info" | "servers">;
134
+ type AsyncAPIGeneratorGenerateOptions = Pick<AsyncAPIObject, "info"> & Partial<Pick<AsyncAPIObject, "id" | "servers">>;
135
135
  ```
136
136
 
137
- Defined in: [index.ts:43](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/asyncapi/src/index.ts#L43)
137
+ Defined in: [index.ts:44](https://github.com/btravers/amqp-contract/blob/7897a0ef6745684800b11ec9bbc26bfab645f2a7/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:31](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/asyncapi/src/index.ts#L31)
150
+ Defined in: [index.ts:32](https://github.com/btravers/amqp-contract/blob/7897a0ef6745684800b11ec9bbc26bfab645f2a7/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:36](https://github.com/btravers/amqp-contract/blob/8711ad20abb1d4e537ec473d43e85fac3ebd4d0b/packages/asyncapi/src/index.ts#L36) |
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/7897a0ef6745684800b11ec9bbc26bfab645f2a7/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.6.0",
3
+ "version": "0.8.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,34 +49,33 @@
42
49
  "docs"
43
50
  ],
44
51
  "dependencies": {
45
- "@orpc/openapi": "1.13.1",
52
+ "@orpc/openapi": "1.13.2",
46
53
  "@standard-schema/spec": "1.1.0",
47
- "@amqp-contract/contract": "0.6.0"
54
+ "@amqp-contract/contract": "0.8.0"
48
55
  },
49
56
  "devDependencies": {
50
57
  "@asyncapi/parser": "3.4.0",
51
- "@orpc/arktype": "1.13.1",
52
- "@orpc/valibot": "1.13.1",
53
- "@orpc/zod": "1.13.1",
54
- "@types/node": "25.0.3",
58
+ "@orpc/arktype": "1.13.2",
59
+ "@orpc/valibot": "1.13.2",
60
+ "@orpc/zod": "1.13.2",
61
+ "@types/node": "25.0.5",
55
62
  "@vitest/coverage-v8": "4.0.16",
56
63
  "arktype": "2.1.29",
57
- "tsdown": "0.18.3",
64
+ "tsdown": "0.19.0",
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.2.1",
64
- "@amqp-contract/tsconfig": "0.0.0",
65
- "@amqp-contract/typedoc": "0.0.1"
70
+ "zod": "4.3.5",
71
+ "@amqp-contract/tsconfig": "0.1.0",
72
+ "@amqp-contract/typedoc": "0.1.0"
66
73
  },
67
74
  "scripts": {
68
75
  "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
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
  }