@amqp-contract/worker 1.0.0 → 2.0.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 +2 -2
- package/dist/index.cjs +35 -35
- package/dist/index.d.cts +18 -18
- package/dist/index.d.mts +18 -18
- package/dist/index.mjs +36 -36
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +66 -66
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -129,13 +129,13 @@ Worker handlers return `AsyncResult<void, HandlerError>` for explicit error hand
|
|
|
129
129
|
|
|
130
130
|
```typescript
|
|
131
131
|
import { RetryableError, NonRetryableError } from "@amqp-contract/worker";
|
|
132
|
-
import {
|
|
132
|
+
import { Err, fromPromise, type AsyncResult } from "unthrown";
|
|
133
133
|
|
|
134
134
|
handlers: {
|
|
135
135
|
processOrder: ({ payload }) => {
|
|
136
136
|
// Validation errors - non-retryable
|
|
137
137
|
if (payload.amount <= 0) {
|
|
138
|
-
return
|
|
138
|
+
return Err(new NonRetryableError("Invalid amount")).toAsync();
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Transient errors - retryable
|
package/dist/index.cjs
CHANGED
|
@@ -27,9 +27,9 @@ function isSupportedEncoding(encoding) {
|
|
|
27
27
|
* @internal
|
|
28
28
|
*/
|
|
29
29
|
function decompressBuffer(buffer, contentEncoding) {
|
|
30
|
-
if (!contentEncoding) return (0, unthrown.
|
|
30
|
+
if (!contentEncoding) return (0, unthrown.Ok)(buffer).toAsync();
|
|
31
31
|
const normalizedEncoding = contentEncoding.toLowerCase();
|
|
32
|
-
if (!isSupportedEncoding(normalizedEncoding)) return (0, unthrown.
|
|
32
|
+
if (!isSupportedEncoding(normalizedEncoding)) return (0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`Unsupported content-encoding: "${contentEncoding}". Supported encodings are: ${SUPPORTED_ENCODINGS.join(", ")}. Please check your publisher configuration.`)).toAsync();
|
|
33
33
|
switch (normalizedEncoding) {
|
|
34
34
|
case "gzip": return (0, unthrown.fromPromise)(gunzipAsync(buffer), (error) => new _amqp_contract_core.TechnicalError("Failed to decompress gzip", error));
|
|
35
35
|
case "deflate": return (0, unthrown.fromPromise)(inflateAsync(buffer), (error) => new _amqp_contract_core.TechnicalError("Failed to decompress deflate", error));
|
|
@@ -186,17 +186,17 @@ function retryable(message, cause) {
|
|
|
186
186
|
* @example
|
|
187
187
|
* ```typescript
|
|
188
188
|
* import { nonRetryable } from '@amqp-contract/worker';
|
|
189
|
-
* import {
|
|
189
|
+
* import { Err, Ok } from 'unthrown';
|
|
190
190
|
*
|
|
191
191
|
* const handler = ({ payload }) => {
|
|
192
192
|
* if (!isValidPayload(payload)) {
|
|
193
|
-
* return
|
|
193
|
+
* return Err(nonRetryable('Invalid payload format')).toAsync();
|
|
194
194
|
* }
|
|
195
|
-
* return
|
|
195
|
+
* return Ok(undefined).toAsync();
|
|
196
196
|
* };
|
|
197
197
|
*
|
|
198
198
|
* // Equivalent to:
|
|
199
|
-
* // return
|
|
199
|
+
* // return Err(new NonRetryableError('Invalid payload format')).toAsync();
|
|
200
200
|
* ```
|
|
201
201
|
*/
|
|
202
202
|
function nonRetryable(message, cause) {
|
|
@@ -225,7 +225,7 @@ function nonRetryable(message, cause) {
|
|
|
225
225
|
function handleError(ctx, error, msg, consumerName, consumer) {
|
|
226
226
|
if (error instanceof NonRetryableError) {
|
|
227
227
|
sendToDLQ(ctx, msg, consumer);
|
|
228
|
-
return (0, unthrown.
|
|
228
|
+
return (0, unthrown.Ok)(void 0).toAsync();
|
|
229
229
|
}
|
|
230
230
|
const config = (0, _amqp_contract_contract.extractQueue)(consumer.queue).retry;
|
|
231
231
|
if (config.mode === "immediate-requeue") return handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, config);
|
|
@@ -235,7 +235,7 @@ function handleError(ctx, error, msg, consumerName, consumer) {
|
|
|
235
235
|
queueName: (0, _amqp_contract_contract.extractQueue)(consumer.queue).name
|
|
236
236
|
});
|
|
237
237
|
sendToDLQ(ctx, msg, consumer);
|
|
238
|
-
return (0, unthrown.
|
|
238
|
+
return (0, unthrown.Ok)(void 0).toAsync();
|
|
239
239
|
}
|
|
240
240
|
/**
|
|
241
241
|
* Handle error by requeuing immediately.
|
|
@@ -258,7 +258,7 @@ function handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, co
|
|
|
258
258
|
maxRetries: config.maxRetries
|
|
259
259
|
});
|
|
260
260
|
sendToDLQ(ctx, msg, consumer);
|
|
261
|
-
return (0, unthrown.
|
|
261
|
+
return (0, unthrown.Ok)(void 0).toAsync();
|
|
262
262
|
}
|
|
263
263
|
ctx.logger?.info("Retrying message (immediate-requeue mode)", {
|
|
264
264
|
consumerName,
|
|
@@ -268,7 +268,7 @@ function handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, co
|
|
|
268
268
|
});
|
|
269
269
|
if (queue.type === "quorum") {
|
|
270
270
|
ctx.amqpClient.nack(msg, false, true);
|
|
271
|
-
return (0, unthrown.
|
|
271
|
+
return (0, unthrown.Ok)(void 0).toAsync();
|
|
272
272
|
} else return publishForRetry(ctx, {
|
|
273
273
|
msg,
|
|
274
274
|
exchange: msg.fields.exchange,
|
|
@@ -309,7 +309,7 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
|
|
|
309
309
|
consumerName,
|
|
310
310
|
queueName: consumer.queue.name
|
|
311
311
|
});
|
|
312
|
-
return (0, unthrown.
|
|
312
|
+
return (0, unthrown.Err)(new _amqp_contract_core.TechnicalError("Queue does not have TTL-backoff infrastructure")).toAsync();
|
|
313
313
|
}
|
|
314
314
|
const queueEntry = consumer.queue;
|
|
315
315
|
const queueName = (0, _amqp_contract_contract.extractQueue)(queueEntry).name;
|
|
@@ -322,7 +322,7 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
|
|
|
322
322
|
maxRetries: config.maxRetries
|
|
323
323
|
});
|
|
324
324
|
sendToDLQ(ctx, msg, consumer);
|
|
325
|
-
return (0, unthrown.
|
|
325
|
+
return (0, unthrown.Ok)(void 0).toAsync();
|
|
326
326
|
}
|
|
327
327
|
const delayMs = calculateRetryDelay(retryCount, config);
|
|
328
328
|
ctx.logger?.info("Retrying message (ttl-backoff mode)", {
|
|
@@ -402,7 +402,7 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
|
|
|
402
402
|
retryCount: newRetryCount,
|
|
403
403
|
...delayMs !== void 0 ? { delayMs } : {}
|
|
404
404
|
});
|
|
405
|
-
return (0, unthrown.
|
|
405
|
+
return (0, unthrown.Err)(new _amqp_contract_core.TechnicalError("Failed to publish message for retry (write buffer full)"));
|
|
406
406
|
}
|
|
407
407
|
ctx.amqpClient.ack(msg);
|
|
408
408
|
ctx.logger?.info("Message published for retry", {
|
|
@@ -410,7 +410,7 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
|
|
|
410
410
|
retryCount: newRetryCount,
|
|
411
411
|
...delayMs !== void 0 ? { delayMs } : {}
|
|
412
412
|
});
|
|
413
|
-
return (0, unthrown.
|
|
413
|
+
return (0, unthrown.Ok)(void 0);
|
|
414
414
|
}).orElse((publishError) => {
|
|
415
415
|
ctx.logger?.error("Publish for retry failed; leaving original un-ack'd for redelivery", {
|
|
416
416
|
queueName,
|
|
@@ -418,7 +418,7 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
|
|
|
418
418
|
...delayMs !== void 0 ? { delayMs } : {},
|
|
419
419
|
error: publishError
|
|
420
420
|
});
|
|
421
|
-
return (0, unthrown.
|
|
421
|
+
return (0, unthrown.Err)(publishError);
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
/**
|
|
@@ -455,7 +455,7 @@ function isHandlerTuple(entry) {
|
|
|
455
455
|
* ```typescript
|
|
456
456
|
* import { TypedAmqpWorker } from '@amqp-contract/worker';
|
|
457
457
|
* import { defineQueue, defineMessage, defineContract, defineConsumer } from '@amqp-contract/contract';
|
|
458
|
-
* import {
|
|
458
|
+
* import { Ok } from 'unthrown';
|
|
459
459
|
* import { z } from 'zod';
|
|
460
460
|
*
|
|
461
461
|
* const orderQueue = defineQueue('order-processing');
|
|
@@ -475,7 +475,7 @@ function isHandlerTuple(entry) {
|
|
|
475
475
|
* handlers: {
|
|
476
476
|
* processOrder: ({ payload }) => {
|
|
477
477
|
* console.log('Processing order', payload.orderId);
|
|
478
|
-
* return
|
|
478
|
+
* return Ok(undefined).toAsync();
|
|
479
479
|
* },
|
|
480
480
|
* },
|
|
481
481
|
* urls: ['amqp://localhost'],
|
|
@@ -571,7 +571,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
571
571
|
* const result = await TypedAmqpWorker.create({
|
|
572
572
|
* contract: myContract,
|
|
573
573
|
* handlers: {
|
|
574
|
-
* processOrder: ({ payload }) =>
|
|
574
|
+
* processOrder: ({ payload }) => Ok(undefined).toAsync(),
|
|
575
575
|
* },
|
|
576
576
|
* urls: ['amqp://localhost'],
|
|
577
577
|
* });
|
|
@@ -613,7 +613,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
613
613
|
consumerTag,
|
|
614
614
|
error
|
|
615
615
|
});
|
|
616
|
-
return (0, unthrown.
|
|
616
|
+
return (0, unthrown.Ok)(void 0);
|
|
617
617
|
}))).tap(() => {
|
|
618
618
|
this.consumerTags.clear();
|
|
619
619
|
}).flatMap(() => this.amqpClient.close()).map(() => void 0);
|
|
@@ -645,8 +645,8 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
645
645
|
validateSchema(schema, data, context) {
|
|
646
646
|
const rawValidation = schema["~standard"].validate(data);
|
|
647
647
|
return (0, unthrown.fromPromise)(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new _amqp_contract_core.TechnicalError(`Error validating ${context.field}`, error)).flatMap((result) => {
|
|
648
|
-
if (result.issues) return (0, unthrown.
|
|
649
|
-
return (0, unthrown.
|
|
648
|
+
if (result.issues) return (0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`${context.field} validation failed`, new _amqp_contract_core.MessageValidationError(context.consumerName, result.issues)));
|
|
649
|
+
return (0, unthrown.Ok)(result.value);
|
|
650
650
|
});
|
|
651
651
|
}
|
|
652
652
|
/**
|
|
@@ -664,7 +664,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
664
664
|
})), consumer.message.headers ? this.validateSchema(consumer.message.headers, msg.properties.headers ?? {}, {
|
|
665
665
|
...context,
|
|
666
666
|
field: "headers"
|
|
667
|
-
}) : (0, unthrown.
|
|
667
|
+
}) : (0, unthrown.Ok)(void 0).toAsync()]).map(([payload, headers]) => ({
|
|
668
668
|
payload,
|
|
669
669
|
headers
|
|
670
670
|
}));
|
|
@@ -696,7 +696,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
696
696
|
rpcName: String(rpcName),
|
|
697
697
|
queueName
|
|
698
698
|
});
|
|
699
|
-
return (0, unthrown.
|
|
699
|
+
return (0, unthrown.Err)(new NonRetryableError(`RPC "${String(rpcName)}" received a message without replyTo; cannot deliver response`)).toAsync();
|
|
700
700
|
}
|
|
701
701
|
if (typeof correlationId !== "string" || correlationId.length === 0) {
|
|
702
702
|
this.logger?.error("RPC handler returned a response but the incoming message has no correlationId", {
|
|
@@ -704,21 +704,21 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
704
704
|
queueName,
|
|
705
705
|
replyTo
|
|
706
706
|
});
|
|
707
|
-
return (0, unthrown.
|
|
707
|
+
return (0, unthrown.Err)(new NonRetryableError(`RPC "${String(rpcName)}" received a message without correlationId; cannot deliver response`)).toAsync();
|
|
708
708
|
}
|
|
709
709
|
let rawValidation;
|
|
710
710
|
try {
|
|
711
711
|
rawValidation = responseSchema["~standard"].validate(response);
|
|
712
712
|
} catch (error) {
|
|
713
|
-
return (0, unthrown.
|
|
713
|
+
return (0, unthrown.Err)(new NonRetryableError("RPC response schema validation threw", error)).toAsync();
|
|
714
714
|
}
|
|
715
715
|
return (0, unthrown.fromPromise)(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new NonRetryableError("RPC response schema validation threw", error)).flatMap((validation) => {
|
|
716
|
-
if (validation.issues) return (0, unthrown.
|
|
717
|
-
return (0, unthrown.
|
|
716
|
+
if (validation.issues) return (0, unthrown.Err)(new NonRetryableError(`RPC response for "${String(rpcName)}" failed schema validation`, new _amqp_contract_core.MessageValidationError(String(rpcName), validation.issues)));
|
|
717
|
+
return (0, unthrown.Ok)(validation.value);
|
|
718
718
|
}).flatMap((validatedResponse) => this.amqpClient.publish("", replyTo, validatedResponse, {
|
|
719
719
|
correlationId,
|
|
720
720
|
contentType: "application/json"
|
|
721
|
-
}).mapErr((error) => new NonRetryableError("Failed to publish RPC response", error)).flatMap((published) => published ? (0, unthrown.
|
|
721
|
+
}).mapErr((error) => new NonRetryableError("Failed to publish RPC response", error)).flatMap((published) => published ? (0, unthrown.Ok)(void 0) : (0, unthrown.Err)(new NonRetryableError("Failed to publish RPC response: channel buffer full"))));
|
|
722
722
|
}
|
|
723
723
|
/**
|
|
724
724
|
* Parse and validate the message; on failure, nack(requeue=false) so the
|
|
@@ -729,7 +729,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
729
729
|
parseAndValidateOrNack(msg, consumer, name) {
|
|
730
730
|
return this.parseAndValidateMessage(msg, consumer, name).orElse((parseError) => {
|
|
731
731
|
this.amqpClient.nack(msg, false, false);
|
|
732
|
-
return (0, unthrown.
|
|
732
|
+
return (0, unthrown.Err)(parseError).toAsync();
|
|
733
733
|
});
|
|
734
734
|
}
|
|
735
735
|
/**
|
|
@@ -744,10 +744,10 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
744
744
|
/**
|
|
745
745
|
* For RPC handlers, validate and publish the reply on the caller's
|
|
746
746
|
* `replyTo` / `correlationId`. For non-RPC consumers, this is a no-op that
|
|
747
|
-
* resolves to `
|
|
747
|
+
* resolves to `Ok(undefined).toAsync()`.
|
|
748
748
|
*/
|
|
749
749
|
publishReplyIfRpc(msg, view, name, handlerResponse) {
|
|
750
|
-
if (!view.isRpc || !view.responseSchema) return (0, unthrown.
|
|
750
|
+
if (!view.isRpc || !view.responseSchema) return (0, unthrown.Ok)(void 0).toAsync();
|
|
751
751
|
const queueName = (0, _amqp_contract_contract.extractQueue)(view.consumer.queue).name;
|
|
752
752
|
return this.publishRpcResponse(msg, queueName, name, view.responseSchema, handlerResponse);
|
|
753
753
|
}
|
|
@@ -760,7 +760,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
760
760
|
* is still needed (see {@link consumeSingle}).
|
|
761
761
|
*
|
|
762
762
|
* Success-vs-failure telemetry is data-driven: the chain resolves to
|
|
763
|
-
* `
|
|
763
|
+
* `Ok(undefined)` only on handler success (and reply-publish success for
|
|
764
764
|
* RPC). Handler failures — even when {@link handleError} routes them
|
|
765
765
|
* successfully to retry/DLQ — are classified as failures for metrics by
|
|
766
766
|
* re-failing the chain with a `TechnicalError` whose `cause` is the
|
|
@@ -800,7 +800,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
|
|
|
800
800
|
logger: this.logger
|
|
801
801
|
}, handlerError, msg, String(name), consumer).tap(() => {
|
|
802
802
|
state.messageHandled = true;
|
|
803
|
-
}).flatMap(() => (0, unthrown.
|
|
803
|
+
}).flatMap(() => (0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`Handler "${String(name)}" failed: ${handlerError.message}`, handlerError)).toAsync());
|
|
804
804
|
})).tap(() => {
|
|
805
805
|
try {
|
|
806
806
|
(0, _amqp_contract_core.endSpanSuccess)(span);
|
|
@@ -922,7 +922,7 @@ function defineHandler(contract, name, handler, options) {
|
|
|
922
922
|
* @example
|
|
923
923
|
* ```typescript
|
|
924
924
|
* import { defineHandlers, RetryableError } from '@amqp-contract/worker';
|
|
925
|
-
* import { fromPromise,
|
|
925
|
+
* import { fromPromise, Ok } from 'unthrown';
|
|
926
926
|
*
|
|
927
927
|
* const handlers = defineHandlers(orderContract, {
|
|
928
928
|
* processOrder: ({ payload }) =>
|
|
@@ -930,7 +930,7 @@ function defineHandler(contract, name, handler, options) {
|
|
|
930
930
|
* processPayment(payload),
|
|
931
931
|
* (error) => new RetryableError('Payment failed', error),
|
|
932
932
|
* ).map(() => undefined),
|
|
933
|
-
* calculate: ({ payload }) =>
|
|
933
|
+
* calculate: ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
|
|
934
934
|
* });
|
|
935
935
|
* ```
|
|
936
936
|
*/
|
package/dist/index.d.cts
CHANGED
|
@@ -333,17 +333,17 @@ declare function retryable(message: string, cause?: unknown): RetryableError;
|
|
|
333
333
|
* @example
|
|
334
334
|
* ```typescript
|
|
335
335
|
* import { nonRetryable } from '@amqp-contract/worker';
|
|
336
|
-
* import {
|
|
336
|
+
* import { Err, Ok } from 'unthrown';
|
|
337
337
|
*
|
|
338
338
|
* const handler = ({ payload }) => {
|
|
339
339
|
* if (!isValidPayload(payload)) {
|
|
340
|
-
* return
|
|
340
|
+
* return Err(nonRetryable('Invalid payload format')).toAsync();
|
|
341
341
|
* }
|
|
342
|
-
* return
|
|
342
|
+
* return Ok(undefined).toAsync();
|
|
343
343
|
* };
|
|
344
344
|
*
|
|
345
345
|
* // Equivalent to:
|
|
346
|
-
* // return
|
|
346
|
+
* // return Err(new NonRetryableError('Invalid payload format')).toAsync();
|
|
347
347
|
* ```
|
|
348
348
|
*/
|
|
349
349
|
declare function nonRetryable(message: string, cause?: unknown): NonRetryableError;
|
|
@@ -412,7 +412,7 @@ type WorkerInferRpcResponse<TContract extends ContractDefinition, TName extends
|
|
|
412
412
|
* console.log(message.payload.orderId); // Typed payload
|
|
413
413
|
* console.log(message.headers?.priority); // Typed headers (if defined)
|
|
414
414
|
* console.log(rawMessage.fields.deliveryTag); // Raw AMQP message
|
|
415
|
-
* return
|
|
415
|
+
* return Ok(undefined).toAsync();
|
|
416
416
|
* });
|
|
417
417
|
* ```
|
|
418
418
|
*/
|
|
@@ -463,7 +463,7 @@ type WorkerInferRpcHandlerEntry<TContract extends ContractDefinition, TName exte
|
|
|
463
463
|
* processPayment(payload),
|
|
464
464
|
* (error) => new RetryableError('Payment failed', error),
|
|
465
465
|
* ).map(() => undefined),
|
|
466
|
-
* calculate: ({ payload }) =>
|
|
466
|
+
* calculate: ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
|
|
467
467
|
* };
|
|
468
468
|
* ```
|
|
469
469
|
*/
|
|
@@ -484,13 +484,13 @@ type ConsumerOptions = ConsumerOptions$1;
|
|
|
484
484
|
* // Simple handler
|
|
485
485
|
* processOrder: ({ payload }) => {
|
|
486
486
|
* console.log('Processing order:', payload.orderId);
|
|
487
|
-
* return
|
|
487
|
+
* return Ok(undefined).toAsync();
|
|
488
488
|
* },
|
|
489
489
|
* // Handler with prefetch configuration
|
|
490
490
|
* processPayment: [
|
|
491
491
|
* ({ payload }) => {
|
|
492
492
|
* console.log('Processing payment:', payload.paymentId);
|
|
493
|
-
* return
|
|
493
|
+
* return Ok(undefined).toAsync();
|
|
494
494
|
* },
|
|
495
495
|
* { prefetch: 10 }
|
|
496
496
|
* ]
|
|
@@ -538,7 +538,7 @@ type CreateWorkerOptions<TContract extends ContractDefinition> = {
|
|
|
538
538
|
defaultConsumerOptions?: ConsumerOptions | undefined;
|
|
539
539
|
/**
|
|
540
540
|
* Maximum time in ms to wait for the AMQP connection to become ready before
|
|
541
|
-
* `create()` resolves to an `
|
|
541
|
+
* `create()` resolves to an `Err(TechnicalError)`. Defaults to 30s
|
|
542
542
|
* (the {@link AmqpClient}'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to
|
|
543
543
|
* disable the timeout and let amqp-connection-manager retry indefinitely.
|
|
544
544
|
*/
|
|
@@ -556,7 +556,7 @@ type CreateWorkerOptions<TContract extends ContractDefinition> = {
|
|
|
556
556
|
* ```typescript
|
|
557
557
|
* import { TypedAmqpWorker } from '@amqp-contract/worker';
|
|
558
558
|
* import { defineQueue, defineMessage, defineContract, defineConsumer } from '@amqp-contract/contract';
|
|
559
|
-
* import {
|
|
559
|
+
* import { Ok } from 'unthrown';
|
|
560
560
|
* import { z } from 'zod';
|
|
561
561
|
*
|
|
562
562
|
* const orderQueue = defineQueue('order-processing');
|
|
@@ -576,7 +576,7 @@ type CreateWorkerOptions<TContract extends ContractDefinition> = {
|
|
|
576
576
|
* handlers: {
|
|
577
577
|
* processOrder: ({ payload }) => {
|
|
578
578
|
* console.log('Processing order', payload.orderId);
|
|
579
|
-
* return
|
|
579
|
+
* return Ok(undefined).toAsync();
|
|
580
580
|
* },
|
|
581
581
|
* },
|
|
582
582
|
* urls: ['amqp://localhost'],
|
|
@@ -630,7 +630,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
630
630
|
* const result = await TypedAmqpWorker.create({
|
|
631
631
|
* contract: myContract,
|
|
632
632
|
* handlers: {
|
|
633
|
-
* processOrder: ({ payload }) =>
|
|
633
|
+
* processOrder: ({ payload }) => Ok(undefined).toAsync(),
|
|
634
634
|
* },
|
|
635
635
|
* urls: ['amqp://localhost'],
|
|
636
636
|
* });
|
|
@@ -721,7 +721,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
721
721
|
/**
|
|
722
722
|
* For RPC handlers, validate and publish the reply on the caller's
|
|
723
723
|
* `replyTo` / `correlationId`. For non-RPC consumers, this is a no-op that
|
|
724
|
-
* resolves to `
|
|
724
|
+
* resolves to `Ok(undefined).toAsync()`.
|
|
725
725
|
*/
|
|
726
726
|
private publishReplyIfRpc;
|
|
727
727
|
/**
|
|
@@ -733,7 +733,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
733
733
|
* is still needed (see {@link consumeSingle}).
|
|
734
734
|
*
|
|
735
735
|
* Success-vs-failure telemetry is data-driven: the chain resolves to
|
|
736
|
-
* `
|
|
736
|
+
* `Ok(undefined)` only on handler success (and reply-publish success for
|
|
737
737
|
* RPC). Handler failures — even when {@link handleError} routes them
|
|
738
738
|
* successfully to retry/DLQ — are classified as failures for metrics by
|
|
739
739
|
* re-failing the chain with a `TechnicalError` whose `cause` is the
|
|
@@ -774,7 +774,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
774
774
|
* @example Consumer handler
|
|
775
775
|
* ```typescript
|
|
776
776
|
* import { defineHandler, RetryableError, NonRetryableError } from '@amqp-contract/worker';
|
|
777
|
-
* import { fromPromise,
|
|
777
|
+
* import { fromPromise, Ok } from 'unthrown';
|
|
778
778
|
*
|
|
779
779
|
* const processOrderHandler = defineHandler(
|
|
780
780
|
* orderContract,
|
|
@@ -792,7 +792,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
792
792
|
* const calculateHandler = defineHandler(
|
|
793
793
|
* rpcContract,
|
|
794
794
|
* 'calculate',
|
|
795
|
-
* ({ payload }) =>
|
|
795
|
+
* ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
|
|
796
796
|
* );
|
|
797
797
|
* ```
|
|
798
798
|
*/
|
|
@@ -819,7 +819,7 @@ declare function defineHandler<TContract extends ContractDefinition, TName exten
|
|
|
819
819
|
* @example
|
|
820
820
|
* ```typescript
|
|
821
821
|
* import { defineHandlers, RetryableError } from '@amqp-contract/worker';
|
|
822
|
-
* import { fromPromise,
|
|
822
|
+
* import { fromPromise, Ok } from 'unthrown';
|
|
823
823
|
*
|
|
824
824
|
* const handlers = defineHandlers(orderContract, {
|
|
825
825
|
* processOrder: ({ payload }) =>
|
|
@@ -827,7 +827,7 @@ declare function defineHandler<TContract extends ContractDefinition, TName exten
|
|
|
827
827
|
* processPayment(payload),
|
|
828
828
|
* (error) => new RetryableError('Payment failed', error),
|
|
829
829
|
* ).map(() => undefined),
|
|
830
|
-
* calculate: ({ payload }) =>
|
|
830
|
+
* calculate: ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
|
|
831
831
|
* });
|
|
832
832
|
* ```
|
|
833
833
|
*/
|
package/dist/index.d.mts
CHANGED
|
@@ -333,17 +333,17 @@ declare function retryable(message: string, cause?: unknown): RetryableError;
|
|
|
333
333
|
* @example
|
|
334
334
|
* ```typescript
|
|
335
335
|
* import { nonRetryable } from '@amqp-contract/worker';
|
|
336
|
-
* import {
|
|
336
|
+
* import { Err, Ok } from 'unthrown';
|
|
337
337
|
*
|
|
338
338
|
* const handler = ({ payload }) => {
|
|
339
339
|
* if (!isValidPayload(payload)) {
|
|
340
|
-
* return
|
|
340
|
+
* return Err(nonRetryable('Invalid payload format')).toAsync();
|
|
341
341
|
* }
|
|
342
|
-
* return
|
|
342
|
+
* return Ok(undefined).toAsync();
|
|
343
343
|
* };
|
|
344
344
|
*
|
|
345
345
|
* // Equivalent to:
|
|
346
|
-
* // return
|
|
346
|
+
* // return Err(new NonRetryableError('Invalid payload format')).toAsync();
|
|
347
347
|
* ```
|
|
348
348
|
*/
|
|
349
349
|
declare function nonRetryable(message: string, cause?: unknown): NonRetryableError;
|
|
@@ -412,7 +412,7 @@ type WorkerInferRpcResponse<TContract extends ContractDefinition, TName extends
|
|
|
412
412
|
* console.log(message.payload.orderId); // Typed payload
|
|
413
413
|
* console.log(message.headers?.priority); // Typed headers (if defined)
|
|
414
414
|
* console.log(rawMessage.fields.deliveryTag); // Raw AMQP message
|
|
415
|
-
* return
|
|
415
|
+
* return Ok(undefined).toAsync();
|
|
416
416
|
* });
|
|
417
417
|
* ```
|
|
418
418
|
*/
|
|
@@ -463,7 +463,7 @@ type WorkerInferRpcHandlerEntry<TContract extends ContractDefinition, TName exte
|
|
|
463
463
|
* processPayment(payload),
|
|
464
464
|
* (error) => new RetryableError('Payment failed', error),
|
|
465
465
|
* ).map(() => undefined),
|
|
466
|
-
* calculate: ({ payload }) =>
|
|
466
|
+
* calculate: ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
|
|
467
467
|
* };
|
|
468
468
|
* ```
|
|
469
469
|
*/
|
|
@@ -484,13 +484,13 @@ type ConsumerOptions = ConsumerOptions$1;
|
|
|
484
484
|
* // Simple handler
|
|
485
485
|
* processOrder: ({ payload }) => {
|
|
486
486
|
* console.log('Processing order:', payload.orderId);
|
|
487
|
-
* return
|
|
487
|
+
* return Ok(undefined).toAsync();
|
|
488
488
|
* },
|
|
489
489
|
* // Handler with prefetch configuration
|
|
490
490
|
* processPayment: [
|
|
491
491
|
* ({ payload }) => {
|
|
492
492
|
* console.log('Processing payment:', payload.paymentId);
|
|
493
|
-
* return
|
|
493
|
+
* return Ok(undefined).toAsync();
|
|
494
494
|
* },
|
|
495
495
|
* { prefetch: 10 }
|
|
496
496
|
* ]
|
|
@@ -538,7 +538,7 @@ type CreateWorkerOptions<TContract extends ContractDefinition> = {
|
|
|
538
538
|
defaultConsumerOptions?: ConsumerOptions | undefined;
|
|
539
539
|
/**
|
|
540
540
|
* Maximum time in ms to wait for the AMQP connection to become ready before
|
|
541
|
-
* `create()` resolves to an `
|
|
541
|
+
* `create()` resolves to an `Err(TechnicalError)`. Defaults to 30s
|
|
542
542
|
* (the {@link AmqpClient}'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to
|
|
543
543
|
* disable the timeout and let amqp-connection-manager retry indefinitely.
|
|
544
544
|
*/
|
|
@@ -556,7 +556,7 @@ type CreateWorkerOptions<TContract extends ContractDefinition> = {
|
|
|
556
556
|
* ```typescript
|
|
557
557
|
* import { TypedAmqpWorker } from '@amqp-contract/worker';
|
|
558
558
|
* import { defineQueue, defineMessage, defineContract, defineConsumer } from '@amqp-contract/contract';
|
|
559
|
-
* import {
|
|
559
|
+
* import { Ok } from 'unthrown';
|
|
560
560
|
* import { z } from 'zod';
|
|
561
561
|
*
|
|
562
562
|
* const orderQueue = defineQueue('order-processing');
|
|
@@ -576,7 +576,7 @@ type CreateWorkerOptions<TContract extends ContractDefinition> = {
|
|
|
576
576
|
* handlers: {
|
|
577
577
|
* processOrder: ({ payload }) => {
|
|
578
578
|
* console.log('Processing order', payload.orderId);
|
|
579
|
-
* return
|
|
579
|
+
* return Ok(undefined).toAsync();
|
|
580
580
|
* },
|
|
581
581
|
* },
|
|
582
582
|
* urls: ['amqp://localhost'],
|
|
@@ -630,7 +630,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
630
630
|
* const result = await TypedAmqpWorker.create({
|
|
631
631
|
* contract: myContract,
|
|
632
632
|
* handlers: {
|
|
633
|
-
* processOrder: ({ payload }) =>
|
|
633
|
+
* processOrder: ({ payload }) => Ok(undefined).toAsync(),
|
|
634
634
|
* },
|
|
635
635
|
* urls: ['amqp://localhost'],
|
|
636
636
|
* });
|
|
@@ -721,7 +721,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
721
721
|
/**
|
|
722
722
|
* For RPC handlers, validate and publish the reply on the caller's
|
|
723
723
|
* `replyTo` / `correlationId`. For non-RPC consumers, this is a no-op that
|
|
724
|
-
* resolves to `
|
|
724
|
+
* resolves to `Ok(undefined).toAsync()`.
|
|
725
725
|
*/
|
|
726
726
|
private publishReplyIfRpc;
|
|
727
727
|
/**
|
|
@@ -733,7 +733,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
733
733
|
* is still needed (see {@link consumeSingle}).
|
|
734
734
|
*
|
|
735
735
|
* Success-vs-failure telemetry is data-driven: the chain resolves to
|
|
736
|
-
* `
|
|
736
|
+
* `Ok(undefined)` only on handler success (and reply-publish success for
|
|
737
737
|
* RPC). Handler failures — even when {@link handleError} routes them
|
|
738
738
|
* successfully to retry/DLQ — are classified as failures for metrics by
|
|
739
739
|
* re-failing the chain with a `TechnicalError` whose `cause` is the
|
|
@@ -774,7 +774,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
774
774
|
* @example Consumer handler
|
|
775
775
|
* ```typescript
|
|
776
776
|
* import { defineHandler, RetryableError, NonRetryableError } from '@amqp-contract/worker';
|
|
777
|
-
* import { fromPromise,
|
|
777
|
+
* import { fromPromise, Ok } from 'unthrown';
|
|
778
778
|
*
|
|
779
779
|
* const processOrderHandler = defineHandler(
|
|
780
780
|
* orderContract,
|
|
@@ -792,7 +792,7 @@ declare class TypedAmqpWorker<TContract extends ContractDefinition> {
|
|
|
792
792
|
* const calculateHandler = defineHandler(
|
|
793
793
|
* rpcContract,
|
|
794
794
|
* 'calculate',
|
|
795
|
-
* ({ payload }) =>
|
|
795
|
+
* ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
|
|
796
796
|
* );
|
|
797
797
|
* ```
|
|
798
798
|
*/
|
|
@@ -819,7 +819,7 @@ declare function defineHandler<TContract extends ContractDefinition, TName exten
|
|
|
819
819
|
* @example
|
|
820
820
|
* ```typescript
|
|
821
821
|
* import { defineHandlers, RetryableError } from '@amqp-contract/worker';
|
|
822
|
-
* import { fromPromise,
|
|
822
|
+
* import { fromPromise, Ok } from 'unthrown';
|
|
823
823
|
*
|
|
824
824
|
* const handlers = defineHandlers(orderContract, {
|
|
825
825
|
* processOrder: ({ payload }) =>
|
|
@@ -827,7 +827,7 @@ declare function defineHandler<TContract extends ContractDefinition, TName exten
|
|
|
827
827
|
* processPayment(payload),
|
|
828
828
|
* (error) => new RetryableError('Payment failed', error),
|
|
829
829
|
* ).map(() => undefined),
|
|
830
|
-
* calculate: ({ payload }) =>
|
|
830
|
+
* calculate: ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
|
|
831
831
|
* });
|
|
832
832
|
* ```
|
|
833
833
|
*/
|