@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/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { extractConsumer, extractQueue, isQueueWithTtlBackoffInfrastructure } from "@amqp-contract/contract";
2
2
  import { AmqpClient, MessageValidationError, TechnicalError, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, safeJsonParse, startConsumeSpan } from "@amqp-contract/core";
3
- import { TaggedError, allAsync, err, fromPromise, fromSafePromise, ok } from "unthrown";
3
+ import { Err, Ok, TaggedError, allAsync, fromPromise, fromSafePromise } from "unthrown";
4
4
  import { gunzip, inflate } from "node:zlib";
5
5
  import { promisify } from "node:util";
6
6
  //#region src/decompression.ts
@@ -26,9 +26,9 @@ function isSupportedEncoding(encoding) {
26
26
  * @internal
27
27
  */
28
28
  function decompressBuffer(buffer, contentEncoding) {
29
- if (!contentEncoding) return ok(buffer).toAsync();
29
+ if (!contentEncoding) return Ok(buffer).toAsync();
30
30
  const normalizedEncoding = contentEncoding.toLowerCase();
31
- if (!isSupportedEncoding(normalizedEncoding)) return err(new TechnicalError(`Unsupported content-encoding: "${contentEncoding}". Supported encodings are: ${SUPPORTED_ENCODINGS.join(", ")}. Please check your publisher configuration.`)).toAsync();
31
+ if (!isSupportedEncoding(normalizedEncoding)) return Err(new TechnicalError(`Unsupported content-encoding: "${contentEncoding}". Supported encodings are: ${SUPPORTED_ENCODINGS.join(", ")}. Please check your publisher configuration.`)).toAsync();
32
32
  switch (normalizedEncoding) {
33
33
  case "gzip": return fromPromise(gunzipAsync(buffer), (error) => new TechnicalError("Failed to decompress gzip", error));
34
34
  case "deflate": return fromPromise(inflateAsync(buffer), (error) => new TechnicalError("Failed to decompress deflate", error));
@@ -185,17 +185,17 @@ function retryable(message, cause) {
185
185
  * @example
186
186
  * ```typescript
187
187
  * import { nonRetryable } from '@amqp-contract/worker';
188
- * import { err, ok } from 'unthrown';
188
+ * import { Err, Ok } from 'unthrown';
189
189
  *
190
190
  * const handler = ({ payload }) => {
191
191
  * if (!isValidPayload(payload)) {
192
- * return err(nonRetryable('Invalid payload format')).toAsync();
192
+ * return Err(nonRetryable('Invalid payload format')).toAsync();
193
193
  * }
194
- * return ok(undefined).toAsync();
194
+ * return Ok(undefined).toAsync();
195
195
  * };
196
196
  *
197
197
  * // Equivalent to:
198
- * // return err(new NonRetryableError('Invalid payload format')).toAsync();
198
+ * // return Err(new NonRetryableError('Invalid payload format')).toAsync();
199
199
  * ```
200
200
  */
201
201
  function nonRetryable(message, cause) {
@@ -224,7 +224,7 @@ function nonRetryable(message, cause) {
224
224
  function handleError(ctx, error, msg, consumerName, consumer) {
225
225
  if (error instanceof NonRetryableError) {
226
226
  sendToDLQ(ctx, msg, consumer);
227
- return ok(void 0).toAsync();
227
+ return Ok(void 0).toAsync();
228
228
  }
229
229
  const config = extractQueue(consumer.queue).retry;
230
230
  if (config.mode === "immediate-requeue") return handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, config);
@@ -234,7 +234,7 @@ function handleError(ctx, error, msg, consumerName, consumer) {
234
234
  queueName: extractQueue(consumer.queue).name
235
235
  });
236
236
  sendToDLQ(ctx, msg, consumer);
237
- return ok(void 0).toAsync();
237
+ return Ok(void 0).toAsync();
238
238
  }
239
239
  /**
240
240
  * Handle error by requeuing immediately.
@@ -257,7 +257,7 @@ function handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, co
257
257
  maxRetries: config.maxRetries
258
258
  });
259
259
  sendToDLQ(ctx, msg, consumer);
260
- return ok(void 0).toAsync();
260
+ return Ok(void 0).toAsync();
261
261
  }
262
262
  ctx.logger?.info("Retrying message (immediate-requeue mode)", {
263
263
  consumerName,
@@ -267,7 +267,7 @@ function handleErrorImmediateRequeue(ctx, error, msg, consumerName, consumer, co
267
267
  });
268
268
  if (queue.type === "quorum") {
269
269
  ctx.amqpClient.nack(msg, false, true);
270
- return ok(void 0).toAsync();
270
+ return Ok(void 0).toAsync();
271
271
  } else return publishForRetry(ctx, {
272
272
  msg,
273
273
  exchange: msg.fields.exchange,
@@ -308,7 +308,7 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
308
308
  consumerName,
309
309
  queueName: consumer.queue.name
310
310
  });
311
- return err(new TechnicalError("Queue does not have TTL-backoff infrastructure")).toAsync();
311
+ return Err(new TechnicalError("Queue does not have TTL-backoff infrastructure")).toAsync();
312
312
  }
313
313
  const queueEntry = consumer.queue;
314
314
  const queueName = extractQueue(queueEntry).name;
@@ -321,7 +321,7 @@ function handleErrorTtlBackoff(ctx, error, msg, consumerName, consumer, config)
321
321
  maxRetries: config.maxRetries
322
322
  });
323
323
  sendToDLQ(ctx, msg, consumer);
324
- return ok(void 0).toAsync();
324
+ return Ok(void 0).toAsync();
325
325
  }
326
326
  const delayMs = calculateRetryDelay(retryCount, config);
327
327
  ctx.logger?.info("Retrying message (ttl-backoff mode)", {
@@ -401,7 +401,7 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
401
401
  retryCount: newRetryCount,
402
402
  ...delayMs !== void 0 ? { delayMs } : {}
403
403
  });
404
- return err(new TechnicalError("Failed to publish message for retry (write buffer full)"));
404
+ return Err(new TechnicalError("Failed to publish message for retry (write buffer full)"));
405
405
  }
406
406
  ctx.amqpClient.ack(msg);
407
407
  ctx.logger?.info("Message published for retry", {
@@ -409,7 +409,7 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
409
409
  retryCount: newRetryCount,
410
410
  ...delayMs !== void 0 ? { delayMs } : {}
411
411
  });
412
- return ok(void 0);
412
+ return Ok(void 0);
413
413
  }).orElse((publishError) => {
414
414
  ctx.logger?.error("Publish for retry failed; leaving original un-ack'd for redelivery", {
415
415
  queueName,
@@ -417,7 +417,7 @@ function publishForRetry(ctx, { msg, exchange, routingKey, queueName, waitQueueN
417
417
  ...delayMs !== void 0 ? { delayMs } : {},
418
418
  error: publishError
419
419
  });
420
- return err(publishError);
420
+ return Err(publishError);
421
421
  });
422
422
  }
423
423
  /**
@@ -454,7 +454,7 @@ function isHandlerTuple(entry) {
454
454
  * ```typescript
455
455
  * import { TypedAmqpWorker } from '@amqp-contract/worker';
456
456
  * import { defineQueue, defineMessage, defineContract, defineConsumer } from '@amqp-contract/contract';
457
- * import { ok } from 'unthrown';
457
+ * import { Ok } from 'unthrown';
458
458
  * import { z } from 'zod';
459
459
  *
460
460
  * const orderQueue = defineQueue('order-processing');
@@ -474,7 +474,7 @@ function isHandlerTuple(entry) {
474
474
  * handlers: {
475
475
  * processOrder: ({ payload }) => {
476
476
  * console.log('Processing order', payload.orderId);
477
- * return ok(undefined).toAsync();
477
+ * return Ok(undefined).toAsync();
478
478
  * },
479
479
  * },
480
480
  * urls: ['amqp://localhost'],
@@ -570,7 +570,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
570
570
  * const result = await TypedAmqpWorker.create({
571
571
  * contract: myContract,
572
572
  * handlers: {
573
- * processOrder: ({ payload }) => ok(undefined).toAsync(),
573
+ * processOrder: ({ payload }) => Ok(undefined).toAsync(),
574
574
  * },
575
575
  * urls: ['amqp://localhost'],
576
576
  * });
@@ -612,7 +612,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
612
612
  consumerTag,
613
613
  error
614
614
  });
615
- return ok(void 0);
615
+ return Ok(void 0);
616
616
  }))).tap(() => {
617
617
  this.consumerTags.clear();
618
618
  }).flatMap(() => this.amqpClient.close()).map(() => void 0);
@@ -644,8 +644,8 @@ var TypedAmqpWorker = class TypedAmqpWorker {
644
644
  validateSchema(schema, data, context) {
645
645
  const rawValidation = schema["~standard"].validate(data);
646
646
  return fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new TechnicalError(`Error validating ${context.field}`, error)).flatMap((result) => {
647
- if (result.issues) return err(new TechnicalError(`${context.field} validation failed`, new MessageValidationError(context.consumerName, result.issues)));
648
- return ok(result.value);
647
+ if (result.issues) return Err(new TechnicalError(`${context.field} validation failed`, new MessageValidationError(context.consumerName, result.issues)));
648
+ return Ok(result.value);
649
649
  });
650
650
  }
651
651
  /**
@@ -663,7 +663,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
663
663
  })), consumer.message.headers ? this.validateSchema(consumer.message.headers, msg.properties.headers ?? {}, {
664
664
  ...context,
665
665
  field: "headers"
666
- }) : ok(void 0).toAsync()]).map(([payload, headers]) => ({
666
+ }) : Ok(void 0).toAsync()]).map(([payload, headers]) => ({
667
667
  payload,
668
668
  headers
669
669
  }));
@@ -695,7 +695,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
695
695
  rpcName: String(rpcName),
696
696
  queueName
697
697
  });
698
- return err(new NonRetryableError(`RPC "${String(rpcName)}" received a message without replyTo; cannot deliver response`)).toAsync();
698
+ return Err(new NonRetryableError(`RPC "${String(rpcName)}" received a message without replyTo; cannot deliver response`)).toAsync();
699
699
  }
700
700
  if (typeof correlationId !== "string" || correlationId.length === 0) {
701
701
  this.logger?.error("RPC handler returned a response but the incoming message has no correlationId", {
@@ -703,21 +703,21 @@ var TypedAmqpWorker = class TypedAmqpWorker {
703
703
  queueName,
704
704
  replyTo
705
705
  });
706
- return err(new NonRetryableError(`RPC "${String(rpcName)}" received a message without correlationId; cannot deliver response`)).toAsync();
706
+ return Err(new NonRetryableError(`RPC "${String(rpcName)}" received a message without correlationId; cannot deliver response`)).toAsync();
707
707
  }
708
708
  let rawValidation;
709
709
  try {
710
710
  rawValidation = responseSchema["~standard"].validate(response);
711
711
  } catch (error) {
712
- return err(new NonRetryableError("RPC response schema validation threw", error)).toAsync();
712
+ return Err(new NonRetryableError("RPC response schema validation threw", error)).toAsync();
713
713
  }
714
714
  return fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new NonRetryableError("RPC response schema validation threw", error)).flatMap((validation) => {
715
- if (validation.issues) return err(new NonRetryableError(`RPC response for "${String(rpcName)}" failed schema validation`, new MessageValidationError(String(rpcName), validation.issues)));
716
- return ok(validation.value);
715
+ if (validation.issues) return Err(new NonRetryableError(`RPC response for "${String(rpcName)}" failed schema validation`, new MessageValidationError(String(rpcName), validation.issues)));
716
+ return Ok(validation.value);
717
717
  }).flatMap((validatedResponse) => this.amqpClient.publish("", replyTo, validatedResponse, {
718
718
  correlationId,
719
719
  contentType: "application/json"
720
- }).mapErr((error) => new NonRetryableError("Failed to publish RPC response", error)).flatMap((published) => published ? ok(void 0) : err(new NonRetryableError("Failed to publish RPC response: channel buffer full"))));
720
+ }).mapErr((error) => new NonRetryableError("Failed to publish RPC response", error)).flatMap((published) => published ? Ok(void 0) : Err(new NonRetryableError("Failed to publish RPC response: channel buffer full"))));
721
721
  }
722
722
  /**
723
723
  * Parse and validate the message; on failure, nack(requeue=false) so the
@@ -728,7 +728,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
728
728
  parseAndValidateOrNack(msg, consumer, name) {
729
729
  return this.parseAndValidateMessage(msg, consumer, name).orElse((parseError) => {
730
730
  this.amqpClient.nack(msg, false, false);
731
- return err(parseError).toAsync();
731
+ return Err(parseError).toAsync();
732
732
  });
733
733
  }
734
734
  /**
@@ -743,10 +743,10 @@ var TypedAmqpWorker = class TypedAmqpWorker {
743
743
  /**
744
744
  * For RPC handlers, validate and publish the reply on the caller's
745
745
  * `replyTo` / `correlationId`. For non-RPC consumers, this is a no-op that
746
- * resolves to `ok(undefined).toAsync()`.
746
+ * resolves to `Ok(undefined).toAsync()`.
747
747
  */
748
748
  publishReplyIfRpc(msg, view, name, handlerResponse) {
749
- if (!view.isRpc || !view.responseSchema) return ok(void 0).toAsync();
749
+ if (!view.isRpc || !view.responseSchema) return Ok(void 0).toAsync();
750
750
  const queueName = extractQueue(view.consumer.queue).name;
751
751
  return this.publishRpcResponse(msg, queueName, name, view.responseSchema, handlerResponse);
752
752
  }
@@ -759,7 +759,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
759
759
  * is still needed (see {@link consumeSingle}).
760
760
  *
761
761
  * Success-vs-failure telemetry is data-driven: the chain resolves to
762
- * `ok(undefined)` only on handler success (and reply-publish success for
762
+ * `Ok(undefined)` only on handler success (and reply-publish success for
763
763
  * RPC). Handler failures — even when {@link handleError} routes them
764
764
  * successfully to retry/DLQ — are classified as failures for metrics by
765
765
  * re-failing the chain with a `TechnicalError` whose `cause` is the
@@ -799,7 +799,7 @@ var TypedAmqpWorker = class TypedAmqpWorker {
799
799
  logger: this.logger
800
800
  }, handlerError, msg, String(name), consumer).tap(() => {
801
801
  state.messageHandled = true;
802
- }).flatMap(() => err(new TechnicalError(`Handler "${String(name)}" failed: ${handlerError.message}`, handlerError)).toAsync());
802
+ }).flatMap(() => Err(new TechnicalError(`Handler "${String(name)}" failed: ${handlerError.message}`, handlerError)).toAsync());
803
803
  })).tap(() => {
804
804
  try {
805
805
  endSpanSuccess(span);
@@ -921,7 +921,7 @@ function defineHandler(contract, name, handler, options) {
921
921
  * @example
922
922
  * ```typescript
923
923
  * import { defineHandlers, RetryableError } from '@amqp-contract/worker';
924
- * import { fromPromise, ok } from 'unthrown';
924
+ * import { fromPromise, Ok } from 'unthrown';
925
925
  *
926
926
  * const handlers = defineHandlers(orderContract, {
927
927
  * processOrder: ({ payload }) =>
@@ -929,7 +929,7 @@ function defineHandler(contract, name, handler, options) {
929
929
  * processPayment(payload),
930
930
  * (error) => new RetryableError('Payment failed', error),
931
931
  * ).map(() => undefined),
932
- * calculate: ({ payload }) => ok({ sum: payload.a + payload.b }).toAsync(),
932
+ * calculate: ({ payload }) => Ok({ sum: payload.a + payload.b }).toAsync(),
933
933
  * });
934
934
  * ```
935
935
  */