duron 0.3.0-beta.8 → 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 +93 -26
- 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 +166 -9
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +189 -19
- 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 +411 -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 +43 -32
- 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 +141 -30
- package/src/index.ts +2 -0
- package/src/server.ts +39 -37
- package/src/step-manager.ts +254 -91
- 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/client.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { type Span, type Tracer, trace } from '@opentelemetry/api'
|
|
2
|
+
import { resourceFromAttributes } from '@opentelemetry/resources'
|
|
3
|
+
import { BatchSpanProcessor, type SpanExporter, type SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
4
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
|
5
|
+
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
|
|
1
6
|
import pino, { type Logger } from 'pino'
|
|
2
7
|
import { zocker } from 'zocker'
|
|
3
8
|
import * as z from 'zod'
|
|
@@ -11,14 +16,14 @@ import type {
|
|
|
11
16
|
GetJobStepsResult,
|
|
12
17
|
GetJobsOptions,
|
|
13
18
|
GetJobsResult,
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
GetSpansOptions,
|
|
20
|
+
GetSpansResult,
|
|
16
21
|
Job,
|
|
17
22
|
JobStep,
|
|
18
23
|
} from './adapters/adapter.js'
|
|
19
24
|
import type { JobStatusResult, JobStepStatusResult } from './adapters/schemas.js'
|
|
20
25
|
import { JOB_STATUS_CANCELLED, JOB_STATUS_COMPLETED, JOB_STATUS_FAILED, type JobStatus } from './constants.js'
|
|
21
|
-
import {
|
|
26
|
+
import { LocalSpanExporter } from './telemetry/local-span-exporter.js'
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* Extracts the inferred type from an action's input/output schema.
|
|
@@ -30,10 +35,11 @@ type InferActionSchema<T> = T extends z.ZodTypeAny ? z.infer<T> : Record<string,
|
|
|
30
35
|
* Result returned from waitForJob with untyped input and output.
|
|
31
36
|
*/
|
|
32
37
|
export interface JobResult {
|
|
33
|
-
|
|
38
|
+
id: string
|
|
34
39
|
actionName: string
|
|
35
40
|
status: JobStatus
|
|
36
41
|
groupKey: string
|
|
42
|
+
description: string | null
|
|
37
43
|
input: unknown
|
|
38
44
|
output: unknown
|
|
39
45
|
error: Job['error']
|
|
@@ -43,102 +49,228 @@ export interface JobResult {
|
|
|
43
49
|
* Result returned from runActionAndWait with typed input and output based on the action's Zod schemas.
|
|
44
50
|
*/
|
|
45
51
|
export interface TypedJobResult<TAction extends Action<any, any, any>> {
|
|
46
|
-
|
|
52
|
+
id: string
|
|
47
53
|
actionName: string
|
|
48
54
|
status: JobStatus
|
|
49
55
|
groupKey: string
|
|
56
|
+
description: string | null
|
|
50
57
|
input: InferActionSchema<NonNullable<TAction['input']>>
|
|
51
58
|
output: InferActionSchema<NonNullable<TAction['output']>>
|
|
52
59
|
error: Job['error']
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Telemetry context provided to action and step handlers.
|
|
64
|
+
* Provides access to OpenTelemetry APIs for recording traces and metrics.
|
|
65
|
+
*/
|
|
66
|
+
export interface TelemetryContext {
|
|
67
|
+
/**
|
|
68
|
+
* Get the active OpenTelemetry span for the current job/step.
|
|
69
|
+
* Use standard OTel Span methods: setAttribute, addEvent, recordException, etc.
|
|
70
|
+
*/
|
|
71
|
+
getActiveSpan(): Span
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get an OpenTelemetry tracer for creating custom spans.
|
|
75
|
+
*
|
|
76
|
+
* @param name - The name of the tracer (typically your service or library name)
|
|
77
|
+
*/
|
|
78
|
+
getTracer(name: string): Tracer
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Record a custom metric as a span event.
|
|
82
|
+
* This is a convenience method that stores metrics as span events
|
|
83
|
+
* which can be queried from the local database when telemetry.local is enabled.
|
|
84
|
+
*
|
|
85
|
+
* @param name - The metric name (e.g., 'tokens.input', 'latency.ms')
|
|
86
|
+
* @param value - The metric value
|
|
87
|
+
* @param attributes - Optional attributes for the metric
|
|
88
|
+
*/
|
|
89
|
+
recordMetric(name: string, value: number, attributes?: Record<string, any>): void
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Options for local telemetry storage.
|
|
94
|
+
*/
|
|
95
|
+
export interface LocalTelemetryOptions {
|
|
96
|
+
/**
|
|
97
|
+
* Delay in milliseconds before flushing spans to the database.
|
|
98
|
+
* Uses BatchSpanProcessor with this delay.
|
|
99
|
+
* @default 5000
|
|
100
|
+
*/
|
|
101
|
+
flushDelayMs?: number
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Telemetry configuration options.
|
|
106
|
+
* Uses OpenTelemetry SDK for tracing.
|
|
107
|
+
*/
|
|
108
|
+
export interface TelemetryOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Enable local span storage in the database.
|
|
111
|
+
* When enabled, spans are stored in the database and can be queried via getSpans().
|
|
112
|
+
* Set to true for default options, or provide LocalTelemetryOptions for custom config.
|
|
113
|
+
*/
|
|
114
|
+
local?: LocalTelemetryOptions | boolean
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Additional span processors to add to the tracer provider.
|
|
118
|
+
* These are merged with the local processor (if enabled).
|
|
119
|
+
*/
|
|
120
|
+
spanProcessors?: SpanProcessor[]
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Additional span exporter to use.
|
|
124
|
+
* Will be wrapped in a BatchSpanProcessor and merged with other processors.
|
|
125
|
+
*/
|
|
126
|
+
traceExporter?: SpanExporter
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Service name for OpenTelemetry resource.
|
|
130
|
+
* @default 'duron'
|
|
131
|
+
*/
|
|
132
|
+
serviceName?: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Base configuration options for a Duron client instance.
|
|
137
|
+
* These options control job fetching, concurrency, and recovery behavior.
|
|
138
|
+
*/
|
|
139
|
+
export interface BaseOptionsInput {
|
|
56
140
|
/**
|
|
57
141
|
* Unique identifier for this Duron instance.
|
|
58
142
|
* Used for multi-process coordination and job ownership.
|
|
59
|
-
*
|
|
143
|
+
* If not provided, a random UUID will be generated.
|
|
144
|
+
*
|
|
145
|
+
* @example 'worker-1', 'api-server', 'background-processor'
|
|
60
146
|
*/
|
|
61
|
-
id
|
|
147
|
+
id?: string
|
|
62
148
|
|
|
63
149
|
/**
|
|
64
|
-
* Synchronization pattern for fetching jobs.
|
|
65
|
-
*
|
|
66
|
-
* - `'
|
|
67
|
-
* - `'
|
|
68
|
-
* - `
|
|
150
|
+
* Synchronization pattern for fetching jobs from the database.
|
|
151
|
+
*
|
|
152
|
+
* - `'pull'`: Periodically poll the database for new jobs at `pullInterval`
|
|
153
|
+
* - `'push'`: Listen for database notifications when jobs are available (real-time)
|
|
154
|
+
* - `'hybrid'`: Use both pull and push patterns (recommended for reliability)
|
|
155
|
+
* - `false`: Disable automatic job fetching (use `fetch()` manually)
|
|
69
156
|
*
|
|
70
157
|
* @default 'hybrid'
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* // Real-time job processing with fallback polling
|
|
162
|
+
* syncPattern: 'hybrid'
|
|
163
|
+
*
|
|
164
|
+
* // Disable auto-fetching for API-only servers
|
|
165
|
+
* syncPattern: false
|
|
166
|
+
* ```
|
|
71
167
|
*/
|
|
72
|
-
syncPattern
|
|
168
|
+
syncPattern?: 'pull' | 'push' | 'hybrid' | false
|
|
73
169
|
|
|
74
170
|
/**
|
|
75
|
-
* Interval in milliseconds between pull operations when using pull or hybrid sync pattern.
|
|
171
|
+
* Interval in milliseconds between pull operations when using `'pull'` or `'hybrid'` sync pattern.
|
|
172
|
+
* Lower values mean faster job pickup but more database queries.
|
|
76
173
|
*
|
|
77
174
|
* @default 5000
|
|
78
175
|
*/
|
|
79
|
-
pullInterval
|
|
176
|
+
pullInterval?: number
|
|
80
177
|
|
|
81
178
|
/**
|
|
82
|
-
* Maximum number of jobs to fetch in a single batch.
|
|
179
|
+
* Maximum number of jobs to fetch in a single batch from the database.
|
|
180
|
+
* Higher values reduce database round-trips but may increase memory usage.
|
|
83
181
|
*
|
|
84
182
|
* @default 10
|
|
85
183
|
*/
|
|
86
|
-
batchSize
|
|
184
|
+
batchSize?: number
|
|
87
185
|
|
|
88
186
|
/**
|
|
89
187
|
* Maximum number of jobs that can run concurrently per action.
|
|
90
|
-
* This controls the concurrency limit for
|
|
188
|
+
* This controls the concurrency limit for each action's internal queue.
|
|
189
|
+
* Use this to prevent any single action from consuming all resources.
|
|
91
190
|
*
|
|
92
191
|
* @default 100
|
|
93
192
|
*/
|
|
94
|
-
actionConcurrencyLimit
|
|
193
|
+
actionConcurrencyLimit?: number
|
|
95
194
|
|
|
96
195
|
/**
|
|
97
196
|
* Maximum number of jobs that can run concurrently per group key.
|
|
98
197
|
* Jobs with the same group key will respect this limit.
|
|
99
|
-
* This can be overridden using action
|
|
198
|
+
* This is the default value; it can be overridden per-job using `action.groups.concurrency`.
|
|
100
199
|
*
|
|
101
200
|
* @default 10
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* // Limit concurrent jobs per user to 2
|
|
205
|
+
* groupConcurrencyLimit: 2
|
|
206
|
+
* ```
|
|
102
207
|
*/
|
|
103
|
-
groupConcurrencyLimit
|
|
208
|
+
groupConcurrencyLimit?: number
|
|
104
209
|
|
|
105
210
|
/**
|
|
106
211
|
* Whether to run database migrations on startup.
|
|
107
212
|
* When enabled, Duron will automatically apply pending migrations when the adapter starts.
|
|
213
|
+
* Disable this if you manage migrations separately or use a read-only database connection.
|
|
108
214
|
*
|
|
109
215
|
* @default true
|
|
110
216
|
*/
|
|
111
|
-
migrateOnStart
|
|
217
|
+
migrateOnStart?: boolean
|
|
112
218
|
|
|
113
219
|
/**
|
|
114
220
|
* Whether to recover stuck jobs on startup.
|
|
115
221
|
* Stuck jobs are jobs that were marked as active but the process that owned them
|
|
116
|
-
* is no longer running.
|
|
222
|
+
* is no longer running (e.g., after a crash or restart).
|
|
223
|
+
* These jobs will be reset to 'created' status so they can be picked up again.
|
|
117
224
|
*
|
|
118
225
|
* @default true
|
|
119
226
|
*/
|
|
120
|
-
recoverJobsOnStart
|
|
227
|
+
recoverJobsOnStart?: boolean
|
|
121
228
|
|
|
122
229
|
/**
|
|
123
230
|
* Enable multi-process mode for job recovery.
|
|
124
231
|
* When enabled, Duron will ping other processes to check if they're alive
|
|
125
|
-
* before recovering their jobs.
|
|
232
|
+
* before recovering their jobs. This prevents recovering jobs from processes
|
|
233
|
+
* that are still running but slow to respond.
|
|
234
|
+
*
|
|
235
|
+
* Only enable this if you're running multiple Duron instances sharing the same database.
|
|
126
236
|
*
|
|
127
237
|
* @default false
|
|
128
238
|
*/
|
|
129
|
-
multiProcessMode
|
|
239
|
+
multiProcessMode?: boolean
|
|
130
240
|
|
|
131
241
|
/**
|
|
132
242
|
* Timeout in milliseconds to wait for process ping responses in multi-process mode.
|
|
133
243
|
* Processes that don't respond within this timeout will have their jobs recovered.
|
|
244
|
+
* Increase this value if your processes may be temporarily unresponsive under load.
|
|
134
245
|
*
|
|
135
|
-
* @default 5000
|
|
246
|
+
* @default 5000
|
|
136
247
|
*/
|
|
137
|
-
processTimeout
|
|
248
|
+
processTimeout?: number
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const BaseOptionsSchema = z.object({
|
|
252
|
+
id: z.string().optional(),
|
|
253
|
+
syncPattern: z.union([z.literal('pull'), z.literal('push'), z.literal('hybrid'), z.literal(false)]).default('hybrid'),
|
|
254
|
+
pullInterval: z.number().default(5_000),
|
|
255
|
+
batchSize: z.number().default(10),
|
|
256
|
+
actionConcurrencyLimit: z.number().default(100),
|
|
257
|
+
groupConcurrencyLimit: z.number().default(10),
|
|
258
|
+
migrateOnStart: z.boolean().default(true),
|
|
259
|
+
recoverJobsOnStart: z.boolean().default(true),
|
|
260
|
+
multiProcessMode: z.boolean().default(false),
|
|
261
|
+
processTimeout: z.number().default(5 * 1000),
|
|
138
262
|
})
|
|
139
263
|
|
|
264
|
+
// Compile-time check: ensure BaseOptionsInput is assignable to the Zod schema's input type
|
|
265
|
+
type _EnsureBaseOptionsCompatible = BaseOptionsInput extends z.input<typeof BaseOptionsSchema>
|
|
266
|
+
? true
|
|
267
|
+
: 'ERROR: BaseOptionsInput does not match Zod schema input type'
|
|
268
|
+
|
|
269
|
+
declare const _baseOptionsCheck: _EnsureBaseOptionsCompatible
|
|
270
|
+
const _checkOptions: _EnsureBaseOptionsCompatible = true
|
|
271
|
+
|
|
140
272
|
/**
|
|
141
|
-
* Options for configuring a Duron instance.
|
|
273
|
+
* Options for configuring a Duron client instance.
|
|
142
274
|
*
|
|
143
275
|
* @template TActions - Record of action definitions keyed by action name
|
|
144
276
|
* @template TVariables - Type of variables available to actions
|
|
@@ -146,7 +278,7 @@ const BaseOptionsSchema = z.object({
|
|
|
146
278
|
export interface ClientOptions<
|
|
147
279
|
TActions extends Record<string, Action<any, any, TVariables>>,
|
|
148
280
|
TVariables = Record<string, unknown>,
|
|
149
|
-
> extends
|
|
281
|
+
> extends BaseOptionsInput {
|
|
150
282
|
/**
|
|
151
283
|
* The database adapter to use for storing jobs and steps.
|
|
152
284
|
* Required.
|
|
@@ -173,15 +305,25 @@ export interface ClientOptions<
|
|
|
173
305
|
variables?: TVariables
|
|
174
306
|
|
|
175
307
|
/**
|
|
176
|
-
* Optional telemetry
|
|
177
|
-
*
|
|
308
|
+
* Optional telemetry configuration for observability.
|
|
309
|
+
* Uses OpenTelemetry SDK for tracing.
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* // Enable local span storage (stored in the database)
|
|
314
|
+
* telemetry: { local: true }
|
|
315
|
+
*
|
|
316
|
+
* // Enable local storage with custom flush delay
|
|
317
|
+
* telemetry: { local: { flushDelayMs: 10000 } }
|
|
178
318
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
319
|
+
* // Export to external systems (e.g., OTLP)
|
|
320
|
+
* telemetry: { traceExporter: new OTLPTraceExporter() }
|
|
321
|
+
*
|
|
322
|
+
* // Both local storage and external export
|
|
323
|
+
* telemetry: { local: true, traceExporter: new OTLPTraceExporter() }
|
|
324
|
+
* ```
|
|
183
325
|
*/
|
|
184
|
-
telemetry?:
|
|
326
|
+
telemetry?: TelemetryOptions
|
|
185
327
|
}
|
|
186
328
|
|
|
187
329
|
interface FetchOptions {
|
|
@@ -203,7 +345,10 @@ export class Client<
|
|
|
203
345
|
#id: string
|
|
204
346
|
#actions: TActions | null
|
|
205
347
|
#database: Adapter
|
|
206
|
-
#
|
|
348
|
+
#tracerProvider: NodeTracerProvider | null = null
|
|
349
|
+
#tracer: Tracer
|
|
350
|
+
#telemetryOptions: TelemetryOptions | null = null
|
|
351
|
+
#localSpansEnabled: boolean = false
|
|
207
352
|
#variables: Record<string, unknown>
|
|
208
353
|
#logger: Logger
|
|
209
354
|
#started: boolean = false
|
|
@@ -237,14 +382,66 @@ export class Client<
|
|
|
237
382
|
this.#options = BaseOptionsSchema.parse(options)
|
|
238
383
|
this.#id = options.id ?? globalThis.crypto.randomUUID()
|
|
239
384
|
this.#database = options.database
|
|
240
|
-
this.#
|
|
385
|
+
this.#telemetryOptions = options.telemetry ?? null
|
|
241
386
|
this.#actions = options.actions ?? null
|
|
242
387
|
this.#variables = options?.variables ?? {}
|
|
243
388
|
this.#logger = this.#normalizeLogger(options?.logger)
|
|
244
389
|
this.#database.setId(this.#id)
|
|
245
390
|
this.#database.setLogger(this.#logger)
|
|
246
|
-
|
|
247
|
-
|
|
391
|
+
|
|
392
|
+
// Initialize OpenTelemetry TracerProvider if telemetry options are provided
|
|
393
|
+
// When no options are provided, the tracer will be a no-op (from OpenTelemetry API)
|
|
394
|
+
if (this.#telemetryOptions) {
|
|
395
|
+
this.#initTelemetry(this.#telemetryOptions)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Get tracer from our provider if configured, otherwise use global no-op tracer
|
|
399
|
+
// This keeps telemetry scoped to this client instance rather than globally registered
|
|
400
|
+
this.#tracer = this.#tracerProvider?.getTracer('duron') ?? trace.getTracer('duron')
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Initialize OpenTelemetry TracerProvider with configured processors.
|
|
405
|
+
*/
|
|
406
|
+
#initTelemetry(options: TelemetryOptions): void {
|
|
407
|
+
const serviceName = options.serviceName ?? 'duron'
|
|
408
|
+
const processors: SpanProcessor[] = []
|
|
409
|
+
|
|
410
|
+
// Add local span exporter if enabled
|
|
411
|
+
if (options.local) {
|
|
412
|
+
const localOptions = typeof options.local === 'boolean' ? {} : options.local
|
|
413
|
+
const flushDelayMs = localOptions.flushDelayMs ?? 5000
|
|
414
|
+
|
|
415
|
+
const localExporter = new LocalSpanExporter({ adapter: this.#database })
|
|
416
|
+
processors.push(
|
|
417
|
+
new BatchSpanProcessor(localExporter, {
|
|
418
|
+
scheduledDelayMillis: flushDelayMs,
|
|
419
|
+
}),
|
|
420
|
+
)
|
|
421
|
+
this.#localSpansEnabled = true
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Add custom span processors
|
|
425
|
+
if (options.spanProcessors) {
|
|
426
|
+
processors.push(...options.spanProcessors)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Add custom trace exporter wrapped in BatchSpanProcessor
|
|
430
|
+
if (options.traceExporter) {
|
|
431
|
+
processors.push(new BatchSpanProcessor(options.traceExporter))
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Only create TracerProvider if we have processors
|
|
435
|
+
if (processors.length > 0) {
|
|
436
|
+
this.#tracerProvider = new NodeTracerProvider({
|
|
437
|
+
resource: resourceFromAttributes({
|
|
438
|
+
[ATTR_SERVICE_NAME]: serviceName,
|
|
439
|
+
}),
|
|
440
|
+
spanProcessors: processors,
|
|
441
|
+
})
|
|
442
|
+
// Note: We do NOT call .register() here to avoid global state pollution
|
|
443
|
+
// The tracer is obtained directly from this provider instance
|
|
444
|
+
}
|
|
248
445
|
}
|
|
249
446
|
|
|
250
447
|
#normalizeLogger(logger?: Logger | 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent'): Logger {
|
|
@@ -268,10 +465,11 @@ export class Client<
|
|
|
268
465
|
}
|
|
269
466
|
|
|
270
467
|
/**
|
|
271
|
-
* Get the
|
|
468
|
+
* Get the OpenTelemetry tracer for creating custom spans.
|
|
469
|
+
* Always returns a tracer - it's a no-op tracer when no SDK is configured.
|
|
272
470
|
*/
|
|
273
|
-
get
|
|
274
|
-
return this.#
|
|
471
|
+
get tracer(): Tracer {
|
|
472
|
+
return this.#tracer
|
|
275
473
|
}
|
|
276
474
|
|
|
277
475
|
/**
|
|
@@ -282,11 +480,21 @@ export class Client<
|
|
|
282
480
|
}
|
|
283
481
|
|
|
284
482
|
/**
|
|
285
|
-
* Check if local
|
|
286
|
-
* Returns true if
|
|
483
|
+
* Check if local span storage is enabled.
|
|
484
|
+
* Returns true if telemetry.local is enabled.
|
|
485
|
+
*/
|
|
486
|
+
get spansEnabled(): boolean {
|
|
487
|
+
return this.#localSpansEnabled
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Force flush any pending telemetry data.
|
|
492
|
+
* Useful in tests or when you need to ensure spans are exported before querying.
|
|
287
493
|
*/
|
|
288
|
-
|
|
289
|
-
|
|
494
|
+
async flushTelemetry(): Promise<void> {
|
|
495
|
+
if (this.#tracerProvider) {
|
|
496
|
+
await this.#tracerProvider.forceFlush()
|
|
497
|
+
}
|
|
290
498
|
}
|
|
291
499
|
|
|
292
500
|
/**
|
|
@@ -348,6 +556,12 @@ export class Client<
|
|
|
348
556
|
concurrencyLimit = await action.groups.concurrency(concurrencyCtx)
|
|
349
557
|
}
|
|
350
558
|
|
|
559
|
+
// Calculate description if provided
|
|
560
|
+
let description: string | null = null
|
|
561
|
+
if (action.description) {
|
|
562
|
+
description = await action.description(concurrencyCtx)
|
|
563
|
+
}
|
|
564
|
+
|
|
351
565
|
// Create job in database
|
|
352
566
|
const jobId = await this.#database.createJob({
|
|
353
567
|
queue: action.name,
|
|
@@ -356,6 +570,8 @@ export class Client<
|
|
|
356
570
|
timeoutMs: action.expire,
|
|
357
571
|
checksum: action.checksum,
|
|
358
572
|
concurrencyLimit,
|
|
573
|
+
concurrencyStepLimit: action.steps.concurrency,
|
|
574
|
+
description,
|
|
359
575
|
})
|
|
360
576
|
|
|
361
577
|
if (!jobId) {
|
|
@@ -497,10 +713,10 @@ export class Client<
|
|
|
497
713
|
* Fetch and process jobs from the database.
|
|
498
714
|
* Concurrency limits are determined from the latest job created for each groupKey.
|
|
499
715
|
*
|
|
500
|
-
* @param options -
|
|
716
|
+
* @param [options.batchSize] - Maximum number of jobs to fetch in this batch (defaults to `batchSize` from client options)
|
|
501
717
|
* @returns Promise resolving to the array of fetched jobs
|
|
502
718
|
*/
|
|
503
|
-
async fetch(options: FetchOptions) {
|
|
719
|
+
async fetch(options: FetchOptions = {}) {
|
|
504
720
|
await this.start()
|
|
505
721
|
|
|
506
722
|
if (!this.#actions) {
|
|
@@ -705,10 +921,11 @@ export class Client<
|
|
|
705
921
|
return null
|
|
706
922
|
}
|
|
707
923
|
return {
|
|
708
|
-
|
|
924
|
+
id: job.id,
|
|
709
925
|
actionName: job.actionName,
|
|
710
926
|
status: job.status,
|
|
711
927
|
groupKey: job.groupKey,
|
|
928
|
+
description: job.description,
|
|
712
929
|
input: job.input,
|
|
713
930
|
output: job.output,
|
|
714
931
|
error: job.error,
|
|
@@ -770,19 +987,19 @@ export class Client<
|
|
|
770
987
|
}
|
|
771
988
|
|
|
772
989
|
/**
|
|
773
|
-
* Get
|
|
774
|
-
* Only available when
|
|
990
|
+
* Get spans for a job or step.
|
|
991
|
+
* Only available when telemetry.local is enabled.
|
|
775
992
|
*
|
|
776
|
-
* @param options - Query options including jobId/stepId, filters,
|
|
777
|
-
* @returns Promise resolving to
|
|
778
|
-
* @throws Error if not
|
|
993
|
+
* @param options - Query options including jobId/stepId, filters, and sort
|
|
994
|
+
* @returns Promise resolving to spans result
|
|
995
|
+
* @throws Error if local telemetry is not enabled
|
|
779
996
|
*/
|
|
780
|
-
async
|
|
997
|
+
async getSpans(options: GetSpansOptions): Promise<GetSpansResult> {
|
|
781
998
|
await this.start()
|
|
782
|
-
if (!this.
|
|
783
|
-
throw new Error('
|
|
999
|
+
if (!this.spansEnabled) {
|
|
1000
|
+
throw new Error('Spans are only available when telemetry.local is enabled')
|
|
784
1001
|
}
|
|
785
|
-
return this.#database.
|
|
1002
|
+
return this.#database.getSpans(options)
|
|
786
1003
|
}
|
|
787
1004
|
|
|
788
1005
|
/**
|
|
@@ -806,6 +1023,43 @@ export class Client<
|
|
|
806
1023
|
action.name,
|
|
807
1024
|
zocker(action.input as z.ZodObject)
|
|
808
1025
|
.override(z.ZodString, 'string')
|
|
1026
|
+
.override(z.ZodBigInt, '4000' as any) // Convert BigInt to string for JSON serialization
|
|
1027
|
+
.override(z.ZodNumber, (schema, _ctx) => {
|
|
1028
|
+
const greaterThan = schema.def.checks?.find((check) => check._zod.def.check === 'greater_than')?._zod
|
|
1029
|
+
.def as unknown as { value: number; inclusive: boolean }
|
|
1030
|
+
const lessThan = schema.def.checks?.find((check) => check._zod.def.check === 'less_than')?._zod
|
|
1031
|
+
.def as unknown as { value: number; inclusive: boolean }
|
|
1032
|
+
|
|
1033
|
+
if (greaterThan && lessThan) {
|
|
1034
|
+
const min = greaterThan.inclusive ? greaterThan.value : greaterThan.value + 1
|
|
1035
|
+
// For inclusive lessThan, we want to include the value, so max should be value + 1
|
|
1036
|
+
// For exclusive lessThan, we want to exclude the value, so max is the value itself
|
|
1037
|
+
const max = lessThan.inclusive ? lessThan.value + 1 : lessThan.value
|
|
1038
|
+
// Ensure min < max
|
|
1039
|
+
if (min >= max) {
|
|
1040
|
+
return Math.floor(min)
|
|
1041
|
+
}
|
|
1042
|
+
return Math.floor(Math.random() * (max - min) + min)
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (greaterThan) {
|
|
1046
|
+
const min = greaterThan.inclusive ? greaterThan.value : greaterThan.value + 1
|
|
1047
|
+
const max = min + 1000 // Use 1000 as default range
|
|
1048
|
+
return Math.floor(Math.random() * (max - min) + min)
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (lessThan) {
|
|
1052
|
+
// For inclusive lessThan, we want to include the value, so max should be value + 1
|
|
1053
|
+
// For exclusive lessThan, we want to exclude the value, so max is the value itself
|
|
1054
|
+
const max = lessThan.inclusive ? lessThan.value + 1 : lessThan.value
|
|
1055
|
+
return Math.floor(Math.random() * max)
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return Math.floor(Math.random() * 1000)
|
|
1059
|
+
})
|
|
1060
|
+
.number({
|
|
1061
|
+
extreme_value_chance: 0.01,
|
|
1062
|
+
})
|
|
809
1063
|
.generate(),
|
|
810
1064
|
)
|
|
811
1065
|
}
|
|
@@ -847,9 +1101,6 @@ export class Client<
|
|
|
847
1101
|
return false
|
|
848
1102
|
}
|
|
849
1103
|
|
|
850
|
-
// Start telemetry adapter
|
|
851
|
-
await this.#telemetry.start()
|
|
852
|
-
|
|
853
1104
|
if (this.#actions) {
|
|
854
1105
|
if (this.#options.recoverJobsOnStart) {
|
|
855
1106
|
await this.#database.recoverJobs({
|
|
@@ -920,8 +1171,10 @@ export class Client<
|
|
|
920
1171
|
}),
|
|
921
1172
|
)
|
|
922
1173
|
|
|
923
|
-
//
|
|
924
|
-
|
|
1174
|
+
// Shutdown TracerProvider if configured
|
|
1175
|
+
if (this.#tracerProvider) {
|
|
1176
|
+
await this.#tracerProvider.shutdown()
|
|
1177
|
+
}
|
|
925
1178
|
|
|
926
1179
|
const dbStopped = await this.#database.stop()
|
|
927
1180
|
if (!dbStopped) {
|
|
@@ -965,10 +1218,11 @@ export class Client<
|
|
|
965
1218
|
// Transform to JobResult
|
|
966
1219
|
const result: JobResult | null = job
|
|
967
1220
|
? {
|
|
968
|
-
|
|
1221
|
+
id: job.id,
|
|
969
1222
|
actionName: job.actionName,
|
|
970
1223
|
status: job.status,
|
|
971
1224
|
groupKey: job.groupKey,
|
|
1225
|
+
description: job.description,
|
|
972
1226
|
input: job.input,
|
|
973
1227
|
output: job.output,
|
|
974
1228
|
error: job.error,
|
|
@@ -1051,7 +1305,7 @@ export class Client<
|
|
|
1051
1305
|
actionManager = new ActionManager({
|
|
1052
1306
|
action,
|
|
1053
1307
|
database: this.#database,
|
|
1054
|
-
|
|
1308
|
+
tracer: this.#tracer,
|
|
1055
1309
|
variables: this.#variables,
|
|
1056
1310
|
logger: this.#logger,
|
|
1057
1311
|
concurrencyLimit: this.#options.actionConcurrencyLimit,
|