duron 0.3.0-beta.10 → 0.3.0-beta.12
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 +87 -22
- 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 +146 -3
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +131 -0
- 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 +174 -5
- package/dist/adapters/postgres/base.d.ts.map +1 -1
- package/dist/adapters/postgres/base.js +349 -66
- 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 +118 -35
- package/dist/adapters/postgres/schema.d.ts.map +1 -1
- package/dist/adapters/postgres/schema.default.d.ts +119 -36
- 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 +55 -22
- package/dist/adapters/schemas.d.ts +107 -80
- package/dist/adapters/schemas.d.ts.map +1 -1
- package/dist/adapters/schemas.js +131 -26
- package/dist/client.d.ts +315 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +391 -21
- package/dist/constants.js +6 -0
- package/dist/errors.d.ts +119 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +111 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/server.d.ts +91 -37
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +81 -25
- package/dist/step-manager.d.ts +111 -4
- package/dist/step-manager.d.ts.map +1 -1
- package/dist/step-manager.js +340 -69
- package/dist/telemetry/adapter.d.ts +322 -0
- package/dist/telemetry/adapter.d.ts.map +1 -1
- package/dist/telemetry/adapter.js +145 -0
- 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/telemetry/local.d.ts +48 -0
- package/dist/telemetry/local.d.ts.map +1 -1
- package/dist/telemetry/local.js +102 -0
- package/dist/telemetry/noop.d.ts +10 -0
- package/dist/telemetry/noop.d.ts.map +1 -1
- package/dist/telemetry/noop.js +43 -0
- package/dist/telemetry/opentelemetry.d.ts +23 -0
- package/dist/telemetry/opentelemetry.d.ts.map +1 -1
- package/dist/telemetry/opentelemetry.js +39 -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 → 20260120154151_mean_magdalene}/migration.sql +27 -19
- package/migrations/postgres/{20260119153838_flimsy_thor_girl → 20260120154151_mean_magdalene}/snapshot.json +172 -65
- package/package.json +7 -2
- package/src/action-job.ts +32 -28
- package/src/action-manager.ts +5 -5
- package/src/action.ts +7 -7
- package/src/adapters/adapter.ts +54 -54
- package/src/adapters/postgres/base.ts +140 -77
- package/src/adapters/postgres/schema.default.ts +2 -2
- package/src/adapters/postgres/schema.ts +47 -23
- package/src/adapters/schemas.ts +83 -36
- package/src/client.ts +195 -42
- package/src/index.ts +1 -0
- package/src/server.ts +37 -37
- package/src/step-manager.ts +170 -86
- package/src/telemetry/index.ts +2 -20
- package/src/telemetry/local-span-exporter.ts +148 -0
- 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/action-manager.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import type { Tracer } from '@opentelemetry/api'
|
|
1
2
|
import fastq from 'fastq'
|
|
2
3
|
import type { Logger } from 'pino'
|
|
3
4
|
|
|
4
5
|
import type { Action } from './action.js'
|
|
5
6
|
import { ActionJob } from './action-job.js'
|
|
6
7
|
import type { Adapter, Job } from './adapters/adapter.js'
|
|
7
|
-
import type { TelemetryAdapter } from './telemetry/adapter.js'
|
|
8
8
|
|
|
9
9
|
export interface ActionManagerOptions<TAction extends Action<any, any, any>> {
|
|
10
10
|
action: TAction
|
|
11
11
|
database: Adapter
|
|
12
|
-
|
|
12
|
+
tracer: Tracer
|
|
13
13
|
variables: Record<string, unknown>
|
|
14
14
|
logger: Logger
|
|
15
15
|
concurrencyLimit: number
|
|
@@ -24,7 +24,7 @@ export interface ActionManagerOptions<TAction extends Action<any, any, any>> {
|
|
|
24
24
|
export class ActionManager<TAction extends Action<any, any, any>> {
|
|
25
25
|
#action: TAction
|
|
26
26
|
#database: Adapter
|
|
27
|
-
#
|
|
27
|
+
#tracer: Tracer
|
|
28
28
|
#variables: Record<string, unknown>
|
|
29
29
|
#logger: Logger
|
|
30
30
|
#queue: fastq.queueAsPromised<Job, void>
|
|
@@ -44,7 +44,7 @@ export class ActionManager<TAction extends Action<any, any, any>> {
|
|
|
44
44
|
constructor(options: ActionManagerOptions<TAction>) {
|
|
45
45
|
this.#action = options.action
|
|
46
46
|
this.#database = options.database
|
|
47
|
-
this.#
|
|
47
|
+
this.#tracer = options.tracer
|
|
48
48
|
this.#variables = options.variables
|
|
49
49
|
this.#logger = options.logger
|
|
50
50
|
this.#concurrencyLimit = options.concurrencyLimit
|
|
@@ -153,7 +153,7 @@ export class ActionManager<TAction extends Action<any, any, any>> {
|
|
|
153
153
|
},
|
|
154
154
|
action: this.#action,
|
|
155
155
|
database: this.#database,
|
|
156
|
-
|
|
156
|
+
tracer: this.#tracer,
|
|
157
157
|
variables: this.#variables,
|
|
158
158
|
logger: this.#logger,
|
|
159
159
|
})
|
package/src/action.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Logger } from 'pino'
|
|
2
2
|
import * as z from 'zod'
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { TelemetryContext } from './step-manager.js'
|
|
5
5
|
import generateChecksum from './utils/checksum.js'
|
|
6
6
|
|
|
7
7
|
export type RetryOptions = z.infer<typeof RetryOptionsSchema>
|
|
@@ -16,10 +16,10 @@ export interface ActionHandlerContext<TInput extends z.ZodObject, TVariables = R
|
|
|
16
16
|
logger: Logger
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Telemetry context for recording metrics and span data.
|
|
20
|
+
* Provides access to OpenTelemetry APIs for recording traces and metrics.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
telemetry: TelemetryContext
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Execute an inline step within the action.
|
|
@@ -71,10 +71,10 @@ export interface StepHandlerContext {
|
|
|
71
71
|
parentStepId: string | null
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* Telemetry context for recording metrics and span data.
|
|
75
|
+
* Provides access to OpenTelemetry APIs for recording traces and metrics.
|
|
76
76
|
*/
|
|
77
|
-
|
|
77
|
+
telemetry: TelemetryContext
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Create a nested child step.
|
package/src/adapters/adapter.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
DelayJobStepOptions,
|
|
25
25
|
DeleteJobOptions,
|
|
26
26
|
DeleteJobsOptions,
|
|
27
|
-
|
|
27
|
+
DeleteSpansOptions,
|
|
28
28
|
FailJobOptions,
|
|
29
29
|
FailJobStepOptions,
|
|
30
30
|
FetchOptions,
|
|
@@ -33,9 +33,9 @@ import type {
|
|
|
33
33
|
GetJobStepsResult,
|
|
34
34
|
GetJobsOptions,
|
|
35
35
|
GetJobsResult,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
GetSpansOptions,
|
|
37
|
+
GetSpansResult,
|
|
38
|
+
InsertSpanOptions,
|
|
39
39
|
Job,
|
|
40
40
|
JobStatusResult,
|
|
41
41
|
JobStep,
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
DelayJobStepOptionsSchema,
|
|
57
57
|
DeleteJobOptionsSchema,
|
|
58
58
|
DeleteJobsOptionsSchema,
|
|
59
|
-
|
|
59
|
+
DeleteSpansOptionsSchema,
|
|
60
60
|
FailJobOptionsSchema,
|
|
61
61
|
FailJobStepOptionsSchema,
|
|
62
62
|
FetchOptionsSchema,
|
|
@@ -65,9 +65,9 @@ import {
|
|
|
65
65
|
GetJobStepsResultSchema,
|
|
66
66
|
GetJobsOptionsSchema,
|
|
67
67
|
GetJobsResultSchema,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
GetSpansOptionsSchema,
|
|
69
|
+
GetSpansResultSchema,
|
|
70
|
+
InsertSpanOptionsSchema,
|
|
71
71
|
JobIdResultSchema,
|
|
72
72
|
JobSchema,
|
|
73
73
|
JobStatusResultSchema,
|
|
@@ -93,7 +93,7 @@ export type {
|
|
|
93
93
|
DelayJobStepOptions,
|
|
94
94
|
DeleteJobOptions,
|
|
95
95
|
DeleteJobsOptions,
|
|
96
|
-
|
|
96
|
+
DeleteSpansOptions,
|
|
97
97
|
FailJobOptions,
|
|
98
98
|
FailJobStepOptions,
|
|
99
99
|
FetchOptions,
|
|
@@ -102,9 +102,9 @@ export type {
|
|
|
102
102
|
GetJobStepsResult,
|
|
103
103
|
GetJobsOptions,
|
|
104
104
|
GetJobsResult,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
GetSpansOptions,
|
|
106
|
+
GetSpansResult,
|
|
107
|
+
InsertSpanOptions,
|
|
108
108
|
Job,
|
|
109
109
|
JobFilters,
|
|
110
110
|
JobSort,
|
|
@@ -112,14 +112,16 @@ export type {
|
|
|
112
112
|
JobStatusResult,
|
|
113
113
|
JobStep,
|
|
114
114
|
JobStepStatusResult,
|
|
115
|
-
Metric,
|
|
116
|
-
MetricFilters,
|
|
117
|
-
MetricSort,
|
|
118
|
-
MetricSortField,
|
|
119
|
-
MetricType,
|
|
120
115
|
RecoverJobsOptions,
|
|
121
116
|
RetryJobOptions,
|
|
122
117
|
SortOrder,
|
|
118
|
+
Span,
|
|
119
|
+
SpanEvent,
|
|
120
|
+
SpanFilters,
|
|
121
|
+
SpanKind,
|
|
122
|
+
SpanSort,
|
|
123
|
+
SpanSortField,
|
|
124
|
+
SpanStatusCode,
|
|
123
125
|
TimeTravelJobOptions,
|
|
124
126
|
} from './schemas.js'
|
|
125
127
|
|
|
@@ -993,100 +995,98 @@ export abstract class Adapter extends EventEmitter<AdapterEvents> {
|
|
|
993
995
|
protected abstract _getActions(): Promise<GetActionsResult>
|
|
994
996
|
|
|
995
997
|
// ============================================================================
|
|
996
|
-
//
|
|
998
|
+
// Span Methods (OpenTelemetry)
|
|
997
999
|
// ============================================================================
|
|
998
1000
|
|
|
999
1001
|
/**
|
|
1000
|
-
* Insert multiple
|
|
1001
|
-
*
|
|
1002
|
+
* Insert multiple span records in a single batch operation.
|
|
1003
|
+
* Used by LocalSpanExporter to store spans from the OpenTelemetry SDK.
|
|
1002
1004
|
*
|
|
1003
|
-
* @param
|
|
1004
|
-
* @returns Promise resolving to the number of
|
|
1005
|
+
* @param spans - Array of span data to insert
|
|
1006
|
+
* @returns Promise resolving to the number of spans inserted
|
|
1005
1007
|
*/
|
|
1006
|
-
async
|
|
1008
|
+
async insertSpans(spans: InsertSpanOptions[]): Promise<number> {
|
|
1007
1009
|
try {
|
|
1008
|
-
if (
|
|
1010
|
+
if (spans.length === 0) {
|
|
1009
1011
|
return 0
|
|
1010
1012
|
}
|
|
1011
1013
|
await this.start()
|
|
1012
|
-
const
|
|
1013
|
-
const result = await this.
|
|
1014
|
+
const parsedSpans = spans.map((s) => InsertSpanOptionsSchema.parse(s))
|
|
1015
|
+
const result = await this._insertSpans(parsedSpans)
|
|
1014
1016
|
return NumberResultSchema.parse(result)
|
|
1015
1017
|
} catch (error) {
|
|
1016
|
-
this.#logger?.error(error, 'Error in Adapter.
|
|
1018
|
+
this.#logger?.error(error, 'Error in Adapter.insertSpans()')
|
|
1017
1019
|
throw error
|
|
1018
1020
|
}
|
|
1019
1021
|
}
|
|
1020
1022
|
|
|
1021
1023
|
/**
|
|
1022
|
-
* Get
|
|
1023
|
-
* Note: This method bypasses telemetry tracing to prevent infinite loops.
|
|
1024
|
+
* Get spans for a job or step.
|
|
1024
1025
|
*
|
|
1025
|
-
* @param options - Query options including jobId/stepId, filters,
|
|
1026
|
-
* @returns Promise resolving to
|
|
1026
|
+
* @param options - Query options including jobId/stepId, filters, and sort
|
|
1027
|
+
* @returns Promise resolving to spans result
|
|
1027
1028
|
*/
|
|
1028
|
-
async
|
|
1029
|
+
async getSpans(options: GetSpansOptions): Promise<GetSpansResult> {
|
|
1029
1030
|
try {
|
|
1030
1031
|
await this.start()
|
|
1031
|
-
const parsedOptions =
|
|
1032
|
+
const parsedOptions = GetSpansOptionsSchema.parse(options)
|
|
1032
1033
|
// Validate that at least one of jobId or stepId is provided
|
|
1033
1034
|
if (!parsedOptions.jobId && !parsedOptions.stepId) {
|
|
1034
1035
|
throw new Error('At least one of jobId or stepId must be provided')
|
|
1035
1036
|
}
|
|
1036
|
-
const result = await this.
|
|
1037
|
-
return
|
|
1037
|
+
const result = await this._getSpans(parsedOptions)
|
|
1038
|
+
return GetSpansResultSchema.parse(result)
|
|
1038
1039
|
} catch (error) {
|
|
1039
|
-
this.#logger?.error(error, 'Error in Adapter.
|
|
1040
|
+
this.#logger?.error(error, 'Error in Adapter.getSpans()')
|
|
1040
1041
|
throw error
|
|
1041
1042
|
}
|
|
1042
1043
|
}
|
|
1043
1044
|
|
|
1044
1045
|
/**
|
|
1045
|
-
* Delete all
|
|
1046
|
-
* Note: This method bypasses telemetry tracing to prevent infinite loops.
|
|
1046
|
+
* Delete all spans for a job.
|
|
1047
1047
|
*
|
|
1048
1048
|
* @param options - Options containing the jobId
|
|
1049
|
-
* @returns Promise resolving to the number of
|
|
1049
|
+
* @returns Promise resolving to the number of spans deleted
|
|
1050
1050
|
*/
|
|
1051
|
-
async
|
|
1051
|
+
async deleteSpans(options: DeleteSpansOptions): Promise<number> {
|
|
1052
1052
|
try {
|
|
1053
1053
|
await this.start()
|
|
1054
|
-
const parsedOptions =
|
|
1055
|
-
const result = await this.
|
|
1054
|
+
const parsedOptions = DeleteSpansOptionsSchema.parse(options)
|
|
1055
|
+
const result = await this._deleteSpans(parsedOptions)
|
|
1056
1056
|
return NumberResultSchema.parse(result)
|
|
1057
1057
|
} catch (error) {
|
|
1058
|
-
this.#logger?.error(error, 'Error in Adapter.
|
|
1058
|
+
this.#logger?.error(error, 'Error in Adapter.deleteSpans()')
|
|
1059
1059
|
throw error
|
|
1060
1060
|
}
|
|
1061
1061
|
}
|
|
1062
1062
|
|
|
1063
1063
|
// ============================================================================
|
|
1064
|
-
// Private
|
|
1064
|
+
// Private Span Methods (to be implemented by adapters)
|
|
1065
1065
|
// ============================================================================
|
|
1066
1066
|
|
|
1067
1067
|
/**
|
|
1068
|
-
* Internal method to insert multiple
|
|
1068
|
+
* Internal method to insert multiple span records in a single batch.
|
|
1069
1069
|
*
|
|
1070
|
-
* @param
|
|
1071
|
-
* @returns Promise resolving to the number of
|
|
1070
|
+
* @param spans - Array of validated span data
|
|
1071
|
+
* @returns Promise resolving to the number of spans inserted
|
|
1072
1072
|
*/
|
|
1073
|
-
protected abstract
|
|
1073
|
+
protected abstract _insertSpans(spans: InsertSpanOptions[]): Promise<number>
|
|
1074
1074
|
|
|
1075
1075
|
/**
|
|
1076
|
-
* Internal method to get
|
|
1076
|
+
* Internal method to get spans for a job or step.
|
|
1077
1077
|
*
|
|
1078
1078
|
* @param options - Validated query options
|
|
1079
|
-
* @returns Promise resolving to
|
|
1079
|
+
* @returns Promise resolving to spans result
|
|
1080
1080
|
*/
|
|
1081
|
-
protected abstract
|
|
1081
|
+
protected abstract _getSpans(options: GetSpansOptions): Promise<GetSpansResult>
|
|
1082
1082
|
|
|
1083
1083
|
/**
|
|
1084
|
-
* Internal method to delete all
|
|
1084
|
+
* Internal method to delete all spans for a job.
|
|
1085
1085
|
*
|
|
1086
1086
|
* @param options - Validated options containing the jobId
|
|
1087
|
-
* @returns Promise resolving to the number of
|
|
1087
|
+
* @returns Promise resolving to the number of spans deleted
|
|
1088
1088
|
*/
|
|
1089
|
-
protected abstract
|
|
1089
|
+
protected abstract _deleteSpans(options: DeleteSpansOptions): Promise<number>
|
|
1090
1090
|
|
|
1091
1091
|
// ============================================================================
|
|
1092
1092
|
// Protected Abstract Methods (to be implemented by adapters)
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type DelayJobStepOptions,
|
|
25
25
|
type DeleteJobOptions,
|
|
26
26
|
type DeleteJobsOptions,
|
|
27
|
-
type
|
|
27
|
+
type DeleteSpansOptions,
|
|
28
28
|
type FailJobOptions,
|
|
29
29
|
type FailJobStepOptions,
|
|
30
30
|
type FetchOptions,
|
|
@@ -33,17 +33,17 @@ import {
|
|
|
33
33
|
type GetJobStepsResult,
|
|
34
34
|
type GetJobsOptions,
|
|
35
35
|
type GetJobsResult,
|
|
36
|
-
type
|
|
37
|
-
type
|
|
38
|
-
type
|
|
36
|
+
type GetSpansOptions,
|
|
37
|
+
type GetSpansResult,
|
|
38
|
+
type InsertSpanOptions,
|
|
39
39
|
type Job,
|
|
40
40
|
type JobSort,
|
|
41
41
|
type JobStatusResult,
|
|
42
42
|
type JobStep,
|
|
43
43
|
type JobStepStatusResult,
|
|
44
|
-
type MetricSort,
|
|
45
44
|
type RecoverJobsOptions,
|
|
46
45
|
type RetryJobOptions,
|
|
46
|
+
type SpanSort,
|
|
47
47
|
type TimeTravelJobOptions,
|
|
48
48
|
} from '../adapter.js'
|
|
49
49
|
import createSchema from './schema.js'
|
|
@@ -788,7 +788,7 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
|
|
|
788
788
|
step_existed AS (
|
|
789
789
|
SELECT EXISTS(
|
|
790
790
|
SELECT 1 FROM ${this.tables.jobStepsTable} s
|
|
791
|
-
WHERE s.job_id = ${jobId}
|
|
791
|
+
WHERE s.job_id = ${jobId}
|
|
792
792
|
AND s.name = ${name}
|
|
793
793
|
AND s.parent_step_id IS NOT DISTINCT FROM ${parentStepId}
|
|
794
794
|
) AS existed
|
|
@@ -1011,26 +1011,38 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
|
|
|
1011
1011
|
* Internal method to get a job by its ID. Does not include step information.
|
|
1012
1012
|
*/
|
|
1013
1013
|
protected async _getJobById(jobId: string): Promise<Job | null> {
|
|
1014
|
+
const jobsTable = this.tables.jobsTable
|
|
1015
|
+
|
|
1016
|
+
// Calculate duration as a SQL expression (finishedAt - startedAt in milliseconds)
|
|
1017
|
+
const durationMs = sql<number | null>`
|
|
1018
|
+
CASE
|
|
1019
|
+
WHEN ${jobsTable.started_at} IS NOT NULL AND ${jobsTable.finished_at} IS NOT NULL
|
|
1020
|
+
THEN EXTRACT(EPOCH FROM (${jobsTable.finished_at} - ${jobsTable.started_at})) * 1000
|
|
1021
|
+
ELSE NULL
|
|
1022
|
+
END
|
|
1023
|
+
`.as('duration_ms')
|
|
1024
|
+
|
|
1014
1025
|
const [job] = await this.db
|
|
1015
1026
|
.select({
|
|
1016
|
-
id:
|
|
1017
|
-
actionName:
|
|
1018
|
-
groupKey:
|
|
1019
|
-
input:
|
|
1020
|
-
output:
|
|
1021
|
-
error:
|
|
1022
|
-
status:
|
|
1023
|
-
timeoutMs:
|
|
1024
|
-
expiresAt:
|
|
1025
|
-
startedAt:
|
|
1026
|
-
finishedAt:
|
|
1027
|
-
createdAt:
|
|
1028
|
-
updatedAt:
|
|
1029
|
-
concurrencyLimit:
|
|
1030
|
-
clientId:
|
|
1027
|
+
id: jobsTable.id,
|
|
1028
|
+
actionName: jobsTable.action_name,
|
|
1029
|
+
groupKey: jobsTable.group_key,
|
|
1030
|
+
input: jobsTable.input,
|
|
1031
|
+
output: jobsTable.output,
|
|
1032
|
+
error: jobsTable.error,
|
|
1033
|
+
status: jobsTable.status,
|
|
1034
|
+
timeoutMs: jobsTable.timeout_ms,
|
|
1035
|
+
expiresAt: jobsTable.expires_at,
|
|
1036
|
+
startedAt: jobsTable.started_at,
|
|
1037
|
+
finishedAt: jobsTable.finished_at,
|
|
1038
|
+
createdAt: jobsTable.created_at,
|
|
1039
|
+
updatedAt: jobsTable.updated_at,
|
|
1040
|
+
concurrencyLimit: jobsTable.concurrency_limit,
|
|
1041
|
+
clientId: jobsTable.client_id,
|
|
1042
|
+
durationMs,
|
|
1031
1043
|
})
|
|
1032
|
-
.from(
|
|
1033
|
-
.where(eq(
|
|
1044
|
+
.from(jobsTable)
|
|
1045
|
+
.where(eq(jobsTable.id, jobId))
|
|
1034
1046
|
.limit(1)
|
|
1035
1047
|
|
|
1036
1048
|
return job ?? null
|
|
@@ -1194,6 +1206,15 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
|
|
|
1194
1206
|
}
|
|
1195
1207
|
}
|
|
1196
1208
|
|
|
1209
|
+
// Calculate duration as a SQL expression (finishedAt - startedAt in milliseconds)
|
|
1210
|
+
const durationMs = sql<number | null>`
|
|
1211
|
+
CASE
|
|
1212
|
+
WHEN ${jobsTable.started_at} IS NOT NULL AND ${jobsTable.finished_at} IS NOT NULL
|
|
1213
|
+
THEN EXTRACT(EPOCH FROM (${jobsTable.finished_at} - ${jobsTable.started_at})) * 1000
|
|
1214
|
+
ELSE NULL
|
|
1215
|
+
END
|
|
1216
|
+
`.as('duration_ms')
|
|
1217
|
+
|
|
1197
1218
|
const sortFieldMap: Record<JobSort['field'], any> = {
|
|
1198
1219
|
createdAt: jobsTable.created_at,
|
|
1199
1220
|
startedAt: jobsTable.started_at,
|
|
@@ -1201,6 +1222,7 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
|
|
|
1201
1222
|
status: jobsTable.status,
|
|
1202
1223
|
actionName: jobsTable.action_name,
|
|
1203
1224
|
expiresAt: jobsTable.expires_at,
|
|
1225
|
+
duration: durationMs,
|
|
1204
1226
|
}
|
|
1205
1227
|
|
|
1206
1228
|
const jobs = await this.db
|
|
@@ -1220,6 +1242,7 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
|
|
|
1220
1242
|
updatedAt: jobsTable.updated_at,
|
|
1221
1243
|
concurrencyLimit: jobsTable.concurrency_limit,
|
|
1222
1244
|
clientId: jobsTable.client_id,
|
|
1245
|
+
durationMs,
|
|
1223
1246
|
})
|
|
1224
1247
|
.from(jobsTable)
|
|
1225
1248
|
.where(where)
|
|
@@ -1362,54 +1385,60 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
|
|
|
1362
1385
|
// ============================================================================
|
|
1363
1386
|
|
|
1364
1387
|
/**
|
|
1365
|
-
* Internal method to insert multiple
|
|
1388
|
+
* Internal method to insert multiple span records in a single batch.
|
|
1366
1389
|
*/
|
|
1367
|
-
protected async
|
|
1368
|
-
if (
|
|
1390
|
+
protected async _insertSpans(spans: InsertSpanOptions[]): Promise<number> {
|
|
1391
|
+
if (spans.length === 0) {
|
|
1369
1392
|
return 0
|
|
1370
1393
|
}
|
|
1371
1394
|
|
|
1372
|
-
const values =
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1395
|
+
const values = spans.map((s) => ({
|
|
1396
|
+
trace_id: s.traceId,
|
|
1397
|
+
span_id: s.spanId,
|
|
1398
|
+
parent_span_id: s.parentSpanId,
|
|
1399
|
+
job_id: s.jobId,
|
|
1400
|
+
step_id: s.stepId,
|
|
1401
|
+
name: s.name,
|
|
1402
|
+
kind: s.kind,
|
|
1403
|
+
start_time_unix_nano: s.startTimeUnixNano,
|
|
1404
|
+
end_time_unix_nano: s.endTimeUnixNano,
|
|
1405
|
+
status_code: s.statusCode,
|
|
1406
|
+
status_message: s.statusMessage,
|
|
1407
|
+
attributes: s.attributes ?? {},
|
|
1408
|
+
events: s.events ?? [],
|
|
1379
1409
|
}))
|
|
1380
1410
|
|
|
1381
1411
|
const result = await this.db
|
|
1382
|
-
.insert(this.tables.
|
|
1412
|
+
.insert(this.tables.spansTable)
|
|
1383
1413
|
.values(values)
|
|
1384
|
-
.returning({ id: this.tables.
|
|
1414
|
+
.returning({ id: this.tables.spansTable.id })
|
|
1385
1415
|
|
|
1386
1416
|
return result.length
|
|
1387
1417
|
}
|
|
1388
1418
|
|
|
1389
1419
|
/**
|
|
1390
|
-
* Internal method to get
|
|
1420
|
+
* Internal method to get spans for a job or step.
|
|
1391
1421
|
*/
|
|
1392
|
-
protected async
|
|
1393
|
-
const
|
|
1422
|
+
protected async _getSpans(options: GetSpansOptions): Promise<GetSpansResult> {
|
|
1423
|
+
const spansTable = this.tables.spansTable
|
|
1394
1424
|
const filters = options.filters ?? {}
|
|
1395
1425
|
|
|
1396
1426
|
// Build WHERE clause
|
|
1397
|
-
const where = this.
|
|
1427
|
+
const where = this._buildSpansWhereClause(options.jobId, options.stepId, filters)
|
|
1398
1428
|
|
|
1399
1429
|
// Build sort
|
|
1400
|
-
const sortInput = options.sort ?? { field: '
|
|
1401
|
-
const sortFieldMap: Record<
|
|
1402
|
-
name:
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
createdAt: metricsTable.created_at,
|
|
1430
|
+
const sortInput = options.sort ?? { field: 'startTimeUnixNano', order: 'asc' }
|
|
1431
|
+
const sortFieldMap: Record<SpanSort['field'], any> = {
|
|
1432
|
+
name: spansTable.name,
|
|
1433
|
+
startTimeUnixNano: spansTable.start_time_unix_nano,
|
|
1434
|
+
endTimeUnixNano: spansTable.end_time_unix_nano,
|
|
1406
1435
|
}
|
|
1407
1436
|
|
|
1408
1437
|
// Get total count
|
|
1409
|
-
const total = await this.db.$count(
|
|
1438
|
+
const total = await this.db.$count(spansTable, where)
|
|
1410
1439
|
if (!total) {
|
|
1411
1440
|
return {
|
|
1412
|
-
|
|
1441
|
+
spans: [],
|
|
1413
1442
|
total: 0,
|
|
1414
1443
|
}
|
|
1415
1444
|
}
|
|
@@ -1417,62 +1446,96 @@ export class PostgresBaseAdapter<Database extends DrizzleDatabase, Connection> e
|
|
|
1417
1446
|
const sortField = sortFieldMap[sortInput.field]
|
|
1418
1447
|
const orderByClause = sortInput.order === 'asc' ? asc(sortField) : desc(sortField)
|
|
1419
1448
|
|
|
1420
|
-
const
|
|
1449
|
+
const rows = await this.db
|
|
1421
1450
|
.select({
|
|
1422
|
-
id:
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1451
|
+
id: spansTable.id,
|
|
1452
|
+
traceId: spansTable.trace_id,
|
|
1453
|
+
spanId: spansTable.span_id,
|
|
1454
|
+
parentSpanId: spansTable.parent_span_id,
|
|
1455
|
+
jobId: spansTable.job_id,
|
|
1456
|
+
stepId: spansTable.step_id,
|
|
1457
|
+
name: spansTable.name,
|
|
1458
|
+
kind: spansTable.kind,
|
|
1459
|
+
startTimeUnixNano: spansTable.start_time_unix_nano,
|
|
1460
|
+
endTimeUnixNano: spansTable.end_time_unix_nano,
|
|
1461
|
+
statusCode: spansTable.status_code,
|
|
1462
|
+
statusMessage: spansTable.status_message,
|
|
1463
|
+
attributes: spansTable.attributes,
|
|
1464
|
+
events: spansTable.events,
|
|
1431
1465
|
})
|
|
1432
|
-
.from(
|
|
1466
|
+
.from(spansTable)
|
|
1433
1467
|
.where(where)
|
|
1434
1468
|
.orderBy(orderByClause)
|
|
1435
1469
|
|
|
1470
|
+
// Cast kind and statusCode to proper types, convert BigInt to string for JSON serialization
|
|
1471
|
+
const spans = rows.map((row) => ({
|
|
1472
|
+
...row,
|
|
1473
|
+
kind: row.kind as 0 | 1 | 2 | 3 | 4,
|
|
1474
|
+
statusCode: row.statusCode as 0 | 1 | 2,
|
|
1475
|
+
// Convert BigInt to string for JSON serialization
|
|
1476
|
+
startTimeUnixNano: row.startTimeUnixNano?.toString() ?? null,
|
|
1477
|
+
endTimeUnixNano: row.endTimeUnixNano?.toString() ?? null,
|
|
1478
|
+
}))
|
|
1479
|
+
|
|
1436
1480
|
return {
|
|
1437
|
-
|
|
1481
|
+
spans,
|
|
1438
1482
|
total,
|
|
1439
1483
|
}
|
|
1440
1484
|
}
|
|
1441
1485
|
|
|
1442
1486
|
/**
|
|
1443
|
-
* Internal method to delete all
|
|
1487
|
+
* Internal method to delete all spans for a job.
|
|
1444
1488
|
*/
|
|
1445
|
-
protected async
|
|
1489
|
+
protected async _deleteSpans(options: DeleteSpansOptions): Promise<number> {
|
|
1446
1490
|
const result = await this.db
|
|
1447
|
-
.delete(this.tables.
|
|
1448
|
-
.where(eq(this.tables.
|
|
1449
|
-
.returning({ id: this.tables.
|
|
1491
|
+
.delete(this.tables.spansTable)
|
|
1492
|
+
.where(eq(this.tables.spansTable.job_id, options.jobId))
|
|
1493
|
+
.returning({ id: this.tables.spansTable.id })
|
|
1450
1494
|
|
|
1451
1495
|
return result.length
|
|
1452
1496
|
}
|
|
1453
1497
|
|
|
1454
1498
|
/**
|
|
1455
|
-
* Build WHERE clause for
|
|
1499
|
+
* Build WHERE clause for spans queries.
|
|
1500
|
+
* When querying by jobId or stepId, we find all spans that share the same trace_id
|
|
1501
|
+
* as spans with that job/step. This includes spans from external libraries that
|
|
1502
|
+
* don't have the duron.job.id attribute but are part of the same trace.
|
|
1456
1503
|
*/
|
|
1457
|
-
protected
|
|
1458
|
-
const
|
|
1504
|
+
protected _buildSpansWhereClause(jobId?: string, stepId?: string, filters?: GetSpansOptions['filters']) {
|
|
1505
|
+
const spansTable = this.tables.spansTable
|
|
1506
|
+
|
|
1507
|
+
// Build condition for finding spans by trace_id (includes external spans)
|
|
1508
|
+
let traceCondition: ReturnType<typeof eq> | undefined
|
|
1509
|
+
|
|
1510
|
+
if (jobId) {
|
|
1511
|
+
// Find all spans that share a trace_id with any span that has this job_id
|
|
1512
|
+
// This includes external spans (like from AI SDK) that don't have duron.job.id
|
|
1513
|
+
traceCondition = inArray(
|
|
1514
|
+
spansTable.trace_id,
|
|
1515
|
+
this.db.select({ traceId: spansTable.trace_id }).from(spansTable).where(eq(spansTable.job_id, jobId)),
|
|
1516
|
+
)
|
|
1517
|
+
} else if (stepId) {
|
|
1518
|
+
// Find all spans that share a trace_id with any span that has this step_id
|
|
1519
|
+
traceCondition = inArray(
|
|
1520
|
+
spansTable.trace_id,
|
|
1521
|
+
this.db.select({ traceId: spansTable.trace_id }).from(spansTable).where(eq(spansTable.step_id, stepId)),
|
|
1522
|
+
)
|
|
1523
|
+
}
|
|
1459
1524
|
|
|
1460
1525
|
return and(
|
|
1461
|
-
|
|
1462
|
-
stepId ? eq(metricsTable.step_id, stepId) : undefined,
|
|
1526
|
+
traceCondition,
|
|
1463
1527
|
filters?.name
|
|
1464
1528
|
? Array.isArray(filters.name)
|
|
1465
|
-
? or(...filters.name.map((n) => ilike(
|
|
1466
|
-
: ilike(
|
|
1467
|
-
: undefined,
|
|
1468
|
-
filters?.type
|
|
1469
|
-
? inArray(metricsTable.type, Array.isArray(filters.type) ? filters.type : [filters.type])
|
|
1529
|
+
? or(...filters.name.map((n) => ilike(spansTable.name, `%${n}%`)))
|
|
1530
|
+
: ilike(spansTable.name, `%${filters.name}%`)
|
|
1470
1531
|
: undefined,
|
|
1471
|
-
filters?.
|
|
1472
|
-
|
|
1532
|
+
filters?.kind ? inArray(spansTable.kind, Array.isArray(filters.kind) ? filters.kind : [filters.kind]) : undefined,
|
|
1533
|
+
filters?.statusCode
|
|
1534
|
+
? inArray(spansTable.status_code, Array.isArray(filters.statusCode) ? filters.statusCode : [filters.statusCode])
|
|
1473
1535
|
: undefined,
|
|
1536
|
+
filters?.traceId ? eq(spansTable.trace_id, filters.traceId) : undefined,
|
|
1474
1537
|
...(filters?.attributesFilter && Object.keys(filters.attributesFilter).length > 0
|
|
1475
|
-
? this.#buildJsonbWhereConditions(filters.attributesFilter,
|
|
1538
|
+
? this.#buildJsonbWhereConditions(filters.attributesFilter, spansTable.attributes)
|
|
1476
1539
|
: []),
|
|
1477
1540
|
)
|
|
1478
1541
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createSchema from './schema.js'
|
|
2
2
|
|
|
3
|
-
const { schema, jobsTable, jobStepsTable,
|
|
3
|
+
const { schema, jobsTable, jobStepsTable, spansTable } = createSchema('duron')
|
|
4
4
|
|
|
5
|
-
export { schema, jobsTable, jobStepsTable,
|
|
5
|
+
export { schema, jobsTable, jobStepsTable, spansTable }
|