@btravstack/amqp-worker 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -32
- package/dist/index.cjs +42 -18
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +44 -20
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
15
|
pnpm add @btravstack/amqp-worker @btravstack/core @btravstack/config @btravstack/di unthrown \
|
|
16
|
-
@amqp-contract/worker @opentelemetry/api
|
|
16
|
+
@amqp-contract/worker@^3.0.0-beta.7 @opentelemetry/api
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
All six are peer dependencies — install them (`@opentelemetry/api` because
|
|
@@ -50,37 +50,35 @@ import { ErrAsync, OkAsync, P } from "unthrown";
|
|
|
50
50
|
// built by di from the use cases it lists — no injected context — on the
|
|
51
51
|
// starter's own handlers port, typed by the contract (a consumer serves one
|
|
52
52
|
// handlers record, so there is nothing to name).
|
|
53
|
-
const orderHandlers = AmqpHandlers(orderContract)(
|
|
54
|
-
{ placeOrder: PlaceOrder },
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
(error) => new NonRetryableError(error._tag, error),
|
|
72
|
-
),
|
|
73
|
-
)
|
|
74
|
-
// Infrastructure failing is not: recover the defect into a retry.
|
|
75
|
-
.recoverDefect((cause) =>
|
|
76
|
-
ErrAsync(new RetryableError("placing the order failed", cause)),
|
|
53
|
+
const orderHandlers = AmqpHandlers(orderContract)({
|
|
54
|
+
inject: { placeOrder: PlaceOrder },
|
|
55
|
+
sync: ({ placeOrder }) => ({
|
|
56
|
+
orderNotifications: ({ input: message }) =>
|
|
57
|
+
placeOrder
|
|
58
|
+
.execute(
|
|
59
|
+
TenantId(message.payload.tenantId),
|
|
60
|
+
message.payload.id,
|
|
61
|
+
message.payload.payload?.quantity ?? 0,
|
|
62
|
+
)
|
|
63
|
+
.map(() => undefined)
|
|
64
|
+
// A modeled domain error is permanent: dead-letter it, no retry.
|
|
65
|
+
.mapErrCases((matcher) =>
|
|
66
|
+
matcher.with(
|
|
67
|
+
P.tag("InvalidQuantity"),
|
|
68
|
+
P.tag("InvalidOrderId"),
|
|
69
|
+
P.tag("DuplicateOrder"),
|
|
70
|
+
(error) => new NonRetryableError(error._tag, error),
|
|
77
71
|
),
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
)
|
|
73
|
+
// Infrastructure failing is not: recover the defect into a retry.
|
|
74
|
+
.recoverDefect((cause) =>
|
|
75
|
+
ErrAsync(new RetryableError("placing the order failed", cause)),
|
|
76
|
+
),
|
|
77
|
+
// Every consumer the contract declares must be covered — an uncovered
|
|
78
|
+
// key is refused at this call, not on the first delivery.
|
|
79
|
+
orderAudit: () => OkAsync(undefined),
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
84
82
|
|
|
85
83
|
const Worker = AmqpModule("Worker")({
|
|
86
84
|
needs: [Env],
|
|
@@ -99,7 +97,7 @@ call, not on the first delivery. `runtimeInfo()` reads `{ queues }` back once
|
|
|
99
97
|
consuming.
|
|
100
98
|
|
|
101
99
|
A worker with several consumers can be several slices instead of one record:
|
|
102
|
-
`AmqpHandler(contract, key)({ name: Dep }, arm)` mints a provider for ONE consumer or
|
|
100
|
+
`AmqpHandler(contract, key)({ inject: { name: Dep }, ...arm })` mints a provider for ONE consumer or
|
|
103
101
|
rpc, typed by the key alone, and `AmqpHandlers(contract)([...])` composes an
|
|
104
102
|
array of them into the same handlers provider `AmqpModule` takes — the array
|
|
105
103
|
must cover every key the contract declares, and each piece's own port must
|
|
@@ -108,6 +106,10 @@ pieces' ports, not what they close over. Two slices claiming one key are di's
|
|
|
108
106
|
duplicate-provider defect at build, which is the point: a consumer belongs to
|
|
109
107
|
exactly one slice.
|
|
110
108
|
|
|
109
|
+
Its own test suite needs a **Docker daemon**: the specs run against the shared
|
|
110
|
+
`rabbitmq` container `internal/test-infra` starts once per machine and every
|
|
111
|
+
workspace reuses. Nothing else here does.
|
|
112
|
+
|
|
111
113
|
## Options
|
|
112
114
|
|
|
113
115
|
`AmqpModule(name)({...})` takes `amqp()`'s options plus `handlers` and the
|
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,17 @@ const AmqpHandler = (contract, key) => {
|
|
|
34
34
|
* handler that must stop when the kernel stops waiting reads
|
|
35
35
|
* `currentUnit()?.signal`.
|
|
36
36
|
*/
|
|
37
|
-
const messageUnits = (host
|
|
37
|
+
const messageUnits = (host, observers) => (args, next) => {
|
|
38
|
+
const settle = (0, _btravstack_core.observe)(observers, {
|
|
39
|
+
component: "amqp",
|
|
40
|
+
name: "delivery",
|
|
41
|
+
attributes: { handler: args.handlerName }
|
|
42
|
+
});
|
|
43
|
+
return host.run(metaFor(args.rawMessage), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
|
|
44
|
+
outcome: "error",
|
|
45
|
+
cause: failure.tag === "Err" ? failure.error : failure.cause
|
|
46
|
+
}));
|
|
47
|
+
};
|
|
38
48
|
/**
|
|
39
49
|
* `UnitMeta.id` must be unique per unit, and a **delivery tag is not one**: tags
|
|
40
50
|
* are per-channel and restart at `1` after a reconnect, which
|
|
@@ -115,27 +125,38 @@ const amqp = (options) => {
|
|
|
115
125
|
}));
|
|
116
126
|
return (0, _btravstack_di.Module)("Amqp")({
|
|
117
127
|
needs: [_btravstack_config.Env, AmqpHandlersPort],
|
|
118
|
-
provides: [
|
|
119
|
-
config
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
provides: [
|
|
129
|
+
config,
|
|
130
|
+
_btravstack_di.Provider.member(_btravstack_core.Observers)({
|
|
131
|
+
inject: {},
|
|
132
|
+
value: _btravstack_core.noObserver
|
|
133
|
+
}),
|
|
134
|
+
(0, _btravstack_di.Provider)(AmqpRuntime)({
|
|
135
|
+
inject: {
|
|
136
|
+
config: AmqpConfig,
|
|
137
|
+
handlers: AmqpHandlersPort,
|
|
138
|
+
observers: _btravstack_core.Observers
|
|
139
|
+
},
|
|
140
|
+
sync: ({ config: bound, handlers, observers }) => ({
|
|
141
|
+
name: "amqp",
|
|
142
|
+
resolves: [],
|
|
143
|
+
start: (host) => createWorker(host, bound, options, handlers, observers)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
],
|
|
126
147
|
exports: [AmqpRuntime, AmqpConfig]
|
|
127
148
|
});
|
|
128
149
|
};
|
|
129
150
|
/**
|
|
130
|
-
* The handlers as a provider, from the contract.
|
|
151
|
+
* The handlers as a provider, from the contract. Two call forms, one port.
|
|
131
152
|
*
|
|
132
153
|
* ```ts
|
|
133
|
-
* AmqpHandlers(orderContract)({ logger: Logger },
|
|
154
|
+
* AmqpHandlers(orderContract)({ inject: { logger: Logger }, sync: ({ logger }) => ({ orderNotifications: (m) => … }) })
|
|
134
155
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
135
156
|
* ```
|
|
136
157
|
*
|
|
137
|
-
* The first
|
|
138
|
-
* typed for the contract. The
|
|
158
|
+
* The first is di's own `Provider(port)` on the starter's handlers port
|
|
159
|
+
* typed for the contract. The second takes the pieces `AmqpHandler(contract,
|
|
139
160
|
* key)` builds: they are the provider's deps, keyed by the contract key each
|
|
140
161
|
* piece's port id carries, so the services record IS the handlers record. Every
|
|
141
162
|
* declared key must be covered, and two slices claiming one key are di's
|
|
@@ -143,8 +164,11 @@ const amqp = (options) => {
|
|
|
143
164
|
*/
|
|
144
165
|
const AmqpHandlers = (contract) => {
|
|
145
166
|
const build = (0, _btravstack_di.Provider)(AmqpHandlersPort);
|
|
146
|
-
const compose = (pieces) => build(
|
|
147
|
-
|
|
167
|
+
const compose = (pieces) => build({
|
|
168
|
+
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(12), piece.port])),
|
|
169
|
+
sync: (services) => services
|
|
170
|
+
});
|
|
171
|
+
return ((first) => Array.isArray(first) ? compose(first) : build(first));
|
|
148
172
|
};
|
|
149
173
|
const startFailed = (cause) => new _btravstack_core.RuntimeStartFailed({
|
|
150
174
|
runtime: "amqp",
|
|
@@ -159,15 +183,15 @@ const queuesOf = (contract) => [...new Set(Object.values({
|
|
|
159
183
|
...contract.consumers,
|
|
160
184
|
...contract.rpcs
|
|
161
185
|
}).map((entry) => entry.queue.name))].sort();
|
|
162
|
-
const createWorker = (host, config, options, handlers) => _amqp_contract_worker.TypedAmqpWorker.create({
|
|
186
|
+
const createWorker = (host, config, options, handlers, observers) => _amqp_contract_worker.TypedAmqpWorker.create({
|
|
163
187
|
contract: options.contract,
|
|
164
188
|
handlers,
|
|
165
|
-
middleware: messageUnits(host),
|
|
189
|
+
middleware: messageUnits(host, observers),
|
|
166
190
|
urls: [config.url],
|
|
167
191
|
...options.connectionOptions === void 0 ? {} : { connectionOptions: options.connectionOptions },
|
|
168
192
|
...options.defaultConsumerOptions === void 0 ? {} : { defaultConsumerOptions: options.defaultConsumerOptions },
|
|
169
193
|
connectTimeoutMs: config.connectTimeoutMs
|
|
170
|
-
}).map((worker) => consume(worker, queuesOf(options.contract))).
|
|
194
|
+
}).map((worker) => consume(worker, queuesOf(options.contract))).mapErrCases((matcher) => matcher.with(unthrown.P.tag("@amqp-contract/ConnectionError"), (error) => startFailed(error)));
|
|
171
195
|
const consume = (worker, queues) => {
|
|
172
196
|
let closing;
|
|
173
197
|
const beginClose = () => closing ??= worker.close({ drainTimeoutMs: null });
|
package/dist/index.d.cts
CHANGED
|
@@ -123,15 +123,15 @@ type Compose<C extends AnyAmqpContract> = <const T extends readonly PieceOf<C>[]
|
|
|
123
123
|
readonly port: HandlersPortOf<C>;
|
|
124
124
|
};
|
|
125
125
|
/**
|
|
126
|
-
* The handlers as a provider, from the contract.
|
|
126
|
+
* The handlers as a provider, from the contract. Two call forms, one port.
|
|
127
127
|
*
|
|
128
128
|
* ```ts
|
|
129
|
-
* AmqpHandlers(orderContract)({ logger: Logger },
|
|
129
|
+
* AmqpHandlers(orderContract)({ inject: { logger: Logger }, sync: ({ logger }) => ({ orderNotifications: (m) => … }) })
|
|
130
130
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
131
131
|
* ```
|
|
132
132
|
*
|
|
133
|
-
* The first
|
|
134
|
-
* typed for the contract. The
|
|
133
|
+
* The first is di's own `Provider(port)` on the starter's handlers port
|
|
134
|
+
* typed for the contract. The second takes the pieces `AmqpHandler(contract,
|
|
135
135
|
* key)` builds: they are the provider's deps, keyed by the contract key each
|
|
136
136
|
* piece's port id carries, so the services record IS the handlers record. Every
|
|
137
137
|
* declared key must be covered, and two slices claiming one key are di's
|
package/dist/index.d.mts
CHANGED
|
@@ -123,15 +123,15 @@ type Compose<C extends AnyAmqpContract> = <const T extends readonly PieceOf<C>[]
|
|
|
123
123
|
readonly port: HandlersPortOf<C>;
|
|
124
124
|
};
|
|
125
125
|
/**
|
|
126
|
-
* The handlers as a provider, from the contract.
|
|
126
|
+
* The handlers as a provider, from the contract. Two call forms, one port.
|
|
127
127
|
*
|
|
128
128
|
* ```ts
|
|
129
|
-
* AmqpHandlers(orderContract)({ logger: Logger },
|
|
129
|
+
* AmqpHandlers(orderContract)({ inject: { logger: Logger }, sync: ({ logger }) => ({ orderNotifications: (m) => … }) })
|
|
130
130
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
131
131
|
* ```
|
|
132
132
|
*
|
|
133
|
-
* The first
|
|
134
|
-
* typed for the contract. The
|
|
133
|
+
* The first is di's own `Provider(port)` on the starter's handlers port
|
|
134
|
+
* typed for the contract. The second takes the pieces `AmqpHandler(contract,
|
|
135
135
|
* key)` builds: they are the provider's deps, keyed by the contract key each
|
|
136
136
|
* piece's port id carries, so the services record IS the handlers record. Every
|
|
137
137
|
* declared key must be covered, and two slices claiming one key are di's
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Module, Port, Provider } from "@btravstack/di";
|
|
2
2
|
import { TypedAmqpWorker } from "@amqp-contract/worker";
|
|
3
3
|
import { Config, Env } from "@btravstack/config";
|
|
4
|
-
import { RuntimePort, RuntimeStartFailed, releasedBy } from "@btravstack/core";
|
|
5
|
-
import {
|
|
4
|
+
import { Observers, RuntimePort, RuntimeStartFailed, noObserver, observe, releasedBy } from "@btravstack/core";
|
|
5
|
+
import { P } from "unthrown";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
//#region src/handler.ts
|
|
8
8
|
/** The prefix a piece's port id carries; the composing form strips it to recover the key. */
|
|
@@ -33,7 +33,17 @@ const AmqpHandler = (contract, key) => {
|
|
|
33
33
|
* handler that must stop when the kernel stops waiting reads
|
|
34
34
|
* `currentUnit()?.signal`.
|
|
35
35
|
*/
|
|
36
|
-
const messageUnits = (host
|
|
36
|
+
const messageUnits = (host, observers) => (args, next) => {
|
|
37
|
+
const settle = observe(observers, {
|
|
38
|
+
component: "amqp",
|
|
39
|
+
name: "delivery",
|
|
40
|
+
attributes: { handler: args.handlerName }
|
|
41
|
+
});
|
|
42
|
+
return host.run(metaFor(args.rawMessage), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
|
|
43
|
+
outcome: "error",
|
|
44
|
+
cause: failure.tag === "Err" ? failure.error : failure.cause
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
37
47
|
/**
|
|
38
48
|
* `UnitMeta.id` must be unique per unit, and a **delivery tag is not one**: tags
|
|
39
49
|
* are per-channel and restart at `1` after a reconnect, which
|
|
@@ -114,27 +124,38 @@ const amqp = (options) => {
|
|
|
114
124
|
}));
|
|
115
125
|
return Module("Amqp")({
|
|
116
126
|
needs: [Env, AmqpHandlersPort],
|
|
117
|
-
provides: [
|
|
118
|
-
config
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
provides: [
|
|
128
|
+
config,
|
|
129
|
+
Provider.member(Observers)({
|
|
130
|
+
inject: {},
|
|
131
|
+
value: noObserver
|
|
132
|
+
}),
|
|
133
|
+
Provider(AmqpRuntime)({
|
|
134
|
+
inject: {
|
|
135
|
+
config: AmqpConfig,
|
|
136
|
+
handlers: AmqpHandlersPort,
|
|
137
|
+
observers: Observers
|
|
138
|
+
},
|
|
139
|
+
sync: ({ config: bound, handlers, observers }) => ({
|
|
140
|
+
name: "amqp",
|
|
141
|
+
resolves: [],
|
|
142
|
+
start: (host) => createWorker(host, bound, options, handlers, observers)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
],
|
|
125
146
|
exports: [AmqpRuntime, AmqpConfig]
|
|
126
147
|
});
|
|
127
148
|
};
|
|
128
149
|
/**
|
|
129
|
-
* The handlers as a provider, from the contract.
|
|
150
|
+
* The handlers as a provider, from the contract. Two call forms, one port.
|
|
130
151
|
*
|
|
131
152
|
* ```ts
|
|
132
|
-
* AmqpHandlers(orderContract)({ logger: Logger },
|
|
153
|
+
* AmqpHandlers(orderContract)({ inject: { logger: Logger }, sync: ({ logger }) => ({ orderNotifications: (m) => … }) })
|
|
133
154
|
* AmqpHandlers(orderContract)([orderNotifications, orderAudit])
|
|
134
155
|
* ```
|
|
135
156
|
*
|
|
136
|
-
* The first
|
|
137
|
-
* typed for the contract. The
|
|
157
|
+
* The first is di's own `Provider(port)` on the starter's handlers port
|
|
158
|
+
* typed for the contract. The second takes the pieces `AmqpHandler(contract,
|
|
138
159
|
* key)` builds: they are the provider's deps, keyed by the contract key each
|
|
139
160
|
* piece's port id carries, so the services record IS the handlers record. Every
|
|
140
161
|
* declared key must be covered, and two slices claiming one key are di's
|
|
@@ -142,8 +163,11 @@ const amqp = (options) => {
|
|
|
142
163
|
*/
|
|
143
164
|
const AmqpHandlers = (contract) => {
|
|
144
165
|
const build = Provider(AmqpHandlersPort);
|
|
145
|
-
const compose = (pieces) => build(
|
|
146
|
-
|
|
166
|
+
const compose = (pieces) => build({
|
|
167
|
+
inject: Object.fromEntries(pieces.map((piece) => [piece.port.portId.slice(12), piece.port])),
|
|
168
|
+
sync: (services) => services
|
|
169
|
+
});
|
|
170
|
+
return ((first) => Array.isArray(first) ? compose(first) : build(first));
|
|
147
171
|
};
|
|
148
172
|
const startFailed = (cause) => new RuntimeStartFailed({
|
|
149
173
|
runtime: "amqp",
|
|
@@ -158,15 +182,15 @@ const queuesOf = (contract) => [...new Set(Object.values({
|
|
|
158
182
|
...contract.consumers,
|
|
159
183
|
...contract.rpcs
|
|
160
184
|
}).map((entry) => entry.queue.name))].sort();
|
|
161
|
-
const createWorker = (host, config, options, handlers) => TypedAmqpWorker.create({
|
|
185
|
+
const createWorker = (host, config, options, handlers, observers) => TypedAmqpWorker.create({
|
|
162
186
|
contract: options.contract,
|
|
163
187
|
handlers,
|
|
164
|
-
middleware: messageUnits(host),
|
|
188
|
+
middleware: messageUnits(host, observers),
|
|
165
189
|
urls: [config.url],
|
|
166
190
|
...options.connectionOptions === void 0 ? {} : { connectionOptions: options.connectionOptions },
|
|
167
191
|
...options.defaultConsumerOptions === void 0 ? {} : { defaultConsumerOptions: options.defaultConsumerOptions },
|
|
168
192
|
connectTimeoutMs: config.connectTimeoutMs
|
|
169
|
-
}).map((worker) => consume(worker, queuesOf(options.contract))).
|
|
193
|
+
}).map((worker) => consume(worker, queuesOf(options.contract))).mapErrCases((matcher) => matcher.with(P.tag("@amqp-contract/ConnectionError"), (error) => startFailed(error)));
|
|
170
194
|
const consume = (worker, queues) => {
|
|
171
195
|
let closing;
|
|
172
196
|
const beginClose = () => closing ??= worker.close({ drainTimeoutMs: null });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/amqp-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "The AMQP starter for @btravstack/core: the handlers as a port, one unit per delivery, and a drain the kernel's deadline releases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"amqp",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"./package.json": "./package.json"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@amqp-contract/contract": "3.0.0-beta.
|
|
48
|
-
"@amqp-contract/testing": "3.0.0-beta.
|
|
49
|
-
"@amqp-contract/worker": "3.0.0-beta.
|
|
47
|
+
"@amqp-contract/contract": "3.0.0-beta.7",
|
|
48
|
+
"@amqp-contract/testing": "3.0.0-beta.7",
|
|
49
|
+
"@amqp-contract/worker": "3.0.0-beta.7",
|
|
50
50
|
"@btravstack/tsconfig": "0.2.0",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@types/node": "26.4.0",
|
|
@@ -56,20 +56,20 @@
|
|
|
56
56
|
"typescript": "7.0.2",
|
|
57
57
|
"unthrown": "5.7.0",
|
|
58
58
|
"vitest": "4.1.11",
|
|
59
|
-
"zod": "4.5.
|
|
60
|
-
"@btravstack/config": "0.
|
|
61
|
-
"@btravstack/core": "0.
|
|
62
|
-
"@btravstack/di": "0.
|
|
59
|
+
"zod": "4.5.3",
|
|
60
|
+
"@btravstack/config": "0.10.0",
|
|
61
|
+
"@btravstack/core": "0.10.0",
|
|
62
|
+
"@btravstack/di": "0.10.0",
|
|
63
63
|
"@btravstack/internal-test-infra": "0.0.0",
|
|
64
|
-
"@btravstack/testing": "0.
|
|
64
|
+
"@btravstack/testing": "0.10.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@amqp-contract/worker": "^3.0.0-beta.
|
|
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.10.0",
|
|
71
|
+
"@btravstack/core": "^0.10.0",
|
|
72
|
+
"@btravstack/di": "^0.10.0"
|
|
73
73
|
},
|
|
74
74
|
"engines": {
|
|
75
75
|
"node": ">=22"
|