@btravstack/amqp-worker 0.9.0 → 0.10.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 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 @opentelemetry/api
16
+ @amqp-contract/worker@^3.0.0-beta.7 @opentelemetry/api
17
17
  ```
18
18
 
19
19
  All six are peer dependencies — install them (`@opentelemetry/api` because
@@ -53,7 +53,7 @@ import { ErrAsync, OkAsync, P } from "unthrown";
53
53
  const orderHandlers = AmqpHandlers(orderContract)({
54
54
  inject: { placeOrder: PlaceOrder },
55
55
  sync: ({ placeOrder }) => ({
56
- orderNotifications: (message) =>
56
+ orderNotifications: ({ input: message }) =>
57
57
  placeOrder
58
58
  .execute(
59
59
  TenantId(message.payload.tenantId),
@@ -106,6 +106,10 @@ pieces' ports, not what they close over. Two slices claiming one key are di's
106
106
  duplicate-provider defect at build, which is the point: a consumer belongs to
107
107
  exactly one slice.
108
108
 
109
+ Its own test suite needs a **Docker daemon**: the specs run against the shared
110
+ `rabbitmq` container `internal/test-infra` starts once per machine and every
111
+ workspace reuses. Nothing else here does.
112
+
109
113
  ## Options
110
114
 
111
115
  `AmqpModule(name)({...})` takes `amqp()`'s options plus `handlers` and the
package/dist/index.cjs CHANGED
@@ -34,7 +34,17 @@ const AmqpHandler = (contract, key) => {
34
34
  * handler that must stop when the kernel stops waiting reads
35
35
  * `currentUnit()?.signal`.
36
36
  */
37
- const messageUnits = (host) => (args, next) => host.run(metaFor(args.rawMessage), () => next());
37
+ const messageUnits = (host, observers) => (args, next) => {
38
+ const settle = (0, _btravstack_core.observe)(observers, {
39
+ component: "amqp",
40
+ name: "delivery",
41
+ attributes: { handler: args.handlerName }
42
+ });
43
+ return host.run(metaFor(args.rawMessage), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
44
+ outcome: "error",
45
+ cause: failure.tag === "Err" ? failure.error : failure.cause
46
+ }));
47
+ };
38
48
  /**
39
49
  * `UnitMeta.id` must be unique per unit, and a **delivery tag is not one**: tags
40
50
  * are per-channel and restart at `1` after a reconnect, which
@@ -115,17 +125,25 @@ const amqp = (options) => {
115
125
  }));
116
126
  return (0, _btravstack_di.Module)("Amqp")({
117
127
  needs: [_btravstack_config.Env, AmqpHandlersPort],
118
- provides: [config, (0, _btravstack_di.Provider)(AmqpRuntime)({
119
- inject: {
120
- config: AmqpConfig,
121
- handlers: AmqpHandlersPort
122
- },
123
- sync: ({ config: bound, handlers }) => ({
124
- name: "amqp",
125
- resolves: [],
126
- start: (host) => createWorker(host, bound, options, handlers)
128
+ provides: [
129
+ config,
130
+ _btravstack_di.Provider.member(_btravstack_core.Observers)({
131
+ inject: {},
132
+ value: _btravstack_core.noObserver
133
+ }),
134
+ (0, _btravstack_di.Provider)(AmqpRuntime)({
135
+ inject: {
136
+ config: AmqpConfig,
137
+ handlers: AmqpHandlersPort,
138
+ observers: _btravstack_core.Observers
139
+ },
140
+ sync: ({ config: bound, handlers, observers }) => ({
141
+ name: "amqp",
142
+ resolves: [],
143
+ start: (host) => createWorker(host, bound, options, handlers, observers)
144
+ })
127
145
  })
128
- })],
146
+ ],
129
147
  exports: [AmqpRuntime, AmqpConfig]
130
148
  });
131
149
  };
@@ -165,15 +183,15 @@ const queuesOf = (contract) => [...new Set(Object.values({
165
183
  ...contract.consumers,
166
184
  ...contract.rpcs
167
185
  }).map((entry) => entry.queue.name))].sort();
168
- const createWorker = (host, config, options, handlers) => _amqp_contract_worker.TypedAmqpWorker.create({
186
+ const createWorker = (host, config, options, handlers, observers) => _amqp_contract_worker.TypedAmqpWorker.create({
169
187
  contract: options.contract,
170
188
  handlers,
171
- middleware: messageUnits(host),
189
+ middleware: messageUnits(host, observers),
172
190
  urls: [config.url],
173
191
  ...options.connectionOptions === void 0 ? {} : { connectionOptions: options.connectionOptions },
174
192
  ...options.defaultConsumerOptions === void 0 ? {} : { defaultConsumerOptions: options.defaultConsumerOptions },
175
193
  connectTimeoutMs: config.connectTimeoutMs
176
- }).map((worker) => consume(worker, queuesOf(options.contract))).recoverDefect((cause) => (0, unthrown.ErrAsync)(startFailed(cause)));
194
+ }).map((worker) => consume(worker, queuesOf(options.contract))).mapErrCases((matcher) => matcher.with(unthrown.P.tag("@amqp-contract/ConnectionError"), (error) => startFailed(error)));
177
195
  const consume = (worker, queues) => {
178
196
  let closing;
179
197
  const beginClose = () => closing ??= worker.close({ drainTimeoutMs: null });
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Module, Port, Provider } from "@btravstack/di";
2
2
  import { TypedAmqpWorker } from "@amqp-contract/worker";
3
3
  import { Config, Env } from "@btravstack/config";
4
- import { RuntimePort, RuntimeStartFailed, releasedBy } from "@btravstack/core";
5
- import { ErrAsync } from "unthrown";
4
+ import { Observers, RuntimePort, RuntimeStartFailed, noObserver, observe, releasedBy } from "@btravstack/core";
5
+ import { P } from "unthrown";
6
6
  import { randomUUID } from "node:crypto";
7
7
  //#region src/handler.ts
8
8
  /** The prefix a piece's port id carries; the composing form strips it to recover the key. */
@@ -33,7 +33,17 @@ const AmqpHandler = (contract, key) => {
33
33
  * handler that must stop when the kernel stops waiting reads
34
34
  * `currentUnit()?.signal`.
35
35
  */
36
- const messageUnits = (host) => (args, next) => host.run(metaFor(args.rawMessage), () => next());
36
+ const messageUnits = (host, observers) => (args, next) => {
37
+ const settle = observe(observers, {
38
+ component: "amqp",
39
+ name: "delivery",
40
+ attributes: { handler: args.handlerName }
41
+ });
42
+ return host.run(metaFor(args.rawMessage), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
43
+ outcome: "error",
44
+ cause: failure.tag === "Err" ? failure.error : failure.cause
45
+ }));
46
+ };
37
47
  /**
38
48
  * `UnitMeta.id` must be unique per unit, and a **delivery tag is not one**: tags
39
49
  * are per-channel and restart at `1` after a reconnect, which
@@ -114,17 +124,25 @@ const amqp = (options) => {
114
124
  }));
115
125
  return Module("Amqp")({
116
126
  needs: [Env, AmqpHandlersPort],
117
- provides: [config, Provider(AmqpRuntime)({
118
- inject: {
119
- config: AmqpConfig,
120
- handlers: AmqpHandlersPort
121
- },
122
- sync: ({ config: bound, handlers }) => ({
123
- name: "amqp",
124
- resolves: [],
125
- start: (host) => createWorker(host, bound, options, handlers)
127
+ provides: [
128
+ config,
129
+ Provider.member(Observers)({
130
+ inject: {},
131
+ value: noObserver
132
+ }),
133
+ Provider(AmqpRuntime)({
134
+ inject: {
135
+ config: AmqpConfig,
136
+ handlers: AmqpHandlersPort,
137
+ observers: Observers
138
+ },
139
+ sync: ({ config: bound, handlers, observers }) => ({
140
+ name: "amqp",
141
+ resolves: [],
142
+ start: (host) => createWorker(host, bound, options, handlers, observers)
143
+ })
126
144
  })
127
- })],
145
+ ],
128
146
  exports: [AmqpRuntime, AmqpConfig]
129
147
  });
130
148
  };
@@ -164,15 +182,15 @@ const queuesOf = (contract) => [...new Set(Object.values({
164
182
  ...contract.consumers,
165
183
  ...contract.rpcs
166
184
  }).map((entry) => entry.queue.name))].sort();
167
- const createWorker = (host, config, options, handlers) => TypedAmqpWorker.create({
185
+ const createWorker = (host, config, options, handlers, observers) => TypedAmqpWorker.create({
168
186
  contract: options.contract,
169
187
  handlers,
170
- middleware: messageUnits(host),
188
+ middleware: messageUnits(host, observers),
171
189
  urls: [config.url],
172
190
  ...options.connectionOptions === void 0 ? {} : { connectionOptions: options.connectionOptions },
173
191
  ...options.defaultConsumerOptions === void 0 ? {} : { defaultConsumerOptions: options.defaultConsumerOptions },
174
192
  connectTimeoutMs: config.connectTimeoutMs
175
- }).map((worker) => consume(worker, queuesOf(options.contract))).recoverDefect((cause) => ErrAsync(startFailed(cause)));
193
+ }).map((worker) => consume(worker, queuesOf(options.contract))).mapErrCases((matcher) => matcher.with(P.tag("@amqp-contract/ConnectionError"), (error) => startFailed(error)));
176
194
  const consume = (worker, queues) => {
177
195
  let closing;
178
196
  const beginClose = () => closing ??= worker.close({ drainTimeoutMs: null });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/amqp-worker",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",
@@ -44,9 +44,9 @@
44
44
  "./package.json": "./package.json"
45
45
  },
46
46
  "devDependencies": {
47
- "@amqp-contract/contract": "3.0.0-beta.6",
48
- "@amqp-contract/testing": "3.0.0-beta.6",
49
- "@amqp-contract/worker": "3.0.0-beta.6",
47
+ "@amqp-contract/contract": "3.0.0-beta.7",
48
+ "@amqp-contract/testing": "3.0.0-beta.7",
49
+ "@amqp-contract/worker": "3.0.0-beta.7",
50
50
  "@btravstack/tsconfig": "0.2.0",
51
51
  "@opentelemetry/api": "1.9.1",
52
52
  "@types/node": "26.4.0",
@@ -56,20 +56,20 @@
56
56
  "typescript": "7.0.2",
57
57
  "unthrown": "5.7.0",
58
58
  "vitest": "4.1.11",
59
- "zod": "4.5.1",
60
- "@btravstack/config": "0.9.0",
61
- "@btravstack/core": "0.9.0",
62
- "@btravstack/di": "0.9.0",
59
+ "zod": "4.5.3",
60
+ "@btravstack/config": "0.10.0",
61
+ "@btravstack/core": "0.10.0",
62
+ "@btravstack/di": "0.10.0",
63
63
  "@btravstack/internal-test-infra": "0.0.0",
64
- "@btravstack/testing": "0.9.0"
64
+ "@btravstack/testing": "0.10.0"
65
65
  },
66
66
  "peerDependencies": {
67
- "@amqp-contract/worker": "^3.0.0-beta.6",
67
+ "@amqp-contract/worker": "^3.0.0-beta.7",
68
68
  "@opentelemetry/api": "^1",
69
69
  "unthrown": "^5.0.0",
70
- "@btravstack/config": "^0.9.0",
71
- "@btravstack/core": "^0.9.0",
72
- "@btravstack/di": "^0.9.0"
70
+ "@btravstack/config": "^0.10.0",
71
+ "@btravstack/core": "^0.10.0",
72
+ "@btravstack/di": "^0.10.0"
73
73
  },
74
74
  "engines": {
75
75
  "node": ">=22"