duron 0.2.2 → 0.3.0-beta.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.
- package/dist/action-job.d.ts +2 -0
- package/dist/action-job.d.ts.map +1 -1
- package/dist/action-job.js +20 -1
- package/dist/action-manager.d.ts +2 -0
- package/dist/action-manager.d.ts.map +1 -1
- package/dist/action-manager.js +3 -0
- package/dist/action.d.ts +27 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +9 -0
- package/dist/adapters/adapter.d.ts +10 -2
- package/dist/adapters/adapter.d.ts.map +1 -1
- package/dist/adapters/adapter.js +59 -1
- package/dist/adapters/postgres/base.d.ts +9 -4
- package/dist/adapters/postgres/base.d.ts.map +1 -1
- package/dist/adapters/postgres/base.js +269 -19
- package/dist/adapters/postgres/schema.d.ts +249 -105
- package/dist/adapters/postgres/schema.d.ts.map +1 -1
- package/dist/adapters/postgres/schema.default.d.ts +249 -106
- 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 +29 -1
- package/dist/adapters/schemas.d.ts +140 -7
- package/dist/adapters/schemas.d.ts.map +1 -1
- package/dist/adapters/schemas.js +52 -4
- package/dist/client.d.ts +8 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +28 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +16 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/server.d.ts +220 -16
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +123 -8
- package/dist/step-manager.d.ts +8 -2
- package/dist/step-manager.d.ts.map +1 -1
- package/dist/step-manager.js +174 -15
- package/dist/telemetry/adapter.d.ts +85 -0
- package/dist/telemetry/adapter.d.ts.map +1 -0
- package/dist/telemetry/adapter.js +128 -0
- package/dist/telemetry/index.d.ts +5 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/local.d.ts +21 -0
- package/dist/telemetry/local.d.ts.map +1 -0
- package/dist/telemetry/local.js +180 -0
- package/dist/telemetry/noop.d.ts +16 -0
- package/dist/telemetry/noop.d.ts.map +1 -0
- package/dist/telemetry/noop.js +39 -0
- package/dist/telemetry/opentelemetry.d.ts +24 -0
- package/dist/telemetry/opentelemetry.d.ts.map +1 -0
- package/dist/telemetry/opentelemetry.js +202 -0
- package/migrations/postgres/20260117231749_clumsy_penance/migration.sql +3 -0
- package/migrations/postgres/20260117231749_clumsy_penance/snapshot.json +988 -0
- package/migrations/postgres/20260118202533_wealthy_mysterio/migration.sql +24 -0
- package/migrations/postgres/20260118202533_wealthy_mysterio/snapshot.json +1362 -0
- package/package.json +6 -4
- package/src/action-job.ts +35 -0
- package/src/action-manager.ts +5 -0
- package/src/action.ts +199 -0
- package/src/adapters/adapter.ts +151 -0
- package/src/adapters/postgres/base.ts +342 -23
- package/src/adapters/postgres/schema.default.ts +2 -2
- package/src/adapters/postgres/schema.ts +49 -1
- package/src/adapters/schemas.ts +81 -5
- package/src/client.ts +78 -0
- package/src/errors.ts +45 -1
- package/src/index.ts +10 -2
- package/src/server.ts +163 -8
- package/src/step-manager.ts +293 -13
- package/src/telemetry/adapter.ts +468 -0
- package/src/telemetry/index.ts +17 -0
- package/src/telemetry/local.ts +336 -0
- package/src/telemetry/noop.ts +95 -0
- package/src/telemetry/opentelemetry.ts +310 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "duron",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -67,17 +67,19 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@electric-sql/pglite": "^0.3.14",
|
|
70
|
+
"@opentelemetry/api": "^1.9.0",
|
|
70
71
|
"@types/bun": "latest",
|
|
71
72
|
"@types/node": "^24.0.15",
|
|
72
|
-
"drizzle-kit": "^1.0.0-beta.
|
|
73
|
-
"drizzle-orm": "^1.0.0-beta.
|
|
73
|
+
"drizzle-kit": "^1.0.0-beta.11-05230d9",
|
|
74
|
+
"drizzle-orm": "^1.0.0-beta.11-05230d9",
|
|
74
75
|
"postgres": "^3.4.7",
|
|
75
76
|
"typescript": "^5.6.3"
|
|
76
77
|
},
|
|
77
78
|
"dependencies": {
|
|
78
|
-
"elysia": "^1.4.
|
|
79
|
+
"elysia": "^1.4.22",
|
|
79
80
|
"fastq": "^1.19.1",
|
|
80
81
|
"jose": "^6.1.2",
|
|
82
|
+
"p-all": "^5.0.1",
|
|
81
83
|
"p-retry": "^7.1.0",
|
|
82
84
|
"p-timeout": "^7.0.1",
|
|
83
85
|
"pino": "^10.1.0",
|
package/src/action-job.ts
CHANGED
|
@@ -4,12 +4,14 @@ import type { Action } from './action.js'
|
|
|
4
4
|
import type { Adapter } from './adapters/adapter.js'
|
|
5
5
|
import { ActionCancelError, ActionTimeoutError, isCancelError, StepTimeoutError, serializeError } from './errors.js'
|
|
6
6
|
import { StepManager } from './step-manager.js'
|
|
7
|
+
import type { Span, TelemetryAdapter } from './telemetry/adapter.js'
|
|
7
8
|
import waitForAbort from './utils/wait-for-abort.js'
|
|
8
9
|
|
|
9
10
|
export interface ActionJobOptions<TAction extends Action<any, any, any>> {
|
|
10
11
|
job: { id: string; input: any; groupKey: string; timeoutMs: number; actionName: string }
|
|
11
12
|
action: TAction
|
|
12
13
|
database: Adapter
|
|
14
|
+
telemetry: TelemetryAdapter
|
|
13
15
|
variables: Record<string, unknown>
|
|
14
16
|
logger: Logger
|
|
15
17
|
}
|
|
@@ -24,6 +26,7 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
24
26
|
#job: { id: string; input: any; groupKey: string; timeoutMs: number; actionName: string }
|
|
25
27
|
#action: TAction
|
|
26
28
|
#database: Adapter
|
|
29
|
+
#telemetry: TelemetryAdapter
|
|
27
30
|
#variables: Record<string, unknown>
|
|
28
31
|
#logger: Logger
|
|
29
32
|
#stepManager: StepManager
|
|
@@ -31,6 +34,7 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
31
34
|
#timeoutId: NodeJS.Timeout | null = null
|
|
32
35
|
#done: Promise<void>
|
|
33
36
|
#resolve: (() => void) | null = null
|
|
37
|
+
#jobSpan: Span | null = null
|
|
34
38
|
|
|
35
39
|
// ============================================================================
|
|
36
40
|
// Constructor
|
|
@@ -45,6 +49,7 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
45
49
|
this.#job = options.job
|
|
46
50
|
this.#action = options.action
|
|
47
51
|
this.#database = options.database
|
|
52
|
+
this.#telemetry = options.telemetry
|
|
48
53
|
this.#variables = options.variables
|
|
49
54
|
this.#logger = options.logger
|
|
50
55
|
this.#abortController = new AbortController()
|
|
@@ -54,6 +59,7 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
54
59
|
jobId: options.job.id,
|
|
55
60
|
actionName: options.job.actionName,
|
|
56
61
|
adapter: options.database,
|
|
62
|
+
telemetry: options.telemetry,
|
|
57
63
|
logger: options.logger,
|
|
58
64
|
concurrencyLimit: options.action.concurrency,
|
|
59
65
|
})
|
|
@@ -78,6 +84,17 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
78
84
|
* @throws Error if the job fails or output validation fails
|
|
79
85
|
*/
|
|
80
86
|
async execute() {
|
|
87
|
+
// Start job telemetry span
|
|
88
|
+
this.#jobSpan = await this.#telemetry.startJobSpan({
|
|
89
|
+
jobId: this.#job.id,
|
|
90
|
+
actionName: this.#action.name,
|
|
91
|
+
groupKey: this.#job.groupKey,
|
|
92
|
+
input: this.#job.input,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Set the job span on the step manager
|
|
96
|
+
this.#stepManager.setJobSpan(this.#jobSpan)
|
|
97
|
+
|
|
81
98
|
try {
|
|
82
99
|
// Create a child logger for this job
|
|
83
100
|
const jobLogger = this.#logger.child({
|
|
@@ -85,6 +102,9 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
85
102
|
actionName: this.#action.name,
|
|
86
103
|
})
|
|
87
104
|
|
|
105
|
+
// Create observe context for the action handler
|
|
106
|
+
const observeContext = this.#telemetry.createObserveContext(this.#job.id, null, this.#jobSpan)
|
|
107
|
+
|
|
88
108
|
// Create action context with step manager
|
|
89
109
|
const ctx = this.#stepManager.createActionContext(
|
|
90
110
|
this.#job,
|
|
@@ -92,6 +112,7 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
92
112
|
this.#variables as any,
|
|
93
113
|
this.#abortController.signal,
|
|
94
114
|
jobLogger,
|
|
115
|
+
observeContext,
|
|
95
116
|
)
|
|
96
117
|
|
|
97
118
|
this.#timeoutId = setTimeout(() => {
|
|
@@ -138,6 +159,9 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
138
159
|
'[ActionJob] Action finished executing',
|
|
139
160
|
)
|
|
140
161
|
|
|
162
|
+
// End job span successfully
|
|
163
|
+
await this.#telemetry.endJobSpan(this.#jobSpan, { status: 'ok' })
|
|
164
|
+
|
|
141
165
|
return result
|
|
142
166
|
} catch (error) {
|
|
143
167
|
if (
|
|
@@ -146,6 +170,11 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
146
170
|
) {
|
|
147
171
|
this.#logger.warn({ jobId: this.#job.id, actionName: this.#action.name }, '[ActionJob] Job cancelled')
|
|
148
172
|
await this.#database.cancelJob({ jobId: this.#job.id })
|
|
173
|
+
|
|
174
|
+
// End job span as cancelled
|
|
175
|
+
if (this.#jobSpan) {
|
|
176
|
+
await this.#telemetry.endJobSpan(this.#jobSpan, { status: 'cancelled' })
|
|
177
|
+
}
|
|
149
178
|
return
|
|
150
179
|
}
|
|
151
180
|
|
|
@@ -158,6 +187,12 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
158
187
|
|
|
159
188
|
this.#logger.error({ jobId: this.#job.id, actionName: this.#action.name }, message)
|
|
160
189
|
await this.#database.failJob({ jobId: this.#job.id, error: serializeError(error) })
|
|
190
|
+
|
|
191
|
+
// End job span with error
|
|
192
|
+
if (this.#jobSpan) {
|
|
193
|
+
await this.#telemetry.endJobSpan(this.#jobSpan, { status: 'error', error })
|
|
194
|
+
}
|
|
195
|
+
|
|
161
196
|
throw error
|
|
162
197
|
} finally {
|
|
163
198
|
this.#clear()
|
package/src/action-manager.ts
CHANGED
|
@@ -4,10 +4,12 @@ import type { Logger } from 'pino'
|
|
|
4
4
|
import type { Action } from './action.js'
|
|
5
5
|
import { ActionJob } from './action-job.js'
|
|
6
6
|
import type { Adapter, Job } from './adapters/adapter.js'
|
|
7
|
+
import type { TelemetryAdapter } from './telemetry/adapter.js'
|
|
7
8
|
|
|
8
9
|
export interface ActionManagerOptions<TAction extends Action<any, any, any>> {
|
|
9
10
|
action: TAction
|
|
10
11
|
database: Adapter
|
|
12
|
+
telemetry: TelemetryAdapter
|
|
11
13
|
variables: Record<string, unknown>
|
|
12
14
|
logger: Logger
|
|
13
15
|
concurrencyLimit: number
|
|
@@ -22,6 +24,7 @@ export interface ActionManagerOptions<TAction extends Action<any, any, any>> {
|
|
|
22
24
|
export class ActionManager<TAction extends Action<any, any, any>> {
|
|
23
25
|
#action: TAction
|
|
24
26
|
#database: Adapter
|
|
27
|
+
#telemetry: TelemetryAdapter
|
|
25
28
|
#variables: Record<string, unknown>
|
|
26
29
|
#logger: Logger
|
|
27
30
|
#queue: fastq.queueAsPromised<Job, void>
|
|
@@ -41,6 +44,7 @@ export class ActionManager<TAction extends Action<any, any, any>> {
|
|
|
41
44
|
constructor(options: ActionManagerOptions<TAction>) {
|
|
42
45
|
this.#action = options.action
|
|
43
46
|
this.#database = options.database
|
|
47
|
+
this.#telemetry = options.telemetry
|
|
44
48
|
this.#variables = options.variables
|
|
45
49
|
this.#logger = options.logger
|
|
46
50
|
this.#concurrencyLimit = options.concurrencyLimit
|
|
@@ -149,6 +153,7 @@ export class ActionManager<TAction extends Action<any, any, any>> {
|
|
|
149
153
|
},
|
|
150
154
|
action: this.#action,
|
|
151
155
|
database: this.#database,
|
|
156
|
+
telemetry: this.#telemetry,
|
|
152
157
|
variables: this.#variables,
|
|
153
158
|
logger: this.#logger,
|
|
154
159
|
})
|
package/src/action.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Logger } from 'pino'
|
|
2
2
|
import * as z from 'zod'
|
|
3
3
|
|
|
4
|
+
import type { ObserveContext } from './telemetry/adapter.js'
|
|
4
5
|
import generateChecksum from './utils/checksum.js'
|
|
5
6
|
|
|
6
7
|
export type RetryOptions = z.infer<typeof RetryOptionsSchema>
|
|
@@ -13,15 +14,157 @@ export interface ActionHandlerContext<TInput extends z.ZodObject, TVariables = R
|
|
|
13
14
|
groupKey: string
|
|
14
15
|
var: TVariables
|
|
15
16
|
logger: Logger
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Observability context for recording metrics and span data.
|
|
20
|
+
* Allows recording custom metrics, span attributes, and events.
|
|
21
|
+
*/
|
|
22
|
+
observe: ObserveContext
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Execute an inline step within the action.
|
|
26
|
+
*
|
|
27
|
+
* @param name - The name of the step (must be unique within the job)
|
|
28
|
+
* @param cb - The step handler callback
|
|
29
|
+
* @param options - Optional step configuration
|
|
30
|
+
* @returns Promise resolving to the step result
|
|
31
|
+
*/
|
|
16
32
|
step: <TResult>(
|
|
17
33
|
name: string,
|
|
18
34
|
cb: (ctx: StepHandlerContext) => Promise<TResult>,
|
|
19
35
|
options?: z.input<typeof StepOptionsSchema>,
|
|
20
36
|
) => Promise<TResult>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Execute a reusable step definition created with createStep().
|
|
40
|
+
*
|
|
41
|
+
* @param stepDef - The step definition to execute
|
|
42
|
+
* @param input - The input data for the step (validated against the step's input schema)
|
|
43
|
+
* @param options - Optional step configuration overrides
|
|
44
|
+
* @returns Promise resolving to the step result
|
|
45
|
+
*/
|
|
46
|
+
run: <TStepInput extends z.ZodObject, TResult>(
|
|
47
|
+
stepDef: StepDefinition<TStepInput, TResult, TVariables>,
|
|
48
|
+
input: z.input<TStepInput>,
|
|
49
|
+
options?: Partial<z.input<typeof StepOptionsSchema>>,
|
|
50
|
+
) => Promise<TResult>
|
|
21
51
|
}
|
|
22
52
|
|
|
23
53
|
export interface StepHandlerContext {
|
|
54
|
+
/**
|
|
55
|
+
* The abort signal for this step.
|
|
56
|
+
* This signal will be aborted when:
|
|
57
|
+
* - The action is cancelled
|
|
58
|
+
* - The parent step times out
|
|
59
|
+
* - This step times out
|
|
60
|
+
*/
|
|
24
61
|
signal: AbortSignal
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The unique ID of this step.
|
|
65
|
+
*/
|
|
66
|
+
stepId: string
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The ID of the parent step, or null if this is a root step.
|
|
70
|
+
*/
|
|
71
|
+
parentStepId: string | null
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Observability context for recording metrics and span data.
|
|
75
|
+
* Allows recording custom metrics, span attributes, and events.
|
|
76
|
+
*/
|
|
77
|
+
observe: ObserveContext
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a nested child step.
|
|
81
|
+
* Child steps inherit the abort signal chain from their parent.
|
|
82
|
+
* All child steps MUST be awaited before the parent step returns.
|
|
83
|
+
*
|
|
84
|
+
* @param name - The name of the child step (must be unique within the job)
|
|
85
|
+
* @param cb - The step handler callback
|
|
86
|
+
* @param options - Optional step configuration
|
|
87
|
+
* @returns Promise resolving to the step result
|
|
88
|
+
*/
|
|
89
|
+
step: <TResult>(
|
|
90
|
+
name: string,
|
|
91
|
+
cb: (ctx: StepHandlerContext) => Promise<TResult>,
|
|
92
|
+
options?: z.input<typeof StepOptionsSchema>,
|
|
93
|
+
) => Promise<TResult>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extended context for step definition handlers.
|
|
98
|
+
* Includes all StepHandlerContext properties plus action-level context.
|
|
99
|
+
*/
|
|
100
|
+
export interface StepDefinitionHandlerContext<TInput extends z.ZodObject, TVariables = Record<string, unknown>>
|
|
101
|
+
extends StepHandlerContext {
|
|
102
|
+
/**
|
|
103
|
+
* The validated input for this step.
|
|
104
|
+
*/
|
|
105
|
+
input: z.infer<TInput>
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Variables shared across the action.
|
|
109
|
+
*/
|
|
110
|
+
var: TVariables
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Logger instance for this step.
|
|
114
|
+
*/
|
|
115
|
+
logger: Logger
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The job ID this step belongs to.
|
|
119
|
+
*/
|
|
120
|
+
jobId: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A reusable step definition created with createStep().
|
|
125
|
+
* Can be executed within an action handler using ctx.run().
|
|
126
|
+
*/
|
|
127
|
+
export interface StepDefinition<
|
|
128
|
+
TInput extends z.ZodObject,
|
|
129
|
+
TResult,
|
|
130
|
+
TVariables = Record<string, unknown>,
|
|
131
|
+
> {
|
|
132
|
+
/**
|
|
133
|
+
* The name of the step.
|
|
134
|
+
* Can be a static string or a function that generates the name from the input.
|
|
135
|
+
*/
|
|
136
|
+
name: string | ((ctx: { input: z.infer<TInput> }) => string)
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Zod schema for validating the step input.
|
|
140
|
+
*/
|
|
141
|
+
input?: TInput
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Retry configuration for this step.
|
|
145
|
+
*/
|
|
146
|
+
retry?: z.input<typeof RetryOptionsSchema>
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Timeout in milliseconds for this step.
|
|
150
|
+
*/
|
|
151
|
+
expire?: number
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Whether this step runs in parallel with siblings.
|
|
155
|
+
*/
|
|
156
|
+
parallel?: boolean
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The handler function that executes the step logic.
|
|
160
|
+
*/
|
|
161
|
+
handler: (ctx: StepDefinitionHandlerContext<TInput, TVariables>) => Promise<TResult>
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Internal marker to identify this as a step definition.
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
__stepDefinition: true
|
|
25
168
|
}
|
|
26
169
|
|
|
27
170
|
export interface ConcurrencyHandlerContext<TInput extends z.ZodObject, TVariables = Record<string, unknown>> {
|
|
@@ -99,6 +242,15 @@ export const StepOptionsSchema = z.object({
|
|
|
99
242
|
.number()
|
|
100
243
|
.default(5 * 60 * 1000)
|
|
101
244
|
.describe('The expire time for the step (milliseconds)'),
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Whether this step runs in parallel with siblings.
|
|
248
|
+
* Parallel steps are independent from siblings during time travel.
|
|
249
|
+
* When time traveling to a step, completed parallel siblings are preserved.
|
|
250
|
+
*
|
|
251
|
+
* @default false
|
|
252
|
+
*/
|
|
253
|
+
parallel: z.boolean().default(false).describe('Whether this step runs in parallel (independent from siblings)'),
|
|
102
254
|
})
|
|
103
255
|
|
|
104
256
|
/**
|
|
@@ -245,3 +397,50 @@ export const defineAction = <TVariables = Record<string, unknown>>() => {
|
|
|
245
397
|
})
|
|
246
398
|
}
|
|
247
399
|
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Input type for createStep() - the definition object before transformation.
|
|
403
|
+
*/
|
|
404
|
+
export type StepDefinitionInput<
|
|
405
|
+
TInput extends z.ZodObject,
|
|
406
|
+
TResult,
|
|
407
|
+
TVariables = Record<string, unknown>,
|
|
408
|
+
> = Omit<StepDefinition<TInput, TResult, TVariables>, '__stepDefinition'>
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Creates a reusable step definition that can be executed within action handlers.
|
|
412
|
+
*
|
|
413
|
+
* @template TVariables - Type of variables available to the step handler
|
|
414
|
+
* @returns A curried function that accepts the step definition and returns a StepDefinition
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```typescript
|
|
418
|
+
* const sendEmailStep = createStep<typeof variables>()({
|
|
419
|
+
* name: 'send-email',
|
|
420
|
+
* input: z.object({
|
|
421
|
+
* email: z.string().email(),
|
|
422
|
+
* body: z.string(),
|
|
423
|
+
* }),
|
|
424
|
+
* retry: { limit: 3 },
|
|
425
|
+
* expire: 60000,
|
|
426
|
+
* handler: async (ctx) => {
|
|
427
|
+
* // ctx.input is typed as { email: string, body: string }
|
|
428
|
+
* // ctx.var, ctx.logger, ctx.jobId are also available
|
|
429
|
+
* return { success: true }
|
|
430
|
+
* },
|
|
431
|
+
* })
|
|
432
|
+
*
|
|
433
|
+
* // In an action handler:
|
|
434
|
+
* const result = await ctx.run(sendEmailStep, { email: 'test@example.com', body: 'Hello' })
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
export const createStep = <TVariables = Record<string, unknown>>() => {
|
|
438
|
+
return <TInput extends z.ZodObject, TResult>(
|
|
439
|
+
def: StepDefinitionInput<TInput, TResult, TVariables>,
|
|
440
|
+
): StepDefinition<TInput, TResult, TVariables> => {
|
|
441
|
+
return {
|
|
442
|
+
...def,
|
|
443
|
+
__stepDefinition: true as const,
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
package/src/adapters/adapter.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
DelayJobStepOptions,
|
|
25
25
|
DeleteJobOptions,
|
|
26
26
|
DeleteJobsOptions,
|
|
27
|
+
DeleteMetricsOptions,
|
|
27
28
|
FailJobOptions,
|
|
28
29
|
FailJobStepOptions,
|
|
29
30
|
FetchOptions,
|
|
@@ -32,12 +33,16 @@ import type {
|
|
|
32
33
|
GetJobStepsResult,
|
|
33
34
|
GetJobsOptions,
|
|
34
35
|
GetJobsResult,
|
|
36
|
+
GetMetricsOptions,
|
|
37
|
+
GetMetricsResult,
|
|
38
|
+
InsertMetricOptions,
|
|
35
39
|
Job,
|
|
36
40
|
JobStatusResult,
|
|
37
41
|
JobStep,
|
|
38
42
|
JobStepStatusResult,
|
|
39
43
|
RecoverJobsOptions,
|
|
40
44
|
RetryJobOptions,
|
|
45
|
+
TimeTravelJobOptions,
|
|
41
46
|
} from './schemas.js'
|
|
42
47
|
import {
|
|
43
48
|
BooleanResultSchema,
|
|
@@ -51,6 +56,7 @@ import {
|
|
|
51
56
|
DelayJobStepOptionsSchema,
|
|
52
57
|
DeleteJobOptionsSchema,
|
|
53
58
|
DeleteJobsOptionsSchema,
|
|
59
|
+
DeleteMetricsOptionsSchema,
|
|
54
60
|
FailJobOptionsSchema,
|
|
55
61
|
FailJobStepOptionsSchema,
|
|
56
62
|
FetchOptionsSchema,
|
|
@@ -59,6 +65,9 @@ import {
|
|
|
59
65
|
GetJobStepsResultSchema,
|
|
60
66
|
GetJobsOptionsSchema,
|
|
61
67
|
GetJobsResultSchema,
|
|
68
|
+
GetMetricsOptionsSchema,
|
|
69
|
+
GetMetricsResultSchema,
|
|
70
|
+
InsertMetricOptionsSchema,
|
|
62
71
|
JobIdResultSchema,
|
|
63
72
|
JobSchema,
|
|
64
73
|
JobStatusResultSchema,
|
|
@@ -68,6 +77,7 @@ import {
|
|
|
68
77
|
NumberResultSchema,
|
|
69
78
|
RecoverJobsOptionsSchema,
|
|
70
79
|
RetryJobOptionsSchema,
|
|
80
|
+
TimeTravelJobOptionsSchema,
|
|
71
81
|
} from './schemas.js'
|
|
72
82
|
|
|
73
83
|
// Re-export types from schemas for backward compatibility
|
|
@@ -83,6 +93,7 @@ export type {
|
|
|
83
93
|
DelayJobStepOptions,
|
|
84
94
|
DeleteJobOptions,
|
|
85
95
|
DeleteJobsOptions,
|
|
96
|
+
DeleteMetricsOptions,
|
|
86
97
|
FailJobOptions,
|
|
87
98
|
FailJobStepOptions,
|
|
88
99
|
FetchOptions,
|
|
@@ -91,6 +102,9 @@ export type {
|
|
|
91
102
|
GetJobStepsResult,
|
|
92
103
|
GetJobsOptions,
|
|
93
104
|
GetJobsResult,
|
|
105
|
+
GetMetricsOptions,
|
|
106
|
+
GetMetricsResult,
|
|
107
|
+
InsertMetricOptions,
|
|
94
108
|
Job,
|
|
95
109
|
JobFilters,
|
|
96
110
|
JobSort,
|
|
@@ -98,9 +112,15 @@ export type {
|
|
|
98
112
|
JobStatusResult,
|
|
99
113
|
JobStep,
|
|
100
114
|
JobStepStatusResult,
|
|
115
|
+
Metric,
|
|
116
|
+
MetricFilters,
|
|
117
|
+
MetricSort,
|
|
118
|
+
MetricSortField,
|
|
119
|
+
MetricType,
|
|
101
120
|
RecoverJobsOptions,
|
|
102
121
|
RetryJobOptions,
|
|
103
122
|
SortOrder,
|
|
123
|
+
TimeTravelJobOptions,
|
|
104
124
|
} from './schemas.js'
|
|
105
125
|
|
|
106
126
|
// ============================================================================
|
|
@@ -400,6 +420,30 @@ export abstract class Adapter extends EventEmitter<AdapterEvents> {
|
|
|
400
420
|
}
|
|
401
421
|
}
|
|
402
422
|
|
|
423
|
+
/**
|
|
424
|
+
* Time travel a job to restart from a specific step.
|
|
425
|
+
* The job must be in completed, failed, or cancelled status.
|
|
426
|
+
* Resets the job and ancestor steps to active status, deletes subsequent steps,
|
|
427
|
+
* and preserves completed parallel siblings.
|
|
428
|
+
*
|
|
429
|
+
* @returns Promise resolving to `true` if time travel succeeded, `false` otherwise
|
|
430
|
+
*/
|
|
431
|
+
async timeTravelJob(options: TimeTravelJobOptions): Promise<boolean> {
|
|
432
|
+
try {
|
|
433
|
+
await this.start()
|
|
434
|
+
const parsedOptions = TimeTravelJobOptionsSchema.parse(options)
|
|
435
|
+
const result = await this._timeTravelJob(parsedOptions)
|
|
436
|
+
const success = BooleanResultSchema.parse(result)
|
|
437
|
+
if (success) {
|
|
438
|
+
await this._notify('job-available', { jobId: parsedOptions.jobId })
|
|
439
|
+
}
|
|
440
|
+
return success
|
|
441
|
+
} catch (error) {
|
|
442
|
+
this.#logger?.error(error, 'Error in Adapter.timeTravelJob()')
|
|
443
|
+
throw error
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
403
447
|
/**
|
|
404
448
|
* Delete a job by its ID.
|
|
405
449
|
* Active jobs cannot be deleted.
|
|
@@ -659,6 +703,17 @@ export abstract class Adapter extends EventEmitter<AdapterEvents> {
|
|
|
659
703
|
*/
|
|
660
704
|
protected abstract _retryJob(options: RetryJobOptions): Promise<string | null>
|
|
661
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Internal method to time travel a job to restart from a specific step.
|
|
708
|
+
* The job must be in completed, failed, or cancelled status.
|
|
709
|
+
* Resets the job and ancestor steps to active status, deletes subsequent steps,
|
|
710
|
+
* and preserves completed parallel siblings.
|
|
711
|
+
*
|
|
712
|
+
* @param options - Validated time travel options
|
|
713
|
+
* @returns Promise resolving to `true` if time travel succeeded, `false` otherwise
|
|
714
|
+
*/
|
|
715
|
+
protected abstract _timeTravelJob(options: TimeTravelJobOptions): Promise<boolean>
|
|
716
|
+
|
|
662
717
|
/**
|
|
663
718
|
* Internal method to delete a job by its ID.
|
|
664
719
|
* Active jobs cannot be deleted.
|
|
@@ -937,6 +992,102 @@ export abstract class Adapter extends EventEmitter<AdapterEvents> {
|
|
|
937
992
|
*/
|
|
938
993
|
protected abstract _getActions(): Promise<GetActionsResult>
|
|
939
994
|
|
|
995
|
+
// ============================================================================
|
|
996
|
+
// Metrics Methods
|
|
997
|
+
// ============================================================================
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Insert multiple metric records in a single batch operation.
|
|
1001
|
+
* Note: This method bypasses telemetry tracing to prevent infinite loops.
|
|
1002
|
+
*
|
|
1003
|
+
* @param metrics - Array of metric data to insert
|
|
1004
|
+
* @returns Promise resolving to the number of metrics inserted
|
|
1005
|
+
*/
|
|
1006
|
+
async insertMetrics(metrics: InsertMetricOptions[]): Promise<number> {
|
|
1007
|
+
try {
|
|
1008
|
+
if (metrics.length === 0) {
|
|
1009
|
+
return 0
|
|
1010
|
+
}
|
|
1011
|
+
await this.start()
|
|
1012
|
+
const parsedMetrics = metrics.map((m) => InsertMetricOptionsSchema.parse(m))
|
|
1013
|
+
const result = await this._insertMetrics(parsedMetrics)
|
|
1014
|
+
return NumberResultSchema.parse(result)
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
this.#logger?.error(error, 'Error in Adapter.insertMetrics()')
|
|
1017
|
+
throw error
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Get metrics for a job or step.
|
|
1023
|
+
* Note: This method bypasses telemetry tracing to prevent infinite loops.
|
|
1024
|
+
*
|
|
1025
|
+
* @param options - Query options including jobId/stepId, filters, sort, and pagination
|
|
1026
|
+
* @returns Promise resolving to metrics result with pagination info
|
|
1027
|
+
*/
|
|
1028
|
+
async getMetrics(options: GetMetricsOptions): Promise<GetMetricsResult> {
|
|
1029
|
+
try {
|
|
1030
|
+
await this.start()
|
|
1031
|
+
const parsedOptions = GetMetricsOptionsSchema.parse(options)
|
|
1032
|
+
// Validate that at least one of jobId or stepId is provided
|
|
1033
|
+
if (!parsedOptions.jobId && !parsedOptions.stepId) {
|
|
1034
|
+
throw new Error('At least one of jobId or stepId must be provided')
|
|
1035
|
+
}
|
|
1036
|
+
const result = await this._getMetrics(parsedOptions)
|
|
1037
|
+
return GetMetricsResultSchema.parse(result)
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
this.#logger?.error(error, 'Error in Adapter.getMetrics()')
|
|
1040
|
+
throw error
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Delete all metrics for a job.
|
|
1046
|
+
* Note: This method bypasses telemetry tracing to prevent infinite loops.
|
|
1047
|
+
*
|
|
1048
|
+
* @param options - Options containing the jobId
|
|
1049
|
+
* @returns Promise resolving to the number of metrics deleted
|
|
1050
|
+
*/
|
|
1051
|
+
async deleteMetrics(options: DeleteMetricsOptions): Promise<number> {
|
|
1052
|
+
try {
|
|
1053
|
+
await this.start()
|
|
1054
|
+
const parsedOptions = DeleteMetricsOptionsSchema.parse(options)
|
|
1055
|
+
const result = await this._deleteMetrics(parsedOptions)
|
|
1056
|
+
return NumberResultSchema.parse(result)
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
this.#logger?.error(error, 'Error in Adapter.deleteMetrics()')
|
|
1059
|
+
throw error
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// ============================================================================
|
|
1064
|
+
// Private Metrics Methods (to be implemented by adapters)
|
|
1065
|
+
// ============================================================================
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Internal method to insert multiple metric records in a single batch.
|
|
1069
|
+
*
|
|
1070
|
+
* @param metrics - Array of validated metric data
|
|
1071
|
+
* @returns Promise resolving to the number of metrics inserted
|
|
1072
|
+
*/
|
|
1073
|
+
protected abstract _insertMetrics(metrics: InsertMetricOptions[]): Promise<number>
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Internal method to get metrics for a job or step.
|
|
1077
|
+
*
|
|
1078
|
+
* @param options - Validated query options
|
|
1079
|
+
* @returns Promise resolving to metrics result with pagination info
|
|
1080
|
+
*/
|
|
1081
|
+
protected abstract _getMetrics(options: GetMetricsOptions): Promise<GetMetricsResult>
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Internal method to delete all metrics for a job.
|
|
1085
|
+
*
|
|
1086
|
+
* @param options - Validated options containing the jobId
|
|
1087
|
+
* @returns Promise resolving to the number of metrics deleted
|
|
1088
|
+
*/
|
|
1089
|
+
protected abstract _deleteMetrics(options: DeleteMetricsOptions): Promise<number>
|
|
1090
|
+
|
|
940
1091
|
// ============================================================================
|
|
941
1092
|
// Protected Abstract Methods (to be implemented by adapters)
|
|
942
1093
|
// ============================================================================
|