@amqp-contract/client 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.cjs CHANGED
@@ -45,7 +45,7 @@ var RpcTimeoutError = class extends (0, unthrown.TaggedError)("@amqp-contract/Rp
45
45
  /**
46
46
  * Returned from any in-flight RPC call when the client is closed before the
47
47
  * reply is received. The correlation map is cleared on close and every pending
48
- * caller's promise resolves with `err(RpcCancelledError)`. Carries a namespaced
48
+ * caller's promise resolves with `Err(RpcCancelledError)`. Carries a namespaced
49
49
  * `_tag` of `"@amqp-contract/RpcCancelledError"`; the `Error.name` is kept bare
50
50
  * (`"RpcCancelledError"`).
51
51
  */
@@ -130,7 +130,7 @@ var TypedAmqpClient = class TypedAmqpClient {
130
130
  */
131
131
  setupReplyConsumerIfNeeded() {
132
132
  const rpcs = this.contract.rpcs ?? {};
133
- if (Object.keys(rpcs).length === 0) return (0, unthrown.ok)(void 0).toAsync();
133
+ if (Object.keys(rpcs).length === 0) return (0, unthrown.Ok)(void 0).toAsync();
134
134
  return this.amqpClient.consume(DIRECT_REPLY_TO, (msg) => this.handleRpcReply(msg), { noAck: true }).tap((tag) => {
135
135
  this.replyConsumerTag = tag;
136
136
  }).map(() => void 0);
@@ -162,7 +162,7 @@ var TypedAmqpClient = class TypedAmqpClient {
162
162
  clearTimeout(pending.timer);
163
163
  const parseResult = (0, _amqp_contract_core.safeJsonParse)(msg.content, (error) => new _amqp_contract_core.TechnicalError(`Failed to parse RPC reply JSON for "${pending.rpcName}"`, error));
164
164
  if (!parseResult.isOk()) {
165
- pending.resolve((0, unthrown.err)(parseResult.isErr() ? parseResult.error : new _amqp_contract_core.TechnicalError(`Failed to parse RPC reply JSON for "${pending.rpcName}"`, parseResult.cause)));
165
+ pending.resolve((0, unthrown.Err)(parseResult.isErr() ? parseResult.error : new _amqp_contract_core.TechnicalError(`Failed to parse RPC reply JSON for "${pending.rpcName}"`, parseResult.cause)));
166
166
  return;
167
167
  }
168
168
  const parsed = parseResult.value;
@@ -170,17 +170,17 @@ var TypedAmqpClient = class TypedAmqpClient {
170
170
  try {
171
171
  rawValidation = pending.responseSchema["~standard"].validate(parsed);
172
172
  } catch (error) {
173
- pending.resolve((0, unthrown.err)(new _amqp_contract_core.TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
173
+ pending.resolve((0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
174
174
  return;
175
175
  }
176
176
  (rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).then((validation) => {
177
177
  if (validation.issues) {
178
- pending.resolve((0, unthrown.err)(new _amqp_contract_core.MessageValidationError(pending.rpcName, validation.issues)));
178
+ pending.resolve((0, unthrown.Err)(new _amqp_contract_core.MessageValidationError(pending.rpcName, validation.issues)));
179
179
  return;
180
180
  }
181
- pending.resolve((0, unthrown.ok)(validation.value));
181
+ pending.resolve((0, unthrown.Ok)(validation.value));
182
182
  }, (error) => {
183
- pending.resolve((0, unthrown.err)(new _amqp_contract_core.TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
183
+ pending.resolve((0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
184
184
  });
185
185
  }
186
186
  /**
@@ -203,8 +203,8 @@ var TypedAmqpClient = class TypedAmqpClient {
203
203
  const validateMessage = () => {
204
204
  const validationResult = publisher.message.payload["~standard"].validate(message);
205
205
  return (0, unthrown.fromPromise)(validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult), (error) => new _amqp_contract_core.TechnicalError("Validation failed", error)).flatMap((validation) => {
206
- if (validation.issues) return (0, unthrown.err)(new _amqp_contract_core.MessageValidationError(String(publisherName), validation.issues));
207
- return (0, unthrown.ok)(validation.value);
206
+ if (validation.issues) return (0, unthrown.Err)(new _amqp_contract_core.MessageValidationError(String(publisherName), validation.issues));
207
+ return (0, unthrown.Ok)(validation.value);
208
208
  });
209
209
  };
210
210
  const publishMessage = (validatedMessage) => {
@@ -219,17 +219,17 @@ var TypedAmqpClient = class TypedAmqpClient {
219
219
  publishOptions.contentEncoding = compression;
220
220
  return compressBuffer(messageBuffer, compression);
221
221
  }
222
- return (0, unthrown.ok)(validatedMessage).toAsync();
222
+ return (0, unthrown.Ok)(validatedMessage).toAsync();
223
223
  };
224
224
  return preparePayload().flatMap((payload) => this.amqpClient.publish(publisher.exchange.name, publisher.routingKey ?? "", payload, publishOptions).flatMap((published) => {
225
- if (!published) return (0, unthrown.err)(new _amqp_contract_core.TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
225
+ if (!published) return (0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
226
226
  this.logger?.info("Message published successfully", {
227
227
  publisherName: String(publisherName),
228
228
  exchange: publisher.exchange.name,
229
229
  routingKey: publisher.routingKey,
230
230
  compressed: !!compression
231
231
  });
232
- return (0, unthrown.ok)(void 0);
232
+ return (0, unthrown.Ok)(void 0);
233
233
  }));
234
234
  };
235
235
  return validateMessage().flatMap((validatedMessage) => publishMessage(validatedMessage)).tap(() => {
@@ -264,7 +264,7 @@ var TypedAmqpClient = class TypedAmqpClient {
264
264
  */
265
265
  call(rpcName, request, options) {
266
266
  const TIMEOUT_MAX_MS = 2147483647;
267
- if (typeof options.timeoutMs !== "number" || !Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0 || options.timeoutMs > TIMEOUT_MAX_MS) return (0, unthrown.err)(new _amqp_contract_core.TechnicalError(`Invalid timeoutMs for RPC call to "${String(rpcName)}": expected a finite positive number ≤ ${TIMEOUT_MAX_MS}, got ${String(options.timeoutMs)}`)).toAsync();
267
+ if (typeof options.timeoutMs !== "number" || !Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0 || options.timeoutMs > TIMEOUT_MAX_MS) return (0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`Invalid timeoutMs for RPC call to "${String(rpcName)}": expected a finite positive number ≤ ${TIMEOUT_MAX_MS}, got ${String(options.timeoutMs)}`)).toAsync();
268
268
  const startTime = Date.now();
269
269
  const rpc = this.contract.rpcs[rpcName];
270
270
  const requestSchema = rpc.request.payload;
@@ -279,7 +279,7 @@ var TypedAmqpClient = class TypedAmqpClient {
279
279
  const timer = setTimeout(() => {
280
280
  if (!this.pendingCalls.has(correlationId)) return;
281
281
  this.pendingCalls.delete(correlationId);
282
- resolveCall((0, unthrown.err)(new RpcTimeoutError(String(rpcName), options.timeoutMs)));
282
+ resolveCall((0, unthrown.Err)(new RpcTimeoutError(String(rpcName), options.timeoutMs)));
283
283
  }, options.timeoutMs);
284
284
  this.pendingCalls.set(correlationId, {
285
285
  rpcName: String(rpcName),
@@ -292,9 +292,9 @@ var TypedAmqpClient = class TypedAmqpClient {
292
292
  try {
293
293
  rawValidation = requestSchema["~standard"].validate(request);
294
294
  } catch (error) {
295
- return (0, unthrown.err)(new _amqp_contract_core.TechnicalError("RPC request validation threw", error)).toAsync();
295
+ return (0, unthrown.Err)(new _amqp_contract_core.TechnicalError("RPC request validation threw", error)).toAsync();
296
296
  }
297
- return (0, unthrown.fromPromise)(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new _amqp_contract_core.TechnicalError("RPC request validation threw", error)).flatMap((validation) => validation.issues ? (0, unthrown.err)(new _amqp_contract_core.MessageValidationError(String(rpcName), validation.issues)) : (0, unthrown.ok)(validation.value));
297
+ return (0, unthrown.fromPromise)(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new _amqp_contract_core.TechnicalError("RPC request validation threw", error)).flatMap((validation) => validation.issues ? (0, unthrown.Err)(new _amqp_contract_core.MessageValidationError(String(rpcName), validation.issues)) : (0, unthrown.Ok)(validation.value));
298
298
  };
299
299
  const publishRequest = (validatedRequest) => {
300
300
  const { compression: _ignoredCompression, ...defaultsWithoutCompression } = this.defaultPublishOptions;
@@ -305,14 +305,14 @@ var TypedAmqpClient = class TypedAmqpClient {
305
305
  correlationId,
306
306
  contentType: "application/json"
307
307
  };
308
- return this.amqpClient.publish("", queueName, validatedRequest, publishOptions).flatMap((published) => published ? (0, unthrown.ok)(void 0) : (0, unthrown.err)(new _amqp_contract_core.TechnicalError(`Failed to publish RPC request for "${String(rpcName)}": channel buffer full`)));
308
+ return this.amqpClient.publish("", queueName, validatedRequest, publishOptions).flatMap((published) => published ? (0, unthrown.Ok)(void 0) : (0, unthrown.Err)(new _amqp_contract_core.TechnicalError(`Failed to publish RPC request for "${String(rpcName)}": channel buffer full`)));
309
309
  };
310
310
  return validateRequest().flatMap((validated) => publishRequest(validated)).flatMap(() => callResultAsync).orElse((error) => {
311
311
  if (this.pendingCalls.has(correlationId)) {
312
312
  clearTimeout(timer);
313
313
  this.pendingCalls.delete(correlationId);
314
314
  }
315
- return (0, unthrown.err)(error).toAsync();
315
+ return (0, unthrown.Err)(error).toAsync();
316
316
  }).tap(() => {
317
317
  const durationMs = Date.now() - startTime;
318
318
  (0, _amqp_contract_core.endSpanSuccess)(span);
@@ -330,13 +330,13 @@ var TypedAmqpClient = class TypedAmqpClient {
330
330
  close() {
331
331
  for (const [, pending] of this.pendingCalls) {
332
332
  clearTimeout(pending.timer);
333
- pending.resolve((0, unthrown.err)(new RpcCancelledError(pending.rpcName)));
333
+ pending.resolve((0, unthrown.Err)(new RpcCancelledError(pending.rpcName)));
334
334
  }
335
335
  this.pendingCalls.clear();
336
336
  return (this.replyConsumerTag ? this.amqpClient.cancel(this.replyConsumerTag).orElse((error) => {
337
337
  this.logger?.warn("Failed to cancel RPC reply consumer during close", { error });
338
- return (0, unthrown.ok)(void 0);
339
- }) : (0, unthrown.ok)(void 0).toAsync()).flatMap(() => this.amqpClient.close());
338
+ return (0, unthrown.Ok)(void 0);
339
+ }) : (0, unthrown.Ok)(void 0).toAsync()).flatMap(() => this.amqpClient.close());
340
340
  }
341
341
  waitForConnectionReady() {
342
342
  return this.amqpClient.waitForConnect();
package/dist/index.d.cts CHANGED
@@ -149,7 +149,7 @@ declare const RpcCancelledError_base: import("unthrown").TaggedErrorConstructor<
149
149
  /**
150
150
  * Returned from any in-flight RPC call when the client is closed before the
151
151
  * reply is received. The correlation map is cleared on close and every pending
152
- * caller's promise resolves with `err(RpcCancelledError)`. Carries a namespaced
152
+ * caller's promise resolves with `Err(RpcCancelledError)`. Carries a namespaced
153
153
  * `_tag` of `"@amqp-contract/RpcCancelledError"`; the `Error.name` is kept bare
154
154
  * (`"RpcCancelledError"`).
155
155
  */
@@ -230,7 +230,7 @@ type CreateClientOptions<TContract extends ContractDefinition> = {
230
230
  defaultPublishOptions?: PublishOptions | undefined;
231
231
  /**
232
232
  * Maximum time in ms to wait for the AMQP connection to become ready before
233
- * `create()` resolves to an `err(TechnicalError)`. Defaults to 30s
233
+ * `create()` resolves to an `Err(TechnicalError)`. Defaults to 30s
234
234
  * (the {@link AmqpClient}'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to
235
235
  * disable the timeout and let amqp-connection-manager retry indefinitely.
236
236
  */
@@ -242,7 +242,7 @@ type CreateClientOptions<TContract extends ContractDefinition> = {
242
242
  type CallOptions = {
243
243
  /**
244
244
  * Maximum time in ms to wait for an RPC reply. If exceeded, the call resolves
245
- * to `err(RpcTimeoutError)` and the in-memory correlation entry is cleared.
245
+ * to `Err(RpcTimeoutError)` and the in-memory correlation entry is cleared.
246
246
  * A late reply arriving after the timeout is silently dropped.
247
247
  *
248
248
  * Required: RPC without a timeout is a footgun.
package/dist/index.d.mts CHANGED
@@ -149,7 +149,7 @@ declare const RpcCancelledError_base: import("unthrown").TaggedErrorConstructor<
149
149
  /**
150
150
  * Returned from any in-flight RPC call when the client is closed before the
151
151
  * reply is received. The correlation map is cleared on close and every pending
152
- * caller's promise resolves with `err(RpcCancelledError)`. Carries a namespaced
152
+ * caller's promise resolves with `Err(RpcCancelledError)`. Carries a namespaced
153
153
  * `_tag` of `"@amqp-contract/RpcCancelledError"`; the `Error.name` is kept bare
154
154
  * (`"RpcCancelledError"`).
155
155
  */
@@ -230,7 +230,7 @@ type CreateClientOptions<TContract extends ContractDefinition> = {
230
230
  defaultPublishOptions?: PublishOptions | undefined;
231
231
  /**
232
232
  * Maximum time in ms to wait for the AMQP connection to become ready before
233
- * `create()` resolves to an `err(TechnicalError)`. Defaults to 30s
233
+ * `create()` resolves to an `Err(TechnicalError)`. Defaults to 30s
234
234
  * (the {@link AmqpClient}'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to
235
235
  * disable the timeout and let amqp-connection-manager retry indefinitely.
236
236
  */
@@ -242,7 +242,7 @@ type CreateClientOptions<TContract extends ContractDefinition> = {
242
242
  type CallOptions = {
243
243
  /**
244
244
  * Maximum time in ms to wait for an RPC reply. If exceeded, the call resolves
245
- * to `err(RpcTimeoutError)` and the in-memory correlation entry is cleared.
245
+ * to `Err(RpcTimeoutError)` and the in-memory correlation entry is cleared.
246
246
  * A late reply arriving after the timeout is silently dropped.
247
247
  *
248
248
  * Required: RPC without a timeout is a footgun.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { extractQueue } from "@amqp-contract/contract";
2
2
  import { AmqpClient, MessageValidationError, MessagingSemanticConventions, TechnicalError, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordLateRpcReply, recordPublishMetric, safeJsonParse, startPublishSpan } from "@amqp-contract/core";
3
- import { TaggedError, err, fromPromise, fromSafePromise, ok } from "unthrown";
3
+ import { Err, Ok, TaggedError, fromPromise, fromSafePromise } from "unthrown";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { deflate, gzip } from "node:zlib";
6
6
  import { promisify } from "node:util";
@@ -44,7 +44,7 @@ var RpcTimeoutError = class extends TaggedError("@amqp-contract/RpcTimeoutError"
44
44
  /**
45
45
  * Returned from any in-flight RPC call when the client is closed before the
46
46
  * reply is received. The correlation map is cleared on close and every pending
47
- * caller's promise resolves with `err(RpcCancelledError)`. Carries a namespaced
47
+ * caller's promise resolves with `Err(RpcCancelledError)`. Carries a namespaced
48
48
  * `_tag` of `"@amqp-contract/RpcCancelledError"`; the `Error.name` is kept bare
49
49
  * (`"RpcCancelledError"`).
50
50
  */
@@ -129,7 +129,7 @@ var TypedAmqpClient = class TypedAmqpClient {
129
129
  */
130
130
  setupReplyConsumerIfNeeded() {
131
131
  const rpcs = this.contract.rpcs ?? {};
132
- if (Object.keys(rpcs).length === 0) return ok(void 0).toAsync();
132
+ if (Object.keys(rpcs).length === 0) return Ok(void 0).toAsync();
133
133
  return this.amqpClient.consume(DIRECT_REPLY_TO, (msg) => this.handleRpcReply(msg), { noAck: true }).tap((tag) => {
134
134
  this.replyConsumerTag = tag;
135
135
  }).map(() => void 0);
@@ -161,7 +161,7 @@ var TypedAmqpClient = class TypedAmqpClient {
161
161
  clearTimeout(pending.timer);
162
162
  const parseResult = safeJsonParse(msg.content, (error) => new TechnicalError(`Failed to parse RPC reply JSON for "${pending.rpcName}"`, error));
163
163
  if (!parseResult.isOk()) {
164
- pending.resolve(err(parseResult.isErr() ? parseResult.error : new TechnicalError(`Failed to parse RPC reply JSON for "${pending.rpcName}"`, parseResult.cause)));
164
+ pending.resolve(Err(parseResult.isErr() ? parseResult.error : new TechnicalError(`Failed to parse RPC reply JSON for "${pending.rpcName}"`, parseResult.cause)));
165
165
  return;
166
166
  }
167
167
  const parsed = parseResult.value;
@@ -169,17 +169,17 @@ var TypedAmqpClient = class TypedAmqpClient {
169
169
  try {
170
170
  rawValidation = pending.responseSchema["~standard"].validate(parsed);
171
171
  } catch (error) {
172
- pending.resolve(err(new TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
172
+ pending.resolve(Err(new TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
173
173
  return;
174
174
  }
175
175
  (rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation)).then((validation) => {
176
176
  if (validation.issues) {
177
- pending.resolve(err(new MessageValidationError(pending.rpcName, validation.issues)));
177
+ pending.resolve(Err(new MessageValidationError(pending.rpcName, validation.issues)));
178
178
  return;
179
179
  }
180
- pending.resolve(ok(validation.value));
180
+ pending.resolve(Ok(validation.value));
181
181
  }, (error) => {
182
- pending.resolve(err(new TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
182
+ pending.resolve(Err(new TechnicalError(`RPC reply validation threw for "${pending.rpcName}"`, error)));
183
183
  });
184
184
  }
185
185
  /**
@@ -202,8 +202,8 @@ var TypedAmqpClient = class TypedAmqpClient {
202
202
  const validateMessage = () => {
203
203
  const validationResult = publisher.message.payload["~standard"].validate(message);
204
204
  return fromPromise(validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult), (error) => new TechnicalError("Validation failed", error)).flatMap((validation) => {
205
- if (validation.issues) return err(new MessageValidationError(String(publisherName), validation.issues));
206
- return ok(validation.value);
205
+ if (validation.issues) return Err(new MessageValidationError(String(publisherName), validation.issues));
206
+ return Ok(validation.value);
207
207
  });
208
208
  };
209
209
  const publishMessage = (validatedMessage) => {
@@ -218,17 +218,17 @@ var TypedAmqpClient = class TypedAmqpClient {
218
218
  publishOptions.contentEncoding = compression;
219
219
  return compressBuffer(messageBuffer, compression);
220
220
  }
221
- return ok(validatedMessage).toAsync();
221
+ return Ok(validatedMessage).toAsync();
222
222
  };
223
223
  return preparePayload().flatMap((payload) => this.amqpClient.publish(publisher.exchange.name, publisher.routingKey ?? "", payload, publishOptions).flatMap((published) => {
224
- if (!published) return err(new TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
224
+ if (!published) return Err(new TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
225
225
  this.logger?.info("Message published successfully", {
226
226
  publisherName: String(publisherName),
227
227
  exchange: publisher.exchange.name,
228
228
  routingKey: publisher.routingKey,
229
229
  compressed: !!compression
230
230
  });
231
- return ok(void 0);
231
+ return Ok(void 0);
232
232
  }));
233
233
  };
234
234
  return validateMessage().flatMap((validatedMessage) => publishMessage(validatedMessage)).tap(() => {
@@ -263,7 +263,7 @@ var TypedAmqpClient = class TypedAmqpClient {
263
263
  */
264
264
  call(rpcName, request, options) {
265
265
  const TIMEOUT_MAX_MS = 2147483647;
266
- if (typeof options.timeoutMs !== "number" || !Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0 || options.timeoutMs > TIMEOUT_MAX_MS) return err(new TechnicalError(`Invalid timeoutMs for RPC call to "${String(rpcName)}": expected a finite positive number ≤ ${TIMEOUT_MAX_MS}, got ${String(options.timeoutMs)}`)).toAsync();
266
+ if (typeof options.timeoutMs !== "number" || !Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0 || options.timeoutMs > TIMEOUT_MAX_MS) return Err(new TechnicalError(`Invalid timeoutMs for RPC call to "${String(rpcName)}": expected a finite positive number ≤ ${TIMEOUT_MAX_MS}, got ${String(options.timeoutMs)}`)).toAsync();
267
267
  const startTime = Date.now();
268
268
  const rpc = this.contract.rpcs[rpcName];
269
269
  const requestSchema = rpc.request.payload;
@@ -278,7 +278,7 @@ var TypedAmqpClient = class TypedAmqpClient {
278
278
  const timer = setTimeout(() => {
279
279
  if (!this.pendingCalls.has(correlationId)) return;
280
280
  this.pendingCalls.delete(correlationId);
281
- resolveCall(err(new RpcTimeoutError(String(rpcName), options.timeoutMs)));
281
+ resolveCall(Err(new RpcTimeoutError(String(rpcName), options.timeoutMs)));
282
282
  }, options.timeoutMs);
283
283
  this.pendingCalls.set(correlationId, {
284
284
  rpcName: String(rpcName),
@@ -291,9 +291,9 @@ var TypedAmqpClient = class TypedAmqpClient {
291
291
  try {
292
292
  rawValidation = requestSchema["~standard"].validate(request);
293
293
  } catch (error) {
294
- return err(new TechnicalError("RPC request validation threw", error)).toAsync();
294
+ return Err(new TechnicalError("RPC request validation threw", error)).toAsync();
295
295
  }
296
- return fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new TechnicalError("RPC request validation threw", error)).flatMap((validation) => validation.issues ? err(new MessageValidationError(String(rpcName), validation.issues)) : ok(validation.value));
296
+ return fromPromise(rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation), (error) => new TechnicalError("RPC request validation threw", error)).flatMap((validation) => validation.issues ? Err(new MessageValidationError(String(rpcName), validation.issues)) : Ok(validation.value));
297
297
  };
298
298
  const publishRequest = (validatedRequest) => {
299
299
  const { compression: _ignoredCompression, ...defaultsWithoutCompression } = this.defaultPublishOptions;
@@ -304,14 +304,14 @@ var TypedAmqpClient = class TypedAmqpClient {
304
304
  correlationId,
305
305
  contentType: "application/json"
306
306
  };
307
- return this.amqpClient.publish("", queueName, validatedRequest, publishOptions).flatMap((published) => published ? ok(void 0) : err(new TechnicalError(`Failed to publish RPC request for "${String(rpcName)}": channel buffer full`)));
307
+ return this.amqpClient.publish("", queueName, validatedRequest, publishOptions).flatMap((published) => published ? Ok(void 0) : Err(new TechnicalError(`Failed to publish RPC request for "${String(rpcName)}": channel buffer full`)));
308
308
  };
309
309
  return validateRequest().flatMap((validated) => publishRequest(validated)).flatMap(() => callResultAsync).orElse((error) => {
310
310
  if (this.pendingCalls.has(correlationId)) {
311
311
  clearTimeout(timer);
312
312
  this.pendingCalls.delete(correlationId);
313
313
  }
314
- return err(error).toAsync();
314
+ return Err(error).toAsync();
315
315
  }).tap(() => {
316
316
  const durationMs = Date.now() - startTime;
317
317
  endSpanSuccess(span);
@@ -329,13 +329,13 @@ var TypedAmqpClient = class TypedAmqpClient {
329
329
  close() {
330
330
  for (const [, pending] of this.pendingCalls) {
331
331
  clearTimeout(pending.timer);
332
- pending.resolve(err(new RpcCancelledError(pending.rpcName)));
332
+ pending.resolve(Err(new RpcCancelledError(pending.rpcName)));
333
333
  }
334
334
  this.pendingCalls.clear();
335
335
  return (this.replyConsumerTag ? this.amqpClient.cancel(this.replyConsumerTag).orElse((error) => {
336
336
  this.logger?.warn("Failed to cancel RPC reply consumer during close", { error });
337
- return ok(void 0);
338
- }) : ok(void 0).toAsync()).flatMap(() => this.amqpClient.close());
337
+ return Ok(void 0);
338
+ }) : Ok(void 0).toAsync()).flatMap(() => this.amqpClient.close());
339
339
  }
340
340
  waitForConnectionReady() {
341
341
  return this.amqpClient.waitForConnect();
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/compression.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["import type { CompressionAlgorithm } from \"@amqp-contract/contract\";\nimport { TechnicalError } from \"@amqp-contract/core\";\nimport { fromPromise, type AsyncResult } from \"unthrown\";\nimport { deflate, gzip } from \"node:zlib\";\nimport { promisify } from \"node:util\";\nimport { match } from \"ts-pattern\";\n\nconst gzipAsync = promisify(gzip);\nconst deflateAsync = promisify(deflate);\n\n/**\n * Compress a buffer using the specified compression algorithm.\n *\n * @param buffer - The buffer to compress\n * @param algorithm - The compression algorithm to use\n * @returns An AsyncResult resolving to the compressed buffer or a TechnicalError\n *\n * @internal\n */\nexport function compressBuffer(\n buffer: Buffer,\n algorithm: CompressionAlgorithm,\n): AsyncResult<Buffer, TechnicalError> {\n return match(algorithm)\n .with(\"gzip\", () =>\n fromPromise(\n gzipAsync(buffer),\n (error) => new TechnicalError(\"Failed to compress with gzip\", error),\n ),\n )\n .with(\"deflate\", () =>\n fromPromise(\n deflateAsync(buffer),\n (error) => new TechnicalError(\"Failed to compress with deflate\", error),\n ),\n )\n .exhaustive();\n}\n","import { TaggedError } from \"unthrown\";\n\nexport { MessageValidationError } from \"@amqp-contract/core\";\n\n/**\n * Returned from `TypedAmqpClient.call()` when the configured `timeoutMs` elapses\n * before the RPC server publishes a reply with the matching `correlationId`.\n *\n * The pending call is removed from the in-memory correlation map; if a reply\n * arrives after the timeout it is dropped (and a debug log is emitted by the\n * client if a logger is configured). Carries a namespaced `_tag` of\n * `\"@amqp-contract/RpcTimeoutError\"`; the `Error.name` is kept bare\n * (`\"RpcTimeoutError\"`).\n */\nexport class RpcTimeoutError extends TaggedError(\"@amqp-contract/RpcTimeoutError\", {\n name: \"RpcTimeoutError\",\n})<{\n message: string;\n rpcName: string;\n timeoutMs: number;\n}> {\n constructor(rpcName: string, timeoutMs: number) {\n super({\n message: `RPC call to \"${rpcName}\" timed out after ${timeoutMs}ms with no reply received`,\n rpcName,\n timeoutMs,\n });\n }\n}\n\n/**\n * Returned from any in-flight RPC call when the client is closed before the\n * reply is received. The correlation map is cleared on close and every pending\n * caller's promise resolves with `err(RpcCancelledError)`. Carries a namespaced\n * `_tag` of `\"@amqp-contract/RpcCancelledError\"`; the `Error.name` is kept bare\n * (`\"RpcCancelledError\"`).\n */\nexport class RpcCancelledError extends TaggedError(\"@amqp-contract/RpcCancelledError\", {\n name: \"RpcCancelledError\",\n})<{\n message: string;\n rpcName: string;\n}> {\n constructor(rpcName: string) {\n super({\n message: `RPC call to \"${rpcName}\" was cancelled because the client was closed`,\n rpcName,\n });\n }\n}\n","import {\n extractQueue,\n type CompressionAlgorithm,\n type ContractDefinition,\n type InferPublisherNames,\n type InferRpcNames,\n} from \"@amqp-contract/contract\";\nimport {\n AmqpClient,\n PublishOptions as AmqpClientPublishOptions,\n type Logger,\n MessagingSemanticConventions,\n TechnicalError,\n type TelemetryProvider,\n defaultTelemetryProvider,\n endSpanError,\n endSpanSuccess,\n recordLateRpcReply,\n recordPublishMetric,\n safeJsonParse,\n startPublishSpan,\n} from \"@amqp-contract/core\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport { err, fromPromise, fromSafePromise, ok, type AsyncResult, type Result } from \"unthrown\";\nimport { randomUUID } from \"node:crypto\";\nimport { compressBuffer } from \"./compression.js\";\nimport { MessageValidationError, RpcCancelledError, RpcTimeoutError } from \"./errors.js\";\nimport type {\n ClientInferPublisherInput,\n ClientInferRpcRequestInput,\n ClientInferRpcResponseOutput,\n} from \"./types.js\";\n\n/**\n * The RabbitMQ direct-reply-to pseudo-queue. Publishing with `replyTo` set to\n * this value tells the server to deliver the response back to the consumer\n * subscribed on this queue on the same channel — no real queue is created and\n * no setup is required beyond consuming from it once with `noAck: true`.\n *\n * @see https://www.rabbitmq.com/docs/direct-reply-to\n */\nconst DIRECT_REPLY_TO = \"amq.rabbitmq.reply-to\";\n\n/**\n * In-flight RPC call tracked by `TypedAmqpClient`. The reply consumer\n * looks up entries by `correlationId` when responses arrive.\n */\ntype PendingCall = {\n rpcName: string;\n responseSchema: StandardSchemaV1;\n resolve: (\n result: Result<\n unknown,\n TechnicalError | MessageValidationError | RpcTimeoutError | RpcCancelledError\n >,\n ) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/**\n * Publish options that extend amqp-client's PublishOptions with optional compression support.\n */\nexport type PublishOptions = AmqpClientPublishOptions & {\n /**\n * Optional compression algorithm to use for the message payload.\n * When specified, the message will be compressed using the chosen algorithm\n * and the contentEncoding header will be set automatically.\n */\n compression?: CompressionAlgorithm | undefined;\n};\n\n/**\n * Options for creating a client\n */\nexport type CreateClientOptions<TContract extends ContractDefinition> = {\n contract: TContract;\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n logger?: Logger | undefined;\n /**\n * Optional telemetry provider for tracing and metrics.\n * If not provided, uses the default provider which attempts to load OpenTelemetry.\n * OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed.\n */\n telemetry?: TelemetryProvider | undefined;\n /**\n * Default publish options that will be applied to all publish operations.\n * These can be overridden by options passed to the publish method.\n * By default, persistent is set to true for message durability.\n */\n defaultPublishOptions?: PublishOptions | undefined;\n /**\n * Maximum time in ms to wait for the AMQP connection to become ready before\n * `create()` resolves to an `err(TechnicalError)`. Defaults to 30s\n * (the {@link AmqpClient}'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to\n * disable the timeout and let amqp-connection-manager retry indefinitely.\n */\n connectTimeoutMs?: number | null | undefined;\n};\n\n/**\n * Per-call options for `client.call()`.\n */\nexport type CallOptions = {\n /**\n * Maximum time in ms to wait for an RPC reply. If exceeded, the call resolves\n * to `err(RpcTimeoutError)` and the in-memory correlation entry is cleared.\n * A late reply arriving after the timeout is silently dropped.\n *\n * Required: RPC without a timeout is a footgun.\n */\n timeoutMs: number;\n\n /**\n * Optional AMQP message properties to merge into the request. `replyTo` and\n * `correlationId` are managed by the client and cannot be overridden.\n */\n publishOptions?: Omit<AmqpClientPublishOptions, \"replyTo\" | \"correlationId\">;\n};\n\n/**\n * Type-safe AMQP client for publishing messages\n */\nexport class TypedAmqpClient<TContract extends ContractDefinition> {\n /**\n * In-flight RPC calls keyed by `correlationId`. Cleared when a reply is\n * received, when the call times out, or when the client is closed.\n */\n private readonly pendingCalls = new Map<string, PendingCall>();\n\n /**\n * Consumer tag of the reply consumer subscribed on `amq.rabbitmq.reply-to`.\n * Set when the contract has at least one entry in `rpcs`; undefined otherwise.\n */\n private replyConsumerTag?: string;\n\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n private readonly defaultPublishOptions: PublishOptions,\n private readonly logger?: Logger,\n private readonly telemetry: TelemetryProvider = defaultTelemetryProvider,\n ) {}\n\n /**\n * Create a type-safe AMQP client from a contract.\n *\n * Connection management (including automatic reconnection) is handled internally\n * by amqp-connection-manager via the {@link AmqpClient}. The client establishes\n * infrastructure asynchronously in the background once the connection is ready.\n *\n * Connections are automatically shared across clients with the same URLs and\n * connection options, following RabbitMQ best practices.\n */\n static create<TContract extends ContractDefinition>({\n contract,\n urls,\n connectionOptions,\n defaultPublishOptions,\n logger,\n telemetry,\n connectTimeoutMs,\n }: CreateClientOptions<TContract>): AsyncResult<TypedAmqpClient<TContract>, TechnicalError> {\n const client = new TypedAmqpClient(\n contract,\n new AmqpClient(contract, { urls, connectionOptions, connectTimeoutMs }),\n { persistent: true, ...defaultPublishOptions },\n logger,\n telemetry ?? defaultTelemetryProvider,\n );\n\n const setup = client\n .waitForConnectionReady()\n .flatMap(() => client.setupReplyConsumerIfNeeded());\n\n const inner = (async (): Promise<Result<TypedAmqpClient<TContract>, TechnicalError>> => {\n const setupResult = await setup;\n if (!setupResult.isOk()) {\n const closeResult = await client.close();\n if (closeResult.isErr()) {\n logger?.warn(\"Failed to close client after connection failure\", {\n error: closeResult.error,\n });\n }\n }\n // `map` runs only on Ok; an Err/Defect passes through with its value type\n // re-shaped to the client, so the failure surfaces unchanged.\n return setupResult.map(() => client);\n })();\n\n return fromSafePromise(inner).flatMap((result) => result);\n }\n\n /**\n * If the contract has any RPC entry, subscribe to `amq.rabbitmq.reply-to`\n * once. Replies for every in-flight call arrive on this single consumer and\n * are demultiplexed by `correlationId`.\n */\n private setupReplyConsumerIfNeeded(): AsyncResult<void, TechnicalError> {\n const rpcs = this.contract.rpcs ?? {};\n if (Object.keys(rpcs).length === 0) {\n return ok(undefined).toAsync();\n }\n\n return this.amqpClient\n .consume(DIRECT_REPLY_TO, (msg) => this.handleRpcReply(msg), { noAck: true })\n .tap((tag) => {\n this.replyConsumerTag = tag;\n })\n .map(() => undefined);\n }\n\n /**\n * Demultiplex an RPC reply by `correlationId`, validate the body against the\n * call's response schema, and resolve the awaiting caller. Replies with no\n * matching pending call (the call already timed out, was cancelled, or the\n * correlationId is unknown) are logged at warn — a non-zero rate of these\n * usually indicates a tuning problem (handler latency exceeds caller\n * timeout). The `messaging.rpc.late_reply` counter lets dashboards alert on\n * sustained drift without parsing logs.\n */\n private handleRpcReply(msg: Parameters<Parameters<AmqpClient[\"consume\"]>[1]>[0]): void {\n if (!msg) return;\n const correlationId = msg.properties.correlationId;\n if (typeof correlationId !== \"string\") {\n this.logger?.warn(\"Received RPC reply without correlationId; dropping\", {\n deliveryTag: msg.fields.deliveryTag,\n });\n recordLateRpcReply(this.telemetry, \"missing-correlation-id\");\n return;\n }\n const pending = this.pendingCalls.get(correlationId);\n if (!pending) {\n this.logger?.warn(\n \"Received RPC reply for unknown correlationId (caller already timed out or cancelled)\",\n { correlationId },\n );\n recordLateRpcReply(this.telemetry, \"unknown-correlation-id\");\n return;\n }\n this.pendingCalls.delete(correlationId);\n clearTimeout(pending.timer);\n\n const parseResult = safeJsonParse(\n msg.content,\n (error) =>\n new TechnicalError(`Failed to parse RPC reply JSON for \"${pending.rpcName}\"`, error),\n );\n if (!parseResult.isOk()) {\n pending.resolve(\n err(\n parseResult.isErr()\n ? parseResult.error\n : new TechnicalError(\n `Failed to parse RPC reply JSON for \"${pending.rpcName}\"`,\n parseResult.cause,\n ),\n ),\n );\n return;\n }\n const parsed = parseResult.value;\n\n // Wrap the validate call itself — a Standard Schema implementation may\n // throw synchronously, and the throw would otherwise escape the consume\n // callback and could crash the reply consumer.\n let rawValidation: ReturnType<StandardSchemaV1[\"~standard\"][\"validate\"]>;\n try {\n rawValidation = pending.responseSchema[\"~standard\"].validate(parsed);\n } catch (error: unknown) {\n pending.resolve(\n err(new TechnicalError(`RPC reply validation threw for \"${pending.rpcName}\"`, error)),\n );\n return;\n }\n const validationPromise =\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation);\n\n validationPromise.then(\n (validation) => {\n if (validation.issues) {\n pending.resolve(err(new MessageValidationError(pending.rpcName, validation.issues)));\n return;\n }\n pending.resolve(ok(validation.value));\n },\n (error: unknown) => {\n pending.resolve(\n err(new TechnicalError(`RPC reply validation threw for \"${pending.rpcName}\"`, error)),\n );\n },\n );\n }\n\n /**\n * Publish a message using a defined publisher.\n *\n * @param publisherName - The name of the publisher to use\n * @param message - The message to publish\n * @param options - Optional publish options including compression, headers, priority, etc.\n *\n * @remarks\n * If `options.compression` is specified, the message will be compressed before publishing\n * and the `contentEncoding` property will be set automatically. Any `contentEncoding`\n * value already in options will be overwritten by the compression algorithm.\n */\n publish<TName extends InferPublisherNames<TContract>>(\n publisherName: TName,\n message: ClientInferPublisherInput<TContract, TName>,\n options?: PublishOptions,\n ): AsyncResult<void, TechnicalError | MessageValidationError> {\n const startTime = Date.now();\n // Non-null assertions safe: TypeScript guarantees these exist for valid TName\n const publisher = this.contract.publishers![publisherName as string]!;\n const { exchange, routingKey } = publisher;\n\n // Start telemetry span\n const span = startPublishSpan(this.telemetry, exchange.name, routingKey, {\n [MessagingSemanticConventions.AMQP_PUBLISHER_NAME]: String(publisherName),\n });\n\n const validateMessage = (): AsyncResult<unknown, TechnicalError | MessageValidationError> => {\n const validationResult = publisher.message.payload[\"~standard\"].validate(message);\n const promise =\n validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult);\n return fromPromise(\n promise,\n (error): TechnicalError | MessageValidationError =>\n new TechnicalError(\"Validation failed\", error),\n ).flatMap((validation) => {\n if (validation.issues) {\n return err<TechnicalError | MessageValidationError>(\n new MessageValidationError(String(publisherName), validation.issues),\n );\n }\n return ok(validation.value);\n });\n };\n\n const publishMessage = (validatedMessage: unknown): AsyncResult<void, TechnicalError> => {\n // Merge default options with provided options\n const mergedOptions = { ...this.defaultPublishOptions, ...options };\n\n // Extract compression from merged options and create publish options without it\n const { compression, ...restOptions } = mergedOptions;\n const publishOptions: AmqpClientPublishOptions = { ...restOptions };\n\n // Prepare payload and options based on compression configuration\n const preparePayload = (): AsyncResult<Buffer | unknown, TechnicalError> => {\n if (compression) {\n // Compress the message payload\n const messageBuffer = Buffer.from(JSON.stringify(validatedMessage));\n publishOptions.contentEncoding = compression;\n return compressBuffer(messageBuffer, compression);\n }\n\n // No compression: use the channel's built-in JSON serialization\n return ok(validatedMessage).toAsync();\n };\n\n return preparePayload().flatMap((payload) =>\n this.amqpClient\n .publish(publisher.exchange.name, publisher.routingKey ?? \"\", payload, publishOptions)\n .flatMap((published) => {\n if (!published) {\n return err<TechnicalError>(\n new TechnicalError(\n `Failed to publish message for publisher \"${String(publisherName)}\": Channel rejected the message (buffer full or other channel issue)`,\n ),\n );\n }\n\n this.logger?.info(\"Message published successfully\", {\n publisherName: String(publisherName),\n exchange: publisher.exchange.name,\n routingKey: publisher.routingKey,\n compressed: !!compression,\n });\n\n return ok(undefined);\n }),\n );\n };\n\n return validateMessage()\n .flatMap((validatedMessage) => publishMessage(validatedMessage))\n .tap(() => {\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordPublishMetric(this.telemetry, exchange.name, routingKey, true, durationMs);\n })\n .tapErr((error) => {\n const durationMs = Date.now() - startTime;\n endSpanError(span, error);\n recordPublishMetric(this.telemetry, exchange.name, routingKey, false, durationMs);\n });\n }\n\n /**\n * Invoke an RPC defined via `defineRpc` and await the typed response.\n *\n * The request payload is validated against the RPC's request schema, then\n * published to the AMQP default exchange with the server's queue name as\n * routing key, `replyTo` set to `amq.rabbitmq.reply-to`, and a fresh UUID\n * `correlationId`. The returned AsyncResult resolves once a matching reply\n * arrives and validates against the response schema, or once `timeoutMs`\n * elapses (whichever comes first).\n *\n * @example\n * ```typescript\n * const result = await client.call('calculate', { a: 1, b: 2 }, { timeoutMs: 5_000 });\n * result.match({\n * ok: (value) => console.log(value.sum), // 3\n * err: (error) => console.error(error),\n * defect: (cause) => console.error(cause),\n * });\n * ```\n */\n call<TName extends InferRpcNames<TContract>>(\n rpcName: TName,\n request: ClientInferRpcRequestInput<TContract, TName>,\n options: CallOptions,\n ): AsyncResult<\n ClientInferRpcResponseOutput<TContract, TName>,\n TechnicalError | MessageValidationError | RpcTimeoutError | RpcCancelledError\n > {\n type ResponseType = ClientInferRpcResponseOutput<TContract, TName>;\n type CallError = TechnicalError | MessageValidationError | RpcTimeoutError | RpcCancelledError;\n type CallResult = Result<ResponseType, CallError>;\n\n // setTimeout truncates fractional ms and clamps anything outside the\n // 32-bit signed integer range (~24.8 days) to 1ms, so reject those up\n // front as user errors rather than producing surprising behavior.\n const TIMEOUT_MAX_MS = 2_147_483_647;\n if (\n typeof options.timeoutMs !== \"number\" ||\n !Number.isFinite(options.timeoutMs) ||\n options.timeoutMs <= 0 ||\n options.timeoutMs > TIMEOUT_MAX_MS\n ) {\n return err<CallError>(\n new TechnicalError(\n `Invalid timeoutMs for RPC call to \"${String(rpcName)}\": expected a finite positive number ≤ ${TIMEOUT_MAX_MS}, got ${String(options.timeoutMs)}`,\n ),\n ).toAsync();\n }\n\n const startTime = Date.now();\n // Non-null assertion safe: TName is constrained to RPC names in the contract.\n const rpc = this.contract.rpcs![rpcName as string]!;\n const requestSchema = rpc.request.payload;\n const responseSchema = rpc.response.payload;\n const queueName = extractQueue(rpc.queue).name;\n\n // RPC publishes to the default exchange with the queue name as routing key.\n const span = startPublishSpan(this.telemetry, \"\", queueName, {\n [MessagingSemanticConventions.AMQP_PUBLISHER_NAME]: String(rpcName),\n });\n\n const correlationId = randomUUID();\n\n // Set up the reply future + pending entry up front so a reply that arrives\n // racing the publish round-trip can find a slot. Cleanup on preflight\n // failure happens in the `.orElse` below.\n let resolveCall!: (result: CallResult) => void;\n const callPromise = new Promise<CallResult>((res) => {\n resolveCall = res;\n });\n // `callPromise` resolves to a `Result` (never rejects), so lift it with\n // `fromSafePromise` and collapse the nested `Result` back into the channel.\n const callResultAsync: AsyncResult<ResponseType, CallError> = fromSafePromise(\n callPromise,\n ).flatMap((result) => result);\n\n const timer = setTimeout(() => {\n if (!this.pendingCalls.has(correlationId)) return;\n this.pendingCalls.delete(correlationId);\n resolveCall(err(new RpcTimeoutError(String(rpcName), options.timeoutMs)));\n }, options.timeoutMs);\n\n this.pendingCalls.set(correlationId, {\n rpcName: String(rpcName),\n responseSchema,\n resolve: resolveCall as PendingCall[\"resolve\"],\n timer,\n });\n\n const validateRequest = (): AsyncResult<unknown, TechnicalError | MessageValidationError> => {\n // Wrap the validate call — a Standard Schema implementation may throw\n // synchronously, and that throw would otherwise escape the chain and\n // leave the pending-call entry/timer dangling until timeout.\n let rawValidation: ReturnType<StandardSchemaV1[\"~standard\"][\"validate\"]>;\n try {\n rawValidation = requestSchema[\"~standard\"].validate(request);\n } catch (error: unknown) {\n return err<TechnicalError | MessageValidationError>(\n new TechnicalError(\"RPC request validation threw\", error),\n ).toAsync();\n }\n const validationPromise =\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation);\n return fromPromise(\n validationPromise,\n (error): TechnicalError | MessageValidationError =>\n new TechnicalError(\"RPC request validation threw\", error),\n ).flatMap((validation) =>\n validation.issues\n ? err<TechnicalError | MessageValidationError>(\n new MessageValidationError(String(rpcName), validation.issues),\n )\n : ok(validation.value),\n );\n };\n\n const publishRequest = (validatedRequest: unknown): AsyncResult<void, TechnicalError> => {\n // Merge `defaultPublishOptions` (persistent, priority, headers, …) with\n // the per-call options, then layer the RPC-managed fields on top so they\n // cannot be overridden. `compression` is intentionally dropped: RPC v1\n // does not implement reply-side decompression, so request-side\n // compression would break the round-trip.\n const { compression: _ignoredCompression, ...defaultsWithoutCompression } =\n this.defaultPublishOptions;\n const publishOptions: AmqpClientPublishOptions = {\n ...defaultsWithoutCompression,\n ...options.publishOptions,\n replyTo: DIRECT_REPLY_TO,\n correlationId,\n contentType: \"application/json\",\n };\n return this.amqpClient\n .publish(\"\", queueName, validatedRequest, publishOptions)\n .flatMap((published) =>\n published\n ? ok(undefined)\n : err<TechnicalError>(\n new TechnicalError(\n `Failed to publish RPC request for \"${String(rpcName)}\": channel buffer full`,\n ),\n ),\n );\n };\n\n return validateRequest()\n .flatMap((validated) => publishRequest(validated))\n .flatMap(() => callResultAsync)\n .orElse((error: CallError) => {\n // If preflight failed (validate or publish), the pending entry still\n // exists and the timer is alive. Clean both up so the call doesn't\n // leak. Timer-fired errors and reply-resolved errors have already\n // cleaned the entry, so the .has() check guards against double cleanup.\n if (this.pendingCalls.has(correlationId)) {\n clearTimeout(timer);\n this.pendingCalls.delete(correlationId);\n }\n return err(error).toAsync();\n })\n .tap(() => {\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordPublishMetric(this.telemetry, \"\", queueName, true, durationMs);\n })\n .tapErr((error) => {\n const durationMs = Date.now() - startTime;\n endSpanError(span, error);\n recordPublishMetric(this.telemetry, \"\", queueName, false, durationMs);\n });\n }\n\n /**\n * Close the channel and connection. Cancels the reply consumer (if any) and\n * rejects every in-flight RPC call with `RpcCancelledError`.\n */\n close(): AsyncResult<void, TechnicalError> {\n // Reject pending calls first — once close() runs, no reply will arrive.\n for (const [, pending] of this.pendingCalls) {\n clearTimeout(pending.timer);\n pending.resolve(err(new RpcCancelledError(pending.rpcName)));\n }\n this.pendingCalls.clear();\n\n const cancelReply: AsyncResult<void, TechnicalError> = this.replyConsumerTag\n ? this.amqpClient.cancel(this.replyConsumerTag).orElse((error) => {\n this.logger?.warn(\"Failed to cancel RPC reply consumer during close\", { error });\n return ok(undefined);\n })\n : ok(undefined).toAsync();\n\n return cancelReply.flatMap(() => this.amqpClient.close());\n }\n\n private waitForConnectionReady(): AsyncResult<void, TechnicalError> {\n return this.amqpClient.waitForConnect();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/compression.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["import type { CompressionAlgorithm } from \"@amqp-contract/contract\";\nimport { TechnicalError } from \"@amqp-contract/core\";\nimport { fromPromise, type AsyncResult } from \"unthrown\";\nimport { deflate, gzip } from \"node:zlib\";\nimport { promisify } from \"node:util\";\nimport { match } from \"ts-pattern\";\n\nconst gzipAsync = promisify(gzip);\nconst deflateAsync = promisify(deflate);\n\n/**\n * Compress a buffer using the specified compression algorithm.\n *\n * @param buffer - The buffer to compress\n * @param algorithm - The compression algorithm to use\n * @returns An AsyncResult resolving to the compressed buffer or a TechnicalError\n *\n * @internal\n */\nexport function compressBuffer(\n buffer: Buffer,\n algorithm: CompressionAlgorithm,\n): AsyncResult<Buffer, TechnicalError> {\n return match(algorithm)\n .with(\"gzip\", () =>\n fromPromise(\n gzipAsync(buffer),\n (error) => new TechnicalError(\"Failed to compress with gzip\", error),\n ),\n )\n .with(\"deflate\", () =>\n fromPromise(\n deflateAsync(buffer),\n (error) => new TechnicalError(\"Failed to compress with deflate\", error),\n ),\n )\n .exhaustive();\n}\n","import { TaggedError } from \"unthrown\";\n\nexport { MessageValidationError } from \"@amqp-contract/core\";\n\n/**\n * Returned from `TypedAmqpClient.call()` when the configured `timeoutMs` elapses\n * before the RPC server publishes a reply with the matching `correlationId`.\n *\n * The pending call is removed from the in-memory correlation map; if a reply\n * arrives after the timeout it is dropped (and a debug log is emitted by the\n * client if a logger is configured). Carries a namespaced `_tag` of\n * `\"@amqp-contract/RpcTimeoutError\"`; the `Error.name` is kept bare\n * (`\"RpcTimeoutError\"`).\n */\nexport class RpcTimeoutError extends TaggedError(\"@amqp-contract/RpcTimeoutError\", {\n name: \"RpcTimeoutError\",\n})<{\n message: string;\n rpcName: string;\n timeoutMs: number;\n}> {\n constructor(rpcName: string, timeoutMs: number) {\n super({\n message: `RPC call to \"${rpcName}\" timed out after ${timeoutMs}ms with no reply received`,\n rpcName,\n timeoutMs,\n });\n }\n}\n\n/**\n * Returned from any in-flight RPC call when the client is closed before the\n * reply is received. The correlation map is cleared on close and every pending\n * caller's promise resolves with `Err(RpcCancelledError)`. Carries a namespaced\n * `_tag` of `\"@amqp-contract/RpcCancelledError\"`; the `Error.name` is kept bare\n * (`\"RpcCancelledError\"`).\n */\nexport class RpcCancelledError extends TaggedError(\"@amqp-contract/RpcCancelledError\", {\n name: \"RpcCancelledError\",\n})<{\n message: string;\n rpcName: string;\n}> {\n constructor(rpcName: string) {\n super({\n message: `RPC call to \"${rpcName}\" was cancelled because the client was closed`,\n rpcName,\n });\n }\n}\n","import {\n extractQueue,\n type CompressionAlgorithm,\n type ContractDefinition,\n type InferPublisherNames,\n type InferRpcNames,\n} from \"@amqp-contract/contract\";\nimport {\n AmqpClient,\n PublishOptions as AmqpClientPublishOptions,\n type Logger,\n MessagingSemanticConventions,\n TechnicalError,\n type TelemetryProvider,\n defaultTelemetryProvider,\n endSpanError,\n endSpanSuccess,\n recordLateRpcReply,\n recordPublishMetric,\n safeJsonParse,\n startPublishSpan,\n} from \"@amqp-contract/core\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport { Err, fromPromise, fromSafePromise, Ok, type AsyncResult, type Result } from \"unthrown\";\nimport { randomUUID } from \"node:crypto\";\nimport { compressBuffer } from \"./compression.js\";\nimport { MessageValidationError, RpcCancelledError, RpcTimeoutError } from \"./errors.js\";\nimport type {\n ClientInferPublisherInput,\n ClientInferRpcRequestInput,\n ClientInferRpcResponseOutput,\n} from \"./types.js\";\n\n/**\n * The RabbitMQ direct-reply-to pseudo-queue. Publishing with `replyTo` set to\n * this value tells the server to deliver the response back to the consumer\n * subscribed on this queue on the same channel — no real queue is created and\n * no setup is required beyond consuming from it once with `noAck: true`.\n *\n * @see https://www.rabbitmq.com/docs/direct-reply-to\n */\nconst DIRECT_REPLY_TO = \"amq.rabbitmq.reply-to\";\n\n/**\n * In-flight RPC call tracked by `TypedAmqpClient`. The reply consumer\n * looks up entries by `correlationId` when responses arrive.\n */\ntype PendingCall = {\n rpcName: string;\n responseSchema: StandardSchemaV1;\n resolve: (\n result: Result<\n unknown,\n TechnicalError | MessageValidationError | RpcTimeoutError | RpcCancelledError\n >,\n ) => void;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/**\n * Publish options that extend amqp-client's PublishOptions with optional compression support.\n */\nexport type PublishOptions = AmqpClientPublishOptions & {\n /**\n * Optional compression algorithm to use for the message payload.\n * When specified, the message will be compressed using the chosen algorithm\n * and the contentEncoding header will be set automatically.\n */\n compression?: CompressionAlgorithm | undefined;\n};\n\n/**\n * Options for creating a client\n */\nexport type CreateClientOptions<TContract extends ContractDefinition> = {\n contract: TContract;\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n logger?: Logger | undefined;\n /**\n * Optional telemetry provider for tracing and metrics.\n * If not provided, uses the default provider which attempts to load OpenTelemetry.\n * OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed.\n */\n telemetry?: TelemetryProvider | undefined;\n /**\n * Default publish options that will be applied to all publish operations.\n * These can be overridden by options passed to the publish method.\n * By default, persistent is set to true for message durability.\n */\n defaultPublishOptions?: PublishOptions | undefined;\n /**\n * Maximum time in ms to wait for the AMQP connection to become ready before\n * `create()` resolves to an `Err(TechnicalError)`. Defaults to 30s\n * (the {@link AmqpClient}'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to\n * disable the timeout and let amqp-connection-manager retry indefinitely.\n */\n connectTimeoutMs?: number | null | undefined;\n};\n\n/**\n * Per-call options for `client.call()`.\n */\nexport type CallOptions = {\n /**\n * Maximum time in ms to wait for an RPC reply. If exceeded, the call resolves\n * to `Err(RpcTimeoutError)` and the in-memory correlation entry is cleared.\n * A late reply arriving after the timeout is silently dropped.\n *\n * Required: RPC without a timeout is a footgun.\n */\n timeoutMs: number;\n\n /**\n * Optional AMQP message properties to merge into the request. `replyTo` and\n * `correlationId` are managed by the client and cannot be overridden.\n */\n publishOptions?: Omit<AmqpClientPublishOptions, \"replyTo\" | \"correlationId\">;\n};\n\n/**\n * Type-safe AMQP client for publishing messages\n */\nexport class TypedAmqpClient<TContract extends ContractDefinition> {\n /**\n * In-flight RPC calls keyed by `correlationId`. Cleared when a reply is\n * received, when the call times out, or when the client is closed.\n */\n private readonly pendingCalls = new Map<string, PendingCall>();\n\n /**\n * Consumer tag of the reply consumer subscribed on `amq.rabbitmq.reply-to`.\n * Set when the contract has at least one entry in `rpcs`; undefined otherwise.\n */\n private replyConsumerTag?: string;\n\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n private readonly defaultPublishOptions: PublishOptions,\n private readonly logger?: Logger,\n private readonly telemetry: TelemetryProvider = defaultTelemetryProvider,\n ) {}\n\n /**\n * Create a type-safe AMQP client from a contract.\n *\n * Connection management (including automatic reconnection) is handled internally\n * by amqp-connection-manager via the {@link AmqpClient}. The client establishes\n * infrastructure asynchronously in the background once the connection is ready.\n *\n * Connections are automatically shared across clients with the same URLs and\n * connection options, following RabbitMQ best practices.\n */\n static create<TContract extends ContractDefinition>({\n contract,\n urls,\n connectionOptions,\n defaultPublishOptions,\n logger,\n telemetry,\n connectTimeoutMs,\n }: CreateClientOptions<TContract>): AsyncResult<TypedAmqpClient<TContract>, TechnicalError> {\n const client = new TypedAmqpClient(\n contract,\n new AmqpClient(contract, { urls, connectionOptions, connectTimeoutMs }),\n { persistent: true, ...defaultPublishOptions },\n logger,\n telemetry ?? defaultTelemetryProvider,\n );\n\n const setup = client\n .waitForConnectionReady()\n .flatMap(() => client.setupReplyConsumerIfNeeded());\n\n const inner = (async (): Promise<Result<TypedAmqpClient<TContract>, TechnicalError>> => {\n const setupResult = await setup;\n if (!setupResult.isOk()) {\n const closeResult = await client.close();\n if (closeResult.isErr()) {\n logger?.warn(\"Failed to close client after connection failure\", {\n error: closeResult.error,\n });\n }\n }\n // `map` runs only on Ok; an Err/Defect passes through with its value type\n // re-shaped to the client, so the failure surfaces unchanged.\n return setupResult.map(() => client);\n })();\n\n return fromSafePromise(inner).flatMap((result) => result);\n }\n\n /**\n * If the contract has any RPC entry, subscribe to `amq.rabbitmq.reply-to`\n * once. Replies for every in-flight call arrive on this single consumer and\n * are demultiplexed by `correlationId`.\n */\n private setupReplyConsumerIfNeeded(): AsyncResult<void, TechnicalError> {\n const rpcs = this.contract.rpcs ?? {};\n if (Object.keys(rpcs).length === 0) {\n return Ok(undefined).toAsync();\n }\n\n return this.amqpClient\n .consume(DIRECT_REPLY_TO, (msg) => this.handleRpcReply(msg), { noAck: true })\n .tap((tag) => {\n this.replyConsumerTag = tag;\n })\n .map(() => undefined);\n }\n\n /**\n * Demultiplex an RPC reply by `correlationId`, validate the body against the\n * call's response schema, and resolve the awaiting caller. Replies with no\n * matching pending call (the call already timed out, was cancelled, or the\n * correlationId is unknown) are logged at warn — a non-zero rate of these\n * usually indicates a tuning problem (handler latency exceeds caller\n * timeout). The `messaging.rpc.late_reply` counter lets dashboards alert on\n * sustained drift without parsing logs.\n */\n private handleRpcReply(msg: Parameters<Parameters<AmqpClient[\"consume\"]>[1]>[0]): void {\n if (!msg) return;\n const correlationId = msg.properties.correlationId;\n if (typeof correlationId !== \"string\") {\n this.logger?.warn(\"Received RPC reply without correlationId; dropping\", {\n deliveryTag: msg.fields.deliveryTag,\n });\n recordLateRpcReply(this.telemetry, \"missing-correlation-id\");\n return;\n }\n const pending = this.pendingCalls.get(correlationId);\n if (!pending) {\n this.logger?.warn(\n \"Received RPC reply for unknown correlationId (caller already timed out or cancelled)\",\n { correlationId },\n );\n recordLateRpcReply(this.telemetry, \"unknown-correlation-id\");\n return;\n }\n this.pendingCalls.delete(correlationId);\n clearTimeout(pending.timer);\n\n const parseResult = safeJsonParse(\n msg.content,\n (error) =>\n new TechnicalError(`Failed to parse RPC reply JSON for \"${pending.rpcName}\"`, error),\n );\n if (!parseResult.isOk()) {\n pending.resolve(\n Err(\n parseResult.isErr()\n ? parseResult.error\n : new TechnicalError(\n `Failed to parse RPC reply JSON for \"${pending.rpcName}\"`,\n parseResult.cause,\n ),\n ),\n );\n return;\n }\n const parsed = parseResult.value;\n\n // Wrap the validate call itself — a Standard Schema implementation may\n // throw synchronously, and the throw would otherwise escape the consume\n // callback and could crash the reply consumer.\n let rawValidation: ReturnType<StandardSchemaV1[\"~standard\"][\"validate\"]>;\n try {\n rawValidation = pending.responseSchema[\"~standard\"].validate(parsed);\n } catch (error: unknown) {\n pending.resolve(\n Err(new TechnicalError(`RPC reply validation threw for \"${pending.rpcName}\"`, error)),\n );\n return;\n }\n const validationPromise =\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation);\n\n validationPromise.then(\n (validation) => {\n if (validation.issues) {\n pending.resolve(Err(new MessageValidationError(pending.rpcName, validation.issues)));\n return;\n }\n pending.resolve(Ok(validation.value));\n },\n (error: unknown) => {\n pending.resolve(\n Err(new TechnicalError(`RPC reply validation threw for \"${pending.rpcName}\"`, error)),\n );\n },\n );\n }\n\n /**\n * Publish a message using a defined publisher.\n *\n * @param publisherName - The name of the publisher to use\n * @param message - The message to publish\n * @param options - Optional publish options including compression, headers, priority, etc.\n *\n * @remarks\n * If `options.compression` is specified, the message will be compressed before publishing\n * and the `contentEncoding` property will be set automatically. Any `contentEncoding`\n * value already in options will be overwritten by the compression algorithm.\n */\n publish<TName extends InferPublisherNames<TContract>>(\n publisherName: TName,\n message: ClientInferPublisherInput<TContract, TName>,\n options?: PublishOptions,\n ): AsyncResult<void, TechnicalError | MessageValidationError> {\n const startTime = Date.now();\n // Non-null assertions safe: TypeScript guarantees these exist for valid TName\n const publisher = this.contract.publishers![publisherName as string]!;\n const { exchange, routingKey } = publisher;\n\n // Start telemetry span\n const span = startPublishSpan(this.telemetry, exchange.name, routingKey, {\n [MessagingSemanticConventions.AMQP_PUBLISHER_NAME]: String(publisherName),\n });\n\n const validateMessage = (): AsyncResult<unknown, TechnicalError | MessageValidationError> => {\n const validationResult = publisher.message.payload[\"~standard\"].validate(message);\n const promise =\n validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult);\n return fromPromise(\n promise,\n (error): TechnicalError | MessageValidationError =>\n new TechnicalError(\"Validation failed\", error),\n ).flatMap((validation) => {\n if (validation.issues) {\n return Err<TechnicalError | MessageValidationError>(\n new MessageValidationError(String(publisherName), validation.issues),\n );\n }\n return Ok(validation.value);\n });\n };\n\n const publishMessage = (validatedMessage: unknown): AsyncResult<void, TechnicalError> => {\n // Merge default options with provided options\n const mergedOptions = { ...this.defaultPublishOptions, ...options };\n\n // Extract compression from merged options and create publish options without it\n const { compression, ...restOptions } = mergedOptions;\n const publishOptions: AmqpClientPublishOptions = { ...restOptions };\n\n // Prepare payload and options based on compression configuration\n const preparePayload = (): AsyncResult<Buffer | unknown, TechnicalError> => {\n if (compression) {\n // Compress the message payload\n const messageBuffer = Buffer.from(JSON.stringify(validatedMessage));\n publishOptions.contentEncoding = compression;\n return compressBuffer(messageBuffer, compression);\n }\n\n // No compression: use the channel's built-in JSON serialization\n return Ok(validatedMessage).toAsync();\n };\n\n return preparePayload().flatMap((payload) =>\n this.amqpClient\n .publish(publisher.exchange.name, publisher.routingKey ?? \"\", payload, publishOptions)\n .flatMap((published) => {\n if (!published) {\n return Err<TechnicalError>(\n new TechnicalError(\n `Failed to publish message for publisher \"${String(publisherName)}\": Channel rejected the message (buffer full or other channel issue)`,\n ),\n );\n }\n\n this.logger?.info(\"Message published successfully\", {\n publisherName: String(publisherName),\n exchange: publisher.exchange.name,\n routingKey: publisher.routingKey,\n compressed: !!compression,\n });\n\n return Ok(undefined);\n }),\n );\n };\n\n return validateMessage()\n .flatMap((validatedMessage) => publishMessage(validatedMessage))\n .tap(() => {\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordPublishMetric(this.telemetry, exchange.name, routingKey, true, durationMs);\n })\n .tapErr((error) => {\n const durationMs = Date.now() - startTime;\n endSpanError(span, error);\n recordPublishMetric(this.telemetry, exchange.name, routingKey, false, durationMs);\n });\n }\n\n /**\n * Invoke an RPC defined via `defineRpc` and await the typed response.\n *\n * The request payload is validated against the RPC's request schema, then\n * published to the AMQP default exchange with the server's queue name as\n * routing key, `replyTo` set to `amq.rabbitmq.reply-to`, and a fresh UUID\n * `correlationId`. The returned AsyncResult resolves once a matching reply\n * arrives and validates against the response schema, or once `timeoutMs`\n * elapses (whichever comes first).\n *\n * @example\n * ```typescript\n * const result = await client.call('calculate', { a: 1, b: 2 }, { timeoutMs: 5_000 });\n * result.match({\n * ok: (value) => console.log(value.sum), // 3\n * err: (error) => console.error(error),\n * defect: (cause) => console.error(cause),\n * });\n * ```\n */\n call<TName extends InferRpcNames<TContract>>(\n rpcName: TName,\n request: ClientInferRpcRequestInput<TContract, TName>,\n options: CallOptions,\n ): AsyncResult<\n ClientInferRpcResponseOutput<TContract, TName>,\n TechnicalError | MessageValidationError | RpcTimeoutError | RpcCancelledError\n > {\n type ResponseType = ClientInferRpcResponseOutput<TContract, TName>;\n type CallError = TechnicalError | MessageValidationError | RpcTimeoutError | RpcCancelledError;\n type CallResult = Result<ResponseType, CallError>;\n\n // setTimeout truncates fractional ms and clamps anything outside the\n // 32-bit signed integer range (~24.8 days) to 1ms, so reject those up\n // front as user errors rather than producing surprising behavior.\n const TIMEOUT_MAX_MS = 2_147_483_647;\n if (\n typeof options.timeoutMs !== \"number\" ||\n !Number.isFinite(options.timeoutMs) ||\n options.timeoutMs <= 0 ||\n options.timeoutMs > TIMEOUT_MAX_MS\n ) {\n return Err<CallError>(\n new TechnicalError(\n `Invalid timeoutMs for RPC call to \"${String(rpcName)}\": expected a finite positive number ≤ ${TIMEOUT_MAX_MS}, got ${String(options.timeoutMs)}`,\n ),\n ).toAsync();\n }\n\n const startTime = Date.now();\n // Non-null assertion safe: TName is constrained to RPC names in the contract.\n const rpc = this.contract.rpcs![rpcName as string]!;\n const requestSchema = rpc.request.payload;\n const responseSchema = rpc.response.payload;\n const queueName = extractQueue(rpc.queue).name;\n\n // RPC publishes to the default exchange with the queue name as routing key.\n const span = startPublishSpan(this.telemetry, \"\", queueName, {\n [MessagingSemanticConventions.AMQP_PUBLISHER_NAME]: String(rpcName),\n });\n\n const correlationId = randomUUID();\n\n // Set up the reply future + pending entry up front so a reply that arrives\n // racing the publish round-trip can find a slot. Cleanup on preflight\n // failure happens in the `.orElse` below.\n let resolveCall!: (result: CallResult) => void;\n const callPromise = new Promise<CallResult>((res) => {\n resolveCall = res;\n });\n // `callPromise` resolves to a `Result` (never rejects), so lift it with\n // `fromSafePromise` and collapse the nested `Result` back into the channel.\n const callResultAsync: AsyncResult<ResponseType, CallError> = fromSafePromise(\n callPromise,\n ).flatMap((result) => result);\n\n const timer = setTimeout(() => {\n if (!this.pendingCalls.has(correlationId)) return;\n this.pendingCalls.delete(correlationId);\n resolveCall(Err(new RpcTimeoutError(String(rpcName), options.timeoutMs)));\n }, options.timeoutMs);\n\n this.pendingCalls.set(correlationId, {\n rpcName: String(rpcName),\n responseSchema,\n resolve: resolveCall as PendingCall[\"resolve\"],\n timer,\n });\n\n const validateRequest = (): AsyncResult<unknown, TechnicalError | MessageValidationError> => {\n // Wrap the validate call — a Standard Schema implementation may throw\n // synchronously, and that throw would otherwise escape the chain and\n // leave the pending-call entry/timer dangling until timeout.\n let rawValidation: ReturnType<StandardSchemaV1[\"~standard\"][\"validate\"]>;\n try {\n rawValidation = requestSchema[\"~standard\"].validate(request);\n } catch (error: unknown) {\n return Err<TechnicalError | MessageValidationError>(\n new TechnicalError(\"RPC request validation threw\", error),\n ).toAsync();\n }\n const validationPromise =\n rawValidation instanceof Promise ? rawValidation : Promise.resolve(rawValidation);\n return fromPromise(\n validationPromise,\n (error): TechnicalError | MessageValidationError =>\n new TechnicalError(\"RPC request validation threw\", error),\n ).flatMap((validation) =>\n validation.issues\n ? Err<TechnicalError | MessageValidationError>(\n new MessageValidationError(String(rpcName), validation.issues),\n )\n : Ok(validation.value),\n );\n };\n\n const publishRequest = (validatedRequest: unknown): AsyncResult<void, TechnicalError> => {\n // Merge `defaultPublishOptions` (persistent, priority, headers, …) with\n // the per-call options, then layer the RPC-managed fields on top so they\n // cannot be overridden. `compression` is intentionally dropped: RPC v1\n // does not implement reply-side decompression, so request-side\n // compression would break the round-trip.\n const { compression: _ignoredCompression, ...defaultsWithoutCompression } =\n this.defaultPublishOptions;\n const publishOptions: AmqpClientPublishOptions = {\n ...defaultsWithoutCompression,\n ...options.publishOptions,\n replyTo: DIRECT_REPLY_TO,\n correlationId,\n contentType: \"application/json\",\n };\n return this.amqpClient\n .publish(\"\", queueName, validatedRequest, publishOptions)\n .flatMap((published) =>\n published\n ? Ok(undefined)\n : Err<TechnicalError>(\n new TechnicalError(\n `Failed to publish RPC request for \"${String(rpcName)}\": channel buffer full`,\n ),\n ),\n );\n };\n\n return validateRequest()\n .flatMap((validated) => publishRequest(validated))\n .flatMap(() => callResultAsync)\n .orElse((error: CallError) => {\n // If preflight failed (validate or publish), the pending entry still\n // exists and the timer is alive. Clean both up so the call doesn't\n // leak. Timer-fired errors and reply-resolved errors have already\n // cleaned the entry, so the .has() check guards against double cleanup.\n if (this.pendingCalls.has(correlationId)) {\n clearTimeout(timer);\n this.pendingCalls.delete(correlationId);\n }\n return Err(error).toAsync();\n })\n .tap(() => {\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordPublishMetric(this.telemetry, \"\", queueName, true, durationMs);\n })\n .tapErr((error) => {\n const durationMs = Date.now() - startTime;\n endSpanError(span, error);\n recordPublishMetric(this.telemetry, \"\", queueName, false, durationMs);\n });\n }\n\n /**\n * Close the channel and connection. Cancels the reply consumer (if any) and\n * rejects every in-flight RPC call with `RpcCancelledError`.\n */\n close(): AsyncResult<void, TechnicalError> {\n // Reject pending calls first — once close() runs, no reply will arrive.\n for (const [, pending] of this.pendingCalls) {\n clearTimeout(pending.timer);\n pending.resolve(Err(new RpcCancelledError(pending.rpcName)));\n }\n this.pendingCalls.clear();\n\n const cancelReply: AsyncResult<void, TechnicalError> = this.replyConsumerTag\n ? this.amqpClient.cancel(this.replyConsumerTag).orElse((error) => {\n this.logger?.warn(\"Failed to cancel RPC reply consumer during close\", { error });\n return Ok(undefined);\n })\n : Ok(undefined).toAsync();\n\n return cancelReply.flatMap(() => this.amqpClient.close());\n }\n\n private waitForConnectionReady(): AsyncResult<void, TechnicalError> {\n return this.amqpClient.waitForConnect();\n }\n}\n"],"mappings":"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"}
package/docs/index.md CHANGED
@@ -72,7 +72,7 @@ MessageValidationError_base<{
72
72
 
73
73
  | Property | Modifier | Type | Inherited from | Defined in |
74
74
  | ------ | ------ | ------ | ------ | ------ |
75
- | <a id="_tag"></a> `_tag` | `readonly` | `"@amqp-contract/MessageValidationError"` | `MessageValidationError_base._tag` | node\_modules/.pnpm/unthrown@0.2.0/node\_modules/unthrown/dist/index.d.mts:638 |
75
+ | <a id="_tag"></a> `_tag` | `readonly` | `"@amqp-contract/MessageValidationError"` | `MessageValidationError_base._tag` | node\_modules/.pnpm/unthrown@1.0.0/node\_modules/unthrown/dist/index.d.mts:778 |
76
76
  | <a id="cause"></a> `cause?` | `public` | `unknown` | `MessageValidationError_base.cause` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24 |
77
77
  | <a id="issues"></a> `issues` | `readonly` | `unknown` | `MessageValidationError_base.issues` | packages/core/dist/index.d.mts:43 |
78
78
  | <a id="message"></a> `message` | `public` | `string` | `MessageValidationError_base.message` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075 |
@@ -84,11 +84,11 @@ MessageValidationError_base<{
84
84
 
85
85
  ### RpcCancelledError
86
86
 
87
- Defined in: [packages/client/src/errors.ts:38](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/errors.ts#L38)
87
+ Defined in: [packages/client/src/errors.ts:38](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/errors.ts#L38)
88
88
 
89
89
  Returned from any in-flight RPC call when the client is closed before the
90
90
  reply is received. The correlation map is cleared on close and every pending
91
- caller's promise resolves with `err(RpcCancelledError)`. Carries a namespaced
91
+ caller's promise resolves with `Err(RpcCancelledError)`. Carries a namespaced
92
92
  `_tag` of `"@amqp-contract/RpcCancelledError"`; the `Error.name` is kept bare
93
93
  (`"RpcCancelledError"`).
94
94
 
@@ -107,7 +107,7 @@ caller's promise resolves with `err(RpcCancelledError)`. Carries a namespaced
107
107
  new RpcCancelledError(rpcName): RpcCancelledError;
108
108
  ```
109
109
 
110
- Defined in: [packages/client/src/errors.ts:44](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/errors.ts#L44)
110
+ Defined in: [packages/client/src/errors.ts:44](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/errors.ts#L44)
111
111
 
112
112
  ###### Parameters
113
113
 
@@ -134,18 +134,18 @@ TaggedError("@amqp-contract/RpcCancelledError", {
134
134
 
135
135
  | Property | Modifier | Type | Inherited from | Defined in |
136
136
  | ------ | ------ | ------ | ------ | ------ |
137
- | <a id="_tag-1"></a> `_tag` | `readonly` | `"@amqp-contract/RpcCancelledError"` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", })._tag` | node\_modules/.pnpm/unthrown@0.2.0/node\_modules/unthrown/dist/index.d.mts:638 |
137
+ | <a id="_tag-1"></a> `_tag` | `readonly` | `"@amqp-contract/RpcCancelledError"` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", })._tag` | node\_modules/.pnpm/unthrown@1.0.0/node\_modules/unthrown/dist/index.d.mts:778 |
138
138
  | <a id="cause-1"></a> `cause?` | `public` | `unknown` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", }).cause` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24 |
139
139
  | <a id="message-1"></a> `message` | `public` | `string` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", }).message` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075 |
140
140
  | <a id="name-1"></a> `name` | `public` | `string` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", }).name` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074 |
141
- | <a id="rpcname"></a> `rpcName` | `readonly` | `string` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", }).rpcName` | [packages/client/src/errors.ts:42](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/errors.ts#L42) |
141
+ | <a id="rpcname"></a> `rpcName` | `readonly` | `string` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", }).rpcName` | [packages/client/src/errors.ts:42](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/errors.ts#L42) |
142
142
  | <a id="stack-1"></a> `stack?` | `public` | `string` | `TaggedError("@amqp-contract/RpcCancelledError", { name: "RpcCancelledError", }).stack` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
143
143
 
144
144
  ***
145
145
 
146
146
  ### RpcTimeoutError
147
147
 
148
- Defined in: [packages/client/src/errors.ts:15](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/errors.ts#L15)
148
+ Defined in: [packages/client/src/errors.ts:15](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/errors.ts#L15)
149
149
 
150
150
  Returned from `TypedAmqpClient.call()` when the configured `timeoutMs` elapses
151
151
  before the RPC server publishes a reply with the matching `correlationId`.
@@ -172,7 +172,7 @@ client if a logger is configured). Carries a namespaced `_tag` of
172
172
  new RpcTimeoutError(rpcName, timeoutMs): RpcTimeoutError;
173
173
  ```
174
174
 
175
- Defined in: [packages/client/src/errors.ts:22](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/errors.ts#L22)
175
+ Defined in: [packages/client/src/errors.ts:22](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/errors.ts#L22)
176
176
 
177
177
  ###### Parameters
178
178
 
@@ -201,19 +201,19 @@ TaggedError("@amqp-contract/RpcTimeoutError", {
201
201
 
202
202
  | Property | Modifier | Type | Inherited from | Defined in |
203
203
  | ------ | ------ | ------ | ------ | ------ |
204
- | <a id="_tag-2"></a> `_tag` | `readonly` | `"@amqp-contract/RpcTimeoutError"` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", })._tag` | node\_modules/.pnpm/unthrown@0.2.0/node\_modules/unthrown/dist/index.d.mts:638 |
204
+ | <a id="_tag-2"></a> `_tag` | `readonly` | `"@amqp-contract/RpcTimeoutError"` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", })._tag` | node\_modules/.pnpm/unthrown@1.0.0/node\_modules/unthrown/dist/index.d.mts:778 |
205
205
  | <a id="cause-2"></a> `cause?` | `public` | `unknown` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).cause` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:24 |
206
206
  | <a id="message-2"></a> `message` | `public` | `string` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).message` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1075 |
207
207
  | <a id="name-2"></a> `name` | `public` | `string` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).name` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1074 |
208
- | <a id="rpcname-1"></a> `rpcName` | `readonly` | `string` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).rpcName` | [packages/client/src/errors.ts:19](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/errors.ts#L19) |
208
+ | <a id="rpcname-1"></a> `rpcName` | `readonly` | `string` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).rpcName` | [packages/client/src/errors.ts:19](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/errors.ts#L19) |
209
209
  | <a id="stack-2"></a> `stack?` | `public` | `string` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).stack` | node\_modules/.pnpm/typescript@6.0.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
210
- | <a id="timeoutms"></a> `timeoutMs` | `readonly` | `number` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).timeoutMs` | [packages/client/src/errors.ts:20](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/errors.ts#L20) |
210
+ | <a id="timeoutms"></a> `timeoutMs` | `readonly` | `number` | `TaggedError("@amqp-contract/RpcTimeoutError", { name: "RpcTimeoutError", }).timeoutMs` | [packages/client/src/errors.ts:20](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/errors.ts#L20) |
211
211
 
212
212
  ***
213
213
 
214
214
  ### TypedAmqpClient
215
215
 
216
- Defined in: [packages/client/src/client.ts:125](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L125)
216
+ Defined in: [packages/client/src/client.ts:125](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L125)
217
217
 
218
218
  Type-safe AMQP client for publishing messages
219
219
 
@@ -238,7 +238,7 @@ call<TName>(
238
238
  | RpcCancelledError>;
239
239
  ```
240
240
 
241
- Defined in: [packages/client/src/client.ts:420](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L420)
241
+ Defined in: [packages/client/src/client.ts:420](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L420)
242
242
 
243
243
  Invoke an RPC defined via `defineRpc` and await the typed response.
244
244
 
@@ -288,7 +288,7 @@ result.match({
288
288
  close(): AsyncResult<void, TechnicalError>;
289
289
  ```
290
290
 
291
- Defined in: [packages/client/src/client.ts:574](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L574)
291
+ Defined in: [packages/client/src/client.ts:574](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L574)
292
292
 
293
293
  Close the channel and connection. Cancels the reply consumer (if any) and
294
294
  rejects every in-flight RPC call with `RpcCancelledError`.
@@ -306,7 +306,7 @@ publish<TName>(
306
306
  options?): AsyncResult<void, TechnicalError | MessageValidationError>;
307
307
  ```
308
308
 
309
- Defined in: [packages/client/src/client.ts:308](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L308)
309
+ Defined in: [packages/client/src/client.ts:308](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L308)
310
310
 
311
311
  Publish a message using a defined publisher.
312
312
 
@@ -340,7 +340,7 @@ value already in options will be overwritten by the compression algorithm.
340
340
  static create<TContract>(__namedParameters): AsyncResult<TypedAmqpClient<TContract>, TechnicalError>;
341
341
  ```
342
342
 
343
- Defined in: [packages/client/src/client.ts:156](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L156)
343
+ Defined in: [packages/client/src/client.ts:156](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L156)
344
344
 
345
345
  Create a type-safe AMQP client from a contract.
346
346
 
@@ -375,7 +375,7 @@ connection options, following RabbitMQ best practices.
375
375
  type CallOptions = object;
376
376
  ```
377
377
 
378
- Defined in: [packages/client/src/client.ts:105](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L105)
378
+ Defined in: [packages/client/src/client.ts:105](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L105)
379
379
 
380
380
  Per-call options for `client.call()`.
381
381
 
@@ -383,8 +383,8 @@ Per-call options for `client.call()`.
383
383
 
384
384
  | Property | Type | Description | Defined in |
385
385
  | ------ | ------ | ------ | ------ |
386
- | <a id="publishoptions"></a> `publishOptions?` | `Omit`&lt;`AmqpClientPublishOptions`, `"replyTo"` \| `"correlationId"`&gt; | Optional AMQP message properties to merge into the request. `replyTo` and `correlationId` are managed by the client and cannot be overridden. | [packages/client/src/client.ts:119](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L119) |
387
- | <a id="timeoutms-1"></a> `timeoutMs` | `number` | Maximum time in ms to wait for an RPC reply. If exceeded, the call resolves to `err(RpcTimeoutError)` and the in-memory correlation entry is cleared. A late reply arriving after the timeout is silently dropped. Required: RPC without a timeout is a footgun. | [packages/client/src/client.ts:113](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L113) |
386
+ | <a id="publishoptions"></a> `publishOptions?` | `Omit`&lt;`AmqpClientPublishOptions`, `"replyTo"` \| `"correlationId"`&gt; | Optional AMQP message properties to merge into the request. `replyTo` and `correlationId` are managed by the client and cannot be overridden. | [packages/client/src/client.ts:119](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L119) |
387
+ | <a id="timeoutms-1"></a> `timeoutMs` | `number` | Maximum time in ms to wait for an RPC reply. If exceeded, the call resolves to `Err(RpcTimeoutError)` and the in-memory correlation entry is cleared. A late reply arriving after the timeout is silently dropped. Required: RPC without a timeout is a footgun. | [packages/client/src/client.ts:113](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L113) |
388
388
 
389
389
  ***
390
390
 
@@ -394,7 +394,7 @@ Per-call options for `client.call()`.
394
394
  type ClientInferPublisherInput<TContract, TName> = PublisherInferInput<InferPublisher<TContract, TName>>;
395
395
  ```
396
396
 
397
- Defined in: [packages/client/src/types.ts:43](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/types.ts#L43)
397
+ Defined in: [packages/client/src/types.ts:43](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/types.ts#L43)
398
398
 
399
399
  Input type accepted by `client.publish(name, ...)` for a specific publisher.
400
400
 
@@ -413,7 +413,7 @@ Input type accepted by `client.publish(name, ...)` for a specific publisher.
413
413
  type ClientInferRpcRequestInput<TContract, TName> = InferRpc<TContract, TName> extends RpcDefinition<infer TRequest, MessageDefinition> ? TRequest extends MessageDefinition ? InferSchemaInput<TRequest["payload"]> : never : never;
414
414
  ```
415
415
 
416
- Defined in: [packages/client/src/types.ts:61](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/types.ts#L61)
416
+ Defined in: [packages/client/src/types.ts:61](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/types.ts#L61)
417
417
 
418
418
  Input type accepted by `client.call(name, request, ...)`.
419
419
 
@@ -432,7 +432,7 @@ Input type accepted by `client.call(name, request, ...)`.
432
432
  type ClientInferRpcResponseOutput<TContract, TName> = InferRpc<TContract, TName> extends RpcDefinition<MessageDefinition, infer TResponse> ? TResponse extends MessageDefinition ? InferSchemaOutput<TResponse["payload"]> : never : never;
433
433
  ```
434
434
 
435
- Defined in: [packages/client/src/types.ts:74](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/types.ts#L74)
435
+ Defined in: [packages/client/src/types.ts:74](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/types.ts#L74)
436
436
 
437
437
  Output (validated) response type returned by `client.call(name, ...)`.
438
438
 
@@ -451,7 +451,7 @@ Output (validated) response type returned by `client.call(name, ...)`.
451
451
  type CreateClientOptions<TContract> = object;
452
452
  ```
453
453
 
454
- Defined in: [packages/client/src/client.ts:76](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L76)
454
+ Defined in: [packages/client/src/client.ts:76](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L76)
455
455
 
456
456
  Options for creating a client
457
457
 
@@ -465,13 +465,13 @@ Options for creating a client
465
465
 
466
466
  | Property | Type | Description | Defined in |
467
467
  | ------ | ------ | ------ | ------ |
468
- | <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | - | [packages/client/src/client.ts:79](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L79) |
469
- | <a id="connecttimeoutms"></a> `connectTimeoutMs?` | `number` \| `null` | Maximum time in ms to wait for the AMQP connection to become ready before `create()` resolves to an `err(TechnicalError)`. Defaults to 30s (the [AmqpClient](https://btravstack.github.io/amqp-contract/api/core#amqpclient)'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to disable the timeout and let amqp-connection-manager retry indefinitely. | [packages/client/src/client.ts:99](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L99) |
470
- | <a id="contract"></a> `contract` | `TContract` | - | [packages/client/src/client.ts:77](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L77) |
471
- | <a id="defaultpublishoptions"></a> `defaultPublishOptions?` | [`PublishOptions`](#publishoptions-1) | Default publish options that will be applied to all publish operations. These can be overridden by options passed to the publish method. By default, persistent is set to true for message durability. | [packages/client/src/client.ts:92](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L92) |
472
- | <a id="logger"></a> `logger?` | `Logger` | - | [packages/client/src/client.ts:80](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L80) |
473
- | <a id="telemetry"></a> `telemetry?` | `TelemetryProvider` | Optional telemetry provider for tracing and metrics. If not provided, uses the default provider which attempts to load OpenTelemetry. OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed. | [packages/client/src/client.ts:86](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L86) |
474
- | <a id="urls"></a> `urls` | `ConnectionUrl`[] | - | [packages/client/src/client.ts:78](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L78) |
468
+ | <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | - | [packages/client/src/client.ts:79](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L79) |
469
+ | <a id="connecttimeoutms"></a> `connectTimeoutMs?` | `number` \| `null` | Maximum time in ms to wait for the AMQP connection to become ready before `create()` resolves to an `Err(TechnicalError)`. Defaults to 30s (the [AmqpClient](https://btravstack.github.io/amqp-contract/api/core#amqpclient)'s `DEFAULT_CONNECT_TIMEOUT_MS`). Pass `null` to disable the timeout and let amqp-connection-manager retry indefinitely. | [packages/client/src/client.ts:99](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L99) |
470
+ | <a id="contract"></a> `contract` | `TContract` | - | [packages/client/src/client.ts:77](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L77) |
471
+ | <a id="defaultpublishoptions"></a> `defaultPublishOptions?` | [`PublishOptions`](#publishoptions-1) | Default publish options that will be applied to all publish operations. These can be overridden by options passed to the publish method. By default, persistent is set to true for message durability. | [packages/client/src/client.ts:92](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L92) |
472
+ | <a id="logger"></a> `logger?` | `Logger` | - | [packages/client/src/client.ts:80](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L80) |
473
+ | <a id="telemetry"></a> `telemetry?` | `TelemetryProvider` | Optional telemetry provider for tracing and metrics. If not provided, uses the default provider which attempts to load OpenTelemetry. OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed. | [packages/client/src/client.ts:86](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L86) |
474
+ | <a id="urls"></a> `urls` | `ConnectionUrl`[] | - | [packages/client/src/client.ts:78](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L78) |
475
475
 
476
476
  ***
477
477
 
@@ -481,7 +481,7 @@ Options for creating a client
481
481
  type PublishOptions = AmqpClientPublishOptions & object;
482
482
  ```
483
483
 
484
- Defined in: [packages/client/src/client.ts:64](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L64)
484
+ Defined in: [packages/client/src/client.ts:64](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L64)
485
485
 
486
486
  Publish options that extend amqp-client's PublishOptions with optional compression support.
487
487
 
@@ -489,4 +489,4 @@ Publish options that extend amqp-client's PublishOptions with optional compressi
489
489
 
490
490
  | Name | Type | Description | Defined in |
491
491
  | ------ | ------ | ------ | ------ |
492
- | `compression?` | `CompressionAlgorithm` | Optional compression algorithm to use for the message payload. When specified, the message will be compressed using the chosen algorithm and the contentEncoding header will be set automatically. | [packages/client/src/client.ts:70](https://github.com/btravstack/amqp-contract/blob/5bdec81d9ab13131c5b7f699c426616cfbed2b5f/packages/client/src/client.ts#L70) |
492
+ | `compression?` | `CompressionAlgorithm` | Optional compression algorithm to use for the message payload. When specified, the message will be compressed using the chosen algorithm and the contentEncoding header will be set automatically. | [packages/client/src/client.ts:70](https://github.com/btravstack/amqp-contract/blob/cbafe3c4c44a5efd57ab3b8ebb53564b509d9d31/packages/client/src/client.ts#L70) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/client",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Client utilities for publishing messages using amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -52,13 +52,13 @@
52
52
  "dependencies": {
53
53
  "@standard-schema/spec": "1.1.0",
54
54
  "ts-pattern": "5.9.0",
55
- "unthrown": "0.2.0",
56
- "@amqp-contract/contract": "1.0.0",
57
- "@amqp-contract/core": "1.0.0"
55
+ "unthrown": "1.0.0",
56
+ "@amqp-contract/contract": "2.0.0",
57
+ "@amqp-contract/core": "2.0.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "24.13.2",
61
- "@unthrown/vitest": "0.2.0",
61
+ "@unthrown/vitest": "1.0.0",
62
62
  "@vitest/coverage-v8": "4.1.9",
63
63
  "amqp-connection-manager": "5.0.0",
64
64
  "amqplib": "2.0.1",
@@ -68,7 +68,7 @@
68
68
  "typescript": "6.0.3",
69
69
  "vitest": "4.1.9",
70
70
  "zod": "4.4.3",
71
- "@amqp-contract/testing": "1.0.0",
71
+ "@amqp-contract/testing": "2.0.0",
72
72
  "@amqp-contract/tsconfig": "0.1.0",
73
73
  "@amqp-contract/typedoc": "0.1.0"
74
74
  },