@btravstack/amqp-worker 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +17 -71
- package/dist/index.d.cts +6 -49
- package/dist/index.d.mts +6 -49
- package/dist/index.mjs +18 -72
- package/package.json +19 -18
package/dist/index.cjs
CHANGED
|
@@ -61,46 +61,11 @@ const brokerLog = (observers) => {
|
|
|
61
61
|
//#endregion
|
|
62
62
|
//#region src/unit.ts
|
|
63
63
|
/**
|
|
64
|
-
* Where `messageUnits` leaves the forked context for the piece wrappers to read.
|
|
65
|
-
* A symbol rather than a name: the dispatcher merges it into the context every
|
|
66
|
-
* handler sees, and a string key would appear on the record a handler written
|
|
67
|
-
* against the `{ inject, sync }` arm destructures.
|
|
68
|
-
*/
|
|
69
|
-
const UNIT_SCOPE = Symbol("@btravstack/amqp-worker/unit-scope");
|
|
70
|
-
/**
|
|
71
|
-
* The declared record, as a getter per name resolved on read out of the fork.
|
|
72
|
-
* Neither writable nor configurable — a handler reads what the fork holds, and
|
|
73
|
-
* cannot reshape the record under the next delivery. With no `unit.message`
|
|
74
|
-
* bound there is nothing to resolve from, so the record is empty whatever was
|
|
75
|
-
* declared.
|
|
76
|
-
*/
|
|
77
|
-
const unitRecordOf = (forked, record) => {
|
|
78
|
-
const unit = {};
|
|
79
|
-
if (forked === void 0) return unit;
|
|
80
|
-
for (const [name, port] of Object.entries(record)) Object.defineProperty(unit, name, {
|
|
81
|
-
enumerable: true,
|
|
82
|
-
get: () => forked.get(port)
|
|
83
|
-
});
|
|
84
|
-
return unit;
|
|
85
|
-
};
|
|
86
|
-
/**
|
|
87
64
|
* One handler entry — a function, or `[handler, options]` — with its piece's
|
|
88
65
|
* declared record put on `context.unit`. Applied once per piece as di
|
|
89
66
|
* constructs it, so a delivery costs one record and one context object.
|
|
90
67
|
*/
|
|
91
|
-
const withUnit = (record, entry) =>
|
|
92
|
-
const wrap = (handler) => (helpers, message) => {
|
|
93
|
-
const { [UNIT_SCOPE]: forked, ...rest } = helpers.context ?? {};
|
|
94
|
-
return handler({
|
|
95
|
-
...helpers,
|
|
96
|
-
context: {
|
|
97
|
-
...rest,
|
|
98
|
-
unit: unitRecordOf(forked, record)
|
|
99
|
-
}
|
|
100
|
-
}, message);
|
|
101
|
-
};
|
|
102
|
-
return Array.isArray(entry) ? [wrap(entry[0]), entry[1]] : wrap(entry);
|
|
103
|
-
};
|
|
68
|
+
const withUnit = (record, entry) => Array.isArray(entry) ? [(0, _btravstack_core.withUnitRecord)(record, entry[0]), entry[1]] : (0, _btravstack_core.withUnitRecord)(record, entry);
|
|
104
69
|
//#endregion
|
|
105
70
|
//#region src/handler.ts
|
|
106
71
|
/** One id, declared once, so no contract instantiating it warns about a duplicate. */
|
|
@@ -144,13 +109,8 @@ const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
144
109
|
*/
|
|
145
110
|
const AmqpHandler = (contract, key) => {
|
|
146
111
|
const port = class extends (0, _btravstack_di.Port)(`${HANDLER_PREFIX}${key}`) {};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return Object.assign((0, _btravstack_di.Provider)(port)({
|
|
150
|
-
inject: options.inject,
|
|
151
|
-
sync: (services) => withUnit(record, options.sync(services))
|
|
152
|
-
}), { unit: record });
|
|
153
|
-
};
|
|
112
|
+
const mint = (0, _btravstack_core.mintPiece)(port, withUnit);
|
|
113
|
+
return (options) => mint(options);
|
|
154
114
|
};
|
|
155
115
|
//#endregion
|
|
156
116
|
//#region src/message-units.ts
|
|
@@ -163,9 +123,8 @@ const AmqpHandler = (contract, key) => {
|
|
|
163
123
|
* runs unchanged, so the ambient `currentUnit()` record is what the unit leaves
|
|
164
124
|
* for the adapters that read it.
|
|
165
125
|
*
|
|
166
|
-
* The forked context rides the dispatcher's context
|
|
167
|
-
*
|
|
168
|
-
* that piece declared — the records live with the pieces, so a hand-composed
|
|
126
|
+
* The forked context rides the dispatcher's context, where each piece's own
|
|
127
|
+
* wrapper turns it into the typed `context.unit` record that piece declared — the records live with the pieces, so a hand-composed
|
|
169
128
|
* `amqp()` gets them without threading a second option through the starter.
|
|
170
129
|
*
|
|
171
130
|
* **The kernel's per-unit `AbortSignal` rides that record too**, and it is the
|
|
@@ -173,17 +132,19 @@ const AmqpHandler = (contract, key) => {
|
|
|
173
132
|
* handler that must stop when the kernel stops waiting reads
|
|
174
133
|
* `currentUnit()?.signal`.
|
|
175
134
|
*/
|
|
176
|
-
const messageUnits = (host, observers, unit) => (args, next) => {
|
|
177
|
-
|
|
135
|
+
const messageUnits = (host, observers, unit) => (args, next) => (0, _btravstack_core.dispatchUnit)({
|
|
136
|
+
host,
|
|
137
|
+
observers,
|
|
138
|
+
operation: {
|
|
178
139
|
component: "amqp",
|
|
179
140
|
name: "delivery",
|
|
180
141
|
attributes: { handler: args.handlerName }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
};
|
|
142
|
+
},
|
|
143
|
+
meta: metaFor(args.rawMessage),
|
|
144
|
+
unit,
|
|
145
|
+
seed: [AmqpMessagePort, args.message],
|
|
146
|
+
next
|
|
147
|
+
});
|
|
187
148
|
/**
|
|
188
149
|
* `UnitMeta.id` must be unique per unit, and a **delivery tag is not one**: tags
|
|
189
150
|
* are per-channel and restart at `1` after a reconnect, which
|
|
@@ -255,10 +216,7 @@ const amqp = (options) => {
|
|
|
255
216
|
needs: [_btravstack_config.Env, AmqpHandlersPort],
|
|
256
217
|
provides: [
|
|
257
218
|
config,
|
|
258
|
-
|
|
259
|
-
inject: {},
|
|
260
|
-
value: _btravstack_core.noObserver
|
|
261
|
-
}),
|
|
219
|
+
_btravstack_core.noObserverMember,
|
|
262
220
|
(0, _btravstack_di.Provider)(AmqpRuntime)({
|
|
263
221
|
inject: {
|
|
264
222
|
config: AmqpConfig,
|
|
@@ -291,19 +249,7 @@ const amqp = (options) => {
|
|
|
291
249
|
* slices claiming one key are di's duplicate-provider defect at build.
|
|
292
250
|
*/
|
|
293
251
|
const AmqpHandlers = (contract) => {
|
|
294
|
-
|
|
295
|
-
const compose = (pieces) => build({
|
|
296
|
-
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(12), piece.port])),
|
|
297
|
-
sync: (services) => services
|
|
298
|
-
});
|
|
299
|
-
const whole = (options) => {
|
|
300
|
-
const record = options.unit ?? {};
|
|
301
|
-
return build({
|
|
302
|
-
inject: options.inject,
|
|
303
|
-
sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
|
|
304
|
-
});
|
|
305
|
-
};
|
|
306
|
-
return ((first) => Array.isArray(first) ? compose(first) : whole(first));
|
|
252
|
+
return (0, _btravstack_core.composeByPrefix)(AmqpHandlersPort, HANDLER_PREFIX, withUnit);
|
|
307
253
|
};
|
|
308
254
|
const startFailed = (cause) => new _btravstack_core.RuntimeStartFailed({
|
|
309
255
|
runtime: "amqp",
|
package/dist/index.d.cts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
|
2
|
-
import {
|
|
2
|
+
import { AnyUnitModule, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
3
|
+
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, ServiceOf } from "@btravstack/di";
|
|
3
4
|
import { ConsumerOptions, TypedAmqpWorker, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
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
|
|
19
5
|
//#region src/handler.d.ts
|
|
20
6
|
/** The consumer and rpc names `C` declares — the keys of its handlers record. */
|
|
21
7
|
type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & string;
|
|
@@ -65,8 +51,6 @@ declare const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
65
51
|
/** What a minted piece returns. */
|
|
66
52
|
type MintedHandler<C extends AnyAmqpContract, K extends HandlerKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>> = Provider<InstanceType<HandlerPortOf<C, K>>, never, N> & {
|
|
67
53
|
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
54
|
/** Phantom: the ports this piece injects, which the root's `unit.message` must export. */
|
|
71
55
|
readonly _declared?: InstanceType<U[keyof U]>;
|
|
72
56
|
};
|
|
@@ -114,22 +98,8 @@ export declare class AmqpConfig extends AmqpConfig_base<{
|
|
|
114
98
|
}> {}
|
|
115
99
|
/** The runtime's port: what `amqp()` provides, and what the module `start` boots must export. */
|
|
116
100
|
export declare class AmqpRuntime extends RuntimePort<Runtime<never, AmqpInfo>> {}
|
|
117
|
-
/**
|
|
118
|
-
|
|
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;
|
|
101
|
+
/** The needs a bound `unit.message` module still owes, less the delivery the fork's seed discharges. */
|
|
102
|
+
type UnitNeedsOf$1<Unit> = UnitNeedsOf<Unit, AmqpMessageInstance>;
|
|
133
103
|
/**
|
|
134
104
|
* The contract type `TypedAmqpWorker.create` accepts, extracted rather than
|
|
135
105
|
* imported by name so `@amqp-contract/contract` stays out of the peer range.
|
|
@@ -192,7 +162,7 @@ type AmqpTuning<Unit extends AnyUnitModule | undefined = undefined> = {
|
|
|
192
162
|
*
|
|
193
163
|
* With `url` pinned the module reads nothing from the environment.
|
|
194
164
|
*/
|
|
195
|
-
export declare const amqp: <TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined>(options: AmqpOptions<TContract, Unit>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
165
|
+
export declare const amqp: <TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined>(options: AmqpOptions<TContract, Unit>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf$1<Unit>>;
|
|
196
166
|
/** One piece of the handlers record — what `AmqpHandler(contract, key)(…)` returns, as the composing form consumes it. */
|
|
197
167
|
type PieceOf<C extends AnyAmqpContract> = { readonly [K in HandlerKeyOf<C>]: {
|
|
198
168
|
readonly port: HandlerPortOf<C, K>;
|
|
@@ -211,19 +181,6 @@ type KeyOfPiece<P> = P extends {
|
|
|
211
181
|
} ? K : never;
|
|
212
182
|
/** The consumers and rpcs no piece in `T` covers. */
|
|
213
183
|
type Uncovered<C extends AnyAmqpContract, T extends readonly PieceOf<C>[]> = Exclude<HandlerKeyOf<C>, KeyOfPiece<T[number]>>;
|
|
214
|
-
/**
|
|
215
|
-
* A refused array: as long as the array the caller wrote, its head the caller's
|
|
216
|
-
* own elements — which match — and its LAST element the marker paired with what
|
|
217
|
-
* is wrong.
|
|
218
|
-
*
|
|
219
|
-
* TypeScript compares two equal-length tuples element by element, so the extra
|
|
220
|
-
* diagnostic it reports lands on the trailing element and carries both the
|
|
221
|
-
* sentence and the missing key. A fixed two-element tuple named the key only
|
|
222
|
-
* when the array happened to be two elements long; every other arity was a
|
|
223
|
-
* length mismatch, and the developer diffed the contract against the array by
|
|
224
|
-
* hand.
|
|
225
|
-
*/
|
|
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
184
|
/**
|
|
228
185
|
* The record arm: the whole handlers record from one `sync`, with one `unit:`
|
|
229
186
|
* record shared by every entry in it.
|
|
@@ -280,7 +237,7 @@ export declare const AmqpHandlers: <C extends AnyAmqpContract>(contract: C) => W
|
|
|
280
237
|
//#endregion
|
|
281
238
|
//#region src/amqp-module.d.ts
|
|
282
239
|
/** The starter's own module, as the sugar adds it to the application's imports. */
|
|
283
|
-
type AmqpStarter<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
240
|
+
type AmqpStarter<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf$1<Unit>>;
|
|
284
241
|
/** The application's imports plus the starter — the tuple `Module(name)` is handed. */
|
|
285
242
|
type Imports<I extends readonly AnyModule[], TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = readonly [...I, AmqpStarter<TContract, Unit>];
|
|
286
243
|
/** The handlers provider plus the application's own — the tuple `Module(name)` is handed. */
|
package/dist/index.d.mts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider,
|
|
1
|
+
import { AnyModule, AnyPort, AnyProvider, Exportable, Module, NeedsGate, PortClassOf, PortInstance, Provider, ServiceOf } from "@btravstack/di";
|
|
2
2
|
import { ConsumerOptions, TypedAmqpWorker, WorkerInferHandlers } from "@amqp-contract/worker";
|
|
3
3
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
|
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
|
|
4
|
+
import { AnyUnitModule, Refuse, Runtime, RuntimePort, UnitGate, UnitNeedsOf, UnitRecordOf } from "@btravstack/core";
|
|
19
5
|
//#region src/handler.d.ts
|
|
20
6
|
/** The consumer and rpc names `C` declares — the keys of its handlers record. */
|
|
21
7
|
type HandlerKeyOf<C extends AnyAmqpContract> = keyof WorkerInferHandlers<C> & string;
|
|
@@ -65,8 +51,6 @@ declare const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
65
51
|
/** What a minted piece returns. */
|
|
66
52
|
type MintedHandler<C extends AnyAmqpContract, K extends HandlerKeyOf<C>, N, U extends Readonly<Record<string, AnyPort>>> = Provider<InstanceType<HandlerPortOf<C, K>>, never, N> & {
|
|
67
53
|
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
54
|
/** Phantom: the ports this piece injects, which the root's `unit.message` must export. */
|
|
71
55
|
readonly _declared?: InstanceType<U[keyof U]>;
|
|
72
56
|
};
|
|
@@ -114,22 +98,8 @@ export declare class AmqpConfig extends AmqpConfig_base<{
|
|
|
114
98
|
}> {}
|
|
115
99
|
/** The runtime's port: what `amqp()` provides, and what the module `start` boots must export. */
|
|
116
100
|
export declare class AmqpRuntime extends RuntimePort<Runtime<never, AmqpInfo>> {}
|
|
117
|
-
/**
|
|
118
|
-
|
|
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;
|
|
101
|
+
/** The needs a bound `unit.message` module still owes, less the delivery the fork's seed discharges. */
|
|
102
|
+
type UnitNeedsOf$1<Unit> = UnitNeedsOf<Unit, AmqpMessageInstance>;
|
|
133
103
|
/**
|
|
134
104
|
* The contract type `TypedAmqpWorker.create` accepts, extracted rather than
|
|
135
105
|
* imported by name so `@amqp-contract/contract` stays out of the peer range.
|
|
@@ -192,7 +162,7 @@ type AmqpTuning<Unit extends AnyUnitModule | undefined = undefined> = {
|
|
|
192
162
|
*
|
|
193
163
|
* With `url` pinned the module reads nothing from the environment.
|
|
194
164
|
*/
|
|
195
|
-
export declare const amqp: <TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined>(options: AmqpOptions<TContract, Unit>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
165
|
+
export declare const amqp: <TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined = undefined>(options: AmqpOptions<TContract, Unit>) => Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf$1<Unit>>;
|
|
196
166
|
/** One piece of the handlers record — what `AmqpHandler(contract, key)(…)` returns, as the composing form consumes it. */
|
|
197
167
|
type PieceOf<C extends AnyAmqpContract> = { readonly [K in HandlerKeyOf<C>]: {
|
|
198
168
|
readonly port: HandlerPortOf<C, K>;
|
|
@@ -211,19 +181,6 @@ type KeyOfPiece<P> = P extends {
|
|
|
211
181
|
} ? K : never;
|
|
212
182
|
/** The consumers and rpcs no piece in `T` covers. */
|
|
213
183
|
type Uncovered<C extends AnyAmqpContract, T extends readonly PieceOf<C>[]> = Exclude<HandlerKeyOf<C>, KeyOfPiece<T[number]>>;
|
|
214
|
-
/**
|
|
215
|
-
* A refused array: as long as the array the caller wrote, its head the caller's
|
|
216
|
-
* own elements — which match — and its LAST element the marker paired with what
|
|
217
|
-
* is wrong.
|
|
218
|
-
*
|
|
219
|
-
* TypeScript compares two equal-length tuples element by element, so the extra
|
|
220
|
-
* diagnostic it reports lands on the trailing element and carries both the
|
|
221
|
-
* sentence and the missing key. A fixed two-element tuple named the key only
|
|
222
|
-
* when the array happened to be two elements long; every other arity was a
|
|
223
|
-
* length mismatch, and the developer diffed the contract against the array by
|
|
224
|
-
* hand.
|
|
225
|
-
*/
|
|
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
184
|
/**
|
|
228
185
|
* The record arm: the whole handlers record from one `sync`, with one `unit:`
|
|
229
186
|
* record shared by every entry in it.
|
|
@@ -280,7 +237,7 @@ export declare const AmqpHandlers: <C extends AnyAmqpContract>(contract: C) => W
|
|
|
280
237
|
//#endregion
|
|
281
238
|
//#region src/amqp-module.d.ts
|
|
282
239
|
/** The starter's own module, as the sugar adds it to the application's imports. */
|
|
283
|
-
type AmqpStarter<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf<Unit>>;
|
|
240
|
+
type AmqpStarter<TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = Module<AmqpRuntime | AmqpConfig, ConfigInvalid, Env | HandlersInstanceOf<TContract> | UnitNeedsOf$1<Unit>>;
|
|
284
241
|
/** The application's imports plus the starter — the tuple `Module(name)` is handed. */
|
|
285
242
|
type Imports<I extends readonly AnyModule[], TContract extends AnyAmqpContract, Unit extends AnyUnitModule | undefined> = readonly [...I, AmqpStarter<TContract, Unit>];
|
|
286
243
|
/** The handlers provider plus the application's own — the tuple `Module(name)` is handed. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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,
|
|
4
|
+
import { Observers, RuntimePort, RuntimeStartFailed, composeByPrefix, dispatchUnit, mintPiece, noObserverMember, observe, releasedBy, traceIdOfTraceparent, withUnitRecord } from "@btravstack/core";
|
|
5
5
|
import { P } from "unthrown";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
//#region src/broker-log.ts
|
|
@@ -60,46 +60,11 @@ const brokerLog = (observers) => {
|
|
|
60
60
|
//#endregion
|
|
61
61
|
//#region src/unit.ts
|
|
62
62
|
/**
|
|
63
|
-
* Where `messageUnits` leaves the forked context for the piece wrappers to read.
|
|
64
|
-
* A symbol rather than a name: the dispatcher merges it into the context every
|
|
65
|
-
* handler sees, and a string key would appear on the record a handler written
|
|
66
|
-
* against the `{ inject, sync }` arm destructures.
|
|
67
|
-
*/
|
|
68
|
-
const UNIT_SCOPE = Symbol("@btravstack/amqp-worker/unit-scope");
|
|
69
|
-
/**
|
|
70
|
-
* The declared record, as a getter per name resolved on read out of the fork.
|
|
71
|
-
* Neither writable nor configurable — a handler reads what the fork holds, and
|
|
72
|
-
* cannot reshape the record under the next delivery. With no `unit.message`
|
|
73
|
-
* bound there is nothing to resolve from, so the record is empty whatever was
|
|
74
|
-
* declared.
|
|
75
|
-
*/
|
|
76
|
-
const unitRecordOf = (forked, record) => {
|
|
77
|
-
const unit = {};
|
|
78
|
-
if (forked === void 0) return unit;
|
|
79
|
-
for (const [name, port] of Object.entries(record)) Object.defineProperty(unit, name, {
|
|
80
|
-
enumerable: true,
|
|
81
|
-
get: () => forked.get(port)
|
|
82
|
-
});
|
|
83
|
-
return unit;
|
|
84
|
-
};
|
|
85
|
-
/**
|
|
86
63
|
* One handler entry — a function, or `[handler, options]` — with its piece's
|
|
87
64
|
* declared record put on `context.unit`. Applied once per piece as di
|
|
88
65
|
* constructs it, so a delivery costs one record and one context object.
|
|
89
66
|
*/
|
|
90
|
-
const withUnit = (record, entry) =>
|
|
91
|
-
const wrap = (handler) => (helpers, message) => {
|
|
92
|
-
const { [UNIT_SCOPE]: forked, ...rest } = helpers.context ?? {};
|
|
93
|
-
return handler({
|
|
94
|
-
...helpers,
|
|
95
|
-
context: {
|
|
96
|
-
...rest,
|
|
97
|
-
unit: unitRecordOf(forked, record)
|
|
98
|
-
}
|
|
99
|
-
}, message);
|
|
100
|
-
};
|
|
101
|
-
return Array.isArray(entry) ? [wrap(entry[0]), entry[1]] : wrap(entry);
|
|
102
|
-
};
|
|
67
|
+
const withUnit = (record, entry) => Array.isArray(entry) ? [withUnitRecord(record, entry[0]), entry[1]] : withUnitRecord(record, entry);
|
|
103
68
|
//#endregion
|
|
104
69
|
//#region src/handler.ts
|
|
105
70
|
/** One id, declared once, so no contract instantiating it warns about a duplicate. */
|
|
@@ -143,13 +108,8 @@ const HANDLER_PREFIX = "AmqpHandler:";
|
|
|
143
108
|
*/
|
|
144
109
|
const AmqpHandler = (contract, key) => {
|
|
145
110
|
const port = class extends Port(`${HANDLER_PREFIX}${key}`) {};
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return Object.assign(Provider(port)({
|
|
149
|
-
inject: options.inject,
|
|
150
|
-
sync: (services) => withUnit(record, options.sync(services))
|
|
151
|
-
}), { unit: record });
|
|
152
|
-
};
|
|
111
|
+
const mint = mintPiece(port, withUnit);
|
|
112
|
+
return (options) => mint(options);
|
|
153
113
|
};
|
|
154
114
|
//#endregion
|
|
155
115
|
//#region src/message-units.ts
|
|
@@ -162,9 +122,8 @@ const AmqpHandler = (contract, key) => {
|
|
|
162
122
|
* runs unchanged, so the ambient `currentUnit()` record is what the unit leaves
|
|
163
123
|
* for the adapters that read it.
|
|
164
124
|
*
|
|
165
|
-
* The forked context rides the dispatcher's context
|
|
166
|
-
*
|
|
167
|
-
* that piece declared — the records live with the pieces, so a hand-composed
|
|
125
|
+
* The forked context rides the dispatcher's context, where each piece's own
|
|
126
|
+
* wrapper turns it into the typed `context.unit` record that piece declared — the records live with the pieces, so a hand-composed
|
|
168
127
|
* `amqp()` gets them without threading a second option through the starter.
|
|
169
128
|
*
|
|
170
129
|
* **The kernel's per-unit `AbortSignal` rides that record too**, and it is the
|
|
@@ -172,17 +131,19 @@ const AmqpHandler = (contract, key) => {
|
|
|
172
131
|
* handler that must stop when the kernel stops waiting reads
|
|
173
132
|
* `currentUnit()?.signal`.
|
|
174
133
|
*/
|
|
175
|
-
const messageUnits = (host, observers, unit) => (args, next) => {
|
|
176
|
-
|
|
134
|
+
const messageUnits = (host, observers, unit) => (args, next) => dispatchUnit({
|
|
135
|
+
host,
|
|
136
|
+
observers,
|
|
137
|
+
operation: {
|
|
177
138
|
component: "amqp",
|
|
178
139
|
name: "delivery",
|
|
179
140
|
attributes: { handler: args.handlerName }
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
};
|
|
141
|
+
},
|
|
142
|
+
meta: metaFor(args.rawMessage),
|
|
143
|
+
unit,
|
|
144
|
+
seed: [AmqpMessagePort, args.message],
|
|
145
|
+
next
|
|
146
|
+
});
|
|
186
147
|
/**
|
|
187
148
|
* `UnitMeta.id` must be unique per unit, and a **delivery tag is not one**: tags
|
|
188
149
|
* are per-channel and restart at `1` after a reconnect, which
|
|
@@ -254,10 +215,7 @@ const amqp = (options) => {
|
|
|
254
215
|
needs: [Env, AmqpHandlersPort],
|
|
255
216
|
provides: [
|
|
256
217
|
config,
|
|
257
|
-
|
|
258
|
-
inject: {},
|
|
259
|
-
value: noObserver
|
|
260
|
-
}),
|
|
218
|
+
noObserverMember,
|
|
261
219
|
Provider(AmqpRuntime)({
|
|
262
220
|
inject: {
|
|
263
221
|
config: AmqpConfig,
|
|
@@ -290,19 +248,7 @@ const amqp = (options) => {
|
|
|
290
248
|
* slices claiming one key are di's duplicate-provider defect at build.
|
|
291
249
|
*/
|
|
292
250
|
const AmqpHandlers = (contract) => {
|
|
293
|
-
|
|
294
|
-
const compose = (pieces) => build({
|
|
295
|
-
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(12), piece.port])),
|
|
296
|
-
sync: (services) => services
|
|
297
|
-
});
|
|
298
|
-
const whole = (options) => {
|
|
299
|
-
const record = options.unit ?? {};
|
|
300
|
-
return build({
|
|
301
|
-
inject: options.inject,
|
|
302
|
-
sync: (services) => Object.fromEntries(Object.entries(options.sync(services)).map(([key, entry]) => [key, withUnit(record, entry)]))
|
|
303
|
-
});
|
|
304
|
-
};
|
|
305
|
-
return ((first) => Array.isArray(first) ? compose(first) : whole(first));
|
|
251
|
+
return composeByPrefix(AmqpHandlersPort, HANDLER_PREFIX, withUnit);
|
|
306
252
|
};
|
|
307
253
|
const startFailed = (cause) => new RuntimeStartFailed({
|
|
308
254
|
runtime: "amqp",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/amqp-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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,30 +44,31 @@
|
|
|
44
44
|
"./package.json": "./package.json"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@amqp-contract/contract": "3.0.0-beta.
|
|
48
|
-
"@amqp-contract/testing": "3.0.0-beta.
|
|
49
|
-
"@amqp-contract/worker": "3.0.0-beta.
|
|
50
|
-
"@btravstack/config": "0.
|
|
51
|
-
"@btravstack/core": "0.
|
|
52
|
-
"@btravstack/di": "0.
|
|
47
|
+
"@amqp-contract/contract": "3.0.0-beta.11",
|
|
48
|
+
"@amqp-contract/testing": "3.0.0-beta.11",
|
|
49
|
+
"@amqp-contract/worker": "3.0.0-beta.11",
|
|
50
|
+
"@btravstack/config": "0.18.0",
|
|
51
|
+
"@btravstack/core": "0.18.0",
|
|
52
|
+
"@btravstack/di": "0.18.0",
|
|
53
53
|
"@btravstack/internal-test-infra": "0.0.0",
|
|
54
|
-
"@btravstack/testing": "0.
|
|
54
|
+
"@btravstack/testing": "0.18.0",
|
|
55
55
|
"@btravstack/tsconfig": "0.4.0",
|
|
56
56
|
"@opentelemetry/api": "1.9.1",
|
|
57
|
-
"@types/node": "26.4
|
|
58
|
-
"@unthrown/vitest": "5.
|
|
59
|
-
"@vitest/coverage-v8": "
|
|
57
|
+
"@types/node": "26.6.4",
|
|
58
|
+
"@unthrown/vitest": "5.12.0",
|
|
59
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
60
60
|
"tsdown": "0.23.0",
|
|
61
61
|
"typescript": "7.0.2",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
62
|
+
"typescript-consumer": "npm:typescript@5.9.3",
|
|
63
|
+
"unthrown": "5.12.0",
|
|
64
|
+
"vitest": "5.0.3",
|
|
65
|
+
"zod": "4.6.5"
|
|
65
66
|
},
|
|
66
67
|
"peerDependencies": {
|
|
67
68
|
"@amqp-contract/worker": "^3.0.0-beta.7",
|
|
68
|
-
"@btravstack/config": "^0.
|
|
69
|
-
"@btravstack/core": "^0.
|
|
70
|
-
"@btravstack/di": "^0.
|
|
69
|
+
"@btravstack/config": "^0.18.0",
|
|
70
|
+
"@btravstack/core": "^0.18.0",
|
|
71
|
+
"@btravstack/di": "^0.18.0",
|
|
71
72
|
"@opentelemetry/api": "^1",
|
|
72
73
|
"unthrown": "^5.0.0"
|
|
73
74
|
},
|
|
@@ -78,6 +79,6 @@
|
|
|
78
79
|
"build": "tsdown src/index.ts --format cjs,esm --dts --clean",
|
|
79
80
|
"dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
|
|
80
81
|
"test": "vitest run --coverage",
|
|
81
|
-
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test-d.json"
|
|
82
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test-d.json && node ./node_modules/typescript-consumer/bin/tsc --noEmit -p tsconfig.test-d.json"
|
|
82
83
|
}
|
|
83
84
|
}
|