@btravstack/amqp-worker 0.11.0 → 0.13.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 +12 -1
- package/dist/index.cjs +108 -26
- package/dist/index.d.cts +155 -22
- package/dist/index.d.mts +155 -22
- package/dist/index.mjs +108 -27
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ call, not on the first delivery. `runtimeInfo()` reads `{ queues }` back once
|
|
|
156
156
|
consuming.
|
|
157
157
|
|
|
158
158
|
A worker with several consumers can be several slices instead of one record:
|
|
159
|
-
`AmqpHandler(contract, key)({ inject: { name: Dep },
|
|
159
|
+
`AmqpHandler(contract, key)({ inject: { name: Dep }, unit, sync })` mints a provider for ONE consumer or
|
|
160
160
|
rpc, typed by the key alone, and `AmqpHandlers(contract)([...])` composes an
|
|
161
161
|
array of them into the same handlers provider `AmqpModule` takes — the array
|
|
162
162
|
must cover every key the contract declares, and each piece's own port must
|
|
@@ -167,6 +167,16 @@ two slices claiming one key type-check together. It is di's duplicate-provider
|
|
|
167
167
|
defect at build once **both** pieces are discharged as providers in one graph
|
|
168
168
|
— wire in only one and the other's handler is silently never registered.
|
|
169
169
|
|
|
170
|
+
A piece's `unit` names the ports its handler reads off `context.unit`, resolved
|
|
171
|
+
out of the per-delivery fork of the module bound on `unit.message` — which is
|
|
172
|
+
seeded with the validated message on `AmqpMessage(contract)`, so a unit module
|
|
173
|
+
derives a tenant from the delivery rather than from an ambient record. The
|
|
174
|
+
record arm above takes the same `unit:` — one record for every entry in it — so
|
|
175
|
+
a worker that has not outgrown one function reaches `context.unit` without
|
|
176
|
+
slicing first. The root is checked against what was declared either way: bind a
|
|
177
|
+
module that does not export one of those ports and `AmqpModule` refuses it,
|
|
178
|
+
naming the port.
|
|
179
|
+
|
|
170
180
|
Its own test suite needs a **Docker daemon**: the specs run against the shared
|
|
171
181
|
`rabbitmq` container `internal/test-infra` starts once per machine and every
|
|
172
182
|
workspace reuses. Nothing else here does.
|
|
@@ -186,6 +196,7 @@ provider on its own port, which is how the composition root supplies it:
|
|
|
186
196
|
| `connectionOptions` | `AmqpConnectionOptions`, the library's own connection tuning: heartbeat, reconnect interval, `findServers`, TLS |
|
|
187
197
|
| `defaultConsumerOptions` | the library's `ConsumerOptions`, applied to every handler: `prefetch` (the throughput knob), `priority`, … |
|
|
188
198
|
| `connectTimeoutMs` | pins `AMQP_CONNECT_TIMEOUT_MS` — how long startup waits before an unreachable broker is a `RuntimeStartFailed` (default `5000`) |
|
|
199
|
+
| `unit` | `{ message }` — the module the worker forks per delivery, seeded with the message on `AmqpMessage(contract)` |
|
|
189
200
|
|
|
190
201
|
The full table — required/optional, defaults, and the reasoning — lives on
|
|
191
202
|
[the reference page](https://btravstack.github.io/btravstack/reference/amqp-worker),
|
package/dist/index.cjs
CHANGED
|
@@ -5,7 +5,73 @@ let _btravstack_config = require("@btravstack/config");
|
|
|
5
5
|
let _btravstack_core = require("@btravstack/core");
|
|
6
6
|
let unthrown = require("unthrown");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
|
+
//#region src/unit.ts
|
|
9
|
+
/**
|
|
10
|
+
* Where `messageUnits` leaves the forked context for the piece wrappers to read.
|
|
11
|
+
* A symbol rather than a name: the dispatcher merges it into the context every
|
|
12
|
+
* handler sees, and a string key would appear on the record a handler written
|
|
13
|
+
* against the `{ inject, sync }` arm destructures.
|
|
14
|
+
*/
|
|
15
|
+
const UNIT_SCOPE = Symbol("@btravstack/amqp-worker/unit-scope");
|
|
16
|
+
/**
|
|
17
|
+
* The declared record, as a getter per name resolved on read out of the fork.
|
|
18
|
+
* Neither writable nor configurable — a handler reads what the fork holds, and
|
|
19
|
+
* cannot reshape the record under the next delivery. With no `unit.message`
|
|
20
|
+
* bound there is nothing to resolve from, so the record is empty whatever was
|
|
21
|
+
* declared.
|
|
22
|
+
*/
|
|
23
|
+
const unitRecordOf = (forked, record) => {
|
|
24
|
+
const unit = {};
|
|
25
|
+
if (forked === void 0) return unit;
|
|
26
|
+
for (const [name, port] of Object.entries(record)) Object.defineProperty(unit, name, {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
get: () => forked.get(port)
|
|
29
|
+
});
|
|
30
|
+
return unit;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* One handler entry — a function, or `[handler, options]` — with its piece's
|
|
34
|
+
* declared record put on `context.unit`. Applied once per piece as di
|
|
35
|
+
* constructs it, so a delivery costs one record and one context object.
|
|
36
|
+
*/
|
|
37
|
+
const withUnit = (record, entry) => {
|
|
38
|
+
const wrap = (handler) => (helpers, message) => {
|
|
39
|
+
const { [UNIT_SCOPE]: forked, ...rest } = helpers.context ?? {};
|
|
40
|
+
return handler({
|
|
41
|
+
...helpers,
|
|
42
|
+
context: {
|
|
43
|
+
...rest,
|
|
44
|
+
unit: unitRecordOf(forked, record)
|
|
45
|
+
}
|
|
46
|
+
}, message);
|
|
47
|
+
};
|
|
48
|
+
return Array.isArray(entry) ? [wrap(entry[0]), entry[1]] : wrap(entry);
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
8
51
|
//#region src/handler.ts
|
|
52
|
+
/** One id, declared once, so no contract instantiating it warns about a duplicate. */
|
|
53
|
+
const AmqpMessagePort = (0, _btravstack_di.Port)("AmqpMessage");
|
|
54
|
+
/**
|
|
55
|
+
* The validated delivery as a port: the one thing the worker seeds the fork
|
|
56
|
+
* with, so a `unit.message` module derives a tenant — or anything else — from
|
|
57
|
+
* the message rather than from an ambient record.
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* const Message = AmqpMessage(orderContract);
|
|
61
|
+
* const MessageUnit = Module("MessageUnit")({
|
|
62
|
+
* needs: [Message],
|
|
63
|
+
* provides: [Provider(Tenant)({ inject: { message: Message }, sync: ({ message }) => message.payload.tenantId })],
|
|
64
|
+
* exports: [Tenant],
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* `contract` is read for its TYPE only. One `Port(...)` call fixed per contract
|
|
69
|
+
* at the type level, the move `AmqpHandlersPort` makes, so a module built for
|
|
70
|
+
* one contract cannot read another's message.
|
|
71
|
+
*/
|
|
72
|
+
const AmqpMessage = (contract) => {
|
|
73
|
+
return AmqpMessagePort;
|
|
74
|
+
};
|
|
9
75
|
/** The prefix a piece's port id carries; the composing form strips it to recover the key. */
|
|
10
76
|
const HANDLER_PREFIX = "AmqpHandler:";
|
|
11
77
|
/**
|
|
@@ -17,30 +83,49 @@ const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
17
83
|
* so a consumer the contract does not declare is a compile error here.
|
|
18
84
|
*
|
|
19
85
|
* There is no name to give: the contract key IS the port's name.
|
|
86
|
+
*
|
|
87
|
+
* `unit` declares the ports the handler reads off `context.unit`, resolved out
|
|
88
|
+
* of the fork the delivery opened; the root's `unit.message` module must export
|
|
89
|
+
* every one of them.
|
|
20
90
|
*/
|
|
21
91
|
const AmqpHandler = (contract, key) => {
|
|
22
92
|
const port = class extends (0, _btravstack_di.Port)(`${HANDLER_PREFIX}${key}`) {};
|
|
23
|
-
return (
|
|
93
|
+
return (options) => {
|
|
94
|
+
const record = options.unit ?? {};
|
|
95
|
+
return Object.assign((0, _btravstack_di.Provider)(port)({
|
|
96
|
+
inject: options.inject,
|
|
97
|
+
sync: (services) => withUnit(record, options.sync(services))
|
|
98
|
+
}), { unit: record });
|
|
99
|
+
};
|
|
24
100
|
};
|
|
25
101
|
//#endregion
|
|
26
102
|
//#region src/message-units.ts
|
|
27
103
|
/**
|
|
28
|
-
* Open one kernel unit per delivery
|
|
29
|
-
* the
|
|
30
|
-
* unit
|
|
104
|
+
* Open one kernel unit per delivery, forking `unit` — when one is bound — after
|
|
105
|
+
* the message is validated, before the handler runs; the fork is torn down
|
|
106
|
+
* when the unit closes. It is seeded with the validated delivery on
|
|
107
|
+
* `AmqpMessage(contract)`, so a unit module derives a tenant from the message
|
|
108
|
+
* rather than reading one off an ambient record. With no `unit` bound, `next()`
|
|
109
|
+
* runs unchanged, so the ambient `currentUnit()` record is what the unit leaves
|
|
110
|
+
* for the adapters that read it.
|
|
111
|
+
*
|
|
112
|
+
* The forked context rides the dispatcher's context under {@link UNIT_SCOPE},
|
|
113
|
+
* where each piece's own wrapper turns it into the typed `context.unit` record
|
|
114
|
+
* that piece declared — the records live with the pieces, so a hand-composed
|
|
115
|
+
* `amqp()` gets them without threading a second option through the starter.
|
|
31
116
|
*
|
|
32
117
|
* **The kernel's per-unit `AbortSignal` rides that record too**, and it is the
|
|
33
118
|
* only route to it here: a handler has no parameter to receive one through. A
|
|
34
119
|
* handler that must stop when the kernel stops waiting reads
|
|
35
120
|
* `currentUnit()?.signal`.
|
|
36
121
|
*/
|
|
37
|
-
const messageUnits = (host, observers) => (args, next) => {
|
|
122
|
+
const messageUnits = (host, observers, unit) => (args, next) => {
|
|
38
123
|
const settle = (0, _btravstack_core.observe)(observers, {
|
|
39
124
|
component: "amqp",
|
|
40
125
|
name: "delivery",
|
|
41
126
|
attributes: { handler: args.handlerName }
|
|
42
127
|
});
|
|
43
|
-
return host.run(metaFor(args.rawMessage), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
|
|
128
|
+
return host.run(metaFor(args.rawMessage), (scope) => unit === void 0 ? next() : scope.fork(unit, [[AmqpMessagePort, args.message]]).flatMap((forked) => next({ context: { [UNIT_SCOPE]: forked } }))).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
|
|
44
129
|
outcome: "error",
|
|
45
130
|
cause: failure.tag === "Err" ? failure.error : failure.cause
|
|
46
131
|
}));
|
|
@@ -62,7 +147,7 @@ const messageUnits = (host, observers) => (args, next) => {
|
|
|
62
147
|
const metaFor = (raw) => {
|
|
63
148
|
const id = (0, node_crypto.randomUUID)();
|
|
64
149
|
const parent = raw.properties.headers?.["traceparent"];
|
|
65
|
-
const fromParent = typeof parent === "string" ? traceIdOfTraceparent(parent) : void 0;
|
|
150
|
+
const fromParent = typeof parent === "string" ? (0, _btravstack_core.traceIdOfTraceparent)(parent) : void 0;
|
|
66
151
|
const inbound = [raw.properties.messageId, raw.properties.correlationId].map((value) => value?.trim() ?? "").find((value) => value !== "");
|
|
67
152
|
return {
|
|
68
153
|
kind: "delivery",
|
|
@@ -70,17 +155,6 @@ const metaFor = (raw) => {
|
|
|
70
155
|
traceId: fromParent ?? inbound ?? id
|
|
71
156
|
};
|
|
72
157
|
};
|
|
73
|
-
/**
|
|
74
|
-
* The trace id inside a W3C `traceparent` header, which outranks `messageId`
|
|
75
|
-
* because it is the one value minted to span processes. Only the trace-id field
|
|
76
|
-
* is taken: `traceId` is a correlation id, not a span context, so the parent's
|
|
77
|
-
* span id is dropped rather than half-carried. The all-zero id is the spec's own
|
|
78
|
-
* "invalid" and is refused like a malformed header.
|
|
79
|
-
*/
|
|
80
|
-
const traceIdOfTraceparent = (header) => {
|
|
81
|
-
const traceId = /^[\da-f]{2}-([\da-f]{32})-[\da-f]{16}-[\da-f]{2}$/.exec(header.trim())?.[1];
|
|
82
|
-
return traceId === void 0 || /^0{32}$/.test(traceId) ? void 0 : traceId;
|
|
83
|
-
};
|
|
84
158
|
//#endregion
|
|
85
159
|
//#region src/amqp-runtime.ts
|
|
86
160
|
/**
|
|
@@ -155,12 +229,12 @@ const amqp = (options) => {
|
|
|
155
229
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
156
230
|
* ```
|
|
157
231
|
*
|
|
158
|
-
* The first is
|
|
159
|
-
*
|
|
160
|
-
* key)` builds: they are the provider's deps,
|
|
161
|
-
* piece's port id carries, so the services
|
|
162
|
-
* declared key must be covered, and two
|
|
163
|
-
* duplicate-provider defect at build.
|
|
232
|
+
* The first is `{ inject, unit?, sync }`, whose `sync` hands back the whole
|
|
233
|
+
* handlers record — one `unit:` record for every entry in it. The second takes
|
|
234
|
+
* the pieces `AmqpHandler(contract, key)` builds: they are the provider's deps,
|
|
235
|
+
* keyed by the contract key each piece's port id carries, so the services
|
|
236
|
+
* record IS the handlers record. Every declared key must be covered, and two
|
|
237
|
+
* slices claiming one key are di's duplicate-provider defect at build.
|
|
164
238
|
*/
|
|
165
239
|
const AmqpHandlers = (contract) => {
|
|
166
240
|
const build = (0, _btravstack_di.Provider)(AmqpHandlersPort);
|
|
@@ -168,7 +242,14 @@ const AmqpHandlers = (contract) => {
|
|
|
168
242
|
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(12), piece.port])),
|
|
169
243
|
sync: (services) => services
|
|
170
244
|
});
|
|
171
|
-
|
|
245
|
+
const whole = (options) => {
|
|
246
|
+
const record = options.unit ?? {};
|
|
247
|
+
return build({
|
|
248
|
+
inject: options.inject,
|
|
249
|
+
sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
return ((first) => Array.isArray(first) ? compose(first) : whole(first));
|
|
172
253
|
};
|
|
173
254
|
const startFailed = (cause) => new _btravstack_core.RuntimeStartFailed({
|
|
174
255
|
runtime: "amqp",
|
|
@@ -186,7 +267,7 @@ const queuesOf = (contract) => [...new Set(Object.values({
|
|
|
186
267
|
const createWorker = (host, config, options, handlers, observers) => _amqp_contract_worker.TypedAmqpWorker.create({
|
|
187
268
|
contract: options.contract,
|
|
188
269
|
handlers,
|
|
189
|
-
middleware: messageUnits(host, observers),
|
|
270
|
+
middleware: messageUnits(host, observers, options.unit?.message),
|
|
190
271
|
urls: [config.url],
|
|
191
272
|
...options.connectionOptions === void 0 ? {} : { connectionOptions: options.connectionOptions },
|
|
192
273
|
...options.defaultConsumerOptions === void 0 ? {} : { defaultConsumerOptions: options.defaultConsumerOptions },
|
|
@@ -241,6 +322,7 @@ const AmqpModule = (name) => (options) => {
|
|
|
241
322
|
exports.AmqpConfig = AmqpConfig;
|
|
242
323
|
exports.AmqpHandler = AmqpHandler;
|
|
243
324
|
exports.AmqpHandlers = AmqpHandlers;
|
|
325
|
+
exports.AmqpMessage = AmqpMessage;
|
|
244
326
|
exports.AmqpModule = AmqpModule;
|
|
245
327
|
exports.AmqpRuntime = AmqpRuntime;
|
|
246
328
|
exports.amqp = amqp;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,59 @@
|
|
|
1
1
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
|
2
|
-
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider } from "@btravstack/di";
|
|
2
|
+
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope, ServiceOf } from "@btravstack/di";
|
|
3
3
|
import { ConsumerOptions, TypedAmqpWorker, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
4
4
|
import { Runtime, RuntimePort } from "@btravstack/core";
|
|
5
|
+
//#region src/unit.d.ts
|
|
6
|
+
/** What a bound unit module exports — the port instances a handler of that kind may read. */
|
|
7
|
+
type UnitExportsOf<M> = M extends Module<infer X, never, unknown> ? X : never;
|
|
8
|
+
/** The declared `unit:` record, as the services a handler reads off `context.unit`. */
|
|
9
|
+
type UnitRecordOf<U extends Readonly<Record<string, AnyPort>>> = { readonly [N in keyof U]: ServiceOf<InstanceType<U[N]>>; };
|
|
10
|
+
/**
|
|
11
|
+
* A piece injecting a port the bound `unit.message` module does not export.
|
|
12
|
+
* `unknown` when every declared port is covered — including the empty case, so
|
|
13
|
+
* a worker whose pieces declare no `unit:` is gated on nothing.
|
|
14
|
+
*/
|
|
15
|
+
type UnitGate<Unit, Declared> = [Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>] extends [never] ? unknown : {
|
|
16
|
+
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export": Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
5
19
|
//#region src/handler.d.ts
|
|
6
20
|
/** The consumer and rpc names `C` declares — the keys of its handlers record. */
|
|
7
21
|
type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & string;
|
|
22
|
+
/**
|
|
23
|
+
* The validated delivery, typed by the contract: the union of every consumer's
|
|
24
|
+
* and rpc's own consumed message. Reached through the handler's own second
|
|
25
|
+
* parameter, since `@amqp-contract/worker` exports
|
|
26
|
+
* `WorkerInferConsumedMessage` but not the `InferConsumerNames` constraint it
|
|
27
|
+
* takes — the same by-index route {@link AnyAmqpContract} travels.
|
|
28
|
+
*/
|
|
29
|
+
type AmqpMessageOf<C extends AnyAmqpContract> = Parameters<Extract<WorkerInferHandlers<C>[HandlerKeyOf<C>], (...args: never) => unknown>>[1];
|
|
30
|
+
/** The seeded port's class, typed for `C`: what `AmqpMessage(contract)` answers. */
|
|
31
|
+
type AmqpMessagePortOf<C extends AnyAmqpContract> = PortClassOf<"AmqpMessage", AmqpMessageOf<C>>;
|
|
32
|
+
/**
|
|
33
|
+
* The seed's port instance, as it appears in a needs union — subtracted from
|
|
34
|
+
* what a bound `unit.message` module still owes, since the fork is what
|
|
35
|
+
* discharges it.
|
|
36
|
+
*/
|
|
37
|
+
type AmqpMessageInstance = PortInstance<"AmqpMessage", unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* The validated delivery as a port: the one thing the worker seeds the fork
|
|
40
|
+
* with, so a `unit.message` module derives a tenant — or anything else — from
|
|
41
|
+
* the message rather than from an ambient record.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* const Message = AmqpMessage(orderContract);
|
|
45
|
+
* const MessageUnit = Module("MessageUnit")({
|
|
46
|
+
* needs: [Message],
|
|
47
|
+
* provides: [Provider(Tenant)({ inject: { message: Message }, sync: ({ message }) => message.payload.tenantId })],
|
|
48
|
+
* exports: [Tenant],
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* `contract` is read for its TYPE only. One `Port(...)` call fixed per contract
|
|
53
|
+
* at the type level, the move `AmqpHandlersPort` makes, so a module built for
|
|
54
|
+
* one contract cannot read another's message.
|
|
55
|
+
*/
|
|
56
|
+
declare const AmqpMessage: <C extends AnyAmqpContract>(contract: C) => AmqpMessagePortOf<C>;
|
|
8
57
|
/**
|
|
9
58
|
* The port one piece targets. Its id carries the contract key, which is what
|
|
10
59
|
* makes two slices claiming one consumer di's duplicate-provider defect rather
|
|
@@ -13,6 +62,14 @@ type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & st
|
|
|
13
62
|
type HandlerPortOf<C extends AnyAmqpContract, K extends HandlerKeyOf<C>> = PortClassOf<`${typeof HANDLER_PREFIX}${K}`, WorkerInferHandlers<C>[K]>;
|
|
14
63
|
/** The prefix a piece's port id carries; the composing form strips it to recover the key. */
|
|
15
64
|
declare const HANDLER_PREFIX = "AmqpHandler:";
|
|
65
|
+
/** What a minted piece returns. */
|
|
66
|
+
type MintedHandler<C extends AnyAmqpContract, K extends HandlerKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>> = Provider<InstanceType<HandlerPortOf<C, K>>, never, N> & {
|
|
67
|
+
readonly port: HandlerPortOf<C, K>;
|
|
68
|
+
/** The declared `unit:` record, which `AmqpHandlers`'s array arm reads back off the piece. */
|
|
69
|
+
readonly unit: U;
|
|
70
|
+
/** Phantom: the ports this piece injects, which the root's `unit.message` must export. */
|
|
71
|
+
readonly _declared?: InstanceType<U[keyof U]>;
|
|
72
|
+
};
|
|
16
73
|
/**
|
|
17
74
|
* One consumer or rpc of a contract, as a provider on a port of its own.
|
|
18
75
|
*
|
|
@@ -22,8 +79,21 @@ declare const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
22
79
|
* so a consumer the contract does not declare is a compile error here.
|
|
23
80
|
*
|
|
24
81
|
* There is no name to give: the contract key IS the port's name.
|
|
82
|
+
*
|
|
83
|
+
* `unit` declares the ports the handler reads off `context.unit`, resolved out
|
|
84
|
+
* of the fork the delivery opened; the root's `unit.message` module must export
|
|
85
|
+
* every one of them.
|
|
25
86
|
*/
|
|
26
|
-
declare const AmqpHandler: <C extends AnyAmqpContract, const K extends HandlerKeyOf<C>>(contract: C, key: K) =>
|
|
87
|
+
declare const AmqpHandler: <C extends AnyAmqpContract, const K extends HandlerKeyOf<C>>(contract: C, key: K) => <const D extends Readonly<Record<string, AnyPort>>, const U extends Readonly<Record<string, AnyPort>> = Record<never, never>>(options: {
|
|
88
|
+
readonly inject: D;
|
|
89
|
+
/** The unit-scoped ports this handler reads off `context.unit`. */
|
|
90
|
+
readonly unit?: U;
|
|
91
|
+
readonly sync: (services: { readonly [N in keyof D]: ServiceOf<InstanceType<D[N]>>; }) => WorkerInferHandlers<C, {
|
|
92
|
+
readonly unit: UnitRecordOf<U>;
|
|
93
|
+
}>[K & keyof WorkerInferHandlers<C, {
|
|
94
|
+
readonly unit: UnitRecordOf<U>;
|
|
95
|
+
}>];
|
|
96
|
+
}) => MintedHandler<C, K, InstanceType<D[keyof D]>, U>;
|
|
27
97
|
//#endregion
|
|
28
98
|
//#region src/amqp-runtime.d.ts
|
|
29
99
|
/** What the worker publishes once it is consuming, read back through `RunningApp.runtimeInfo()`. */
|
|
@@ -44,6 +114,22 @@ declare class AmqpConfig extends AmqpConfig_base<{
|
|
|
44
114
|
}> {}
|
|
45
115
|
/** The runtime's port: what `amqp()` provides, and what the module `start` boots must export. */
|
|
46
116
|
declare class AmqpRuntime extends RuntimePort<Runtime<never, AmqpInfo>> {}
|
|
117
|
+
/**
|
|
118
|
+
* A module `unit.message` may bind, as the upper bound `amqp()` constrains its
|
|
119
|
+
* own `Unit` type parameter to. `Module`'s `_exports` channel is contravariant,
|
|
120
|
+
* so `Exports = never` — never `unknown` — is what makes a REAL module's own
|
|
121
|
+
* (necessarily narrower) export type assignable to this bound: `(x: Concrete)
|
|
122
|
+
* => void` is assignable to `(x: never) => void`, not to `(x: unknown) =>
|
|
123
|
+
* void`.
|
|
124
|
+
*/
|
|
125
|
+
type AnyUnitModule = Module<never, never, unknown>;
|
|
126
|
+
/**
|
|
127
|
+
* The needs a bound `unit.message` module still owes, or `never` when none is
|
|
128
|
+
* bound. `Scope` is excluded, since nothing can ever provide it — the same
|
|
129
|
+
* exemption `NeedsGate` itself carries — and so is the delivery, which the
|
|
130
|
+
* fork's own seed discharges.
|
|
131
|
+
*/
|
|
132
|
+
type UnitNeedsOf<Unit> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | AmqpMessageInstance> : never;
|
|
47
133
|
/**
|
|
48
134
|
* The contract type `TypedAmqpWorker.create` accepts, extracted rather than
|
|
49
135
|
* imported by name so `@amqp-contract/contract` stays out of the peer range.
|
|
@@ -60,20 +146,20 @@ type AmqpConnectionOptions = NonNullable<Parameters<typeof TypedAmqpWorker.creat
|
|
|
60
146
|
type HandlersPortOf<C extends AnyAmqpContract> = PortClassOf<"AmqpHandlers", WorkerInferHandlers<C>>;
|
|
61
147
|
/** The handlers port's instance for `C` — the module's one need. */
|
|
62
148
|
type HandlersInstanceOf<C extends AnyAmqpContract> = PortInstance<"AmqpHandlers", WorkerInferHandlers<C>>;
|
|
63
|
-
type AmqpOptions<TContract extends AnyAmqpContract> = {
|
|
149
|
+
type AmqpOptions<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined> = {
|
|
64
150
|
/**
|
|
65
151
|
* The contract; the handlers port is typed by it — one handler per
|
|
66
152
|
* `consumers` / `rpcs` key, with no injected context, since a handler is
|
|
67
153
|
* built by di from the services it declares.
|
|
68
154
|
*/
|
|
69
155
|
readonly contract: TContract;
|
|
70
|
-
} & AmqpTuning
|
|
156
|
+
} & AmqpTuning<Unit>;
|
|
71
157
|
/**
|
|
72
158
|
* What an AMQP deployment tunes, shared verbatim by `amqp()` and `AmqpModule` —
|
|
73
159
|
* spelled once so the two cannot drift, which is what a second copy of an
|
|
74
160
|
* option list always eventually does.
|
|
75
161
|
*/
|
|
76
|
-
type AmqpTuning = {
|
|
162
|
+
type AmqpTuning<Unit extends AnyUnitModule | undefined = undefined> = {
|
|
77
163
|
/** Pins `AmqpConfig.url` instead of reading `AMQP_URL` — a test's container. */
|
|
78
164
|
readonly url?: string;
|
|
79
165
|
/** Connection tuning, `@amqp-contract/worker`'s own type: heartbeat, reconnect interval, `findServers`, TLS/socket options. */
|
|
@@ -88,6 +174,15 @@ type AmqpTuning = {
|
|
|
88
174
|
* `connectionOptions`, where setting it is silently inert.
|
|
89
175
|
*/
|
|
90
176
|
readonly connectTimeoutMs?: number;
|
|
177
|
+
/**
|
|
178
|
+
* The unit module the worker forks around every delivery it dispatches,
|
|
179
|
+
* seeded with the validated message on `AmqpMessage(contract)`. Built after
|
|
180
|
+
* the message is validated, torn down when the unit closes; what it exports
|
|
181
|
+
* is what a piece may declare on its own `unit:` record.
|
|
182
|
+
*/
|
|
183
|
+
readonly unit?: {
|
|
184
|
+
readonly message?: Unit;
|
|
185
|
+
};
|
|
91
186
|
};
|
|
92
187
|
/**
|
|
93
188
|
* The AMQP starter: a module providing the runtime and its configuration (bound
|
|
@@ -97,11 +192,17 @@ type AmqpTuning = {
|
|
|
97
192
|
*
|
|
98
193
|
* With `url` pinned the module reads nothing from the environment.
|
|
99
194
|
*/
|
|
100
|
-
declare const amqp: <TContract extends AnyAmqpContract>(options: AmqpOptions<TContract>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract>>;
|
|
195
|
+
declare const amqp: <TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined>(options: AmqpOptions<TContract, Unit>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
101
196
|
/** One piece of the handlers record — what `AmqpHandler(contract, key)(…)` returns, as the composing form consumes it. */
|
|
102
197
|
type PieceOf<C extends AnyAmqpContract> = { readonly [K in HandlerKeyOf<C>]: {
|
|
103
198
|
readonly port: HandlerPortOf<C, K>;
|
|
199
|
+
/** Phantom, carrying the ports the piece injects off `context.unit`. */
|
|
200
|
+
readonly _declared?: unknown;
|
|
104
201
|
}; }[HandlerKeyOf<C>];
|
|
202
|
+
/** Every port the pieces in `T` inject off `context.unit` — what the root's `unit.message` must export. */
|
|
203
|
+
type DeclaredOf<T extends readonly {
|
|
204
|
+
readonly _declared?: unknown;
|
|
205
|
+
}[]> = NonNullable<T[number]["_declared"]>;
|
|
105
206
|
/** The key a piece carries, read back off its port id. */
|
|
106
207
|
type KeyOfPiece<P> = P extends {
|
|
107
208
|
readonly port: {
|
|
@@ -123,6 +224,29 @@ type Uncovered<C extends AnyAmqpContract, T extends readonly PieceOf<C>[]> = Exc
|
|
|
123
224
|
* hand.
|
|
124
225
|
*/
|
|
125
226
|
type Refuse<T extends readonly unknown[], Marker extends string, Detail> = T extends readonly [...infer Head, unknown] ? readonly [...Head, readonly [Marker, Detail]] : readonly [readonly [Marker, Detail]];
|
|
227
|
+
/**
|
|
228
|
+
* The record arm: the whole handlers record from one `sync`, with one `unit:`
|
|
229
|
+
* record shared by every entry in it.
|
|
230
|
+
*
|
|
231
|
+
* It is `{ inject, unit?, sync }` — di's other arms are gone — for PARITY, not
|
|
232
|
+
* for a type-system reason: `value` could have carried the record just as well,
|
|
233
|
+
* since `U` infers from `unit` and never from the arm. `@btravstack/http-server`'s
|
|
234
|
+
* `api.OrpcRouter(contract)` is `{ inject, unit?, sync }` and so are all three
|
|
235
|
+
* packages' piece factories, so one arm across the family is one surface to
|
|
236
|
+
* learn and one to keep.
|
|
237
|
+
*/
|
|
238
|
+
type Whole<C extends AnyAmqpContract> = <const D extends Readonly<Record<string, AnyPort>>, const U extends Readonly<Record<string, AnyPort>> = Record<never, never>>(options: {
|
|
239
|
+
readonly inject: D;
|
|
240
|
+
/** The unit-scoped ports every handler in the record reads off `context.unit`. */
|
|
241
|
+
readonly unit?: U;
|
|
242
|
+
readonly sync: (services: { readonly [N in keyof D]: ServiceOf<InstanceType<D[N]>>; }) => WorkerInferHandlers<C, {
|
|
243
|
+
readonly unit: UnitRecordOf<U>;
|
|
244
|
+
}>;
|
|
245
|
+
}) => Provider<HandlersInstanceOf<C>, never, InstanceType<D[keyof D]>> & {
|
|
246
|
+
readonly port: HandlersPortOf<C>;
|
|
247
|
+
/** Phantom: the declared ports, which `AmqpModule` gates `unit.message` against. */
|
|
248
|
+
readonly _declaredUnit?: InstanceType<U[keyof U]>;
|
|
249
|
+
};
|
|
126
250
|
/**
|
|
127
251
|
* The composing arm. Declared LAST in the intersection below on purpose:
|
|
128
252
|
* TypeScript reports the last overload's failure, so a non-covering array is
|
|
@@ -134,6 +258,8 @@ type Refuse<T extends readonly unknown[], Marker extends string, Detail> = T ext
|
|
|
134
258
|
*/
|
|
135
259
|
type Compose<C extends AnyAmqpContract> = <const T extends readonly PieceOf<C>[]>(pieces: [Uncovered<C, T>] extends [never] ? T : Refuse<T, "UNCOVERED HANDLERS — the contract declares a consumer this array does not cover", Uncovered<C, T>>) => Provider<HandlersInstanceOf<C>, never, InstanceType<T[number]["port"]>> & {
|
|
136
260
|
readonly port: HandlersPortOf<C>;
|
|
261
|
+
/** Phantom: the union of every piece's declared ports, which `AmqpModule` gates `unit.message` against. */
|
|
262
|
+
readonly _declaredUnit?: DeclaredOf<T>;
|
|
137
263
|
};
|
|
138
264
|
/**
|
|
139
265
|
* The handlers as a provider, from the contract. Two call forms, one port.
|
|
@@ -143,26 +269,33 @@ type Compose<C extends AnyAmqpContract> = <const T extends readonly PieceOf<C>[]
|
|
|
143
269
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
144
270
|
* ```
|
|
145
271
|
*
|
|
146
|
-
* The first is
|
|
147
|
-
*
|
|
148
|
-
* key)` builds: they are the provider's deps,
|
|
149
|
-
* piece's port id carries, so the services
|
|
150
|
-
* declared key must be covered, and two
|
|
151
|
-
* duplicate-provider defect at build.
|
|
152
|
-
*/
|
|
153
|
-
declare const AmqpHandlers: <C extends AnyAmqpContract>(contract: C) =>
|
|
272
|
+
* The first is `{ inject, unit?, sync }`, whose `sync` hands back the whole
|
|
273
|
+
* handlers record — one `unit:` record for every entry in it. The second takes
|
|
274
|
+
* the pieces `AmqpHandler(contract, key)` builds: they are the provider's deps,
|
|
275
|
+
* keyed by the contract key each piece's port id carries, so the services
|
|
276
|
+
* record IS the handlers record. Every declared key must be covered, and two
|
|
277
|
+
* slices claiming one key are di's duplicate-provider defect at build.
|
|
278
|
+
*/
|
|
279
|
+
declare const AmqpHandlers: <C extends AnyAmqpContract>(contract: C) => Whole<C> & Compose<C>;
|
|
154
280
|
//#endregion
|
|
155
281
|
//#region src/amqp-module.d.ts
|
|
156
282
|
/** The starter's own module, as the sugar adds it to the application's imports. */
|
|
157
|
-
type AmqpStarter<TContract extends AnyAmqpContract> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract>>;
|
|
283
|
+
type AmqpStarter<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
158
284
|
/** The application's imports plus the starter — the tuple `Module(name)` is handed. */
|
|
159
|
-
type Imports<I extends readonly AnyModule[], TContract extends AnyAmqpContract> = readonly [...I, AmqpStarter<TContract>];
|
|
285
|
+
type Imports<I extends readonly AnyModule[], TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = readonly [...I, AmqpStarter<TContract, Unit>];
|
|
160
286
|
/** The handlers provider plus the application's own — the tuple `Module(name)` is handed. */
|
|
161
287
|
type Provides<P extends readonly AnyProvider[], TContract extends AnyAmqpContract, HandlersError, HandlersNeeds> = readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P];
|
|
162
|
-
type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, TContract>, Provides<P, TContract, HandlersError, HandlersNeeds>>[], N extends readonly AnyPort[]> = AmqpTuning & {
|
|
288
|
+
type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, Declared, Unit extends AnyUnitModule | undefined, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, TContract, Unit>, Provides<P, TContract, HandlersError, HandlersNeeds>>[], N extends readonly AnyPort[]> = AmqpTuning<Unit> & {
|
|
163
289
|
readonly contract: TContract;
|
|
164
|
-
/**
|
|
165
|
-
|
|
290
|
+
/**
|
|
291
|
+
* The application's handlers — what `AmqpHandlers(contract)(…)` returns for
|
|
292
|
+
* THIS contract. Its `_declaredUnit` phantom carries every port its pieces
|
|
293
|
+
* read off `context.unit`, which is what {@link UnitGate} checks `unit.message`
|
|
294
|
+
* against.
|
|
295
|
+
*/
|
|
296
|
+
readonly handlers: Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds> & {
|
|
297
|
+
readonly _declaredUnit?: Declared;
|
|
298
|
+
};
|
|
166
299
|
readonly imports?: I;
|
|
167
300
|
readonly provides?: P;
|
|
168
301
|
/** The application's own exports; `AmqpRuntime` is added, since `start` resolves it. */
|
|
@@ -172,7 +305,7 @@ type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, Handler
|
|
|
172
305
|
* over the augmented tuples below, so forgetting one is an error at THIS call.
|
|
173
306
|
*/
|
|
174
307
|
readonly needs?: N;
|
|
175
|
-
} & NeedsGate<Imports<I, TContract>, Provides<P, TContract, HandlersError, HandlersNeeds>, N>;
|
|
308
|
+
} & NeedsGate<Imports<I, TContract, Unit>, Provides<P, TContract, HandlersError, HandlersNeeds>, N> & UnitGate<Unit, Declared>;
|
|
176
309
|
/**
|
|
177
310
|
* `Module(name)({...})` for an AMQP deployment: everything a di module takes,
|
|
178
311
|
* plus the contract and the handlers provider. The sugar imports the starter,
|
|
@@ -189,6 +322,6 @@ type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, Handler
|
|
|
189
322
|
* await runMain(OrderAmqpWorker);
|
|
190
323
|
* ```
|
|
191
324
|
*/
|
|
192
|
-
declare const AmqpModule: <const Name extends string>(name: Name) => <TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, TContract>, Provides<P, TContract, HandlersError, HandlersNeeds>>[] = [], const N extends readonly AnyPort[] = []>(options: AmqpModuleOptions<TContract, HandlersError, HandlersNeeds, I, P, X, N>) => Module<(Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> extends (infer T) ? T extends Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> ? T extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [typeof AmqpRuntime, ...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_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 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>])[number] extends (infer T_2) ? T_2 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_2 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[1], Exclude<((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)[2], ((readonly [...I, AmqpStarter<TContract>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]> | Exclude<((readonly [...I, AmqpStarter<TContract>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[2], ((readonly [...I, AmqpStarter<TContract>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]>>;
|
|
325
|
+
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[] = []>(options: AmqpModuleOptions<TContract, HandlersError, HandlersNeeds, Declared, Unit, I, P, X, N>) => Module<(Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> extends (infer T) ? T extends Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> ? T extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [typeof AmqpRuntime, ...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_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 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_2) ? T_2 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_2 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[1], Exclude<((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)[2], ((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_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]> | Exclude<((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_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[2], ((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_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]>>;
|
|
193
326
|
//#endregion
|
|
194
|
-
export { AmqpConfig, type AmqpConnectionOptions, AmqpHandler, AmqpHandlers, type AmqpInfo, AmqpModule, type AmqpModuleOptions, type AmqpOptions, AmqpRuntime, type HandlerPortOf, type HandlersInstanceOf, type HandlersPortOf, amqp };
|
|
327
|
+
export { AmqpConfig, type AmqpConnectionOptions, AmqpHandler, AmqpHandlers, type AmqpInfo, AmqpMessage, type AmqpMessageOf, type AmqpMessagePortOf, AmqpModule, type AmqpModuleOptions, type AmqpOptions, AmqpRuntime, type HandlerPortOf, type HandlersInstanceOf, type HandlersPortOf, amqp };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,59 @@
|
|
|
1
|
-
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider } from "@btravstack/di";
|
|
1
|
+
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, Scope, ServiceOf } from "@btravstack/di";
|
|
2
2
|
import { ConsumerOptions, TypedAmqpWorker, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
3
3
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
|
4
4
|
import { Runtime, RuntimePort } from "@btravstack/core";
|
|
5
|
+
//#region src/unit.d.ts
|
|
6
|
+
/** What a bound unit module exports — the port instances a handler of that kind may read. */
|
|
7
|
+
type UnitExportsOf<M> = M extends Module<infer X, never, unknown> ? X : never;
|
|
8
|
+
/** The declared `unit:` record, as the services a handler reads off `context.unit`. */
|
|
9
|
+
type UnitRecordOf<U extends Readonly<Record<string, AnyPort>>> = { readonly [N in keyof U]: ServiceOf<InstanceType<U[N]>>; };
|
|
10
|
+
/**
|
|
11
|
+
* A piece injecting a port the bound `unit.message` module does not export.
|
|
12
|
+
* `unknown` when every declared port is covered — including the empty case, so
|
|
13
|
+
* a worker whose pieces declare no `unit:` is gated on nothing.
|
|
14
|
+
*/
|
|
15
|
+
type UnitGate<Unit, Declared> = [Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>] extends [never] ? unknown : {
|
|
16
|
+
readonly "UNIT DOES NOT PROVIDE — a piece injects a port the bound unit module does not export": Exclude<NonNullable<Declared>, UnitExportsOf<Unit>>;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
5
19
|
//#region src/handler.d.ts
|
|
6
20
|
/** The consumer and rpc names `C` declares — the keys of its handlers record. */
|
|
7
21
|
type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & string;
|
|
22
|
+
/**
|
|
23
|
+
* The validated delivery, typed by the contract: the union of every consumer's
|
|
24
|
+
* and rpc's own consumed message. Reached through the handler's own second
|
|
25
|
+
* parameter, since `@amqp-contract/worker` exports
|
|
26
|
+
* `WorkerInferConsumedMessage` but not the `InferConsumerNames` constraint it
|
|
27
|
+
* takes — the same by-index route {@link AnyAmqpContract} travels.
|
|
28
|
+
*/
|
|
29
|
+
type AmqpMessageOf<C extends AnyAmqpContract> = Parameters<Extract<WorkerInferHandlers<C>[HandlerKeyOf<C>], (...args: never) => unknown>>[1];
|
|
30
|
+
/** The seeded port's class, typed for `C`: what `AmqpMessage(contract)` answers. */
|
|
31
|
+
type AmqpMessagePortOf<C extends AnyAmqpContract> = PortClassOf<"AmqpMessage", AmqpMessageOf<C>>;
|
|
32
|
+
/**
|
|
33
|
+
* The seed's port instance, as it appears in a needs union — subtracted from
|
|
34
|
+
* what a bound `unit.message` module still owes, since the fork is what
|
|
35
|
+
* discharges it.
|
|
36
|
+
*/
|
|
37
|
+
type AmqpMessageInstance = PortInstance<"AmqpMessage", unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* The validated delivery as a port: the one thing the worker seeds the fork
|
|
40
|
+
* with, so a `unit.message` module derives a tenant — or anything else — from
|
|
41
|
+
* the message rather than from an ambient record.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* const Message = AmqpMessage(orderContract);
|
|
45
|
+
* const MessageUnit = Module("MessageUnit")({
|
|
46
|
+
* needs: [Message],
|
|
47
|
+
* provides: [Provider(Tenant)({ inject: { message: Message }, sync: ({ message }) => message.payload.tenantId })],
|
|
48
|
+
* exports: [Tenant],
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* `contract` is read for its TYPE only. One `Port(...)` call fixed per contract
|
|
53
|
+
* at the type level, the move `AmqpHandlersPort` makes, so a module built for
|
|
54
|
+
* one contract cannot read another's message.
|
|
55
|
+
*/
|
|
56
|
+
declare const AmqpMessage: <C extends AnyAmqpContract>(contract: C) => AmqpMessagePortOf<C>;
|
|
8
57
|
/**
|
|
9
58
|
* The port one piece targets. Its id carries the contract key, which is what
|
|
10
59
|
* makes two slices claiming one consumer di's duplicate-provider defect rather
|
|
@@ -13,6 +62,14 @@ type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & st
|
|
|
13
62
|
type HandlerPortOf<C extends AnyAmqpContract, K extends HandlerKeyOf<C>> = PortClassOf<`${typeof HANDLER_PREFIX}${K}`, WorkerInferHandlers<C>[K]>;
|
|
14
63
|
/** The prefix a piece's port id carries; the composing form strips it to recover the key. */
|
|
15
64
|
declare const HANDLER_PREFIX = "AmqpHandler:";
|
|
65
|
+
/** What a minted piece returns. */
|
|
66
|
+
type MintedHandler<C extends AnyAmqpContract, K extends HandlerKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>> = Provider<InstanceType<HandlerPortOf<C, K>>, never, N> & {
|
|
67
|
+
readonly port: HandlerPortOf<C, K>;
|
|
68
|
+
/** The declared `unit:` record, which `AmqpHandlers`'s array arm reads back off the piece. */
|
|
69
|
+
readonly unit: U;
|
|
70
|
+
/** Phantom: the ports this piece injects, which the root's `unit.message` must export. */
|
|
71
|
+
readonly _declared?: InstanceType<U[keyof U]>;
|
|
72
|
+
};
|
|
16
73
|
/**
|
|
17
74
|
* One consumer or rpc of a contract, as a provider on a port of its own.
|
|
18
75
|
*
|
|
@@ -22,8 +79,21 @@ declare const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
22
79
|
* so a consumer the contract does not declare is a compile error here.
|
|
23
80
|
*
|
|
24
81
|
* There is no name to give: the contract key IS the port's name.
|
|
82
|
+
*
|
|
83
|
+
* `unit` declares the ports the handler reads off `context.unit`, resolved out
|
|
84
|
+
* of the fork the delivery opened; the root's `unit.message` module must export
|
|
85
|
+
* every one of them.
|
|
25
86
|
*/
|
|
26
|
-
declare const AmqpHandler: <C extends AnyAmqpContract, const K extends HandlerKeyOf<C>>(contract: C, key: K) =>
|
|
87
|
+
declare const AmqpHandler: <C extends AnyAmqpContract, const K extends HandlerKeyOf<C>>(contract: C, key: K) => <const D extends Readonly<Record<string, AnyPort>>, const U extends Readonly<Record<string, AnyPort>> = Record<never, never>>(options: {
|
|
88
|
+
readonly inject: D;
|
|
89
|
+
/** The unit-scoped ports this handler reads off `context.unit`. */
|
|
90
|
+
readonly unit?: U;
|
|
91
|
+
readonly sync: (services: { readonly [N in keyof D]: ServiceOf<InstanceType<D[N]>>; }) => WorkerInferHandlers<C, {
|
|
92
|
+
readonly unit: UnitRecordOf<U>;
|
|
93
|
+
}>[K & keyof WorkerInferHandlers<C, {
|
|
94
|
+
readonly unit: UnitRecordOf<U>;
|
|
95
|
+
}>];
|
|
96
|
+
}) => MintedHandler<C, K, InstanceType<D[keyof D]>, U>;
|
|
27
97
|
//#endregion
|
|
28
98
|
//#region src/amqp-runtime.d.ts
|
|
29
99
|
/** What the worker publishes once it is consuming, read back through `RunningApp.runtimeInfo()`. */
|
|
@@ -44,6 +114,22 @@ declare class AmqpConfig extends AmqpConfig_base<{
|
|
|
44
114
|
}> {}
|
|
45
115
|
/** The runtime's port: what `amqp()` provides, and what the module `start` boots must export. */
|
|
46
116
|
declare class AmqpRuntime extends RuntimePort<Runtime<never, AmqpInfo>> {}
|
|
117
|
+
/**
|
|
118
|
+
* A module `unit.message` may bind, as the upper bound `amqp()` constrains its
|
|
119
|
+
* own `Unit` type parameter to. `Module`'s `_exports` channel is contravariant,
|
|
120
|
+
* so `Exports = never` — never `unknown` — is what makes a REAL module's own
|
|
121
|
+
* (necessarily narrower) export type assignable to this bound: `(x: Concrete)
|
|
122
|
+
* => void` is assignable to `(x: never) => void`, not to `(x: unknown) =>
|
|
123
|
+
* void`.
|
|
124
|
+
*/
|
|
125
|
+
type AnyUnitModule = Module<never, never, unknown>;
|
|
126
|
+
/**
|
|
127
|
+
* The needs a bound `unit.message` module still owes, or `never` when none is
|
|
128
|
+
* bound. `Scope` is excluded, since nothing can ever provide it — the same
|
|
129
|
+
* exemption `NeedsGate` itself carries — and so is the delivery, which the
|
|
130
|
+
* fork's own seed discharges.
|
|
131
|
+
*/
|
|
132
|
+
type UnitNeedsOf<Unit> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope | AmqpMessageInstance> : never;
|
|
47
133
|
/**
|
|
48
134
|
* The contract type `TypedAmqpWorker.create` accepts, extracted rather than
|
|
49
135
|
* imported by name so `@amqp-contract/contract` stays out of the peer range.
|
|
@@ -60,20 +146,20 @@ type AmqpConnectionOptions = NonNullable<Parameters<typeof TypedAmqpWorker.creat
|
|
|
60
146
|
type HandlersPortOf<C extends AnyAmqpContract> = PortClassOf<"AmqpHandlers", WorkerInferHandlers<C>>;
|
|
61
147
|
/** The handlers port's instance for `C` — the module's one need. */
|
|
62
148
|
type HandlersInstanceOf<C extends AnyAmqpContract> = PortInstance<"AmqpHandlers", WorkerInferHandlers<C>>;
|
|
63
|
-
type AmqpOptions<TContract extends AnyAmqpContract> = {
|
|
149
|
+
type AmqpOptions<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined> = {
|
|
64
150
|
/**
|
|
65
151
|
* The contract; the handlers port is typed by it — one handler per
|
|
66
152
|
* `consumers` / `rpcs` key, with no injected context, since a handler is
|
|
67
153
|
* built by di from the services it declares.
|
|
68
154
|
*/
|
|
69
155
|
readonly contract: TContract;
|
|
70
|
-
} & AmqpTuning
|
|
156
|
+
} & AmqpTuning<Unit>;
|
|
71
157
|
/**
|
|
72
158
|
* What an AMQP deployment tunes, shared verbatim by `amqp()` and `AmqpModule` —
|
|
73
159
|
* spelled once so the two cannot drift, which is what a second copy of an
|
|
74
160
|
* option list always eventually does.
|
|
75
161
|
*/
|
|
76
|
-
type AmqpTuning = {
|
|
162
|
+
type AmqpTuning<Unit extends AnyUnitModule | undefined = undefined> = {
|
|
77
163
|
/** Pins `AmqpConfig.url` instead of reading `AMQP_URL` — a test's container. */
|
|
78
164
|
readonly url?: string;
|
|
79
165
|
/** Connection tuning, `@amqp-contract/worker`'s own type: heartbeat, reconnect interval, `findServers`, TLS/socket options. */
|
|
@@ -88,6 +174,15 @@ type AmqpTuning = {
|
|
|
88
174
|
* `connectionOptions`, where setting it is silently inert.
|
|
89
175
|
*/
|
|
90
176
|
readonly connectTimeoutMs?: number;
|
|
177
|
+
/**
|
|
178
|
+
* The unit module the worker forks around every delivery it dispatches,
|
|
179
|
+
* seeded with the validated message on `AmqpMessage(contract)`. Built after
|
|
180
|
+
* the message is validated, torn down when the unit closes; what it exports
|
|
181
|
+
* is what a piece may declare on its own `unit:` record.
|
|
182
|
+
*/
|
|
183
|
+
readonly unit?: {
|
|
184
|
+
readonly message?: Unit;
|
|
185
|
+
};
|
|
91
186
|
};
|
|
92
187
|
/**
|
|
93
188
|
* The AMQP starter: a module providing the runtime and its configuration (bound
|
|
@@ -97,11 +192,17 @@ type AmqpTuning = {
|
|
|
97
192
|
*
|
|
98
193
|
* With `url` pinned the module reads nothing from the environment.
|
|
99
194
|
*/
|
|
100
|
-
declare const amqp: <TContract extends AnyAmqpContract>(options: AmqpOptions<TContract>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract>>;
|
|
195
|
+
declare const amqp: <TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined>(options: AmqpOptions<TContract, Unit>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
101
196
|
/** One piece of the handlers record — what `AmqpHandler(contract, key)(…)` returns, as the composing form consumes it. */
|
|
102
197
|
type PieceOf<C extends AnyAmqpContract> = { readonly [K in HandlerKeyOf<C>]: {
|
|
103
198
|
readonly port: HandlerPortOf<C, K>;
|
|
199
|
+
/** Phantom, carrying the ports the piece injects off `context.unit`. */
|
|
200
|
+
readonly _declared?: unknown;
|
|
104
201
|
}; }[HandlerKeyOf<C>];
|
|
202
|
+
/** Every port the pieces in `T` inject off `context.unit` — what the root's `unit.message` must export. */
|
|
203
|
+
type DeclaredOf<T extends readonly {
|
|
204
|
+
readonly _declared?: unknown;
|
|
205
|
+
}[]> = NonNullable<T[number]["_declared"]>;
|
|
105
206
|
/** The key a piece carries, read back off its port id. */
|
|
106
207
|
type KeyOfPiece<P> = P extends {
|
|
107
208
|
readonly port: {
|
|
@@ -123,6 +224,29 @@ type Uncovered<C extends AnyAmqpContract, T extends readonly PieceOf<C>[]> = Exc
|
|
|
123
224
|
* hand.
|
|
124
225
|
*/
|
|
125
226
|
type Refuse<T extends readonly unknown[], Marker extends string, Detail> = T extends readonly [...infer Head, unknown] ? readonly [...Head, readonly [Marker, Detail]] : readonly [readonly [Marker, Detail]];
|
|
227
|
+
/**
|
|
228
|
+
* The record arm: the whole handlers record from one `sync`, with one `unit:`
|
|
229
|
+
* record shared by every entry in it.
|
|
230
|
+
*
|
|
231
|
+
* It is `{ inject, unit?, sync }` — di's other arms are gone — for PARITY, not
|
|
232
|
+
* for a type-system reason: `value` could have carried the record just as well,
|
|
233
|
+
* since `U` infers from `unit` and never from the arm. `@btravstack/http-server`'s
|
|
234
|
+
* `api.OrpcRouter(contract)` is `{ inject, unit?, sync }` and so are all three
|
|
235
|
+
* packages' piece factories, so one arm across the family is one surface to
|
|
236
|
+
* learn and one to keep.
|
|
237
|
+
*/
|
|
238
|
+
type Whole<C extends AnyAmqpContract> = <const D extends Readonly<Record<string, AnyPort>>, const U extends Readonly<Record<string, AnyPort>> = Record<never, never>>(options: {
|
|
239
|
+
readonly inject: D;
|
|
240
|
+
/** The unit-scoped ports every handler in the record reads off `context.unit`. */
|
|
241
|
+
readonly unit?: U;
|
|
242
|
+
readonly sync: (services: { readonly [N in keyof D]: ServiceOf<InstanceType<D[N]>>; }) => WorkerInferHandlers<C, {
|
|
243
|
+
readonly unit: UnitRecordOf<U>;
|
|
244
|
+
}>;
|
|
245
|
+
}) => Provider<HandlersInstanceOf<C>, never, InstanceType<D[keyof D]>> & {
|
|
246
|
+
readonly port: HandlersPortOf<C>;
|
|
247
|
+
/** Phantom: the declared ports, which `AmqpModule` gates `unit.message` against. */
|
|
248
|
+
readonly _declaredUnit?: InstanceType<U[keyof U]>;
|
|
249
|
+
};
|
|
126
250
|
/**
|
|
127
251
|
* The composing arm. Declared LAST in the intersection below on purpose:
|
|
128
252
|
* TypeScript reports the last overload's failure, so a non-covering array is
|
|
@@ -134,6 +258,8 @@ type Refuse<T extends readonly unknown[], Marker extends string, Detail> = T ext
|
|
|
134
258
|
*/
|
|
135
259
|
type Compose<C extends AnyAmqpContract> = <const T extends readonly PieceOf<C>[]>(pieces: [Uncovered<C, T>] extends [never] ? T : Refuse<T, "UNCOVERED HANDLERS — the contract declares a consumer this array does not cover", Uncovered<C, T>>) => Provider<HandlersInstanceOf<C>, never, InstanceType<T[number]["port"]>> & {
|
|
136
260
|
readonly port: HandlersPortOf<C>;
|
|
261
|
+
/** Phantom: the union of every piece's declared ports, which `AmqpModule` gates `unit.message` against. */
|
|
262
|
+
readonly _declaredUnit?: DeclaredOf<T>;
|
|
137
263
|
};
|
|
138
264
|
/**
|
|
139
265
|
* The handlers as a provider, from the contract. Two call forms, one port.
|
|
@@ -143,26 +269,33 @@ type Compose<C extends AnyAmqpContract> = <const T extends readonly PieceOf<C>[]
|
|
|
143
269
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
144
270
|
* ```
|
|
145
271
|
*
|
|
146
|
-
* The first is
|
|
147
|
-
*
|
|
148
|
-
* key)` builds: they are the provider's deps,
|
|
149
|
-
* piece's port id carries, so the services
|
|
150
|
-
* declared key must be covered, and two
|
|
151
|
-
* duplicate-provider defect at build.
|
|
152
|
-
*/
|
|
153
|
-
declare const AmqpHandlers: <C extends AnyAmqpContract>(contract: C) =>
|
|
272
|
+
* The first is `{ inject, unit?, sync }`, whose `sync` hands back the whole
|
|
273
|
+
* handlers record — one `unit:` record for every entry in it. The second takes
|
|
274
|
+
* the pieces `AmqpHandler(contract, key)` builds: they are the provider's deps,
|
|
275
|
+
* keyed by the contract key each piece's port id carries, so the services
|
|
276
|
+
* record IS the handlers record. Every declared key must be covered, and two
|
|
277
|
+
* slices claiming one key are di's duplicate-provider defect at build.
|
|
278
|
+
*/
|
|
279
|
+
declare const AmqpHandlers: <C extends AnyAmqpContract>(contract: C) => Whole<C> & Compose<C>;
|
|
154
280
|
//#endregion
|
|
155
281
|
//#region src/amqp-module.d.ts
|
|
156
282
|
/** The starter's own module, as the sugar adds it to the application's imports. */
|
|
157
|
-
type AmqpStarter<TContract extends AnyAmqpContract> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract>>;
|
|
283
|
+
type AmqpStarter<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
158
284
|
/** The application's imports plus the starter — the tuple `Module(name)` is handed. */
|
|
159
|
-
type Imports<I extends readonly AnyModule[], TContract extends AnyAmqpContract> = readonly [...I, AmqpStarter<TContract>];
|
|
285
|
+
type Imports<I extends readonly AnyModule[], TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = readonly [...I, AmqpStarter<TContract, Unit>];
|
|
160
286
|
/** The handlers provider plus the application's own — the tuple `Module(name)` is handed. */
|
|
161
287
|
type Provides<P extends readonly AnyProvider[], TContract extends AnyAmqpContract, HandlersError, HandlersNeeds> = readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P];
|
|
162
|
-
type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, TContract>, Provides<P, TContract, HandlersError, HandlersNeeds>>[], N extends readonly AnyPort[]> = AmqpTuning & {
|
|
288
|
+
type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, Declared, Unit extends AnyUnitModule | undefined, I extends readonly AnyModule[], P extends readonly AnyProvider[], X extends readonly Exportable<Imports<I, TContract, Unit>, Provides<P, TContract, HandlersError, HandlersNeeds>>[], N extends readonly AnyPort[]> = AmqpTuning<Unit> & {
|
|
163
289
|
readonly contract: TContract;
|
|
164
|
-
/**
|
|
165
|
-
|
|
290
|
+
/**
|
|
291
|
+
* The application's handlers — what `AmqpHandlers(contract)(…)` returns for
|
|
292
|
+
* THIS contract. Its `_declaredUnit` phantom carries every port its pieces
|
|
293
|
+
* read off `context.unit`, which is what {@link UnitGate} checks `unit.message`
|
|
294
|
+
* against.
|
|
295
|
+
*/
|
|
296
|
+
readonly handlers: Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds> & {
|
|
297
|
+
readonly _declaredUnit?: Declared;
|
|
298
|
+
};
|
|
166
299
|
readonly imports?: I;
|
|
167
300
|
readonly provides?: P;
|
|
168
301
|
/** The application's own exports; `AmqpRuntime` is added, since `start` resolves it. */
|
|
@@ -172,7 +305,7 @@ type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, Handler
|
|
|
172
305
|
* over the augmented tuples below, so forgetting one is an error at THIS call.
|
|
173
306
|
*/
|
|
174
307
|
readonly needs?: N;
|
|
175
|
-
} & NeedsGate<Imports<I, TContract>, Provides<P, TContract, HandlersError, HandlersNeeds>, N>;
|
|
308
|
+
} & NeedsGate<Imports<I, TContract, Unit>, Provides<P, TContract, HandlersError, HandlersNeeds>, N> & UnitGate<Unit, Declared>;
|
|
176
309
|
/**
|
|
177
310
|
* `Module(name)({...})` for an AMQP deployment: everything a di module takes,
|
|
178
311
|
* plus the contract and the handlers provider. The sugar imports the starter,
|
|
@@ -189,6 +322,6 @@ type AmqpModuleOptions<TContract extends AnyAmqpContract, HandlersError, Handler
|
|
|
189
322
|
* await runMain(OrderAmqpWorker);
|
|
190
323
|
* ```
|
|
191
324
|
*/
|
|
192
|
-
declare const AmqpModule: <const Name extends string>(name: Name) => <TContract extends AnyAmqpContract, HandlersError, HandlersNeeds, const I extends readonly AnyModule[] = [], const P extends readonly AnyProvider[] = [], const X extends readonly Exportable<Imports<I, TContract>, Provides<P, TContract, HandlersError, HandlersNeeds>>[] = [], const N extends readonly AnyPort[] = []>(options: AmqpModuleOptions<TContract, HandlersError, HandlersNeeds, I, P, X, N>) => Module<(Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> extends (infer T) ? T extends Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> ? T extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [typeof AmqpRuntime, ...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_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 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>])[number] extends (infer T_2) ? T_2 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_2 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[1], Exclude<((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)[2], ((readonly [...I, AmqpStarter<TContract>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]> | Exclude<((readonly [...I, AmqpStarter<TContract>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[2], ((readonly [...I, AmqpStarter<TContract>])[number] extends (infer T_4) ? T_4 extends (readonly [...I, AmqpStarter<TContract>])[number] ? T_4 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]>>;
|
|
325
|
+
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[] = []>(options: AmqpModuleOptions<TContract, HandlersError, HandlersNeeds, Declared, Unit, I, P, X, N>) => Module<(Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> extends (infer T) ? T extends Extract<(readonly [typeof AmqpRuntime, ...X])[number], AnyModule> ? T extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((readonly [typeof AmqpRuntime, ...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_1) ? T_1 extends (readonly [Provider<HandlersInstanceOf<TContract>, HandlersError, HandlersNeeds>, ...P])[number] ? T_1 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_2) ? T_2 extends (readonly [...I, AmqpStarter<TContract, Unit>])[number] ? T_2 extends Module<infer X_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[1], Exclude<((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)[2], ((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_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]> | Exclude<((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_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[2], ((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_1, infer E, infer N_1> ? readonly [X_1, E, N_1] : never : never : never)[0] | ((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)[0]>>;
|
|
193
326
|
//#endregion
|
|
194
|
-
export { AmqpConfig, type AmqpConnectionOptions, AmqpHandler, AmqpHandlers, type AmqpInfo, AmqpModule, type AmqpModuleOptions, type AmqpOptions, AmqpRuntime, type HandlerPortOf, type HandlersInstanceOf, type HandlersPortOf, amqp };
|
|
327
|
+
export { AmqpConfig, type AmqpConnectionOptions, AmqpHandler, AmqpHandlers, type AmqpInfo, AmqpMessage, type AmqpMessageOf, type AmqpMessagePortOf, AmqpModule, type AmqpModuleOptions, type AmqpOptions, AmqpRuntime, type HandlerPortOf, type HandlersInstanceOf, type HandlersPortOf, amqp };
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,76 @@
|
|
|
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 { Observers, RuntimePort, RuntimeStartFailed, noObserver, observe, releasedBy } from "@btravstack/core";
|
|
4
|
+
import { Observers, RuntimePort, RuntimeStartFailed, noObserver, observe, releasedBy, traceIdOfTraceparent } from "@btravstack/core";
|
|
5
5
|
import { P } from "unthrown";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
+
//#region src/unit.ts
|
|
8
|
+
/**
|
|
9
|
+
* Where `messageUnits` leaves the forked context for the piece wrappers to read.
|
|
10
|
+
* A symbol rather than a name: the dispatcher merges it into the context every
|
|
11
|
+
* handler sees, and a string key would appear on the record a handler written
|
|
12
|
+
* against the `{ inject, sync }` arm destructures.
|
|
13
|
+
*/
|
|
14
|
+
const UNIT_SCOPE = Symbol("@btravstack/amqp-worker/unit-scope");
|
|
15
|
+
/**
|
|
16
|
+
* The declared record, as a getter per name resolved on read out of the fork.
|
|
17
|
+
* Neither writable nor configurable — a handler reads what the fork holds, and
|
|
18
|
+
* cannot reshape the record under the next delivery. With no `unit.message`
|
|
19
|
+
* bound there is nothing to resolve from, so the record is empty whatever was
|
|
20
|
+
* declared.
|
|
21
|
+
*/
|
|
22
|
+
const unitRecordOf = (forked, record) => {
|
|
23
|
+
const unit = {};
|
|
24
|
+
if (forked === void 0) return unit;
|
|
25
|
+
for (const [name, port] of Object.entries(record)) Object.defineProperty(unit, name, {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: () => forked.get(port)
|
|
28
|
+
});
|
|
29
|
+
return unit;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* One handler entry — a function, or `[handler, options]` — with its piece's
|
|
33
|
+
* declared record put on `context.unit`. Applied once per piece as di
|
|
34
|
+
* constructs it, so a delivery costs one record and one context object.
|
|
35
|
+
*/
|
|
36
|
+
const withUnit = (record, entry) => {
|
|
37
|
+
const wrap = (handler) => (helpers, message) => {
|
|
38
|
+
const { [UNIT_SCOPE]: forked, ...rest } = helpers.context ?? {};
|
|
39
|
+
return handler({
|
|
40
|
+
...helpers,
|
|
41
|
+
context: {
|
|
42
|
+
...rest,
|
|
43
|
+
unit: unitRecordOf(forked, record)
|
|
44
|
+
}
|
|
45
|
+
}, message);
|
|
46
|
+
};
|
|
47
|
+
return Array.isArray(entry) ? [wrap(entry[0]), entry[1]] : wrap(entry);
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
7
50
|
//#region src/handler.ts
|
|
51
|
+
/** One id, declared once, so no contract instantiating it warns about a duplicate. */
|
|
52
|
+
const AmqpMessagePort = Port("AmqpMessage");
|
|
53
|
+
/**
|
|
54
|
+
* The validated delivery as a port: the one thing the worker seeds the fork
|
|
55
|
+
* with, so a `unit.message` module derives a tenant — or anything else — from
|
|
56
|
+
* the message rather than from an ambient record.
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const Message = AmqpMessage(orderContract);
|
|
60
|
+
* const MessageUnit = Module("MessageUnit")({
|
|
61
|
+
* needs: [Message],
|
|
62
|
+
* provides: [Provider(Tenant)({ inject: { message: Message }, sync: ({ message }) => message.payload.tenantId })],
|
|
63
|
+
* exports: [Tenant],
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* `contract` is read for its TYPE only. One `Port(...)` call fixed per contract
|
|
68
|
+
* at the type level, the move `AmqpHandlersPort` makes, so a module built for
|
|
69
|
+
* one contract cannot read another's message.
|
|
70
|
+
*/
|
|
71
|
+
const AmqpMessage = (contract) => {
|
|
72
|
+
return AmqpMessagePort;
|
|
73
|
+
};
|
|
8
74
|
/** The prefix a piece's port id carries; the composing form strips it to recover the key. */
|
|
9
75
|
const HANDLER_PREFIX = "AmqpHandler:";
|
|
10
76
|
/**
|
|
@@ -16,30 +82,49 @@ const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
16
82
|
* so a consumer the contract does not declare is a compile error here.
|
|
17
83
|
*
|
|
18
84
|
* There is no name to give: the contract key IS the port's name.
|
|
85
|
+
*
|
|
86
|
+
* `unit` declares the ports the handler reads off `context.unit`, resolved out
|
|
87
|
+
* of the fork the delivery opened; the root's `unit.message` module must export
|
|
88
|
+
* every one of them.
|
|
19
89
|
*/
|
|
20
90
|
const AmqpHandler = (contract, key) => {
|
|
21
91
|
const port = class extends Port(`${HANDLER_PREFIX}${key}`) {};
|
|
22
|
-
return
|
|
92
|
+
return (options) => {
|
|
93
|
+
const record = options.unit ?? {};
|
|
94
|
+
return Object.assign(Provider(port)({
|
|
95
|
+
inject: options.inject,
|
|
96
|
+
sync: (services) => withUnit(record, options.sync(services))
|
|
97
|
+
}), { unit: record });
|
|
98
|
+
};
|
|
23
99
|
};
|
|
24
100
|
//#endregion
|
|
25
101
|
//#region src/message-units.ts
|
|
26
102
|
/**
|
|
27
|
-
* Open one kernel unit per delivery
|
|
28
|
-
* the
|
|
29
|
-
* unit
|
|
103
|
+
* Open one kernel unit per delivery, forking `unit` — when one is bound — after
|
|
104
|
+
* the message is validated, before the handler runs; the fork is torn down
|
|
105
|
+
* when the unit closes. It is seeded with the validated delivery on
|
|
106
|
+
* `AmqpMessage(contract)`, so a unit module derives a tenant from the message
|
|
107
|
+
* rather than reading one off an ambient record. With no `unit` bound, `next()`
|
|
108
|
+
* runs unchanged, so the ambient `currentUnit()` record is what the unit leaves
|
|
109
|
+
* for the adapters that read it.
|
|
110
|
+
*
|
|
111
|
+
* The forked context rides the dispatcher's context under {@link UNIT_SCOPE},
|
|
112
|
+
* where each piece's own wrapper turns it into the typed `context.unit` record
|
|
113
|
+
* that piece declared — the records live with the pieces, so a hand-composed
|
|
114
|
+
* `amqp()` gets them without threading a second option through the starter.
|
|
30
115
|
*
|
|
31
116
|
* **The kernel's per-unit `AbortSignal` rides that record too**, and it is the
|
|
32
117
|
* only route to it here: a handler has no parameter to receive one through. A
|
|
33
118
|
* handler that must stop when the kernel stops waiting reads
|
|
34
119
|
* `currentUnit()?.signal`.
|
|
35
120
|
*/
|
|
36
|
-
const messageUnits = (host, observers) => (args, next) => {
|
|
121
|
+
const messageUnits = (host, observers, unit) => (args, next) => {
|
|
37
122
|
const settle = observe(observers, {
|
|
38
123
|
component: "amqp",
|
|
39
124
|
name: "delivery",
|
|
40
125
|
attributes: { handler: args.handlerName }
|
|
41
126
|
});
|
|
42
|
-
return host.run(metaFor(args.rawMessage), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
|
|
127
|
+
return host.run(metaFor(args.rawMessage), (scope) => unit === void 0 ? next() : scope.fork(unit, [[AmqpMessagePort, args.message]]).flatMap((forked) => next({ context: { [UNIT_SCOPE]: forked } }))).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
|
|
43
128
|
outcome: "error",
|
|
44
129
|
cause: failure.tag === "Err" ? failure.error : failure.cause
|
|
45
130
|
}));
|
|
@@ -69,17 +154,6 @@ const metaFor = (raw) => {
|
|
|
69
154
|
traceId: fromParent ?? inbound ?? id
|
|
70
155
|
};
|
|
71
156
|
};
|
|
72
|
-
/**
|
|
73
|
-
* The trace id inside a W3C `traceparent` header, which outranks `messageId`
|
|
74
|
-
* because it is the one value minted to span processes. Only the trace-id field
|
|
75
|
-
* is taken: `traceId` is a correlation id, not a span context, so the parent's
|
|
76
|
-
* span id is dropped rather than half-carried. The all-zero id is the spec's own
|
|
77
|
-
* "invalid" and is refused like a malformed header.
|
|
78
|
-
*/
|
|
79
|
-
const traceIdOfTraceparent = (header) => {
|
|
80
|
-
const traceId = /^[\da-f]{2}-([\da-f]{32})-[\da-f]{16}-[\da-f]{2}$/.exec(header.trim())?.[1];
|
|
81
|
-
return traceId === void 0 || /^0{32}$/.test(traceId) ? void 0 : traceId;
|
|
82
|
-
};
|
|
83
157
|
//#endregion
|
|
84
158
|
//#region src/amqp-runtime.ts
|
|
85
159
|
/**
|
|
@@ -154,12 +228,12 @@ const amqp = (options) => {
|
|
|
154
228
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
155
229
|
* ```
|
|
156
230
|
*
|
|
157
|
-
* The first is
|
|
158
|
-
*
|
|
159
|
-
* key)` builds: they are the provider's deps,
|
|
160
|
-
* piece's port id carries, so the services
|
|
161
|
-
* declared key must be covered, and two
|
|
162
|
-
* duplicate-provider defect at build.
|
|
231
|
+
* The first is `{ inject, unit?, sync }`, whose `sync` hands back the whole
|
|
232
|
+
* handlers record — one `unit:` record for every entry in it. The second takes
|
|
233
|
+
* the pieces `AmqpHandler(contract, key)` builds: they are the provider's deps,
|
|
234
|
+
* keyed by the contract key each piece's port id carries, so the services
|
|
235
|
+
* record IS the handlers record. Every declared key must be covered, and two
|
|
236
|
+
* slices claiming one key are di's duplicate-provider defect at build.
|
|
163
237
|
*/
|
|
164
238
|
const AmqpHandlers = (contract) => {
|
|
165
239
|
const build = Provider(AmqpHandlersPort);
|
|
@@ -167,7 +241,14 @@ const AmqpHandlers = (contract) => {
|
|
|
167
241
|
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(12), piece.port])),
|
|
168
242
|
sync: (services) => services
|
|
169
243
|
});
|
|
170
|
-
|
|
244
|
+
const whole = (options) => {
|
|
245
|
+
const record = options.unit ?? {};
|
|
246
|
+
return build({
|
|
247
|
+
inject: options.inject,
|
|
248
|
+
sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
return ((first) => Array.isArray(first) ? compose(first) : whole(first));
|
|
171
252
|
};
|
|
172
253
|
const startFailed = (cause) => new RuntimeStartFailed({
|
|
173
254
|
runtime: "amqp",
|
|
@@ -185,7 +266,7 @@ const queuesOf = (contract) => [...new Set(Object.values({
|
|
|
185
266
|
const createWorker = (host, config, options, handlers, observers) => TypedAmqpWorker.create({
|
|
186
267
|
contract: options.contract,
|
|
187
268
|
handlers,
|
|
188
|
-
middleware: messageUnits(host, observers),
|
|
269
|
+
middleware: messageUnits(host, observers, options.unit?.message),
|
|
189
270
|
urls: [config.url],
|
|
190
271
|
...options.connectionOptions === void 0 ? {} : { connectionOptions: options.connectionOptions },
|
|
191
272
|
...options.defaultConsumerOptions === void 0 ? {} : { defaultConsumerOptions: options.defaultConsumerOptions },
|
|
@@ -237,4 +318,4 @@ const AmqpModule = (name) => (options) => {
|
|
|
237
318
|
});
|
|
238
319
|
};
|
|
239
320
|
//#endregion
|
|
240
|
-
export { AmqpConfig, AmqpHandler, AmqpHandlers, AmqpModule, AmqpRuntime, amqp };
|
|
321
|
+
export { AmqpConfig, AmqpHandler, AmqpHandlers, AmqpMessage, AmqpModule, AmqpRuntime, amqp };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/amqp-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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,7 +47,7 @@
|
|
|
47
47
|
"@amqp-contract/contract": "3.0.0-beta.7",
|
|
48
48
|
"@amqp-contract/testing": "3.0.0-beta.7",
|
|
49
49
|
"@amqp-contract/worker": "3.0.0-beta.7",
|
|
50
|
-
"@btravstack/tsconfig": "0.
|
|
50
|
+
"@btravstack/tsconfig": "0.4.0",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@types/node": "26.4.0",
|
|
53
53
|
"@unthrown/vitest": "5.7.0",
|
|
@@ -57,19 +57,19 @@
|
|
|
57
57
|
"unthrown": "5.7.0",
|
|
58
58
|
"vitest": "4.1.11",
|
|
59
59
|
"zod": "4.5.3",
|
|
60
|
-
"@btravstack/config": "0.
|
|
61
|
-
"@btravstack/core": "0.
|
|
62
|
-
"@btravstack/di": "0.
|
|
60
|
+
"@btravstack/config": "0.13.0",
|
|
61
|
+
"@btravstack/core": "0.13.0",
|
|
62
|
+
"@btravstack/di": "0.13.0",
|
|
63
63
|
"@btravstack/internal-test-infra": "0.0.0",
|
|
64
|
-
"@btravstack/testing": "0.
|
|
64
|
+
"@btravstack/testing": "0.13.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"@amqp-contract/worker": "^3.0.0-beta.7",
|
|
68
68
|
"@opentelemetry/api": "^1",
|
|
69
69
|
"unthrown": "^5.0.0",
|
|
70
|
-
"@btravstack/config": "^0.
|
|
71
|
-
"@btravstack/core": "^0.
|
|
72
|
-
"@btravstack/di": "^0.
|
|
70
|
+
"@btravstack/config": "^0.13.0",
|
|
71
|
+
"@btravstack/core": "^0.13.0",
|
|
72
|
+
"@btravstack/di": "^0.13.0"
|
|
73
73
|
},
|
|
74
74
|
"engines": {
|
|
75
75
|
"node": ">=22"
|