@btravstack/amqp-worker 0.45.1 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/index.cjs +68 -0
- package/dist/index.d.cts +32 -4
- package/dist/index.d.mts +32 -4
- package/dist/index.mjs +69 -3
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
15
|
pnpm add @btravstack/amqp-worker @btravstack/core @btravstack/config @btravstack/di unthrown \
|
|
16
|
-
@amqp-contract/worker@^3.0.0-beta.
|
|
16
|
+
@amqp-contract/worker@^3.0.0-beta.11 @opentelemetry/api
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
All six are peer dependencies — install them (`@opentelemetry/api` because
|
|
@@ -221,3 +221,13 @@ It keeps private infrastructure hidden and business exports explicit.
|
|
|
221
221
|
## License
|
|
222
222
|
|
|
223
223
|
[MIT](./LICENSE) © Benoit TRAVERS
|
|
224
|
+
|
|
225
|
+
## Broadcast subscriptions
|
|
226
|
+
|
|
227
|
+
`amqpSubscription(name, { contract, settings, inject?, sync })` consumes
|
|
228
|
+
best-effort facts beside an HTTP runtime, with one exclusive ephemeral queue
|
|
229
|
+
per replica. It reports failures and broker health and closes with the scope.
|
|
230
|
+
`InvalidAmqpSubscription` names a refused configuration;
|
|
231
|
+
`SubscriptionHandlers<C, E>` types the contract's handler record.
|
|
232
|
+
See [the subscription reference](https://btravstack.github.io/btravstack/reference/amqp-worker#broadcast-subscriptions-beside-another-runtime)
|
|
233
|
+
for the typed contract, settings port and delivery guarantees.
|
package/dist/index.cjs
CHANGED
|
@@ -327,9 +327,77 @@ const AmqpModule = (name) => (options) => {
|
|
|
327
327
|
});
|
|
328
328
|
};
|
|
329
329
|
//#endregion
|
|
330
|
+
//#region src/subscription.ts
|
|
331
|
+
/** A subscription requires explicitly ephemeral queues with no retry or dead-letter routing. */
|
|
332
|
+
var InvalidAmqpSubscription = class extends (0, unthrown.TaggedError)("InvalidAmqpSubscription") {
|
|
333
|
+
message = "subscriptions require consumers on exclusive auto-delete non-durable classic queues with no retry or dead letter, and no RPCs";
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Consume broadcast facts for the application scope's lifetime, beside its one
|
|
337
|
+
* runtime. Every validated delivery is acknowledged, including handler failures;
|
|
338
|
+
* failures are reported to Observers. Each replica must name its own queues.
|
|
339
|
+
*/
|
|
340
|
+
const amqpSubscription = (name, options) => {
|
|
341
|
+
const resource = (0, _btravstack_di.Port)(`AmqpSubscription:${name}`);
|
|
342
|
+
return (0, _btravstack_di.Module)(`AmqpSubscription:${name}`)({
|
|
343
|
+
provides: [
|
|
344
|
+
_btravstack_core.noObserverMember,
|
|
345
|
+
(0, _btravstack_di.Provider)(resource)({
|
|
346
|
+
inject: {
|
|
347
|
+
settings: options.settings,
|
|
348
|
+
observers: _btravstack_core.Observers,
|
|
349
|
+
...Object.fromEntries(Object.entries(options.inject ?? {}).map(([key, port]) => [`dependency:${key}`, port]))
|
|
350
|
+
},
|
|
351
|
+
acquire: (services) => {
|
|
352
|
+
const consumers = Object.values(options.contract.consumers ?? {});
|
|
353
|
+
if (consumers.length === 0 || Object.keys(options.contract.rpcs ?? {}).length > 0 || consumers.some(({ queue }) => queue.type !== "classic" || queue.durable || queue.exclusive !== true || queue.autoDelete !== true || queue.retry.mode !== "none" || queue.deadLetter !== void 0 || queue.arguments?.["x-dead-letter-exchange"] !== void 0)) return (0, unthrown.ErrAsync)(new InvalidAmqpSubscription({ subscription: name }));
|
|
354
|
+
const dependencies = Object.fromEntries(Object.keys(options.inject ?? {}).map((key) => [key, services[`dependency:${key}`]]));
|
|
355
|
+
const handlers = options.sync(dependencies);
|
|
356
|
+
const observers = services.observers;
|
|
357
|
+
const wrapped = Object.fromEntries(Object.entries(handlers).map(([key, handler]) => [key, (...args) => {
|
|
358
|
+
const settle = (0, _btravstack_core.observe)(observers, {
|
|
359
|
+
component: "amqp",
|
|
360
|
+
name: "subscription",
|
|
361
|
+
attributes: {
|
|
362
|
+
subscription: name,
|
|
363
|
+
consumer: key
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
return (0, unthrown.fromSafePromise)((async () => {
|
|
367
|
+
const result = await (0, unthrown.OkAsync)().flatMap(() => handler(...args) ?? (0, unthrown.Ok)());
|
|
368
|
+
settle(result.isOk() ? { outcome: "ok" } : {
|
|
369
|
+
outcome: "error",
|
|
370
|
+
cause: result.isErr() ? result.error : result.cause
|
|
371
|
+
});
|
|
372
|
+
})());
|
|
373
|
+
}]));
|
|
374
|
+
return _amqp_contract_worker.TypedAmqpWorker.create({
|
|
375
|
+
contract: options.contract,
|
|
376
|
+
handlers: wrapped,
|
|
377
|
+
urls: [services.settings.url],
|
|
378
|
+
connectTimeoutMs: services.settings.connectTimeoutMs ?? 5e3,
|
|
379
|
+
logger: brokerLog(observers)
|
|
380
|
+
});
|
|
381
|
+
},
|
|
382
|
+
release: (worker) => worker.close().get()
|
|
383
|
+
}),
|
|
384
|
+
_btravstack_di.Provider.member(_btravstack_core.HealthChecks)({
|
|
385
|
+
inject: { worker: resource },
|
|
386
|
+
sync: ({ worker }) => ({
|
|
387
|
+
name: `amqp-subscription:${name}`,
|
|
388
|
+
check: () => worker.isConnected() ? (0, unthrown.OkAsync)() : (0, unthrown.ErrAsync)(new _btravstack_core.HealthCheckFailed({ reason: "broker disconnected" }))
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
],
|
|
392
|
+
exports: [_btravstack_core.HealthChecks]
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
//#endregion
|
|
330
396
|
exports.AmqpConfig = AmqpConfig;
|
|
331
397
|
exports.AmqpHandler = AmqpHandler;
|
|
332
398
|
exports.AmqpHandlers = AmqpHandlers;
|
|
333
399
|
exports.AmqpMessage = AmqpMessage;
|
|
334
400
|
exports.AmqpModule = AmqpModule;
|
|
335
401
|
exports.AmqpRuntime = AmqpRuntime;
|
|
402
|
+
exports.InvalidAmqpSubscription = InvalidAmqpSubscription;
|
|
403
|
+
exports.amqpSubscription = amqpSubscription;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ConfigInvalid, EnvReading } from "@btravstack/config";
|
|
2
|
-
import { AnyUnitModule, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
3
|
-
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, ServiceOf } from "@btravstack/di";
|
|
4
|
-
import { ConsumerOptions, TypedAmqpWorker, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
2
|
+
import { AnyUnitModule, HealthChecks, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
3
|
+
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope, ServiceOf } from "@btravstack/di";
|
|
4
|
+
import { ConnectionError, ConsumerOptions, TypedAmqpWorker, WorkerInferConsumerHandler, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
5
|
+
import { AsyncResult, Result } from "unthrown";
|
|
5
6
|
//#region src/handler.d.ts
|
|
6
7
|
/** The consumer and rpc names `C` declares — the keys of its handlers record. */
|
|
7
8
|
type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & string;
|
|
@@ -276,4 +277,31 @@ type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, Handler
|
|
|
276
277
|
*/
|
|
277
278
|
export declare const AmqpModule: <const Name extends string>(name: Name) => <TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, Declared = never, Unit extends AnyUnitModule | undefined = undefined, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, TContract, Unit>, Provides<P, TContract, HandlersError, HandlersNeeds>>[] = [], const N extends readonly AnyPort[] | undefined = undefined>(options: AmqpModuleOptions<TContract, HandlersError, HandlersNeeds, Declared, Unit, I, P, X, N>) => Module<(Extract<(readonly [typeof AmqpRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number], AnyModule> extends (infer T_2) ? T_2 extends Extract<(readonly [typeof AmqpRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number], AnyModule> ? T_2 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [typeof AmqpRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number] extends (infer E_1) ? E_1 extends AnyPort ? InstanceType<E_1> : E_1 extends AnyProvider ? InstanceType<E_1["port"]> : never : never), ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[1] | ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_4 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[1], Exclude<((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[2], ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]> | Exclude<((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[2], ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]>>;
|
|
278
279
|
//#endregion
|
|
279
|
-
|
|
280
|
+
//#region src/subscription.d.ts
|
|
281
|
+
declare const InvalidAmqpSubscription_base: import("unthrown").TaggedErrorConstructor<"InvalidAmqpSubscription">;
|
|
282
|
+
/** A subscription requires explicitly ephemeral queues with no retry or dead-letter routing. */
|
|
283
|
+
export declare class InvalidAmqpSubscription extends InvalidAmqpSubscription_base<{
|
|
284
|
+
readonly subscription: string;
|
|
285
|
+
}> {
|
|
286
|
+
message: string;
|
|
287
|
+
}
|
|
288
|
+
/** Best-effort handlers retain their contract's typed input and may run synchronously. */
|
|
289
|
+
type SubscriptionHandlers<C extends AnyAmqpContract, E> = { readonly [K in C["consumers"] extends Record<string, unknown> ? keyof C["consumers"] : never]: (...args: Parameters<WorkerInferConsumerHandler<C, K>>) => void | Result<void, E> | AsyncResult<void, E>; };
|
|
290
|
+
/**
|
|
291
|
+
* Consume broadcast facts for the application scope's lifetime, beside its one
|
|
292
|
+
* runtime. Every validated delivery is acknowledged, including handler failures;
|
|
293
|
+
* failures are reported to Observers. Each replica must name its own queues.
|
|
294
|
+
*/
|
|
295
|
+
export declare const amqpSubscription: <C extends AnyAmqpContract, Settings extends {
|
|
296
|
+
readonly url: string;
|
|
297
|
+
readonly connectTimeoutMs?: number;
|
|
298
|
+
}, const SettingsId extends string, E, const D extends Readonly<Record<string, AnyPort>> = Record<never, never>>(name: string, options: {
|
|
299
|
+
readonly contract: C;
|
|
300
|
+
readonly settings: PortClassOf<SettingsId, Settings>;
|
|
301
|
+
readonly inject?: D;
|
|
302
|
+
readonly sync: (services: { readonly [K in keyof D]: ServiceOf<InstanceType<D[K]>>; }) => SubscriptionHandlers<C, E>;
|
|
303
|
+
} & ([keyof D] extends [never] ? unknown : {
|
|
304
|
+
readonly inject: D;
|
|
305
|
+
})) => Module<HealthChecks, ConnectionError | InvalidAmqpSubscription, InstanceType<PortClassOf<SettingsId, Settings>> | InstanceType<D[keyof D]> | Scope>;
|
|
306
|
+
//#endregion
|
|
307
|
+
export type { AmqpConnectionOptions, AmqpInfo, AmqpMessageOf, AmqpMessagePortOf, AmqpModuleOptions, AmqpOptions, HandlerPortOf, HandlersInstanceOf, HandlersPortOf, SubscriptionHandlers };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AnyUnitModule, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
2
|
-
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, ServiceOf } from "@btravstack/di";
|
|
3
|
-
import { ConsumerOptions, TypedAmqpWorker, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
1
|
+
import { AnyUnitModule, HealthChecks, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
2
|
+
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope, ServiceOf } from "@btravstack/di";
|
|
3
|
+
import { ConnectionError, ConsumerOptions, TypedAmqpWorker, WorkerInferConsumerHandler, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
4
4
|
import { ConfigInvalid, EnvReading } from "@btravstack/config";
|
|
5
|
+
import { AsyncResult, Result } from "unthrown";
|
|
5
6
|
//#region src/handler.d.ts
|
|
6
7
|
/** The consumer and rpc names `C` declares — the keys of its handlers record. */
|
|
7
8
|
type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & string;
|
|
@@ -276,4 +277,31 @@ type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, Handler
|
|
|
276
277
|
*/
|
|
277
278
|
export declare const AmqpModule: <const Name extends string>(name: Name) => <TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, Declared = never, Unit extends AnyUnitModule | undefined = undefined, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, TContract, Unit>, Provides<P, TContract, HandlersError, HandlersNeeds>>[] = [], const N extends readonly AnyPort[] | undefined = undefined>(options: AmqpModuleOptions<TContract, HandlersError, HandlersNeeds, Declared, Unit, I, P, X, N>) => Module<(Extract<(readonly [typeof AmqpRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number], AnyModule> extends (infer T_2) ? T_2 extends Extract<(readonly [typeof AmqpRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number], AnyModule> ? T_2 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [typeof AmqpRuntime, ...((import("@btravstack/core").HealthChecks extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").HealthChecks : never) | (import("@btravstack/core").Logger extends ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T) ? T extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T extends Module<infer X_1, infer _E, infer _N> ? X_1 : never : never : never) | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 extends Provider<infer X_2, infer _E_1, infer _N_1> ? X_2 : never : never : never) ? typeof import("@btravstack/core").Logger : never))[], ...X])[number] extends (infer E_1) ? E_1 extends AnyPort ? InstanceType<E_1> : E_1 extends AnyProvider ? InstanceType<E_1["port"]> : never : never), ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_3) ? T_3 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_3 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[1] | ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_4 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[1], Exclude<((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[2], ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]> | Exclude<((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[2], ((readonly [...I, AmqpStarter<TContract, Unit>])[number] extends (infer T_6) ? T_6 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_6 extends Module<infer X_3, infer E, infer N_1> ? readonly [X_3, E, N_1] : never : never : never)[0] | ((readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] extends (infer T_5) ? T_5 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_5 extends Provider<infer P_1, infer E_2, infer N_2> ? readonly [P_1, E_2, N_2] : never : never : never)[0]>>;
|
|
278
279
|
//#endregion
|
|
279
|
-
|
|
280
|
+
//#region src/subscription.d.ts
|
|
281
|
+
declare const InvalidAmqpSubscription_base: import("unthrown").TaggedErrorConstructor<"InvalidAmqpSubscription">;
|
|
282
|
+
/** A subscription requires explicitly ephemeral queues with no retry or dead-letter routing. */
|
|
283
|
+
export declare class InvalidAmqpSubscription extends InvalidAmqpSubscription_base<{
|
|
284
|
+
readonly subscription: string;
|
|
285
|
+
}> {
|
|
286
|
+
message: string;
|
|
287
|
+
}
|
|
288
|
+
/** Best-effort handlers retain their contract's typed input and may run synchronously. */
|
|
289
|
+
type SubscriptionHandlers<C extends AnyAmqpContract, E> = { readonly [K in C["consumers"] extends Record<string, unknown> ? keyof C["consumers"] : never]: (...args: Parameters<WorkerInferConsumerHandler<C, K>>) => void | Result<void, E> | AsyncResult<void, E>; };
|
|
290
|
+
/**
|
|
291
|
+
* Consume broadcast facts for the application scope's lifetime, beside its one
|
|
292
|
+
* runtime. Every validated delivery is acknowledged, including handler failures;
|
|
293
|
+
* failures are reported to Observers. Each replica must name its own queues.
|
|
294
|
+
*/
|
|
295
|
+
export declare const amqpSubscription: <C extends AnyAmqpContract, Settings extends {
|
|
296
|
+
readonly url: string;
|
|
297
|
+
readonly connectTimeoutMs?: number;
|
|
298
|
+
}, const SettingsId extends string, E, const D extends Readonly<Record<string, AnyPort>> = Record<never, never>>(name: string, options: {
|
|
299
|
+
readonly contract: C;
|
|
300
|
+
readonly settings: PortClassOf<SettingsId, Settings>;
|
|
301
|
+
readonly inject?: D;
|
|
302
|
+
readonly sync: (services: { readonly [K in keyof D]: ServiceOf<InstanceType<D[K]>>; }) => SubscriptionHandlers<C, E>;
|
|
303
|
+
} & ([keyof D] extends [never] ? unknown : {
|
|
304
|
+
readonly inject: D;
|
|
305
|
+
})) => Module<HealthChecks, ConnectionError | InvalidAmqpSubscription, InstanceType<PortClassOf<SettingsId, Settings>> | InstanceType<D[keyof D]> | Scope>;
|
|
306
|
+
//#endregion
|
|
307
|
+
export type { AmqpConnectionOptions, AmqpInfo, AmqpMessageOf, AmqpMessagePortOf, AmqpModuleOptions, AmqpOptions, HandlerPortOf, HandlersInstanceOf, HandlersPortOf, SubscriptionHandlers };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Observers, RuntimePort, RuntimeStartFailed, composeByPrefix, dispatchUnit, infrastructureExports, mintPiece, noObserverMember, observe, releasedBy, traceIdOfTraceparent, withUnitRecord } from "@btravstack/core";
|
|
1
|
+
import { HealthCheckFailed, HealthChecks, Observers, RuntimePort, RuntimeStartFailed, composeByPrefix, dispatchUnit, infrastructureExports, mintPiece, noObserverMember, observe, releasedBy, traceIdOfTraceparent, withUnitRecord } from "@btravstack/core";
|
|
2
2
|
import { Module, Port, Provider } from "@btravstack/di";
|
|
3
3
|
import { TypedAmqpWorker } from "@amqp-contract/worker";
|
|
4
4
|
import { Config, Env } from "@btravstack/config";
|
|
5
|
-
import { P } from "unthrown";
|
|
5
|
+
import { ErrAsync, Ok, OkAsync, P, TaggedError, fromSafePromise } from "unthrown";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
//#region src/broker-log.ts
|
|
8
8
|
/**
|
|
@@ -326,4 +326,70 @@ const AmqpModule = (name) => (options) => {
|
|
|
326
326
|
});
|
|
327
327
|
};
|
|
328
328
|
//#endregion
|
|
329
|
-
|
|
329
|
+
//#region src/subscription.ts
|
|
330
|
+
/** A subscription requires explicitly ephemeral queues with no retry or dead-letter routing. */
|
|
331
|
+
var InvalidAmqpSubscription = class extends TaggedError("InvalidAmqpSubscription") {
|
|
332
|
+
message = "subscriptions require consumers on exclusive auto-delete non-durable classic queues with no retry or dead letter, and no RPCs";
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Consume broadcast facts for the application scope's lifetime, beside its one
|
|
336
|
+
* runtime. Every validated delivery is acknowledged, including handler failures;
|
|
337
|
+
* failures are reported to Observers. Each replica must name its own queues.
|
|
338
|
+
*/
|
|
339
|
+
const amqpSubscription = (name, options) => {
|
|
340
|
+
const resource = Port(`AmqpSubscription:${name}`);
|
|
341
|
+
return Module(`AmqpSubscription:${name}`)({
|
|
342
|
+
provides: [
|
|
343
|
+
noObserverMember,
|
|
344
|
+
Provider(resource)({
|
|
345
|
+
inject: {
|
|
346
|
+
settings: options.settings,
|
|
347
|
+
observers: Observers,
|
|
348
|
+
...Object.fromEntries(Object.entries(options.inject ?? {}).map(([key, port]) => [`dependency:${key}`, port]))
|
|
349
|
+
},
|
|
350
|
+
acquire: (services) => {
|
|
351
|
+
const consumers = Object.values(options.contract.consumers ?? {});
|
|
352
|
+
if (consumers.length === 0 || Object.keys(options.contract.rpcs ?? {}).length > 0 || consumers.some(({ queue }) => queue.type !== "classic" || queue.durable || queue.exclusive !== true || queue.autoDelete !== true || queue.retry.mode !== "none" || queue.deadLetter !== void 0 || queue.arguments?.["x-dead-letter-exchange"] !== void 0)) return ErrAsync(new InvalidAmqpSubscription({ subscription: name }));
|
|
353
|
+
const dependencies = Object.fromEntries(Object.keys(options.inject ?? {}).map((key) => [key, services[`dependency:${key}`]]));
|
|
354
|
+
const handlers = options.sync(dependencies);
|
|
355
|
+
const observers = services.observers;
|
|
356
|
+
const wrapped = Object.fromEntries(Object.entries(handlers).map(([key, handler]) => [key, (...args) => {
|
|
357
|
+
const settle = observe(observers, {
|
|
358
|
+
component: "amqp",
|
|
359
|
+
name: "subscription",
|
|
360
|
+
attributes: {
|
|
361
|
+
subscription: name,
|
|
362
|
+
consumer: key
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
return fromSafePromise((async () => {
|
|
366
|
+
const result = await OkAsync().flatMap(() => handler(...args) ?? Ok());
|
|
367
|
+
settle(result.isOk() ? { outcome: "ok" } : {
|
|
368
|
+
outcome: "error",
|
|
369
|
+
cause: result.isErr() ? result.error : result.cause
|
|
370
|
+
});
|
|
371
|
+
})());
|
|
372
|
+
}]));
|
|
373
|
+
return TypedAmqpWorker.create({
|
|
374
|
+
contract: options.contract,
|
|
375
|
+
handlers: wrapped,
|
|
376
|
+
urls: [services.settings.url],
|
|
377
|
+
connectTimeoutMs: services.settings.connectTimeoutMs ?? 5e3,
|
|
378
|
+
logger: brokerLog(observers)
|
|
379
|
+
});
|
|
380
|
+
},
|
|
381
|
+
release: (worker) => worker.close().get()
|
|
382
|
+
}),
|
|
383
|
+
Provider.member(HealthChecks)({
|
|
384
|
+
inject: { worker: resource },
|
|
385
|
+
sync: ({ worker }) => ({
|
|
386
|
+
name: `amqp-subscription:${name}`,
|
|
387
|
+
check: () => worker.isConnected() ? OkAsync() : ErrAsync(new HealthCheckFailed({ reason: "broker disconnected" }))
|
|
388
|
+
})
|
|
389
|
+
})
|
|
390
|
+
],
|
|
391
|
+
exports: [HealthChecks]
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
//#endregion
|
|
395
|
+
export { AmqpConfig, AmqpHandler, AmqpHandlers, AmqpMessage, AmqpModule, AmqpRuntime, InvalidAmqpSubscription, amqpSubscription };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/amqp-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "The AMQP starter for @btravstack/core: the handlers as a port, one unit per delivery, and a drain the kernel's deadline releases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"amqp",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
"@amqp-contract/contract": "3.0.0-beta.11",
|
|
48
48
|
"@amqp-contract/testing": "3.0.0-beta.11",
|
|
49
49
|
"@amqp-contract/worker": "3.0.0-beta.11",
|
|
50
|
-
"@btravstack/config": "0.
|
|
51
|
-
"@btravstack/core": "0.
|
|
52
|
-
"@btravstack/di": "0.
|
|
50
|
+
"@btravstack/config": "0.46.0",
|
|
51
|
+
"@btravstack/core": "0.46.0",
|
|
52
|
+
"@btravstack/di": "0.46.0",
|
|
53
53
|
"@btravstack/internal-test-infra": "0.0.0",
|
|
54
|
-
"@btravstack/testing": "0.
|
|
54
|
+
"@btravstack/testing": "0.46.0",
|
|
55
55
|
"@btravstack/tsconfig": "0.4.0",
|
|
56
56
|
"@opentelemetry/api": "1.9.1",
|
|
57
57
|
"@types/node": "26.6.5",
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
"zod": "4.6.7"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@amqp-contract/worker": "^3.0.0-beta.
|
|
69
|
-
"@btravstack/config": "^0.
|
|
70
|
-
"@btravstack/core": "^0.
|
|
71
|
-
"@btravstack/di": "^0.
|
|
68
|
+
"@amqp-contract/worker": "^3.0.0-beta.11",
|
|
69
|
+
"@btravstack/config": "^0.46.0",
|
|
70
|
+
"@btravstack/core": "^0.46.0",
|
|
71
|
+
"@btravstack/di": "^0.46.0",
|
|
72
72
|
"@opentelemetry/api": "^1",
|
|
73
|
-
"unthrown": "^5.
|
|
73
|
+
"unthrown": "^5.11.0"
|
|
74
74
|
},
|
|
75
75
|
"engines": {
|
|
76
76
|
"node": ">=22.22"
|