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.
Files changed (77) hide show
  1. package/dist/action-job.d.ts +2 -0
  2. package/dist/action-job.d.ts.map +1 -1
  3. package/dist/action-job.js +20 -1
  4. package/dist/action-manager.d.ts +2 -0
  5. package/dist/action-manager.d.ts.map +1 -1
  6. package/dist/action-manager.js +3 -0
  7. package/dist/action.d.ts +7 -0
  8. package/dist/action.d.ts.map +1 -1
  9. package/dist/action.js +1 -0
  10. package/dist/adapters/adapter.d.ts +10 -2
  11. package/dist/adapters/adapter.d.ts.map +1 -1
  12. package/dist/adapters/adapter.js +59 -1
  13. package/dist/adapters/postgres/base.d.ts +9 -4
  14. package/dist/adapters/postgres/base.d.ts.map +1 -1
  15. package/dist/adapters/postgres/base.js +269 -19
  16. package/dist/adapters/postgres/schema.d.ts +249 -105
  17. package/dist/adapters/postgres/schema.d.ts.map +1 -1
  18. package/dist/adapters/postgres/schema.default.d.ts +249 -106
  19. package/dist/adapters/postgres/schema.default.d.ts.map +1 -1
  20. package/dist/adapters/postgres/schema.default.js +2 -2
  21. package/dist/adapters/postgres/schema.js +29 -1
  22. package/dist/adapters/schemas.d.ts +140 -7
  23. package/dist/adapters/schemas.d.ts.map +1 -1
  24. package/dist/adapters/schemas.js +52 -4
  25. package/dist/client.d.ts +8 -1
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +28 -0
  28. package/dist/errors.d.ts +6 -0
  29. package/dist/errors.d.ts.map +1 -1
  30. package/dist/errors.js +16 -1
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -1
  34. package/dist/server.d.ts +220 -16
  35. package/dist/server.d.ts.map +1 -1
  36. package/dist/server.js +123 -8
  37. package/dist/step-manager.d.ts +8 -2
  38. package/dist/step-manager.d.ts.map +1 -1
  39. package/dist/step-manager.js +138 -15
  40. package/dist/telemetry/adapter.d.ts +85 -0
  41. package/dist/telemetry/adapter.d.ts.map +1 -0
  42. package/dist/telemetry/adapter.js +128 -0
  43. package/dist/telemetry/index.d.ts +5 -0
  44. package/dist/telemetry/index.d.ts.map +1 -0
  45. package/dist/telemetry/index.js +4 -0
  46. package/dist/telemetry/local.d.ts +21 -0
  47. package/dist/telemetry/local.d.ts.map +1 -0
  48. package/dist/telemetry/local.js +180 -0
  49. package/dist/telemetry/noop.d.ts +16 -0
  50. package/dist/telemetry/noop.d.ts.map +1 -0
  51. package/dist/telemetry/noop.js +39 -0
  52. package/dist/telemetry/opentelemetry.d.ts +24 -0
  53. package/dist/telemetry/opentelemetry.d.ts.map +1 -0
  54. package/dist/telemetry/opentelemetry.js +202 -0
  55. package/migrations/postgres/20260117231749_clumsy_penance/migration.sql +3 -0
  56. package/migrations/postgres/20260117231749_clumsy_penance/snapshot.json +988 -0
  57. package/migrations/postgres/20260118202533_wealthy_mysterio/migration.sql +24 -0
  58. package/migrations/postgres/20260118202533_wealthy_mysterio/snapshot.json +1362 -0
  59. package/package.json +6 -4
  60. package/src/action-job.ts +35 -0
  61. package/src/action-manager.ts +5 -0
  62. package/src/action.ts +56 -0
  63. package/src/adapters/adapter.ts +151 -0
  64. package/src/adapters/postgres/base.ts +342 -23
  65. package/src/adapters/postgres/schema.default.ts +2 -2
  66. package/src/adapters/postgres/schema.ts +49 -1
  67. package/src/adapters/schemas.ts +81 -5
  68. package/src/client.ts +78 -0
  69. package/src/errors.ts +45 -1
  70. package/src/index.ts +3 -1
  71. package/src/server.ts +163 -8
  72. package/src/step-manager.ts +232 -13
  73. package/src/telemetry/adapter.ts +468 -0
  74. package/src/telemetry/index.ts +17 -0
  75. package/src/telemetry/local.ts +336 -0
  76. package/src/telemetry/noop.ts +95 -0
  77. package/src/telemetry/opentelemetry.ts +310 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duron",
3
- "version": "0.2.2",
3
+ "version": "0.3.0-beta.0",
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.2-b782ae1",
73
- "drizzle-orm": "^1.0.0-beta.2-b782ae1",
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.16",
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()
@@ -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,6 +14,13 @@ 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
+
16
24
  step: <TResult>(
17
25
  name: string,
18
26
  cb: (ctx: StepHandlerContext) => Promise<TResult>,
@@ -21,7 +29,46 @@ export interface ActionHandlerContext<TInput extends z.ZodObject, TVariables = R
21
29
  }
22
30
 
23
31
  export interface StepHandlerContext {
32
+ /**
33
+ * The abort signal for this step.
34
+ * This signal will be aborted when:
35
+ * - The action is cancelled
36
+ * - The parent step times out
37
+ * - This step times out
38
+ */
24
39
  signal: AbortSignal
40
+
41
+ /**
42
+ * The unique ID of this step.
43
+ */
44
+ stepId: string
45
+
46
+ /**
47
+ * The ID of the parent step, or null if this is a root step.
48
+ */
49
+ parentStepId: string | null
50
+
51
+ /**
52
+ * Observability context for recording metrics and span data.
53
+ * Allows recording custom metrics, span attributes, and events.
54
+ */
55
+ observe: ObserveContext
56
+
57
+ /**
58
+ * Create a nested child step.
59
+ * Child steps inherit the abort signal chain from their parent.
60
+ * All child steps MUST be awaited before the parent step returns.
61
+ *
62
+ * @param name - The name of the child step (must be unique within the job)
63
+ * @param cb - The step handler callback
64
+ * @param options - Optional step configuration
65
+ * @returns Promise resolving to the step result
66
+ */
67
+ step: <TResult>(
68
+ name: string,
69
+ cb: (ctx: StepHandlerContext) => Promise<TResult>,
70
+ options?: z.input<typeof StepOptionsSchema>,
71
+ ) => Promise<TResult>
25
72
  }
26
73
 
27
74
  export interface ConcurrencyHandlerContext<TInput extends z.ZodObject, TVariables = Record<string, unknown>> {
@@ -99,6 +146,15 @@ export const StepOptionsSchema = z.object({
99
146
  .number()
100
147
  .default(5 * 60 * 1000)
101
148
  .describe('The expire time for the step (milliseconds)'),
149
+
150
+ /**
151
+ * Whether this step runs in parallel with siblings.
152
+ * Parallel steps are independent from siblings during time travel.
153
+ * When time traveling to a step, completed parallel siblings are preserved.
154
+ *
155
+ * @default false
156
+ */
157
+ parallel: z.boolean().default(false).describe('Whether this step runs in parallel (independent from siblings)'),
102
158
  })
103
159
 
104
160
  /**
@@ -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
  // ============================================================================