@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +29 -0
  3. package/dist/channel.d.ts +1 -1
  4. package/dist/channel.d.ts.map +1 -1
  5. package/dist/channel.js +3 -1
  6. package/dist/channel.js.map +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/nestjs/delivery.d.ts +92 -0
  12. package/dist/nestjs/delivery.d.ts.map +1 -0
  13. package/dist/nestjs/delivery.js +2 -0
  14. package/dist/nestjs/delivery.js.map +1 -0
  15. package/dist/nestjs/diagnostics.explorer.d.ts +51 -11
  16. package/dist/nestjs/diagnostics.explorer.d.ts.map +1 -1
  17. package/dist/nestjs/diagnostics.explorer.js +209 -53
  18. package/dist/nestjs/diagnostics.explorer.js.map +1 -1
  19. package/dist/nestjs/diagnostics.module.d.ts +9 -2
  20. package/dist/nestjs/diagnostics.module.d.ts.map +1 -1
  21. package/dist/nestjs/diagnostics.module.js +23 -3
  22. package/dist/nestjs/diagnostics.module.js.map +1 -1
  23. package/dist/nestjs/diagnostics.options.d.ts +31 -0
  24. package/dist/nestjs/diagnostics.options.d.ts.map +1 -0
  25. package/dist/nestjs/diagnostics.options.js +3 -0
  26. package/dist/nestjs/diagnostics.options.js.map +1 -0
  27. package/dist/nestjs/index.d.ts +6 -3
  28. package/dist/nestjs/index.d.ts.map +1 -1
  29. package/dist/nestjs/index.js +4 -2
  30. package/dist/nestjs/index.js.map +1 -1
  31. package/dist/nestjs/on-diagnostic.decorator.d.ts +33 -7
  32. package/dist/nestjs/on-diagnostic.decorator.d.ts.map +1 -1
  33. package/dist/nestjs/on-diagnostic.decorator.js +0 -9
  34. package/dist/nestjs/on-diagnostic.decorator.js.map +1 -1
  35. package/dist/nestjs/queue-transport.d.ts +47 -0
  36. package/dist/nestjs/queue-transport.d.ts.map +1 -0
  37. package/dist/nestjs/queue-transport.js +45 -0
  38. package/dist/nestjs/queue-transport.js.map +1 -0
  39. package/dist/nestjs/role.d.ts +13 -0
  40. package/dist/nestjs/role.d.ts.map +1 -0
  41. package/dist/nestjs/role.js +13 -0
  42. package/dist/nestjs/role.js.map +1 -0
  43. package/dist/nestjs/transports.d.ts +125 -0
  44. package/dist/nestjs/transports.d.ts.map +1 -0
  45. package/dist/nestjs/transports.js +127 -0
  46. package/dist/nestjs/transports.js.map +1 -0
  47. package/dist/redis-relay.d.ts +60 -0
  48. package/dist/redis-relay.d.ts.map +1 -0
  49. package/dist/redis-relay.js +170 -0
  50. package/dist/redis-relay.js.map +1 -0
  51. package/dist/relayed.d.ts +8 -0
  52. package/dist/relayed.d.ts.map +1 -0
  53. package/dist/relayed.js +31 -0
  54. package/dist/relayed.js.map +1 -0
  55. package/dist/trace.d.ts.map +1 -1
  56. package/dist/trace.js +2 -0
  57. package/dist/trace.js.map +1 -1
  58. package/dist/types.d.ts +9 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/package.json +14 -6
  61. package/skills/emit-diagnostics/SKILL.md +5 -2
  62. package/skills/react-with-on-diagnostic/SKILL.md +41 -13
  63. 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
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,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,CAuBN"}
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,
@@ -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';
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=delivery.js.map
@@ -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 subscriptions;
8
- private readonly wildcards;
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
- constructor(discovery: DiscoveryService, scanner: MetadataScanner);
11
- onApplicationBootstrap(): void;
12
- onApplicationShutdown(): void;
13
- /** Subscribe once to `name`; the listener fans the envelope into `invoke`. */
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
- /** Invoke a handler, swallowing sync throws and async rejections so a buggy
16
- * reaction can never break the synchronous `emit()` that triggered it. */
17
- private safeInvoke;
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":"AACA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAQjE,qBACa,mBAAoB,YAAW,sBAAsB,EAAE,qBAAqB;IAO3D,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiE;IAC/F,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiD;IAC3E,OAAO,CAAC,oBAAoB,CAA6B;gBAGZ,SAAS,EAAE,gBAAgB,EAC5B,OAAO,EAAE,eAAe;IAGpE,sBAAsB,IAAI,IAAI;IA+B9B,qBAAqB,IAAI,IAAI;IAU7B,8EAA8E;IAC9E,OAAO,CAAC,SAAS;IAMjB;+EAC2E;IAC3E,OAAO,CAAC,UAAU;IAgBlB,OAAO,CAAC,QAAQ;CAIjB"}
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
- subscriptions = [];
24
- wildcards = [];
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
- constructor(discovery, scanner) {
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
- onApplicationBootstrap() {
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 methodName of this.scanner.getAllMethodNames(proto)) {
37
- const metas = Reflect.getMetadata(ON_DIAGNOSTIC_META, proto, methodName);
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
- for (const meta of metas) {
41
- const invoke = (event) => this.safeInvoke(instance, methodName, event);
42
- if (meta.event !== undefined) {
43
- this.subscribe(channelName(meta.lib, meta.event), invoke);
44
- }
45
- else {
46
- const prefix = `${CHANNEL_PREFIX}:${meta.lib}:`;
47
- this.wildcards.push({ prefix, invoke });
48
- for (const name of registeredChannels()) {
49
- if (name.startsWith(prefix))
50
- this.subscribe(name, invoke);
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
- for (const w of this.wildcards) {
58
- if (name.startsWith(w.prefix))
59
- this.subscribe(name, w.invoke);
60
- }
197
+ const lib = libOf(name);
198
+ if (lib !== undefined && this.wildcardLibs.has(lib))
199
+ this.subscribe(name);
61
200
  });
62
201
  }
63
- onApplicationShutdown() {
64
- this.offChannelRegistered?.();
65
- this.offChannelRegistered = null;
66
- for (const { name, listener } of this.subscriptions) {
67
- diagnostics_channel.channel(name).unsubscribe(listener);
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
- /** Invoke a handler, swallowing sync throws and async rejections so a buggy
79
- * reaction can never break the synchronous `emit()` that triggered it. */
80
- safeInvoke(instance, methodName, event) {
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
- const fn = instance[methodName];
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 (err) {
89
- this.logError(methodName, err);
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