@dudousxd/nestjs-diagnostics 0.2.3 → 0.3.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 CHANGED
@@ -1,5 +1,29 @@
1
1
  # @dudousxd/nestjs-diagnostics
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#7](https://github.com/DavideCarvalho/nestjs-diagnostics/pull/7) [`a78672e`](https://github.com/DavideCarvalho/nestjs-diagnostics/commit/a78672ef871610df13fb3cb875d15bc993c564ad) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - feat: ecosystem improvements across the diagnostics core and Telescope watcher.
8
+
9
+ - **Envelope schema version (`v`):** every emitted envelope now carries an explicit schema-version field (`v`) so recorders and watchers can evolve the payload shape over time and detect/route by version. The Telescope watcher reads `v` when recording entries.
10
+ - **Per-emit sampling hook:** emit accepts an optional per-emit sampling decision, letting callers cheaply drop a fraction of events at the emit site (before payload work is done) without disabling a channel wholesale.
11
+ - **`tracingChannel()` + `trace()` helper:** a span-like wrapper over `node:diagnostics_channel`'s tracing-channel semantics that pairs `start`/`end`/`asyncStart`/`asyncEnd`/`error` events for a single logical operation, so a synchronous block or a promise can be traced with correct begin/finish/error correlation. The `trace()` helper wraps a function (sync or async) and publishes the matched lifecycle events automatically, including the `error` channel on throw/reject.
12
+ - **Typed channel registry:** the channel registry is now generic over a compile-time payload-type map, so `channel(name)` and the corresponding publish/subscribe surfaces are typed to the registered payload for that channel name — catching mismatched payloads at compile time while staying erased at runtime.
13
+ - **Packaging hygiene:** the core package declares a conservative `sideEffects` allowlist (rather than `false`) that preserves the module-level `globalThis` / `Symbol.for` registry and context-accessor registration so a bundler can never tree-shake the cross-copy-stable global state away; pure modules remain tree-shakeable.
14
+
15
+ Also includes a `LICENSE` file at the repo root.
16
+
17
+ ### Patch Changes
18
+
19
+ - [#7](https://github.com/DavideCarvalho/nestjs-diagnostics/pull/7) [`a78672e`](https://github.com/DavideCarvalho/nestjs-diagnostics/commit/a78672ef871610df13fb3cb875d15bc993c564ad) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - chore: packaging hygiene — declare `sideEffects` for safe tree-shaking.
20
+
21
+ The core package now declares `sideEffects` as an explicit allowlist of the modules that register process-global state at import time (the cross-copy-stable channel registry and context accessor on `globalThis` via `Symbol.for`, plus the reset hooks): `registry`, `context-accessor`, `channel`, and `trace`. It is deliberately **not** `false`, so a bundler can never tree-shake those global registrations away and break cross-copy registry stability; pure modules stay tree-shakeable.
22
+
23
+ The telescope package declares `sideEffects: false` — it has no module-level side effects (the watcher subscribes to channels only at runtime, inside `register()`), so consumers get full tree-shaking.
24
+
25
+ No runtime behavior changes; the `exports`/`types` maps already resolve correctly under NodeNext.
26
+
3
27
  ## 0.2.3
4
28
 
5
29
  ### Patch Changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Davi Carvalho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/channel.d.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import { type Channel } from 'node:diagnostics_channel';
2
- import type { EmitOptions } from './types.js';
2
+ import type { EmitOptions, EventOf, LibOf, PayloadOf } from './types.js';
3
3
  /** The prefix all channels created by this convention share. */
4
4
  export declare const CHANNEL_PREFIX = "aviary";
5
+ /**
6
+ * Current envelope schema version, stamped onto every emitted
7
+ * {@link DiagnosticEvent} as `v`. Bump this only when the wire shape changes in a
8
+ * way observers must adapt to. Observers should tolerate envelopes without `v`
9
+ * (legacy emitters published before versioning) and treat them as version `1`.
10
+ */
11
+ export declare const SCHEMA_VERSION = 1;
5
12
  /**
6
13
  * The channel name for a `<lib>`/`<event>` pair: `aviary:<lib>:<event>`. This is
7
14
  * the cross-repo wire contract — keep it identical on producer and observer.
@@ -28,11 +35,22 @@ export declare function getChannel(lib: string, event: string): Channel;
28
35
  * registered context accessor (if resolvable), else left undefined.
29
36
  * - The envelope is built and published ONLY when the channel `hasSubscribers`,
30
37
  * so emitting is effectively free when nothing is listening.
38
+ * - When `opts.sample` is given, it is consulted AFTER the `hasSubscribers` gate
39
+ * and BEFORE the envelope is built: a falsy result sheds this event without
40
+ * allocating anything. Default (no `sample`) always publishes when subscribed.
41
+ * - The envelope carries the current {@link SCHEMA_VERSION} as `v`.
31
42
  * - Never throws: emitting observability must not break the caller.
32
43
  *
33
44
  * ```ts
34
45
  * emit('billing', 'invoice-paid', { invoiceId: 'inv_123', amount: 4200 });
46
+ * // Shed 90% of a hot event:
47
+ * emit('authz', 'decision', payload, { sample: () => Math.random() < 0.1 });
35
48
  * ```
49
+ *
50
+ * When `(lib, event)` is declared in the typed
51
+ * {@link import('./types.js').ChannelRegistry ChannelRegistry}, `payload` is
52
+ * checked against the declared type at compile time; every other pair keeps the
53
+ * untyped `unknown` payload. The runtime behavior is identical either way.
36
54
  */
37
- export declare function emit(lib: string, event: string, payload: unknown, opts?: EmitOptions): void;
55
+ export declare function emit<TLib extends LibOf, TEvent extends EventOf<TLib>>(lib: TLib, event: TEvent, payload: PayloadOf<TLib, TEvent>, opts?: EmitOptions): void;
38
56
  //# sourceMappingURL=channel.d.ts.map
@@ -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,MAAM,YAAY,CAAC;AAE/D,gEAAgE;AAChE,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC;;;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;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,IAAI,CAU3F"}
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,CAoBN"}
package/dist/channel.js CHANGED
@@ -3,6 +3,13 @@ import { resolveTraceId } from './context-accessor.js';
3
3
  import { onRegistryReset, registerChannel } from './registry.js';
4
4
  /** The prefix all channels created by this convention share. */
5
5
  export const CHANNEL_PREFIX = 'aviary';
6
+ /**
7
+ * Current envelope schema version, stamped onto every emitted
8
+ * {@link DiagnosticEvent} as `v`. Bump this only when the wire shape changes in a
9
+ * way observers must adapt to. Observers should tolerate envelopes without `v`
10
+ * (legacy emitters published before versioning) and treat them as version `1`.
11
+ */
12
+ export const SCHEMA_VERSION = 1;
6
13
  /**
7
14
  * The channel name for a `<lib>`/`<event>` pair: `aviary:<lib>:<event>`. This is
8
15
  * the cross-repo wire contract — keep it identical on producer and observer.
@@ -64,19 +71,41 @@ export function getChannel(lib, event) {
64
71
  * registered context accessor (if resolvable), else left undefined.
65
72
  * - The envelope is built and published ONLY when the channel `hasSubscribers`,
66
73
  * so emitting is effectively free when nothing is listening.
74
+ * - When `opts.sample` is given, it is consulted AFTER the `hasSubscribers` gate
75
+ * and BEFORE the envelope is built: a falsy result sheds this event without
76
+ * allocating anything. Default (no `sample`) always publishes when subscribed.
77
+ * - The envelope carries the current {@link SCHEMA_VERSION} as `v`.
67
78
  * - Never throws: emitting observability must not break the caller.
68
79
  *
69
80
  * ```ts
70
81
  * emit('billing', 'invoice-paid', { invoiceId: 'inv_123', amount: 4200 });
82
+ * // Shed 90% of a hot event:
83
+ * emit('authz', 'decision', payload, { sample: () => Math.random() < 0.1 });
71
84
  * ```
85
+ *
86
+ * When `(lib, event)` is declared in the typed
87
+ * {@link import('./types.js').ChannelRegistry ChannelRegistry}, `payload` is
88
+ * checked against the declared type at compile time; every other pair keeps the
89
+ * untyped `unknown` payload. The runtime behavior is identical either way.
72
90
  */
73
91
  export function emit(lib, event, payload, opts) {
74
92
  const channel = getChannel(lib, event);
75
93
  if (!channel.hasSubscribers)
76
94
  return;
77
95
  try {
96
+ // Load-shedding gate: consulted only when subscribed, before any allocation.
97
+ // A throwing sampler is treated as a skip (caught below) — never published.
98
+ if (opts?.sample !== undefined && !opts.sample())
99
+ return;
78
100
  const traceId = opts?.traceId ?? resolveTraceId();
79
- const envelope = { ts: Date.now(), lib, event, traceId, payload };
101
+ const envelope = {
102
+ v: SCHEMA_VERSION,
103
+ ts: Date.now(),
104
+ lib,
105
+ event,
106
+ traceId,
107
+ payload,
108
+ };
80
109
  channel.publish(envelope);
81
110
  }
82
111
  catch {
@@ -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;;;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;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,IAAI,CAAC,GAAW,EAAE,KAAa,EAAE,OAAgB,EAAE,IAAkB;IACnF,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,CAAC,cAAc;QAAE,OAAO;IACpC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,cAAc,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAoB,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QACnF,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,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;SACR,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
@@ -1,5 +1,6 @@
1
- export { CHANNEL_PREFIX, channelName, emit, getChannel } from './channel.js';
1
+ export { CHANNEL_PREFIX, channelName, emit, getChannel, SCHEMA_VERSION } from './channel.js';
2
2
  export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, getContextAccessor, resolveTraceId, setContextAccessor, type UserRef, } from './context-accessor.js';
3
3
  export { onChannelRegistered, registerChannel, registeredChannels, resetRegistry, } from './registry.js';
4
- export type { DiagnosticEvent, EmitOptions } from './types.js';
4
+ export { SPAN_SCHEMA_VERSION, trace, type TraceChannelNames, traceChannelNames, type TracingChannel, tracingChannel, } from './trace.js';
5
+ export type { ChannelRegistry, DiagnosticEvent, EmitOptions, EventOf, LibOf, PayloadOf, SpanEvent, SpanPhase, TraceOptions, } from './types.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC7E,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,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7F,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,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
@@ -1,4 +1,5 @@
1
- export { CHANNEL_PREFIX, channelName, emit, getChannel } from './channel.js';
1
+ export { CHANNEL_PREFIX, channelName, emit, getChannel, SCHEMA_VERSION } from './channel.js';
2
2
  export { CONTEXT_ACCESSOR, getContextAccessor, resolveTraceId, setContextAccessor, } from './context-accessor.js';
3
3
  export { onChannelRegistered, registerChannel, registeredChannels, resetRegistry, } from './registry.js';
4
+ export { SPAN_SCHEMA_VERSION, trace, traceChannelNames, tracingChannel, } from './trace.js';
4
5
  //# 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,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EACL,gBAAgB,EAGhB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,GAEnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EACL,gBAAgB,EAGhB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,GAEnB,MAAM,uBAAuB,CAAC;AAC/B,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,74 @@
1
+ import type { EventOf, LibOf, PayloadOf, TraceOptions } from './types.js';
2
+ /**
3
+ * Span envelope schema version, stamped onto every {@link SpanEvent} as `v`.
4
+ * Versioned independently of the POINT `SCHEMA_VERSION` so the two wire shapes
5
+ * can evolve apart. Observers should treat an absent `v` as version `1`.
6
+ */
7
+ export declare const SPAN_SCHEMA_VERSION = 1;
8
+ /**
9
+ * The five span sub-channel names for a `(lib, event)` pair. They extend the
10
+ * POINT `aviary:<lib>:<event>` name with a `:<phase>` suffix, mirroring Node's
11
+ * own `tracingChannel` sub-channels (start/end/asyncStart/asyncEnd/error) while
12
+ * staying inside the `aviary:` convention so the same generic observer can read
13
+ * both POINT and SPAN traffic.
14
+ */
15
+ export interface TraceChannelNames {
16
+ start: string;
17
+ end: string;
18
+ asyncStart: string;
19
+ asyncEnd: string;
20
+ error: string;
21
+ }
22
+ /** Build the five span sub-channel names for a `(lib, event)` pair. */
23
+ export declare function traceChannelNames(lib: string, event: string): TraceChannelNames;
24
+ /**
25
+ * Wrap an (async) operation and publish span-like start / end / asyncStart /
26
+ * asyncEnd / error events over `node:diagnostics_channel`, so consumers get real
27
+ * start/end/error pairing with timing for authz decisions, durable steps, etc.
28
+ *
29
+ * Naming follows the existing convention: events ride
30
+ * `aviary:<lib>:<event>:<phase>` (see {@link traceChannelNames}). `emit` is
31
+ * unchanged — `trace` is the additional span surface.
32
+ *
33
+ * Lifecycle:
34
+ * - Sync `fn`: publishes `start` then, on return, `end` (with `result`); on
35
+ * throw, `error`. The value/throw is propagated to the caller unchanged.
36
+ * - Async `fn` (returns a promise): publishes `start` synchronously, `asyncStart`
37
+ * when the promise settles begins resolving, then `asyncEnd` (with `result`)
38
+ * on fulfilment or `error` on rejection. The promise is returned to the caller.
39
+ *
40
+ * Cost: when NO span sub-channel has a subscriber, `trace` calls `fn` and returns
41
+ * its value directly — no span id, no envelope, no timing — so the hot path is a
42
+ * handful of `hasSubscribers` reads.
43
+ *
44
+ * Trace id: resolved once at span start from `opts.traceId` (wins) else the
45
+ * registered context accessor, and stamped on every phase event.
46
+ *
47
+ * ```ts
48
+ * const decision = trace('authz', 'decision', () => evaluate(req), { subject });
49
+ * const out = await trace('durable', 'step', () => runStep(), { name });
50
+ * ```
51
+ */
52
+ export declare function trace<TLib extends LibOf, TEvent extends EventOf<TLib>, R>(lib: TLib, event: TEvent, fn: () => R, payload?: PayloadOf<TLib, TEvent>, opts?: TraceOptions): R;
53
+ /** A {@link trace} bound to one `(lib, event)` pair — see {@link tracingChannel}. */
54
+ export interface TracingChannel<TPayload = unknown> {
55
+ /** The base `aviary:<lib>:<event>` channel name. */
56
+ readonly name: string;
57
+ /** The five span sub-channel names. */
58
+ readonly channels: TraceChannelNames;
59
+ /** Trace an operation on this channel; same semantics as {@link trace}. */
60
+ trace<R>(fn: () => R, payload?: TPayload, opts?: TraceOptions): R;
61
+ }
62
+ /**
63
+ * A {@link trace} factory bound to a single `(lib, event)` pair — the ergonomic,
64
+ * reusable form for a hot call site that always traces the same operation. Named
65
+ * after Node's `diagnostics_channel.tracingChannel()` (whose sub-channels we
66
+ * mirror) but kept inside the `aviary:` convention.
67
+ *
68
+ * ```ts
69
+ * const decision = tracingChannel('authz', 'decision');
70
+ * decision.trace(() => evaluate(req), { subject });
71
+ * ```
72
+ */
73
+ export declare function tracingChannel<TLib extends LibOf, TEvent extends EventOf<TLib>>(lib: TLib, event: TEvent): TracingChannel<PayloadOf<TLib, TEvent>>;
74
+ //# sourceMappingURL=trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAwB,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhG;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAS/E;AA0GD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,KAAK,CAAC,IAAI,SAAS,KAAK,EAAE,MAAM,SAAS,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EACvE,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,CAAC,EACX,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,EACjC,IAAI,CAAC,EAAE,YAAY,GAClB,CAAC,CAyDH;AAED,qFAAqF;AACrF,MAAM,WAAW,cAAc,CAAC,QAAQ,GAAG,OAAO;IAChD,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,2EAA2E;IAC3E,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC;CACnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,SAAS,KAAK,EAAE,MAAM,SAAS,OAAO,CAAC,IAAI,CAAC,EAC7E,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,MAAM,GACZ,cAAc,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAMzC"}
package/dist/trace.js ADDED
@@ -0,0 +1,196 @@
1
+ import diagnostics_channel from 'node:diagnostics_channel';
2
+ import { channelName } from './channel.js';
3
+ import { resolveTraceId } from './context-accessor.js';
4
+ import { onRegistryReset, registerChannel } from './registry.js';
5
+ /**
6
+ * Span envelope schema version, stamped onto every {@link SpanEvent} as `v`.
7
+ * Versioned independently of the POINT `SCHEMA_VERSION` so the two wire shapes
8
+ * can evolve apart. Observers should treat an absent `v` as version `1`.
9
+ */
10
+ export const SPAN_SCHEMA_VERSION = 1;
11
+ /** Build the five span sub-channel names for a `(lib, event)` pair. */
12
+ export function traceChannelNames(lib, event) {
13
+ const base = channelName(lib, event);
14
+ return {
15
+ start: `${base}:start`,
16
+ end: `${base}:end`,
17
+ asyncStart: `${base}:asyncStart`,
18
+ asyncEnd: `${base}:asyncEnd`,
19
+ error: `${base}:error`,
20
+ };
21
+ }
22
+ /**
23
+ * Per-`(lib, event)` cache of the resolved span {@link Channel}s, keyed by `lib`
24
+ * then `event` — the same two-level memo strategy `getChannel` uses, so the
25
+ * steady state is two `Map.get`s with no string allocation. The first trace of a
26
+ * pair pays the name builds + `diagnostics_channel.channel()` lookups + registry
27
+ * insert; every call after returns the cached object.
28
+ */
29
+ const spanCache = new Map();
30
+ // Drop the memo cache whenever the registry is reset (test-only), so the next
31
+ // trace re-registers the base channel. Mirrors channel.ts.
32
+ onRegistryReset(() => spanCache.clear());
33
+ function getSpanChannels(lib, event) {
34
+ let byEvent = spanCache.get(lib);
35
+ if (byEvent !== undefined) {
36
+ const cached = byEvent.get(event);
37
+ if (cached !== undefined)
38
+ return cached;
39
+ }
40
+ else {
41
+ byEvent = new Map();
42
+ spanCache.set(lib, byEvent);
43
+ }
44
+ const names = traceChannelNames(lib, event);
45
+ const channels = {
46
+ base: channelName(lib, event),
47
+ start: diagnostics_channel.channel(names.start),
48
+ end: diagnostics_channel.channel(names.end),
49
+ asyncStart: diagnostics_channel.channel(names.asyncStart),
50
+ asyncEnd: diagnostics_channel.channel(names.asyncEnd),
51
+ error: diagnostics_channel.channel(names.error),
52
+ };
53
+ // Register the BASE channel name for discovery: a generic observer that knows
54
+ // the base name can derive all five span sub-channels via traceChannelNames.
55
+ registerChannel(channels.base);
56
+ byEvent.set(event, channels);
57
+ return channels;
58
+ }
59
+ /** True when ANY of the five span sub-channels currently has a subscriber. */
60
+ function anySubscribed(ch) {
61
+ return (ch.start.hasSubscribers ||
62
+ ch.end.hasSubscribers ||
63
+ ch.asyncStart.hasSubscribers ||
64
+ ch.asyncEnd.hasSubscribers ||
65
+ ch.error.hasSubscribers);
66
+ }
67
+ let spanCounter = 0;
68
+ /** A cheap, process-unique span id. Allocated only when a span is observed. */
69
+ function nextSpanId() {
70
+ spanCounter = (spanCounter + 1) >>> 0;
71
+ return `${Date.now().toString(36)}-${spanCounter.toString(36)}`;
72
+ }
73
+ /** Publish one phase event on its sub-channel, never throwing. */
74
+ function publishPhase(channel, phase, lib, event, spanId, traceId, extra) {
75
+ if (!channel.hasSubscribers)
76
+ return;
77
+ try {
78
+ const envelope = {
79
+ v: SPAN_SCHEMA_VERSION,
80
+ ts: Date.now(),
81
+ lib,
82
+ event,
83
+ phase,
84
+ spanId,
85
+ traceId,
86
+ ...extra,
87
+ };
88
+ channel.publish(envelope);
89
+ }
90
+ catch {
91
+ // Observability must never break the traced code path.
92
+ }
93
+ }
94
+ /** Detect a thenable so a sync `fn` returning a promise is traced as async. */
95
+ function isPromiseLike(value) {
96
+ return (value != null &&
97
+ (typeof value === 'object' || typeof value === 'function') &&
98
+ typeof value.then === 'function');
99
+ }
100
+ /**
101
+ * Wrap an (async) operation and publish span-like start / end / asyncStart /
102
+ * asyncEnd / error events over `node:diagnostics_channel`, so consumers get real
103
+ * start/end/error pairing with timing for authz decisions, durable steps, etc.
104
+ *
105
+ * Naming follows the existing convention: events ride
106
+ * `aviary:<lib>:<event>:<phase>` (see {@link traceChannelNames}). `emit` is
107
+ * unchanged — `trace` is the additional span surface.
108
+ *
109
+ * Lifecycle:
110
+ * - Sync `fn`: publishes `start` then, on return, `end` (with `result`); on
111
+ * throw, `error`. The value/throw is propagated to the caller unchanged.
112
+ * - Async `fn` (returns a promise): publishes `start` synchronously, `asyncStart`
113
+ * when the promise settles begins resolving, then `asyncEnd` (with `result`)
114
+ * on fulfilment or `error` on rejection. The promise is returned to the caller.
115
+ *
116
+ * Cost: when NO span sub-channel has a subscriber, `trace` calls `fn` and returns
117
+ * its value directly — no span id, no envelope, no timing — so the hot path is a
118
+ * handful of `hasSubscribers` reads.
119
+ *
120
+ * Trace id: resolved once at span start from `opts.traceId` (wins) else the
121
+ * registered context accessor, and stamped on every phase event.
122
+ *
123
+ * ```ts
124
+ * const decision = trace('authz', 'decision', () => evaluate(req), { subject });
125
+ * const out = await trace('durable', 'step', () => runStep(), { name });
126
+ * ```
127
+ */
128
+ export function trace(lib, event, fn, payload, opts) {
129
+ const channels = getSpanChannels(lib, event);
130
+ // Hot path: nothing is listening → run fn directly, allocate nothing.
131
+ if (!anySubscribed(channels)) {
132
+ return fn();
133
+ }
134
+ const spanId = nextSpanId();
135
+ const traceId = opts?.traceId ?? resolveTraceId();
136
+ const startedAt = performance.now();
137
+ publishPhase(channels.start, 'start', lib, event, spanId, traceId, { payload });
138
+ let result;
139
+ try {
140
+ result = fn();
141
+ }
142
+ catch (error) {
143
+ publishPhase(channels.error, 'error', lib, event, spanId, traceId, {
144
+ error,
145
+ durationMs: performance.now() - startedAt,
146
+ });
147
+ throw error;
148
+ }
149
+ if (isPromiseLike(result)) {
150
+ // Async op: end marks the synchronous portion completing; asyncStart marks
151
+ // the continuation; asyncEnd carries the settled result/error + duration.
152
+ publishPhase(channels.end, 'end', lib, event, spanId, traceId, {
153
+ durationMs: performance.now() - startedAt,
154
+ });
155
+ publishPhase(channels.asyncStart, 'asyncStart', lib, event, spanId, traceId, {});
156
+ const settled = Promise.resolve(result).then((value) => {
157
+ publishPhase(channels.asyncEnd, 'asyncEnd', lib, event, spanId, traceId, {
158
+ result: value,
159
+ durationMs: performance.now() - startedAt,
160
+ });
161
+ return value;
162
+ }, (error) => {
163
+ publishPhase(channels.error, 'error', lib, event, spanId, traceId, {
164
+ error,
165
+ durationMs: performance.now() - startedAt,
166
+ });
167
+ throw error;
168
+ });
169
+ return settled;
170
+ }
171
+ // Sync success.
172
+ publishPhase(channels.end, 'end', lib, event, spanId, traceId, {
173
+ result,
174
+ durationMs: performance.now() - startedAt,
175
+ });
176
+ return result;
177
+ }
178
+ /**
179
+ * A {@link trace} factory bound to a single `(lib, event)` pair — the ergonomic,
180
+ * reusable form for a hot call site that always traces the same operation. Named
181
+ * after Node's `diagnostics_channel.tracingChannel()` (whose sub-channels we
182
+ * mirror) but kept inside the `aviary:` convention.
183
+ *
184
+ * ```ts
185
+ * const decision = tracingChannel('authz', 'decision');
186
+ * decision.trace(() => evaluate(req), { subject });
187
+ * ```
188
+ */
189
+ export function tracingChannel(lib, event) {
190
+ return {
191
+ name: channelName(lib, event),
192
+ channels: traceChannelNames(lib, event),
193
+ trace: (fn, payload, opts) => trace(lib, event, fn, payload, opts),
194
+ };
195
+ }
196
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -6,6 +6,14 @@
6
6
  * emitting library and identified by `lib` + `event`.
7
7
  */
8
8
  export interface DiagnosticEvent<TPayload = unknown> {
9
+ /**
10
+ * Envelope schema version — bumped only when this wire shape changes in a way
11
+ * observers must adapt to. Stamped by `emit` with the current
12
+ * {@link import('./channel.js').SCHEMA_VERSION SCHEMA_VERSION}. Optional on the
13
+ * type so observers stay tolerant of legacy envelopes published before
14
+ * versioning existed (treat an absent `v` as the original, unversioned shape).
15
+ */
16
+ v?: number;
9
17
  /** Epoch millis when the event was emitted (`Date.now()` at publish time). */
10
18
  ts: number;
11
19
  /** The emitting library, e.g. `'billing'` — the `<lib>` in `aviary:<lib>:<event>`. */
@@ -21,6 +29,116 @@ export interface DiagnosticEvent<TPayload = unknown> {
21
29
  /** The library-defined payload. Opaque to this package and to observers. */
22
30
  payload: TPayload;
23
31
  }
32
+ /**
33
+ * The optional, consumer-augmented typed channel registry: a `lib → event →
34
+ * payload` map. It is EMPTY by default — the untyped path (`payload: unknown`)
35
+ * is always available and unchanged. A library declares its channels by
36
+ * augmenting this interface via TypeScript declaration merging:
37
+ *
38
+ * ```ts
39
+ * declare module '@dudousxd/nestjs-diagnostics' {
40
+ * interface ChannelRegistry {
41
+ * billing: {
42
+ * 'invoice-paid': { invoiceId: string; amount: number };
43
+ * };
44
+ * }
45
+ * }
46
+ * ```
47
+ *
48
+ * Once augmented, `emit('billing', 'invoice-paid', ...)` and
49
+ * `trace('billing', 'invoice-paid', ...)` get compile-time payload checking for
50
+ * that channel; every other `(lib, event)` pair keeps the untyped `unknown`
51
+ * payload. This is purely a type-level mechanism: there is no runtime registry
52
+ * of payload shapes and nothing is allocated.
53
+ */
54
+ export interface ChannelRegistry {
55
+ }
56
+ /**
57
+ * `string`, kept assignable from any literal but offering autocomplete for the
58
+ * registered names. The `& {}` defeats literal-union widening so registered
59
+ * keys still surface as suggestions while every other string is accepted — this
60
+ * is what keeps the untyped path open even after the registry is augmented.
61
+ */
62
+ type LooseString = string & {};
63
+ /**
64
+ * Every `lib` declared in the {@link ChannelRegistry} (for autocomplete), plus
65
+ * any other `string` (the untyped path). Collapses to plain `string` when the
66
+ * registry is empty.
67
+ */
68
+ export type LibOf = (keyof ChannelRegistry & string) | LooseString;
69
+ /**
70
+ * Every `event` declared for `TLib` in the {@link ChannelRegistry} (for
71
+ * autocomplete), plus any other `string` (the untyped path). Collapses to plain
72
+ * `string` when `TLib` is not a registered lib.
73
+ */
74
+ export type EventOf<TLib extends string> = TLib extends keyof ChannelRegistry ? (keyof ChannelRegistry[TLib] & string) | LooseString : string;
75
+ /**
76
+ * The declared payload type for `(TLib, TEvent)` in the {@link ChannelRegistry},
77
+ * or `unknown` when the pair is not registered. This is what gives `emit`/`trace`
78
+ * their compile-time payload types for registered channels while leaving every
79
+ * other call on the original untyped `unknown` path.
80
+ */
81
+ export type PayloadOf<TLib extends string, TEvent extends string> = TLib extends keyof ChannelRegistry ? TEvent extends keyof ChannelRegistry[TLib] ? ChannelRegistry[TLib][TEvent] : unknown : unknown;
82
+ /**
83
+ * The phase of a traced operation, published as `phase` on every
84
+ * {@link SpanEvent}. Mirrors Node's `tracingChannel` sub-channels so observers
85
+ * can pair start/end and start/error into span-like records.
86
+ */
87
+ export type SpanPhase = 'start' | 'end' | 'asyncStart' | 'asyncEnd' | 'error';
88
+ /**
89
+ * The envelope published on the span sub-channels by `trace`. Extends the
90
+ * {@link DiagnosticEvent} POINT shape with span correlation + timing so a
91
+ * consumer can reconstruct real start/end/error pairs:
92
+ *
93
+ * - `aviary:<lib>:<event>:start` — `phase: 'start'`, carries `payload`.
94
+ * - `aviary:<lib>:<event>:end` — sync (or settled) completion, carries `result`
95
+ * + `durationMs`.
96
+ * - `aviary:<lib>:<event>:asyncStart` / `:asyncEnd` — the async continuation of
97
+ * a promise-returning op; `asyncEnd` carries `result`/`error` + `durationMs`.
98
+ * - `aviary:<lib>:<event>:error` — `phase: 'error'`, carries `error` (+
99
+ * `durationMs`).
100
+ *
101
+ * Every event of a single span shares a `spanId` so observers can correlate
102
+ * them without relying on subscription ordering.
103
+ */
104
+ export interface SpanEvent<TPayload = unknown, TResult = unknown> {
105
+ /**
106
+ * Span envelope schema version, stamped by `trace` with
107
+ * {@link import('./trace.js').SPAN_SCHEMA_VERSION SPAN_SCHEMA_VERSION}.
108
+ * Versioned independently of the POINT {@link DiagnosticEvent} `v`. Optional
109
+ * so observers stay tolerant of legacy/untagged envelopes.
110
+ */
111
+ v?: number;
112
+ /** Epoch millis when this phase event was published (`Date.now()`). */
113
+ ts: number;
114
+ /** The emitting library — the `<lib>` in `aviary:<lib>:<event>`. */
115
+ lib: string;
116
+ /** The traced operation name — the `<event>` in `aviary:<lib>:<event>`. */
117
+ event: string;
118
+ /** Which phase of the span this is. */
119
+ phase: SpanPhase;
120
+ /**
121
+ * Stable per-span correlation id shared by every phase event of one `trace`
122
+ * call. Lets observers pair start↔end / start↔error regardless of ordering.
123
+ */
124
+ spanId: string;
125
+ /**
126
+ * Trace id for the current request, when resolvable. Same semantics + stable
127
+ * monomorphic shape as {@link DiagnosticEvent.traceId}.
128
+ */
129
+ traceId?: string | undefined;
130
+ /** The caller-supplied payload, present on the `start` event. */
131
+ payload?: TPayload;
132
+ /** The operation's return value, present on `end`/`asyncEnd` on success. */
133
+ result?: TResult;
134
+ /** The thrown/rejected value, present on `error` (and `asyncEnd` on failure). */
135
+ error?: unknown;
136
+ /**
137
+ * Wall-clock duration in fractional millis from `start` to this phase,
138
+ * present on `end`/`asyncEnd`/`error`. Measured with `performance.now()`.
139
+ */
140
+ durationMs?: number;
141
+ }
24
142
  /** Optional per-emit overrides. */
25
143
  export interface EmitOptions {
26
144
  /**
@@ -29,5 +147,24 @@ export interface EmitOptions {
29
147
  * any), or leave `traceId` undefined.
30
148
  */
31
149
  traceId?: string;
150
+ /**
151
+ * Optional load-shedding hook. Consulted by `emit` only AFTER the channel has
152
+ * subscribers and BEFORE the envelope is built/published: return `false` to
153
+ * drop this event without allocating the envelope, `true` to publish. Omit for
154
+ * the default (always publish when subscribed). A thrown sampler is treated as
155
+ * a skip — observability must never break the caller. Hot libraries can pass a
156
+ * cheap probabilistic predicate, e.g. `{ sample: () => Math.random() < 0.1 }`.
157
+ */
158
+ sample?: () => boolean;
159
+ }
160
+ /** Optional per-`trace` overrides. */
161
+ export interface TraceOptions {
162
+ /**
163
+ * An explicit trace id for the whole span. When provided it wins over the
164
+ * registered {@link ContextAccessor} and is stamped on every phase event.
165
+ * Omit to auto-fill from the accessor (resolved once at span start).
166
+ */
167
+ traceId?: string;
32
168
  }
169
+ export {};
33
170
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,eAAe,CAAC,QAAQ,GAAG,OAAO;IACjD,8EAA8E;IAC9E,EAAE,EAAE,MAAM,CAAC;IACX,sFAAsF;IACtF,GAAG,EAAE,MAAM,CAAC;IACZ,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,4EAA4E;IAC5E,OAAO,EAAE,QAAQ,CAAC;CACnB;AAED,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,eAAe,CAAC,QAAQ,GAAG,OAAO;IACjD;;;;;;OAMG;IACH,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,8EAA8E;IAC9E,EAAE,EAAE,MAAM,CAAC;IACX,sFAAsF;IACtF,GAAG,EAAE,MAAM,CAAC;IACZ,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,4EAA4E;IAC5E,OAAO,EAAE,QAAQ,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,eAAe;CAAG;AAEnC;;;;;GAKG;AACH,KAAK,WAAW,GAAG,MAAM,GAAG,EAAE,CAAC;AAE/B;;;;GAIG;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,WAAW,CAAC;AAEnE;;;;GAIG;AACH,MAAM,MAAM,OAAO,CAAC,IAAI,SAAS,MAAM,IAAI,IAAI,SAAS,MAAM,eAAe,GACzE,CAAC,MAAM,eAAe,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,WAAW,GACpD,MAAM,CAAC;AAEX;;;;;GAKG;AACH,MAAM,MAAM,SAAS,CACnB,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,MAAM,IACnB,IAAI,SAAS,MAAM,eAAe,GAClC,MAAM,SAAS,MAAM,eAAe,CAAC,IAAI,CAAC,GACxC,eAAe,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAC7B,OAAO,GACT,OAAO,CAAC;AAEZ;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,YAAY,GAAG,UAAU,GAAG,OAAO,CAAC;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,SAAS,CAAC,QAAQ,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO;IAC9D;;;;;OAKG;IACH,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,iEAAiE;IACjE,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC;CACxB;AAED,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-diagnostics",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "A standard convention for @dudousxd/nestjs-* libs to emit observability events over node:diagnostics_channel.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,6 +24,17 @@
24
24
  "README.md",
25
25
  "CHANGELOG.md"
26
26
  ],
27
+ "//sideEffects": "NOT false: registry.ts and context-accessor.ts register process-global state on globalThis via Symbol.for AT IMPORT TIME (the cross-copy-stable channel registry + context accessor), and channel.ts/trace.ts register a reset hook at module load. A bundler told sideEffects:false could tree-shake these initializers away, breaking cross-copy global stability. The array lists exactly the side-effectful modules (matching both src and published dist paths); pure modules (index, types) stay tree-shakeable.",
28
+ "sideEffects": [
29
+ "**/registry.js",
30
+ "**/registry.ts",
31
+ "**/context-accessor.js",
32
+ "**/context-accessor.ts",
33
+ "**/channel.js",
34
+ "**/channel.ts",
35
+ "**/trace.js",
36
+ "**/trace.ts"
37
+ ],
27
38
  "devDependencies": {
28
39
  "@types/node": "^20.0.0",
29
40
  "typescript": "^5.4.0",