autotel 4.0.0 → 4.2.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 (232) hide show
  1. package/README.md +26 -1
  2. package/dist/auto.cjs +2 -2
  3. package/dist/auto.js +1 -1
  4. package/dist/correlation-id.cjs +1 -1
  5. package/dist/correlation-id.js +1 -1
  6. package/dist/decorators.cjs +1 -1
  7. package/dist/decorators.js +1 -1
  8. package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
  9. package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
  10. package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
  11. package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
  12. package/dist/event.cjs +1 -1
  13. package/dist/event.js +1 -1
  14. package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
  15. package/dist/functional-DtI0u4vx.js.map +1 -0
  16. package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
  17. package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
  18. package/dist/functional.cjs +1 -1
  19. package/dist/functional.js +1 -1
  20. package/dist/http.cjs +1 -1
  21. package/dist/http.js +1 -1
  22. package/dist/index.cjs +5 -5
  23. package/dist/index.d.cts +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +5 -5
  26. package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
  27. package/dist/init-B7u-DjxM.d.ts.map +1 -0
  28. package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
  29. package/dist/init-BX7AmFRl.cjs.map +1 -0
  30. package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
  31. package/dist/init-D-jnNMix.js.map +1 -0
  32. package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
  33. package/dist/init-DSrRmVnz.d.cts.map +1 -0
  34. package/dist/instrumentation.cjs +1 -1
  35. package/dist/instrumentation.js +1 -1
  36. package/dist/logger-D3Ej3DII.js +446 -0
  37. package/dist/logger-D3Ej3DII.js.map +1 -0
  38. package/dist/logger-thMPLpOG.cjs +487 -0
  39. package/dist/logger-thMPLpOG.cjs.map +1 -0
  40. package/dist/logger.cjs +8 -236
  41. package/dist/logger.js +2 -204
  42. package/dist/messaging.cjs +1 -1
  43. package/dist/messaging.js +1 -1
  44. package/dist/semantic-helpers.cjs +1 -1
  45. package/dist/semantic-helpers.js +1 -1
  46. package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
  47. package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
  48. package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
  49. package/dist/track-wc0HafS_.js.map +1 -0
  50. package/dist/webhook.cjs +1 -1
  51. package/dist/webhook.js +1 -1
  52. package/dist/workflow-distributed.cjs +1 -1
  53. package/dist/workflow-distributed.js +1 -1
  54. package/dist/workflow.cjs +1 -1
  55. package/dist/workflow.js +1 -1
  56. package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
  57. package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
  58. package/dist/yaml-config.cjs +1 -1
  59. package/dist/yaml-config.d.cts +7 -1
  60. package/dist/yaml-config.d.cts.map +1 -1
  61. package/dist/yaml-config.d.ts +7 -1
  62. package/dist/yaml-config.d.ts.map +1 -1
  63. package/dist/yaml-config.js +1 -0
  64. package/dist/yaml-config.js.map +1 -1
  65. package/package.json +1 -2
  66. package/skills/autotel-core/SKILL.md +2 -0
  67. package/skills/autotel-instrumentation/SKILL.md +25 -0
  68. package/skills/debug-missing-spans/SKILL.md +3 -1
  69. package/skills/migrate-to-autotel/SKILL.md +24 -23
  70. package/skills/review-otel-patterns/SKILL.md +5 -4
  71. package/dist/functional-BGkT8J-h.js.map +0 -1
  72. package/dist/init-CNp-ee80.d.cts.map +0 -1
  73. package/dist/init-Ch6t7MNI.js.map +0 -1
  74. package/dist/init-DJQOdVlN.d.ts.map +0 -1
  75. package/dist/init-DvapOXCc.cjs.map +0 -1
  76. package/dist/logger.cjs.map +0 -1
  77. package/dist/logger.js.map +0 -1
  78. package/dist/track-nsKVy-pj.js.map +0 -1
  79. package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
  80. package/src/attribute-redacting-processor.test.ts +0 -763
  81. package/src/attribute-redacting-processor.ts +0 -621
  82. package/src/attributes/attachers.ts +0 -161
  83. package/src/attributes/builders.ts +0 -529
  84. package/src/attributes/domains.ts +0 -42
  85. package/src/attributes/index.ts +0 -81
  86. package/src/attributes/registry.ts +0 -323
  87. package/src/attributes/types.ts +0 -211
  88. package/src/attributes/utils.ts +0 -64
  89. package/src/attributes/validators.ts +0 -266
  90. package/src/attributes.test.ts +0 -292
  91. package/src/auto.ts +0 -67
  92. package/src/autotel-logger.test.ts +0 -548
  93. package/src/autotel-logger.ts +0 -364
  94. package/src/baggage-span-processor.test.ts +0 -202
  95. package/src/baggage-span-processor.ts +0 -100
  96. package/src/business-baggage.test.ts +0 -500
  97. package/src/business-baggage.ts +0 -669
  98. package/src/circuit-breaker.test.ts +0 -341
  99. package/src/circuit-breaker.ts +0 -184
  100. package/src/config.test.ts +0 -94
  101. package/src/config.ts +0 -172
  102. package/src/correlated-events.test.ts +0 -151
  103. package/src/correlated-events.ts +0 -47
  104. package/src/correlation-id.test.ts +0 -163
  105. package/src/correlation-id.ts +0 -206
  106. package/src/db.test.ts +0 -252
  107. package/src/db.ts +0 -447
  108. package/src/decorators.test.ts +0 -153
  109. package/src/decorators.ts +0 -188
  110. package/src/define-event.test.ts +0 -41
  111. package/src/define-event.ts +0 -58
  112. package/src/devtools.ts +0 -60
  113. package/src/drain-pipeline.test.ts +0 -68
  114. package/src/drain-pipeline.ts +0 -199
  115. package/src/drain-toolkit.test.ts +0 -113
  116. package/src/drain-toolkit.ts +0 -129
  117. package/src/enricher-toolkit.test.ts +0 -67
  118. package/src/enricher-toolkit.ts +0 -79
  119. package/src/enrichers.test.ts +0 -150
  120. package/src/enrichers.ts +0 -145
  121. package/src/env-config.test.ts +0 -323
  122. package/src/env-config.ts +0 -309
  123. package/src/error-catalog.test.ts +0 -133
  124. package/src/error-catalog.ts +0 -262
  125. package/src/event-queue.test.ts +0 -864
  126. package/src/event-queue.ts +0 -699
  127. package/src/event-subscriber.ts +0 -262
  128. package/src/event-testing.ts +0 -197
  129. package/src/event.test.ts +0 -1104
  130. package/src/event.ts +0 -988
  131. package/src/events-config.ts +0 -235
  132. package/src/exporters.ts +0 -165
  133. package/src/filtering-span-processor.test.ts +0 -281
  134. package/src/filtering-span-processor.ts +0 -111
  135. package/src/flatten-attributes.test.ts +0 -76
  136. package/src/flatten-attributes.ts +0 -80
  137. package/src/functional.strict-types.typecheck.ts +0 -53
  138. package/src/functional.test.ts +0 -1464
  139. package/src/functional.ts +0 -2539
  140. package/src/functional.types.test.ts +0 -135
  141. package/src/hook.mjs +0 -15
  142. package/src/http.test.ts +0 -485
  143. package/src/http.ts +0 -424
  144. package/src/index.ts +0 -433
  145. package/src/init-auto-redactor.test.ts +0 -53
  146. package/src/init-redactor.test.ts +0 -8
  147. package/src/init.customization.test.ts +0 -594
  148. package/src/init.integrations.test.ts +0 -399
  149. package/src/init.openllmetry.test.ts +0 -194
  150. package/src/init.protocol.test.ts +0 -215
  151. package/src/init.ts +0 -2312
  152. package/src/instrumentation.test.ts +0 -108
  153. package/src/instrumentation.ts +0 -319
  154. package/src/logger.test.ts +0 -125
  155. package/src/logger.ts +0 -341
  156. package/src/messaging-adapters.test.ts +0 -595
  157. package/src/messaging-adapters.ts +0 -583
  158. package/src/messaging-testing.test.ts +0 -573
  159. package/src/messaging-testing.ts +0 -935
  160. package/src/messaging.test.ts +0 -1646
  161. package/src/messaging.ts +0 -2245
  162. package/src/metric-helpers.ts +0 -47
  163. package/src/metric-testing.ts +0 -197
  164. package/src/metric.ts +0 -446
  165. package/src/metrics.test.ts +0 -241
  166. package/src/node-require.ts +0 -123
  167. package/src/operation-context.ts +0 -93
  168. package/src/parse-error.test.ts +0 -73
  169. package/src/parse-error.ts +0 -112
  170. package/src/posthog-logs.test.ts +0 -115
  171. package/src/posthog-logs.ts +0 -77
  172. package/src/pretty-console-exporter.test.ts +0 -545
  173. package/src/pretty-console-exporter.ts +0 -413
  174. package/src/pretty-log-formatter.test.ts +0 -123
  175. package/src/pretty-log-formatter.ts +0 -210
  176. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  177. package/src/processors/canonical-log-line-processor.ts +0 -396
  178. package/src/processors.ts +0 -152
  179. package/src/rate-limiter.test.ts +0 -199
  180. package/src/rate-limiter.ts +0 -98
  181. package/src/redact-values.test.ts +0 -90
  182. package/src/redact-values.ts +0 -34
  183. package/src/register.ts +0 -37
  184. package/src/request-logger.test.ts +0 -545
  185. package/src/request-logger.ts +0 -342
  186. package/src/sampling.test.ts +0 -1060
  187. package/src/sampling.ts +0 -737
  188. package/src/security-schema.test.ts +0 -45
  189. package/src/security-schema.ts +0 -107
  190. package/src/semantic-conventions.ts +0 -15
  191. package/src/semantic-helpers.test.ts +0 -226
  192. package/src/semantic-helpers.ts +0 -438
  193. package/src/shutdown.test.ts +0 -364
  194. package/src/shutdown.ts +0 -246
  195. package/src/span-name-normalizer.test.ts +0 -377
  196. package/src/span-name-normalizer.ts +0 -213
  197. package/src/stable-hash.ts +0 -27
  198. package/src/structured-error.test.ts +0 -191
  199. package/src/structured-error.ts +0 -157
  200. package/src/stub.integration.test.ts +0 -361
  201. package/src/tail-sampling-processor.test.ts +0 -230
  202. package/src/tail-sampling-processor.ts +0 -55
  203. package/src/test-span-collector.test.ts +0 -234
  204. package/src/test-span-collector.ts +0 -150
  205. package/src/testing.ts +0 -705
  206. package/src/trace-context.test.ts +0 -73
  207. package/src/trace-context.ts +0 -567
  208. package/src/trace-helpers.new.test.ts +0 -278
  209. package/src/trace-helpers.test.ts +0 -290
  210. package/src/trace-helpers.ts +0 -710
  211. package/src/trace-hybrid.test.ts +0 -42
  212. package/src/trace-hybrid.ts +0 -37
  213. package/src/tracer-provider.test.ts +0 -183
  214. package/src/tracer-provider.ts +0 -266
  215. package/src/track.test.ts +0 -154
  216. package/src/track.ts +0 -216
  217. package/src/validate.test.ts +0 -287
  218. package/src/validate.ts +0 -307
  219. package/src/validation-attributes.ts +0 -43
  220. package/src/validation.test.ts +0 -330
  221. package/src/validation.ts +0 -246
  222. package/src/variable-name-inference.test.ts +0 -178
  223. package/src/variable-name-inference.ts +0 -242
  224. package/src/webhook.test.ts +0 -649
  225. package/src/webhook.ts +0 -637
  226. package/src/workflow-distributed.test.ts +0 -786
  227. package/src/workflow-distributed.ts +0 -916
  228. package/src/workflow.async-safety.integration.test.ts +0 -345
  229. package/src/workflow.test.ts +0 -647
  230. package/src/workflow.ts +0 -810
  231. package/src/yaml-config.test.ts +0 -337
  232. package/src/yaml-config.ts +0 -342
@@ -1,669 +0,0 @@
1
- /**
2
- * Safe baggage propagation with guardrails
3
- *
4
- * Provides type-safe baggage schemas with built-in protection against
5
- * common pitfalls: high-cardinality values, PII leakage, and oversized payloads.
6
- *
7
- * @example Define a custom schema
8
- * ```typescript
9
- * import { createSafeBaggageSchema } from 'autotel/business-baggage';
10
- *
11
- * const OrderBaggage = createSafeBaggageSchema({
12
- * orderId: { type: 'string' },
13
- * customerId: { type: 'string', hash: true }, // Auto-hash for privacy
14
- * priority: { type: 'enum', values: ['low', 'normal', 'high'] },
15
- * });
16
- *
17
- * // Usage in traced function
18
- * OrderBaggage.set(ctx, { orderId: 'ord-123', customerId: 'cust-456', priority: 'high' });
19
- * const { orderId, priority } = OrderBaggage.get(ctx);
20
- * ```
21
- *
22
- * @example Use pre-built BusinessBaggage
23
- * ```typescript
24
- * import { BusinessBaggage } from 'autotel/business-baggage';
25
- *
26
- * BusinessBaggage.set(ctx, { tenantId: 'acme', userId: 'user-123' });
27
- * const { tenantId } = BusinessBaggage.get(ctx);
28
- * ```
29
- *
30
- * @module
31
- */
32
-
33
- import { context, propagation } from '@opentelemetry/api';
34
- import type { TraceContext } from './trace-context';
35
-
36
- // ============================================================================
37
- // Types
38
- // ============================================================================
39
-
40
- /**
41
- * Supported field types in baggage schema
42
- */
43
- export type BaggageFieldType = 'string' | 'number' | 'boolean' | 'enum';
44
-
45
- /**
46
- * Field definition in a baggage schema
47
- */
48
- export interface BaggageFieldDefinition {
49
- /** Field type */
50
- type: BaggageFieldType;
51
-
52
- /** Maximum length for string values (default: 256) */
53
- maxLength?: number;
54
-
55
- /** Hash value before storing (for privacy) */
56
- hash?: boolean;
57
-
58
- /** Allowed values for enum type */
59
- values?: readonly string[];
60
-
61
- /** Default value if not provided */
62
- defaultValue?: string | number | boolean;
63
-
64
- /** Whether field is required */
65
- required?: boolean;
66
-
67
- /** Custom validation function */
68
- validate?: (value: unknown) => boolean;
69
- }
70
-
71
- /**
72
- * Options for creating a safe baggage schema
73
- */
74
- export interface SafeBaggageOptions {
75
- /** Maximum key length (default: 64) */
76
- maxKeyLength?: number;
77
-
78
- /** Maximum value length (default: 256) */
79
- maxValueLength?: number;
80
-
81
- /** Maximum total baggage size in bytes (default: 8192) */
82
- maxTotalSize?: number;
83
-
84
- /** Prefix for all keys (default: none) */
85
- prefix?: string;
86
-
87
- /** Hash high-cardinality values automatically */
88
- hashHighCardinality?: boolean;
89
-
90
- /** Detect and redact PII patterns */
91
- redactPII?: boolean;
92
-
93
- /** Allowed keys whitelist (others rejected) */
94
- allowedKeys?: string[];
95
-
96
- /** Custom error handler */
97
- onError?: (error: BaggageError) => void;
98
- }
99
-
100
- /**
101
- * Schema definition type - maps field names to definitions
102
- */
103
- export type BaggageSchemaDefinition = Record<string, BaggageFieldDefinition>;
104
-
105
- /**
106
- * Inferred type from schema definition
107
- */
108
- export type InferBaggageType<T extends BaggageSchemaDefinition> = {
109
- [K in keyof T]?: T[K]['type'] extends 'string'
110
- ? string
111
- : T[K]['type'] extends 'number'
112
- ? number
113
- : T[K]['type'] extends 'boolean'
114
- ? boolean
115
- : T[K]['type'] extends 'enum'
116
- ? T[K]['values'] extends readonly string[]
117
- ? T[K]['values'][number]
118
- : string
119
- : unknown;
120
- };
121
-
122
- /**
123
- * Baggage error details
124
- */
125
- export interface BaggageError {
126
- type: 'validation' | 'size' | 'pii' | 'key_length' | 'value_length';
127
- key: string;
128
- message: string;
129
- value?: unknown;
130
- }
131
-
132
- /**
133
- * Safe baggage schema interface
134
- */
135
- export interface SafeBaggageSchema<T extends BaggageSchemaDefinition> {
136
- /**
137
- * Get baggage values from context
138
- */
139
- get(ctx?: TraceContext): Partial<InferBaggageType<T>>;
140
-
141
- /**
142
- * Set baggage values in context
143
- * Returns new context with baggage (for context propagation)
144
- */
145
- set(
146
- ctx: TraceContext | undefined,
147
- values: Partial<InferBaggageType<T>>,
148
- ): void;
149
-
150
- /**
151
- * Get a single baggage value
152
- */
153
- getValue<K extends keyof T>(
154
- key: K,
155
- ctx?: TraceContext,
156
- ): InferBaggageType<T>[K] | undefined;
157
-
158
- /**
159
- * Set a single baggage value
160
- */
161
- setValue<K extends keyof T>(
162
- key: K,
163
- value: InferBaggageType<T>[K],
164
- ctx?: TraceContext,
165
- ): void;
166
-
167
- /**
168
- * Clear all schema baggage values
169
- */
170
- clear(ctx?: TraceContext): void;
171
-
172
- /**
173
- * Get all baggage as headers for propagation
174
- */
175
- toHeaders(ctx?: TraceContext): Record<string, string>;
176
-
177
- /**
178
- * Restore baggage from headers
179
- */
180
- fromHeaders(headers: Record<string, string>, ctx?: TraceContext): void;
181
- }
182
-
183
- // ============================================================================
184
- // Constants
185
- // ============================================================================
186
-
187
- const DEFAULT_MAX_KEY_LENGTH = 64;
188
- const DEFAULT_MAX_VALUE_LENGTH = 256;
189
- const DEFAULT_MAX_TOTAL_SIZE = 8192;
190
-
191
- // PII patterns to detect and redact
192
- const PII_PATTERNS = [
193
- /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email
194
- /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, // Phone (US)
195
- /\b\d{3}[-]?\d{2}[-]?\d{4}\b/, // SSN
196
- /\b\d{16}\b/, // Credit card (basic)
197
- ];
198
-
199
- // High-cardinality value patterns
200
- const HIGH_CARDINALITY_PATTERNS = [
201
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, // UUID
202
- /^\d{13,}$/, // Timestamps
203
- /^[A-Za-z0-9+/]{20,}={0,2}$/, // Base64
204
- ];
205
-
206
- // ============================================================================
207
- // Implementation
208
- // ============================================================================
209
-
210
- /**
211
- * Create a safe baggage schema with validation and guardrails
212
- *
213
- * @param schema - Field definitions
214
- * @param options - Safety options
215
- * @returns Type-safe baggage schema
216
- *
217
- * @example
218
- * ```typescript
219
- * const MyBaggage = createSafeBaggageSchema({
220
- * userId: { type: 'string', hash: true },
221
- * region: { type: 'enum', values: ['us', 'eu', 'ap'] },
222
- * debug: { type: 'boolean', defaultValue: false },
223
- * });
224
- * ```
225
- */
226
- export function createSafeBaggageSchema<T extends BaggageSchemaDefinition>(
227
- schema: T,
228
- options: SafeBaggageOptions = {},
229
- ): SafeBaggageSchema<T> {
230
- const {
231
- maxKeyLength = DEFAULT_MAX_KEY_LENGTH,
232
- maxValueLength = DEFAULT_MAX_VALUE_LENGTH,
233
- maxTotalSize = DEFAULT_MAX_TOTAL_SIZE,
234
- prefix = '',
235
- hashHighCardinality = false,
236
- redactPII = false,
237
- allowedKeys,
238
- onError,
239
- } = options;
240
-
241
- // Validate schema keys
242
- const schemaKeys = new Set(Object.keys(schema));
243
- if (allowedKeys) {
244
- for (const key of schemaKeys) {
245
- if (!allowedKeys.includes(key)) {
246
- throw new Error(`Key "${key}" not in allowedKeys whitelist`);
247
- }
248
- }
249
- }
250
-
251
- // Prefix a key
252
- const prefixKey = (key: string): string =>
253
- prefix ? `${prefix}.${key}` : key;
254
-
255
- // Hash a value using simple FNV-1a (synchronous, no crypto dependency)
256
- const hashValue = (value: string): string => {
257
- let hash = 2_166_136_261;
258
- for (let i = 0; i < value.length; i++) {
259
- hash ^= value.codePointAt(i) ?? 0;
260
- hash = (hash * 16_777_619) >>> 0;
261
- }
262
- return `h_${hash.toString(16)}`;
263
- };
264
-
265
- // Check for PII
266
- const containsPII = (value: string): boolean => {
267
- return PII_PATTERNS.some((pattern) => pattern.test(value));
268
- };
269
-
270
- // Check for high-cardinality
271
- const isHighCardinality = (value: string): boolean => {
272
- return HIGH_CARDINALITY_PATTERNS.some((pattern) => pattern.test(value));
273
- };
274
-
275
- // Validate and transform a single value
276
- const validateAndTransform = (
277
- key: string,
278
- value: unknown,
279
- fieldDef: BaggageFieldDefinition,
280
- ): string | null => {
281
- const fullKey = prefixKey(key);
282
-
283
- // Check key length
284
- if (fullKey.length > maxKeyLength) {
285
- onError?.({
286
- type: 'key_length',
287
- key,
288
- message: `Key "${key}" exceeds max length ${maxKeyLength}`,
289
- });
290
- return null;
291
- }
292
-
293
- // Handle undefined/null with default
294
- if (value === undefined || value === null) {
295
- if (fieldDef.required) {
296
- onError?.({
297
- type: 'validation',
298
- key,
299
- message: `Required field "${key}" is missing`,
300
- });
301
- return null;
302
- }
303
- if (fieldDef.defaultValue === undefined) {
304
- return null;
305
- } else {
306
- value = fieldDef.defaultValue;
307
- }
308
- }
309
-
310
- // Type validation
311
- let stringValue: string;
312
-
313
- switch (fieldDef.type) {
314
- case 'string': {
315
- if (typeof value !== 'string') {
316
- onError?.({
317
- type: 'validation',
318
- key,
319
- message: `Field "${key}" expected string, got ${typeof value}`,
320
- value,
321
- });
322
- return null;
323
- }
324
- stringValue = value;
325
- break;
326
- }
327
-
328
- case 'number': {
329
- if (typeof value !== 'number' || Number.isNaN(value)) {
330
- onError?.({
331
- type: 'validation',
332
- key,
333
- message: `Field "${key}" expected number, got ${typeof value}`,
334
- value,
335
- });
336
- return null;
337
- }
338
- stringValue = String(value);
339
- break;
340
- }
341
-
342
- case 'boolean': {
343
- if (typeof value !== 'boolean') {
344
- onError?.({
345
- type: 'validation',
346
- key,
347
- message: `Field "${key}" expected boolean, got ${typeof value}`,
348
- value,
349
- });
350
- return null;
351
- }
352
- stringValue = String(value);
353
- break;
354
- }
355
-
356
- case 'enum': {
357
- if (!fieldDef.values?.includes(String(value))) {
358
- onError?.({
359
- type: 'validation',
360
- key,
361
- message: `Field "${key}" value "${value}" not in allowed values: ${fieldDef.values?.join(', ')}`,
362
- value,
363
- });
364
- return null;
365
- }
366
- stringValue = String(value);
367
- break;
368
- }
369
-
370
- default: {
371
- stringValue = String(value);
372
- }
373
- }
374
-
375
- // Custom validation
376
- if (fieldDef.validate && !fieldDef.validate(value)) {
377
- onError?.({
378
- type: 'validation',
379
- key,
380
- message: `Field "${key}" failed custom validation`,
381
- value,
382
- });
383
- return null;
384
- }
385
-
386
- // PII check
387
- if (redactPII && containsPII(stringValue)) {
388
- onError?.({
389
- type: 'pii',
390
- key,
391
- message: `Field "${key}" contains PII pattern`,
392
- value: '[REDACTED]',
393
- });
394
- stringValue = hashValue(stringValue);
395
- }
396
-
397
- // Hash if requested or high-cardinality
398
- if (
399
- fieldDef.hash ||
400
- (hashHighCardinality && isHighCardinality(stringValue))
401
- ) {
402
- stringValue = hashValue(stringValue);
403
- }
404
-
405
- // Length validation
406
- const maxLen = fieldDef.maxLength ?? maxValueLength;
407
- if (stringValue.length > maxLen) {
408
- onError?.({
409
- type: 'value_length',
410
- key,
411
- message: `Field "${key}" value exceeds max length ${maxLen}`,
412
- value: stringValue,
413
- });
414
- stringValue = stringValue.slice(0, maxLen);
415
- }
416
-
417
- return stringValue;
418
- };
419
-
420
- // Parse value back from baggage string
421
- const parseValue = (
422
- key: string,
423
- stringValue: string,
424
- fieldDef: BaggageFieldDefinition,
425
- ): unknown => {
426
- switch (fieldDef.type) {
427
- case 'number': {
428
- return Number.parseFloat(stringValue);
429
- }
430
- case 'boolean': {
431
- return stringValue === 'true';
432
- }
433
- default: {
434
- return stringValue;
435
- }
436
- }
437
- };
438
-
439
- return {
440
- get(): Partial<InferBaggageType<T>> {
441
- const baggage = propagation.getBaggage(context.active());
442
- if (!baggage) {
443
- return {};
444
- }
445
-
446
- const result: Record<string, unknown> = {};
447
-
448
- for (const [key, fieldDef] of Object.entries(schema)) {
449
- const fullKey = prefixKey(key);
450
- const entry = baggage.getEntry(fullKey);
451
-
452
- if (entry) {
453
- result[key] = parseValue(key, entry.value, fieldDef);
454
- } else if (fieldDef.defaultValue !== undefined) {
455
- result[key] = fieldDef.defaultValue;
456
- }
457
- }
458
-
459
- return result as Partial<InferBaggageType<T>>;
460
- },
461
-
462
- set(
463
- ctx: TraceContext | undefined,
464
- values: Partial<InferBaggageType<T>>,
465
- ): void {
466
- let baggage =
467
- propagation.getBaggage(context.active()) ?? propagation.createBaggage();
468
- let totalSize = 0;
469
-
470
- // Calculate existing size
471
- for (const [key, entry] of baggage.getAllEntries()) {
472
- totalSize += key.length + entry.value.length;
473
- }
474
-
475
- for (const [key, value] of Object.entries(values)) {
476
- const fieldDef = schema[key];
477
- if (!fieldDef) continue;
478
-
479
- const fullKey = prefixKey(key);
480
- const stringValue = validateAndTransform(key, value, fieldDef);
481
-
482
- if (stringValue !== null) {
483
- // Check total size
484
- const entrySize = fullKey.length + stringValue.length;
485
- if (totalSize + entrySize > maxTotalSize) {
486
- onError?.({
487
- type: 'size',
488
- key,
489
- message: `Adding "${key}" would exceed max baggage size ${maxTotalSize}`,
490
- value,
491
- });
492
- continue;
493
- }
494
-
495
- baggage = baggage.setEntry(fullKey, { value: stringValue });
496
- totalSize += entrySize;
497
- }
498
- }
499
-
500
- // Update context with new baggage
501
- const newContext = propagation.setBaggage(context.active(), baggage);
502
- // Note: This only works if the caller propagates the context
503
- // In OTel, baggage propagation happens via context.with()
504
- // For now we set on active context
505
- propagation.setBaggage(newContext, baggage);
506
- },
507
-
508
- getValue<K extends keyof T>(key: K): InferBaggageType<T>[K] | undefined {
509
- const baggage = propagation.getBaggage(context.active());
510
- if (!baggage) return undefined;
511
-
512
- const fullKey = prefixKey(String(key));
513
- const entry = baggage.getEntry(fullKey);
514
- const fieldDef = schema[String(key)];
515
-
516
- if (!entry) {
517
- return fieldDef?.defaultValue as InferBaggageType<T>[K] | undefined;
518
- }
519
-
520
- if (!fieldDef) {
521
- return undefined;
522
- }
523
-
524
- return parseValue(
525
- String(key),
526
- entry.value,
527
- fieldDef,
528
- ) as InferBaggageType<T>[K];
529
- },
530
-
531
- setValue<K extends keyof T>(
532
- key: K,
533
- value: InferBaggageType<T>[K],
534
- ctx?: TraceContext,
535
- ): void {
536
- this.set(ctx, { [key]: value } as Partial<InferBaggageType<T>>);
537
- },
538
-
539
- clear(): void {
540
- let baggage = propagation.getBaggage(context.active());
541
- if (!baggage) return;
542
-
543
- for (const key of Object.keys(schema)) {
544
- const fullKey = prefixKey(key);
545
- baggage = baggage.removeEntry(fullKey);
546
- }
547
-
548
- propagation.setBaggage(context.active(), baggage);
549
- },
550
-
551
- toHeaders(): Record<string, string> {
552
- const headers: Record<string, string> = {};
553
- propagation.inject(context.active(), headers);
554
- return headers;
555
- },
556
-
557
- fromHeaders(headers: Record<string, string>, ctx?: TraceContext): void {
558
- const extractedContext = propagation.extract(context.active(), headers);
559
- const baggage = propagation.getBaggage(extractedContext);
560
-
561
- if (baggage) {
562
- const values: Record<string, unknown> = {};
563
-
564
- for (const [key, fieldDef] of Object.entries(schema)) {
565
- const fullKey = prefixKey(key);
566
- const entry = baggage.getEntry(fullKey);
567
-
568
- if (entry) {
569
- values[key] = parseValue(key, entry.value, fieldDef);
570
- }
571
- }
572
-
573
- this.set(ctx, values as Partial<InferBaggageType<T>>);
574
- }
575
- },
576
- };
577
- }
578
-
579
- // ============================================================================
580
- // Pre-built Business Context Schema
581
- // ============================================================================
582
-
583
- /**
584
- * Pre-built baggage schema for common business context fields
585
- *
586
- * Fields:
587
- * - `tenantId`: Multi-tenant identifier (string, max 64 chars)
588
- * - `userId`: User identifier (hashed for privacy)
589
- * - `correlationId`: Request correlation ID (string)
590
- * - `workflowId`: Workflow/saga instance ID (string)
591
- * - `priority`: Request priority (low, normal, high, critical)
592
- * - `region`: Geographic region (string)
593
- * - `channel`: Request channel (web, mobile, api, internal)
594
- *
595
- * @example
596
- * ```typescript
597
- * import { BusinessBaggage } from 'autotel/business-baggage';
598
- *
599
- * // Set business context at entry point
600
- * BusinessBaggage.set(ctx, {
601
- * tenantId: 'acme-corp',
602
- * userId: 'user-123',
603
- * priority: 'high',
604
- * channel: 'api',
605
- * });
606
- *
607
- * // Access anywhere in the trace
608
- * const { tenantId, priority } = BusinessBaggage.get(ctx);
609
- * ```
610
- */
611
- export const BusinessBaggage = createSafeBaggageSchema(
612
- {
613
- tenantId: {
614
- type: 'string',
615
- maxLength: 64,
616
- },
617
- userId: {
618
- type: 'string',
619
- hash: true, // Auto-hash for privacy
620
- maxLength: 64,
621
- },
622
- correlationId: {
623
- type: 'string',
624
- maxLength: 128,
625
- },
626
- workflowId: {
627
- type: 'string',
628
- maxLength: 128,
629
- },
630
- priority: {
631
- type: 'enum',
632
- values: ['low', 'normal', 'high', 'critical'] as const,
633
- defaultValue: 'normal',
634
- },
635
- region: {
636
- type: 'string',
637
- maxLength: 32,
638
- },
639
- channel: {
640
- type: 'enum',
641
- values: [
642
- 'web',
643
- 'mobile',
644
- 'api',
645
- 'internal',
646
- 'webhook',
647
- 'scheduled',
648
- ] as const,
649
- },
650
- },
651
- {
652
- prefix: 'biz',
653
- redactPII: true,
654
- hashHighCardinality: true,
655
- },
656
- );
657
-
658
- /**
659
- * Type alias for BusinessBaggage values
660
- */
661
- export type BusinessBaggageValues = {
662
- tenantId?: string;
663
- userId?: string;
664
- correlationId?: string;
665
- workflowId?: string;
666
- priority?: 'low' | 'normal' | 'high' | 'critical';
667
- region?: string;
668
- channel?: 'web' | 'mobile' | 'api' | 'internal' | 'webhook' | 'scheduled';
669
- };