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.
Files changed (253) hide show
  1. package/dist/auto.cjs +5 -3
  2. package/dist/auto.cjs.map +1 -1
  3. package/dist/auto.js +3 -3
  4. package/dist/auto.js.map +1 -1
  5. package/dist/chunk-C_NdSu1c.cjs +34 -0
  6. package/dist/correlation-id.cjs +1 -1
  7. package/dist/correlation-id.d.cts.map +1 -1
  8. package/dist/correlation-id.d.ts.map +1 -1
  9. package/dist/correlation-id.js +1 -1
  10. package/dist/decorators.cjs +1 -1
  11. package/dist/decorators.js +1 -1
  12. package/dist/{event-ByBTV9M2.js → event-531asIM6.js} +4 -4
  13. package/dist/{event-ByBTV9M2.js.map → event-531asIM6.js.map} +1 -1
  14. package/dist/{event-BhHREDJk.cjs → event-CcZYwp50.cjs} +4 -4
  15. package/dist/{event-BhHREDJk.cjs.map → event-CcZYwp50.cjs.map} +1 -1
  16. package/dist/event.cjs +1 -1
  17. package/dist/event.js +1 -1
  18. package/dist/{functional-zpzNLhky.cjs → functional-C8B0Qa7o.cjs} +10 -7
  19. package/dist/functional-C8B0Qa7o.cjs.map +1 -0
  20. package/dist/{functional-DtI0u4vx.js → functional-r-AUIRy_.js} +9 -9
  21. package/dist/functional-r-AUIRy_.js.map +1 -0
  22. package/dist/functional.cjs +1 -1
  23. package/dist/functional.js +1 -1
  24. package/dist/http.cjs +1 -1
  25. package/dist/http.js +1 -1
  26. package/dist/index.cjs +15 -13
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +14 -14
  31. package/dist/index.js.map +1 -1
  32. package/dist/{init-D-jnNMix.js → init-BS2JVkrL.js} +2 -2
  33. package/dist/{init-D-jnNMix.js.map → init-BS2JVkrL.js.map} +1 -1
  34. package/dist/{init-BX7AmFRl.cjs → init-BXiuPK6j.cjs} +3 -3
  35. package/dist/{init-BX7AmFRl.cjs.map → init-BXiuPK6j.cjs.map} +1 -1
  36. package/dist/instrumentation.cjs +2 -2
  37. package/dist/instrumentation.js +2 -2
  38. package/dist/logger.cjs +236 -8
  39. package/dist/logger.cjs.map +1 -0
  40. package/dist/messaging.cjs +1 -1
  41. package/dist/messaging.js +1 -1
  42. package/dist/{node-require-DF5QBX6z.cjs → node-require-CZ_PU448.cjs} +6 -4
  43. package/dist/node-require-CZ_PU448.cjs.map +1 -0
  44. package/dist/{node-require-Db1oDpLj.js → node-require-vROmTeJ8.js} +5 -5
  45. package/dist/node-require-vROmTeJ8.js.map +1 -0
  46. package/dist/{operation-context-C-2hmmtP.js → operation-context-CKBoA4Qy.js} +3 -3
  47. package/dist/operation-context-CKBoA4Qy.js.map +1 -0
  48. package/dist/{operation-context-n4_obUwq.cjs → operation-context-D6LDf4W_.cjs} +3 -1
  49. package/dist/operation-context-D6LDf4W_.cjs.map +1 -0
  50. package/dist/register.cjs +3 -1
  51. package/dist/register.cjs.map +1 -1
  52. package/dist/register.js +2 -2
  53. package/dist/register.js.map +1 -1
  54. package/dist/semantic-helpers.cjs +1 -1
  55. package/dist/semantic-helpers.js +1 -1
  56. package/dist/{stable-hash-Cg5cT34Q.js → stable-hash-ChFBIhNt.js} +3 -3
  57. package/dist/stable-hash-ChFBIhNt.js.map +1 -0
  58. package/dist/{stable-hash-BNTMrmdB.cjs → stable-hash-brKISGf1.cjs} +4 -2
  59. package/dist/stable-hash-brKISGf1.cjs.map +1 -0
  60. package/dist/trace-context-Cijqoi6e.d.cts.map +1 -1
  61. package/dist/trace-context-Cijqoi6e.d.ts.map +1 -1
  62. package/dist/trace-helpers.cjs +1 -1
  63. package/dist/trace-helpers.js +1 -1
  64. package/dist/{track-wc0HafS_.js → track-COUuU48p.js} +5 -5
  65. package/dist/track-COUuU48p.js.map +1 -0
  66. package/dist/{track-D59FfpL0.cjs → track-Cb3Q4QmS.cjs} +4 -2
  67. package/dist/track-Cb3Q4QmS.cjs.map +1 -0
  68. package/dist/validate.cjs +1 -1
  69. package/dist/validate.js +1 -1
  70. package/dist/webhook.cjs +1 -1
  71. package/dist/webhook.js +1 -1
  72. package/dist/workflow-distributed.cjs +1 -1
  73. package/dist/workflow-distributed.js +1 -1
  74. package/dist/workflow.cjs +3 -1
  75. package/dist/workflow.cjs.map +1 -1
  76. package/dist/workflow.d.cts.map +1 -1
  77. package/dist/workflow.d.ts.map +1 -1
  78. package/dist/workflow.js +3 -3
  79. package/dist/workflow.js.map +1 -1
  80. package/dist/yaml-config.cjs +233 -4
  81. package/dist/yaml-config.cjs.map +1 -0
  82. package/dist/yaml-config.d.cts.map +1 -1
  83. package/dist/yaml-config.d.ts.map +1 -1
  84. package/dist/yaml-config.js +8 -7
  85. package/dist/yaml-config.js.map +1 -1
  86. package/package.json +1 -2
  87. package/dist/functional-DtI0u4vx.js.map +0 -1
  88. package/dist/functional-zpzNLhky.cjs.map +0 -1
  89. package/dist/logger-thMPLpOG.cjs +0 -487
  90. package/dist/logger-thMPLpOG.cjs.map +0 -1
  91. package/dist/node-require-DF5QBX6z.cjs.map +0 -1
  92. package/dist/node-require-Db1oDpLj.js.map +0 -1
  93. package/dist/operation-context-C-2hmmtP.js.map +0 -1
  94. package/dist/operation-context-n4_obUwq.cjs.map +0 -1
  95. package/dist/stable-hash-BNTMrmdB.cjs.map +0 -1
  96. package/dist/stable-hash-Cg5cT34Q.js.map +0 -1
  97. package/dist/track-D59FfpL0.cjs.map +0 -1
  98. package/dist/track-wc0HafS_.js.map +0 -1
  99. package/dist/yaml-config-Ck2uB0Dp.cjs +0 -273
  100. package/dist/yaml-config-Ck2uB0Dp.cjs.map +0 -1
  101. package/src/attribute-redacting-processor.test.ts +0 -763
  102. package/src/attribute-redacting-processor.ts +0 -621
  103. package/src/attributes/attachers.ts +0 -161
  104. package/src/attributes/builders.ts +0 -529
  105. package/src/attributes/domains.ts +0 -42
  106. package/src/attributes/index.ts +0 -81
  107. package/src/attributes/registry.ts +0 -323
  108. package/src/attributes/types.ts +0 -211
  109. package/src/attributes/utils.ts +0 -64
  110. package/src/attributes/validators.ts +0 -266
  111. package/src/attributes.test.ts +0 -292
  112. package/src/auto.ts +0 -67
  113. package/src/autotel-logger.test.ts +0 -548
  114. package/src/autotel-logger.ts +0 -364
  115. package/src/baggage-span-processor.test.ts +0 -202
  116. package/src/baggage-span-processor.ts +0 -100
  117. package/src/business-baggage.test.ts +0 -500
  118. package/src/business-baggage.ts +0 -669
  119. package/src/circuit-breaker.test.ts +0 -341
  120. package/src/circuit-breaker.ts +0 -184
  121. package/src/config.test.ts +0 -94
  122. package/src/config.ts +0 -172
  123. package/src/correlated-events.test.ts +0 -151
  124. package/src/correlated-events.ts +0 -47
  125. package/src/correlation-id.test.ts +0 -163
  126. package/src/correlation-id.ts +0 -206
  127. package/src/db.test.ts +0 -252
  128. package/src/db.ts +0 -447
  129. package/src/decorators.test.ts +0 -153
  130. package/src/decorators.ts +0 -188
  131. package/src/define-event.test.ts +0 -41
  132. package/src/define-event.ts +0 -58
  133. package/src/devtools.ts +0 -60
  134. package/src/drain-pipeline.test.ts +0 -68
  135. package/src/drain-pipeline.ts +0 -199
  136. package/src/drain-toolkit.test.ts +0 -113
  137. package/src/drain-toolkit.ts +0 -129
  138. package/src/enricher-toolkit.test.ts +0 -67
  139. package/src/enricher-toolkit.ts +0 -79
  140. package/src/enrichers.test.ts +0 -150
  141. package/src/enrichers.ts +0 -145
  142. package/src/env-config.test.ts +0 -323
  143. package/src/env-config.ts +0 -309
  144. package/src/error-catalog.test.ts +0 -133
  145. package/src/error-catalog.ts +0 -262
  146. package/src/event-queue.test.ts +0 -864
  147. package/src/event-queue.ts +0 -699
  148. package/src/event-subscriber.ts +0 -262
  149. package/src/event-testing.ts +0 -197
  150. package/src/event.test.ts +0 -1104
  151. package/src/event.ts +0 -988
  152. package/src/events-config.ts +0 -235
  153. package/src/exporters.ts +0 -165
  154. package/src/filtering-span-processor.test.ts +0 -281
  155. package/src/filtering-span-processor.ts +0 -111
  156. package/src/flatten-attributes.test.ts +0 -76
  157. package/src/flatten-attributes.ts +0 -80
  158. package/src/functional.strict-types.typecheck.ts +0 -53
  159. package/src/functional.test.ts +0 -1464
  160. package/src/functional.ts +0 -2539
  161. package/src/functional.types.test.ts +0 -135
  162. package/src/hook.mjs +0 -15
  163. package/src/http.test.ts +0 -485
  164. package/src/http.ts +0 -424
  165. package/src/index.ts +0 -433
  166. package/src/init-auto-redactor.test.ts +0 -53
  167. package/src/init-redactor.test.ts +0 -8
  168. package/src/init.customization.test.ts +0 -665
  169. package/src/init.integrations.test.ts +0 -399
  170. package/src/init.openllmetry.test.ts +0 -194
  171. package/src/init.protocol.test.ts +0 -215
  172. package/src/init.ts +0 -2439
  173. package/src/instrumentation.test.ts +0 -108
  174. package/src/instrumentation.ts +0 -319
  175. package/src/logger.test.ts +0 -125
  176. package/src/logger.ts +0 -341
  177. package/src/messaging-adapters.test.ts +0 -595
  178. package/src/messaging-adapters.ts +0 -583
  179. package/src/messaging-testing.test.ts +0 -573
  180. package/src/messaging-testing.ts +0 -935
  181. package/src/messaging.test.ts +0 -1646
  182. package/src/messaging.ts +0 -2245
  183. package/src/metric-helpers.ts +0 -47
  184. package/src/metric-testing.ts +0 -197
  185. package/src/metric.ts +0 -446
  186. package/src/metrics.test.ts +0 -241
  187. package/src/node-require.ts +0 -123
  188. package/src/operation-context.ts +0 -93
  189. package/src/parse-error.test.ts +0 -73
  190. package/src/parse-error.ts +0 -112
  191. package/src/posthog-logs.test.ts +0 -115
  192. package/src/posthog-logs.ts +0 -77
  193. package/src/pretty-console-exporter.test.ts +0 -545
  194. package/src/pretty-console-exporter.ts +0 -413
  195. package/src/pretty-log-formatter.test.ts +0 -123
  196. package/src/pretty-log-formatter.ts +0 -210
  197. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  198. package/src/processors/canonical-log-line-processor.ts +0 -396
  199. package/src/processors.ts +0 -152
  200. package/src/rate-limiter.test.ts +0 -199
  201. package/src/rate-limiter.ts +0 -98
  202. package/src/redact-values.test.ts +0 -90
  203. package/src/redact-values.ts +0 -34
  204. package/src/register.ts +0 -37
  205. package/src/request-logger.test.ts +0 -545
  206. package/src/request-logger.ts +0 -342
  207. package/src/sampling.test.ts +0 -1060
  208. package/src/sampling.ts +0 -737
  209. package/src/security-schema.test.ts +0 -45
  210. package/src/security-schema.ts +0 -107
  211. package/src/semantic-conventions.ts +0 -15
  212. package/src/semantic-helpers.test.ts +0 -226
  213. package/src/semantic-helpers.ts +0 -438
  214. package/src/shutdown.test.ts +0 -364
  215. package/src/shutdown.ts +0 -246
  216. package/src/span-name-normalizer.test.ts +0 -377
  217. package/src/span-name-normalizer.ts +0 -213
  218. package/src/stable-hash.ts +0 -27
  219. package/src/structured-error.test.ts +0 -191
  220. package/src/structured-error.ts +0 -157
  221. package/src/stub.integration.test.ts +0 -361
  222. package/src/tail-sampling-processor.test.ts +0 -230
  223. package/src/tail-sampling-processor.ts +0 -55
  224. package/src/test-span-collector.test.ts +0 -234
  225. package/src/test-span-collector.ts +0 -150
  226. package/src/testing.ts +0 -705
  227. package/src/trace-context.test.ts +0 -73
  228. package/src/trace-context.ts +0 -567
  229. package/src/trace-helpers.new.test.ts +0 -278
  230. package/src/trace-helpers.test.ts +0 -290
  231. package/src/trace-helpers.ts +0 -710
  232. package/src/trace-hybrid.test.ts +0 -42
  233. package/src/trace-hybrid.ts +0 -37
  234. package/src/tracer-provider.test.ts +0 -183
  235. package/src/tracer-provider.ts +0 -266
  236. package/src/track.test.ts +0 -154
  237. package/src/track.ts +0 -216
  238. package/src/validate.test.ts +0 -287
  239. package/src/validate.ts +0 -307
  240. package/src/validation-attributes.ts +0 -43
  241. package/src/validation.test.ts +0 -330
  242. package/src/validation.ts +0 -246
  243. package/src/variable-name-inference.test.ts +0 -178
  244. package/src/variable-name-inference.ts +0 -242
  245. package/src/webhook.test.ts +0 -649
  246. package/src/webhook.ts +0 -637
  247. package/src/workflow-distributed.test.ts +0 -786
  248. package/src/workflow-distributed.ts +0 -916
  249. package/src/workflow.async-safety.integration.test.ts +0 -345
  250. package/src/workflow.test.ts +0 -647
  251. package/src/workflow.ts +0 -810
  252. package/src/yaml-config.test.ts +0 -373
  253. package/src/yaml-config.ts +0 -351
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
- }