duron 0.2.2 → 0.3.0-beta.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 +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 +7 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +1 -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 +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- 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 +138 -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 +56 -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 +3 -1
- package/src/server.ts +163 -8
- package/src/step-manager.ts +232 -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
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type { Span as OTelSpan, Tracer, TracerProvider } from '@opentelemetry/api'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AddSpanAttributeOptions,
|
|
5
|
+
type AddSpanEventOptions,
|
|
6
|
+
type EndSpanOptions,
|
|
7
|
+
type RecordMetricOptions,
|
|
8
|
+
type Span,
|
|
9
|
+
type StartDatabaseSpanOptions,
|
|
10
|
+
type StartJobSpanOptions,
|
|
11
|
+
type StartStepSpanOptions,
|
|
12
|
+
TelemetryAdapter,
|
|
13
|
+
} from './adapter.js'
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface OpenTelemetryAdapterOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Service name for telemetry.
|
|
22
|
+
* Used as the tracer name.
|
|
23
|
+
*/
|
|
24
|
+
serviceName?: string
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Optional TracerProvider to use.
|
|
28
|
+
* If not provided, uses the global tracer provider.
|
|
29
|
+
*/
|
|
30
|
+
tracerProvider?: TracerProvider
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether to trace database queries.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
traceDatabaseQueries?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ExtendedSpan extends Span {
|
|
40
|
+
otelSpan: OTelSpan
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// OpenTelemetry Adapter
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* OpenTelemetry telemetry adapter.
|
|
49
|
+
* Exports traces to external systems like Jaeger, OTLP, etc.
|
|
50
|
+
*/
|
|
51
|
+
export class OpenTelemetryAdapter extends TelemetryAdapter {
|
|
52
|
+
#serviceName: string
|
|
53
|
+
#tracerProvider: TracerProvider | null
|
|
54
|
+
#traceDatabaseQueries: boolean
|
|
55
|
+
#tracer: Tracer | null = null
|
|
56
|
+
#spanMap = new Map<string, OTelSpan>()
|
|
57
|
+
|
|
58
|
+
constructor(options: OpenTelemetryAdapterOptions = {}) {
|
|
59
|
+
super()
|
|
60
|
+
this.#serviceName = options.serviceName ?? 'duron'
|
|
61
|
+
this.#tracerProvider = options.tracerProvider ?? null
|
|
62
|
+
this.#traceDatabaseQueries = options.traceDatabaseQueries ?? false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Lifecycle Methods
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
protected async _start(): Promise<void> {
|
|
70
|
+
// Dynamically import OpenTelemetry API to make it optional
|
|
71
|
+
const api = await import('@opentelemetry/api')
|
|
72
|
+
|
|
73
|
+
// Get tracer from provider or global
|
|
74
|
+
if (this.#tracerProvider) {
|
|
75
|
+
this.#tracer = this.#tracerProvider.getTracer(this.#serviceName)
|
|
76
|
+
} else {
|
|
77
|
+
this.#tracer = api.trace.getTracer(this.#serviceName)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected async _stop(): Promise<void> {
|
|
82
|
+
this.#spanMap.clear()
|
|
83
|
+
this.#tracer = null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Span Methods
|
|
88
|
+
// ============================================================================
|
|
89
|
+
|
|
90
|
+
protected async _startJobSpan(options: StartJobSpanOptions): Promise<Span> {
|
|
91
|
+
if (!this.#tracer) {
|
|
92
|
+
throw new Error('OpenTelemetry tracer not initialized')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const api = await import('@opentelemetry/api')
|
|
96
|
+
|
|
97
|
+
const otelSpan = this.#tracer.startSpan(`job:${options.actionName}`, {
|
|
98
|
+
kind: api.SpanKind.INTERNAL,
|
|
99
|
+
attributes: {
|
|
100
|
+
'duron.job.id': options.jobId,
|
|
101
|
+
'duron.job.action_name': options.actionName,
|
|
102
|
+
'duron.job.group_key': options.groupKey,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const spanId = `job:${options.jobId}`
|
|
107
|
+
this.#spanMap.set(spanId, otelSpan)
|
|
108
|
+
|
|
109
|
+
const span: ExtendedSpan = {
|
|
110
|
+
id: spanId,
|
|
111
|
+
jobId: options.jobId,
|
|
112
|
+
stepId: null,
|
|
113
|
+
parentSpanId: null,
|
|
114
|
+
otelSpan,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return span
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected async _endJobSpan(span: Span, options: EndSpanOptions): Promise<void> {
|
|
121
|
+
const api = await import('@opentelemetry/api')
|
|
122
|
+
const extSpan = span as ExtendedSpan
|
|
123
|
+
const otelSpan = extSpan.otelSpan
|
|
124
|
+
|
|
125
|
+
if (options.status === 'error') {
|
|
126
|
+
otelSpan.setStatus({
|
|
127
|
+
code: api.SpanStatusCode.ERROR,
|
|
128
|
+
message: options.error?.message ?? 'Unknown error',
|
|
129
|
+
})
|
|
130
|
+
if (options.error) {
|
|
131
|
+
otelSpan.recordException(options.error)
|
|
132
|
+
}
|
|
133
|
+
} else if (options.status === 'cancelled') {
|
|
134
|
+
otelSpan.setStatus({
|
|
135
|
+
code: api.SpanStatusCode.OK,
|
|
136
|
+
message: 'Cancelled',
|
|
137
|
+
})
|
|
138
|
+
otelSpan.setAttribute('duron.job.cancelled', true)
|
|
139
|
+
} else {
|
|
140
|
+
otelSpan.setStatus({ code: api.SpanStatusCode.OK })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
otelSpan.end()
|
|
144
|
+
this.#spanMap.delete(span.id)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected async _startStepSpan(options: StartStepSpanOptions): Promise<Span> {
|
|
148
|
+
if (!this.#tracer) {
|
|
149
|
+
throw new Error('OpenTelemetry tracer not initialized')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const api = await import('@opentelemetry/api')
|
|
153
|
+
|
|
154
|
+
// Get parent span context
|
|
155
|
+
let parentContext = api.context.active()
|
|
156
|
+
if (options.parentSpan) {
|
|
157
|
+
const parentExtSpan = options.parentSpan as ExtendedSpan
|
|
158
|
+
if (parentExtSpan.otelSpan) {
|
|
159
|
+
parentContext = api.trace.setSpan(api.context.active(), parentExtSpan.otelSpan)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const otelSpan = this.#tracer.startSpan(
|
|
164
|
+
`step:${options.stepName}`,
|
|
165
|
+
{
|
|
166
|
+
kind: api.SpanKind.INTERNAL,
|
|
167
|
+
attributes: {
|
|
168
|
+
'duron.job.id': options.jobId,
|
|
169
|
+
'duron.step.id': options.stepId,
|
|
170
|
+
'duron.step.name': options.stepName,
|
|
171
|
+
'duron.step.parent_step_id': options.parentStepId ?? undefined,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
parentContext,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const spanId = `step:${options.stepId}`
|
|
178
|
+
this.#spanMap.set(spanId, otelSpan)
|
|
179
|
+
|
|
180
|
+
const span: ExtendedSpan = {
|
|
181
|
+
id: spanId,
|
|
182
|
+
jobId: options.jobId,
|
|
183
|
+
stepId: options.stepId,
|
|
184
|
+
parentSpanId: options.parentSpan?.id ?? null,
|
|
185
|
+
otelSpan,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return span
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
protected async _endStepSpan(span: Span, options: EndSpanOptions): Promise<void> {
|
|
192
|
+
const api = await import('@opentelemetry/api')
|
|
193
|
+
const extSpan = span as ExtendedSpan
|
|
194
|
+
const otelSpan = extSpan.otelSpan
|
|
195
|
+
|
|
196
|
+
if (options.status === 'error') {
|
|
197
|
+
otelSpan.setStatus({
|
|
198
|
+
code: api.SpanStatusCode.ERROR,
|
|
199
|
+
message: options.error?.message ?? 'Unknown error',
|
|
200
|
+
})
|
|
201
|
+
if (options.error) {
|
|
202
|
+
otelSpan.recordException(options.error)
|
|
203
|
+
}
|
|
204
|
+
} else if (options.status === 'cancelled') {
|
|
205
|
+
otelSpan.setStatus({
|
|
206
|
+
code: api.SpanStatusCode.OK,
|
|
207
|
+
message: 'Cancelled',
|
|
208
|
+
})
|
|
209
|
+
otelSpan.setAttribute('duron.step.cancelled', true)
|
|
210
|
+
} else {
|
|
211
|
+
otelSpan.setStatus({ code: api.SpanStatusCode.OK })
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
otelSpan.end()
|
|
215
|
+
this.#spanMap.delete(span.id)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
protected async _startDatabaseSpan(options: StartDatabaseSpanOptions): Promise<Span | null> {
|
|
219
|
+
if (!this.#traceDatabaseQueries || !this.#tracer) {
|
|
220
|
+
return null
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const api = await import('@opentelemetry/api')
|
|
224
|
+
|
|
225
|
+
const otelSpan = this.#tracer.startSpan(`db:${options.operation}`, {
|
|
226
|
+
kind: api.SpanKind.CLIENT,
|
|
227
|
+
attributes: {
|
|
228
|
+
'db.system': 'postgresql',
|
|
229
|
+
'db.operation': options.operation,
|
|
230
|
+
'db.statement': options.query,
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const spanId = `db:${globalThis.crypto.randomUUID()}`
|
|
235
|
+
this.#spanMap.set(spanId, otelSpan)
|
|
236
|
+
|
|
237
|
+
const span: ExtendedSpan = {
|
|
238
|
+
id: spanId,
|
|
239
|
+
jobId: '',
|
|
240
|
+
stepId: null,
|
|
241
|
+
parentSpanId: null,
|
|
242
|
+
otelSpan,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return span
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
protected async _endDatabaseSpan(span: Span, options: EndSpanOptions): Promise<void> {
|
|
249
|
+
const api = await import('@opentelemetry/api')
|
|
250
|
+
const extSpan = span as ExtendedSpan
|
|
251
|
+
const otelSpan = extSpan.otelSpan
|
|
252
|
+
|
|
253
|
+
if (options.status === 'error') {
|
|
254
|
+
otelSpan.setStatus({
|
|
255
|
+
code: api.SpanStatusCode.ERROR,
|
|
256
|
+
message: options.error?.message ?? 'Unknown error',
|
|
257
|
+
})
|
|
258
|
+
if (options.error) {
|
|
259
|
+
otelSpan.recordException(options.error)
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
otelSpan.setStatus({ code: api.SpanStatusCode.OK })
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
otelSpan.end()
|
|
266
|
+
this.#spanMap.delete(span.id)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================================================
|
|
270
|
+
// Metrics Methods
|
|
271
|
+
// ============================================================================
|
|
272
|
+
|
|
273
|
+
protected async _recordMetric(options: RecordMetricOptions): Promise<void> {
|
|
274
|
+
// OpenTelemetry metrics would require MeterProvider
|
|
275
|
+
// For now, we record as span events on the current active span
|
|
276
|
+
const api = await import('@opentelemetry/api')
|
|
277
|
+
const activeSpan = api.trace.getActiveSpan()
|
|
278
|
+
|
|
279
|
+
if (activeSpan) {
|
|
280
|
+
activeSpan.addEvent('metric', {
|
|
281
|
+
'metric.name': options.name,
|
|
282
|
+
'metric.value': options.value,
|
|
283
|
+
...options.attributes,
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
protected async _addSpanEvent(options: AddSpanEventOptions): Promise<void> {
|
|
289
|
+
const extSpan = options.span as ExtendedSpan
|
|
290
|
+
if (extSpan.otelSpan) {
|
|
291
|
+
extSpan.otelSpan.addEvent(options.name, options.attributes)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
protected async _addSpanAttribute(options: AddSpanAttributeOptions): Promise<void> {
|
|
296
|
+
const extSpan = options.span as ExtendedSpan
|
|
297
|
+
if (extSpan.otelSpan) {
|
|
298
|
+
extSpan.otelSpan.setAttribute(options.key, options.value)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Create an OpenTelemetry telemetry adapter.
|
|
305
|
+
* Exports traces to external systems like Jaeger, OTLP, etc.
|
|
306
|
+
*
|
|
307
|
+
* @param options - Configuration options
|
|
308
|
+
* @returns OpenTelemetryAdapter instance
|
|
309
|
+
*/
|
|
310
|
+
export const openTelemetryAdapter = (options?: OpenTelemetryAdapterOptions) => new OpenTelemetryAdapter(options)
|