duron 0.1.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/LICENSE +7 -0
- package/README.md +140 -0
- package/dist/action-job.d.ts +24 -0
- package/dist/action-job.d.ts.map +1 -0
- package/dist/action-job.js +108 -0
- package/dist/action-manager.d.ts +21 -0
- package/dist/action-manager.d.ts.map +1 -0
- package/dist/action-manager.js +78 -0
- package/dist/action.d.ts +129 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +87 -0
- package/dist/adapters/adapter.d.ts +92 -0
- package/dist/adapters/adapter.d.ts.map +1 -0
- package/dist/adapters/adapter.js +424 -0
- package/dist/adapters/postgres/drizzle.config.d.ts +3 -0
- package/dist/adapters/postgres/drizzle.config.d.ts.map +1 -0
- package/dist/adapters/postgres/drizzle.config.js +10 -0
- package/dist/adapters/postgres/pglite.d.ts +13 -0
- package/dist/adapters/postgres/pglite.d.ts.map +1 -0
- package/dist/adapters/postgres/pglite.js +36 -0
- package/dist/adapters/postgres/postgres.d.ts +51 -0
- package/dist/adapters/postgres/postgres.d.ts.map +1 -0
- package/dist/adapters/postgres/postgres.js +867 -0
- package/dist/adapters/postgres/schema.d.ts +581 -0
- package/dist/adapters/postgres/schema.d.ts.map +1 -0
- package/dist/adapters/postgres/schema.default.d.ts +577 -0
- package/dist/adapters/postgres/schema.default.d.ts.map +1 -0
- package/dist/adapters/postgres/schema.default.js +3 -0
- package/dist/adapters/postgres/schema.js +87 -0
- package/dist/adapters/schemas.d.ts +516 -0
- package/dist/adapters/schemas.d.ts.map +1 -0
- package/dist/adapters/schemas.js +184 -0
- package/dist/client.d.ts +85 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +416 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +22 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +75 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/server.d.ts +1193 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +516 -0
- package/dist/step-manager.d.ts +46 -0
- package/dist/step-manager.d.ts.map +1 -0
- package/dist/step-manager.js +216 -0
- package/dist/utils/checksum.d.ts +2 -0
- package/dist/utils/checksum.d.ts.map +1 -0
- package/dist/utils/checksum.js +6 -0
- package/dist/utils/p-retry.d.ts +19 -0
- package/dist/utils/p-retry.d.ts.map +1 -0
- package/dist/utils/p-retry.js +130 -0
- package/dist/utils/wait-for-abort.d.ts +5 -0
- package/dist/utils/wait-for-abort.d.ts.map +1 -0
- package/dist/utils/wait-for-abort.js +32 -0
- package/migrations/postgres/0000_lethal_speed_demon.sql +64 -0
- package/migrations/postgres/meta/0000_snapshot.json +606 -0
- package/migrations/postgres/meta/_journal.json +13 -0
- package/package.json +88 -0
- package/src/action-job.ts +201 -0
- package/src/action-manager.ts +166 -0
- package/src/action.ts +247 -0
- package/src/adapters/adapter.ts +969 -0
- package/src/adapters/postgres/drizzle.config.ts +11 -0
- package/src/adapters/postgres/pglite.ts +86 -0
- package/src/adapters/postgres/postgres.ts +1346 -0
- package/src/adapters/postgres/schema.default.ts +5 -0
- package/src/adapters/postgres/schema.ts +119 -0
- package/src/adapters/schemas.ts +320 -0
- package/src/client.ts +859 -0
- package/src/constants.ts +37 -0
- package/src/errors.ts +205 -0
- package/src/index.ts +14 -0
- package/src/server.ts +718 -0
- package/src/step-manager.ts +471 -0
- package/src/utils/checksum.ts +7 -0
- package/src/utils/p-retry.ts +213 -0
- package/src/utils/wait-for-abort.ts +40 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { JOB_STATUSES, STEP_STATUSES } from '../constants.js';
|
|
3
|
+
export const JobStatusSchema = z.enum(JOB_STATUSES);
|
|
4
|
+
export const StepStatusSchema = z.enum(STEP_STATUSES);
|
|
5
|
+
const DateSchema = z.union([
|
|
6
|
+
z.date(),
|
|
7
|
+
z.string().transform((str) => new Date(str)),
|
|
8
|
+
z.number().transform((num) => new Date(num)),
|
|
9
|
+
]);
|
|
10
|
+
export const SerializableErrorSchema = z.object({
|
|
11
|
+
name: z.string(),
|
|
12
|
+
message: z.string(),
|
|
13
|
+
cause: z.any().optional(),
|
|
14
|
+
stack: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
export const JobSchema = z.object({
|
|
17
|
+
id: z.string(),
|
|
18
|
+
actionName: z.string(),
|
|
19
|
+
groupKey: z.string(),
|
|
20
|
+
input: z.any(),
|
|
21
|
+
output: z.any().nullable(),
|
|
22
|
+
error: z.any().nullable(),
|
|
23
|
+
status: JobStatusSchema,
|
|
24
|
+
timeoutMs: z.coerce.number(),
|
|
25
|
+
expiresAt: DateSchema.nullable(),
|
|
26
|
+
startedAt: DateSchema.nullable().default(null),
|
|
27
|
+
finishedAt: DateSchema.nullable().default(null),
|
|
28
|
+
createdAt: DateSchema,
|
|
29
|
+
updatedAt: DateSchema,
|
|
30
|
+
concurrencyLimit: z.coerce.number(),
|
|
31
|
+
});
|
|
32
|
+
export const JobStepSchema = z.object({
|
|
33
|
+
id: z.string(),
|
|
34
|
+
jobId: z.string(),
|
|
35
|
+
name: z.string(),
|
|
36
|
+
output: z.any().nullable().default(null),
|
|
37
|
+
status: StepStatusSchema,
|
|
38
|
+
error: z.any().nullable().default(null),
|
|
39
|
+
startedAt: DateSchema,
|
|
40
|
+
finishedAt: DateSchema.nullable().default(null),
|
|
41
|
+
timeoutMs: z.coerce.number(),
|
|
42
|
+
expiresAt: DateSchema.nullable().default(null),
|
|
43
|
+
retriesLimit: z.coerce.number(),
|
|
44
|
+
retriesCount: z.coerce.number(),
|
|
45
|
+
delayedMs: z.coerce.number().nullable().default(null),
|
|
46
|
+
historyFailedAttempts: z.record(z.string(), z.object({ failedAt: DateSchema, error: SerializableErrorSchema, delayedMs: z.coerce.number() })),
|
|
47
|
+
createdAt: DateSchema,
|
|
48
|
+
updatedAt: DateSchema,
|
|
49
|
+
});
|
|
50
|
+
export const JobStepWithoutOutputSchema = JobStepSchema.omit({ output: true });
|
|
51
|
+
export const SortOrderSchema = z.enum(['asc', 'desc']);
|
|
52
|
+
export const JobSortFieldSchema = z.enum(['createdAt', 'startedAt', 'finishedAt', 'status', 'actionName', 'expiresAt']);
|
|
53
|
+
export const JobSortSchema = z.object({
|
|
54
|
+
field: JobSortFieldSchema,
|
|
55
|
+
order: SortOrderSchema,
|
|
56
|
+
});
|
|
57
|
+
export const JobFiltersSchema = z.object({
|
|
58
|
+
status: z.union([JobStatusSchema, z.array(JobStatusSchema)]).optional(),
|
|
59
|
+
actionName: z.union([z.string(), z.array(z.string())]).optional(),
|
|
60
|
+
groupKey: z.union([z.string(), z.array(z.string())]).optional(),
|
|
61
|
+
ownerId: z.union([z.string(), z.array(z.string())]).optional(),
|
|
62
|
+
createdAt: z.union([DateSchema, z.array(DateSchema).length(2)]).optional(),
|
|
63
|
+
startedAt: z.union([DateSchema, z.array(DateSchema).length(2)]).optional(),
|
|
64
|
+
finishedAt: z.union([DateSchema, z.array(DateSchema).length(2)]).optional(),
|
|
65
|
+
updatedAfter: DateSchema.optional(),
|
|
66
|
+
inputFilter: z.record(z.string(), z.any()).optional(),
|
|
67
|
+
outputFilter: z.record(z.string(), z.any()).optional(),
|
|
68
|
+
search: z.string().optional(),
|
|
69
|
+
});
|
|
70
|
+
export const GetJobsOptionsSchema = z.object({
|
|
71
|
+
page: z.number().int().positive().optional(),
|
|
72
|
+
pageSize: z.number().int().positive().optional(),
|
|
73
|
+
filters: JobFiltersSchema.optional(),
|
|
74
|
+
sort: z.union([JobSortSchema, z.array(JobSortSchema)]).optional(),
|
|
75
|
+
});
|
|
76
|
+
export const GetJobStepsOptionsSchema = z.object({
|
|
77
|
+
jobId: z.string(),
|
|
78
|
+
page: z.number().int().positive().optional(),
|
|
79
|
+
pageSize: z.number().int().positive().optional(),
|
|
80
|
+
search: z.string().optional(),
|
|
81
|
+
updatedAfter: DateSchema.optional(),
|
|
82
|
+
});
|
|
83
|
+
export const CreateJobOptionsSchema = z.object({
|
|
84
|
+
queue: z.string(),
|
|
85
|
+
groupKey: z.string(),
|
|
86
|
+
checksum: z.string(),
|
|
87
|
+
input: z.any(),
|
|
88
|
+
timeoutMs: z.number(),
|
|
89
|
+
concurrencyLimit: z.number(),
|
|
90
|
+
});
|
|
91
|
+
export const RecoverJobsOptionsSchema = z.object({
|
|
92
|
+
checksums: z.array(z.string()),
|
|
93
|
+
multiProcessMode: z.boolean().optional(),
|
|
94
|
+
processTimeout: z.number().optional(),
|
|
95
|
+
});
|
|
96
|
+
export const FetchOptionsSchema = z.object({
|
|
97
|
+
batch: z.number(),
|
|
98
|
+
});
|
|
99
|
+
export const CompleteJobOptionsSchema = z.object({
|
|
100
|
+
jobId: z.string(),
|
|
101
|
+
output: z.any(),
|
|
102
|
+
});
|
|
103
|
+
export const FailJobOptionsSchema = z.object({
|
|
104
|
+
jobId: z.string(),
|
|
105
|
+
error: z.any(),
|
|
106
|
+
});
|
|
107
|
+
export const CancelJobOptionsSchema = z.object({
|
|
108
|
+
jobId: z.string(),
|
|
109
|
+
});
|
|
110
|
+
export const RetryJobOptionsSchema = z.object({
|
|
111
|
+
jobId: z.string(),
|
|
112
|
+
});
|
|
113
|
+
export const DeleteJobOptionsSchema = z.object({
|
|
114
|
+
jobId: z.string(),
|
|
115
|
+
});
|
|
116
|
+
export const DeleteJobsOptionsSchema = GetJobsOptionsSchema.optional();
|
|
117
|
+
export const CreateOrRecoverJobStepOptionsSchema = z.object({
|
|
118
|
+
jobId: z.string(),
|
|
119
|
+
name: z.string(),
|
|
120
|
+
timeoutMs: z.number(),
|
|
121
|
+
retriesLimit: z.number(),
|
|
122
|
+
});
|
|
123
|
+
export const CompleteJobStepOptionsSchema = z.object({
|
|
124
|
+
stepId: z.string(),
|
|
125
|
+
output: z.any(),
|
|
126
|
+
});
|
|
127
|
+
export const FailJobStepOptionsSchema = z.object({
|
|
128
|
+
stepId: z.string(),
|
|
129
|
+
error: z.any(),
|
|
130
|
+
});
|
|
131
|
+
export const DelayJobStepOptionsSchema = z.object({
|
|
132
|
+
stepId: z.string(),
|
|
133
|
+
delayMs: z.number(),
|
|
134
|
+
error: z.any(),
|
|
135
|
+
});
|
|
136
|
+
export const CancelJobStepOptionsSchema = z.object({
|
|
137
|
+
stepId: z.string(),
|
|
138
|
+
});
|
|
139
|
+
export const CreateOrRecoverJobStepResultSchema = z.object({
|
|
140
|
+
id: z.string(),
|
|
141
|
+
status: StepStatusSchema,
|
|
142
|
+
retriesLimit: z.number(),
|
|
143
|
+
retriesCount: z.number(),
|
|
144
|
+
timeoutMs: z.number(),
|
|
145
|
+
error: z.any().nullable(),
|
|
146
|
+
output: z.any().nullable(),
|
|
147
|
+
isNew: z.boolean(),
|
|
148
|
+
});
|
|
149
|
+
export const JobIdResultSchema = z.union([z.string(), z.null()]);
|
|
150
|
+
export const BooleanResultSchema = z.boolean();
|
|
151
|
+
export const NumberResultSchema = z.number();
|
|
152
|
+
export const JobsArrayResultSchema = z.array(JobSchema);
|
|
153
|
+
export const CreateOrRecoverJobStepResultNullableSchema = z.union([CreateOrRecoverJobStepResultSchema, z.null()]);
|
|
154
|
+
export const GetJobsResultSchema = z.object({
|
|
155
|
+
jobs: z.array(JobSchema),
|
|
156
|
+
total: z.number().int().nonnegative(),
|
|
157
|
+
page: z.number().int().positive(),
|
|
158
|
+
pageSize: z.number().int().positive(),
|
|
159
|
+
});
|
|
160
|
+
export const GetJobStepsResultSchema = z.object({
|
|
161
|
+
steps: z.array(JobStepWithoutOutputSchema),
|
|
162
|
+
total: z.number().int().nonnegative(),
|
|
163
|
+
page: z.number().int().positive(),
|
|
164
|
+
pageSize: z.number().int().positive(),
|
|
165
|
+
});
|
|
166
|
+
export const ActionStatsSchema = z.object({
|
|
167
|
+
name: z.string(),
|
|
168
|
+
lastJobCreated: DateSchema.nullable(),
|
|
169
|
+
active: z.number().int().nonnegative(),
|
|
170
|
+
completed: z.number().int().nonnegative(),
|
|
171
|
+
failed: z.number().int().nonnegative(),
|
|
172
|
+
cancelled: z.number().int().nonnegative(),
|
|
173
|
+
});
|
|
174
|
+
export const GetActionsResultSchema = z.object({
|
|
175
|
+
actions: z.array(ActionStatsSchema),
|
|
176
|
+
});
|
|
177
|
+
export const JobStatusResultSchema = z.object({
|
|
178
|
+
status: JobStatusSchema,
|
|
179
|
+
updatedAt: DateSchema,
|
|
180
|
+
});
|
|
181
|
+
export const JobStepStatusResultSchema = z.object({
|
|
182
|
+
status: StepStatusSchema,
|
|
183
|
+
updatedAt: DateSchema,
|
|
184
|
+
});
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import pino, { type Logger } from 'pino';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import type { Action } from './action.js';
|
|
4
|
+
import type { Adapter, GetActionsResult, GetJobStepsOptions, GetJobStepsResult, GetJobsOptions, GetJobsResult, Job, JobStep } from './adapters/adapter.js';
|
|
5
|
+
import type { JobStatusResult, JobStepStatusResult } from './adapters/schemas.js';
|
|
6
|
+
declare const BaseOptionsSchema: z.ZodObject<{
|
|
7
|
+
id: z.ZodOptional<z.ZodString>;
|
|
8
|
+
syncPattern: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<"pull">, z.ZodLiteral<"push">, z.ZodLiteral<"hybrid">, z.ZodLiteral<false>]>>;
|
|
9
|
+
pullInterval: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
batchSize: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
actionConcurrencyLimit: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
groupConcurrencyLimit: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
migrateOnStart: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
recoverJobsOnStart: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
multiProcessMode: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
processTimeout: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export interface ClientOptions<TActions extends Record<string, Action<any, any, TVariables>>, TVariables = Record<string, unknown>> extends z.input<typeof BaseOptionsSchema> {
|
|
19
|
+
database: Adapter;
|
|
20
|
+
actions?: TActions;
|
|
21
|
+
logger?: Logger | 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent';
|
|
22
|
+
variables?: TVariables;
|
|
23
|
+
}
|
|
24
|
+
interface FetchOptions {
|
|
25
|
+
batchSize?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class Client<TActions extends Record<string, Action<any, any, TVariables>>, TVariables = Record<string, unknown>> {
|
|
28
|
+
#private;
|
|
29
|
+
constructor(options: ClientOptions<TActions, TVariables>);
|
|
30
|
+
get logger(): pino.Logger;
|
|
31
|
+
getConfig(): {
|
|
32
|
+
actions: TActions | null;
|
|
33
|
+
variables: Record<string, unknown>;
|
|
34
|
+
syncPattern: false | "push" | "pull" | "hybrid";
|
|
35
|
+
pullInterval: number;
|
|
36
|
+
batchSize: number;
|
|
37
|
+
actionConcurrencyLimit: number;
|
|
38
|
+
groupConcurrencyLimit: number;
|
|
39
|
+
migrateOnStart: boolean;
|
|
40
|
+
recoverJobsOnStart: boolean;
|
|
41
|
+
multiProcessMode: boolean;
|
|
42
|
+
processTimeout: number;
|
|
43
|
+
id?: string | undefined;
|
|
44
|
+
};
|
|
45
|
+
runAction<TActionName extends keyof TActions>(actionName: TActionName, input?: NonNullable<TActions[TActionName]['input']> extends z.ZodObject ? z.input<NonNullable<TActions[TActionName]['input']>> : never): Promise<string>;
|
|
46
|
+
fetch(options: FetchOptions): Promise<{
|
|
47
|
+
id: string;
|
|
48
|
+
actionName: string;
|
|
49
|
+
groupKey: string;
|
|
50
|
+
input: any;
|
|
51
|
+
output: any;
|
|
52
|
+
error: any;
|
|
53
|
+
status: "created" | "active" | "completed" | "failed" | "cancelled";
|
|
54
|
+
timeoutMs: number;
|
|
55
|
+
expiresAt: Date | null;
|
|
56
|
+
startedAt: Date | null;
|
|
57
|
+
finishedAt: Date | null;
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
updatedAt: Date;
|
|
60
|
+
concurrencyLimit: number;
|
|
61
|
+
}[]>;
|
|
62
|
+
cancelJob(jobId: string): Promise<boolean>;
|
|
63
|
+
retryJob(jobId: string): Promise<string | null>;
|
|
64
|
+
deleteJob(jobId: string): Promise<boolean>;
|
|
65
|
+
deleteJobs(options?: GetJobsOptions): Promise<number>;
|
|
66
|
+
getJobById(jobId: string): Promise<Job | null>;
|
|
67
|
+
getJobSteps(options: GetJobStepsOptions): Promise<GetJobStepsResult>;
|
|
68
|
+
getJobs(options?: GetJobsOptions): Promise<GetJobsResult>;
|
|
69
|
+
getJobStepById(stepId: string): Promise<JobStep | null>;
|
|
70
|
+
getJobStatus(jobId: string): Promise<JobStatusResult | null>;
|
|
71
|
+
getJobStepStatus(stepId: string): Promise<JobStepStatusResult | null>;
|
|
72
|
+
waitForJob(jobId: string, options?: {
|
|
73
|
+
timeout?: number;
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
}): Promise<Job | null>;
|
|
76
|
+
getActions(): Promise<GetActionsResult>;
|
|
77
|
+
getActionsMetadata(): Promise<Array<{
|
|
78
|
+
name: string;
|
|
79
|
+
mockInput: any;
|
|
80
|
+
}>>;
|
|
81
|
+
start(): Promise<boolean>;
|
|
82
|
+
stop(): Promise<boolean>;
|
|
83
|
+
}
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import pino, {} from 'pino';
|
|
2
|
+
import { zocker } from 'zocker';
|
|
3
|
+
import * as z from 'zod';
|
|
4
|
+
import { ActionManager } from './action-manager.js';
|
|
5
|
+
import { JOB_STATUS_CANCELLED, JOB_STATUS_COMPLETED, JOB_STATUS_FAILED } from './constants.js';
|
|
6
|
+
const BaseOptionsSchema = z.object({
|
|
7
|
+
id: z.string().optional(),
|
|
8
|
+
syncPattern: z.union([z.literal('pull'), z.literal('push'), z.literal('hybrid'), z.literal(false)]).default('hybrid'),
|
|
9
|
+
pullInterval: z.number().default(5_000),
|
|
10
|
+
batchSize: z.number().default(10),
|
|
11
|
+
actionConcurrencyLimit: z.number().default(100),
|
|
12
|
+
groupConcurrencyLimit: z.number().default(10),
|
|
13
|
+
migrateOnStart: z.boolean().default(true),
|
|
14
|
+
recoverJobsOnStart: z.boolean().default(true),
|
|
15
|
+
multiProcessMode: z.boolean().default(false),
|
|
16
|
+
processTimeout: z.number().default(5 * 60 * 1000),
|
|
17
|
+
});
|
|
18
|
+
export class Client {
|
|
19
|
+
#options;
|
|
20
|
+
#id;
|
|
21
|
+
#actions;
|
|
22
|
+
#database;
|
|
23
|
+
#variables;
|
|
24
|
+
#logger;
|
|
25
|
+
#started = false;
|
|
26
|
+
#stopped = false;
|
|
27
|
+
#starting = null;
|
|
28
|
+
#stopping = null;
|
|
29
|
+
#pullInterval = null;
|
|
30
|
+
#actionManagers = new Map();
|
|
31
|
+
#mockInputSchemas = new Map();
|
|
32
|
+
#pendingJobWaits = new Map();
|
|
33
|
+
#jobStatusListenerSetup = false;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.#options = BaseOptionsSchema.parse(options);
|
|
36
|
+
this.#id = options.id ?? globalThis.crypto.randomUUID();
|
|
37
|
+
this.#database = options.database;
|
|
38
|
+
this.#actions = options.actions ?? null;
|
|
39
|
+
this.#variables = options?.variables ?? {};
|
|
40
|
+
this.#logger = this.#normalizeLogger(options?.logger);
|
|
41
|
+
this.#database.setId(this.#id);
|
|
42
|
+
this.#database.setLogger(this.#logger);
|
|
43
|
+
}
|
|
44
|
+
#normalizeLogger(logger) {
|
|
45
|
+
let pinoInstance = null;
|
|
46
|
+
if (!logger) {
|
|
47
|
+
pinoInstance = pino({ level: 'error' });
|
|
48
|
+
}
|
|
49
|
+
else if (typeof logger === 'string') {
|
|
50
|
+
pinoInstance = pino({ level: logger });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
pinoInstance = logger;
|
|
54
|
+
}
|
|
55
|
+
return pinoInstance.child({ duron: this.#id });
|
|
56
|
+
}
|
|
57
|
+
get logger() {
|
|
58
|
+
return this.#logger;
|
|
59
|
+
}
|
|
60
|
+
getConfig() {
|
|
61
|
+
return {
|
|
62
|
+
...this.#options,
|
|
63
|
+
actions: this.#actions,
|
|
64
|
+
variables: this.#variables,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async runAction(actionName, input) {
|
|
68
|
+
await this.start();
|
|
69
|
+
const action = this.#actions?.[actionName];
|
|
70
|
+
if (!action) {
|
|
71
|
+
throw new Error(`Action ${String(actionName)} not found`);
|
|
72
|
+
}
|
|
73
|
+
let validatedInput = input ?? {};
|
|
74
|
+
if (action.input) {
|
|
75
|
+
validatedInput = action.input.parse(validatedInput, {
|
|
76
|
+
error: () => 'Error parsing action input',
|
|
77
|
+
reportInput: true,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const concurrencyCtx = {
|
|
81
|
+
input: validatedInput,
|
|
82
|
+
var: this.#variables,
|
|
83
|
+
};
|
|
84
|
+
let groupKey = '@default';
|
|
85
|
+
if (action.groups?.groupKey) {
|
|
86
|
+
groupKey = await action.groups.groupKey(concurrencyCtx);
|
|
87
|
+
}
|
|
88
|
+
let concurrencyLimit = this.#options.groupConcurrencyLimit;
|
|
89
|
+
if (action.groups?.concurrency) {
|
|
90
|
+
concurrencyLimit = await action.groups.concurrency(concurrencyCtx);
|
|
91
|
+
}
|
|
92
|
+
const jobId = await this.#database.createJob({
|
|
93
|
+
queue: action.name,
|
|
94
|
+
groupKey,
|
|
95
|
+
input: validatedInput,
|
|
96
|
+
timeoutMs: action.expire,
|
|
97
|
+
checksum: action.checksum,
|
|
98
|
+
concurrencyLimit,
|
|
99
|
+
});
|
|
100
|
+
if (!jobId) {
|
|
101
|
+
throw new Error(`Failed to create job for action ${String(actionName)}`);
|
|
102
|
+
}
|
|
103
|
+
this.#logger.debug({ jobId, actionName: String(actionName), groupKey }, '[Duron] Action sent/created');
|
|
104
|
+
return jobId;
|
|
105
|
+
}
|
|
106
|
+
async fetch(options) {
|
|
107
|
+
await this.start();
|
|
108
|
+
if (!this.#actions) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const jobs = await this.#database.fetch({
|
|
112
|
+
batch: options.batchSize ?? this.#options.batchSize,
|
|
113
|
+
});
|
|
114
|
+
for (const job of jobs) {
|
|
115
|
+
this.#executeJob(job);
|
|
116
|
+
}
|
|
117
|
+
return jobs;
|
|
118
|
+
}
|
|
119
|
+
async cancelJob(jobId) {
|
|
120
|
+
await this.start();
|
|
121
|
+
let cancelled = false;
|
|
122
|
+
for (const manager of this.#actionManagers.values()) {
|
|
123
|
+
cancelled = manager.cancelJob(jobId);
|
|
124
|
+
if (cancelled) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!cancelled) {
|
|
129
|
+
await this.#database.cancelJob({ jobId });
|
|
130
|
+
}
|
|
131
|
+
return cancelled;
|
|
132
|
+
}
|
|
133
|
+
async retryJob(jobId) {
|
|
134
|
+
await this.start();
|
|
135
|
+
return this.#database.retryJob({ jobId });
|
|
136
|
+
}
|
|
137
|
+
async deleteJob(jobId) {
|
|
138
|
+
await this.start();
|
|
139
|
+
return this.#database.deleteJob({ jobId });
|
|
140
|
+
}
|
|
141
|
+
async deleteJobs(options) {
|
|
142
|
+
await this.start();
|
|
143
|
+
return this.#database.deleteJobs(options);
|
|
144
|
+
}
|
|
145
|
+
async getJobById(jobId) {
|
|
146
|
+
await this.start();
|
|
147
|
+
return this.#database.getJobById(jobId);
|
|
148
|
+
}
|
|
149
|
+
async getJobSteps(options) {
|
|
150
|
+
await this.start();
|
|
151
|
+
return this.#database.getJobSteps(options);
|
|
152
|
+
}
|
|
153
|
+
async getJobs(options) {
|
|
154
|
+
await this.start();
|
|
155
|
+
return this.#database.getJobs(options);
|
|
156
|
+
}
|
|
157
|
+
async getJobStepById(stepId) {
|
|
158
|
+
await this.start();
|
|
159
|
+
return this.#database.getJobStepById(stepId);
|
|
160
|
+
}
|
|
161
|
+
async getJobStatus(jobId) {
|
|
162
|
+
await this.start();
|
|
163
|
+
return this.#database.getJobStatus(jobId);
|
|
164
|
+
}
|
|
165
|
+
async getJobStepStatus(stepId) {
|
|
166
|
+
await this.start();
|
|
167
|
+
return this.#database.getJobStepStatus(stepId);
|
|
168
|
+
}
|
|
169
|
+
async waitForJob(jobId, options) {
|
|
170
|
+
await this.start();
|
|
171
|
+
const existingJobStatus = await this.getJobStatus(jobId);
|
|
172
|
+
if (existingJobStatus) {
|
|
173
|
+
const terminalStatuses = [JOB_STATUS_COMPLETED, JOB_STATUS_FAILED, JOB_STATUS_CANCELLED];
|
|
174
|
+
if (terminalStatuses.includes(existingJobStatus.status)) {
|
|
175
|
+
const job = await this.getJobById(jobId);
|
|
176
|
+
if (!job) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return job;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
this.#setupJobStatusListener();
|
|
183
|
+
return new Promise((resolve) => {
|
|
184
|
+
if (options?.signal?.aborted) {
|
|
185
|
+
resolve(null);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
let timeoutId;
|
|
189
|
+
let abortHandler;
|
|
190
|
+
if (options?.timeout) {
|
|
191
|
+
timeoutId = setTimeout(() => {
|
|
192
|
+
this.#removeJobWait(jobId, resolve);
|
|
193
|
+
resolve(null);
|
|
194
|
+
}, options.timeout);
|
|
195
|
+
}
|
|
196
|
+
if (options?.signal) {
|
|
197
|
+
abortHandler = () => {
|
|
198
|
+
this.#removeJobWait(jobId, resolve);
|
|
199
|
+
resolve(null);
|
|
200
|
+
};
|
|
201
|
+
options.signal.addEventListener('abort', abortHandler);
|
|
202
|
+
}
|
|
203
|
+
if (!this.#pendingJobWaits.has(jobId)) {
|
|
204
|
+
this.#pendingJobWaits.set(jobId, new Set());
|
|
205
|
+
}
|
|
206
|
+
this.#pendingJobWaits.get(jobId).add({
|
|
207
|
+
resolve,
|
|
208
|
+
timeoutId,
|
|
209
|
+
signal: options?.signal,
|
|
210
|
+
abortHandler,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async getActions() {
|
|
215
|
+
await this.start();
|
|
216
|
+
return this.#database.getActions();
|
|
217
|
+
}
|
|
218
|
+
async getActionsMetadata() {
|
|
219
|
+
await this.start();
|
|
220
|
+
if (!this.#actions) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
return Object.values(this.#actions).map((action) => {
|
|
224
|
+
let mockInput = {};
|
|
225
|
+
if (action.input) {
|
|
226
|
+
if (!this.#mockInputSchemas.has(action.name)) {
|
|
227
|
+
this.#mockInputSchemas.set(action.name, zocker(action.input)
|
|
228
|
+
.override(z.ZodString, 'string')
|
|
229
|
+
.generate());
|
|
230
|
+
}
|
|
231
|
+
mockInput = this.#mockInputSchemas.get(action.name);
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
name: action.name,
|
|
235
|
+
mockInput,
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async start() {
|
|
240
|
+
if (this.#stopping || this.#stopped) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (this.#started) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (this.#starting) {
|
|
247
|
+
return this.#starting;
|
|
248
|
+
}
|
|
249
|
+
this.#starting = (async () => {
|
|
250
|
+
const dbStarted = await this.#database.start();
|
|
251
|
+
if (!dbStarted) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
if (this.#actions) {
|
|
255
|
+
if (this.#options.recoverJobsOnStart) {
|
|
256
|
+
await this.#database.recoverJobs({
|
|
257
|
+
checksums: Object.values(this.#actions).map((action) => action.checksum),
|
|
258
|
+
multiProcessMode: this.#options.multiProcessMode,
|
|
259
|
+
processTimeout: this.#options.processTimeout,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (this.#options.syncPattern === 'pull' || this.#options.syncPattern === 'hybrid') {
|
|
263
|
+
this.#startPullLoop();
|
|
264
|
+
}
|
|
265
|
+
if (this.#options.syncPattern === 'push' || this.#options.syncPattern === 'hybrid') {
|
|
266
|
+
this.#setupPushListener();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
this.#started = true;
|
|
270
|
+
this.#starting = null;
|
|
271
|
+
return true;
|
|
272
|
+
})();
|
|
273
|
+
return this.#starting;
|
|
274
|
+
}
|
|
275
|
+
async stop() {
|
|
276
|
+
if (this.#stopped) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (this.#stopping) {
|
|
280
|
+
return this.#stopping;
|
|
281
|
+
}
|
|
282
|
+
this.#stopping = (async () => {
|
|
283
|
+
if (this.#pullInterval) {
|
|
284
|
+
clearTimeout(this.#pullInterval);
|
|
285
|
+
this.#pullInterval = null;
|
|
286
|
+
}
|
|
287
|
+
for (const waits of this.#pendingJobWaits.values()) {
|
|
288
|
+
for (const wait of waits) {
|
|
289
|
+
if (wait.timeoutId) {
|
|
290
|
+
clearTimeout(wait.timeoutId);
|
|
291
|
+
}
|
|
292
|
+
if (wait.signal && wait.abortHandler) {
|
|
293
|
+
wait.signal.removeEventListener('abort', wait.abortHandler);
|
|
294
|
+
}
|
|
295
|
+
wait.resolve(null);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this.#pendingJobWaits.clear();
|
|
299
|
+
await Promise.all(Array.from(this.#actionManagers.values()).map(async (manager) => {
|
|
300
|
+
await manager.stop();
|
|
301
|
+
}));
|
|
302
|
+
const dbStopped = await this.#database.stop();
|
|
303
|
+
if (!dbStopped) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
this.#stopped = true;
|
|
307
|
+
this.#stopping = null;
|
|
308
|
+
return true;
|
|
309
|
+
})();
|
|
310
|
+
return this.#stopping;
|
|
311
|
+
}
|
|
312
|
+
#setupJobStatusListener() {
|
|
313
|
+
if (this.#jobStatusListenerSetup) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
this.#jobStatusListenerSetup = true;
|
|
317
|
+
this.#database.on('job-status-changed', async (event) => {
|
|
318
|
+
const pendingWaits = this.#pendingJobWaits.get(event.jobId);
|
|
319
|
+
if (!pendingWaits || pendingWaits.size === 0) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const job = await this.getJobById(event.jobId);
|
|
323
|
+
const waitsToResolve = Array.from(pendingWaits);
|
|
324
|
+
this.#pendingJobWaits.delete(event.jobId);
|
|
325
|
+
for (const wait of waitsToResolve) {
|
|
326
|
+
if (wait.timeoutId) {
|
|
327
|
+
clearTimeout(wait.timeoutId);
|
|
328
|
+
}
|
|
329
|
+
if (wait.signal && wait.abortHandler) {
|
|
330
|
+
wait.signal.removeEventListener('abort', wait.abortHandler);
|
|
331
|
+
}
|
|
332
|
+
wait.resolve(job);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
#removeJobWait(jobId, resolve) {
|
|
337
|
+
const pendingWaits = this.#pendingJobWaits.get(jobId);
|
|
338
|
+
if (!pendingWaits) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
for (const wait of pendingWaits) {
|
|
342
|
+
if (wait.resolve === resolve) {
|
|
343
|
+
if (wait.timeoutId) {
|
|
344
|
+
clearTimeout(wait.timeoutId);
|
|
345
|
+
}
|
|
346
|
+
if (wait.signal && wait.abortHandler) {
|
|
347
|
+
wait.signal.removeEventListener('abort', wait.abortHandler);
|
|
348
|
+
}
|
|
349
|
+
pendingWaits.delete(wait);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (pendingWaits.size === 0) {
|
|
354
|
+
this.#pendingJobWaits.delete(jobId);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
#executeJob(job) {
|
|
358
|
+
if (!this.#actions) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const action = Object.values(this.#actions).find((a) => a.name === job.actionName);
|
|
362
|
+
if (!action) {
|
|
363
|
+
const error = { name: 'ActionNotFoundError', message: `Action "${job.actionName}" not found for job ${job.id}` };
|
|
364
|
+
this.#logger.warn({ jobId: job.id, actionName: job.actionName }, `[Duron] Action not found for job ${job.id}`);
|
|
365
|
+
this.#database.failJob({ jobId: job.id, error }).catch((dbError) => {
|
|
366
|
+
this.#logger.error({ error: dbError, jobId: job.id }, `[Duron] Error failing job ${job.id}`);
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
let actionManager = this.#actionManagers.get(action.name);
|
|
371
|
+
if (!actionManager) {
|
|
372
|
+
actionManager = new ActionManager({
|
|
373
|
+
action,
|
|
374
|
+
database: this.#database,
|
|
375
|
+
variables: this.#variables,
|
|
376
|
+
logger: this.#logger,
|
|
377
|
+
concurrencyLimit: this.#options.actionConcurrencyLimit,
|
|
378
|
+
});
|
|
379
|
+
this.#actionManagers.set(action.name, actionManager);
|
|
380
|
+
}
|
|
381
|
+
actionManager.push(job).catch((err) => {
|
|
382
|
+
this.#logger.error({ err, jobId: job.id, actionName: action.name }, `[Duron] Error executing job ${job.id} for action ${action.name}`);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
#startPullLoop() {
|
|
386
|
+
if (this.#pullInterval) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const pull = async () => {
|
|
390
|
+
if (this.#stopped) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
await this.fetch({
|
|
395
|
+
batchSize: this.#options.batchSize,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
this.#logger.error({ error }, '[Duron] [PullLoop] Error in pull loop');
|
|
400
|
+
}
|
|
401
|
+
if (!this.#stopped) {
|
|
402
|
+
this.#pullInterval = setTimeout(pull, this.#options.pullInterval);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
pull();
|
|
406
|
+
}
|
|
407
|
+
#setupPushListener() {
|
|
408
|
+
this.#database.on('job-available', async () => {
|
|
409
|
+
this.fetch({
|
|
410
|
+
batchSize: 1,
|
|
411
|
+
}).catch((error) => {
|
|
412
|
+
this.#logger.error({ error }, '[Duron] [PushListener] Error fetching job');
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|