@provide-io/telemetry 0.2.2

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 (122) hide show
  1. package/README.md +247 -0
  2. package/dist/backpressure.d.ts +19 -0
  3. package/dist/backpressure.d.ts.map +1 -0
  4. package/dist/backpressure.js +51 -0
  5. package/dist/cardinality.d.ts +15 -0
  6. package/dist/cardinality.d.ts.map +1 -0
  7. package/dist/cardinality.js +69 -0
  8. package/dist/classification.d.ts +29 -0
  9. package/dist/classification.d.ts.map +1 -0
  10. package/dist/classification.js +58 -0
  11. package/dist/config.d.ts +156 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +350 -0
  14. package/dist/consent.d.ts +11 -0
  15. package/dist/consent.d.ts.map +1 -0
  16. package/dist/consent.js +50 -0
  17. package/dist/context.d.ts +60 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/context.js +127 -0
  20. package/dist/exceptions.d.ts +14 -0
  21. package/dist/exceptions.d.ts.map +1 -0
  22. package/dist/exceptions.js +21 -0
  23. package/dist/fingerprint.d.ts +5 -0
  24. package/dist/fingerprint.d.ts.map +1 -0
  25. package/dist/fingerprint.js +50 -0
  26. package/dist/hash.d.ts +8 -0
  27. package/dist/hash.d.ts.map +1 -0
  28. package/dist/hash.js +102 -0
  29. package/dist/health.d.ts +54 -0
  30. package/dist/health.d.ts.map +1 -0
  31. package/dist/health.js +102 -0
  32. package/dist/index.d.ts +52 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +59 -0
  35. package/dist/logger.d.ts +28 -0
  36. package/dist/logger.d.ts.map +1 -0
  37. package/dist/logger.js +254 -0
  38. package/dist/metrics.d.ts +78 -0
  39. package/dist/metrics.d.ts.map +1 -0
  40. package/dist/metrics.js +238 -0
  41. package/dist/otel-logs.d.ts +29 -0
  42. package/dist/otel-logs.d.ts.map +1 -0
  43. package/dist/otel-logs.js +127 -0
  44. package/dist/otel-noop.d.ts +13 -0
  45. package/dist/otel-noop.d.ts.map +1 -0
  46. package/dist/otel-noop.js +5 -0
  47. package/dist/otel.d.ts +20 -0
  48. package/dist/otel.d.ts.map +1 -0
  49. package/dist/otel.js +80 -0
  50. package/dist/pii.d.ts +43 -0
  51. package/dist/pii.d.ts.map +1 -0
  52. package/dist/pii.js +278 -0
  53. package/dist/pretty.d.ts +12 -0
  54. package/dist/pretty.d.ts.map +1 -0
  55. package/dist/pretty.js +85 -0
  56. package/dist/propagation.d.ts +52 -0
  57. package/dist/propagation.d.ts.map +1 -0
  58. package/dist/propagation.js +183 -0
  59. package/dist/react.d.ts +38 -0
  60. package/dist/react.d.ts.map +1 -0
  61. package/dist/react.js +72 -0
  62. package/dist/receipts.d.ts +26 -0
  63. package/dist/receipts.d.ts.map +1 -0
  64. package/dist/receipts.js +69 -0
  65. package/dist/resilience.d.ts +26 -0
  66. package/dist/resilience.d.ts.map +1 -0
  67. package/dist/resilience.js +183 -0
  68. package/dist/runtime.d.ts +33 -0
  69. package/dist/runtime.d.ts.map +1 -0
  70. package/dist/runtime.js +133 -0
  71. package/dist/sampling.d.ts +9 -0
  72. package/dist/sampling.d.ts.map +1 -0
  73. package/dist/sampling.js +53 -0
  74. package/dist/sanitize.d.ts +6 -0
  75. package/dist/sanitize.d.ts.map +1 -0
  76. package/dist/sanitize.js +7 -0
  77. package/dist/schema.d.ts +41 -0
  78. package/dist/schema.d.ts.map +1 -0
  79. package/dist/schema.js +109 -0
  80. package/dist/shutdown.d.ts +2 -0
  81. package/dist/shutdown.d.ts.map +1 -0
  82. package/dist/shutdown.js +15 -0
  83. package/dist/slo.d.ts +25 -0
  84. package/dist/slo.d.ts.map +1 -0
  85. package/dist/slo.js +115 -0
  86. package/dist/testing.d.ts +10 -0
  87. package/dist/testing.d.ts.map +1 -0
  88. package/dist/testing.js +51 -0
  89. package/dist/tracing.d.ts +51 -0
  90. package/dist/tracing.d.ts.map +1 -0
  91. package/dist/tracing.js +181 -0
  92. package/package.json +139 -0
  93. package/src/backpressure.ts +68 -0
  94. package/src/cardinality.ts +83 -0
  95. package/src/classification.ts +87 -0
  96. package/src/config.ts +589 -0
  97. package/src/consent.ts +61 -0
  98. package/src/context.ts +157 -0
  99. package/src/exceptions.ts +24 -0
  100. package/src/fingerprint.ts +53 -0
  101. package/src/hash.ts +118 -0
  102. package/src/health.ts +175 -0
  103. package/src/index.ts +183 -0
  104. package/src/logger.ts +287 -0
  105. package/src/metrics.ts +204 -0
  106. package/src/otel-logs.ts +161 -0
  107. package/src/otel-noop.ts +19 -0
  108. package/src/otel.ts +112 -0
  109. package/src/pii.ts +358 -0
  110. package/src/pretty.ts +93 -0
  111. package/src/propagation.ts +222 -0
  112. package/src/react.ts +98 -0
  113. package/src/receipts.ts +97 -0
  114. package/src/resilience.ts +220 -0
  115. package/src/runtime.ts +171 -0
  116. package/src/sampling.ts +68 -0
  117. package/src/sanitize.ts +8 -0
  118. package/src/schema.ts +135 -0
  119. package/src/shutdown.ts +18 -0
  120. package/src/slo.ts +156 -0
  121. package/src/testing.ts +56 -0
  122. package/src/tracing.ts +211 -0
package/dist/pretty.js ADDED
@@ -0,0 +1,85 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Pretty ANSI log renderer for CLI / terminal output.
5
+ *
6
+ * Mirrors Python provide.telemetry.logger.pretty.PrettyRenderer.
7
+ * Same color scheme, same layout: timestamp [level] event key=value pairs.
8
+ */
9
+ const RESET = '\x1b[0m';
10
+ const DIM = '\x1b[2m';
11
+ const LEVEL_COLORS = {
12
+ fatal: '\x1b[31;1m', // bold red
13
+ error: '\x1b[31m', // red
14
+ warn: '\x1b[33m', // yellow
15
+ info: '\x1b[32m', // green
16
+ debug: '\x1b[34m', // blue
17
+ trace: '\x1b[36m', // cyan
18
+ };
19
+ // pino level number → name
20
+ const LEVEL_NAMES = {
21
+ 10: 'trace',
22
+ 20: 'debug',
23
+ 30: 'info',
24
+ 40: 'warn',
25
+ 50: 'error',
26
+ 60: 'fatal',
27
+ };
28
+ const LEVEL_PAD = 6; // "fatal" = 5, pad to 6
29
+ // Keys to exclude from the key=value tail (already rendered or internal)
30
+ const SKIP_KEYS = new Set(['level', 'time', 'msg', 'event', 'v', 'pid', 'hostname']);
31
+ /**
32
+ * Detect whether stdout supports color.
33
+ * Returns false in browsers, CI without FORCE_COLOR, or piped output.
34
+ */
35
+ export function supportsColor() {
36
+ // Stryker disable next-line ConditionalExpression,StringLiteral,BooleanLiteral -- browser-only guard
37
+ /* v8 ignore next -- browser-only path, untestable in Node */
38
+ if (typeof process === 'undefined')
39
+ return false;
40
+ if (process.env['FORCE_COLOR'] === '1' || process.env['FORCE_COLOR'] === 'true')
41
+ return true;
42
+ if (process.env['NO_COLOR'] !== undefined)
43
+ return false;
44
+ // Stryker disable next-line OptionalChaining -- process.stdout is always defined in Node/test env
45
+ if (typeof process.stdout?.isTTY === 'boolean')
46
+ return process.stdout.isTTY;
47
+ return false;
48
+ }
49
+ /**
50
+ * Format a pino log object as a pretty ANSI string.
51
+ *
52
+ * Layout: `timestamp [level ] event key=value key=value`
53
+ */
54
+ export function formatPretty(obj, colors) {
55
+ const parts = [];
56
+ // 1. Timestamp
57
+ const time = obj['time'];
58
+ if (time !== undefined) {
59
+ const ts = typeof time === 'number' ? new Date(time).toISOString() : String(time);
60
+ parts.push(colors ? DIM + ts + RESET : ts);
61
+ }
62
+ // 2. Level
63
+ const levelNum = obj['level'];
64
+ const levelName = LEVEL_NAMES[levelNum] ?? 'log';
65
+ const padded = levelName.padEnd(LEVEL_PAD);
66
+ if (colors) {
67
+ const c = LEVEL_COLORS[levelName] ?? '';
68
+ parts.push('[' + c + padded + RESET + ']');
69
+ }
70
+ else {
71
+ parts.push('[' + padded + ']');
72
+ }
73
+ // 3. Event / message
74
+ const event = obj['event'] ?? obj['msg'] ?? '';
75
+ parts.push(String(event));
76
+ // 4. Remaining key=value pairs (sorted, skip internal keys)
77
+ const keys = Object.keys(obj)
78
+ .filter((k) => !SKIP_KEYS.has(k))
79
+ .sort();
80
+ for (const k of keys) {
81
+ const v = JSON.stringify(obj[k]);
82
+ parts.push(colors ? DIM + k + RESET + '=' + v : k + '=' + v);
83
+ }
84
+ return parts.join(' ');
85
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * W3C trace context propagation helpers.
3
+ * Mirrors Python provide.telemetry.propagation.
4
+ */
5
+ export interface PropagationContext {
6
+ traceparent?: string;
7
+ tracestate?: string;
8
+ baggage?: string;
9
+ traceId?: string;
10
+ spanId?: string;
11
+ }
12
+ /** Maximum length (in characters) for traceparent or tracestate header values. */
13
+ export declare const MAX_HEADER_LENGTH = 512;
14
+ /** Maximum number of comma-separated key=value pairs in tracestate. */
15
+ export declare const MAX_TRACESTATE_PAIRS = 32;
16
+ /** Maximum length (in characters) for the baggage header value. */
17
+ export declare const MAX_BAGGAGE_LENGTH = 8192;
18
+ type PropagationStore = {
19
+ active: PropagationContext;
20
+ stack: PropagationContext[];
21
+ otelCtxStack: unknown[];
22
+ };
23
+ export type PropagationALS = {
24
+ getStore(): PropagationStore | undefined;
25
+ run<T>(store: PropagationStore, fn: () => T): T;
26
+ enterWith(store: PropagationStore): void;
27
+ };
28
+ /**
29
+ * Extract W3C trace context from an HTTP headers object.
30
+ */
31
+ export declare function extractW3cContext(headers: Record<string, string>): PropagationContext;
32
+ /**
33
+ * Push ctx onto the propagation stack, making it the active context.
34
+ * When traceparent is present and OTel API is available, extracts an OTel
35
+ * context so that child spans created via withTrace() inherit the parent.
36
+ */
37
+ export declare function bindPropagationContext(ctx: PropagationContext): void;
38
+ /**
39
+ * Pop the last saved context, restoring the previous state.
40
+ */
41
+ export declare function clearPropagationContext(): void;
42
+ /** Return the currently active propagation context. */
43
+ export declare function getActivePropagationContext(): PropagationContext;
44
+ /** Return the top of the OTel context stack, or undefined if empty/no OTel wiring. */
45
+ export declare function getActiveOtelContext(): unknown | undefined;
46
+ export declare function _resetPropagationForTests(): void;
47
+ /** Disable AsyncLocalStorage for testing the module-level fallback path. */
48
+ export declare function _disablePropagationALSForTest(): PropagationALS | null;
49
+ /** Re-enable AsyncLocalStorage after testing (pass value from _disable call). */
50
+ export declare function _restorePropagationALSForTest(saved: PropagationALS | null): void;
51
+ export {};
52
+ //# sourceMappingURL=propagation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propagation.d.ts","sourceRoot":"","sources":["../src/propagation.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,uEAAuE;AACvE,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAGvC,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,YAAY,EAAE,OAAO,EAAE,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,IAAI,gBAAgB,GAAG,SAAS,CAAC;IACzC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAChD,SAAS,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC1C,CAAC;AAiEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,kBAAkB,CAkCrF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,CAyBpE;AAED;;GAEG;AAEH,wBAAgB,uBAAuB,IAAI,IAAI,CAa9C;AAGD,uDAAuD;AACvD,wBAAgB,2BAA2B,IAAI,kBAAkB,CAEhE;AAED,sFAAsF;AACtF,wBAAgB,oBAAoB,IAAI,OAAO,GAAG,SAAS,CAK1D;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAMhD;AAED,4EAA4E;AAC5E,wBAAgB,6BAA6B,IAAI,cAAc,GAAG,IAAI,CAIrE;AAED,iFAAiF;AACjF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAEhF"}
@@ -0,0 +1,183 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /** Maximum length (in characters) for traceparent or tracestate header values. */
4
+ export const MAX_HEADER_LENGTH = 512;
5
+ /** Maximum number of comma-separated key=value pairs in tracestate. */
6
+ export const MAX_TRACESTATE_PAIRS = 32;
7
+ /** Maximum length (in characters) for the baggage header value. */
8
+ export const MAX_BAGGAGE_LENGTH = 8192;
9
+ // ── AsyncLocalStorage (Node.js / Cloudflare Workers) ──────────────────────────
10
+ let _als = null;
11
+ let _AlsConstructor = null;
12
+ // Stryker disable BlockStatement: module-level try/catch runs once at import time — cannot be tested by unit tests
13
+ try {
14
+ // Dynamic require so the import doesn't break browser bundles.
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ const als = require('node:async_hooks');
17
+ _AlsConstructor = als.AsyncLocalStorage;
18
+ _als = new _AlsConstructor();
19
+ }
20
+ catch {
21
+ // Not available — fall back to module-level store below.
22
+ }
23
+ // Stryker restore BlockStatement
24
+ // ── Fallback: module-level store (browser / single-thread) ────────────────────
25
+ // Stryker disable next-line ArrayDeclaration: initial empty arrays are overwritten by _resetPropagationForTests in every test beforeEach
26
+ let _fallbackStore = { active: {}, stack: [], otelCtxStack: [] };
27
+ function _getStore() {
28
+ if (_als) {
29
+ return _als.getStore() ?? _fallbackStore;
30
+ }
31
+ return _fallbackStore;
32
+ }
33
+ // Stryker disable ConditionalExpression,BlockStatement,ArrayDeclaration: _ensureStore ALS-to-fallback clone path — tested by "clones fallback stack" test; remaining mutants are equivalent because _resetPropagationForTests empties both stores
34
+ function _ensureStore() {
35
+ if (_als) {
36
+ const store = _als.getStore();
37
+ if (store)
38
+ return store;
39
+ const next = {
40
+ active: { ..._fallbackStore.active },
41
+ stack: _fallbackStore.stack.map((entry) => ({ ...entry })),
42
+ otelCtxStack: [..._fallbackStore.otelCtxStack],
43
+ };
44
+ _als.enterWith(next);
45
+ return next;
46
+ }
47
+ return _fallbackStore;
48
+ }
49
+ // Stryker restore ConditionalExpression,BlockStatement,ArrayDeclaration
50
+ function _parseTraceparent(value) {
51
+ const parts = value.split('-');
52
+ if (parts.length !== 4)
53
+ return {};
54
+ const [version, traceId, spanId] = parts;
55
+ if (version.length !== 2 || traceId.length !== 32 || spanId.length !== 16)
56
+ return {};
57
+ if (version.toLowerCase() === 'ff')
58
+ return {};
59
+ if (traceId === '0'.repeat(32) || spanId === '0'.repeat(16))
60
+ return {};
61
+ // Validate that all fields are valid hex strings.
62
+ if (!/^[0-9a-fA-F]+$/.test(version) ||
63
+ !/^[0-9a-fA-F]+$/.test(traceId) ||
64
+ !/^[0-9a-fA-F]+$/.test(spanId)) {
65
+ return {};
66
+ }
67
+ return { traceId: traceId.toLowerCase(), spanId: spanId.toLowerCase() };
68
+ }
69
+ /**
70
+ * Extract W3C trace context from an HTTP headers object.
71
+ */
72
+ export function extractW3cContext(headers) {
73
+ const lower = {};
74
+ for (const [k, v] of Object.entries(headers))
75
+ lower[k.toLowerCase()] = v;
76
+ let rawTraceparent = lower['traceparent'];
77
+ let tracestate = lower['tracestate'];
78
+ let baggage = lower['baggage'];
79
+ // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: size guard — >= vs > on boundary is equivalent (512-char valid traceparent doesn't exist)
80
+ if (rawTraceparent !== undefined && rawTraceparent.length > MAX_HEADER_LENGTH) {
81
+ rawTraceparent = undefined;
82
+ }
83
+ if (tracestate !== undefined) {
84
+ if (tracestate.length > MAX_HEADER_LENGTH) {
85
+ tracestate = undefined;
86
+ }
87
+ else if (tracestate.split(',').length > MAX_TRACESTATE_PAIRS) {
88
+ tracestate = undefined;
89
+ }
90
+ }
91
+ if (baggage !== undefined && baggage.length > MAX_BAGGAGE_LENGTH) {
92
+ baggage = undefined;
93
+ }
94
+ const { traceId, spanId } = rawTraceparent ? _parseTraceparent(rawTraceparent) : {};
95
+ // Stryker disable next-line LogicalOperator: traceId and spanId are always both defined or both undefined (from _parseTraceparent) — && and || give identical results
96
+ const traceparent = traceId && spanId ? rawTraceparent : undefined;
97
+ return {
98
+ ...(traceparent !== undefined && { traceparent }),
99
+ ...(tracestate !== undefined && { tracestate }),
100
+ ...(baggage !== undefined && { baggage }),
101
+ ...(traceId !== undefined && { traceId }),
102
+ ...(spanId !== undefined && { spanId }),
103
+ };
104
+ }
105
+ /**
106
+ * Push ctx onto the propagation stack, making it the active context.
107
+ * When traceparent is present and OTel API is available, extracts an OTel
108
+ * context so that child spans created via withTrace() inherit the parent.
109
+ */
110
+ export function bindPropagationContext(ctx) {
111
+ const store = _ensureStore();
112
+ store.stack.push({ ...store.active });
113
+ store.active = { ...store.active, ...ctx };
114
+ // Wire into OTel context chain when traceparent is present.
115
+ if (ctx.traceparent) {
116
+ try {
117
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
118
+ const otelApi = require('@opentelemetry/api');
119
+ /* Stryker disable all: OTel context wiring — carrier key, extract call, catch/else sentinels are equivalent when OTel SDK behavior varies */
120
+ const carrier = { traceparent: ctx.traceparent };
121
+ if (ctx.tracestate)
122
+ carrier['tracestate'] = ctx.tracestate;
123
+ const extracted = otelApi.propagation.extract(otelApi.context.active(), carrier);
124
+ store.otelCtxStack.push(extracted);
125
+ }
126
+ catch {
127
+ store.otelCtxStack.push(undefined);
128
+ }
129
+ }
130
+ else {
131
+ store.otelCtxStack.push(undefined);
132
+ }
133
+ /* Stryker restore all */
134
+ }
135
+ /**
136
+ * Pop the last saved context, restoring the previous state.
137
+ */
138
+ // Stryker disable BlockStatement
139
+ export function clearPropagationContext() {
140
+ const store = _ensureStore();
141
+ // Stryker disable next-line ConditionalExpression,EqualityOperator
142
+ if (store.stack.length > 0) {
143
+ // Stryker enable BlockStatement
144
+ const restored = store.stack.pop();
145
+ /* v8 ignore next */
146
+ store.active = restored ?? {};
147
+ }
148
+ else {
149
+ // Stryker disable BlockStatement: empty else body is equivalent — active is always {} here because pop() restores prior state
150
+ store.active = {};
151
+ }
152
+ store.otelCtxStack.pop();
153
+ }
154
+ // Stryker enable BlockStatement
155
+ /** Return the currently active propagation context. */
156
+ export function getActivePropagationContext() {
157
+ return { ..._getStore().active };
158
+ }
159
+ /** Return the top of the OTel context stack, or undefined if empty/no OTel wiring. */
160
+ export function getActiveOtelContext() {
161
+ const stack = _getStore().otelCtxStack;
162
+ // Stryker disable next-line ConditionalExpression: empty stack returns undefined; removing returns undefined from array[-1] which is also undefined
163
+ if (stack.length === 0)
164
+ return undefined;
165
+ return stack[stack.length - 1];
166
+ }
167
+ export function _resetPropagationForTests() {
168
+ // Recreate the ALS instance so no enterWith-seeded store leaks between tests.
169
+ // The null branch is only reachable in environments without node:async_hooks (e.g. browsers).
170
+ /* v8 ignore next */
171
+ _als = _AlsConstructor ? new _AlsConstructor() : null;
172
+ _fallbackStore = { active: {}, stack: [], otelCtxStack: [] };
173
+ }
174
+ /** Disable AsyncLocalStorage for testing the module-level fallback path. */
175
+ export function _disablePropagationALSForTest() {
176
+ const prev = _als;
177
+ _als = null;
178
+ return prev;
179
+ }
180
+ /** Re-enable AsyncLocalStorage after testing (pass value from _disable call). */
181
+ export function _restorePropagationALSForTest(saved) {
182
+ _als = saved;
183
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * React 18+ helpers for @provide-io/telemetry.
3
+ *
4
+ * Import from '@provide-io/telemetry/react'.
5
+ * React must be installed as a peer dependency (>=18).
6
+ */
7
+ import { Component } from 'react';
8
+ import type { ErrorInfo, ReactNode } from 'react';
9
+ /**
10
+ * Bind key/value pairs into telemetry context for the lifetime of the component.
11
+ * Cleans up on unmount. Re-runs when values change (content-compared, not by reference).
12
+ */
13
+ export declare function useTelemetryContext(values: Record<string, unknown>): void;
14
+ interface TelemetryErrorBoundaryProps {
15
+ children: ReactNode;
16
+ fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode);
17
+ onError?: (error: Error, info: ErrorInfo) => void;
18
+ }
19
+ interface TelemetryErrorBoundaryState {
20
+ error: Error | null;
21
+ }
22
+ /**
23
+ * React error boundary that logs caught render errors via getLogger and renders
24
+ * a fallback UI. Accepts a static ReactNode or a render-prop that receives the
25
+ * caught error and a reset callback.
26
+ *
27
+ * Auto-logs to getLogger('react.error_boundary') on every catch. Call onError
28
+ * for any additional handling (alerting, Sentry, etc.).
29
+ */
30
+ export declare class TelemetryErrorBoundary extends Component<TelemetryErrorBoundaryProps, TelemetryErrorBoundaryState> {
31
+ constructor(props: TelemetryErrorBoundaryProps);
32
+ static getDerivedStateFromError(error: Error): TelemetryErrorBoundaryState;
33
+ componentDidCatch(error: Error, info: ErrorInfo): void;
34
+ reset(): void;
35
+ render(): ReactNode;
36
+ }
37
+ export {};
38
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAa,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMlD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAezE;AAID,UAAU,2BAA2B;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC;IACvE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACnD;AAED,UAAU,2BAA2B;IACnC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,qBAAa,sBAAuB,SAAQ,SAAS,CACnD,2BAA2B,EAC3B,2BAA2B,CAC5B;gBACa,KAAK,EAAE,2BAA2B;IAM9C,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,2BAA2B;IAI1E,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUtD,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,SAAS;CAWpB"}
package/dist/react.js ADDED
@@ -0,0 +1,72 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * React 18+ helpers for @provide-io/telemetry.
5
+ *
6
+ * Import from '@provide-io/telemetry/react'.
7
+ * React must be installed as a peer dependency (>=18).
8
+ */
9
+ import { Component, useEffect } from 'react';
10
+ import { bindContext, unbindContext } from './context';
11
+ import { getLogger } from './logger';
12
+ // ── useTelemetryContext ──────────────────────────────────────────────────────
13
+ /**
14
+ * Bind key/value pairs into telemetry context for the lifetime of the component.
15
+ * Cleans up on unmount. Re-runs when values change (content-compared, not by reference).
16
+ */
17
+ export function useTelemetryContext(values) {
18
+ // Content-stable dep: avoids re-running when the object reference changes but values are equal.
19
+ // Note: key insertion order affects JSON.stringify — { b:1, a:2 } !== { a:2, b:1 }.
20
+ // Callers that build `values` via dynamic spread should keep key order consistent.
21
+ const serialized = JSON.stringify(values);
22
+ useEffect(() => {
23
+ const keys = Object.keys(values);
24
+ bindContext(values);
25
+ return () => {
26
+ unbindContext(...keys);
27
+ };
28
+ // `serialized` is the intentional dep — avoids re-running for referentially-new-but-equal
29
+ // objects. `values` is deliberately omitted; the serialized string is the stable proxy.
30
+ }, [serialized]);
31
+ }
32
+ /**
33
+ * React error boundary that logs caught render errors via getLogger and renders
34
+ * a fallback UI. Accepts a static ReactNode or a render-prop that receives the
35
+ * caught error and a reset callback.
36
+ *
37
+ * Auto-logs to getLogger('react.error_boundary') on every catch. Call onError
38
+ * for any additional handling (alerting, Sentry, etc.).
39
+ */
40
+ export class TelemetryErrorBoundary extends Component {
41
+ constructor(props) {
42
+ super(props);
43
+ this.state = { error: null };
44
+ this.reset = this.reset.bind(this);
45
+ }
46
+ static getDerivedStateFromError(error) {
47
+ return { error };
48
+ }
49
+ componentDidCatch(error, info) {
50
+ getLogger('react.error_boundary').error({
51
+ event: 'react_error_caught',
52
+ error_message: error.message,
53
+ error_stack: error.stack ?? '',
54
+ component_stack: info.componentStack ?? '',
55
+ });
56
+ this.props.onError?.(error, info);
57
+ }
58
+ reset() {
59
+ this.setState({ error: null });
60
+ }
61
+ render() {
62
+ const { error } = this.state;
63
+ if (error !== null) {
64
+ const { fallback } = this.props;
65
+ if (typeof fallback === 'function') {
66
+ return fallback(error, this.reset);
67
+ }
68
+ return fallback;
69
+ }
70
+ return this.props.children;
71
+ }
72
+ }
@@ -0,0 +1,26 @@
1
+ /** An immutable audit record for a single PII redaction event. */
2
+ export interface RedactionReceipt {
3
+ receiptId: string;
4
+ timestamp: string;
5
+ serviceName: string;
6
+ fieldPath: string;
7
+ action: string;
8
+ originalHash: string;
9
+ hmac: string;
10
+ }
11
+ /** Options for enabling receipt generation. */
12
+ export interface EnableReceiptsOptions {
13
+ enabled: boolean;
14
+ signingKey?: string;
15
+ serviceName?: string;
16
+ }
17
+ /**
18
+ * Enable or disable receipt generation.
19
+ * When enabled, a hook is registered on the PII engine to capture redaction events.
20
+ */
21
+ export declare function enableReceipts(options: EnableReceiptsOptions): void;
22
+ /** Returns receipts collected during test mode. */
23
+ export declare function getEmittedReceiptsForTests(): RedactionReceipt[];
24
+ /** Resets all receipt state and enables test-mode collection. */
25
+ export declare function resetReceiptsForTests(): void;
26
+ //# sourceMappingURL=receipts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipts.d.ts","sourceRoot":"","sources":["../src/receipts.ts"],"names":[],"mappings":"AAaA,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AASD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAUnE;AA+BD,mDAAmD;AACnD,wBAAgB,0BAA0B,IAAI,gBAAgB,EAAE,CAE/D;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,IAAI,IAAI,CAO5C"}
@@ -0,0 +1,69 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Cryptographic redaction receipts — strippable governance module.
5
+ *
6
+ * Registers a receipt hook on the PII engine when enabled.
7
+ * If this file is deleted, the PII engine runs unchanged (hook stays null).
8
+ */
9
+ import { createHash, createHmac, randomUUID } from 'crypto';
10
+ import { setReceiptHook } from './pii';
11
+ let _enabled = false;
12
+ let _signingKey;
13
+ let _serviceName = 'unknown';
14
+ let _testMode = false;
15
+ // Stryker disable next-line ArrayDeclaration
16
+ const _testReceipts = [];
17
+ /**
18
+ * Enable or disable receipt generation.
19
+ * When enabled, a hook is registered on the PII engine to capture redaction events.
20
+ */
21
+ export function enableReceipts(options) {
22
+ _enabled = options.enabled;
23
+ _signingKey = options.signingKey;
24
+ _serviceName = options.serviceName ?? 'unknown';
25
+ if (_enabled) {
26
+ setReceiptHook(_onRedaction);
27
+ }
28
+ else {
29
+ setReceiptHook(null);
30
+ }
31
+ }
32
+ function _onRedaction(fieldPath, action, originalValue) {
33
+ const receiptId = randomUUID();
34
+ const timestamp = new Date().toISOString();
35
+ const originalHash = createHash('sha256').update(String(originalValue)).digest('hex');
36
+ let hmacValue = '';
37
+ if (_signingKey) {
38
+ const payload = `${receiptId}|${timestamp}|${fieldPath}|${action}|${originalHash}`;
39
+ hmacValue = createHmac('sha256', _signingKey).update(payload).digest('hex');
40
+ }
41
+ const receipt = {
42
+ receiptId,
43
+ timestamp,
44
+ serviceName: _serviceName,
45
+ fieldPath,
46
+ action,
47
+ originalHash,
48
+ hmac: hmacValue,
49
+ };
50
+ /* v8 ignore next 3: production-mode receipt emission — not exercised in test mode */
51
+ if (_testMode) {
52
+ _testReceipts.push(receipt);
53
+ }
54
+ // In production mode, receipts would be emitted via the logger.
55
+ // (No-op here when not in test mode — callers integrate with their logging pipeline.)
56
+ }
57
+ /** Returns receipts collected during test mode. */
58
+ export function getEmittedReceiptsForTests() {
59
+ return [..._testReceipts];
60
+ }
61
+ /** Resets all receipt state and enables test-mode collection. */
62
+ export function resetReceiptsForTests() {
63
+ _enabled = false;
64
+ _signingKey = undefined;
65
+ _serviceName = 'unknown';
66
+ _testMode = true;
67
+ _testReceipts.length = 0;
68
+ setReceiptHook(null);
69
+ }
@@ -0,0 +1,26 @@
1
+ export declare class TelemetryTimeoutError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export interface ExporterPolicy {
5
+ retries: number;
6
+ backoffMs: number;
7
+ timeoutMs: number;
8
+ failOpen: boolean;
9
+ }
10
+ export declare const CIRCUIT_BREAKER_THRESHOLD = 3;
11
+ export declare const CIRCUIT_BASE_COOLDOWN_MS = 30000;
12
+ export declare const _consecutiveTimeouts: Record<string, number>;
13
+ export declare const _circuitTrippedAt: Record<string, number>;
14
+ export declare const _openCount: Record<string, number>;
15
+ export declare const _halfOpenProbing: Record<string, boolean>;
16
+ export declare function setExporterPolicy(signal: string, policy: Partial<ExporterPolicy>): void;
17
+ export declare function getExporterPolicy(signal: string): ExporterPolicy;
18
+ export declare function runWithResilience<T>(signal: string, fn: () => Promise<T>): Promise<T | null>;
19
+ export interface CircuitState {
20
+ state: string;
21
+ openCount: number;
22
+ cooldownRemainingMs: number;
23
+ }
24
+ export declare function getCircuitState(signal: string): CircuitState;
25
+ export declare function _resetResilienceForTests(): void;
26
+ //# sourceMappingURL=resilience.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience.d.ts","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"AAgBA,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AASD,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAM/C,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAsC,CAAC;AAE/F,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAsC,CAAC;AAE5F,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAsC,CAAC;AAGrF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAIpD,CAAC;AAGF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAEvF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAEhE;AA4BD,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAwFnB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAe5D;AAKD,wBAAgB,wBAAwB,IAAI,IAAI,CAQ/C"}