duron 0.3.0-beta.9 → 0.3.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.
- package/dist/action-job.d.ts +33 -2
- package/dist/action-job.d.ts.map +1 -1
- package/dist/action-job.js +88 -23
- package/dist/action-manager.d.ts +44 -2
- package/dist/action-manager.d.ts.map +1 -1
- package/dist/action-manager.js +64 -3
- package/dist/action.d.ts +388 -7
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +44 -23
- package/dist/adapters/adapter.d.ts +365 -8
- package/dist/adapters/adapter.d.ts.map +1 -1
- package/dist/adapters/adapter.js +221 -15
- package/dist/adapters/postgres/base.d.ts +184 -6
- package/dist/adapters/postgres/base.d.ts.map +1 -1
- package/dist/adapters/postgres/base.js +436 -75
- package/dist/adapters/postgres/pglite.d.ts +37 -0
- package/dist/adapters/postgres/pglite.d.ts.map +1 -1
- package/dist/adapters/postgres/pglite.js +38 -0
- package/dist/adapters/postgres/postgres.d.ts +35 -0
- package/dist/adapters/postgres/postgres.d.ts.map +1 -1
- package/dist/adapters/postgres/postgres.js +42 -0
- package/dist/adapters/postgres/schema.d.ts +150 -37
- package/dist/adapters/postgres/schema.d.ts.map +1 -1
- package/dist/adapters/postgres/schema.default.d.ts +151 -38
- package/dist/adapters/postgres/schema.default.d.ts.map +1 -1
- package/dist/adapters/postgres/schema.default.js +2 -2
- package/dist/adapters/postgres/schema.js +60 -23
- package/dist/adapters/schemas.d.ts +124 -80
- package/dist/adapters/schemas.d.ts.map +1 -1
- package/dist/adapters/schemas.js +139 -26
- package/dist/client.d.ts +426 -22
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +370 -20
- package/dist/constants.js +6 -0
- package/dist/errors.d.ts +140 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +152 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/server.d.ts +99 -37
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +84 -25
- package/dist/step-manager.d.ts +111 -4
- package/dist/step-manager.d.ts.map +1 -1
- package/dist/step-manager.js +403 -75
- package/dist/telemetry/index.d.ts +1 -4
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +2 -4
- package/dist/telemetry/local-span-exporter.d.ts +56 -0
- package/dist/telemetry/local-span-exporter.d.ts.map +1 -0
- package/dist/telemetry/local-span-exporter.js +118 -0
- package/dist/utils/p-retry.d.ts +5 -0
- package/dist/utils/p-retry.d.ts.map +1 -1
- package/dist/utils/p-retry.js +8 -0
- package/dist/utils/wait-for-abort.d.ts +1 -0
- package/dist/utils/wait-for-abort.d.ts.map +1 -1
- package/dist/utils/wait-for-abort.js +1 -0
- package/migrations/postgres/{20260119153838_flimsy_thor_girl → 20260121160012_normal_bloodstrike}/migration.sql +32 -20
- package/migrations/postgres/{20260119153838_flimsy_thor_girl → 20260121160012_normal_bloodstrike}/snapshot.json +241 -66
- package/package.json +42 -26
- package/src/action-job.ts +33 -29
- package/src/action-manager.ts +5 -5
- package/src/action.ts +317 -149
- package/src/adapters/adapter.ts +54 -54
- package/src/adapters/postgres/base.ts +266 -86
- package/src/adapters/postgres/schema.default.ts +2 -2
- package/src/adapters/postgres/schema.ts +52 -24
- package/src/adapters/schemas.ts +91 -36
- package/src/client.ts +322 -68
- package/src/errors.ts +84 -12
- package/src/index.ts +2 -0
- package/src/server.ts +39 -37
- package/src/step-manager.ts +246 -95
- package/src/telemetry/index.ts +2 -20
- package/src/telemetry/local-span-exporter.ts +148 -0
- package/dist/telemetry/adapter.d.ts +0 -107
- package/dist/telemetry/adapter.d.ts.map +0 -1
- package/dist/telemetry/adapter.js +0 -134
- package/dist/telemetry/local.d.ts +0 -22
- package/dist/telemetry/local.d.ts.map +0 -1
- package/dist/telemetry/local.js +0 -243
- package/dist/telemetry/noop.d.ts +0 -17
- package/dist/telemetry/noop.d.ts.map +0 -1
- package/dist/telemetry/noop.js +0 -66
- package/dist/telemetry/opentelemetry.d.ts +0 -25
- package/dist/telemetry/opentelemetry.d.ts.map +0 -1
- package/dist/telemetry/opentelemetry.js +0 -312
- package/src/telemetry/adapter.ts +0 -642
- package/src/telemetry/local.ts +0 -429
- package/src/telemetry/noop.ts +0 -141
- package/src/telemetry/opentelemetry.ts +0 -453
package/src/step-manager.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Context,
|
|
3
|
+
context,
|
|
4
|
+
type Span,
|
|
5
|
+
SpanKind,
|
|
6
|
+
type SpanOptions,
|
|
7
|
+
SpanStatusCode,
|
|
8
|
+
type Tracer,
|
|
9
|
+
trace,
|
|
10
|
+
} from '@opentelemetry/api'
|
|
1
11
|
import fastq from 'fastq'
|
|
2
12
|
import type { Logger } from 'pino'
|
|
3
13
|
import type { z } from 'zod'
|
|
@@ -24,45 +34,118 @@ import {
|
|
|
24
34
|
serializeError,
|
|
25
35
|
UnhandledChildStepsError,
|
|
26
36
|
} from './errors.js'
|
|
27
|
-
import type { ObserveContext, Span, TelemetryAdapter, Tracer, TracerSpan } from './telemetry/adapter.js'
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Telemetry context provided to action and step handlers.
|
|
40
|
+
* Provides access to OpenTelemetry APIs for recording traces and metrics.
|
|
41
|
+
*/
|
|
42
|
+
export interface TelemetryContext {
|
|
43
|
+
/**
|
|
44
|
+
* Get the active OpenTelemetry span for the current job/step.
|
|
45
|
+
*/
|
|
46
|
+
getActiveSpan(): Span | undefined
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Get an OpenTelemetry tracer for creating custom spans.
|
|
50
|
+
*/
|
|
51
|
+
getTracer(name: string): Tracer
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Start a new span as a child of the current job/step span.
|
|
55
|
+
* This is a convenience method that properly links the span to the trace hierarchy.
|
|
56
|
+
*/
|
|
57
|
+
startSpan(name: string, options?: { attributes?: Record<string, any> }): Span
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Record a custom metric as a span event.
|
|
61
|
+
*/
|
|
62
|
+
recordMetric(name: string, value: number, attributes?: Record<string, any>): void
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Inject parent span into a context if we have one.
|
|
67
|
+
*/
|
|
68
|
+
function injectParentSpan(ctx: Context, parentSpan: Span | null): Context {
|
|
69
|
+
return parentSpan ? trace.setSpan(ctx, parentSpan) : ctx
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a context-aware tracer wrapper that automatically injects the parent span.
|
|
74
|
+
* This ensures spans created by external libraries (like AI SDK) are properly linked
|
|
75
|
+
* to the current job/step trace hierarchy.
|
|
76
|
+
*/
|
|
77
|
+
function createContextAwareTracer(tracer: Tracer, parentSpan: Span | null): Tracer {
|
|
78
|
+
return {
|
|
79
|
+
startSpan(name: string, options?: SpanOptions, ctx?: Context): Span {
|
|
80
|
+
// Always inject our parent span into the context, regardless of what context is passed.
|
|
81
|
+
// This is necessary because without global registration, context.active() returns
|
|
82
|
+
// ROOT_CONTEXT, so external libraries (like AI SDK) that pass context.active()
|
|
83
|
+
// would otherwise create orphan spans.
|
|
84
|
+
const baseContext = ctx ?? context.active()
|
|
85
|
+
const effectiveContext = injectParentSpan(baseContext, parentSpan)
|
|
86
|
+
return tracer.startSpan(name, options, effectiveContext)
|
|
87
|
+
},
|
|
88
|
+
// startActiveSpan has multiple overloads, we need to handle them all
|
|
89
|
+
startActiveSpan<F extends (span: Span) => unknown>(
|
|
90
|
+
name: string,
|
|
91
|
+
optionsOrFn: SpanOptions | F,
|
|
92
|
+
ctxOrFn?: Context | F,
|
|
93
|
+
fn?: F,
|
|
94
|
+
): ReturnType<F> {
|
|
95
|
+
// Parse the overloaded arguments
|
|
96
|
+
let options: SpanOptions | undefined
|
|
97
|
+
let ctx: Context | undefined
|
|
98
|
+
let callback: F
|
|
99
|
+
|
|
100
|
+
if (typeof optionsOrFn === 'function') {
|
|
101
|
+
// startActiveSpan(name, fn)
|
|
102
|
+
callback = optionsOrFn
|
|
103
|
+
} else if (typeof ctxOrFn === 'function') {
|
|
104
|
+
// startActiveSpan(name, options, fn)
|
|
105
|
+
options = optionsOrFn
|
|
106
|
+
callback = ctxOrFn
|
|
107
|
+
} else {
|
|
108
|
+
// startActiveSpan(name, options, context, fn)
|
|
109
|
+
options = optionsOrFn
|
|
110
|
+
ctx = ctxOrFn
|
|
111
|
+
callback = fn!
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const baseContext = ctx ?? context.active()
|
|
115
|
+
const effectiveContext = injectParentSpan(baseContext, parentSpan)
|
|
116
|
+
|
|
117
|
+
return tracer.startActiveSpan(name, options ?? {}, effectiveContext, callback)
|
|
118
|
+
},
|
|
119
|
+
} as Tracer
|
|
58
120
|
}
|
|
59
121
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Create a TelemetryContext that wraps an OTel span.
|
|
124
|
+
*/
|
|
125
|
+
function createTelemetryContext(span: Span | null, tracer: Tracer): TelemetryContext {
|
|
126
|
+
return {
|
|
127
|
+
getActiveSpan(): Span | undefined {
|
|
128
|
+
return span ?? undefined
|
|
129
|
+
},
|
|
130
|
+
getTracer(_name: string): Tracer {
|
|
131
|
+
// Return a context-aware tracer that automatically links spans to the current trace
|
|
132
|
+
return createContextAwareTracer(tracer, span)
|
|
133
|
+
},
|
|
134
|
+
startSpan(name: string, options?: { attributes?: Record<string, any> }): Span {
|
|
135
|
+
// Create a child span linked to the current span (job or step)
|
|
136
|
+
const parentContext = span ? trace.setSpan(context.active(), span) : context.active()
|
|
137
|
+
return tracer.startSpan(name, { attributes: options?.attributes }, parentContext)
|
|
138
|
+
},
|
|
139
|
+
recordMetric(name: string, value: number, attributes?: Record<string, any>): void {
|
|
140
|
+
if (span) {
|
|
141
|
+
span.addEvent(`metric:${name}`, {
|
|
142
|
+
'metric.value': value,
|
|
143
|
+
...attributes,
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
}
|
|
66
149
|
|
|
67
150
|
import pRetry from './utils/p-retry.js'
|
|
68
151
|
import waitForAbort from './utils/wait-for-abort.js'
|
|
@@ -172,7 +255,7 @@ export interface StepManagerOptions {
|
|
|
172
255
|
jobId: string
|
|
173
256
|
actionName: string
|
|
174
257
|
adapter: Adapter
|
|
175
|
-
|
|
258
|
+
tracer: Tracer
|
|
176
259
|
logger: Logger
|
|
177
260
|
concurrencyLimit: number
|
|
178
261
|
}
|
|
@@ -185,7 +268,7 @@ export class StepManager {
|
|
|
185
268
|
#jobId: string
|
|
186
269
|
#actionName: string
|
|
187
270
|
#stepStore: StepStore
|
|
188
|
-
#
|
|
271
|
+
#tracer: Tracer
|
|
189
272
|
#queue: fastq.queueAsPromised<TaskStep, any>
|
|
190
273
|
#logger: Logger
|
|
191
274
|
// each step name should be executed only once per parent (name + parentStepId)
|
|
@@ -193,7 +276,7 @@ export class StepManager {
|
|
|
193
276
|
// Store step spans for nested step tracking
|
|
194
277
|
#stepSpans = new Map<string, Span>()
|
|
195
278
|
// Store the job span for creating step spans
|
|
196
|
-
#jobSpan
|
|
279
|
+
#jobSpan!: Span
|
|
197
280
|
// Factory function to create run functions with the correct parent step ID and abort signal
|
|
198
281
|
#runFnFactory: ((parentStepId: string | null, abortSignal: AbortSignal) => StepHandlerContext['run']) | null = null
|
|
199
282
|
|
|
@@ -210,7 +293,7 @@ export class StepManager {
|
|
|
210
293
|
this.#jobId = options.jobId
|
|
211
294
|
this.#actionName = options.actionName
|
|
212
295
|
this.#logger = options.logger
|
|
213
|
-
this.#
|
|
296
|
+
this.#tracer = options.tracer
|
|
214
297
|
this.#stepStore = new StepStore(options.adapter)
|
|
215
298
|
this.#queue = fastq.promise(async (task: TaskStep) => {
|
|
216
299
|
// Create composite key: name + parentStepId (allows same name under different parents)
|
|
@@ -254,7 +337,6 @@ export class StepManager {
|
|
|
254
337
|
* @param variables - Variables available to the action
|
|
255
338
|
* @param abortSignal - Abort signal for cancelling the action
|
|
256
339
|
* @param logger - Pino child logger for this job
|
|
257
|
-
* @param observeContext - Observability context for telemetry
|
|
258
340
|
* @returns ActionHandlerContext instance
|
|
259
341
|
*/
|
|
260
342
|
createActionContext<TInput extends z.ZodObject, TOutput extends z.ZodObject, TVariables = Record<string, unknown>>(
|
|
@@ -263,38 +345,21 @@ export class StepManager {
|
|
|
263
345
|
variables: TVariables,
|
|
264
346
|
abortSignal: AbortSignal,
|
|
265
347
|
logger: Logger,
|
|
266
|
-
observeContext: ObserveContext,
|
|
267
348
|
): ActionHandlerContext<TInput, TVariables> {
|
|
268
|
-
|
|
349
|
+
const telemetryContext = createTelemetryContext(this.#jobSpan, this.#tracer)
|
|
350
|
+
return new ActionContext(this, job, action, variables, abortSignal, logger, telemetryContext)
|
|
269
351
|
}
|
|
270
352
|
|
|
271
353
|
/**
|
|
272
|
-
* Create
|
|
354
|
+
* Create a telemetry context for a step.
|
|
273
355
|
*/
|
|
274
|
-
|
|
356
|
+
createStepTelemetryContext(stepId: string): TelemetryContext {
|
|
275
357
|
const stepSpan = this.#stepSpans.get(stepId)
|
|
276
358
|
if (stepSpan) {
|
|
277
|
-
return
|
|
359
|
+
return createTelemetryContext(stepSpan, this.#tracer)
|
|
278
360
|
}
|
|
279
361
|
// Fallback to job span if step span not found
|
|
280
|
-
|
|
281
|
-
return this.#telemetry.createObserveContext(this.#jobId, stepId, this.#jobSpan)
|
|
282
|
-
}
|
|
283
|
-
// No-op observe context
|
|
284
|
-
return {
|
|
285
|
-
recordMetric: () => {
|
|
286
|
-
// No-op
|
|
287
|
-
},
|
|
288
|
-
addSpanAttribute: () => {
|
|
289
|
-
// No-op
|
|
290
|
-
},
|
|
291
|
-
addSpanEvent: () => {
|
|
292
|
-
// No-op
|
|
293
|
-
},
|
|
294
|
-
getTracer: (name: string) => {
|
|
295
|
-
return createNoopTracer(name)
|
|
296
|
-
},
|
|
297
|
-
}
|
|
362
|
+
return createTelemetryContext(this.#jobSpan, this.#tracer)
|
|
298
363
|
}
|
|
299
364
|
|
|
300
365
|
/**
|
|
@@ -381,15 +446,22 @@ export class StepManager {
|
|
|
381
446
|
|
|
382
447
|
step = newStep
|
|
383
448
|
|
|
384
|
-
// Start step
|
|
449
|
+
// Start step span - uses no-op tracer if no SDK is configured
|
|
385
450
|
const parentSpan = parentStepId ? this.#stepSpans.get(parentStepId) : this.#jobSpan
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
451
|
+
const parentContext = parentSpan ? trace.setSpan(context.active(), parentSpan) : context.active()
|
|
452
|
+
const stepSpan = this.#tracer.startSpan(
|
|
453
|
+
`step:${name}`,
|
|
454
|
+
{
|
|
455
|
+
kind: SpanKind.INTERNAL,
|
|
456
|
+
attributes: {
|
|
457
|
+
'duron.job.id': this.#jobId,
|
|
458
|
+
'duron.step.id': step.id,
|
|
459
|
+
'duron.step.name': name,
|
|
460
|
+
'duron.step.parent_id': parentStepId ?? undefined,
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
parentContext,
|
|
464
|
+
)
|
|
393
465
|
this.#stepSpans.set(step.id, stepSpan)
|
|
394
466
|
|
|
395
467
|
if (abortSignal.aborted) {
|
|
@@ -427,7 +499,11 @@ export class StepManager {
|
|
|
427
499
|
// Create abort controller for this step's timeout
|
|
428
500
|
const stepAbortController = new AbortController()
|
|
429
501
|
const timeoutId = setTimeout(() => {
|
|
430
|
-
const timeoutError = new StepTimeoutError(name, this.#jobId, expire
|
|
502
|
+
const timeoutError = new StepTimeoutError(name, this.#jobId, expire, {
|
|
503
|
+
stepId: step?.id,
|
|
504
|
+
parentStepId,
|
|
505
|
+
actionName: this.#actionName,
|
|
506
|
+
})
|
|
431
507
|
stepAbortController.abort(timeoutError)
|
|
432
508
|
}, expire)
|
|
433
509
|
|
|
@@ -447,15 +523,15 @@ export class StepManager {
|
|
|
447
523
|
const childAbortController = new AbortController()
|
|
448
524
|
const childSignal = AbortSignal.any([stepSignal, childAbortController.signal])
|
|
449
525
|
|
|
450
|
-
// Create
|
|
451
|
-
const
|
|
526
|
+
// Create telemetry context for this step
|
|
527
|
+
const stepTelemetryContext = this.createStepTelemetryContext(step.id)
|
|
452
528
|
|
|
453
529
|
// Create StepHandlerContext with nested step support
|
|
454
530
|
const stepContext: StepHandlerContext = {
|
|
455
531
|
signal: stepSignal,
|
|
456
532
|
stepId: step.id,
|
|
457
533
|
parentStepId,
|
|
458
|
-
|
|
534
|
+
telemetry: stepTelemetryContext,
|
|
459
535
|
step: <TChildResult>(
|
|
460
536
|
childName: string,
|
|
461
537
|
childCb: (ctx: StepHandlerContext) => Promise<TChildResult>,
|
|
@@ -505,10 +581,15 @@ export class StepManager {
|
|
|
505
581
|
try {
|
|
506
582
|
// Race between abort signal and callback execution
|
|
507
583
|
const abortPromise = waitForAbort(stepSignal)
|
|
508
|
-
|
|
584
|
+
|
|
585
|
+
// Execute callback within the span context so that child spans inherit the trace
|
|
586
|
+
const currentStepSpan = step?.id ? this.#stepSpans.get(step.id) : undefined
|
|
587
|
+
const spanContext = currentStepSpan ? trace.setSpan(context.active(), currentStepSpan) : context.active()
|
|
588
|
+
const callbackPromise = context.with(spanContext, () => cb(stepContext))
|
|
509
589
|
|
|
510
590
|
let result: any = null
|
|
511
591
|
let aborted = false
|
|
592
|
+
let callbackError: Error | null = null
|
|
512
593
|
|
|
513
594
|
await Promise.race([
|
|
514
595
|
abortPromise.promise.then(() => {
|
|
@@ -520,11 +601,25 @@ export class StepManager {
|
|
|
520
601
|
result = res
|
|
521
602
|
}
|
|
522
603
|
})
|
|
604
|
+
.catch((err) => {
|
|
605
|
+
callbackError = err
|
|
606
|
+
})
|
|
523
607
|
.finally(() => {
|
|
524
608
|
abortPromise.release()
|
|
525
609
|
}),
|
|
526
610
|
])
|
|
527
611
|
|
|
612
|
+
// If callback threw an error, abort children and wait for them before re-throwing
|
|
613
|
+
if (callbackError) {
|
|
614
|
+
if (childSteps.length > 0) {
|
|
615
|
+
// Abort all children with the callback error as reason
|
|
616
|
+
childAbortController.abort(callbackError)
|
|
617
|
+
// Wait for all children to settle
|
|
618
|
+
await Promise.allSettled(childSteps.map((c) => c.promise))
|
|
619
|
+
}
|
|
620
|
+
throw callbackError
|
|
621
|
+
}
|
|
622
|
+
|
|
528
623
|
// If aborted, wait for child steps to settle before propagating
|
|
529
624
|
if (aborted) {
|
|
530
625
|
// Wait for all child steps to settle (they'll be aborted via signal propagation)
|
|
@@ -550,7 +645,12 @@ export class StepManager {
|
|
|
550
645
|
)
|
|
551
646
|
|
|
552
647
|
// Abort all pending children
|
|
553
|
-
const unhandledError = new UnhandledChildStepsError(name, unsettledChildren.length
|
|
648
|
+
const unhandledError = new UnhandledChildStepsError(name, unsettledChildren.length, {
|
|
649
|
+
stepId: step.id,
|
|
650
|
+
parentStepId,
|
|
651
|
+
jobId: this.#jobId,
|
|
652
|
+
actionName: this.#actionName,
|
|
653
|
+
})
|
|
554
654
|
childAbortController.abort(unhandledError)
|
|
555
655
|
|
|
556
656
|
// Wait for all children to settle (they'll reject with cancellation)
|
|
@@ -566,10 +666,11 @@ export class StepManager {
|
|
|
566
666
|
throw new Error(`Failed to complete step "${name}" for job "${this.#jobId}" action "${this.#actionName}"`)
|
|
567
667
|
}
|
|
568
668
|
|
|
569
|
-
// End step
|
|
669
|
+
// End step span successfully
|
|
570
670
|
const stepSpan = this.#stepSpans.get(step.id)
|
|
571
671
|
if (stepSpan) {
|
|
572
|
-
|
|
672
|
+
stepSpan.setStatus({ code: SpanStatusCode.OK })
|
|
673
|
+
stepSpan.end()
|
|
573
674
|
this.#stepSpans.delete(step.id)
|
|
574
675
|
}
|
|
575
676
|
|
|
@@ -599,12 +700,27 @@ export class StepManager {
|
|
|
599
700
|
if (
|
|
600
701
|
isNonRetriableError(error) ||
|
|
601
702
|
(error.cause && isNonRetriableError(error.cause)) ||
|
|
602
|
-
(error instanceof Error && error.name === 'AbortError'
|
|
703
|
+
(error instanceof Error && error.name === 'AbortError')
|
|
603
704
|
) {
|
|
604
|
-
|
|
705
|
+
const err = isNonRetriableError(error)
|
|
706
|
+
? error
|
|
707
|
+
: error instanceof Error && error.name === 'AbortError'
|
|
708
|
+
? new NonRetriableError(error.message, { cause: error.cause })
|
|
709
|
+
: (error.cause as NonRetriableError)
|
|
710
|
+
|
|
711
|
+
if (Object.keys(err.metadata).length === 0) {
|
|
712
|
+
err.setMetadata({
|
|
713
|
+
stepId: step?.id,
|
|
714
|
+
parentStepId,
|
|
715
|
+
jobId: this.#jobId,
|
|
716
|
+
actionName: this.#actionName,
|
|
717
|
+
})
|
|
718
|
+
}
|
|
719
|
+
throw err
|
|
605
720
|
}
|
|
606
721
|
|
|
607
722
|
if (ctx.retriesLeft > 0 && step) {
|
|
723
|
+
this.#clearHistoryForStep(step.id)
|
|
608
724
|
const delayed = await this.#stepStore.delay(step.id, ctx.finalDelay, serializeError(error))
|
|
609
725
|
if (!delayed) {
|
|
610
726
|
throw new Error(`Failed to delay step "${name}" for job "${this.#jobId}" action "${this.#actionName}"`)
|
|
@@ -614,22 +730,35 @@ export class StepManager {
|
|
|
614
730
|
;(error as any).nonRetriable = true
|
|
615
731
|
throw error
|
|
616
732
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
733
|
+
|
|
734
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
735
|
+
const err = new NonRetriableError(errorMessage, { cause: error })
|
|
736
|
+
err.setMetadata({
|
|
737
|
+
stepId: step?.id,
|
|
738
|
+
parentStepId,
|
|
739
|
+
jobId: this.#jobId,
|
|
740
|
+
actionName: this.#actionName,
|
|
741
|
+
})
|
|
742
|
+
throw err
|
|
621
743
|
}
|
|
622
744
|
},
|
|
623
745
|
}).catch(async (error) => {
|
|
624
746
|
if (step) {
|
|
625
|
-
// End step
|
|
747
|
+
// End step span with error/cancelled status
|
|
626
748
|
const stepSpan = this.#stepSpans.get(step.id)
|
|
627
749
|
if (stepSpan) {
|
|
628
750
|
if (isCancelError(error)) {
|
|
629
|
-
|
|
751
|
+
stepSpan.setStatus({ code: SpanStatusCode.ERROR, message: 'Step cancelled' })
|
|
630
752
|
} else {
|
|
631
|
-
|
|
753
|
+
stepSpan.setStatus({
|
|
754
|
+
code: SpanStatusCode.ERROR,
|
|
755
|
+
message: error instanceof Error ? error.message : String(error),
|
|
756
|
+
})
|
|
757
|
+
if (error instanceof Error) {
|
|
758
|
+
stepSpan.recordException(error)
|
|
759
|
+
}
|
|
632
760
|
}
|
|
761
|
+
stepSpan.end()
|
|
633
762
|
this.#stepSpans.delete(step.id)
|
|
634
763
|
}
|
|
635
764
|
|
|
@@ -642,6 +771,19 @@ export class StepManager {
|
|
|
642
771
|
throw error
|
|
643
772
|
})
|
|
644
773
|
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Clear the history of nested steps for a given step.
|
|
777
|
+
* We do't need to clear the history for the root step because it's not a parent step, it's the action itself.
|
|
778
|
+
* @param stepId - The ID of the step to clear the history for
|
|
779
|
+
*/
|
|
780
|
+
#clearHistoryForStep(stepId: string): void {
|
|
781
|
+
this.#historySteps.forEach((stepKey) => {
|
|
782
|
+
if (stepKey.startsWith(stepId)) {
|
|
783
|
+
this.#historySteps.delete(stepKey)
|
|
784
|
+
}
|
|
785
|
+
})
|
|
786
|
+
}
|
|
645
787
|
}
|
|
646
788
|
|
|
647
789
|
// ============================================================================
|
|
@@ -663,7 +805,7 @@ class ActionContext<TInput extends z.ZodObject, TOutput extends z.ZodObject, TVa
|
|
|
663
805
|
#jobId: string
|
|
664
806
|
#groupKey: string = '@default'
|
|
665
807
|
#action: Action<TInput, TOutput, TVariables>
|
|
666
|
-
#
|
|
808
|
+
#telemetryContext: TelemetryContext
|
|
667
809
|
|
|
668
810
|
// ============================================================================
|
|
669
811
|
// Constructor
|
|
@@ -676,7 +818,7 @@ class ActionContext<TInput extends z.ZodObject, TOutput extends z.ZodObject, TVa
|
|
|
676
818
|
variables: TVariables,
|
|
677
819
|
abortSignal: AbortSignal,
|
|
678
820
|
logger: Logger,
|
|
679
|
-
|
|
821
|
+
telemetryContext: TelemetryContext,
|
|
680
822
|
) {
|
|
681
823
|
this.#stepManager = stepManager
|
|
682
824
|
this.#variables = variables
|
|
@@ -685,7 +827,7 @@ class ActionContext<TInput extends z.ZodObject, TOutput extends z.ZodObject, TVa
|
|
|
685
827
|
this.#action = action
|
|
686
828
|
this.#jobId = job.id
|
|
687
829
|
this.#groupKey = job.groupKey ?? '@default'
|
|
688
|
-
this.#
|
|
830
|
+
this.#telemetryContext = telemetryContext
|
|
689
831
|
if (action.input) {
|
|
690
832
|
this.#input = action.input.parse(job.input, {
|
|
691
833
|
error: () => 'Error parsing action input',
|
|
@@ -745,10 +887,10 @@ class ActionContext<TInput extends z.ZodObject, TOutput extends z.ZodObject, TVa
|
|
|
745
887
|
}
|
|
746
888
|
|
|
747
889
|
/**
|
|
748
|
-
* Get the
|
|
890
|
+
* Get the telemetry context for recording metrics and span data.
|
|
749
891
|
*/
|
|
750
|
-
get
|
|
751
|
-
return this.#
|
|
892
|
+
get telemetry(): TelemetryContext {
|
|
893
|
+
return this.#telemetryContext
|
|
752
894
|
}
|
|
753
895
|
|
|
754
896
|
/**
|
|
@@ -818,7 +960,16 @@ class ActionContext<TInput extends z.ZodObject, TOutput extends z.ZodObject, TVa
|
|
|
818
960
|
: (input as z.infer<TStepInput>)
|
|
819
961
|
|
|
820
962
|
// Resolve step name (static or dynamic)
|
|
821
|
-
|
|
963
|
+
// If it's a function, pass the full context including input, variables, jobId, and parentStepId
|
|
964
|
+
const stepName =
|
|
965
|
+
typeof stepDef.name === 'function'
|
|
966
|
+
? stepDef.name({
|
|
967
|
+
input: validatedInput,
|
|
968
|
+
var: this.#variables,
|
|
969
|
+
jobId: this.#jobId,
|
|
970
|
+
parentStepId,
|
|
971
|
+
})
|
|
972
|
+
: stepDef.name
|
|
822
973
|
|
|
823
974
|
// Merge options: action defaults -> step definition -> call-time overrides
|
|
824
975
|
const mergedOptions: z.input<typeof StepOptionsSchema> = {
|
package/src/telemetry/index.ts
CHANGED
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
type AddSpanAttributeOptions,
|
|
5
|
-
type AddSpanEventOptions,
|
|
6
|
-
type EndSpanOptions,
|
|
7
|
-
type ObserveContext,
|
|
8
|
-
type RecordMetricOptions,
|
|
9
|
-
type Span,
|
|
10
|
-
type StartDatabaseSpanOptions,
|
|
11
|
-
type StartJobSpanOptions,
|
|
12
|
-
type StartSpanOptions,
|
|
13
|
-
type StartStepSpanOptions,
|
|
14
|
-
TelemetryAdapter,
|
|
15
|
-
type Tracer,
|
|
16
|
-
type TracerSpan,
|
|
17
|
-
} from './adapter.js'
|
|
18
|
-
export { LocalTelemetryAdapter, type LocalTelemetryAdapterOptions, localTelemetryAdapter } from './local.js'
|
|
19
|
-
export { NoopTelemetryAdapter, noopTelemetryAdapter } from './noop.js'
|
|
20
|
-
export { OpenTelemetryAdapter, type OpenTelemetryAdapterOptions, openTelemetryAdapter } from './opentelemetry.js'
|
|
1
|
+
// OpenTelemetry-based span exporter for local database persistence
|
|
2
|
+
export { LocalSpanExporter, type LocalSpanExporterOptions } from './local-span-exporter.js'
|