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
package/src/webhook.ts DELETED
@@ -1,637 +0,0 @@
1
- /**
2
- * Webhook and callback tracing with the "Parking Lot" pattern
3
- *
4
- * When initiating async operations that return hours/days later (webhooks,
5
- * payment callbacks, human approvals), you can't keep a span open. This module
6
- * provides utilities to "park" trace context and retrieve it when callbacks arrive.
7
- *
8
- * @example Stripe payment webhook
9
- * ```typescript
10
- * import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';
11
- *
12
- * const parkingLot = createParkingLot({
13
- * store: new InMemoryTraceContextStore(),
14
- * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours
15
- * });
16
- *
17
- * // When initiating payment
18
- * export const initiatePayment = trace(ctx => async (orderId: string) => {
19
- * await parkingLot.park(`payment:${orderId}`, { orderId });
20
- * await stripeClient.createPaymentIntent({ metadata: { orderId } });
21
- * });
22
- *
23
- * // When Stripe webhook arrives (hours later)
24
- * export const handleStripeWebhook = parkingLot.traceCallback({
25
- * name: 'stripe.webhook.payment_intent.succeeded',
26
- * correlationKeyFrom: (event) => `payment:${event.data.object.metadata.orderId}`,
27
- * })(ctx => async (event: Stripe.Event) => {
28
- * // ctx.parkedContext contains the original trace context
29
- * // ctx.elapsedMs shows time since payment was initiated
30
- * await fulfillOrder(event.data.object);
31
- * });
32
- * ```
33
- *
34
- * @module
35
- */
36
-
37
- import { SpanKind, trace as otelTrace } from '@opentelemetry/api';
38
- import type { SpanContext, Link } from '@opentelemetry/api';
39
- import { emitCorrelatedEvent } from './correlated-events';
40
- import { trace } from './functional';
41
- import type { AttributeValue, TraceContext } from './trace-context';
42
- import { recordStructuredError } from './structured-error';
43
-
44
- // ============================================================================
45
- // Types
46
- // ============================================================================
47
-
48
- /**
49
- * Stored trace context for parking lot pattern
50
- */
51
- export interface StoredTraceContext {
52
- /** Trace ID from the original span */
53
- traceId: string;
54
-
55
- /** Span ID from the original span */
56
- spanId: string;
57
-
58
- /** Trace flags (sampling decision) */
59
- traceFlags: number;
60
-
61
- /** When the context was parked */
62
- parkedAt: number;
63
-
64
- /** Optional TTL in milliseconds */
65
- ttlMs?: number;
66
-
67
- /** User-provided metadata */
68
- metadata?: Record<string, string>;
69
- }
70
-
71
- /**
72
- * Interface for trace context storage backends
73
- *
74
- * Implement this interface to use different storage backends (Redis, DynamoDB, etc.)
75
- */
76
- export interface TraceContextStore {
77
- /**
78
- * Save trace context with a correlation key
79
- *
80
- * @param key - Unique correlation key (e.g., "payment:order-123")
81
- * @param context - The trace context to store
82
- */
83
- save(key: string, context: StoredTraceContext): Promise<void>;
84
-
85
- /**
86
- * Load trace context by correlation key
87
- *
88
- * @param key - The correlation key used when parking
89
- * @returns The stored context, or null if not found/expired
90
- */
91
- load(key: string): Promise<StoredTraceContext | null>;
92
-
93
- /**
94
- * Delete trace context by correlation key
95
- *
96
- * @param key - The correlation key to delete
97
- */
98
- delete(key: string): Promise<void>;
99
- }
100
-
101
- /**
102
- * Configuration for creating a parking lot
103
- */
104
- export interface ParkingLotConfig {
105
- /** Storage backend for parked contexts */
106
- store: TraceContextStore;
107
-
108
- /** Default TTL in milliseconds (default: 24 hours) */
109
- defaultTTLMs?: number;
110
-
111
- /** Prefix for all correlation keys (default: "parkingLot:") */
112
- keyPrefix?: string;
113
-
114
- /** Whether to auto-delete after retrieval (default: true) */
115
- autoDeleteOnRetrieve?: boolean;
116
-
117
- /** Callback when context expires or is not found */
118
- onMiss?: (correlationKey: string) => void;
119
- }
120
-
121
- /**
122
- * Configuration for traceCallback wrapper
123
- */
124
- export interface CallbackConfig {
125
- /** Span name for the callback handler */
126
- name: string;
127
-
128
- /**
129
- * Extract correlation key from callback arguments
130
- *
131
- * @example
132
- * ```typescript
133
- * correlationKeyFrom: (event) => `payment:${event.data.orderId}`
134
- * ```
135
- */
136
- correlationKeyFrom: (args: unknown[]) => string;
137
-
138
- /** Additional span attributes */
139
- attributes?: Record<string, string | number | boolean>;
140
-
141
- /** Whether to fail if parked context is not found (default: false) */
142
- requireParkedContext?: boolean;
143
- }
144
-
145
- /**
146
- * Extended context for callback handlers
147
- */
148
- export interface CallbackContext extends TraceContext {
149
- /** The retrieved parked context, if found */
150
- parkedContext: StoredTraceContext | null;
151
-
152
- /** Time elapsed since context was parked (ms), or null if not found */
153
- elapsedMs: number | null;
154
-
155
- /** The correlation key used for retrieval */
156
- correlationKey: string;
157
- }
158
-
159
- /**
160
- * The parking lot instance
161
- */
162
- export interface ParkingLot {
163
- /**
164
- * Park current trace context before initiating async operation
165
- *
166
- * Call this before sending a webhook, initiating a payment, or starting
167
- * any operation that will complete via callback.
168
- *
169
- * @param correlationKey - Unique key to retrieve context later (e.g., "payment:order-123")
170
- * @param metadata - Optional metadata to store with the context
171
- * @returns The correlation key (with prefix applied)
172
- *
173
- * @example
174
- * ```typescript
175
- * await parkingLot.park(`payment:${orderId}`, {
176
- * customerId: customer.id,
177
- * amount: payment.amount.toString(),
178
- * });
179
- * ```
180
- */
181
- park(
182
- correlationKey: string,
183
- metadata?: Record<string, string>,
184
- ): Promise<string>;
185
-
186
- /**
187
- * Retrieve parked context when callback arrives
188
- *
189
- * @param correlationKey - The key used when parking
190
- * @returns The stored context, or null if not found/expired
191
- */
192
- retrieve(correlationKey: string): Promise<StoredTraceContext | null>;
193
-
194
- /**
195
- * Wrap a callback handler with automatic context retrieval and linking
196
- *
197
- * Creates a traced function that:
198
- * 1. Extracts correlation key from arguments
199
- * 2. Retrieves parked context from storage
200
- * 3. Creates a span link to the original trace
201
- * 4. Provides elapsed time since parking
202
- *
203
- * @param config - Callback configuration
204
- * @returns Factory function for the callback handler
205
- *
206
- * @example
207
- * ```typescript
208
- * export const handleWebhook = parkingLot.traceCallback({
209
- * name: 'webhook.payment.completed',
210
- * correlationKeyFrom: (args) => `payment:${args[0].orderId}`,
211
- * })(ctx => async (event) => {
212
- * console.log(`Payment completed after ${ctx.elapsedMs}ms`);
213
- * await processPayment(event);
214
- * });
215
- * ```
216
- */
217
- traceCallback<TArgs extends unknown[], TReturn>(
218
- config: CallbackConfig,
219
- ): (
220
- fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,
221
- ) => (...args: TArgs) => Promise<TReturn>;
222
-
223
- /**
224
- * Manually create a span link from stored context
225
- *
226
- * Useful when you need more control over span creation.
227
- *
228
- * @param storedContext - The stored trace context
229
- * @returns A span link that can be added to a span
230
- */
231
- createLink(storedContext: StoredTraceContext): Link;
232
-
233
- /**
234
- * Check if a parked context exists (without retrieving/deleting it)
235
- *
236
- * @param correlationKey - The key to check
237
- * @returns True if context exists and hasn't expired
238
- */
239
- exists(correlationKey: string): Promise<boolean>;
240
- }
241
-
242
- // ============================================================================
243
- // In-Memory Store (for testing and development)
244
- // ============================================================================
245
-
246
- /**
247
- * In-memory trace context store
248
- *
249
- * Useful for testing and development. For production, use a persistent
250
- * store like Redis or DynamoDB.
251
- *
252
- * @example
253
- * ```typescript
254
- * const store = new InMemoryTraceContextStore();
255
- * const parkingLot = createParkingLot({ store });
256
- * ```
257
- */
258
- export class InMemoryTraceContextStore implements TraceContextStore {
259
- private store = new Map<string, StoredTraceContext>();
260
- private cleanupInterval: ReturnType<typeof setInterval> | null = null;
261
-
262
- constructor(
263
- private options: {
264
- /** Cleanup interval in ms (default: 60000) */
265
- cleanupIntervalMs?: number;
266
- } = {},
267
- ) {
268
- // Start periodic cleanup of expired entries
269
- const cleanupMs = options.cleanupIntervalMs ?? 60_000;
270
- if (cleanupMs > 0) {
271
- this.cleanupInterval = setInterval(() => this.cleanup(), cleanupMs);
272
- // Don't prevent process exit
273
- if (this.cleanupInterval.unref) {
274
- this.cleanupInterval.unref();
275
- }
276
- }
277
- }
278
-
279
- async save(key: string, context: StoredTraceContext): Promise<void> {
280
- this.store.set(key, context);
281
- }
282
-
283
- async load(key: string): Promise<StoredTraceContext | null> {
284
- const context = this.store.get(key);
285
- if (!context) {
286
- return null;
287
- }
288
-
289
- // Check TTL expiration
290
- if (context.ttlMs) {
291
- const age = Date.now() - context.parkedAt;
292
- if (age > context.ttlMs) {
293
- this.store.delete(key);
294
- return null;
295
- }
296
- }
297
-
298
- return context;
299
- }
300
-
301
- async delete(key: string): Promise<void> {
302
- this.store.delete(key);
303
- }
304
-
305
- /**
306
- * Get number of stored contexts (for testing)
307
- */
308
- get size(): number {
309
- return this.store.size;
310
- }
311
-
312
- /**
313
- * Clear all stored contexts (for testing)
314
- */
315
- clear(): void {
316
- this.store.clear();
317
- }
318
-
319
- /**
320
- * Stop the cleanup interval
321
- */
322
- destroy(): void {
323
- if (this.cleanupInterval) {
324
- clearInterval(this.cleanupInterval);
325
- this.cleanupInterval = null;
326
- }
327
- }
328
-
329
- private cleanup(): void {
330
- const now = Date.now();
331
- for (const [key, context] of this.store.entries()) {
332
- if (context.ttlMs) {
333
- const age = now - context.parkedAt;
334
- if (age > context.ttlMs) {
335
- this.store.delete(key);
336
- }
337
- }
338
- }
339
- }
340
- }
341
-
342
- // ============================================================================
343
- // Parking Lot Factory
344
- // ============================================================================
345
-
346
- /**
347
- * Create a parking lot for trace context storage and retrieval
348
- *
349
- * @param config - Parking lot configuration
350
- * @returns A parking lot instance
351
- *
352
- * @example Basic usage
353
- * ```typescript
354
- * const parkingLot = createParkingLot({
355
- * store: new InMemoryTraceContextStore(),
356
- * defaultTTLMs: 24 * 60 * 60 * 1000, // 24 hours
357
- * });
358
- * ```
359
- *
360
- * @example With Redis store
361
- * ```typescript
362
- * class RedisTraceContextStore implements TraceContextStore {
363
- * constructor(private redis: Redis) {}
364
- *
365
- * async save(key: string, context: StoredTraceContext) {
366
- * const ttlSeconds = context.ttlMs ? Math.ceil(context.ttlMs / 1000) : 86400;
367
- * await this.redis.setex(key, ttlSeconds, JSON.stringify(context));
368
- * }
369
- *
370
- * async load(key: string) {
371
- * const data = await this.redis.get(key);
372
- * return data ? JSON.parse(data) : null;
373
- * }
374
- *
375
- * async delete(key: string) {
376
- * await this.redis.del(key);
377
- * }
378
- * }
379
- *
380
- * const parkingLot = createParkingLot({
381
- * store: new RedisTraceContextStore(redis),
382
- * });
383
- * ```
384
- */
385
- export function createParkingLot(config: ParkingLotConfig): ParkingLot {
386
- const {
387
- store,
388
- defaultTTLMs = 24 * 60 * 60 * 1000, // 24 hours
389
- keyPrefix = 'parkingLot:',
390
- autoDeleteOnRetrieve = true,
391
- onMiss,
392
- } = config;
393
-
394
- /**
395
- * Get current span context from active context
396
- */
397
- function getCurrentSpanContext(): SpanContext | null {
398
- const activeSpan = otelTrace.getActiveSpan();
399
- if (!activeSpan) {
400
- return null;
401
- }
402
- return activeSpan.spanContext();
403
- }
404
-
405
- /**
406
- * Apply key prefix
407
- */
408
- function prefixKey(key: string): string {
409
- return `${keyPrefix}${key}`;
410
- }
411
-
412
- const parkingLot: ParkingLot = {
413
- async park(
414
- correlationKey: string,
415
- metadata?: Record<string, string>,
416
- ): Promise<string> {
417
- const spanContext = getCurrentSpanContext();
418
- const fullKey = prefixKey(correlationKey);
419
-
420
- const storedContext: StoredTraceContext = {
421
- traceId: spanContext?.traceId ?? '',
422
- spanId: spanContext?.spanId ?? '',
423
- traceFlags: spanContext?.traceFlags ?? 0,
424
- parkedAt: Date.now(),
425
- ttlMs: defaultTTLMs,
426
- metadata,
427
- };
428
-
429
- await store.save(fullKey, storedContext);
430
-
431
- const activeSpan = otelTrace.getActiveSpan();
432
- if (activeSpan) {
433
- const parkAttrs: Record<string, AttributeValue> = {
434
- 'parking_lot.correlation_key': correlationKey,
435
- 'parking_lot.ttl_ms': defaultTTLMs,
436
- ...(metadata &&
437
- Object.fromEntries(
438
- Object.entries(metadata).map(([k, v]) => [
439
- `parking_lot.metadata.${k}`,
440
- v,
441
- ]),
442
- )),
443
- };
444
- emitCorrelatedEvent(
445
- {
446
- setAttribute: (k, v) => activeSpan.setAttribute(k, v),
447
- setAttributes: (a) => activeSpan.setAttributes(a),
448
- addEvent: (n, a) => activeSpan.addEvent(n, a),
449
- },
450
- 'trace_context_parked',
451
- parkAttrs,
452
- );
453
- }
454
-
455
- // Return the unprefixed key so callers can use the same key for retrieve()
456
- return correlationKey;
457
- },
458
-
459
- async retrieve(correlationKey: string): Promise<StoredTraceContext | null> {
460
- const fullKey = prefixKey(correlationKey);
461
- const storedContext = await store.load(fullKey);
462
-
463
- if (!storedContext) {
464
- onMiss?.(correlationKey);
465
- return null;
466
- }
467
-
468
- if (autoDeleteOnRetrieve) {
469
- await store.delete(fullKey);
470
- }
471
-
472
- return storedContext;
473
- },
474
-
475
- traceCallback<TArgs extends unknown[], TReturn>(
476
- callbackConfig: CallbackConfig,
477
- ): (
478
- fnFactory: (ctx: CallbackContext) => (...args: TArgs) => Promise<TReturn>,
479
- ) => (...args: TArgs) => Promise<TReturn> {
480
- return (
481
- fnFactory: (
482
- ctx: CallbackContext,
483
- ) => (...args: TArgs) => Promise<TReturn>,
484
- ): ((...args: TArgs) => Promise<TReturn>) => {
485
- return trace<TArgs, TReturn>(
486
- {
487
- name: callbackConfig.name,
488
- spanKind: SpanKind.SERVER,
489
- },
490
- (baseCtx) => {
491
- return async (...args: TArgs) => {
492
- // Extract correlation key from arguments
493
- const correlationKey = callbackConfig.correlationKeyFrom(args);
494
-
495
- // Retrieve parked context
496
- const parkedContext = await parkingLot.retrieve(correlationKey);
497
-
498
- // Calculate elapsed time
499
- const elapsedMs = parkedContext
500
- ? Date.now() - parkedContext.parkedAt
501
- : null;
502
-
503
- // Set span attributes
504
- baseCtx.setAttribute(
505
- 'parking_lot.correlation_key',
506
- correlationKey,
507
- );
508
-
509
- if (parkedContext) {
510
- baseCtx.setAttribute('parking_lot.elapsed_ms', elapsedMs!);
511
- baseCtx.setAttribute(
512
- 'parking_lot.original_trace_id',
513
- parkedContext.traceId,
514
- );
515
- baseCtx.setAttribute(
516
- 'parking_lot.original_span_id',
517
- parkedContext.spanId,
518
- );
519
-
520
- // Add metadata as attributes
521
- if (parkedContext.metadata) {
522
- for (const [key, value] of Object.entries(
523
- parkedContext.metadata,
524
- )) {
525
- baseCtx.setAttribute(`parking_lot.metadata.${key}`, value);
526
- }
527
- }
528
-
529
- // Create span link to original trace
530
- const link = parkingLot.createLink(parkedContext);
531
- baseCtx.addLinks([link]);
532
-
533
- emitCorrelatedEvent(baseCtx, 'parked_context_retrieved', {
534
- 'parking_lot.correlation_key': correlationKey,
535
- 'parking_lot.elapsed_ms': elapsedMs!,
536
- 'parking_lot.original_trace_id': parkedContext.traceId,
537
- });
538
- } else {
539
- baseCtx.setAttribute('parking_lot.context_found', false);
540
-
541
- if (callbackConfig.requireParkedContext) {
542
- const error = new Error(
543
- `Required parked context not found for key: ${correlationKey}`,
544
- );
545
- recordStructuredError(baseCtx, error);
546
- throw error;
547
- }
548
- }
549
-
550
- // Apply custom attributes
551
- if (callbackConfig.attributes) {
552
- for (const [key, value] of Object.entries(
553
- callbackConfig.attributes,
554
- )) {
555
- baseCtx.setAttribute(key, value);
556
- }
557
- }
558
-
559
- // Create extended context
560
- const callbackCtx: CallbackContext = {
561
- ...baseCtx,
562
- parkedContext,
563
- elapsedMs,
564
- correlationKey,
565
- };
566
-
567
- // Execute user's function
568
- const userFn = fnFactory(callbackCtx);
569
- return userFn(...args);
570
- };
571
- },
572
- );
573
- };
574
- },
575
-
576
- createLink(storedContext: StoredTraceContext): Link {
577
- return {
578
- context: {
579
- traceId: storedContext.traceId,
580
- spanId: storedContext.spanId,
581
- traceFlags: storedContext.traceFlags,
582
- isRemote: true,
583
- },
584
- attributes: {
585
- 'link.type': 'parking_lot',
586
- 'parking_lot.parked_at': storedContext.parkedAt,
587
- ...(storedContext.metadata && {
588
- 'parking_lot.has_metadata': true,
589
- }),
590
- },
591
- };
592
- },
593
-
594
- async exists(correlationKey: string): Promise<boolean> {
595
- const fullKey = prefixKey(correlationKey);
596
- const context = await store.load(fullKey);
597
- return context !== null;
598
- },
599
- };
600
-
601
- return parkingLot;
602
- }
603
-
604
- // ============================================================================
605
- // Utility Functions
606
- // ============================================================================
607
-
608
- /**
609
- * Create a correlation key from multiple parts
610
- *
611
- * @param parts - Key parts to join
612
- * @returns A correlation key string
613
- *
614
- * @example
615
- * ```typescript
616
- * const key = createCorrelationKey('payment', orderId, 'stripe');
617
- * // Returns: "payment:order-123:stripe"
618
- * ```
619
- */
620
- export function createCorrelationKey(...parts: (string | number)[]): string {
621
- return parts.map(String).join(':');
622
- }
623
-
624
- /**
625
- * Extract span context from stored context for manual linking
626
- *
627
- * @param storedContext - The stored trace context
628
- * @returns SpanContext compatible object
629
- */
630
- export function toSpanContext(storedContext: StoredTraceContext): SpanContext {
631
- return {
632
- traceId: storedContext.traceId,
633
- spanId: storedContext.spanId,
634
- traceFlags: storedContext.traceFlags,
635
- isRemote: true,
636
- };
637
- }