@amqp-contract/worker 0.0.2 → 0.0.4

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  Type-safe AMQP worker for consuming messages using amqp-contract.
4
4
 
5
+ 📖 **[Full documentation →](https://btravers.github.io/amqp-contract/api/worker)**
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -18,39 +20,42 @@ import { contract } from './contract';
18
20
  // Connect to RabbitMQ
19
21
  const connection = await connect('amqp://localhost');
20
22
 
21
- // Create worker from contract with handlers
22
- const worker = createWorker(contract, {
23
- processOrder: async (message) => {
24
- console.log('Processing order:', message.orderId);
25
- // Your business logic here
23
+ // Create worker from contract with handlers (automatically connects and starts consuming)
24
+ const worker = await createWorker({
25
+ contract,
26
+ handlers: {
27
+ processOrder: async (message) => {
28
+ console.log('Processing order:', message.orderId);
29
+ // Your business logic here
30
+ },
26
31
  },
32
+ connection,
27
33
  });
28
34
 
29
- await worker.connect(connection);
30
-
31
- // Start consuming all consumers
32
- await worker.consumeAll();
33
-
34
- // Or start consuming a specific consumer
35
- // await worker.consume('processOrder');
36
-
37
- // Stop consuming when needed
38
- // await worker.stopConsuming();
35
+ // Worker is already consuming messages
39
36
 
40
- // Clean up
37
+ // Clean up when needed
41
38
  // await worker.close();
42
39
  ```
43
40
 
44
41
  ## API
45
42
 
46
- ### `createWorker(contract, handlers)`
43
+ ### `createWorker(options)`
47
44
 
48
- Create a type-safe AMQP worker from a contract with message handlers.
45
+ Create a type-safe AMQP worker from a contract with message handlers. Automatically connects and starts consuming all messages.
46
+
47
+ **Parameters:**
48
+
49
+ - `options.contract` - Contract definition
50
+ - `options.handlers` - Object with handler functions for each consumer
51
+ - `options.connection` - amqplib Connection object
49
52
 
50
53
  ### `AmqpWorker.connect(connection)`
51
54
 
52
55
  Connect to an AMQP broker and set up all exchanges, queues, and bindings defined in the contract.
53
56
 
57
+ **Note:** When using `createWorker()`, this is called automatically.
58
+
54
59
  ### `AmqpWorker.consume(consumerName)`
55
60
 
56
61
  Start consuming messages for a specific consumer.
@@ -59,6 +64,8 @@ Start consuming messages for a specific consumer.
59
64
 
60
65
  Start consuming messages for all consumers defined in the contract.
61
66
 
67
+ **Note:** When using `createWorker()`, this is called automatically.
68
+
62
69
  ### `AmqpWorker.stopConsuming()`
63
70
 
64
71
  Stop consuming messages from all consumers.
package/dist/index.cjs CHANGED
@@ -96,9 +96,13 @@ var AmqpWorker = class {
96
96
  };
97
97
  /**
98
98
  * Create a type-safe AMQP worker from a contract
99
+ * The worker will automatically connect and start consuming all messages
99
100
  */
100
- function createWorker(contract, handlers) {
101
- return new AmqpWorker(contract, handlers);
101
+ async function createWorker(options) {
102
+ const worker = new AmqpWorker(options.contract, options.handlers);
103
+ await worker.connect(options.connection);
104
+ await worker.consumeAll();
105
+ return worker;
102
106
  }
103
107
 
104
108
  //#endregion
package/dist/index.d.cts CHANGED
@@ -34,10 +34,19 @@ declare class AmqpWorker<TContract extends ContractDefinition> {
34
34
  */
35
35
  close(): Promise<void>;
36
36
  }
37
+ /**
38
+ * Options for creating a worker
39
+ */
40
+ interface CreateWorkerOptions<TContract extends ContractDefinition> {
41
+ contract: TContract;
42
+ handlers: WorkerInferConsumerHandlers<TContract>;
43
+ connection: ChannelModel;
44
+ }
37
45
  /**
38
46
  * Create a type-safe AMQP worker from a contract
47
+ * The worker will automatically connect and start consuming all messages
39
48
  */
40
- declare function createWorker<TContract extends ContractDefinition>(contract: TContract, handlers: WorkerInferConsumerHandlers<TContract>): AmqpWorker<TContract>;
49
+ declare function createWorker<TContract extends ContractDefinition>(options: CreateWorkerOptions<TContract>): Promise<AmqpWorker<TContract>>;
41
50
  //#endregion
42
- export { AmqpWorker, createWorker };
51
+ export { AmqpWorker, type CreateWorkerOptions, createWorker };
43
52
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/worker.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAA0C,cAA7B,UAA6B,CAAA,kBAAA,kBAAA,CAAA,CAAA;EAMX,iBAAA,QAAA;EAC4B,iBAAA,QAAA;EAA5B,QAAA,OAAA;EAMH,QAAA,UAAA;EAAe,QAAA,YAAA;EA4CM,WAAA,CAAA,QAAA,EAnDlB,SAmDkB,EAAA,QAAA,EAlDlB,2BAkDkB,CAlDU,SAkDV,CAAA;EAAnB;;;EAuFR,OAAA,CAAA,UAAA,EAnIM,YAmIN,CAAA,EAnIqB,OAmIrB,CAAA,IAAA,CAAA;EAeG;;;EAiCT,OAAA,CAAA,cAvIc,kBAuIF,CAvIqB,SAuIrB,CAAA,CAAA,CAAA,YAAA,EAvI+C,KAuI/C,CAAA,EAvIuD,OAuIvD,CAAA,IAAA,CAAA;EAAmB;;;EAEnC,UAAA,CAAA,CAAA,EAlDU,OAkDV,CAAA,IAAA,CAAA;EACE;;;mBApCW;;;;WAeR;;;;;iBAkBD,+BAA+B,8BACnC,qBACA,4BAA4B,aACrC,WAAW"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/worker.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAA0C,cAA7B,UAA6B,CAAA,kBAAA,kBAAA,CAAA,CAAA;EAMX,iBAAA,QAAA;EAC4B,iBAAA,QAAA;EAA5B,QAAA,OAAA;EAMH,QAAA,UAAA;EAAe,QAAA,YAAA;EA4CM,WAAA,CAAA,QAAA,EAnDlB,SAmDkB,EAAA,QAAA,EAlDlB,2BAkDkB,CAlDU,SAkDV,CAAA;EAAnB;;;EAuFR,OAAA,CAAA,UAAA,EAnIM,YAmIN,CAAA,EAnIqB,OAmIrB,CAAA,IAAA,CAAA;EAeG;;;EAiCR,OAAA,CAAA,cAvIa,kBAuIM,CAvIa,SAuIb,CAAA,CAAA,CAAA,YAAA,EAvIuC,KAuIvC,CAAA,EAvI+C,OAuI/C,CAAA,IAAA,CAAA;EAAmB;;;EAE3C,UAAA,CAAA,CAAA,EAlDU,OAkDV,CAAA,IAAA,CAAA;EACE;;AAOd;EAAqD,aAAA,CAAA,CAAA,EA3C5B,OA2C4B,CAAA,IAAA,CAAA;EACtB;;;EACpB,KAAA,CAAA,CAAA,EA9BM,OA8BN,CAAA,IAAA,CAAA;;;;;UAZM,sCAAsC;YAC3C;YACA,4BAA4B;cAC1B;;;;;;iBAOQ,+BAA+B,6BAC1C,oBAAoB,aAC5B,QAAQ,WAAW"}
package/dist/index.d.mts CHANGED
@@ -34,10 +34,19 @@ declare class AmqpWorker<TContract extends ContractDefinition> {
34
34
  */
35
35
  close(): Promise<void>;
36
36
  }
37
+ /**
38
+ * Options for creating a worker
39
+ */
40
+ interface CreateWorkerOptions<TContract extends ContractDefinition> {
41
+ contract: TContract;
42
+ handlers: WorkerInferConsumerHandlers<TContract>;
43
+ connection: ChannelModel;
44
+ }
37
45
  /**
38
46
  * Create a type-safe AMQP worker from a contract
47
+ * The worker will automatically connect and start consuming all messages
39
48
  */
40
- declare function createWorker<TContract extends ContractDefinition>(contract: TContract, handlers: WorkerInferConsumerHandlers<TContract>): AmqpWorker<TContract>;
49
+ declare function createWorker<TContract extends ContractDefinition>(options: CreateWorkerOptions<TContract>): Promise<AmqpWorker<TContract>>;
41
50
  //#endregion
42
- export { AmqpWorker, createWorker };
51
+ export { AmqpWorker, type CreateWorkerOptions, createWorker };
43
52
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/worker.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAA0C,cAA7B,UAA6B,CAAA,kBAAA,kBAAA,CAAA,CAAA;EAMX,iBAAA,QAAA;EAC4B,iBAAA,QAAA;EAA5B,QAAA,OAAA;EAMH,QAAA,UAAA;EAAe,QAAA,YAAA;EA4CM,WAAA,CAAA,QAAA,EAnDlB,SAmDkB,EAAA,QAAA,EAlDlB,2BAkDkB,CAlDU,SAkDV,CAAA;EAAnB;;;EAuFR,OAAA,CAAA,UAAA,EAnIM,YAmIN,CAAA,EAnIqB,OAmIrB,CAAA,IAAA,CAAA;EAeG;;;EAiCT,OAAA,CAAA,cAvIc,kBAuIF,CAvIqB,SAuIrB,CAAA,CAAA,CAAA,YAAA,EAvI+C,KAuI/C,CAAA,EAvIuD,OAuIvD,CAAA,IAAA,CAAA;EAAmB;;;EAEnC,UAAA,CAAA,CAAA,EAlDU,OAkDV,CAAA,IAAA,CAAA;EACE;;;mBApCW;;;;WAeR;;;;;iBAkBD,+BAA+B,8BACnC,qBACA,4BAA4B,aACrC,WAAW"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/worker.ts"],"sourcesContent":[],"mappings":";;;;;;;AAUA;AAA0C,cAA7B,UAA6B,CAAA,kBAAA,kBAAA,CAAA,CAAA;EAMX,iBAAA,QAAA;EAC4B,iBAAA,QAAA;EAA5B,QAAA,OAAA;EAMH,QAAA,UAAA;EAAe,QAAA,YAAA;EA4CM,WAAA,CAAA,QAAA,EAnDlB,SAmDkB,EAAA,QAAA,EAlDlB,2BAkDkB,CAlDU,SAkDV,CAAA;EAAnB;;;EAuFR,OAAA,CAAA,UAAA,EAnIM,YAmIN,CAAA,EAnIqB,OAmIrB,CAAA,IAAA,CAAA;EAeG;;;EAiCR,OAAA,CAAA,cAvIa,kBAuIM,CAvIa,SAuIb,CAAA,CAAA,CAAA,YAAA,EAvIuC,KAuIvC,CAAA,EAvI+C,OAuI/C,CAAA,IAAA,CAAA;EAAmB;;;EAE3C,UAAA,CAAA,CAAA,EAlDU,OAkDV,CAAA,IAAA,CAAA;EACE;;AAOd;EAAqD,aAAA,CAAA,CAAA,EA3C5B,OA2C4B,CAAA,IAAA,CAAA;EACtB;;;EACpB,KAAA,CAAA,CAAA,EA9BM,OA8BN,CAAA,IAAA,CAAA;;;;;UAZM,sCAAsC;YAC3C;YACA,4BAA4B;cAC1B;;;;;;iBAOQ,+BAA+B,6BAC1C,oBAAoB,aAC5B,QAAQ,WAAW"}
package/dist/index.mjs CHANGED
@@ -95,9 +95,13 @@ var AmqpWorker = class {
95
95
  };
96
96
  /**
97
97
  * Create a type-safe AMQP worker from a contract
98
+ * The worker will automatically connect and start consuming all messages
98
99
  */
99
- function createWorker(contract, handlers) {
100
- return new AmqpWorker(contract, handlers);
100
+ async function createWorker(options) {
101
+ const worker = new AmqpWorker(options.contract, options.handlers);
102
+ await worker.connect(options.connection);
103
+ await worker.consumeAll();
104
+ return worker;
101
105
  }
102
106
 
103
107
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["contract: TContract","handlers: WorkerInferConsumerHandlers<TContract>"],"sources":["../src/worker.ts"],"sourcesContent":["import type { Channel, ChannelModel, ConsumeMessage } from \"amqplib\";\nimport type {\n ContractDefinition,\n InferConsumerNames,\n WorkerInferConsumerHandlers,\n} from \"@amqp-contract/contract\";\n\n/**\n * Type-safe AMQP worker for consuming messages\n */\nexport class AmqpWorker<TContract extends ContractDefinition> {\n private channel: Channel | null = null;\n private connection: ChannelModel | null = null;\n private consumerTags: string[] = [];\n\n constructor(\n private readonly contract: TContract,\n private readonly handlers: WorkerInferConsumerHandlers<TContract>,\n ) {}\n\n /**\n * Connect to AMQP broker\n */\n async connect(connection: ChannelModel): Promise<void> {\n this.connection = connection;\n this.channel = await connection.createChannel();\n\n // Setup exchanges\n if (this.contract.exchanges && this.channel) {\n for (const exchange of Object.values(this.contract.exchanges)) {\n await this.channel.assertExchange(exchange.name, exchange.type, {\n durable: exchange.durable,\n autoDelete: exchange.autoDelete,\n internal: exchange.internal,\n arguments: exchange.arguments,\n });\n }\n }\n\n // Setup queues\n if (this.contract.queues && this.channel) {\n for (const queue of Object.values(this.contract.queues)) {\n await this.channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queue.arguments,\n });\n }\n }\n\n // Setup bindings\n if (this.contract.bindings && this.channel) {\n for (const binding of Object.values(this.contract.bindings)) {\n await this.channel.bindQueue(\n binding.queue,\n binding.exchange,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }\n }\n }\n\n /**\n * Start consuming messages for a specific consumer\n */\n async consume<TName extends InferConsumerNames<TContract>>(consumerName: TName): Promise<void> {\n if (!this.channel) {\n throw new Error(\"Worker not connected. Call connect() first.\");\n }\n\n const consumers = this.contract.consumers as Record<string, unknown>;\n if (!consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumer = consumers[consumerName as string];\n if (!consumer || typeof consumer !== \"object\") {\n throw new Error(`Consumer \"${String(consumerName)}\" not found in contract`);\n }\n\n const consumerDef = consumer as {\n queue: string;\n message: { \"~standard\": { validate: (value: unknown) => unknown } };\n prefetch?: number;\n noAck?: boolean;\n };\n\n const handler = this.handlers[consumerName];\n if (!handler) {\n throw new Error(`Handler for \"${String(consumerName)}\" not provided`);\n }\n\n // Set prefetch if specified\n if (consumerDef.prefetch !== undefined) {\n await this.channel.prefetch(consumerDef.prefetch);\n }\n\n // Start consuming\n const result = await this.channel.consume(\n consumerDef.queue,\n async (msg: ConsumeMessage | null) => {\n if (!msg) {\n return;\n }\n\n try {\n // Parse message\n const content = JSON.parse(msg.content.toString());\n\n // Validate message using schema\n const validation = consumerDef.message[\"~standard\"].validate(content);\n if (\n typeof validation === \"object\" &&\n validation !== null &&\n \"issues\" in validation &&\n validation.issues\n ) {\n console.error(\"Message validation failed:\", validation.issues);\n // Reject message with no requeue\n this.channel?.nack(msg, false, false);\n return;\n }\n\n const validatedMessage =\n typeof validation === \"object\" && validation !== null && \"value\" in validation\n ? validation.value\n : content;\n\n // Call handler\n await handler(validatedMessage);\n\n // Acknowledge message if not in noAck mode\n if (!consumerDef.noAck) {\n this.channel?.ack(msg);\n }\n } catch (error) {\n console.error(\"Error processing message:\", error);\n // Reject message and requeue\n this.channel?.nack(msg, false, true);\n }\n },\n {\n noAck: consumerDef.noAck ?? false,\n },\n );\n\n this.consumerTags.push(result.consumerTag);\n }\n\n /**\n * Start consuming messages for all consumers\n */\n async consumeAll(): Promise<void> {\n if (!this.contract.consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n\n for (const consumerName of consumerNames) {\n await this.consume(consumerName);\n }\n }\n\n /**\n * Stop consuming messages\n */\n async stopConsuming(): Promise<void> {\n if (!this.channel) {\n return;\n }\n\n for (const tag of this.consumerTags) {\n await this.channel.cancel(tag);\n }\n\n this.consumerTags = [];\n }\n\n /**\n * Close the connection\n */\n async close(): Promise<void> {\n await this.stopConsuming();\n\n if (this.channel) {\n await this.channel.close();\n this.channel = null;\n }\n\n if (this.connection) {\n await (this.connection as unknown as { close(): Promise<void> }).close();\n this.connection = null;\n }\n }\n}\n\n/**\n * Create a type-safe AMQP worker from a contract\n */\nexport function createWorker<TContract extends ContractDefinition>(\n contract: TContract,\n handlers: WorkerInferConsumerHandlers<TContract>,\n): AmqpWorker<TContract> {\n return new AmqpWorker(contract, handlers);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["contract: TContract","handlers: WorkerInferConsumerHandlers<TContract>"],"sources":["../src/worker.ts"],"sourcesContent":["import type { Channel, ChannelModel, ConsumeMessage } from \"amqplib\";\nimport type {\n ContractDefinition,\n InferConsumerNames,\n WorkerInferConsumerHandlers,\n} from \"@amqp-contract/contract\";\n\n/**\n * Type-safe AMQP worker for consuming messages\n */\nexport class AmqpWorker<TContract extends ContractDefinition> {\n private channel: Channel | null = null;\n private connection: ChannelModel | null = null;\n private consumerTags: string[] = [];\n\n constructor(\n private readonly contract: TContract,\n private readonly handlers: WorkerInferConsumerHandlers<TContract>,\n ) {}\n\n /**\n * Connect to AMQP broker\n */\n async connect(connection: ChannelModel): Promise<void> {\n this.connection = connection;\n this.channel = await connection.createChannel();\n\n // Setup exchanges\n if (this.contract.exchanges && this.channel) {\n for (const exchange of Object.values(this.contract.exchanges)) {\n await this.channel.assertExchange(exchange.name, exchange.type, {\n durable: exchange.durable,\n autoDelete: exchange.autoDelete,\n internal: exchange.internal,\n arguments: exchange.arguments,\n });\n }\n }\n\n // Setup queues\n if (this.contract.queues && this.channel) {\n for (const queue of Object.values(this.contract.queues)) {\n await this.channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queue.arguments,\n });\n }\n }\n\n // Setup bindings\n if (this.contract.bindings && this.channel) {\n for (const binding of Object.values(this.contract.bindings)) {\n await this.channel.bindQueue(\n binding.queue,\n binding.exchange,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }\n }\n }\n\n /**\n * Start consuming messages for a specific consumer\n */\n async consume<TName extends InferConsumerNames<TContract>>(consumerName: TName): Promise<void> {\n if (!this.channel) {\n throw new Error(\"Worker not connected. Call connect() first.\");\n }\n\n const consumers = this.contract.consumers as Record<string, unknown>;\n if (!consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumer = consumers[consumerName as string];\n if (!consumer || typeof consumer !== \"object\") {\n throw new Error(`Consumer \"${String(consumerName)}\" not found in contract`);\n }\n\n const consumerDef = consumer as {\n queue: string;\n message: { \"~standard\": { validate: (value: unknown) => unknown } };\n prefetch?: number;\n noAck?: boolean;\n };\n\n const handler = this.handlers[consumerName];\n if (!handler) {\n throw new Error(`Handler for \"${String(consumerName)}\" not provided`);\n }\n\n // Set prefetch if specified\n if (consumerDef.prefetch !== undefined) {\n await this.channel.prefetch(consumerDef.prefetch);\n }\n\n // Start consuming\n const result = await this.channel.consume(\n consumerDef.queue,\n async (msg: ConsumeMessage | null) => {\n if (!msg) {\n return;\n }\n\n try {\n // Parse message\n const content = JSON.parse(msg.content.toString());\n\n // Validate message using schema\n const validation = consumerDef.message[\"~standard\"].validate(content);\n if (\n typeof validation === \"object\" &&\n validation !== null &&\n \"issues\" in validation &&\n validation.issues\n ) {\n console.error(\"Message validation failed:\", validation.issues);\n // Reject message with no requeue\n this.channel?.nack(msg, false, false);\n return;\n }\n\n const validatedMessage =\n typeof validation === \"object\" && validation !== null && \"value\" in validation\n ? validation.value\n : content;\n\n // Call handler\n await handler(validatedMessage);\n\n // Acknowledge message if not in noAck mode\n if (!consumerDef.noAck) {\n this.channel?.ack(msg);\n }\n } catch (error) {\n console.error(\"Error processing message:\", error);\n // Reject message and requeue\n this.channel?.nack(msg, false, true);\n }\n },\n {\n noAck: consumerDef.noAck ?? false,\n },\n );\n\n this.consumerTags.push(result.consumerTag);\n }\n\n /**\n * Start consuming messages for all consumers\n */\n async consumeAll(): Promise<void> {\n if (!this.contract.consumers) {\n throw new Error(\"No consumers defined in contract\");\n }\n\n const consumerNames = Object.keys(this.contract.consumers) as InferConsumerNames<TContract>[];\n\n for (const consumerName of consumerNames) {\n await this.consume(consumerName);\n }\n }\n\n /**\n * Stop consuming messages\n */\n async stopConsuming(): Promise<void> {\n if (!this.channel) {\n return;\n }\n\n for (const tag of this.consumerTags) {\n await this.channel.cancel(tag);\n }\n\n this.consumerTags = [];\n }\n\n /**\n * Close the connection\n */\n async close(): Promise<void> {\n await this.stopConsuming();\n\n if (this.channel) {\n await this.channel.close();\n this.channel = null;\n }\n\n if (this.connection) {\n await (this.connection as unknown as { close(): Promise<void> }).close();\n this.connection = null;\n }\n }\n}\n\n/**\n * Options for creating a worker\n */\nexport interface CreateWorkerOptions<TContract extends ContractDefinition> {\n contract: TContract;\n handlers: WorkerInferConsumerHandlers<TContract>;\n connection: ChannelModel;\n}\n\n/**\n * Create a type-safe AMQP worker from a contract\n * The worker will automatically connect and start consuming all messages\n */\nexport async function createWorker<TContract extends ContractDefinition>(\n options: CreateWorkerOptions<TContract>,\n): Promise<AmqpWorker<TContract>> {\n const worker = new AmqpWorker(options.contract, options.handlers);\n await worker.connect(options.connection);\n await worker.consumeAll();\n return worker;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/worker",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Worker utilities for consuming messages using amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -41,7 +41,7 @@
41
41
  "dist"
42
42
  ],
43
43
  "dependencies": {
44
- "@amqp-contract/contract": "0.0.2"
44
+ "@amqp-contract/contract": "0.0.4"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/amqplib": "0.10.8",
@@ -52,6 +52,8 @@
52
52
  "typescript": "5.9.3",
53
53
  "vitest": "4.0.16",
54
54
  "zod": "4.2.1",
55
+ "@amqp-contract/client": "0.0.4",
56
+ "@amqp-contract/testing": "0.0.4",
55
57
  "@amqp-contract/tsconfig": "0.0.0"
56
58
  },
57
59
  "peerDependencies": {
@@ -60,8 +62,9 @@
60
62
  "scripts": {
61
63
  "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
62
64
  "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
63
- "test": "vitest run",
64
- "test:watch": "vitest",
65
+ "test": "vitest run --project unit",
66
+ "test:integration": "vitest run --project integration",
67
+ "test:watch": "vitest --project unit",
65
68
  "typecheck": "tsc --noEmit"
66
69
  }
67
70
  }