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.
Files changed (91) hide show
  1. package/dist/action-job.d.ts +33 -2
  2. package/dist/action-job.d.ts.map +1 -1
  3. package/dist/action-job.js +93 -26
  4. package/dist/action-manager.d.ts +44 -2
  5. package/dist/action-manager.d.ts.map +1 -1
  6. package/dist/action-manager.js +64 -3
  7. package/dist/action.d.ts +388 -7
  8. package/dist/action.d.ts.map +1 -1
  9. package/dist/action.js +44 -23
  10. package/dist/adapters/adapter.d.ts +365 -8
  11. package/dist/adapters/adapter.d.ts.map +1 -1
  12. package/dist/adapters/adapter.js +221 -15
  13. package/dist/adapters/postgres/base.d.ts +184 -6
  14. package/dist/adapters/postgres/base.d.ts.map +1 -1
  15. package/dist/adapters/postgres/base.js +436 -75
  16. package/dist/adapters/postgres/pglite.d.ts +37 -0
  17. package/dist/adapters/postgres/pglite.d.ts.map +1 -1
  18. package/dist/adapters/postgres/pglite.js +38 -0
  19. package/dist/adapters/postgres/postgres.d.ts +35 -0
  20. package/dist/adapters/postgres/postgres.d.ts.map +1 -1
  21. package/dist/adapters/postgres/postgres.js +42 -0
  22. package/dist/adapters/postgres/schema.d.ts +150 -37
  23. package/dist/adapters/postgres/schema.d.ts.map +1 -1
  24. package/dist/adapters/postgres/schema.default.d.ts +151 -38
  25. package/dist/adapters/postgres/schema.default.d.ts.map +1 -1
  26. package/dist/adapters/postgres/schema.default.js +2 -2
  27. package/dist/adapters/postgres/schema.js +60 -23
  28. package/dist/adapters/schemas.d.ts +124 -80
  29. package/dist/adapters/schemas.d.ts.map +1 -1
  30. package/dist/adapters/schemas.js +139 -26
  31. package/dist/client.d.ts +426 -22
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +370 -20
  34. package/dist/constants.js +6 -0
  35. package/dist/errors.d.ts +166 -9
  36. package/dist/errors.d.ts.map +1 -1
  37. package/dist/errors.js +189 -19
  38. package/dist/index.d.ts +2 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/server.d.ts +99 -37
  41. package/dist/server.d.ts.map +1 -1
  42. package/dist/server.js +84 -25
  43. package/dist/step-manager.d.ts +111 -4
  44. package/dist/step-manager.d.ts.map +1 -1
  45. package/dist/step-manager.js +411 -75
  46. package/dist/telemetry/index.d.ts +1 -4
  47. package/dist/telemetry/index.d.ts.map +1 -1
  48. package/dist/telemetry/index.js +2 -4
  49. package/dist/telemetry/local-span-exporter.d.ts +56 -0
  50. package/dist/telemetry/local-span-exporter.d.ts.map +1 -0
  51. package/dist/telemetry/local-span-exporter.js +118 -0
  52. package/dist/utils/p-retry.d.ts +5 -0
  53. package/dist/utils/p-retry.d.ts.map +1 -1
  54. package/dist/utils/p-retry.js +8 -0
  55. package/dist/utils/wait-for-abort.d.ts +1 -0
  56. package/dist/utils/wait-for-abort.d.ts.map +1 -1
  57. package/dist/utils/wait-for-abort.js +1 -0
  58. package/migrations/postgres/{20260119153838_flimsy_thor_girl → 20260121160012_normal_bloodstrike}/migration.sql +32 -20
  59. package/migrations/postgres/{20260119153838_flimsy_thor_girl → 20260121160012_normal_bloodstrike}/snapshot.json +241 -66
  60. package/package.json +42 -26
  61. package/src/action-job.ts +43 -32
  62. package/src/action-manager.ts +5 -5
  63. package/src/action.ts +317 -149
  64. package/src/adapters/adapter.ts +54 -54
  65. package/src/adapters/postgres/base.ts +266 -86
  66. package/src/adapters/postgres/schema.default.ts +2 -2
  67. package/src/adapters/postgres/schema.ts +52 -24
  68. package/src/adapters/schemas.ts +91 -36
  69. package/src/client.ts +322 -68
  70. package/src/errors.ts +141 -30
  71. package/src/index.ts +2 -0
  72. package/src/server.ts +39 -37
  73. package/src/step-manager.ts +254 -91
  74. package/src/telemetry/index.ts +2 -20
  75. package/src/telemetry/local-span-exporter.ts +148 -0
  76. package/dist/telemetry/adapter.d.ts +0 -107
  77. package/dist/telemetry/adapter.d.ts.map +0 -1
  78. package/dist/telemetry/adapter.js +0 -134
  79. package/dist/telemetry/local.d.ts +0 -22
  80. package/dist/telemetry/local.d.ts.map +0 -1
  81. package/dist/telemetry/local.js +0 -243
  82. package/dist/telemetry/noop.d.ts +0 -17
  83. package/dist/telemetry/noop.d.ts.map +0 -1
  84. package/dist/telemetry/noop.js +0 -66
  85. package/dist/telemetry/opentelemetry.d.ts +0 -25
  86. package/dist/telemetry/opentelemetry.d.ts.map +0 -1
  87. package/dist/telemetry/opentelemetry.js +0 -312
  88. package/src/telemetry/adapter.ts +0 -642
  89. package/src/telemetry/local.ts +0 -429
  90. package/src/telemetry/noop.ts +0 -141
  91. package/src/telemetry/opentelemetry.ts +0 -453
@@ -1,429 +0,0 @@
1
- import type { Adapter, InsertMetricOptions } from '../adapters/adapter.js'
2
- import {
3
- type AddSpanAttributeOptions,
4
- type AddSpanEventOptions,
5
- type EndSpanOptions,
6
- type RecordMetricOptions,
7
- type Span,
8
- type StartDatabaseSpanOptions,
9
- type StartJobSpanOptions,
10
- type StartSpanOptions,
11
- type StartStepSpanOptions,
12
- TelemetryAdapter,
13
- type Tracer,
14
- type TracerSpan,
15
- } from './adapter.js'
16
-
17
- // ============================================================================
18
- // Types
19
- // ============================================================================
20
-
21
- export interface LocalTelemetryAdapterOptions {
22
- /**
23
- * Delay in milliseconds before flushing queued metrics to the database.
24
- * Metrics are batched and inserted after this delay of inactivity.
25
- * @default 1000
26
- */
27
- flushDelayMs?: number
28
- }
29
-
30
- // ============================================================================
31
- // Constants
32
- // ============================================================================
33
-
34
- const DEFAULT_FLUSH_DELAY_MS = 1000
35
-
36
- // ============================================================================
37
- // Local Telemetry Adapter
38
- // ============================================================================
39
-
40
- /**
41
- * Local telemetry adapter that stores metrics directly in the Duron database.
42
- * Perfect for development and self-hosted deployments.
43
- *
44
- * This adapter automatically uses the database adapter configured in the Duron client.
45
- * Metrics are batched and inserted after a configurable delay of inactivity to reduce database load.
46
- *
47
- * @example
48
- * ```typescript
49
- * const client = duron({
50
- * database: postgresAdapter({ connection: 'postgres://...' }),
51
- * telemetry: localTelemetryAdapter({ flushDelayMs: 500 }), // Custom 500ms delay
52
- * actions: { ... }
53
- * })
54
- * ```
55
- */
56
- export class LocalTelemetryAdapter extends TelemetryAdapter {
57
- #spanStartTimes = new Map<string, number>()
58
- #metricsQueue: InsertMetricOptions[] = []
59
- #flushTimer: ReturnType<typeof setTimeout> | null = null
60
- #flushPromise: Promise<void> | null = null
61
- #flushDelayMs: number
62
-
63
- constructor(options?: LocalTelemetryAdapterOptions) {
64
- super()
65
- this.#flushDelayMs = options?.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS
66
- }
67
-
68
- /**
69
- * Get the database adapter from the Duron client.
70
- * @throws Error if the client is not set
71
- */
72
- get #database(): Adapter {
73
- const client = this.client
74
- if (!client) {
75
- throw new Error(
76
- 'LocalTelemetryAdapter requires the Duron client to be set. This is done automatically by the Duron client.',
77
- )
78
- }
79
- return client.database
80
- }
81
-
82
- // ============================================================================
83
- // Queue Management
84
- // ============================================================================
85
-
86
- /**
87
- * Queue a metric for batch insertion.
88
- * The metric will be inserted after 1 second of inactivity.
89
- */
90
- #queueMetric(options: InsertMetricOptions): void {
91
- this.#metricsQueue.push(options)
92
- this.#scheduleFlush()
93
- }
94
-
95
- /**
96
- * Schedule a flush of the metrics queue.
97
- * Resets the timer on each call (debounce behavior).
98
- */
99
- #scheduleFlush(): void {
100
- if (this.#flushTimer) {
101
- clearTimeout(this.#flushTimer)
102
- }
103
-
104
- this.#flushTimer = setTimeout(() => {
105
- this.#flushTimer = null
106
- this.#flushPromise = this.#flushQueue().finally(() => {
107
- this.#flushPromise = null
108
- })
109
- }, this.#flushDelayMs)
110
- }
111
-
112
- /**
113
- * Flush all queued metrics to the database.
114
- */
115
- async #flushQueue(): Promise<void> {
116
- if (this.#metricsQueue.length === 0) {
117
- return
118
- }
119
-
120
- // Take all metrics from the queue
121
- const metrics = this.#metricsQueue.splice(0, this.#metricsQueue.length)
122
-
123
- // Batch insert all metrics in a single database operation
124
- await this.#database.insertMetrics(metrics)
125
- }
126
-
127
- /**
128
- * Force flush the queue immediately.
129
- * Used during shutdown to ensure all metrics are persisted.
130
- */
131
- async #forceFlush(): Promise<void> {
132
- // Clear any pending timer
133
- if (this.#flushTimer) {
134
- clearTimeout(this.#flushTimer)
135
- this.#flushTimer = null
136
- }
137
-
138
- // Wait for any in-progress flush
139
- if (this.#flushPromise) {
140
- await this.#flushPromise
141
- }
142
-
143
- // Flush remaining metrics
144
- await this.#flushQueue()
145
- }
146
-
147
- // ============================================================================
148
- // Lifecycle Methods
149
- // ============================================================================
150
-
151
- protected async _start(): Promise<void> {
152
- // Database adapter should already be started by the client
153
- }
154
-
155
- protected async _stop(): Promise<void> {
156
- // Flush any remaining metrics before stopping
157
- await this.#forceFlush()
158
- this.#spanStartTimes.clear()
159
- }
160
-
161
- // ============================================================================
162
- // Span Methods
163
- // ============================================================================
164
-
165
- protected async _startJobSpan(options: StartJobSpanOptions): Promise<Span> {
166
- const spanId = `job:${options.jobId}`
167
- this.#spanStartTimes.set(spanId, Date.now())
168
-
169
- // Record span start as a metric
170
- this.#queueMetric({
171
- jobId: options.jobId,
172
- name: 'duron.job.span.start',
173
- value: Date.now(),
174
- type: 'span_event',
175
- attributes: {
176
- actionName: options.actionName,
177
- groupKey: options.groupKey,
178
- spanId,
179
- },
180
- })
181
-
182
- return {
183
- id: spanId,
184
- jobId: options.jobId,
185
- stepId: null,
186
- parentSpanId: null,
187
- }
188
- }
189
-
190
- protected async _endJobSpan(span: Span, options: EndSpanOptions): Promise<void> {
191
- const startTime = this.#spanStartTimes.get(span.id)
192
- const duration = startTime ? Date.now() - startTime : 0
193
- this.#spanStartTimes.delete(span.id)
194
-
195
- // Record span end with duration
196
- this.#queueMetric({
197
- jobId: span.jobId,
198
- name: 'duron.job.span.end',
199
- value: duration,
200
- type: 'span_event',
201
- attributes: {
202
- spanId: span.id,
203
- status: options.status,
204
- error: options.error?.message ?? null,
205
- durationMs: duration,
206
- },
207
- })
208
- }
209
-
210
- protected async _startStepSpan(options: StartStepSpanOptions): Promise<Span> {
211
- const spanId = `step:${options.stepId}`
212
- this.#spanStartTimes.set(spanId, Date.now())
213
-
214
- // Record span start as a metric
215
- this.#queueMetric({
216
- jobId: options.jobId,
217
- stepId: options.stepId,
218
- name: 'duron.step.span.start',
219
- value: Date.now(),
220
- type: 'span_event',
221
- attributes: {
222
- stepName: options.stepName,
223
- parentStepId: options.parentStepId,
224
- parentSpanId: options.parentSpan?.id ?? null,
225
- spanId,
226
- },
227
- })
228
-
229
- return {
230
- id: spanId,
231
- jobId: options.jobId,
232
- stepId: options.stepId,
233
- parentSpanId: options.parentSpan?.id ?? null,
234
- }
235
- }
236
-
237
- protected async _endStepSpan(span: Span, options: EndSpanOptions): Promise<void> {
238
- const startTime = this.#spanStartTimes.get(span.id)
239
- const duration = startTime ? Date.now() - startTime : 0
240
- this.#spanStartTimes.delete(span.id)
241
-
242
- // Record span end with duration
243
- this.#queueMetric({
244
- jobId: span.jobId,
245
- stepId: span.stepId ?? undefined,
246
- name: 'duron.step.span.end',
247
- value: duration,
248
- type: 'span_event',
249
- attributes: {
250
- spanId: span.id,
251
- status: options.status,
252
- error: options.error?.message ?? null,
253
- durationMs: duration,
254
- },
255
- })
256
- }
257
-
258
- protected async _startDatabaseSpan(_options: StartDatabaseSpanOptions): Promise<Span | null> {
259
- // Local adapter doesn't trace database operations to avoid infinite loops
260
- return null
261
- }
262
-
263
- protected async _endDatabaseSpan(_span: Span, _options: EndSpanOptions): Promise<void> {
264
- // No-op for local adapter
265
- }
266
-
267
- // ============================================================================
268
- // Metrics Methods
269
- // ============================================================================
270
-
271
- protected async _recordMetric(options: RecordMetricOptions): Promise<void> {
272
- this.#queueMetric({
273
- jobId: options.jobId,
274
- stepId: options.stepId,
275
- name: options.name,
276
- value: options.value,
277
- type: 'metric',
278
- attributes: options.attributes,
279
- })
280
- }
281
-
282
- protected async _addSpanEvent(options: AddSpanEventOptions): Promise<void> {
283
- this.#queueMetric({
284
- jobId: options.span.jobId,
285
- stepId: options.span.stepId ?? undefined,
286
- name: options.name,
287
- value: Date.now(),
288
- type: 'span_event',
289
- attributes: {
290
- spanId: options.span.id,
291
- ...options.attributes,
292
- },
293
- })
294
- }
295
-
296
- protected async _addSpanAttribute(options: AddSpanAttributeOptions): Promise<void> {
297
- this.#queueMetric({
298
- jobId: options.span.jobId,
299
- stepId: options.span.stepId ?? undefined,
300
- name: `attribute:${options.key}`,
301
- value: typeof options.value === 'number' ? options.value : 0,
302
- type: 'span_attribute',
303
- attributes: {
304
- spanId: options.span.id,
305
- key: options.key,
306
- value: String(options.value),
307
- },
308
- })
309
- }
310
-
311
- // ============================================================================
312
- // Tracer Methods
313
- // ============================================================================
314
-
315
- protected _getTracer(name: string): Tracer {
316
- const adapter = this
317
-
318
- return {
319
- name,
320
-
321
- startSpan(spanName: string, options?: StartSpanOptions): TracerSpan {
322
- const spanId = `tracer:${name}:${globalThis.crypto.randomUUID()}`
323
- const startTime = Date.now()
324
- let ended = false
325
- const attributes: Record<string, string | number | boolean> = {
326
- ...options?.attributes,
327
- }
328
-
329
- // Note: Local adapter tracer spans don't have a jobId context,
330
- // so they can't be stored in the database. They're essentially no-ops
331
- // but provide a consistent API for code that needs a tracer.
332
- // For actual metrics storage, use ctx.observe within action/step handlers.
333
-
334
- const tracerSpan: TracerSpan = {
335
- setAttribute(key: string, value: string | number | boolean): void {
336
- if (!ended) {
337
- attributes[key] = value
338
- }
339
- },
340
-
341
- setAttributes(attrs: Record<string, string | number | boolean>): void {
342
- if (!ended) {
343
- Object.assign(attributes, attrs)
344
- }
345
- },
346
-
347
- addEvent(eventName: string, eventAttrs?: Record<string, string | number | boolean>): void {
348
- if (!ended) {
349
- adapter.logger?.debug({ spanId, event: eventName, attributes: eventAttrs }, 'Tracer span event')
350
- }
351
- },
352
-
353
- recordException(error: Error): void {
354
- if (!ended) {
355
- attributes['error.message'] = error.message
356
- attributes['error.name'] = error.name
357
- adapter.logger?.debug({ spanId, error: error.message }, 'Tracer span exception')
358
- }
359
- },
360
-
361
- setStatusOk(): void {
362
- if (!ended) {
363
- // biome-ignore lint/complexity/useLiteralKeys: Index signature requires bracket notation
364
- attributes['status'] = 'ok'
365
- }
366
- },
367
-
368
- setStatusError(message?: string): void {
369
- if (!ended) {
370
- // biome-ignore lint/complexity/useLiteralKeys: Index signature requires bracket notation
371
- attributes['status'] = 'error'
372
- if (message) {
373
- attributes['status.message'] = message
374
- }
375
- }
376
- },
377
-
378
- end(): void {
379
- if (!ended) {
380
- ended = true
381
- const duration = Date.now() - startTime
382
- adapter.logger?.debug(
383
- { spanId, spanName, tracerName: name, durationMs: duration, attributes },
384
- 'Tracer span ended',
385
- )
386
- }
387
- },
388
-
389
- isRecording(): boolean {
390
- return !ended
391
- },
392
- }
393
-
394
- adapter.logger?.debug({ spanId, spanName, tracerName: name }, 'Tracer span started')
395
-
396
- return tracerSpan
397
- },
398
- }
399
- }
400
- }
401
-
402
- /**
403
- * Create a local telemetry adapter that stores metrics in the Duron database.
404
- * Perfect for development and self-hosted deployments.
405
- *
406
- * The database adapter is automatically obtained from the Duron client.
407
- * Metrics are batched and inserted after a configurable delay of inactivity to reduce database load.
408
- *
409
- * @param options - Configuration options
410
- * @param options.flushDelayMs - Delay in milliseconds before flushing queued metrics (default: 1000)
411
- * @returns LocalTelemetryAdapter instance
412
- *
413
- * @example
414
- * ```typescript
415
- * const client = duron({
416
- * database: postgresAdapter({ connection: 'postgres://...' }),
417
- * telemetry: localTelemetryAdapter(), // Uses default 1 second delay
418
- * actions: { ... }
419
- * })
420
- *
421
- * // Or with custom flush delay
422
- * const client = duron({
423
- * database: postgresAdapter({ connection: 'postgres://...' }),
424
- * telemetry: localTelemetryAdapter({ flushDelayMs: 500 }), // 500ms delay
425
- * actions: { ... }
426
- * })
427
- * ```
428
- */
429
- export const localTelemetryAdapter = (options?: LocalTelemetryAdapterOptions) => new LocalTelemetryAdapter(options)
@@ -1,141 +0,0 @@
1
- import {
2
- type AddSpanAttributeOptions,
3
- type AddSpanEventOptions,
4
- type EndSpanOptions,
5
- type RecordMetricOptions,
6
- type Span,
7
- type StartDatabaseSpanOptions,
8
- type StartJobSpanOptions,
9
- type StartStepSpanOptions,
10
- TelemetryAdapter,
11
- type Tracer,
12
- type TracerSpan,
13
- } from './adapter.js'
14
-
15
- // ============================================================================
16
- // Noop Tracer Span
17
- // ============================================================================
18
-
19
- const noopTracerSpan: TracerSpan = {
20
- setAttribute() {
21
- // No-op
22
- },
23
- setAttributes() {
24
- // No-op
25
- },
26
- addEvent() {
27
- // No-op
28
- },
29
- recordException() {
30
- // No-op
31
- },
32
- setStatusOk() {
33
- // No-op
34
- },
35
- setStatusError() {
36
- // No-op
37
- },
38
- end() {
39
- // No-op
40
- },
41
- isRecording() {
42
- return false
43
- },
44
- }
45
-
46
- // ============================================================================
47
- // Noop Telemetry Adapter
48
- // ============================================================================
49
-
50
- /**
51
- * No-operation telemetry adapter.
52
- * Used when telemetry is disabled. All methods are no-ops.
53
- */
54
- export class NoopTelemetryAdapter extends TelemetryAdapter {
55
- // ============================================================================
56
- // Lifecycle Methods
57
- // ============================================================================
58
-
59
- protected async _start(): Promise<void> {
60
- // No-op
61
- }
62
-
63
- protected async _stop(): Promise<void> {
64
- // No-op
65
- }
66
-
67
- // ============================================================================
68
- // Span Methods
69
- // ============================================================================
70
-
71
- protected async _startJobSpan(options: StartJobSpanOptions): Promise<Span> {
72
- return {
73
- id: 'noop',
74
- jobId: options.jobId,
75
- stepId: null,
76
- parentSpanId: null,
77
- }
78
- }
79
-
80
- protected async _endJobSpan(_span: Span, _options: EndSpanOptions): Promise<void> {
81
- // No-op
82
- }
83
-
84
- protected async _startStepSpan(options: StartStepSpanOptions): Promise<Span> {
85
- return {
86
- id: 'noop',
87
- jobId: options.jobId,
88
- stepId: options.stepId,
89
- parentSpanId: options.parentSpan?.id ?? null,
90
- }
91
- }
92
-
93
- protected async _endStepSpan(_span: Span, _options: EndSpanOptions): Promise<void> {
94
- // No-op
95
- }
96
-
97
- protected async _startDatabaseSpan(_options: StartDatabaseSpanOptions): Promise<Span | null> {
98
- return null
99
- }
100
-
101
- protected async _endDatabaseSpan(_span: Span, _options: EndSpanOptions): Promise<void> {
102
- // No-op
103
- }
104
-
105
- // ============================================================================
106
- // Metrics Methods
107
- // ============================================================================
108
-
109
- protected async _recordMetric(_options: RecordMetricOptions): Promise<void> {
110
- // No-op
111
- }
112
-
113
- protected async _addSpanEvent(_options: AddSpanEventOptions): Promise<void> {
114
- // No-op
115
- }
116
-
117
- protected async _addSpanAttribute(_options: AddSpanAttributeOptions): Promise<void> {
118
- // No-op
119
- }
120
-
121
- // ============================================================================
122
- // Tracer Methods
123
- // ============================================================================
124
-
125
- protected _getTracer(name: string): Tracer {
126
- return {
127
- name,
128
- startSpan(): TracerSpan {
129
- return noopTracerSpan
130
- },
131
- }
132
- }
133
- }
134
-
135
- /**
136
- * Create a no-operation telemetry adapter.
137
- * Use this when telemetry should be disabled.
138
- *
139
- * @returns NoopTelemetryAdapter instance
140
- */
141
- export const noopTelemetryAdapter = () => new NoopTelemetryAdapter()