@dudousxd/nestjs-diagnostics 0.8.1 → 0.9.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/CHANGELOG.md +16 -0
- package/README.md +29 -0
- package/dist/channel.d.ts +1 -1
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +3 -1
- package/dist/channel.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/nestjs/delivery.d.ts +92 -0
- package/dist/nestjs/delivery.d.ts.map +1 -0
- package/dist/nestjs/delivery.js +2 -0
- package/dist/nestjs/delivery.js.map +1 -0
- package/dist/nestjs/diagnostics.explorer.d.ts +51 -11
- package/dist/nestjs/diagnostics.explorer.d.ts.map +1 -1
- package/dist/nestjs/diagnostics.explorer.js +209 -53
- package/dist/nestjs/diagnostics.explorer.js.map +1 -1
- package/dist/nestjs/diagnostics.module.d.ts +9 -2
- package/dist/nestjs/diagnostics.module.d.ts.map +1 -1
- package/dist/nestjs/diagnostics.module.js +23 -3
- package/dist/nestjs/diagnostics.module.js.map +1 -1
- package/dist/nestjs/diagnostics.options.d.ts +31 -0
- package/dist/nestjs/diagnostics.options.d.ts.map +1 -0
- package/dist/nestjs/diagnostics.options.js +3 -0
- package/dist/nestjs/diagnostics.options.js.map +1 -0
- package/dist/nestjs/index.d.ts +6 -3
- package/dist/nestjs/index.d.ts.map +1 -1
- package/dist/nestjs/index.js +4 -2
- package/dist/nestjs/index.js.map +1 -1
- package/dist/nestjs/on-diagnostic.decorator.d.ts +33 -7
- package/dist/nestjs/on-diagnostic.decorator.d.ts.map +1 -1
- package/dist/nestjs/on-diagnostic.decorator.js +0 -9
- package/dist/nestjs/on-diagnostic.decorator.js.map +1 -1
- package/dist/nestjs/queue-transport.d.ts +47 -0
- package/dist/nestjs/queue-transport.d.ts.map +1 -0
- package/dist/nestjs/queue-transport.js +45 -0
- package/dist/nestjs/queue-transport.js.map +1 -0
- package/dist/nestjs/role.d.ts +13 -0
- package/dist/nestjs/role.d.ts.map +1 -0
- package/dist/nestjs/role.js +13 -0
- package/dist/nestjs/role.js.map +1 -0
- package/dist/nestjs/transports.d.ts +125 -0
- package/dist/nestjs/transports.d.ts.map +1 -0
- package/dist/nestjs/transports.js +127 -0
- package/dist/nestjs/transports.js.map +1 -0
- package/dist/redis-relay.d.ts +60 -0
- package/dist/redis-relay.d.ts.map +1 -0
- package/dist/redis-relay.js +170 -0
- package/dist/redis-relay.js.map +1 -0
- package/dist/relayed.d.ts +8 -0
- package/dist/relayed.d.ts.map +1 -0
- package/dist/relayed.js +31 -0
- package/dist/relayed.js.map +1 -0
- package/dist/trace.d.ts.map +1 -1
- package/dist/trace.js +2 -0
- package/dist/trace.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +14 -6
- package/skills/emit-diagnostics/SKILL.md +5 -2
- package/skills/react-with-on-diagnostic/SKILL.md +41 -13
- package/skills/trace-spans/SKILL.md +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @dudousxd/nestjs-diagnostics
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#81](https://github.com/DavideCarvalho/nestjs-diagnostics/pull/81) [`ebdacd0`](https://github.com/DavideCarvalho/nestjs-diagnostics/commit/ebdacd09676892eca5bf2d359b6edd8079600015) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Listeners with delivery transports and `role`.
|
|
8
|
+
|
|
9
|
+
- Every envelope from `emit` and `trace` carries a unique `id` (a UUID). Relays keep it, so the same event has the same `id` in every process.
|
|
10
|
+
- `@OnDiagnostic(lib, event?)` accepts a `RegExp` for `event`, and checks the handler's parameter against the `ChannelRegistry` payload for registered channels. Each listener has a stable id, `<ClassName>#<method>`; two listener classes with the same name fail the bootstrap.
|
|
11
|
+
- `DiagnosticsModule.forRoot({ default, transports, role, nodeId })` and `forRootAsync(...)` select how listeners run across processes, with the `transports` factory:
|
|
12
|
+
- `transports.redis({ pub, sub })`: broadcast over Redis pub/sub, best-effort.
|
|
13
|
+
- `transports.queue({ connection })`: one BullMQ job per `(listener, event)`, retried when the handler throws (optional peer `bullmq`).
|
|
14
|
+
- `transports.durable(options)`: one durable run per `(listener, event)` through `@dudousxd/nestjs-durable-diagnostics/delivery`, imported only when selected; bootstrap fails with a clear message when it is missing or durable is not configured.
|
|
15
|
+
With `queue` and `durable`, delivery happens once, from the emitting process.
|
|
16
|
+
- `role: 'all' | 'api' | 'worker'`: `'api'` emits and hands work to the transport but never consumes or runs listeners. `'api'`/`'worker'` without a `default` transport fail the bootstrap.
|
|
17
|
+
- The Redis relay now lives in `@dudousxd/nestjs-diagnostics` (`createDiagnosticsRedisRelay`); `@dudousxd/nestjs-diagnostics-redis` re-exports it. It takes `receive: false` for a publish-only relay (then `sub` is optional), and marks re-emitted copies (`isRelayed`).
|
|
18
|
+
|
|
3
19
|
## 0.8.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -184,6 +184,35 @@ handler that throws or rejects is logged and swallowed — it can never break th
|
|
|
184
184
|
code that emitted. `@nestjs/common` + `@nestjs/core` are optional peers; the main
|
|
185
185
|
barrel stays framework-agnostic.
|
|
186
186
|
|
|
187
|
+
`event` also takes a `RegExp`. Each listener has a stable id, `<ClassName>#<method>`.
|
|
188
|
+
|
|
189
|
+
### Across processes: transports and `role`
|
|
190
|
+
|
|
191
|
+
Pick a `default` transport to run listeners across processes:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { DiagnosticsModule, transports } from '@dudousxd/nestjs-diagnostics/nestjs';
|
|
195
|
+
|
|
196
|
+
DiagnosticsModule.forRoot({
|
|
197
|
+
role: 'api', // 'all' (default) | 'api' | 'worker'
|
|
198
|
+
default: 'queue',
|
|
199
|
+
transports: {
|
|
200
|
+
redis: transports.redis({ pub: redis, sub: redis.duplicate() }), // broadcast, best-effort
|
|
201
|
+
queue: transports.queue({ connection: { host: 'localhost', port: 6379 } }), // BullMQ, retried
|
|
202
|
+
durable: transports.durable({ retries: 5 }), // durable runs (@dudousxd/nestjs-durable-diagnostics)
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| transport | where the listener runs | lost if the process dies? | if the handler throws |
|
|
208
|
+
|---|---|---|---|
|
|
209
|
+
| none (local) | in the emitting process | yes | logged, dropped |
|
|
210
|
+
| `redis` | in every process (broadcast) | yes | logged, dropped |
|
|
211
|
+
| `queue` | in one worker | no | BullMQ retries the job (at-least-once) |
|
|
212
|
+
| `durable` | in one durable worker | no | durable retries with backoff; idempotent per event id |
|
|
213
|
+
|
|
214
|
+
See the [Listeners and transports](https://davidecarvalho.github.io/aviary/docs/diagnostics/listeners) docs.
|
|
215
|
+
|
|
187
216
|
## License
|
|
188
217
|
|
|
189
218
|
MIT
|
package/dist/channel.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare function getChannel(lib: string, event: string): Channel;
|
|
|
38
38
|
* - When `opts.sample` is given, it is consulted AFTER the `hasSubscribers` gate
|
|
39
39
|
* and BEFORE the envelope is built: a falsy result sheds this event without
|
|
40
40
|
* allocating anything. Default (no `sample`) always publishes when subscribed.
|
|
41
|
-
* - The envelope carries the current {@link SCHEMA_VERSION} as `v`.
|
|
41
|
+
* - The envelope carries the current {@link SCHEMA_VERSION} as `v` and a fresh UUID as `id`.
|
|
42
42
|
* - Never throws: emitting observability must not break the caller.
|
|
43
43
|
*
|
|
44
44
|
* ```ts
|
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AACA,OAA4B,EAAE,KAAK,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAG7E,OAAO,KAAK,EAAmB,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE1F,gEAAgE;AAChE,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9D;AAqBD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAc9D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,IAAI,CAAC,IAAI,SAAS,KAAK,EAAE,MAAM,SAAS,OAAO,CAAC,IAAI,CAAC,EACnE,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,EAChC,IAAI,CAAC,EAAE,WAAW,GACjB,IAAI,CAwBN"}
|
package/dist/channel.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import diagnostics_channel from 'node:diagnostics_channel';
|
|
2
3
|
import { resolveTraceId } from './context-accessor.js';
|
|
3
4
|
import { onRegistryReset, registerChannel } from './registry.js';
|
|
@@ -74,7 +75,7 @@ export function getChannel(lib, event) {
|
|
|
74
75
|
* - When `opts.sample` is given, it is consulted AFTER the `hasSubscribers` gate
|
|
75
76
|
* and BEFORE the envelope is built: a falsy result sheds this event without
|
|
76
77
|
* allocating anything. Default (no `sample`) always publishes when subscribed.
|
|
77
|
-
* - The envelope carries the current {@link SCHEMA_VERSION} as `v`.
|
|
78
|
+
* - The envelope carries the current {@link SCHEMA_VERSION} as `v` and a fresh UUID as `id`.
|
|
78
79
|
* - Never throws: emitting observability must not break the caller.
|
|
79
80
|
*
|
|
80
81
|
* ```ts
|
|
@@ -100,6 +101,7 @@ export function emit(lib, event, payload, opts) {
|
|
|
100
101
|
const traceId = opts?.traceId ?? resolveTraceId();
|
|
101
102
|
const envelope = {
|
|
102
103
|
v: SCHEMA_VERSION,
|
|
104
|
+
id: randomUUID(),
|
|
103
105
|
ts: Date.now(),
|
|
104
106
|
lib,
|
|
105
107
|
event,
|
package/dist/channel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAqC,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,KAAa;IACpD,OAAO,GAAG,cAAc,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,EAAgC,CAAC;AAE7D,8EAA8E;AAC9E,6EAA6E;AAC7E,eAAe,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,KAAa;IACnD,IAAI,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;QACrC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,eAAe,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC5B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,IAAI,CAClB,GAAS,EACT,KAAa,EACb,OAAgC,EAChC,IAAkB;IAElB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,CAAC,cAAc;QAAE,OAAO;IACpC,IAAI,CAAC;QACH,6EAA6E;QAC7E,4EAA4E;QAC5E,IAAI,IAAI,EAAE,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO;QACzD,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,cAAc,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAoB;YAChC,CAAC,EAAE,cAAc;YACjB,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,GAAG;YACH,KAAK;YACL,OAAO;YACP,OAAO;YACP,wEAAwE;YACxE,yEAAyE;YACzE,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;SACvE,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;IAC3D,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,mBAAqC,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,KAAa;IACpD,OAAO,GAAG,cAAc,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,EAAgC,CAAC;AAE7D,8EAA8E;AAC9E,6EAA6E;AAC7E,eAAe,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,KAAa;IACnD,IAAI,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;QACrC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,eAAe,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC5B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,IAAI,CAClB,GAAS,EACT,KAAa,EACb,OAAgC,EAChC,IAAkB;IAElB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,CAAC,cAAc;QAAE,OAAO;IACpC,IAAI,CAAC;QACH,6EAA6E;QAC7E,4EAA4E;QAC5E,IAAI,IAAI,EAAE,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO;QACzD,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,cAAc,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAoB;YAChC,CAAC,EAAE,cAAc;YACjB,EAAE,EAAE,UAAU,EAAE;YAChB,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,GAAG;YACH,KAAK;YACL,OAAO;YACP,OAAO;YACP,wEAAwE;YACxE,yEAAyE;YACzE,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;SACvE,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;IAC3D,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export { claimDiagnostics, isDiagnosticClaimed } from './claims.js';
|
|
|
5
5
|
export { assertCapabilityNaming } from './conformance.js';
|
|
6
6
|
export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, getContextAccessor, resolveTraceId, setContextAccessor, type UserRef, } from './context-accessor.js';
|
|
7
7
|
export { NESTJS_AUTHENTICATION_CHANNELS, NESTJS_AUTHORIZATION_CHANNELS, NESTJS_MAIL_CHANNELS, NESTJS_RESILIENCE_CHANNELS, NESTJS_WORKFLOWS_CHANNELS, OFFICIAL_CHANNEL_PREFIX, OFFICIAL_CHANNELS, OFFICIAL_PACKAGE_VERSIONS, type OfficialChannelName, type OfficialDiagnosticEvent, type OfficialLib, type OfficialListener, type OfficialType, officialChannelNames, subscribeOfficial, } from './official.js';
|
|
8
|
+
export { type ChannelRef, createDiagnosticsRedisRelay, type DiagnosticsRedisRelayOptions, type RedisLike, type RedisPipelineLike, } from './redis-relay.js';
|
|
9
|
+
export { isRelayed, publishRelayed } from './relayed.js';
|
|
8
10
|
export { onChannelRegistered, registerChannel, registeredChannels, resetRegistry, } from './registry.js';
|
|
9
11
|
export { SPAN_SCHEMA_VERSION, trace, type TraceChannelNames, traceChannelNames, type TracingChannel, tracingChannel, } from './trace.js';
|
|
10
12
|
export type { ChannelRegistry, DiagnosticEvent, EmitOptions, EventOf, LibOf, PayloadOf, SpanEvent, SpanPhase, TraceOptions, } from './types.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,KAAK,OAAO,GACb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC7B,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,KAAK,EACL,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,KAAK,cAAc,EACnB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,eAAe,EACf,eAAe,EACf,WAAW,EACX,OAAO,EACP,KAAK,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,KAAK,OAAO,GACb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC7B,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,UAAU,EACf,2BAA2B,EAC3B,KAAK,4BAA4B,EACjC,KAAK,SAAS,EACd,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,KAAK,EACL,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,KAAK,cAAc,EACnB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,eAAe,EACf,eAAe,EACf,WAAW,EACX,OAAO,EACP,KAAK,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@ export { claimDiagnostics, isDiagnosticClaimed } from './claims.js';
|
|
|
4
4
|
export { assertCapabilityNaming } from './conformance.js';
|
|
5
5
|
export { CONTEXT_ACCESSOR, getContextAccessor, resolveTraceId, setContextAccessor, } from './context-accessor.js';
|
|
6
6
|
export { NESTJS_AUTHENTICATION_CHANNELS, NESTJS_AUTHORIZATION_CHANNELS, NESTJS_MAIL_CHANNELS, NESTJS_RESILIENCE_CHANNELS, NESTJS_WORKFLOWS_CHANNELS, OFFICIAL_CHANNEL_PREFIX, OFFICIAL_CHANNELS, OFFICIAL_PACKAGE_VERSIONS, officialChannelNames, subscribeOfficial, } from './official.js';
|
|
7
|
+
export { createDiagnosticsRedisRelay, } from './redis-relay.js';
|
|
8
|
+
export { isRelayed, publishRelayed } from './relayed.js';
|
|
7
9
|
export { onChannelRegistered, registerChannel, registeredChannels, resetRegistry, } from './registry.js';
|
|
8
10
|
export { SPAN_SCHEMA_VERSION, trace, traceChannelNames, tracingChannel, } from './trace.js';
|
|
9
11
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAGhB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,GAEnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC7B,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EAMzB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,KAAK,EAEL,iBAAiB,EAEjB,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAGhB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,GAEnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,EAC7B,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EAMzB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,2BAA2B,GAI5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,KAAK,EAEL,iBAAiB,EAEjB,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The listener delivery contract. Types only: a transport package (such as
|
|
3
|
+
* `@dudousxd/nestjs-durable-diagnostics`) implements it with `import type` and never loads
|
|
4
|
+
* diagnostics at runtime.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle, in every process that boots `DiagnosticsModule` with a delivering transport selected:
|
|
7
|
+
*
|
|
8
|
+
* 1. At bootstrap, after listeners are discovered, the transport is created with a
|
|
9
|
+
* {@link DeliveryTransportContext}. Unless `role` is `'api'`, it must also set up whatever runs
|
|
10
|
+
* deliveries (a worker, a workflow) in THIS process, because any process may run them.
|
|
11
|
+
* 2. When a process emits an event that matches listeners, diagnostics calls
|
|
12
|
+
* {@link ListenerDeliveryTransport.deliver} in that process only, once per matching listener,
|
|
13
|
+
* off the emitter's call stack, batched per event loop turn, in emit order. Copies of the event
|
|
14
|
+
* that reached other processes through a relay are never delivered again.
|
|
15
|
+
* 3. Wherever the transport runs a delivery, it calls {@link DeliveryTransportContext.invoke}
|
|
16
|
+
* `(listenerId, envelope)`. The promise settles when the handler finishes and rejects with the
|
|
17
|
+
* handler's own error, so the transport can retry.
|
|
18
|
+
* 4. On application shutdown, diagnostics awaits {@link ListenerDeliveryTransport.stop}.
|
|
19
|
+
*/
|
|
20
|
+
import type { ModuleRef } from '@nestjs/core';
|
|
21
|
+
import type { DiagnosticEvent } from '../types.js';
|
|
22
|
+
import type { DiagnosticsRole } from './role.js';
|
|
23
|
+
/** An envelope handed to a delivering transport: `id` is always set. */
|
|
24
|
+
export type DeliveredEvent<TPayload = unknown> = DiagnosticEvent<TPayload> & {
|
|
25
|
+
id: string;
|
|
26
|
+
};
|
|
27
|
+
/** One unit of work: run listener `listenerId` for `envelope`. */
|
|
28
|
+
export interface ListenerDelivery {
|
|
29
|
+
/** Stable listener id, `<ClassName>#<method>`. The same in every process running the same app. */
|
|
30
|
+
listenerId: string;
|
|
31
|
+
/**
|
|
32
|
+
* The event, as emitted. `diag:<envelope.id>:<listenerId>` identifies this delivery uniquely:
|
|
33
|
+
* use it as the idempotency key.
|
|
34
|
+
*/
|
|
35
|
+
envelope: DeliveredEvent;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run listener `listenerId` for `envelope` in this process. Resolves when the handler finishes;
|
|
39
|
+
* rejects with the handler's error (retry it), or with an `UnknownDiagnosticListenerError`
|
|
40
|
+
* (`code: 'E_UNKNOWN_DIAGNOSTICS_LISTENER'`) when this process has no listener with that id.
|
|
41
|
+
*/
|
|
42
|
+
export type ListenerInvoker = (listenerId: string, envelope: DiagnosticEvent) => Promise<void>;
|
|
43
|
+
/** What a delivering transport receives when diagnostics creates it at bootstrap. */
|
|
44
|
+
export interface DeliveryTransportContext {
|
|
45
|
+
/** The application's module ref, to resolve providers. */
|
|
46
|
+
moduleRef: ModuleRef;
|
|
47
|
+
/** Id of this process (`nodeId` in the diagnostics options, or a random one). */
|
|
48
|
+
nodeId: string;
|
|
49
|
+
/** Which half of a split deployment this process is. On `'api'`, never run deliveries here. */
|
|
50
|
+
role: DiagnosticsRole;
|
|
51
|
+
/** Runs a listener in this process. See {@link ListenerInvoker}. */
|
|
52
|
+
invoke: ListenerInvoker;
|
|
53
|
+
/** Ids of the listeners registered in this process. */
|
|
54
|
+
listenerIds: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
/** A transport that owns listener execution (`queue`, `durable`). */
|
|
57
|
+
export interface ListenerDeliveryTransport {
|
|
58
|
+
/**
|
|
59
|
+
* Take ownership of these deliveries. Called only in the emitting process. Return (or resolve)
|
|
60
|
+
* once they are safely stored; a throw or rejection is logged and those deliveries are lost.
|
|
61
|
+
*/
|
|
62
|
+
deliver(deliveries: readonly ListenerDelivery[]): void | Promise<void>;
|
|
63
|
+
/** Release resources on shutdown. Awaited. */
|
|
64
|
+
stop?(): void | Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
/** Options of `transports.durable()`, passed to the durable step that runs each listener. */
|
|
67
|
+
export interface DurableTransportConfig {
|
|
68
|
+
/** Max attempts of a listener before its run fails. */
|
|
69
|
+
retries?: number;
|
|
70
|
+
/** How the delay between attempts grows: constant (`fixed`) or doubling (`exp`). */
|
|
71
|
+
backoff?: 'fixed' | 'exp';
|
|
72
|
+
/** Base delay between attempts, in ms. */
|
|
73
|
+
backoffMs?: number;
|
|
74
|
+
/** Upper bound of the exponential delay, in ms. */
|
|
75
|
+
backoffMaxMs?: number;
|
|
76
|
+
/** Add random jitter to the delay. */
|
|
77
|
+
jitter?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* What `@dudousxd/nestjs-durable-diagnostics/delivery` must export (as named exports) for
|
|
81
|
+
* `transports.durable()`. Diagnostics imports it only when durable is the selected transport.
|
|
82
|
+
*/
|
|
83
|
+
export interface DurableDiagnosticsDeliveryModule {
|
|
84
|
+
/**
|
|
85
|
+
* Whether durable is set up in this app (its engine resolves from `moduleRef`). Called at
|
|
86
|
+
* bootstrap, before {@link createDeliveryTransport}; `false` fails the bootstrap.
|
|
87
|
+
*/
|
|
88
|
+
isConfigured(moduleRef: ModuleRef): boolean | Promise<boolean>;
|
|
89
|
+
/** Create the delivering transport. Called once per process, at bootstrap. */
|
|
90
|
+
createDeliveryTransport(context: DeliveryTransportContext, config: DurableTransportConfig): ListenerDeliveryTransport | Promise<ListenerDeliveryTransport>;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=delivery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../../src/nestjs/delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,wEAAwE;AACxE,MAAM,MAAM,cAAc,CAAC,QAAQ,GAAG,OAAO,IAAI,eAAe,CAAC,QAAQ,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,kGAAkG;IAClG,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/F,qFAAqF;AACrF,MAAM,WAAW,wBAAwB;IACvC,0DAA0D;IAC1D,SAAS,EAAE,SAAS,CAAC;IACrB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,IAAI,EAAE,eAAe,CAAC;IACtB,oEAAoE;IACpE,MAAM,EAAE,eAAe,CAAC;IACxB,uDAAuD;IACvD,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,qEAAqE;AACrE,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,OAAO,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,8CAA8C;IAC9C,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,6FAA6F;AAC7F,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,8EAA8E;IAC9E,uBAAuB,CACrB,OAAO,EAAE,wBAAwB,EACjC,MAAM,EAAE,sBAAsB,GAC7B,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CACnE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery.js","sourceRoot":"","sources":["../../src/nestjs/delivery.ts"],"names":[],"mappings":""}
|
|
@@ -1,20 +1,60 @@
|
|
|
1
1
|
import { type OnApplicationBootstrap, type OnApplicationShutdown } from '@nestjs/common';
|
|
2
|
-
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
|
|
2
|
+
import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core';
|
|
3
|
+
import type { ListenerInvoker } from './delivery.js';
|
|
4
|
+
import { type DiagnosticsModuleOptions } from './diagnostics.options.js';
|
|
5
|
+
import { type OnDiagnosticMeta } from './on-diagnostic.decorator.js';
|
|
6
|
+
/** Thrown by the listener invoker for a listener id this process does not know. */
|
|
7
|
+
export declare class UnknownDiagnosticListenerError extends Error {
|
|
8
|
+
readonly listenerId: string;
|
|
9
|
+
readonly code = "E_UNKNOWN_DIAGNOSTICS_LISTENER";
|
|
10
|
+
constructor(listenerId: string);
|
|
11
|
+
}
|
|
12
|
+
/** A discovered `@OnDiagnostic` method. */
|
|
13
|
+
export interface RegisteredListener {
|
|
14
|
+
/** Stable id, `<ClassName>#<method>`. */
|
|
15
|
+
readonly id: string;
|
|
16
|
+
/** Where it listens (one entry per stacked decorator). */
|
|
17
|
+
readonly matchers: readonly OnDiagnosticMeta[];
|
|
18
|
+
readonly instance: Record<string, unknown>;
|
|
19
|
+
readonly method: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Discovers `@OnDiagnostic` methods, starts the `default` transport and routes every matching
|
|
23
|
+
* event: to the delivering transport (only in the emitting process), or straight to the handler.
|
|
24
|
+
*/
|
|
3
25
|
export declare class DiagnosticsExplorer implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
4
26
|
private readonly discovery;
|
|
5
27
|
private readonly scanner;
|
|
28
|
+
private readonly moduleRef;
|
|
6
29
|
private readonly logger;
|
|
7
|
-
private readonly
|
|
8
|
-
private readonly
|
|
30
|
+
private readonly listeners;
|
|
31
|
+
private readonly matchCache;
|
|
32
|
+
private readonly subscribed;
|
|
33
|
+
private readonly wildcardLibs;
|
|
34
|
+
private readonly pending;
|
|
35
|
+
private scheduled;
|
|
9
36
|
private offChannelRegistered;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
37
|
+
private transport;
|
|
38
|
+
private deliver;
|
|
39
|
+
private role;
|
|
40
|
+
private readonly options;
|
|
41
|
+
constructor(discovery: DiscoveryService, scanner: MetadataScanner, moduleRef: ModuleRef, options?: DiagnosticsModuleOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Run listener `listenerId` for `envelope` in this process. The handler is called synchronously;
|
|
44
|
+
* the promise rejects with its error.
|
|
45
|
+
*/
|
|
46
|
+
readonly invoke: ListenerInvoker;
|
|
47
|
+
/** Every listener discovered in this process. */
|
|
48
|
+
registeredListeners(): RegisteredListener[];
|
|
49
|
+
onApplicationBootstrap(): Promise<void>;
|
|
50
|
+
onApplicationShutdown(): Promise<void>;
|
|
51
|
+
private discover;
|
|
52
|
+
/** The listeners that receive an event on `aviary:<lib>:<event>`. */
|
|
53
|
+
private listenersFor;
|
|
54
|
+
private subscribeListeners;
|
|
14
55
|
private subscribe;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
private logError;
|
|
56
|
+
private readonly onEvent;
|
|
57
|
+
/** Hand this turn's deliveries to the transport, in emit order. Settles once they are handed over. */
|
|
58
|
+
private flush;
|
|
19
59
|
}
|
|
20
60
|
//# sourceMappingURL=diagnostics.explorer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.explorer.d.ts","sourceRoot":"","sources":["../../src/nestjs/diagnostics.explorer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diagnostics.explorer.d.ts","sourceRoot":"","sources":["../../src/nestjs/diagnostics.explorer.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAE3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK5E,OAAO,KAAK,EAAoC,eAAe,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAuB,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAIzF,mFAAmF;AACnF,qBAAa,8BAA+B,SAAQ,KAAK;IAE3C,QAAQ,CAAC,UAAU,EAAE,MAAM;IADvC,QAAQ,CAAC,IAAI,oCAAoC;gBAC5B,UAAU,EAAE,MAAM;CAKxC;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AA0BD;;;GAGG;AACH,qBACa,mBAAoB,YAAW,sBAAsB,EAAE,qBAAqB;IAe3D,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAhB/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyC;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2C;IACtE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,SAAS,CAAgD;IACjE,OAAO,CAAC,oBAAoB,CAA6B;IACzD,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;gBAGN,SAAS,EAAE,gBAAgB,EAC5B,OAAO,EAAE,eAAe,EAC9B,SAAS,EAAE,SAAS,EACf,OAAO,CAAC,EAAE,wBAAwB;IAK7E;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,eAAe,CAS9B;IAEF,iDAAiD;IACjD,mBAAmB,IAAI,kBAAkB,EAAE;IAIrC,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB5C,OAAO,CAAC,QAAQ;IAwBhB,qEAAqE;IACrE,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAmBtB;IAEF,sGAAsG;YACxF,KAAK;CAiBpB"}
|
|
@@ -10,96 +10,252 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
|
+
import { randomUUID } from 'node:crypto';
|
|
13
14
|
import diagnostics_channel from 'node:diagnostics_channel';
|
|
14
|
-
import { Inject, Injectable, Logger, } from '@nestjs/common';
|
|
15
|
-
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
|
|
15
|
+
import { Inject, Injectable, Logger, Optional, } from '@nestjs/common';
|
|
16
|
+
import { DiscoveryService, MetadataScanner, ModuleRef } from '@nestjs/core';
|
|
16
17
|
import { CHANNEL_PREFIX, channelName } from '../channel.js';
|
|
17
18
|
import { onChannelRegistered, registeredChannels } from '../registry.js';
|
|
19
|
+
import { isRelayed } from '../relayed.js';
|
|
20
|
+
import { DIAGNOSTICS_OPTIONS } from './diagnostics.options.js';
|
|
18
21
|
import { ON_DIAGNOSTIC_META } from './on-diagnostic.decorator.js';
|
|
22
|
+
import { resolveRole } from './role.js';
|
|
23
|
+
/** Thrown by the listener invoker for a listener id this process does not know. */
|
|
24
|
+
export class UnknownDiagnosticListenerError extends Error {
|
|
25
|
+
listenerId;
|
|
26
|
+
code = 'E_UNKNOWN_DIAGNOSTICS_LISTENER';
|
|
27
|
+
constructor(listenerId) {
|
|
28
|
+
super(`@dudousxd/nestjs-diagnostics: no listener "${listenerId}" is registered in this process. Every process that runs deliveries must register the same listeners.`);
|
|
29
|
+
this.listenerId = listenerId;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function matches(meta, lib, event) {
|
|
33
|
+
if (meta.lib !== lib)
|
|
34
|
+
return false;
|
|
35
|
+
const matcher = meta.event;
|
|
36
|
+
if (matcher === undefined)
|
|
37
|
+
return true;
|
|
38
|
+
if (typeof matcher === 'string')
|
|
39
|
+
return matcher === event;
|
|
40
|
+
matcher.lastIndex = 0;
|
|
41
|
+
return matcher.test(event);
|
|
42
|
+
}
|
|
43
|
+
/** A diagnostics POINT event (from `emit`). Span phase events from `trace` are never dispatched. */
|
|
44
|
+
function isPointEvent(msg) {
|
|
45
|
+
if (typeof msg !== 'object' || msg === null)
|
|
46
|
+
return false;
|
|
47
|
+
const env = msg;
|
|
48
|
+
if (typeof env.lib !== 'string' || typeof env.event !== 'string')
|
|
49
|
+
return false;
|
|
50
|
+
return env.phase === undefined || env.spanId === undefined;
|
|
51
|
+
}
|
|
52
|
+
function libOf(name) {
|
|
53
|
+
if (!name.startsWith(`${CHANNEL_PREFIX}:`))
|
|
54
|
+
return undefined;
|
|
55
|
+
const rest = name.slice(CHANNEL_PREFIX.length + 1);
|
|
56
|
+
const idx = rest.indexOf(':');
|
|
57
|
+
return idx > 0 ? rest.slice(0, idx) : undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Discovers `@OnDiagnostic` methods, starts the `default` transport and routes every matching
|
|
61
|
+
* event: to the delivering transport (only in the emitting process), or straight to the handler.
|
|
62
|
+
*/
|
|
19
63
|
let DiagnosticsExplorer = class DiagnosticsExplorer {
|
|
20
64
|
discovery;
|
|
21
65
|
scanner;
|
|
66
|
+
moduleRef;
|
|
22
67
|
logger = new Logger('DiagnosticsExplorer');
|
|
23
|
-
|
|
24
|
-
|
|
68
|
+
listeners = new Map();
|
|
69
|
+
matchCache = new Map();
|
|
70
|
+
subscribed = new Set();
|
|
71
|
+
wildcardLibs = new Set();
|
|
72
|
+
pending = [];
|
|
73
|
+
scheduled = null;
|
|
25
74
|
offChannelRegistered = null;
|
|
26
|
-
|
|
75
|
+
transport = null;
|
|
76
|
+
deliver = null;
|
|
77
|
+
role = 'all';
|
|
78
|
+
options;
|
|
79
|
+
constructor(discovery, scanner, moduleRef, options) {
|
|
27
80
|
this.discovery = discovery;
|
|
28
81
|
this.scanner = scanner;
|
|
82
|
+
this.moduleRef = moduleRef;
|
|
83
|
+
this.options = options ?? {};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run listener `listenerId` for `envelope` in this process. The handler is called synchronously;
|
|
87
|
+
* the promise rejects with its error.
|
|
88
|
+
*/
|
|
89
|
+
invoke = (listenerId, envelope) => {
|
|
90
|
+
const listener = this.listeners.get(listenerId);
|
|
91
|
+
if (!listener)
|
|
92
|
+
return Promise.reject(new UnknownDiagnosticListenerError(listenerId));
|
|
93
|
+
try {
|
|
94
|
+
const fn = listener.instance[listener.method];
|
|
95
|
+
return Promise.resolve(fn.call(listener.instance, envelope)).then(() => undefined);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return Promise.reject(error);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/** Every listener discovered in this process. */
|
|
102
|
+
registeredListeners() {
|
|
103
|
+
return [...this.listeners.values()];
|
|
104
|
+
}
|
|
105
|
+
async onApplicationBootstrap() {
|
|
106
|
+
this.role = resolveRole(this.options.role, this.options.default);
|
|
107
|
+
this.discover();
|
|
108
|
+
const name = this.options.default;
|
|
109
|
+
if (name) {
|
|
110
|
+
const provider = this.options.transports?.[name];
|
|
111
|
+
if (!provider) {
|
|
112
|
+
throw new Error(`@dudousxd/nestjs-diagnostics: default is "${name}", but transports.${name} is not defined`);
|
|
113
|
+
}
|
|
114
|
+
const listeners = this.registeredListeners();
|
|
115
|
+
this.transport = await provider({
|
|
116
|
+
moduleRef: this.moduleRef,
|
|
117
|
+
role: this.role,
|
|
118
|
+
nodeId: this.options.nodeId ?? randomUUID(),
|
|
119
|
+
listenerLibs: [...new Set(listeners.flatMap((l) => l.matchers.map((m) => m.lib)))],
|
|
120
|
+
listenerIds: listeners.map((l) => l.id),
|
|
121
|
+
invoke: this.invoke,
|
|
122
|
+
logger: this.logger,
|
|
123
|
+
});
|
|
124
|
+
this.deliver = this.transport.deliver?.bind(this.transport) ?? null;
|
|
125
|
+
}
|
|
126
|
+
// An 'api' process without a delivering transport has nothing to do with its listeners.
|
|
127
|
+
if (this.role === 'api' && !this.deliver)
|
|
128
|
+
return;
|
|
129
|
+
this.subscribeListeners();
|
|
29
130
|
}
|
|
30
|
-
|
|
131
|
+
async onApplicationShutdown() {
|
|
132
|
+
await this.flush();
|
|
133
|
+
this.offChannelRegistered?.();
|
|
134
|
+
this.offChannelRegistered = null;
|
|
135
|
+
for (const name of this.subscribed)
|
|
136
|
+
diagnostics_channel.channel(name).unsubscribe(this.onEvent);
|
|
137
|
+
this.subscribed.clear();
|
|
138
|
+
this.wildcardLibs.clear();
|
|
139
|
+
const transport = this.transport;
|
|
140
|
+
this.transport = null;
|
|
141
|
+
this.deliver = null;
|
|
142
|
+
try {
|
|
143
|
+
await transport?.stop?.();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.logger.error(`failed to stop the diagnostics transport: ${messageOf(error)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
discover() {
|
|
31
150
|
for (const wrapper of this.discovery.getProviders()) {
|
|
32
151
|
const instance = wrapper.instance;
|
|
33
152
|
if (!instance || typeof instance !== 'object')
|
|
34
153
|
continue;
|
|
35
154
|
const proto = Object.getPrototypeOf(instance);
|
|
36
|
-
for (const
|
|
37
|
-
const metas = Reflect.getMetadata(ON_DIAGNOSTIC_META, proto,
|
|
155
|
+
for (const method of this.scanner.getAllMethodNames(proto)) {
|
|
156
|
+
const metas = Reflect.getMetadata(ON_DIAGNOSTIC_META, proto, method);
|
|
38
157
|
if (!metas?.length)
|
|
39
158
|
continue;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
159
|
+
const id = `${instance.constructor.name}#${method}`;
|
|
160
|
+
const existing = this.listeners.get(id);
|
|
161
|
+
if (existing) {
|
|
162
|
+
if (existing.instance.constructor === instance.constructor)
|
|
163
|
+
continue;
|
|
164
|
+
throw new Error(`@dudousxd/nestjs-diagnostics: two listeners share the id "${id}". Listener ids are <ClassName>#<method> and must be unique; rename one class.`);
|
|
165
|
+
}
|
|
166
|
+
this.listeners.set(id, { id, matchers: metas, instance, method });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
this.matchCache.clear();
|
|
170
|
+
}
|
|
171
|
+
/** The listeners that receive an event on `aviary:<lib>:<event>`. */
|
|
172
|
+
listenersFor(lib, event) {
|
|
173
|
+
const key = `${lib}\0${event}`;
|
|
174
|
+
let hit = this.matchCache.get(key);
|
|
175
|
+
if (hit === undefined) {
|
|
176
|
+
hit = this.registeredListeners().filter((l) => l.matchers.some((m) => matches(m, lib, event)));
|
|
177
|
+
this.matchCache.set(key, hit);
|
|
178
|
+
}
|
|
179
|
+
return hit;
|
|
180
|
+
}
|
|
181
|
+
subscribeListeners() {
|
|
182
|
+
for (const listener of this.listeners.values()) {
|
|
183
|
+
for (const meta of listener.matchers) {
|
|
184
|
+
if (typeof meta.event === 'string') {
|
|
185
|
+
this.subscribe(channelName(meta.lib, meta.event));
|
|
186
|
+
}
|
|
187
|
+
else if (!this.wildcardLibs.has(meta.lib)) {
|
|
188
|
+
this.wildcardLibs.add(meta.lib);
|
|
189
|
+
for (const name of registeredChannels()) {
|
|
190
|
+
if (libOf(name) === meta.lib)
|
|
191
|
+
this.subscribe(name);
|
|
52
192
|
}
|
|
53
193
|
}
|
|
54
194
|
}
|
|
55
195
|
}
|
|
56
196
|
this.offChannelRegistered = onChannelRegistered((name) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
197
|
+
const lib = libOf(name);
|
|
198
|
+
if (lib !== undefined && this.wildcardLibs.has(lib))
|
|
199
|
+
this.subscribe(name);
|
|
61
200
|
});
|
|
62
201
|
}
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
this.subscriptions.length = 0;
|
|
70
|
-
this.wildcards.length = 0;
|
|
71
|
-
}
|
|
72
|
-
/** Subscribe once to `name`; the listener fans the envelope into `invoke`. */
|
|
73
|
-
subscribe(name, invoke) {
|
|
74
|
-
const listener = (msg) => invoke(msg);
|
|
75
|
-
diagnostics_channel.channel(name).subscribe(listener);
|
|
76
|
-
this.subscriptions.push({ name, listener });
|
|
202
|
+
subscribe(name) {
|
|
203
|
+
if (this.subscribed.has(name))
|
|
204
|
+
return;
|
|
205
|
+
diagnostics_channel.channel(name).subscribe(this.onEvent);
|
|
206
|
+
this.subscribed.add(name);
|
|
77
207
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
208
|
+
onEvent = (msg) => {
|
|
209
|
+
if (!isPointEvent(msg))
|
|
210
|
+
return;
|
|
211
|
+
const matched = this.listenersFor(msg.lib, msg.event);
|
|
212
|
+
if (matched.length === 0)
|
|
213
|
+
return;
|
|
214
|
+
if (this.deliver) {
|
|
215
|
+
// Delivery happens once, at the origin. A relayed copy was already handed over there.
|
|
216
|
+
if (isRelayed(msg))
|
|
217
|
+
return;
|
|
218
|
+
const envelope = (typeof msg.id === 'string' ? msg : { ...msg, id: randomUUID() });
|
|
219
|
+
for (const listener of matched)
|
|
220
|
+
this.pending.push({ listenerId: listener.id, envelope });
|
|
221
|
+
this.scheduled ??= setImmediate(() => void this.flush());
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
for (const listener of matched) {
|
|
225
|
+
this.invoke(listener.id, msg).catch((error) => this.logger.error(`@OnDiagnostic ${listener.id} handler failed: ${messageOf(error)}`));
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
/** Hand this turn's deliveries to the transport, in emit order. Settles once they are handed over. */
|
|
229
|
+
async flush() {
|
|
230
|
+
if (this.scheduled !== null) {
|
|
231
|
+
clearImmediate(this.scheduled);
|
|
232
|
+
this.scheduled = null;
|
|
233
|
+
}
|
|
234
|
+
if (this.pending.length === 0 || !this.deliver)
|
|
235
|
+
return;
|
|
236
|
+
const deliveries = this.pending.splice(0);
|
|
237
|
+
const report = (error) => this.logger.error(`failed to hand ${deliveries.length} diagnostics deliveries to the transport: ${messageOf(error)}`);
|
|
81
238
|
try {
|
|
82
|
-
|
|
83
|
-
const result = fn.call(instance, event);
|
|
84
|
-
if (result != null && typeof result.then === 'function') {
|
|
85
|
-
result.catch((err) => this.logError(methodName, err));
|
|
86
|
-
}
|
|
239
|
+
await this.deliver(deliveries);
|
|
87
240
|
}
|
|
88
|
-
catch (
|
|
89
|
-
|
|
241
|
+
catch (error) {
|
|
242
|
+
report(error);
|
|
90
243
|
}
|
|
91
244
|
}
|
|
92
|
-
logError(methodName, err) {
|
|
93
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
-
this.logger.error(`@OnDiagnostic ${methodName} handler failed: ${message}`);
|
|
95
|
-
}
|
|
96
245
|
};
|
|
97
246
|
DiagnosticsExplorer = __decorate([
|
|
98
247
|
Injectable(),
|
|
99
248
|
__param(0, Inject(DiscoveryService)),
|
|
100
249
|
__param(1, Inject(MetadataScanner)),
|
|
250
|
+
__param(2, Inject(ModuleRef)),
|
|
251
|
+
__param(3, Optional()),
|
|
252
|
+
__param(3, Inject(DIAGNOSTICS_OPTIONS)),
|
|
101
253
|
__metadata("design:paramtypes", [DiscoveryService,
|
|
102
|
-
MetadataScanner
|
|
254
|
+
MetadataScanner,
|
|
255
|
+
ModuleRef, Object])
|
|
103
256
|
], DiagnosticsExplorer);
|
|
104
257
|
export { DiagnosticsExplorer };
|
|
258
|
+
function messageOf(error) {
|
|
259
|
+
return error instanceof Error ? error.message : String(error);
|
|
260
|
+
}
|
|
105
261
|
//# sourceMappingURL=diagnostics.explorer.js.map
|