ai-workflows 2.0.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -5
- package/.turbo/turbo-test.log +169 -0
- package/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +303 -184
- package/dist/barrier.d.ts +153 -0
- package/dist/barrier.d.ts.map +1 -0
- package/dist/barrier.js +339 -0
- package/dist/barrier.js.map +1 -0
- package/dist/cascade-context.d.ts +149 -0
- package/dist/cascade-context.d.ts.map +1 -0
- package/dist/cascade-context.js +324 -0
- package/dist/cascade-context.js.map +1 -0
- package/dist/cascade-executor.d.ts +196 -0
- package/dist/cascade-executor.d.ts.map +1 -0
- package/dist/cascade-executor.js +384 -0
- package/dist/cascade-executor.js.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +4 -1
- package/dist/context.js.map +1 -1
- package/dist/dependency-graph.d.ts +157 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/dependency-graph.js +382 -0
- package/dist/dependency-graph.js.map +1 -0
- package/dist/every.d.ts +31 -2
- package/dist/every.d.ts.map +1 -1
- package/dist/every.js +63 -32
- package/dist/every.js.map +1 -1
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +8 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/topological-sort.d.ts +121 -0
- package/dist/graph/topological-sort.d.ts.map +1 -0
- package/dist/graph/topological-sort.js +292 -0
- package/dist/graph/topological-sort.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/on.d.ts +35 -10
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +52 -18
- package/dist/on.js.map +1 -1
- package/dist/send.d.ts +0 -5
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +1 -14
- package/dist/send.js.map +1 -1
- package/dist/timer-registry.d.ts +52 -0
- package/dist/timer-registry.d.ts.map +1 -0
- package/dist/timer-registry.js +120 -0
- package/dist/timer-registry.js.map +1 -0
- package/dist/types.d.ts +171 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -1
- package/dist/types.js.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +22 -18
- package/dist/workflow.js.map +1 -1
- package/package.json +12 -16
- package/src/barrier.ts +466 -0
- package/src/cascade-context.ts +488 -0
- package/src/cascade-executor.ts +587 -0
- package/src/context.js +83 -0
- package/src/context.ts +12 -7
- package/src/dependency-graph.ts +518 -0
- package/src/every.js +267 -0
- package/src/every.ts +104 -35
- package/src/graph/index.ts +19 -0
- package/src/graph/topological-sort.ts +414 -0
- package/src/index.js +71 -0
- package/src/index.ts +78 -0
- package/src/on.js +79 -0
- package/src/on.ts +81 -25
- package/src/send.js +111 -0
- package/src/send.ts +1 -16
- package/src/timer-registry.ts +145 -0
- package/src/types.js +4 -0
- package/src/types.ts +218 -11
- package/src/workflow.js +455 -0
- package/src/workflow.ts +32 -23
- package/test/barrier-join.test.ts +434 -0
- package/test/barrier-unhandled-rejections.test.ts +359 -0
- package/test/cascade-context.test.ts +390 -0
- package/test/cascade-executor.test.ts +859 -0
- package/test/context.test.js +116 -0
- package/test/dependency-graph.test.ts +512 -0
- package/test/every.test.js +282 -0
- package/test/graph/topological-sort.test.ts +586 -0
- package/test/on.test.js +80 -0
- package/test/schedule-timer-cleanup.test.ts +344 -0
- package/test/send-race-conditions.test.ts +410 -0
- package/test/send.test.js +89 -0
- package/test/type-safety-every.test.ts +303 -0
- package/test/types-event-handler.test.ts +225 -0
- package/test/types-proxy-autocomplete.test.ts +345 -0
- package/test/workflow.test.js +224 -0
- package/vitest.config.js +7 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cascade Context - Correlation IDs and step metadata for workflow execution
|
|
3
|
+
*
|
|
4
|
+
* Provides distributed tracing support with:
|
|
5
|
+
* - Unique correlation IDs per cascade
|
|
6
|
+
* - Step timing metadata (started_at, completed_at, duration)
|
|
7
|
+
* - Cascade path recording
|
|
8
|
+
* - Context inheritance for nested operations
|
|
9
|
+
* - Serialization for distributed systems
|
|
10
|
+
* - W3C Trace Context compatibility
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Step status in the cascade
|
|
15
|
+
*/
|
|
16
|
+
export type StepStatus = 'running' | 'completed' | 'failed'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 5W+H Event structure
|
|
20
|
+
*/
|
|
21
|
+
export interface FiveWHEvent {
|
|
22
|
+
who: string
|
|
23
|
+
what: string
|
|
24
|
+
when: number
|
|
25
|
+
where: string
|
|
26
|
+
why?: string
|
|
27
|
+
how: {
|
|
28
|
+
duration?: number
|
|
29
|
+
status: StepStatus
|
|
30
|
+
metadata?: Record<string, unknown>
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Cascade step with timing and metadata
|
|
36
|
+
*/
|
|
37
|
+
export interface CascadeStep {
|
|
38
|
+
/** Step name */
|
|
39
|
+
name: string
|
|
40
|
+
/** When the step started (unix timestamp ms) */
|
|
41
|
+
startedAt: number
|
|
42
|
+
/** When the step completed (unix timestamp ms) */
|
|
43
|
+
completedAt?: number
|
|
44
|
+
/** Duration in milliseconds */
|
|
45
|
+
duration?: number
|
|
46
|
+
/** Current status */
|
|
47
|
+
status: StepStatus
|
|
48
|
+
/** Error if failed */
|
|
49
|
+
error?: Error
|
|
50
|
+
/** Additional metadata */
|
|
51
|
+
metadata?: Record<string, unknown>
|
|
52
|
+
/** Mark step as completed */
|
|
53
|
+
complete: () => void
|
|
54
|
+
/** Mark step as failed */
|
|
55
|
+
fail: (error: Error) => void
|
|
56
|
+
/** Add metadata to the step */
|
|
57
|
+
addMetadata: (data: Record<string, unknown>) => void
|
|
58
|
+
/** Convert step to 5W+H event format */
|
|
59
|
+
to5WHEvent: () => FiveWHEvent
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Serialized cascade step (for transmission/storage)
|
|
64
|
+
*/
|
|
65
|
+
export interface SerializedCascadeStep {
|
|
66
|
+
name: string
|
|
67
|
+
startedAt: number
|
|
68
|
+
completedAt?: number
|
|
69
|
+
duration?: number
|
|
70
|
+
status: StepStatus
|
|
71
|
+
metadata?: Record<string, unknown>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* W3C Trace Context
|
|
76
|
+
*/
|
|
77
|
+
export interface TraceContext {
|
|
78
|
+
traceparent: string
|
|
79
|
+
tracestate?: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Serialized cascade context
|
|
84
|
+
*/
|
|
85
|
+
export interface SerializedCascadeContext {
|
|
86
|
+
correlationId: string
|
|
87
|
+
spanId: string
|
|
88
|
+
parentId?: string
|
|
89
|
+
name?: string
|
|
90
|
+
depth: number
|
|
91
|
+
steps: SerializedCascadeStep[]
|
|
92
|
+
path: string[]
|
|
93
|
+
createdAt: number
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Cascade context for tracking workflow execution
|
|
98
|
+
*/
|
|
99
|
+
export interface CascadeContext {
|
|
100
|
+
/** Unique correlation ID for this cascade (propagates to children) */
|
|
101
|
+
correlationId: string
|
|
102
|
+
/** Unique span ID for this context */
|
|
103
|
+
spanId: string
|
|
104
|
+
/** Parent span ID (if this is a child context) */
|
|
105
|
+
parentId?: string
|
|
106
|
+
/** Name of this cascade/context */
|
|
107
|
+
name?: string
|
|
108
|
+
/** Depth in the context hierarchy (0 = root) */
|
|
109
|
+
depth: number
|
|
110
|
+
/** Steps recorded in this context */
|
|
111
|
+
steps: CascadeStep[]
|
|
112
|
+
/** Path of step names in this context */
|
|
113
|
+
path: string[]
|
|
114
|
+
/** Full path including parent context steps */
|
|
115
|
+
fullPath: string[]
|
|
116
|
+
/** When the context was created */
|
|
117
|
+
createdAt: number
|
|
118
|
+
/** Parent context reference */
|
|
119
|
+
parent?: CascadeContext
|
|
120
|
+
/** Serialize context for transmission */
|
|
121
|
+
serialize: () => SerializedCascadeContext
|
|
122
|
+
/** Format context as readable string */
|
|
123
|
+
format: () => string
|
|
124
|
+
/** Format as tree including parent contexts */
|
|
125
|
+
formatTree: () => string
|
|
126
|
+
/** Convert to W3C Trace Context format */
|
|
127
|
+
toTraceContext: () => TraceContext
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Options for creating a cascade context
|
|
132
|
+
*/
|
|
133
|
+
export interface CascadeContextOptions {
|
|
134
|
+
/** Custom correlation ID (auto-generated if not provided) */
|
|
135
|
+
correlationId?: string
|
|
136
|
+
/** Parent context (for nested operations) */
|
|
137
|
+
parent?: CascadeContext
|
|
138
|
+
/** Name for this cascade */
|
|
139
|
+
name?: string
|
|
140
|
+
/** Restore from serialized format */
|
|
141
|
+
fromSerialized?: SerializedCascadeContext
|
|
142
|
+
/** Restore from W3C trace context */
|
|
143
|
+
fromTraceContext?: TraceContext
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate a UUID v4
|
|
148
|
+
*/
|
|
149
|
+
function generateUUID(): string {
|
|
150
|
+
// Use crypto.randomUUID if available, otherwise fallback
|
|
151
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
152
|
+
return crypto.randomUUID()
|
|
153
|
+
}
|
|
154
|
+
// Fallback UUID v4 generation
|
|
155
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
156
|
+
const r = (Math.random() * 16) | 0
|
|
157
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
|
158
|
+
return v.toString(16)
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate a span ID (16 hex characters)
|
|
164
|
+
*/
|
|
165
|
+
function generateSpanId(): string {
|
|
166
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
167
|
+
return crypto.randomUUID().replace(/-/g, '').slice(0, 16)
|
|
168
|
+
}
|
|
169
|
+
return 'xxxxxxxxxxxxxxxx'.replace(/x/g, () => {
|
|
170
|
+
return ((Math.random() * 16) | 0).toString(16)
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Parse W3C traceparent header
|
|
176
|
+
*/
|
|
177
|
+
function parseTraceparent(traceparent: string): { traceId: string; parentId: string } | null {
|
|
178
|
+
const parts = traceparent.split('-')
|
|
179
|
+
if (parts.length !== 4) return null
|
|
180
|
+
return {
|
|
181
|
+
traceId: parts[1]!, // 32 hex chars
|
|
182
|
+
parentId: parts[2]!, // 16 hex chars
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Convert correlation ID to trace ID format (32 hex chars)
|
|
188
|
+
*/
|
|
189
|
+
function correlationIdToTraceId(correlationId: string): string {
|
|
190
|
+
// Remove dashes and pad/truncate to 32 chars
|
|
191
|
+
const hex = correlationId.replace(/-/g, '')
|
|
192
|
+
return hex.slice(0, 32).padEnd(32, '0')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Convert trace ID to correlation ID format (UUID)
|
|
197
|
+
*/
|
|
198
|
+
function traceIdToCorrelationId(traceId: string): string {
|
|
199
|
+
// Convert 32 hex chars to UUID format
|
|
200
|
+
return `${traceId.slice(0, 8)}-${traceId.slice(8, 12)}-${traceId.slice(12, 16)}-${traceId.slice(16, 20)}-${traceId.slice(20, 32)}`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a cascade context for tracking workflow execution
|
|
205
|
+
*/
|
|
206
|
+
export function createCascadeContext(options: CascadeContextOptions = {}): CascadeContext {
|
|
207
|
+
const { parent, name, fromSerialized, fromTraceContext } = options
|
|
208
|
+
|
|
209
|
+
// Handle restoration from serialized format
|
|
210
|
+
if (fromSerialized) {
|
|
211
|
+
const restoredSteps: CascadeStep[] = fromSerialized.steps.map((s) => createRestoredStep(s, name))
|
|
212
|
+
|
|
213
|
+
const ctx: CascadeContext = {
|
|
214
|
+
correlationId: fromSerialized.correlationId,
|
|
215
|
+
spanId: fromSerialized.spanId,
|
|
216
|
+
parentId: fromSerialized.parentId,
|
|
217
|
+
name: fromSerialized.name,
|
|
218
|
+
depth: fromSerialized.depth,
|
|
219
|
+
steps: restoredSteps,
|
|
220
|
+
path: fromSerialized.path,
|
|
221
|
+
createdAt: fromSerialized.createdAt,
|
|
222
|
+
get fullPath() {
|
|
223
|
+
return this.path
|
|
224
|
+
},
|
|
225
|
+
serialize: () => serializeContext(ctx),
|
|
226
|
+
format: () => formatContext(ctx),
|
|
227
|
+
formatTree: () => formatContextTree(ctx),
|
|
228
|
+
toTraceContext: () => toTraceContext(ctx),
|
|
229
|
+
}
|
|
230
|
+
return ctx
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Handle restoration from W3C trace context
|
|
234
|
+
if (fromTraceContext) {
|
|
235
|
+
const parsed = parseTraceparent(fromTraceContext.traceparent)
|
|
236
|
+
if (parsed) {
|
|
237
|
+
const correlationId = traceIdToCorrelationId(parsed.traceId)
|
|
238
|
+
const newSpanId = generateSpanId()
|
|
239
|
+
|
|
240
|
+
const ctx: CascadeContext = {
|
|
241
|
+
correlationId,
|
|
242
|
+
spanId: newSpanId,
|
|
243
|
+
parentId: parsed.parentId,
|
|
244
|
+
name,
|
|
245
|
+
depth: 0,
|
|
246
|
+
steps: [],
|
|
247
|
+
path: [],
|
|
248
|
+
createdAt: Date.now(),
|
|
249
|
+
get fullPath() {
|
|
250
|
+
return this.path
|
|
251
|
+
},
|
|
252
|
+
serialize: () => serializeContext(ctx),
|
|
253
|
+
format: () => formatContext(ctx),
|
|
254
|
+
formatTree: () => formatContextTree(ctx),
|
|
255
|
+
toTraceContext: () => toTraceContext(ctx),
|
|
256
|
+
}
|
|
257
|
+
return ctx
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Determine correlation ID
|
|
262
|
+
let correlationId: string
|
|
263
|
+
if (parent) {
|
|
264
|
+
correlationId = parent.correlationId
|
|
265
|
+
} else if (options.correlationId) {
|
|
266
|
+
correlationId = options.correlationId
|
|
267
|
+
} else {
|
|
268
|
+
correlationId = generateUUID()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const spanId = generateSpanId()
|
|
272
|
+
const parentId = parent?.spanId
|
|
273
|
+
const depth = parent ? parent.depth + 1 : 0
|
|
274
|
+
const createdAt = Date.now()
|
|
275
|
+
|
|
276
|
+
const steps: CascadeStep[] = []
|
|
277
|
+
const path: string[] = []
|
|
278
|
+
|
|
279
|
+
const ctx: CascadeContext = {
|
|
280
|
+
correlationId,
|
|
281
|
+
spanId,
|
|
282
|
+
parentId,
|
|
283
|
+
name,
|
|
284
|
+
depth,
|
|
285
|
+
steps,
|
|
286
|
+
path,
|
|
287
|
+
createdAt,
|
|
288
|
+
parent,
|
|
289
|
+
get fullPath() {
|
|
290
|
+
if (this.parent) {
|
|
291
|
+
return [...this.parent.fullPath, ...this.path]
|
|
292
|
+
}
|
|
293
|
+
return this.path
|
|
294
|
+
},
|
|
295
|
+
serialize: () => serializeContext(ctx),
|
|
296
|
+
format: () => formatContext(ctx),
|
|
297
|
+
formatTree: () => formatContextTree(ctx),
|
|
298
|
+
toTraceContext: () => toTraceContext(ctx),
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return ctx
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Create a restored step from serialized data
|
|
306
|
+
*/
|
|
307
|
+
function createRestoredStep(serialized: SerializedCascadeStep, contextName?: string): CascadeStep {
|
|
308
|
+
const step: CascadeStep = {
|
|
309
|
+
name: serialized.name,
|
|
310
|
+
startedAt: serialized.startedAt,
|
|
311
|
+
completedAt: serialized.completedAt,
|
|
312
|
+
duration: serialized.duration,
|
|
313
|
+
status: serialized.status,
|
|
314
|
+
metadata: serialized.metadata ? { ...serialized.metadata } : undefined,
|
|
315
|
+
complete: () => {
|
|
316
|
+
step.status = 'completed'
|
|
317
|
+
step.completedAt = Date.now()
|
|
318
|
+
step.duration = step.completedAt - step.startedAt
|
|
319
|
+
},
|
|
320
|
+
fail: (error: Error) => {
|
|
321
|
+
step.status = 'failed'
|
|
322
|
+
step.error = error
|
|
323
|
+
step.completedAt = Date.now()
|
|
324
|
+
step.duration = step.completedAt - step.startedAt
|
|
325
|
+
},
|
|
326
|
+
addMetadata: (data: Record<string, unknown>) => {
|
|
327
|
+
step.metadata = { ...step.metadata, ...data }
|
|
328
|
+
},
|
|
329
|
+
to5WHEvent: () => ({
|
|
330
|
+
who: (step.metadata?.actor as string) || 'system',
|
|
331
|
+
what: (step.metadata?.action as string) || step.name,
|
|
332
|
+
when: step.startedAt,
|
|
333
|
+
where: contextName || 'unknown',
|
|
334
|
+
why: step.metadata?.reason as string,
|
|
335
|
+
how: {
|
|
336
|
+
duration: step.duration,
|
|
337
|
+
status: step.status,
|
|
338
|
+
metadata: step.metadata,
|
|
339
|
+
},
|
|
340
|
+
}),
|
|
341
|
+
}
|
|
342
|
+
return step
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Record a step in the cascade context
|
|
347
|
+
*/
|
|
348
|
+
export function recordStep(
|
|
349
|
+
ctx: CascadeContext,
|
|
350
|
+
name: string,
|
|
351
|
+
metadata?: Record<string, unknown>
|
|
352
|
+
): CascadeStep {
|
|
353
|
+
const startedAt = Date.now()
|
|
354
|
+
|
|
355
|
+
const step: CascadeStep = {
|
|
356
|
+
name,
|
|
357
|
+
startedAt,
|
|
358
|
+
status: 'running',
|
|
359
|
+
metadata: metadata ? { ...metadata } : undefined,
|
|
360
|
+
complete: () => {
|
|
361
|
+
step.status = 'completed'
|
|
362
|
+
step.completedAt = Date.now()
|
|
363
|
+
step.duration = step.completedAt - step.startedAt
|
|
364
|
+
ctx.path.push(name)
|
|
365
|
+
},
|
|
366
|
+
fail: (error: Error) => {
|
|
367
|
+
step.status = 'failed'
|
|
368
|
+
step.error = error
|
|
369
|
+
step.completedAt = Date.now()
|
|
370
|
+
step.duration = step.completedAt - step.startedAt
|
|
371
|
+
},
|
|
372
|
+
addMetadata: (data: Record<string, unknown>) => {
|
|
373
|
+
step.metadata = { ...step.metadata, ...data }
|
|
374
|
+
},
|
|
375
|
+
to5WHEvent: () => ({
|
|
376
|
+
who: (step.metadata?.actor as string) || 'system',
|
|
377
|
+
what: (step.metadata?.action as string) || name,
|
|
378
|
+
when: startedAt,
|
|
379
|
+
where: ctx.name || 'cascade',
|
|
380
|
+
why: step.metadata?.reason as string,
|
|
381
|
+
how: {
|
|
382
|
+
duration: step.duration,
|
|
383
|
+
status: step.status,
|
|
384
|
+
metadata: step.metadata,
|
|
385
|
+
},
|
|
386
|
+
}),
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
ctx.steps.push(step)
|
|
390
|
+
return step
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Execute a callback with cascade context
|
|
395
|
+
*/
|
|
396
|
+
export async function withCascadeContext<T>(
|
|
397
|
+
callback: (ctx: CascadeContext) => Promise<T>,
|
|
398
|
+
options: CascadeContextOptions = {}
|
|
399
|
+
): Promise<T> {
|
|
400
|
+
const ctx = createCascadeContext(options)
|
|
401
|
+
return callback(ctx)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Serialize context for transmission/storage
|
|
406
|
+
*/
|
|
407
|
+
function serializeContext(ctx: CascadeContext): SerializedCascadeContext {
|
|
408
|
+
return {
|
|
409
|
+
correlationId: ctx.correlationId,
|
|
410
|
+
spanId: ctx.spanId,
|
|
411
|
+
parentId: ctx.parentId,
|
|
412
|
+
name: ctx.name,
|
|
413
|
+
depth: ctx.depth,
|
|
414
|
+
steps: ctx.steps.map((step) => ({
|
|
415
|
+
name: step.name,
|
|
416
|
+
startedAt: step.startedAt,
|
|
417
|
+
completedAt: step.completedAt,
|
|
418
|
+
duration: step.duration,
|
|
419
|
+
status: step.status,
|
|
420
|
+
metadata: step.metadata,
|
|
421
|
+
})),
|
|
422
|
+
path: ctx.path,
|
|
423
|
+
createdAt: ctx.createdAt,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Format context as readable string
|
|
429
|
+
*/
|
|
430
|
+
function formatContext(ctx: CascadeContext): string {
|
|
431
|
+
const lines: string[] = []
|
|
432
|
+
lines.push(`Cascade: ${ctx.name || 'unnamed'}`)
|
|
433
|
+
lines.push(` Correlation ID: ${ctx.correlationId}`)
|
|
434
|
+
lines.push(` Span ID: ${ctx.spanId}`)
|
|
435
|
+
if (ctx.parentId) {
|
|
436
|
+
lines.push(` Parent ID: ${ctx.parentId}`)
|
|
437
|
+
}
|
|
438
|
+
lines.push(` Depth: ${ctx.depth}`)
|
|
439
|
+
lines.push(` Steps:`)
|
|
440
|
+
for (const step of ctx.steps) {
|
|
441
|
+
const status = step.status === 'completed' ? '[OK]' : step.status === 'failed' ? '[FAIL]' : '[...]'
|
|
442
|
+
const duration = step.duration ? ` (${step.duration}ms)` : ''
|
|
443
|
+
lines.push(` ${status} ${step.name}${duration}`)
|
|
444
|
+
}
|
|
445
|
+
return lines.join('\n')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Format context as tree including parent contexts
|
|
450
|
+
*/
|
|
451
|
+
function formatContextTree(ctx: CascadeContext): string {
|
|
452
|
+
const lines: string[] = []
|
|
453
|
+
|
|
454
|
+
// Build tree from root
|
|
455
|
+
const contexts: CascadeContext[] = []
|
|
456
|
+
let current: CascadeContext | undefined = ctx
|
|
457
|
+
while (current) {
|
|
458
|
+
contexts.unshift(current)
|
|
459
|
+
current = current.parent
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for (let i = 0; i < contexts.length; i++) {
|
|
463
|
+
const context = contexts[i]!
|
|
464
|
+
const indent = ' '.repeat(i)
|
|
465
|
+
lines.push(`${indent}${context.name || 'cascade'} (depth: ${context.depth})`)
|
|
466
|
+
for (const step of context.steps) {
|
|
467
|
+
const status = step.status === 'completed' ? '[OK]' : step.status === 'failed' ? '[FAIL]' : '[...]'
|
|
468
|
+
const duration = step.duration ? ` (${step.duration}ms)` : ''
|
|
469
|
+
lines.push(`${indent} ${status} ${step.name}${duration}`)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return lines.join('\n')
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Convert to W3C Trace Context format
|
|
478
|
+
*/
|
|
479
|
+
function toTraceContext(ctx: CascadeContext): TraceContext {
|
|
480
|
+
const version = '00'
|
|
481
|
+
const traceId = correlationIdToTraceId(ctx.correlationId)
|
|
482
|
+
const spanId = ctx.spanId.padEnd(16, '0').slice(0, 16)
|
|
483
|
+
const flags = '01' // sampled
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
traceparent: `${version}-${traceId}-${spanId}-${flags}`,
|
|
487
|
+
}
|
|
488
|
+
}
|