autotel 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +26 -1
  2. package/dist/auto.cjs +2 -2
  3. package/dist/auto.js +1 -1
  4. package/dist/correlation-id.cjs +1 -1
  5. package/dist/correlation-id.js +1 -1
  6. package/dist/decorators.cjs +1 -1
  7. package/dist/decorators.js +1 -1
  8. package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
  9. package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
  10. package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
  11. package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
  12. package/dist/event.cjs +1 -1
  13. package/dist/event.js +1 -1
  14. package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
  15. package/dist/functional-DtI0u4vx.js.map +1 -0
  16. package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
  17. package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
  18. package/dist/functional.cjs +1 -1
  19. package/dist/functional.js +1 -1
  20. package/dist/http.cjs +1 -1
  21. package/dist/http.js +1 -1
  22. package/dist/index.cjs +5 -5
  23. package/dist/index.d.cts +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +5 -5
  26. package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
  27. package/dist/init-B7u-DjxM.d.ts.map +1 -0
  28. package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
  29. package/dist/init-BX7AmFRl.cjs.map +1 -0
  30. package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
  31. package/dist/init-D-jnNMix.js.map +1 -0
  32. package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
  33. package/dist/init-DSrRmVnz.d.cts.map +1 -0
  34. package/dist/instrumentation.cjs +1 -1
  35. package/dist/instrumentation.js +1 -1
  36. package/dist/logger-D3Ej3DII.js +446 -0
  37. package/dist/logger-D3Ej3DII.js.map +1 -0
  38. package/dist/logger-thMPLpOG.cjs +487 -0
  39. package/dist/logger-thMPLpOG.cjs.map +1 -0
  40. package/dist/logger.cjs +8 -236
  41. package/dist/logger.js +2 -204
  42. package/dist/messaging.cjs +1 -1
  43. package/dist/messaging.js +1 -1
  44. package/dist/semantic-helpers.cjs +1 -1
  45. package/dist/semantic-helpers.js +1 -1
  46. package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
  47. package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
  48. package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
  49. package/dist/track-wc0HafS_.js.map +1 -0
  50. package/dist/webhook.cjs +1 -1
  51. package/dist/webhook.js +1 -1
  52. package/dist/workflow-distributed.cjs +1 -1
  53. package/dist/workflow-distributed.js +1 -1
  54. package/dist/workflow.cjs +1 -1
  55. package/dist/workflow.js +1 -1
  56. package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
  57. package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
  58. package/dist/yaml-config.cjs +1 -1
  59. package/dist/yaml-config.d.cts +7 -1
  60. package/dist/yaml-config.d.cts.map +1 -1
  61. package/dist/yaml-config.d.ts +7 -1
  62. package/dist/yaml-config.d.ts.map +1 -1
  63. package/dist/yaml-config.js +1 -0
  64. package/dist/yaml-config.js.map +1 -1
  65. package/package.json +1 -2
  66. package/skills/autotel-core/SKILL.md +2 -0
  67. package/skills/autotel-instrumentation/SKILL.md +25 -0
  68. package/skills/debug-missing-spans/SKILL.md +3 -1
  69. package/skills/migrate-to-autotel/SKILL.md +24 -23
  70. package/skills/review-otel-patterns/SKILL.md +5 -4
  71. package/dist/functional-BGkT8J-h.js.map +0 -1
  72. package/dist/init-CNp-ee80.d.cts.map +0 -1
  73. package/dist/init-Ch6t7MNI.js.map +0 -1
  74. package/dist/init-DJQOdVlN.d.ts.map +0 -1
  75. package/dist/init-DvapOXCc.cjs.map +0 -1
  76. package/dist/logger.cjs.map +0 -1
  77. package/dist/logger.js.map +0 -1
  78. package/dist/track-nsKVy-pj.js.map +0 -1
  79. package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
  80. package/src/attribute-redacting-processor.test.ts +0 -763
  81. package/src/attribute-redacting-processor.ts +0 -621
  82. package/src/attributes/attachers.ts +0 -161
  83. package/src/attributes/builders.ts +0 -529
  84. package/src/attributes/domains.ts +0 -42
  85. package/src/attributes/index.ts +0 -81
  86. package/src/attributes/registry.ts +0 -323
  87. package/src/attributes/types.ts +0 -211
  88. package/src/attributes/utils.ts +0 -64
  89. package/src/attributes/validators.ts +0 -266
  90. package/src/attributes.test.ts +0 -292
  91. package/src/auto.ts +0 -67
  92. package/src/autotel-logger.test.ts +0 -548
  93. package/src/autotel-logger.ts +0 -364
  94. package/src/baggage-span-processor.test.ts +0 -202
  95. package/src/baggage-span-processor.ts +0 -100
  96. package/src/business-baggage.test.ts +0 -500
  97. package/src/business-baggage.ts +0 -669
  98. package/src/circuit-breaker.test.ts +0 -341
  99. package/src/circuit-breaker.ts +0 -184
  100. package/src/config.test.ts +0 -94
  101. package/src/config.ts +0 -172
  102. package/src/correlated-events.test.ts +0 -151
  103. package/src/correlated-events.ts +0 -47
  104. package/src/correlation-id.test.ts +0 -163
  105. package/src/correlation-id.ts +0 -206
  106. package/src/db.test.ts +0 -252
  107. package/src/db.ts +0 -447
  108. package/src/decorators.test.ts +0 -153
  109. package/src/decorators.ts +0 -188
  110. package/src/define-event.test.ts +0 -41
  111. package/src/define-event.ts +0 -58
  112. package/src/devtools.ts +0 -60
  113. package/src/drain-pipeline.test.ts +0 -68
  114. package/src/drain-pipeline.ts +0 -199
  115. package/src/drain-toolkit.test.ts +0 -113
  116. package/src/drain-toolkit.ts +0 -129
  117. package/src/enricher-toolkit.test.ts +0 -67
  118. package/src/enricher-toolkit.ts +0 -79
  119. package/src/enrichers.test.ts +0 -150
  120. package/src/enrichers.ts +0 -145
  121. package/src/env-config.test.ts +0 -323
  122. package/src/env-config.ts +0 -309
  123. package/src/error-catalog.test.ts +0 -133
  124. package/src/error-catalog.ts +0 -262
  125. package/src/event-queue.test.ts +0 -864
  126. package/src/event-queue.ts +0 -699
  127. package/src/event-subscriber.ts +0 -262
  128. package/src/event-testing.ts +0 -197
  129. package/src/event.test.ts +0 -1104
  130. package/src/event.ts +0 -988
  131. package/src/events-config.ts +0 -235
  132. package/src/exporters.ts +0 -165
  133. package/src/filtering-span-processor.test.ts +0 -281
  134. package/src/filtering-span-processor.ts +0 -111
  135. package/src/flatten-attributes.test.ts +0 -76
  136. package/src/flatten-attributes.ts +0 -80
  137. package/src/functional.strict-types.typecheck.ts +0 -53
  138. package/src/functional.test.ts +0 -1464
  139. package/src/functional.ts +0 -2539
  140. package/src/functional.types.test.ts +0 -135
  141. package/src/hook.mjs +0 -15
  142. package/src/http.test.ts +0 -485
  143. package/src/http.ts +0 -424
  144. package/src/index.ts +0 -433
  145. package/src/init-auto-redactor.test.ts +0 -53
  146. package/src/init-redactor.test.ts +0 -8
  147. package/src/init.customization.test.ts +0 -594
  148. package/src/init.integrations.test.ts +0 -399
  149. package/src/init.openllmetry.test.ts +0 -194
  150. package/src/init.protocol.test.ts +0 -215
  151. package/src/init.ts +0 -2312
  152. package/src/instrumentation.test.ts +0 -108
  153. package/src/instrumentation.ts +0 -319
  154. package/src/logger.test.ts +0 -125
  155. package/src/logger.ts +0 -341
  156. package/src/messaging-adapters.test.ts +0 -595
  157. package/src/messaging-adapters.ts +0 -583
  158. package/src/messaging-testing.test.ts +0 -573
  159. package/src/messaging-testing.ts +0 -935
  160. package/src/messaging.test.ts +0 -1646
  161. package/src/messaging.ts +0 -2245
  162. package/src/metric-helpers.ts +0 -47
  163. package/src/metric-testing.ts +0 -197
  164. package/src/metric.ts +0 -446
  165. package/src/metrics.test.ts +0 -241
  166. package/src/node-require.ts +0 -123
  167. package/src/operation-context.ts +0 -93
  168. package/src/parse-error.test.ts +0 -73
  169. package/src/parse-error.ts +0 -112
  170. package/src/posthog-logs.test.ts +0 -115
  171. package/src/posthog-logs.ts +0 -77
  172. package/src/pretty-console-exporter.test.ts +0 -545
  173. package/src/pretty-console-exporter.ts +0 -413
  174. package/src/pretty-log-formatter.test.ts +0 -123
  175. package/src/pretty-log-formatter.ts +0 -210
  176. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  177. package/src/processors/canonical-log-line-processor.ts +0 -396
  178. package/src/processors.ts +0 -152
  179. package/src/rate-limiter.test.ts +0 -199
  180. package/src/rate-limiter.ts +0 -98
  181. package/src/redact-values.test.ts +0 -90
  182. package/src/redact-values.ts +0 -34
  183. package/src/register.ts +0 -37
  184. package/src/request-logger.test.ts +0 -545
  185. package/src/request-logger.ts +0 -342
  186. package/src/sampling.test.ts +0 -1060
  187. package/src/sampling.ts +0 -737
  188. package/src/security-schema.test.ts +0 -45
  189. package/src/security-schema.ts +0 -107
  190. package/src/semantic-conventions.ts +0 -15
  191. package/src/semantic-helpers.test.ts +0 -226
  192. package/src/semantic-helpers.ts +0 -438
  193. package/src/shutdown.test.ts +0 -364
  194. package/src/shutdown.ts +0 -246
  195. package/src/span-name-normalizer.test.ts +0 -377
  196. package/src/span-name-normalizer.ts +0 -213
  197. package/src/stable-hash.ts +0 -27
  198. package/src/structured-error.test.ts +0 -191
  199. package/src/structured-error.ts +0 -157
  200. package/src/stub.integration.test.ts +0 -361
  201. package/src/tail-sampling-processor.test.ts +0 -230
  202. package/src/tail-sampling-processor.ts +0 -55
  203. package/src/test-span-collector.test.ts +0 -234
  204. package/src/test-span-collector.ts +0 -150
  205. package/src/testing.ts +0 -705
  206. package/src/trace-context.test.ts +0 -73
  207. package/src/trace-context.ts +0 -567
  208. package/src/trace-helpers.new.test.ts +0 -278
  209. package/src/trace-helpers.test.ts +0 -290
  210. package/src/trace-helpers.ts +0 -710
  211. package/src/trace-hybrid.test.ts +0 -42
  212. package/src/trace-hybrid.ts +0 -37
  213. package/src/tracer-provider.test.ts +0 -183
  214. package/src/tracer-provider.ts +0 -266
  215. package/src/track.test.ts +0 -154
  216. package/src/track.ts +0 -216
  217. package/src/validate.test.ts +0 -287
  218. package/src/validate.ts +0 -307
  219. package/src/validation-attributes.ts +0 -43
  220. package/src/validation.test.ts +0 -330
  221. package/src/validation.ts +0 -246
  222. package/src/variable-name-inference.test.ts +0 -178
  223. package/src/variable-name-inference.ts +0 -242
  224. package/src/webhook.test.ts +0 -649
  225. package/src/webhook.ts +0 -637
  226. package/src/workflow-distributed.test.ts +0 -786
  227. package/src/workflow-distributed.ts +0 -916
  228. package/src/workflow.async-safety.integration.test.ts +0 -345
  229. package/src/workflow.test.ts +0 -647
  230. package/src/workflow.ts +0 -810
  231. package/src/yaml-config.test.ts +0 -337
  232. package/src/yaml-config.ts +0 -342
@@ -1,916 +0,0 @@
1
- /**
2
- * Distributed workflow tracing with cross-service correlation
3
- *
4
- * Enables tracking workflows that span multiple microservices by propagating
5
- * workflow identity (workflowId, stepName, stepIndex) via baggage in message headers.
6
- *
7
- * Unlike local workflow.ts (which uses AsyncLocalStorage), distributed workflows
8
- * propagate context across network boundaries using W3C baggage.
9
- *
10
- * @example Order fulfillment saga across services
11
- * ```typescript
12
- * // Service A: Order Service
13
- * import { traceDistributedWorkflow, WorkflowBaggage } from 'autotel/workflow-distributed';
14
- * import { traceProducer } from 'autotel/messaging';
15
- *
16
- * export const createOrder = traceDistributedWorkflow({
17
- * name: 'OrderFulfillment',
18
- * workflowIdFrom: (order) => order.id,
19
- * version: '1.0.0',
20
- * })(ctx => async (order: Order) => {
21
- * // Workflow baggage is auto-set
22
- * await publishToInventory(order);
23
- * });
24
- *
25
- * const publishToInventory = traceProducer({
26
- * system: 'kafka',
27
- * destination: 'inventory-requests',
28
- * propagateBaggage: true, // Includes workflow.* baggage
29
- * })(ctx => async (order) => {
30
- * await producer.send({ topic: 'inventory-requests', value: order });
31
- * });
32
- *
33
- * // Service B: Inventory Service
34
- * import { traceDistributedStep, WorkflowBaggage } from 'autotel/workflow-distributed';
35
- *
36
- * export const processInventory = traceDistributedStep({
37
- * name: 'ReserveInventory',
38
- * extractBaggage: true, // Extracts workflow.* from headers
39
- * })(ctx => async (message) => {
40
- * const workflow = WorkflowBaggage.get(ctx);
41
- * // workflow.workflowId === order.id (propagated from Service A)
42
- * console.log(`Processing step for workflow ${workflow.workflowId}`);
43
- * await reserveItems(message.items);
44
- * });
45
- * ```
46
- *
47
- * @module
48
- */
49
-
50
- import { context, propagation, SpanKind } from '@opentelemetry/api';
51
- import { createSafeBaggageSchema } from './business-baggage';
52
- import { emitCorrelatedEvent } from './correlated-events';
53
- import { trace } from './functional';
54
- import type { TraceContext } from './trace-context';
55
-
56
- // ============================================================================
57
- // Workflow Baggage Schema
58
- // ============================================================================
59
-
60
- /**
61
- * Workflow baggage field definitions
62
- */
63
- const workflowBaggageFields = {
64
- /** Unique identifier for the workflow instance */
65
- workflowId: { type: 'string' as const, maxLength: 128, required: true },
66
-
67
- /** Name/type of the workflow (e.g., "OrderFulfillment") */
68
- workflowName: { type: 'string' as const, maxLength: 64, required: true },
69
-
70
- /** Version of the workflow definition */
71
- workflowVersion: { type: 'string' as const, maxLength: 32 },
72
-
73
- /** Current step name */
74
- stepName: { type: 'string' as const, maxLength: 64 },
75
-
76
- /** Current step index (0-based) */
77
- stepIndex: { type: 'number' as const },
78
-
79
- /** Total number of steps (if known) */
80
- totalSteps: { type: 'number' as const },
81
-
82
- /** Parent workflow ID (for sub-workflows) */
83
- parentWorkflowId: { type: 'string' as const, maxLength: 128 },
84
-
85
- /** Correlation ID for external systems */
86
- correlationId: { type: 'string' as const, maxLength: 128 },
87
-
88
- /** Workflow priority */
89
- priority: {
90
- type: 'enum' as const,
91
- values: ['low', 'normal', 'high', 'critical'] as const,
92
- },
93
-
94
- /** Initiating user/system */
95
- initiatedBy: { type: 'string' as const, maxLength: 64 },
96
-
97
- /** Workflow start timestamp (ISO) */
98
- startedAt: { type: 'string' as const, maxLength: 30 },
99
- } as const;
100
-
101
- /**
102
- * Pre-built baggage schema for distributed workflows
103
- *
104
- * Use this to read/write workflow context that propagates across services.
105
- *
106
- * @example Setting workflow baggage
107
- * ```typescript
108
- * WorkflowBaggage.set(ctx, {
109
- * workflowId: 'order-12345',
110
- * workflowName: 'OrderFulfillment',
111
- * stepName: 'ReserveInventory',
112
- * stepIndex: 1,
113
- * });
114
- * ```
115
- *
116
- * @example Reading workflow baggage in downstream service
117
- * ```typescript
118
- * const { workflowId, workflowName, stepIndex } = WorkflowBaggage.get(ctx);
119
- * console.log(`Processing ${workflowName} step ${stepIndex}`);
120
- * ```
121
- */
122
- export const WorkflowBaggage = createSafeBaggageSchema(workflowBaggageFields, {
123
- prefix: 'workflow',
124
- hashHighCardinality: false, // Workflow IDs should be traceable
125
- redactPII: false, // Workflow fields are internal identifiers
126
- });
127
-
128
- /**
129
- * Type for workflow baggage values
130
- */
131
- export type WorkflowBaggageValues = {
132
- workflowId: string;
133
- workflowName: string;
134
- workflowVersion?: string;
135
- stepName?: string;
136
- stepIndex?: number;
137
- totalSteps?: number;
138
- parentWorkflowId?: string;
139
- correlationId?: string;
140
- priority?: 'low' | 'normal' | 'high' | 'critical';
141
- initiatedBy?: string;
142
- startedAt?: string;
143
- };
144
-
145
- // ============================================================================
146
- // Types
147
- // ============================================================================
148
-
149
- /**
150
- * Configuration for distributed workflow tracing
151
- */
152
- export interface DistributedWorkflowConfig {
153
- /** Workflow name/type (e.g., "OrderFulfillment", "UserOnboarding") */
154
- name: string;
155
-
156
- /**
157
- * Extract workflow ID from function arguments
158
- *
159
- * Receives all arguments passed to the workflow function, allowing
160
- * multi-parameter handlers to derive workflow IDs from any argument.
161
- *
162
- * @example Single argument
163
- * ```typescript
164
- * workflowIdFrom: (order) => order.id
165
- * ```
166
- *
167
- * @example Multiple arguments (payload + metadata)
168
- * ```typescript
169
- * workflowIdFrom: (payload, metadata) => metadata.correlationId ?? payload.id
170
- * ```
171
- */
172
- workflowIdFrom: (...args: unknown[]) => string;
173
-
174
- /** Workflow version (e.g., "1.0.0", "2023-01-15") */
175
- version?: string;
176
-
177
- /** Total number of steps if known */
178
- totalSteps?: number;
179
-
180
- /** Parent workflow ID (for sub-workflows) */
181
- parentWorkflowId?: string;
182
-
183
- /** Correlation ID for external systems */
184
- correlationId?: string;
185
-
186
- /** Workflow priority */
187
- priority?: 'low' | 'normal' | 'high' | 'critical';
188
-
189
- /** User/system that initiated the workflow */
190
- initiatedBy?: string;
191
-
192
- /** Additional span attributes */
193
- attributes?: Record<string, string | number | boolean>;
194
-
195
- /** Callback on workflow start */
196
- onStart?: (ctx: DistributedWorkflowContext) => void;
197
-
198
- /** Callback on workflow completion */
199
- onComplete?: (ctx: DistributedWorkflowContext, result: unknown) => void;
200
-
201
- /** Callback on workflow error */
202
- onError?: (ctx: DistributedWorkflowContext, error: Error) => void;
203
- }
204
-
205
- /**
206
- * Configuration for distributed workflow step
207
- */
208
- export interface DistributedStepConfig {
209
- /** Step name (e.g., "ReserveInventory", "ChargePayment") */
210
- name: string;
211
-
212
- /**
213
- * Extract baggage from incoming message/request
214
- *
215
- * If true, reads workflow baggage from current context (assumes already extracted).
216
- * If function, extracts from arguments.
217
- *
218
- * @default true
219
- */
220
- extractBaggage?:
221
- | boolean
222
- | ((args: unknown[]) => WorkflowBaggageValues | null);
223
-
224
- /** Override step index (otherwise uses baggage or auto-increments) */
225
- stepIndex?: number;
226
-
227
- /** Additional span attributes */
228
- attributes?: Record<string, string | number | boolean>;
229
-
230
- /** Whether this step is idempotent (safe to retry) */
231
- idempotent?: boolean;
232
-
233
- /** Whether this step is a compensation/rollback step */
234
- isCompensation?: boolean;
235
-
236
- /** Callback on step start */
237
- onStart?: (ctx: DistributedStepContext) => void;
238
-
239
- /** Callback on step completion */
240
- onComplete?: (ctx: DistributedStepContext, result: unknown) => void;
241
-
242
- /** Callback on step error */
243
- onError?: (ctx: DistributedStepContext, error: Error) => void;
244
- }
245
-
246
- /**
247
- * Extended context for distributed workflow root
248
- */
249
- export interface DistributedWorkflowContext extends TraceContext {
250
- /** The workflow ID */
251
- workflowId: string;
252
-
253
- /** The workflow name */
254
- workflowName: string;
255
-
256
- /** The workflow version */
257
- workflowVersion?: string;
258
-
259
- /** Get workflow baggage for propagation to other services */
260
- getWorkflowBaggage(): WorkflowBaggageValues;
261
-
262
- /** Set additional workflow baggage fields */
263
- setWorkflowBaggage(values: Partial<WorkflowBaggageValues>): void;
264
-
265
- /** Get headers with workflow baggage for outgoing requests */
266
- getWorkflowHeaders(): Record<string, string>;
267
-
268
- /** Record workflow step completion (for progress tracking) */
269
- recordStepProgress(stepName: string, stepIndex: number): void;
270
- }
271
-
272
- /**
273
- * Extended context for distributed workflow step
274
- */
275
- export interface DistributedStepContext extends TraceContext {
276
- /** The workflow ID (from baggage) */
277
- workflowId: string | null;
278
-
279
- /** The workflow name (from baggage) */
280
- workflowName: string | null;
281
-
282
- /** The current step name */
283
- stepName: string;
284
-
285
- /** The current step index */
286
- stepIndex: number | null;
287
-
288
- /** Whether this step is a compensation */
289
- isCompensation: boolean;
290
-
291
- /** Get the full workflow baggage */
292
- getWorkflowBaggage(): WorkflowBaggageValues | null;
293
-
294
- /** Update workflow baggage (e.g., increment step index) */
295
- updateWorkflowBaggage(values: Partial<WorkflowBaggageValues>): void;
296
-
297
- /** Get headers with updated workflow baggage for downstream calls */
298
- getWorkflowHeaders(): Record<string, string>;
299
-
300
- /** Mark step as requiring compensation on failure */
301
- requiresCompensation(compensationData?: Record<string, unknown>): void;
302
- }
303
-
304
- // ============================================================================
305
- // Distributed Workflow Tracer
306
- // ============================================================================
307
-
308
- /**
309
- * Create a traced distributed workflow function
310
- *
311
- * Wraps a function as the entry point for a distributed workflow. Automatically:
312
- * - Generates or extracts workflow ID
313
- * - Sets workflow baggage for downstream propagation
314
- * - Creates root span with workflow attributes
315
- *
316
- * @param config - Workflow configuration
317
- * @returns Factory function for the workflow handler
318
- *
319
- * @example Basic usage
320
- * ```typescript
321
- * export const createOrder = traceDistributedWorkflow({
322
- * name: 'OrderFulfillment',
323
- * workflowIdFrom: (order) => order.id,
324
- * version: '1.0.0',
325
- * })(ctx => async (order: Order) => {
326
- * ctx.recordStepProgress('ValidateOrder', 0);
327
- * await validateOrder(order);
328
- *
329
- * ctx.recordStepProgress('ReserveInventory', 1);
330
- * await publishToInventoryService(order);
331
- *
332
- * return { workflowId: ctx.workflowId, status: 'started' };
333
- * });
334
- * ```
335
- */
336
- export function traceDistributedWorkflow<TArgs extends unknown[], TReturn>(
337
- config: DistributedWorkflowConfig,
338
- ) {
339
- const spanName = `workflow.${config.name}`;
340
-
341
- return (
342
- fnFactory: (
343
- ctx: DistributedWorkflowContext,
344
- ) => (...args: TArgs) => Promise<TReturn>,
345
- ): ((...args: TArgs) => Promise<TReturn>) => {
346
- return trace<TArgs, TReturn>(
347
- { name: spanName, spanKind: SpanKind.INTERNAL },
348
- (baseCtx) => {
349
- return async (...args: TArgs) => {
350
- // Extract workflow ID from arguments (spread to allow multi-arg access)
351
- const workflowId = config.workflowIdFrom(...args);
352
- const startedAt = new Date().toISOString();
353
-
354
- // Initialize workflow baggage
355
- const baggageValues: WorkflowBaggageValues = {
356
- workflowId,
357
- workflowName: config.name,
358
- workflowVersion: config.version,
359
- stepIndex: 0,
360
- totalSteps: config.totalSteps,
361
- parentWorkflowId: config.parentWorkflowId,
362
- correlationId: config.correlationId,
363
- priority: config.priority,
364
- initiatedBy: config.initiatedBy,
365
- startedAt,
366
- };
367
-
368
- // Set baggage
369
- WorkflowBaggage.set(baseCtx, baggageValues);
370
-
371
- // Set span attributes
372
- baseCtx.setAttribute('workflow.id', workflowId);
373
- baseCtx.setAttribute('workflow.name', config.name);
374
- if (config.version) {
375
- baseCtx.setAttribute('workflow.version', config.version);
376
- }
377
- if (config.totalSteps) {
378
- baseCtx.setAttribute('workflow.total_steps', config.totalSteps);
379
- }
380
- if (config.parentWorkflowId) {
381
- baseCtx.setAttribute('workflow.parent_id', config.parentWorkflowId);
382
- }
383
- if (config.priority) {
384
- baseCtx.setAttribute('workflow.priority', config.priority);
385
- }
386
- if (config.initiatedBy) {
387
- baseCtx.setAttribute('workflow.initiated_by', config.initiatedBy);
388
- }
389
- baseCtx.setAttribute('workflow.started_at', startedAt);
390
-
391
- // Apply custom attributes
392
- if (config.attributes) {
393
- for (const [key, value] of Object.entries(config.attributes)) {
394
- baseCtx.setAttribute(key, value);
395
- }
396
- }
397
-
398
- // Create extended context
399
- const workflowCtx: DistributedWorkflowContext = {
400
- ...baseCtx,
401
- workflowId,
402
- workflowName: config.name,
403
- workflowVersion: config.version,
404
-
405
- getWorkflowBaggage(): WorkflowBaggageValues {
406
- return { ...baggageValues };
407
- },
408
-
409
- setWorkflowBaggage(values: Partial<WorkflowBaggageValues>): void {
410
- Object.assign(baggageValues, values);
411
- WorkflowBaggage.set(baseCtx, baggageValues);
412
- },
413
-
414
- getWorkflowHeaders(): Record<string, string> {
415
- const headers: Record<string, string> = {};
416
- const ctx = context.active();
417
- propagation.inject(ctx, headers);
418
- return headers;
419
- },
420
-
421
- recordStepProgress(stepName: string, stepIndex: number): void {
422
- baggageValues.stepName = stepName;
423
- baggageValues.stepIndex = stepIndex;
424
- WorkflowBaggage.set(baseCtx, baggageValues);
425
-
426
- emitCorrelatedEvent(baseCtx, 'workflow.step_progress', {
427
- 'workflow.step.name': stepName,
428
- 'workflow.step.index': stepIndex,
429
- });
430
- },
431
- };
432
-
433
- // Call onStart callback
434
- config.onStart?.(workflowCtx);
435
-
436
- // Add start event
437
- emitCorrelatedEvent(baseCtx, 'workflow.started', {
438
- 'workflow.id': workflowId,
439
- 'workflow.name': config.name,
440
- });
441
-
442
- try {
443
- const userFn = fnFactory(workflowCtx);
444
- const result = await userFn(...args);
445
-
446
- // Call onComplete callback
447
- config.onComplete?.(workflowCtx, result);
448
-
449
- // Add completion event
450
- emitCorrelatedEvent(baseCtx, 'workflow.completed', {
451
- 'workflow.id': workflowId,
452
- });
453
-
454
- return result;
455
- } catch (error) {
456
- // Call onError callback
457
- config.onError?.(workflowCtx, error as Error);
458
-
459
- // Add error event
460
- emitCorrelatedEvent(baseCtx, 'workflow.failed', {
461
- 'workflow.id': workflowId,
462
- 'workflow.error': (error as Error).message,
463
- });
464
-
465
- throw error;
466
- }
467
- };
468
- },
469
- );
470
- };
471
- }
472
-
473
- // ============================================================================
474
- // Distributed Step Tracer
475
- // ============================================================================
476
-
477
- /**
478
- * Create a traced distributed workflow step
479
- *
480
- * Use in downstream services to trace steps that are part of a distributed workflow.
481
- * Automatically extracts workflow baggage from the current context.
482
- *
483
- * @param config - Step configuration
484
- * @returns Factory function for the step handler
485
- *
486
- * @example Consumer in downstream service
487
- * ```typescript
488
- * export const processInventory = traceConsumer({
489
- * system: 'kafka',
490
- * destination: 'inventory-requests',
491
- * extractBaggage: true, // Extracts workflow.* from headers
492
- * })(ctx => {
493
- * // Wrap inner logic with traceDistributedStep
494
- * return traceDistributedStep({
495
- * name: 'ReserveInventory',
496
- * })(stepCtx => async (message) => {
497
- * console.log(`Processing workflow ${stepCtx.workflowId}`);
498
- * await reserveItems(message.items);
499
- * })(message);
500
- * });
501
- * ```
502
- *
503
- * @example Standalone step handler
504
- * ```typescript
505
- * export const reserveInventory = traceDistributedStep({
506
- * name: 'ReserveInventory',
507
- * idempotent: true,
508
- * })(ctx => async (request: InventoryRequest) => {
509
- * if (ctx.workflowId) {
510
- * console.log(`Part of workflow ${ctx.workflowId}, step ${ctx.stepIndex}`);
511
- * }
512
- * return await inventoryService.reserve(request.items);
513
- * });
514
- * ```
515
- */
516
- export function traceDistributedStep<TArgs extends unknown[], TReturn>(
517
- config: DistributedStepConfig,
518
- ) {
519
- const spanName = `workflow.step.${config.name}`;
520
-
521
- return (
522
- fnFactory: (
523
- ctx: DistributedStepContext,
524
- ) => (...args: TArgs) => Promise<TReturn>,
525
- ): ((...args: TArgs) => Promise<TReturn>) => {
526
- return trace<TArgs, TReturn>(
527
- { name: spanName, spanKind: SpanKind.INTERNAL },
528
- (baseCtx) => {
529
- return async (...args: TArgs) => {
530
- // Extract workflow baggage
531
- let baggageValues: WorkflowBaggageValues | null = null;
532
-
533
- const extractBaggage = config.extractBaggage ?? true;
534
- if (typeof extractBaggage === 'function') {
535
- baggageValues = extractBaggage(args);
536
- } else if (extractBaggage) {
537
- // Read from current context
538
- const extracted = WorkflowBaggage.get(baseCtx);
539
- if (extracted.workflowId && extracted.workflowName) {
540
- baggageValues = extracted as WorkflowBaggageValues;
541
- }
542
- }
543
-
544
- // Determine step index
545
- // If explicit stepIndex provided in config, use it
546
- // Otherwise, auto-increment from baggage if available
547
- let stepIndex: number | null;
548
- if (config.stepIndex !== undefined) {
549
- stepIndex = config.stepIndex;
550
- } else if (baggageValues?.stepIndex === undefined) {
551
- stepIndex = null;
552
- } else {
553
- // Auto-increment from previous step
554
- stepIndex = baggageValues.stepIndex + 1;
555
- }
556
-
557
- // Update baggage with current step
558
- if (baggageValues) {
559
- baggageValues.stepName = config.name;
560
- if (stepIndex !== null) {
561
- baggageValues.stepIndex = stepIndex;
562
- }
563
- WorkflowBaggage.set(baseCtx, baggageValues);
564
- }
565
-
566
- // Set span attributes
567
- baseCtx.setAttribute('workflow.step.name', config.name);
568
- if (stepIndex !== null) {
569
- baseCtx.setAttribute('workflow.step.index', stepIndex);
570
- }
571
- if (config.idempotent !== undefined) {
572
- baseCtx.setAttribute('workflow.step.idempotent', config.idempotent);
573
- }
574
- if (config.isCompensation) {
575
- baseCtx.setAttribute('workflow.step.is_compensation', true);
576
- }
577
-
578
- // Add workflow context attributes if available
579
- if (baggageValues) {
580
- baseCtx.setAttribute('workflow.id', baggageValues.workflowId);
581
- baseCtx.setAttribute('workflow.name', baggageValues.workflowName);
582
- if (baggageValues.workflowVersion) {
583
- baseCtx.setAttribute(
584
- 'workflow.version',
585
- baggageValues.workflowVersion,
586
- );
587
- }
588
- if (baggageValues.totalSteps) {
589
- baseCtx.setAttribute(
590
- 'workflow.total_steps',
591
- baggageValues.totalSteps,
592
- );
593
- }
594
- }
595
-
596
- // Apply custom attributes
597
- if (config.attributes) {
598
- for (const [key, value] of Object.entries(config.attributes)) {
599
- baseCtx.setAttribute(key, value);
600
- }
601
- }
602
-
603
- // Compensation data storage
604
- let compensationData: Record<string, unknown> | undefined;
605
-
606
- // Create extended context
607
- const stepCtx: DistributedStepContext = {
608
- ...baseCtx,
609
- workflowId: baggageValues?.workflowId ?? null,
610
- workflowName: baggageValues?.workflowName ?? null,
611
- stepName: config.name,
612
- stepIndex,
613
- isCompensation: config.isCompensation ?? false,
614
-
615
- getWorkflowBaggage(): WorkflowBaggageValues | null {
616
- return baggageValues ? { ...baggageValues } : null;
617
- },
618
-
619
- updateWorkflowBaggage(
620
- values: Partial<WorkflowBaggageValues>,
621
- ): void {
622
- if (baggageValues) {
623
- Object.assign(baggageValues, values);
624
- WorkflowBaggage.set(baseCtx, baggageValues);
625
- }
626
- },
627
-
628
- getWorkflowHeaders(): Record<string, string> {
629
- const headers: Record<string, string> = {};
630
- const ctx = context.active();
631
- propagation.inject(ctx, headers);
632
- return headers;
633
- },
634
-
635
- requiresCompensation(data?: Record<string, unknown>): void {
636
- compensationData = data;
637
- baseCtx.setAttribute('workflow.step.requires_compensation', true);
638
- emitCorrelatedEvent(
639
- baseCtx,
640
- 'workflow.step.compensation_registered',
641
- {
642
- 'workflow.step.name': config.name,
643
- ...(data && {
644
- 'workflow.step.compensation_data': JSON.stringify(data),
645
- }),
646
- },
647
- );
648
- },
649
- };
650
-
651
- // Call onStart callback
652
- config.onStart?.(stepCtx);
653
-
654
- // Add start event
655
- emitCorrelatedEvent(baseCtx, 'workflow.step.started', {
656
- 'workflow.step.name': config.name,
657
- ...(baggageValues && { 'workflow.id': baggageValues.workflowId }),
658
- });
659
-
660
- try {
661
- const userFn = fnFactory(stepCtx);
662
- const result = await userFn(...args);
663
-
664
- // Call onComplete callback
665
- config.onComplete?.(stepCtx, result);
666
-
667
- // Add completion event
668
- emitCorrelatedEvent(baseCtx, 'workflow.step.completed', {
669
- 'workflow.step.name': config.name,
670
- });
671
-
672
- return result;
673
- } catch (error) {
674
- // Call onError callback
675
- config.onError?.(stepCtx, error as Error);
676
-
677
- // Add error event with compensation info if registered
678
- emitCorrelatedEvent(baseCtx, 'workflow.step.failed', {
679
- 'workflow.step.name': config.name,
680
- 'workflow.step.error': (error as Error).message,
681
- ...(compensationData && {
682
- 'workflow.step.requires_compensation': true,
683
- }),
684
- });
685
-
686
- throw error;
687
- }
688
- };
689
- },
690
- );
691
- };
692
- }
693
-
694
- // ============================================================================
695
- // Utility Functions
696
- // ============================================================================
697
-
698
- /**
699
- * Generate a unique workflow ID
700
- *
701
- * @param prefix - Optional prefix for the ID
702
- * @returns A unique workflow ID
703
- *
704
- * @example
705
- * ```typescript
706
- * const workflowId = generateWorkflowId('order'); // "order-abc123def456"
707
- * ```
708
- */
709
- export function generateWorkflowId(prefix?: string): string {
710
- const random = Math.random().toString(36).slice(2, 15);
711
- const timestamp = Date.now().toString(36);
712
- const id = `${timestamp}-${random}`;
713
- return prefix ? `${prefix}-${id}` : id;
714
- }
715
-
716
- /**
717
- * Check if the current context is part of a distributed workflow
718
- *
719
- * @param ctx - The trace context
720
- * @returns True if workflow baggage is present
721
- */
722
- export function isInDistributedWorkflow(ctx: TraceContext): boolean {
723
- const baggage = WorkflowBaggage.get(ctx);
724
- return !!(baggage.workflowId && baggage.workflowName);
725
- }
726
-
727
- /**
728
- * Get workflow progress information
729
- *
730
- * @param ctx - The trace context
731
- * @returns Progress info or null if not in a workflow
732
- */
733
- export function getWorkflowProgress(ctx: TraceContext): {
734
- workflowId: string;
735
- workflowName: string;
736
- currentStep: string | null;
737
- currentStepIndex: number | null;
738
- totalSteps: number | null;
739
- percentComplete: number | null;
740
- } | null {
741
- const baggage = WorkflowBaggage.get(ctx);
742
- if (!baggage.workflowId || !baggage.workflowName) {
743
- return null;
744
- }
745
-
746
- const percentComplete =
747
- baggage.totalSteps && baggage.stepIndex !== undefined
748
- ? Math.round(((baggage.stepIndex + 1) / baggage.totalSteps) * 100)
749
- : null;
750
-
751
- return {
752
- workflowId: baggage.workflowId,
753
- workflowName: baggage.workflowName,
754
- currentStep: baggage.stepName ?? null,
755
- currentStepIndex: baggage.stepIndex ?? null,
756
- totalSteps: baggage.totalSteps ?? null,
757
- percentComplete,
758
- };
759
- }
760
-
761
- /**
762
- * Create workflow correlation headers for manual propagation
763
- *
764
- * Use when you need to manually add workflow context to outgoing requests.
765
- *
766
- * @param values - Workflow baggage values
767
- * @returns Headers object with workflow baggage
768
- *
769
- * @example
770
- * ```typescript
771
- * const headers = createWorkflowHeaders({
772
- * workflowId: 'order-123',
773
- * workflowName: 'OrderFulfillment',
774
- * stepIndex: 2,
775
- * });
776
- *
777
- * await fetch('/api/inventory', { headers });
778
- * ```
779
- */
780
- export function createWorkflowHeaders(
781
- values: Partial<WorkflowBaggageValues>,
782
- ): Record<string, string> {
783
- const headers: Record<string, string> = {};
784
-
785
- // Build baggage string
786
- const baggageEntries: string[] = [];
787
-
788
- if (values.workflowId) {
789
- baggageEntries.push(
790
- `workflow.workflowId=${encodeURIComponent(values.workflowId)}`,
791
- );
792
- }
793
- if (values.workflowName) {
794
- baggageEntries.push(
795
- `workflow.workflowName=${encodeURIComponent(values.workflowName)}`,
796
- );
797
- }
798
- if (values.workflowVersion) {
799
- baggageEntries.push(
800
- `workflow.workflowVersion=${encodeURIComponent(values.workflowVersion)}`,
801
- );
802
- }
803
- if (values.stepName) {
804
- baggageEntries.push(
805
- `workflow.stepName=${encodeURIComponent(values.stepName)}`,
806
- );
807
- }
808
- if (values.stepIndex !== undefined) {
809
- baggageEntries.push(`workflow.stepIndex=${values.stepIndex}`);
810
- }
811
- if (values.totalSteps !== undefined) {
812
- baggageEntries.push(`workflow.totalSteps=${values.totalSteps}`);
813
- }
814
- if (values.priority) {
815
- baggageEntries.push(`workflow.priority=${values.priority}`);
816
- }
817
- if (values.correlationId) {
818
- baggageEntries.push(
819
- `workflow.correlationId=${encodeURIComponent(values.correlationId)}`,
820
- );
821
- }
822
- if (values.parentWorkflowId) {
823
- baggageEntries.push(
824
- `workflow.parentWorkflowId=${encodeURIComponent(values.parentWorkflowId)}`,
825
- );
826
- }
827
- if (values.initiatedBy) {
828
- baggageEntries.push(
829
- `workflow.initiatedBy=${encodeURIComponent(values.initiatedBy)}`,
830
- );
831
- }
832
- if (values.startedAt) {
833
- baggageEntries.push(
834
- `workflow.startedAt=${encodeURIComponent(values.startedAt)}`,
835
- );
836
- }
837
-
838
- if (baggageEntries.length > 0) {
839
- headers['baggage'] = baggageEntries.join(',');
840
- }
841
-
842
- return headers;
843
- }
844
-
845
- /**
846
- * Parse workflow context from baggage header
847
- *
848
- * @param baggageHeader - The baggage header value
849
- * @returns Parsed workflow values or null
850
- */
851
- export function parseWorkflowFromBaggage(
852
- baggageHeader: string,
853
- ): Partial<WorkflowBaggageValues> | null {
854
- if (!baggageHeader) {
855
- return null;
856
- }
857
-
858
- const values: Partial<WorkflowBaggageValues> = {};
859
- const entries = baggageHeader.split(',');
860
-
861
- for (const entry of entries) {
862
- const [key, value] = entry.trim().split('=');
863
- if (!key || !value) continue;
864
-
865
- const decodedValue = decodeURIComponent(value);
866
-
867
- switch (key) {
868
- case 'workflow.workflowId': {
869
- values.workflowId = decodedValue;
870
- break;
871
- }
872
- case 'workflow.workflowName': {
873
- values.workflowName = decodedValue;
874
- break;
875
- }
876
- case 'workflow.workflowVersion': {
877
- values.workflowVersion = decodedValue;
878
- break;
879
- }
880
- case 'workflow.stepName': {
881
- values.stepName = decodedValue;
882
- break;
883
- }
884
- case 'workflow.stepIndex': {
885
- values.stepIndex = Number.parseInt(decodedValue, 10);
886
- break;
887
- }
888
- case 'workflow.totalSteps': {
889
- values.totalSteps = Number.parseInt(decodedValue, 10);
890
- break;
891
- }
892
- case 'workflow.priority': {
893
- values.priority = decodedValue as WorkflowBaggageValues['priority'];
894
- break;
895
- }
896
- case 'workflow.correlationId': {
897
- values.correlationId = decodedValue;
898
- break;
899
- }
900
- case 'workflow.parentWorkflowId': {
901
- values.parentWorkflowId = decodedValue;
902
- break;
903
- }
904
- case 'workflow.initiatedBy': {
905
- values.initiatedBy = decodedValue;
906
- break;
907
- }
908
- case 'workflow.startedAt': {
909
- values.startedAt = decodedValue;
910
- break;
911
- }
912
- }
913
- }
914
-
915
- return Object.keys(values).length > 0 ? values : null;
916
- }