autotel 4.1.0 → 4.2.1
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/dist/auto.cjs +5 -3
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +3 -3
- package/dist/auto.js.map +1 -1
- package/dist/chunk-C_NdSu1c.cjs +34 -0
- package/dist/correlation-id.cjs +1 -1
- package/dist/correlation-id.d.cts.map +1 -1
- package/dist/correlation-id.d.ts.map +1 -1
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +1 -1
- package/dist/decorators.js +1 -1
- package/dist/{event-ByBTV9M2.js → event-531asIM6.js} +4 -4
- package/dist/{event-ByBTV9M2.js.map → event-531asIM6.js.map} +1 -1
- package/dist/{event-BhHREDJk.cjs → event-CcZYwp50.cjs} +4 -4
- package/dist/{event-BhHREDJk.cjs.map → event-CcZYwp50.cjs.map} +1 -1
- package/dist/event.cjs +1 -1
- package/dist/event.js +1 -1
- package/dist/{functional-zpzNLhky.cjs → functional-C8B0Qa7o.cjs} +10 -7
- package/dist/functional-C8B0Qa7o.cjs.map +1 -0
- package/dist/{functional-DtI0u4vx.js → functional-r-AUIRy_.js} +9 -9
- package/dist/functional-r-AUIRy_.js.map +1 -0
- package/dist/functional.cjs +1 -1
- package/dist/functional.js +1 -1
- package/dist/http.cjs +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +15 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -14
- package/dist/index.js.map +1 -1
- package/dist/{init-D-jnNMix.js → init-BS2JVkrL.js} +2 -2
- package/dist/{init-D-jnNMix.js.map → init-BS2JVkrL.js.map} +1 -1
- package/dist/{init-BX7AmFRl.cjs → init-BXiuPK6j.cjs} +3 -3
- package/dist/{init-BX7AmFRl.cjs.map → init-BXiuPK6j.cjs.map} +1 -1
- package/dist/instrumentation.cjs +2 -2
- package/dist/instrumentation.js +2 -2
- package/dist/logger.cjs +236 -8
- package/dist/logger.cjs.map +1 -0
- package/dist/messaging.cjs +1 -1
- package/dist/messaging.js +1 -1
- package/dist/{node-require-DF5QBX6z.cjs → node-require-CZ_PU448.cjs} +6 -4
- package/dist/node-require-CZ_PU448.cjs.map +1 -0
- package/dist/{node-require-Db1oDpLj.js → node-require-vROmTeJ8.js} +5 -5
- package/dist/node-require-vROmTeJ8.js.map +1 -0
- package/dist/{operation-context-C-2hmmtP.js → operation-context-CKBoA4Qy.js} +3 -3
- package/dist/operation-context-CKBoA4Qy.js.map +1 -0
- package/dist/{operation-context-n4_obUwq.cjs → operation-context-D6LDf4W_.cjs} +3 -1
- package/dist/operation-context-D6LDf4W_.cjs.map +1 -0
- package/dist/register.cjs +3 -1
- package/dist/register.cjs.map +1 -1
- package/dist/register.js +2 -2
- package/dist/register.js.map +1 -1
- package/dist/semantic-helpers.cjs +1 -1
- package/dist/semantic-helpers.js +1 -1
- package/dist/{stable-hash-Cg5cT34Q.js → stable-hash-ChFBIhNt.js} +3 -3
- package/dist/stable-hash-ChFBIhNt.js.map +1 -0
- package/dist/{stable-hash-BNTMrmdB.cjs → stable-hash-brKISGf1.cjs} +4 -2
- package/dist/stable-hash-brKISGf1.cjs.map +1 -0
- package/dist/trace-context-Cijqoi6e.d.cts.map +1 -1
- package/dist/trace-context-Cijqoi6e.d.ts.map +1 -1
- package/dist/trace-helpers.cjs +1 -1
- package/dist/trace-helpers.js +1 -1
- package/dist/{track-wc0HafS_.js → track-COUuU48p.js} +5 -5
- package/dist/track-COUuU48p.js.map +1 -0
- package/dist/{track-D59FfpL0.cjs → track-Cb3Q4QmS.cjs} +4 -2
- package/dist/track-Cb3Q4QmS.cjs.map +1 -0
- package/dist/validate.cjs +1 -1
- package/dist/validate.js +1 -1
- package/dist/webhook.cjs +1 -1
- package/dist/webhook.js +1 -1
- package/dist/workflow-distributed.cjs +1 -1
- package/dist/workflow-distributed.js +1 -1
- package/dist/workflow.cjs +3 -1
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +3 -3
- package/dist/workflow.js.map +1 -1
- package/dist/yaml-config.cjs +233 -4
- package/dist/yaml-config.cjs.map +1 -0
- package/dist/yaml-config.d.cts.map +1 -1
- package/dist/yaml-config.d.ts.map +1 -1
- package/dist/yaml-config.js +8 -7
- package/dist/yaml-config.js.map +1 -1
- package/package.json +1 -2
- package/dist/functional-DtI0u4vx.js.map +0 -1
- package/dist/functional-zpzNLhky.cjs.map +0 -1
- package/dist/logger-thMPLpOG.cjs +0 -487
- package/dist/logger-thMPLpOG.cjs.map +0 -1
- package/dist/node-require-DF5QBX6z.cjs.map +0 -1
- package/dist/node-require-Db1oDpLj.js.map +0 -1
- package/dist/operation-context-C-2hmmtP.js.map +0 -1
- package/dist/operation-context-n4_obUwq.cjs.map +0 -1
- package/dist/stable-hash-BNTMrmdB.cjs.map +0 -1
- package/dist/stable-hash-Cg5cT34Q.js.map +0 -1
- package/dist/track-D59FfpL0.cjs.map +0 -1
- package/dist/track-wc0HafS_.js.map +0 -1
- package/dist/yaml-config-Ck2uB0Dp.cjs +0 -273
- package/dist/yaml-config-Ck2uB0Dp.cjs.map +0 -1
- package/src/attribute-redacting-processor.test.ts +0 -763
- package/src/attribute-redacting-processor.ts +0 -621
- package/src/attributes/attachers.ts +0 -161
- package/src/attributes/builders.ts +0 -529
- package/src/attributes/domains.ts +0 -42
- package/src/attributes/index.ts +0 -81
- package/src/attributes/registry.ts +0 -323
- package/src/attributes/types.ts +0 -211
- package/src/attributes/utils.ts +0 -64
- package/src/attributes/validators.ts +0 -266
- package/src/attributes.test.ts +0 -292
- package/src/auto.ts +0 -67
- package/src/autotel-logger.test.ts +0 -548
- package/src/autotel-logger.ts +0 -364
- package/src/baggage-span-processor.test.ts +0 -202
- package/src/baggage-span-processor.ts +0 -100
- package/src/business-baggage.test.ts +0 -500
- package/src/business-baggage.ts +0 -669
- package/src/circuit-breaker.test.ts +0 -341
- package/src/circuit-breaker.ts +0 -184
- package/src/config.test.ts +0 -94
- package/src/config.ts +0 -172
- package/src/correlated-events.test.ts +0 -151
- package/src/correlated-events.ts +0 -47
- package/src/correlation-id.test.ts +0 -163
- package/src/correlation-id.ts +0 -206
- package/src/db.test.ts +0 -252
- package/src/db.ts +0 -447
- package/src/decorators.test.ts +0 -153
- package/src/decorators.ts +0 -188
- package/src/define-event.test.ts +0 -41
- package/src/define-event.ts +0 -58
- package/src/devtools.ts +0 -60
- package/src/drain-pipeline.test.ts +0 -68
- package/src/drain-pipeline.ts +0 -199
- package/src/drain-toolkit.test.ts +0 -113
- package/src/drain-toolkit.ts +0 -129
- package/src/enricher-toolkit.test.ts +0 -67
- package/src/enricher-toolkit.ts +0 -79
- package/src/enrichers.test.ts +0 -150
- package/src/enrichers.ts +0 -145
- package/src/env-config.test.ts +0 -323
- package/src/env-config.ts +0 -309
- package/src/error-catalog.test.ts +0 -133
- package/src/error-catalog.ts +0 -262
- package/src/event-queue.test.ts +0 -864
- package/src/event-queue.ts +0 -699
- package/src/event-subscriber.ts +0 -262
- package/src/event-testing.ts +0 -197
- package/src/event.test.ts +0 -1104
- package/src/event.ts +0 -988
- package/src/events-config.ts +0 -235
- package/src/exporters.ts +0 -165
- package/src/filtering-span-processor.test.ts +0 -281
- package/src/filtering-span-processor.ts +0 -111
- package/src/flatten-attributes.test.ts +0 -76
- package/src/flatten-attributes.ts +0 -80
- package/src/functional.strict-types.typecheck.ts +0 -53
- package/src/functional.test.ts +0 -1464
- package/src/functional.ts +0 -2539
- package/src/functional.types.test.ts +0 -135
- package/src/hook.mjs +0 -15
- package/src/http.test.ts +0 -485
- package/src/http.ts +0 -424
- package/src/index.ts +0 -433
- package/src/init-auto-redactor.test.ts +0 -53
- package/src/init-redactor.test.ts +0 -8
- package/src/init.customization.test.ts +0 -665
- package/src/init.integrations.test.ts +0 -399
- package/src/init.openllmetry.test.ts +0 -194
- package/src/init.protocol.test.ts +0 -215
- package/src/init.ts +0 -2439
- package/src/instrumentation.test.ts +0 -108
- package/src/instrumentation.ts +0 -319
- package/src/logger.test.ts +0 -125
- package/src/logger.ts +0 -341
- package/src/messaging-adapters.test.ts +0 -595
- package/src/messaging-adapters.ts +0 -583
- package/src/messaging-testing.test.ts +0 -573
- package/src/messaging-testing.ts +0 -935
- package/src/messaging.test.ts +0 -1646
- package/src/messaging.ts +0 -2245
- package/src/metric-helpers.ts +0 -47
- package/src/metric-testing.ts +0 -197
- package/src/metric.ts +0 -446
- package/src/metrics.test.ts +0 -241
- package/src/node-require.ts +0 -123
- package/src/operation-context.ts +0 -93
- package/src/parse-error.test.ts +0 -73
- package/src/parse-error.ts +0 -112
- package/src/posthog-logs.test.ts +0 -115
- package/src/posthog-logs.ts +0 -77
- package/src/pretty-console-exporter.test.ts +0 -545
- package/src/pretty-console-exporter.ts +0 -413
- package/src/pretty-log-formatter.test.ts +0 -123
- package/src/pretty-log-formatter.ts +0 -210
- package/src/processors/canonical-log-line-processor.test.ts +0 -523
- package/src/processors/canonical-log-line-processor.ts +0 -396
- package/src/processors.ts +0 -152
- package/src/rate-limiter.test.ts +0 -199
- package/src/rate-limiter.ts +0 -98
- package/src/redact-values.test.ts +0 -90
- package/src/redact-values.ts +0 -34
- package/src/register.ts +0 -37
- package/src/request-logger.test.ts +0 -545
- package/src/request-logger.ts +0 -342
- package/src/sampling.test.ts +0 -1060
- package/src/sampling.ts +0 -737
- package/src/security-schema.test.ts +0 -45
- package/src/security-schema.ts +0 -107
- package/src/semantic-conventions.ts +0 -15
- package/src/semantic-helpers.test.ts +0 -226
- package/src/semantic-helpers.ts +0 -438
- package/src/shutdown.test.ts +0 -364
- package/src/shutdown.ts +0 -246
- package/src/span-name-normalizer.test.ts +0 -377
- package/src/span-name-normalizer.ts +0 -213
- package/src/stable-hash.ts +0 -27
- package/src/structured-error.test.ts +0 -191
- package/src/structured-error.ts +0 -157
- package/src/stub.integration.test.ts +0 -361
- package/src/tail-sampling-processor.test.ts +0 -230
- package/src/tail-sampling-processor.ts +0 -55
- package/src/test-span-collector.test.ts +0 -234
- package/src/test-span-collector.ts +0 -150
- package/src/testing.ts +0 -705
- package/src/trace-context.test.ts +0 -73
- package/src/trace-context.ts +0 -567
- package/src/trace-helpers.new.test.ts +0 -278
- package/src/trace-helpers.test.ts +0 -290
- package/src/trace-helpers.ts +0 -710
- package/src/trace-hybrid.test.ts +0 -42
- package/src/trace-hybrid.ts +0 -37
- package/src/tracer-provider.test.ts +0 -183
- package/src/tracer-provider.ts +0 -266
- package/src/track.test.ts +0 -154
- package/src/track.ts +0 -216
- package/src/validate.test.ts +0 -287
- package/src/validate.ts +0 -307
- package/src/validation-attributes.ts +0 -43
- package/src/validation.test.ts +0 -330
- package/src/validation.ts +0 -246
- package/src/variable-name-inference.test.ts +0 -178
- package/src/variable-name-inference.ts +0 -242
- package/src/webhook.test.ts +0 -649
- package/src/webhook.ts +0 -637
- package/src/workflow-distributed.test.ts +0 -786
- package/src/workflow-distributed.ts +0 -916
- package/src/workflow.async-safety.integration.test.ts +0 -345
- package/src/workflow.test.ts +0 -647
- package/src/workflow.ts +0 -810
- package/src/yaml-config.test.ts +0 -373
- package/src/yaml-config.ts +0 -351
package/src/business-baggage.ts
DELETED
|
@@ -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
|
-
};
|