@clairejs/server 3.22.15 → 3.23.1
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/README.md +5 -0
- package/dist/job/AbstractJobRepository.d.ts +13 -8
- package/dist/job/AbstractJobScheduler.d.ts +17 -17
- package/dist/job/AbstractJobScheduler.js +78 -28
- package/dist/job/AwsJobScheduler.d.ts +9 -10
- package/dist/job/AwsJobScheduler.js +19 -27
- package/dist/job/LocalJobScheduler.d.ts +6 -18
- package/dist/job/LocalJobScheduler.js +25 -80
- package/dist/job/decorators.d.ts +2 -1
- package/dist/job/decorators.js +2 -9
- package/dist/job/default-job-repo.d.ts +7 -6
- package/dist/job/default-job-repo.js +12 -10
- package/dist/job/interfaces.d.ts +20 -10
- package/dist/job/job.d.ts +15 -3
- package/dist/job/job.js +69 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { type ITransaction } from "@clairejs/orm";
|
|
2
|
-
import { type
|
|
3
|
-
export declare abstract class AbstractJobRepository<T extends
|
|
1
|
+
import { GetManyOptions, QueryCondition, type ITransaction } from "@clairejs/orm";
|
|
2
|
+
import { type AbstractJob } from "./interfaces";
|
|
3
|
+
export declare abstract class AbstractJobRepository<T extends AbstractJob = AbstractJob> {
|
|
4
4
|
/**
|
|
5
5
|
* Return all persisted jobs
|
|
6
6
|
*/
|
|
7
|
-
abstract getJobs(): Promise<T[]>;
|
|
7
|
+
abstract getJobs(query?: QueryCondition<T>, options?: GetManyOptions<T, keyof T>): Promise<T[]>;
|
|
8
8
|
/**
|
|
9
9
|
* Save the job info and return a unique id
|
|
10
|
-
* @param jobInfo The
|
|
10
|
+
* @param jobInfo The job info to save
|
|
11
11
|
*/
|
|
12
|
-
abstract
|
|
12
|
+
abstract insertJob(jobInfo: Partial<T>, tx?: ITransaction): Promise<string>;
|
|
13
13
|
/**
|
|
14
14
|
* Remove job info by id
|
|
15
|
-
* @param
|
|
15
|
+
* @param jobId Unique id of job
|
|
16
16
|
*/
|
|
17
|
-
abstract removeJobById(
|
|
17
|
+
abstract removeJobById(jobId: string, tx?: ITransaction): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Remove job info by id
|
|
20
|
+
* @param jobId Unique id of job
|
|
21
|
+
*/
|
|
22
|
+
abstract updateJobById(jobId: string, update: Partial<Omit<T, "jobId">>, tx?: ITransaction): Promise<void>;
|
|
18
23
|
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
import { AbstractLogger } from "@clairejs/core";
|
|
2
2
|
import { ITransaction, ITransactionFactory } from "@clairejs/orm";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { AbstractJob, JobInfoMetadata, JobSchedulePayload } from "./interfaces";
|
|
4
|
+
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
5
|
+
export type JobHandlerFn = (job: AbstractJob) => Promise<AbstractJob | void>;
|
|
5
6
|
interface JobHandlerMetadata extends JobInfoMetadata {
|
|
6
7
|
handlerFn: JobHandlerFn;
|
|
7
8
|
}
|
|
8
9
|
export declare abstract class AbstractJobScheduler {
|
|
9
10
|
protected readonly logger: AbstractLogger;
|
|
10
11
|
protected readonly db: ITransactionFactory;
|
|
11
|
-
protected
|
|
12
|
+
protected readonly jobRepo: AbstractJobRepository;
|
|
12
13
|
private _jobs;
|
|
13
|
-
constructor(logger: AbstractLogger, db: ITransactionFactory);
|
|
14
|
-
protected
|
|
15
|
-
abstract getAllScheduledJobs(): Promise<ScheduledJob[]>;
|
|
16
|
-
/**
|
|
17
|
-
* Return unique job id which can then be used to cancel the job
|
|
18
|
-
* @param jobInfo the necessary info to launch the job
|
|
19
|
-
*/
|
|
20
|
-
protected abstract scheduleJob(jobInfo: JobInfo): Promise<string>;
|
|
21
|
-
protected abstract afterJob(job: ScheduledJob, tx: ITransaction): Promise<void>;
|
|
14
|
+
constructor(logger: AbstractLogger, db: ITransactionFactory, jobRepo: AbstractJobRepository);
|
|
15
|
+
protected getCurrentJobHandlers(): Promise<JobHandlerMetadata[]>;
|
|
22
16
|
/**
|
|
23
17
|
* Sync all jobs to running state. This should be called only at init time.
|
|
24
18
|
*/
|
|
25
19
|
abstract syncJobs(): Promise<void>;
|
|
26
20
|
/**
|
|
27
21
|
* Return unique job id which can then be used to cancel the job
|
|
28
|
-
* @param
|
|
22
|
+
* @param payload the necessary info to launch the job
|
|
29
23
|
*/
|
|
30
|
-
|
|
24
|
+
protected abstract _scheduleJob(payload: JobSchedulePayload): Promise<void>;
|
|
25
|
+
scheduleJob({ jobId, ...payload }: JobSchedulePayload): Promise<string>;
|
|
31
26
|
/**
|
|
32
|
-
*
|
|
27
|
+
* Cancel the scheduled job and prevent if from running in the future
|
|
33
28
|
* @param id The job id returned from scheduleJobAt function
|
|
34
29
|
*/
|
|
35
|
-
abstract
|
|
30
|
+
protected abstract cancelJob(jobId: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Remove the scheduled job and prevent if from running in the future
|
|
33
|
+
* @param jobId The job id returned from scheduleJobAt function
|
|
34
|
+
*/
|
|
35
|
+
disableJob(jobId: string, tx?: ITransaction): Promise<void>;
|
|
36
36
|
/**
|
|
37
37
|
* Execute the scheduled job
|
|
38
38
|
* @param job The schedled job info to execute
|
|
39
39
|
*/
|
|
40
|
-
protected executeJob(job:
|
|
40
|
+
protected executeJob(job: AbstractJob): Promise<void>;
|
|
41
41
|
}
|
|
42
42
|
export {};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { getServiceProvider } from "@clairejs/core";
|
|
1
|
+
import { diffData, getServiceProvider } from "@clairejs/core";
|
|
2
2
|
import { AbstractJobController } from "./AbstractJobController";
|
|
3
3
|
export class AbstractJobScheduler {
|
|
4
4
|
logger;
|
|
5
5
|
db;
|
|
6
|
+
jobRepo;
|
|
6
7
|
_jobs = null;
|
|
7
|
-
constructor(logger, db) {
|
|
8
|
+
constructor(logger, db, jobRepo) {
|
|
8
9
|
this.logger = logger;
|
|
9
10
|
this.db = db;
|
|
11
|
+
this.jobRepo = jobRepo;
|
|
10
12
|
}
|
|
11
|
-
async
|
|
13
|
+
async getCurrentJobHandlers() {
|
|
12
14
|
if (this._jobs === null) {
|
|
13
15
|
//-- read all job info
|
|
14
16
|
const injector = getServiceProvider().getInjector();
|
|
@@ -20,47 +22,95 @@ export class AbstractJobScheduler {
|
|
|
20
22
|
}
|
|
21
23
|
return this._jobs;
|
|
22
24
|
}
|
|
25
|
+
async scheduleJob({ jobId, ...payload }) {
|
|
26
|
+
let uniqueId = jobId;
|
|
27
|
+
const tx = await this.db.createTransaction();
|
|
28
|
+
if (!uniqueId) {
|
|
29
|
+
uniqueId = await this.jobRepo.insertJob(payload, tx);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const [job] = await this.jobRepo.getJobs({ _eq: { jobId: uniqueId } }, { limit: 1 });
|
|
33
|
+
if (!job) {
|
|
34
|
+
await this.jobRepo.insertJob({ ...payload, jobId: uniqueId }, tx);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const diff = diffData(job, payload);
|
|
38
|
+
if (diff) {
|
|
39
|
+
await this.jobRepo.updateJobById(uniqueId, payload, tx);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
await this._scheduleJob({ ...payload, jobId: uniqueId });
|
|
44
|
+
await tx.commit();
|
|
45
|
+
return uniqueId;
|
|
46
|
+
}
|
|
23
47
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param
|
|
48
|
+
* Remove the scheduled job and prevent if from running in the future
|
|
49
|
+
* @param jobId The job id returned from scheduleJobAt function
|
|
26
50
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
51
|
+
async disableJob(jobId, tx) {
|
|
52
|
+
await this.cancelJob(jobId);
|
|
53
|
+
await this.jobRepo.updateJobById(jobId, { disabled: true }, tx);
|
|
29
54
|
}
|
|
30
55
|
/**
|
|
31
56
|
* Execute the scheduled job
|
|
32
57
|
* @param job The schedled job info to execute
|
|
33
58
|
*/
|
|
34
59
|
async executeJob(job) {
|
|
60
|
+
if (job.disabled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
35
63
|
//-- run job
|
|
36
|
-
const
|
|
37
|
-
const jobHandler =
|
|
64
|
+
const allHandlers = await this.getCurrentJobHandlers();
|
|
65
|
+
const jobHandler = allHandlers.find((j) => j.jobName === job.jobName);
|
|
38
66
|
const tx = await this.db.createTransaction();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
if (!jobHandler) {
|
|
68
|
+
this.logger.info(`Disable job with id: ${job.jobId} as handler is not found`);
|
|
69
|
+
await this.disableJob(job.jobId, tx);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const update = {
|
|
73
|
+
lastRunAt: Date.now(),
|
|
74
|
+
execCount: (job.execCount || 0) + 1,
|
|
75
|
+
};
|
|
76
|
+
try {
|
|
77
|
+
await jobHandler.handlerFn(job);
|
|
78
|
+
//-- job run success, update
|
|
79
|
+
update.lastSuccessAt = Date.now();
|
|
80
|
+
//-- reset retry count if this is cron job
|
|
81
|
+
if (job.cron) {
|
|
82
|
+
if (job.retryCount) {
|
|
83
|
+
update.retryCount = 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
update.disabled = true;
|
|
88
|
+
}
|
|
44
89
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
90
|
+
catch (err) {
|
|
91
|
+
update.failCount = (job.failCount || 0) + 1;
|
|
92
|
+
update.lastError = String(err);
|
|
93
|
+
//-- job run error, check retry
|
|
94
|
+
if (job.retryOnFail) {
|
|
95
|
+
if ((job.retryCount || 0) < (job.maxRetry || 1)) {
|
|
96
|
+
//-- retry by reschedule the job
|
|
97
|
+
update.retryCount = (job.retryCount || 0) + 1;
|
|
98
|
+
const retryDelay = job.retryDelayMs || 0;
|
|
99
|
+
await this.scheduleJob({ ...job, at: Date.now() + (retryDelay < 60000 ? 60000 : retryDelay) });
|
|
51
100
|
}
|
|
52
101
|
else {
|
|
53
|
-
|
|
102
|
+
if (job.cron) {
|
|
103
|
+
update.at = undefined;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
update.disabled = true;
|
|
107
|
+
await this.cancelJob(job.jobId);
|
|
108
|
+
}
|
|
54
109
|
}
|
|
55
110
|
}
|
|
56
111
|
}
|
|
57
|
-
this.
|
|
58
|
-
await tx.commit();
|
|
59
|
-
this.logger.debug("Job tx committed");
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
this.logger.error("Error handling job", err);
|
|
63
|
-
await tx.rollback();
|
|
112
|
+
await this.jobRepo.updateJobById(job.jobId, update, tx);
|
|
64
113
|
}
|
|
114
|
+
await tx.commit();
|
|
65
115
|
}
|
|
66
116
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AbstractLogger } from "@clairejs/core";
|
|
2
|
-
import { AbstractDbAdapter
|
|
2
|
+
import { AbstractDbAdapter } from "@clairejs/orm";
|
|
3
3
|
import aws from "aws-sdk";
|
|
4
|
+
import { AbstractJob } from "./interfaces";
|
|
4
5
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
5
|
-
import {
|
|
6
|
+
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
6
7
|
/**
|
|
7
8
|
* Following EventBrige permissions is required: listRules, putRule, deleteRule, listTargetsByRule, putTargets, removeTargets.
|
|
8
9
|
* Also update Lambda function to alow function invocation from events.amazonaws.com.
|
|
@@ -10,18 +11,16 @@ import { JobInfo, ScheduledJob } from "./interfaces";
|
|
|
10
11
|
export declare class AwsJobScheduler extends AbstractJobScheduler {
|
|
11
12
|
protected readonly logger: AbstractLogger;
|
|
12
13
|
protected readonly db: AbstractDbAdapter;
|
|
13
|
-
protected readonly
|
|
14
|
+
protected readonly jobRepo: AbstractJobRepository;
|
|
14
15
|
protected readonly apiLambdaFunctionArn: string;
|
|
15
16
|
protected readonly jobNamespace: string;
|
|
16
17
|
protected readonly eventBusName: string;
|
|
17
18
|
protected readonly eventbridge: aws.EventBridge;
|
|
18
|
-
constructor(logger: AbstractLogger, db: AbstractDbAdapter,
|
|
19
|
-
handleCron(jobInfo:
|
|
20
|
-
protected afterJob(_job: ScheduledJob, _tx: ITransaction): Promise<void>;
|
|
19
|
+
constructor(logger: AbstractLogger, db: AbstractDbAdapter, jobRepo: AbstractJobRepository, apiLambdaFunctionArn: string, jobNamespace: string, eventBusName?: string);
|
|
20
|
+
handleCron(jobInfo: AbstractJob): Promise<void>;
|
|
21
21
|
private generateCronFromTimestamp;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
protected scheduleJob(jobInfo: JobInfo): Promise<string>;
|
|
22
|
+
getAllScheduledJobs(): Promise<AbstractJob[]>;
|
|
23
|
+
protected _scheduleJob(jobInfo: AbstractJob): Promise<void>;
|
|
25
24
|
syncJobs(): Promise<void>;
|
|
26
|
-
|
|
25
|
+
cancelJob(jobId: string): Promise<void>;
|
|
27
26
|
}
|
|
@@ -12,6 +12,7 @@ import { AbstractDbAdapter } from "@clairejs/orm";
|
|
|
12
12
|
import assert from "assert";
|
|
13
13
|
import aws from "aws-sdk";
|
|
14
14
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
15
|
+
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
15
16
|
/**
|
|
16
17
|
* Following EventBrige permissions is required: listRules, putRule, deleteRule, listTargetsByRule, putTargets, removeTargets.
|
|
17
18
|
* Also update Lambda function to alow function invocation from events.amazonaws.com.
|
|
@@ -19,16 +20,16 @@ import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
|
19
20
|
let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
20
21
|
logger;
|
|
21
22
|
db;
|
|
22
|
-
|
|
23
|
+
jobRepo;
|
|
23
24
|
apiLambdaFunctionArn;
|
|
24
25
|
jobNamespace;
|
|
25
26
|
eventBusName;
|
|
26
27
|
eventbridge = new aws.EventBridge();
|
|
27
|
-
constructor(logger, db,
|
|
28
|
-
super(logger, db);
|
|
28
|
+
constructor(logger, db, jobRepo, apiLambdaFunctionArn, jobNamespace, eventBusName = "default") {
|
|
29
|
+
super(logger, db, jobRepo);
|
|
29
30
|
this.logger = logger;
|
|
30
31
|
this.db = db;
|
|
31
|
-
this.
|
|
32
|
+
this.jobRepo = jobRepo;
|
|
32
33
|
this.apiLambdaFunctionArn = apiLambdaFunctionArn;
|
|
33
34
|
this.jobNamespace = jobNamespace;
|
|
34
35
|
this.eventBusName = eventBusName;
|
|
@@ -37,10 +38,6 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
37
38
|
this.logger.debug(`Handle cron`, jobInfo);
|
|
38
39
|
await this.executeJob(jobInfo);
|
|
39
40
|
}
|
|
40
|
-
async afterJob(_job, _tx) {
|
|
41
|
-
//-- do nothing
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
41
|
generateCronFromTimestamp(at) {
|
|
45
42
|
const date = new Date(at);
|
|
46
43
|
return [
|
|
@@ -52,9 +49,6 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
52
49
|
date.getUTCFullYear(),
|
|
53
50
|
].join(" ");
|
|
54
51
|
}
|
|
55
|
-
isActiveScheduler() {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
52
|
async getAllScheduledJobs() {
|
|
59
53
|
const allRules = await this.eventbridge
|
|
60
54
|
.listRules({
|
|
@@ -78,12 +72,12 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
78
72
|
return jobInfo;
|
|
79
73
|
}));
|
|
80
74
|
//-- concat with interval jobs because we don't register them
|
|
81
|
-
return allJobs.
|
|
75
|
+
return allJobs.flatMap((job) => (job ? [job] : []));
|
|
82
76
|
}
|
|
83
|
-
async
|
|
77
|
+
async _scheduleJob(jobInfo) {
|
|
84
78
|
this.logger.debug("Scheduling job: ", jobInfo);
|
|
85
79
|
if (jobInfo.cron || jobInfo.at) {
|
|
86
|
-
const jobId = `${this.jobNamespace}${
|
|
80
|
+
const jobId = `${this.jobNamespace}${jobInfo.jobId}`;
|
|
87
81
|
let cronExpression;
|
|
88
82
|
if (jobInfo.at) {
|
|
89
83
|
cronExpression = this.generateCronFromTimestamp(jobInfo.at);
|
|
@@ -118,7 +112,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
118
112
|
Input: JSON.stringify({
|
|
119
113
|
requestContext: {
|
|
120
114
|
cronScheduler: {
|
|
121
|
-
data: { ...jobInfo,
|
|
115
|
+
data: { ...jobInfo, jobId },
|
|
122
116
|
},
|
|
123
117
|
},
|
|
124
118
|
}),
|
|
@@ -126,7 +120,6 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
126
120
|
],
|
|
127
121
|
})
|
|
128
122
|
.promise();
|
|
129
|
-
return jobId;
|
|
130
123
|
}
|
|
131
124
|
else {
|
|
132
125
|
throw Errors.SYSTEM_ERROR(`Job does not have time config: ${jobInfo.jobName}`);
|
|
@@ -135,14 +128,12 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
135
128
|
async syncJobs() {
|
|
136
129
|
//-- check jobs
|
|
137
130
|
const scheduledJobs = await this.getAllScheduledJobs();
|
|
138
|
-
const
|
|
131
|
+
const allHandlers = await this.getCurrentJobHandlers();
|
|
139
132
|
//-- remove job that no more exist
|
|
140
|
-
const nomoreExistJobs = scheduledJobs.filter((job) => !
|
|
133
|
+
const nomoreExistJobs = scheduledJobs.filter((job) => !allHandlers.find((handler) => handler.jobName === job.jobName));
|
|
141
134
|
for (const job of nomoreExistJobs) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
await this.removeJob(job.id);
|
|
145
|
-
}
|
|
135
|
+
this.logger.info(`Removing stale job: ${job.jobName} of id: ${job.jobId}`);
|
|
136
|
+
await this.disableJob(job.jobId);
|
|
146
137
|
}
|
|
147
138
|
if (nomoreExistJobs.length) {
|
|
148
139
|
this.logger.info(`Cleaned up: ${nomoreExistJobs.length} stale jobs`);
|
|
@@ -150,12 +141,12 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
150
141
|
//-- remove scheduled cron jobs that diff the cron expression
|
|
151
142
|
this.logger.debug("Remove scheduled cron jobs");
|
|
152
143
|
const scheduledCronJobs = scheduledJobs.filter((j) => j.cron);
|
|
153
|
-
const unmatchedCronJobs = scheduledCronJobs.filter((j) => j.cron && !
|
|
144
|
+
const unmatchedCronJobs = scheduledCronJobs.filter((j) => j.cron && !allHandlers.find((job) => job.jobName === j.jobName && job.cron === j.cron));
|
|
154
145
|
if (unmatchedCronJobs.length) {
|
|
155
|
-
await Promise.all(unmatchedCronJobs.map((j) => this.
|
|
146
|
+
await Promise.all(unmatchedCronJobs.map((j) => this.disableJob(j.jobId)));
|
|
156
147
|
}
|
|
157
148
|
//-- reschedule new cron jobs and those which are not synced
|
|
158
|
-
const resyncCronJobs =
|
|
149
|
+
const resyncCronJobs = allHandlers.filter((job) => job.cron &&
|
|
159
150
|
(unmatchedCronJobs.some((j) => j.jobName === job.jobName) ||
|
|
160
151
|
!scheduledCronJobs.some((j) => j.jobName === job.jobName)));
|
|
161
152
|
this.logger.debug("Reschedule cron jobs", resyncCronJobs);
|
|
@@ -167,7 +158,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
167
158
|
}
|
|
168
159
|
//-- keep "at" jobs as is
|
|
169
160
|
}
|
|
170
|
-
async
|
|
161
|
+
async cancelJob(jobId) {
|
|
171
162
|
await this.eventbridge
|
|
172
163
|
.removeTargets({
|
|
173
164
|
EventBusName: this.eventBusName,
|
|
@@ -186,6 +177,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
186
177
|
AwsJobScheduler = __decorate([
|
|
187
178
|
LogContext(),
|
|
188
179
|
__metadata("design:paramtypes", [AbstractLogger,
|
|
189
|
-
AbstractDbAdapter,
|
|
180
|
+
AbstractDbAdapter,
|
|
181
|
+
AbstractJobRepository, String, String, Object])
|
|
190
182
|
], AwsJobScheduler);
|
|
191
183
|
export { AwsJobScheduler };
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { AbstractLogger, IInit } from "@clairejs/core";
|
|
2
2
|
import Redis from "ioredis";
|
|
3
|
-
import { AbstractDbAdapter
|
|
3
|
+
import { AbstractDbAdapter } from "@clairejs/orm";
|
|
4
|
+
import { AbstractJob } from "./interfaces";
|
|
4
5
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
5
|
-
import { CustomJobInfo, JobInfo, ScheduledJob } from "./interfaces";
|
|
6
6
|
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
7
7
|
export declare class LocalJobScheduler extends AbstractJobScheduler implements IInit {
|
|
8
8
|
protected readonly logger: AbstractLogger;
|
|
9
9
|
protected readonly db: AbstractDbAdapter;
|
|
10
10
|
protected readonly redisClient: Redis;
|
|
11
11
|
protected readonly subscribeClient: Redis;
|
|
12
|
-
protected readonly jobRepo: AbstractJobRepository
|
|
12
|
+
protected readonly jobRepo: AbstractJobRepository;
|
|
13
13
|
/**
|
|
14
14
|
* Redis lock key to select active scheduler
|
|
15
15
|
*/
|
|
@@ -18,10 +18,6 @@ export declare class LocalJobScheduler extends AbstractJobScheduler implements I
|
|
|
18
18
|
* Redis key to hold active scheduler role
|
|
19
19
|
*/
|
|
20
20
|
protected readonly holdMutexKey: string;
|
|
21
|
-
/**
|
|
22
|
-
* Redis key to get unique incremental id
|
|
23
|
-
*/
|
|
24
|
-
protected readonly uniqueIdKey: string;
|
|
25
21
|
/**
|
|
26
22
|
* The channel for communication between passive and active schedulers
|
|
27
23
|
*/
|
|
@@ -33,9 +29,8 @@ export declare class LocalJobScheduler extends AbstractJobScheduler implements I
|
|
|
33
29
|
protected intervals: any[];
|
|
34
30
|
private mutexHoldInterval?;
|
|
35
31
|
private isActive;
|
|
36
|
-
private notifyResolver;
|
|
37
32
|
private jobHolder;
|
|
38
|
-
constructor(logger: AbstractLogger, db: AbstractDbAdapter, redisClient: Redis, subscribeClient: Redis, jobRepo: AbstractJobRepository
|
|
33
|
+
constructor(logger: AbstractLogger, db: AbstractDbAdapter, redisClient: Redis, subscribeClient: Redis, jobRepo: AbstractJobRepository,
|
|
39
34
|
/**
|
|
40
35
|
* Redis lock key to select active scheduler
|
|
41
36
|
*/
|
|
@@ -44,10 +39,6 @@ export declare class LocalJobScheduler extends AbstractJobScheduler implements I
|
|
|
44
39
|
* Redis key to hold active scheduler role
|
|
45
40
|
*/
|
|
46
41
|
holdMutexKey: string,
|
|
47
|
-
/**
|
|
48
|
-
* Redis key to get unique incremental id
|
|
49
|
-
*/
|
|
50
|
-
uniqueIdKey: string,
|
|
51
42
|
/**
|
|
52
43
|
* The channel for communication between passive and active schedulers
|
|
53
44
|
*/
|
|
@@ -59,12 +50,9 @@ export declare class LocalJobScheduler extends AbstractJobScheduler implements I
|
|
|
59
50
|
private sendJob;
|
|
60
51
|
private processMessage;
|
|
61
52
|
private extendMutexKey;
|
|
62
|
-
protected afterJob({ at, ...job }: ScheduledJob, tx: ITransaction): Promise<void>;
|
|
63
53
|
init(): Promise<void>;
|
|
64
54
|
exit(): void;
|
|
65
|
-
protected isActiveScheduler(): boolean;
|
|
66
|
-
getAllScheduledJobs(): Promise<ScheduledJob[]>;
|
|
67
55
|
syncJobs(): Promise<void>;
|
|
68
|
-
protected
|
|
69
|
-
|
|
56
|
+
protected _scheduleJob(jobInfo: AbstractJob): Promise<void>;
|
|
57
|
+
protected cancelJob(jobId: string): Promise<void>;
|
|
70
58
|
}
|
|
@@ -12,7 +12,6 @@ import Redis from "ioredis";
|
|
|
12
12
|
import Redlock from "redlock";
|
|
13
13
|
import scheduler from "node-schedule";
|
|
14
14
|
import { AbstractDbAdapter } from "@clairejs/orm";
|
|
15
|
-
import assert from "assert";
|
|
16
15
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
17
16
|
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
18
17
|
var CommunicationMessage;
|
|
@@ -20,7 +19,6 @@ var CommunicationMessage;
|
|
|
20
19
|
CommunicationMessage["SCHEDULE_JOB"] = "SCHEDULE_JOB";
|
|
21
20
|
CommunicationMessage["REMOVE_JOB"] = "REMOVE_JOB";
|
|
22
21
|
CommunicationMessage["SYNC_JOB"] = "SYNC_JOB";
|
|
23
|
-
CommunicationMessage["NOTIFY"] = "NOTIFY";
|
|
24
22
|
})(CommunicationMessage || (CommunicationMessage = {}));
|
|
25
23
|
let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
26
24
|
logger;
|
|
@@ -30,13 +28,11 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
30
28
|
jobRepo;
|
|
31
29
|
lockMutexKey;
|
|
32
30
|
holdMutexKey;
|
|
33
|
-
uniqueIdKey;
|
|
34
31
|
multiClientChannel;
|
|
35
32
|
keyRetentionDurationSeconds;
|
|
36
33
|
intervals = [];
|
|
37
34
|
mutexHoldInterval;
|
|
38
35
|
isActive = false;
|
|
39
|
-
notifyResolver = {};
|
|
40
36
|
jobHolder = {};
|
|
41
37
|
constructor(logger, db, redisClient, subscribeClient, jobRepo,
|
|
42
38
|
/**
|
|
@@ -47,10 +43,6 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
47
43
|
* Redis key to hold active scheduler role
|
|
48
44
|
*/
|
|
49
45
|
holdMutexKey,
|
|
50
|
-
/**
|
|
51
|
-
* Redis key to get unique incremental id
|
|
52
|
-
*/
|
|
53
|
-
uniqueIdKey,
|
|
54
46
|
/**
|
|
55
47
|
* The channel for communication between passive and active schedulers
|
|
56
48
|
*/
|
|
@@ -59,7 +51,7 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
59
51
|
* The time to lock active scheduler
|
|
60
52
|
*/
|
|
61
53
|
keyRetentionDurationSeconds = 30) {
|
|
62
|
-
super(logger, db);
|
|
54
|
+
super(logger, db, jobRepo);
|
|
63
55
|
this.logger = logger;
|
|
64
56
|
this.db = db;
|
|
65
57
|
this.redisClient = redisClient;
|
|
@@ -67,26 +59,20 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
67
59
|
this.jobRepo = jobRepo;
|
|
68
60
|
this.lockMutexKey = lockMutexKey;
|
|
69
61
|
this.holdMutexKey = holdMutexKey;
|
|
70
|
-
this.uniqueIdKey = uniqueIdKey;
|
|
71
62
|
this.multiClientChannel = multiClientChannel;
|
|
72
63
|
this.keyRetentionDurationSeconds = keyRetentionDurationSeconds;
|
|
73
64
|
}
|
|
74
|
-
sendJob(type,
|
|
75
|
-
this.subscribeClient.publish(this.multiClientChannel, JSON.stringify({ type,
|
|
65
|
+
sendJob(type, data) {
|
|
66
|
+
this.subscribeClient.publish(this.multiClientChannel, JSON.stringify({ type, data }));
|
|
76
67
|
}
|
|
77
|
-
async processMessage(type,
|
|
68
|
+
async processMessage(type, data) {
|
|
78
69
|
switch (type) {
|
|
79
70
|
case CommunicationMessage.SYNC_JOB:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
break;
|
|
83
|
-
case CommunicationMessage.NOTIFY:
|
|
84
|
-
const resolver = this.notifyResolver[messageId];
|
|
85
|
-
if (!resolver) {
|
|
86
|
-
//-- resolver not found, ignore
|
|
71
|
+
if (!this.isActive) {
|
|
72
|
+
//-- not active scheduler, ignore
|
|
87
73
|
return;
|
|
88
74
|
}
|
|
89
|
-
|
|
75
|
+
await this.syncJobs();
|
|
90
76
|
break;
|
|
91
77
|
case CommunicationMessage.SCHEDULE_JOB:
|
|
92
78
|
if (!this.isActive) {
|
|
@@ -94,8 +80,7 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
94
80
|
return;
|
|
95
81
|
}
|
|
96
82
|
const jobInfo = data;
|
|
97
|
-
|
|
98
|
-
this.sendJob(CommunicationMessage.NOTIFY, messageId, scheduledId);
|
|
83
|
+
await this.scheduleJob(jobInfo);
|
|
99
84
|
break;
|
|
100
85
|
case CommunicationMessage.REMOVE_JOB:
|
|
101
86
|
if (!this.isActive) {
|
|
@@ -103,8 +88,7 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
103
88
|
return;
|
|
104
89
|
}
|
|
105
90
|
const jobId = data;
|
|
106
|
-
await this.
|
|
107
|
-
this.sendJob(CommunicationMessage.NOTIFY, messageId);
|
|
91
|
+
await this.disableJob(jobId);
|
|
108
92
|
break;
|
|
109
93
|
default:
|
|
110
94
|
this.logger.error(`Not recognize message type ${type}`);
|
|
@@ -119,10 +103,6 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
119
103
|
await this.redisClient.setex(this.holdMutexKey, this.keyRetentionDurationSeconds, 1);
|
|
120
104
|
this.logger.debug("Scheduler extends mutex key");
|
|
121
105
|
}
|
|
122
|
-
async afterJob({ at, ...job }, tx) {
|
|
123
|
-
assert.ok(at);
|
|
124
|
-
await this.jobRepo.saveJob({ ...job, at }, tx);
|
|
125
|
-
}
|
|
126
106
|
async init() {
|
|
127
107
|
this.logger.debug("LocalJobScheduler init");
|
|
128
108
|
//-- subscribe to multi client channel
|
|
@@ -131,7 +111,7 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
131
111
|
if (channel === this.multiClientChannel) {
|
|
132
112
|
//-- process message
|
|
133
113
|
const payload = JSON.parse(message);
|
|
134
|
-
this.processMessage(payload.type, payload.
|
|
114
|
+
this.processMessage(payload.type, payload.data).catch((err) => this.logger.error(`Fail to process message, ${payload}`, err));
|
|
135
115
|
}
|
|
136
116
|
});
|
|
137
117
|
await this.subscribeClient.subscribe(this.multiClientChannel);
|
|
@@ -162,67 +142,42 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
162
142
|
}
|
|
163
143
|
this.logger.debug("LocalJobScheduler exit");
|
|
164
144
|
}
|
|
165
|
-
isActiveScheduler() {
|
|
166
|
-
return this.isActive;
|
|
167
|
-
}
|
|
168
|
-
async getAllScheduledJobs() {
|
|
169
|
-
return Object.values(this.jobHolder)
|
|
170
|
-
.filter((j) => !!j?.jobInfo)
|
|
171
|
-
.map((info) => info?.jobInfo);
|
|
172
|
-
}
|
|
173
145
|
async syncJobs() {
|
|
174
146
|
if (this.isActive) {
|
|
175
147
|
//-- schedule all cron & interval jobs
|
|
176
|
-
const allJobs = await this.
|
|
148
|
+
const allJobs = await this.getCurrentJobHandlers();
|
|
177
149
|
for (const job of allJobs) {
|
|
178
150
|
if (job.cron) {
|
|
179
151
|
await this.scheduleJob(job);
|
|
180
152
|
}
|
|
181
153
|
}
|
|
182
|
-
//-- re-schedule jobs that are stored in repo
|
|
183
|
-
const allPersistedJobs = await this.jobRepo.getJobs();
|
|
154
|
+
//-- re-schedule "at" jobs that are stored in repo
|
|
155
|
+
const allPersistedJobs = await this.jobRepo.getJobs({ _neq: { disabled: true, at: undefined } });
|
|
184
156
|
//-- run job anyway, expired job will be removed then
|
|
185
157
|
for (const job of allPersistedJobs) {
|
|
186
158
|
await this.scheduleJob(job);
|
|
187
159
|
}
|
|
188
160
|
}
|
|
189
161
|
else {
|
|
190
|
-
|
|
191
|
-
return new Promise((resolve) => {
|
|
192
|
-
this.notifyResolver[uniqueMessageId] = resolve;
|
|
193
|
-
this.sendJob(CommunicationMessage.SYNC_JOB, uniqueMessageId);
|
|
194
|
-
});
|
|
162
|
+
this.sendJob(CommunicationMessage.SYNC_JOB);
|
|
195
163
|
}
|
|
196
164
|
return;
|
|
197
165
|
}
|
|
198
|
-
async
|
|
166
|
+
async _scheduleJob(jobInfo) {
|
|
199
167
|
if (this.isActive) {
|
|
200
168
|
//-- case each job type
|
|
201
169
|
if (jobInfo.at) {
|
|
202
|
-
//-- create new schedule job
|
|
203
|
-
const id = jobInfo.id ||
|
|
204
|
-
(await this.jobRepo.saveJob({
|
|
205
|
-
jobName: jobInfo.jobName,
|
|
206
|
-
params: jobInfo.params,
|
|
207
|
-
at: jobInfo.at,
|
|
208
|
-
}, tx));
|
|
209
170
|
//-- use the lib
|
|
210
|
-
const scheduledJob = { ...jobInfo, id };
|
|
211
171
|
const timeout = setTimeout(() => {
|
|
212
|
-
this.executeJob(
|
|
172
|
+
this.executeJob(jobInfo).catch((err) => this.logger.error(`Error execute job ${jobInfo.jobName} with id: ${jobInfo.jobId}`, err));
|
|
213
173
|
}, new Date(jobInfo.at).getTime() - Date.now());
|
|
214
|
-
this.jobHolder[
|
|
215
|
-
return id;
|
|
174
|
+
this.jobHolder[jobInfo.jobId] = { jobCanceler: () => clearTimeout(timeout), jobInfo };
|
|
216
175
|
}
|
|
217
176
|
else if (jobInfo.cron) {
|
|
218
|
-
const id = jobInfo.jobName;
|
|
219
|
-
//-- set cron and does not need to persist
|
|
220
|
-
const scheduledJob = { ...jobInfo, id };
|
|
221
177
|
const job = scheduler.scheduleJob(jobInfo.cron, () => {
|
|
222
|
-
this.executeJob(
|
|
178
|
+
this.executeJob(jobInfo).catch((err) => this.logger.error(`Error execute job ${jobInfo.jobName} with id: ${jobInfo.jobId}`, err));
|
|
223
179
|
});
|
|
224
|
-
this.jobHolder[
|
|
225
|
-
return id;
|
|
180
|
+
this.jobHolder[jobInfo.jobId] = { jobCanceler: () => job.cancel(), jobInfo };
|
|
226
181
|
}
|
|
227
182
|
else {
|
|
228
183
|
throw Errors.SYSTEM_ERROR(`Job does not have time config: ${jobInfo.jobName}`);
|
|
@@ -230,31 +185,21 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
230
185
|
}
|
|
231
186
|
else {
|
|
232
187
|
//-- get unique message id
|
|
233
|
-
|
|
234
|
-
return new Promise((resolve) => {
|
|
235
|
-
this.notifyResolver[uniqueMessageId] = resolve;
|
|
236
|
-
this.sendJob(CommunicationMessage.SCHEDULE_JOB, uniqueMessageId, jobInfo);
|
|
237
|
-
});
|
|
188
|
+
this.sendJob(CommunicationMessage.SCHEDULE_JOB, jobInfo);
|
|
238
189
|
}
|
|
239
190
|
}
|
|
240
|
-
async
|
|
191
|
+
async cancelJob(jobId) {
|
|
241
192
|
if (this.isActive) {
|
|
242
193
|
//-- remove from holder
|
|
243
|
-
const job = this.jobHolder[
|
|
194
|
+
const job = this.jobHolder[jobId];
|
|
244
195
|
if (job) {
|
|
245
196
|
job.jobCanceler();
|
|
246
|
-
this.jobHolder[
|
|
197
|
+
this.jobHolder[jobId] = undefined;
|
|
247
198
|
}
|
|
248
|
-
//-- remove from persistence
|
|
249
|
-
await this.jobRepo.removeJobById(id, tx);
|
|
250
199
|
}
|
|
251
200
|
else {
|
|
252
201
|
//-- get unique message id
|
|
253
|
-
|
|
254
|
-
return new Promise((resolve) => {
|
|
255
|
-
this.notifyResolver[uniqueMessageId] = resolve;
|
|
256
|
-
this.sendJob(CommunicationMessage.REMOVE_JOB, uniqueMessageId, id);
|
|
257
|
-
});
|
|
202
|
+
this.sendJob(CommunicationMessage.REMOVE_JOB, jobId);
|
|
258
203
|
}
|
|
259
204
|
}
|
|
260
205
|
};
|
|
@@ -265,6 +210,6 @@ LocalJobScheduler = __decorate([
|
|
|
265
210
|
AbstractDbAdapter,
|
|
266
211
|
Redis,
|
|
267
212
|
Redis,
|
|
268
|
-
AbstractJobRepository, String, String, String,
|
|
213
|
+
AbstractJobRepository, String, String, String, Number])
|
|
269
214
|
], LocalJobScheduler);
|
|
270
215
|
export { LocalJobScheduler };
|
package/dist/job/decorators.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { JobRetryOptions } from "./interfaces";
|
|
1
2
|
import { AbstractJobController } from "./AbstractJobController";
|
|
2
3
|
import { JobHandlerFn } from "./AbstractJobScheduler";
|
|
3
4
|
type Descriptor<T> = Omit<TypedPropertyDescriptor<T>, "set">;
|
|
4
|
-
export declare const CronJob: (jobName: string, cron: string) => <T extends AbstractJobController>(prototype: T, propertyKey: keyof T, _descriptor: Descriptor<JobHandlerFn>) => void;
|
|
5
|
+
export declare const CronJob: (jobName: string, cron: string, retryOptions?: JobRetryOptions) => <T extends AbstractJobController>(prototype: T, propertyKey: keyof T, _descriptor: Descriptor<JobHandlerFn>) => void;
|
|
5
6
|
export declare const CustomJob: (jobName: string) => <T extends AbstractJobController>(prototype: T, propertyKey: keyof T, _descriptor: Descriptor<JobHandlerFn>) => void;
|
|
6
7
|
export {};
|
package/dist/job/decorators.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
import { Errors, initObjectMetadata } from "@clairejs/core";
|
|
2
|
-
export const CronJob = (
|
|
3
|
-
/**
|
|
4
|
-
* Unique name of job
|
|
5
|
-
*/
|
|
6
|
-
jobName,
|
|
7
|
-
/**
|
|
8
|
-
* Cron expression
|
|
9
|
-
*/
|
|
10
|
-
cron) => (prototype, propertyKey, _descriptor) => {
|
|
2
|
+
export const CronJob = (jobName, cron, retryOptions) => (prototype, propertyKey, _descriptor) => {
|
|
11
3
|
const metadata = initObjectMetadata(prototype);
|
|
12
4
|
if (!metadata.jobs) {
|
|
13
5
|
metadata.jobs = [];
|
|
@@ -18,6 +10,7 @@ cron) => (prototype, propertyKey, _descriptor) => {
|
|
|
18
10
|
}
|
|
19
11
|
metadata.jobs.push({
|
|
20
12
|
jobName,
|
|
13
|
+
retryOptions,
|
|
21
14
|
cron,
|
|
22
15
|
handlerName: propertyKey,
|
|
23
16
|
});
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { AbstractModel, Constructor } from "@clairejs/core";
|
|
2
|
-
import { type ITransaction, AbstractDbAdapter } from "@clairejs/orm";
|
|
3
|
-
import {
|
|
2
|
+
import { type ITransaction, AbstractDbAdapter, GetManyOptions, QueryCondition } from "@clairejs/orm";
|
|
3
|
+
import { AbstractJob } from "./interfaces";
|
|
4
4
|
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
5
|
-
export declare class DefaultJobRepository<T extends
|
|
5
|
+
export declare class DefaultJobRepository<T extends AbstractJob & AbstractModel> extends AbstractJobRepository<T> {
|
|
6
6
|
protected readonly model: Constructor<T>;
|
|
7
7
|
protected db: AbstractDbAdapter;
|
|
8
8
|
constructor(model: Constructor<T>, db: AbstractDbAdapter);
|
|
9
|
-
getJobs(): Promise<T[]>;
|
|
10
|
-
|
|
11
|
-
removeJobById(
|
|
9
|
+
getJobs(query?: QueryCondition<T>, options?: GetManyOptions<T, keyof T>): Promise<T[]>;
|
|
10
|
+
insertJob({ jobId, ...jobInfo }: Partial<T>, tx?: ITransaction): Promise<string>;
|
|
11
|
+
removeJobById(jobId: string, tx?: ITransaction): Promise<void>;
|
|
12
|
+
updateJobById(jobId: string, update: Partial<Omit<T, "jobId">>, tx?: ITransaction): Promise<void>;
|
|
12
13
|
}
|
|
@@ -7,23 +7,25 @@ export class DefaultJobRepository extends AbstractJobRepository {
|
|
|
7
7
|
this.model = model;
|
|
8
8
|
this.db = db;
|
|
9
9
|
}
|
|
10
|
-
async getJobs() {
|
|
11
|
-
|
|
12
|
-
return jobs;
|
|
10
|
+
async getJobs(query, options) {
|
|
11
|
+
return await this.db.use(this.model).getRecords(query, options);
|
|
13
12
|
}
|
|
14
|
-
async
|
|
15
|
-
if (
|
|
13
|
+
async insertJob({ jobId, ...jobInfo }, tx) {
|
|
14
|
+
if (jobId) {
|
|
16
15
|
//-- update
|
|
17
|
-
await this.db.use(this.model, tx).
|
|
16
|
+
await this.db.use(this.model, tx).updateOne({ _eq: { jobId } }, jobInfo);
|
|
17
|
+
return jobId;
|
|
18
18
|
}
|
|
19
19
|
else {
|
|
20
20
|
//-- create new
|
|
21
21
|
const job = await this.db.use(this.model, tx).createOne(jobInfo);
|
|
22
|
-
|
|
22
|
+
return job.jobId;
|
|
23
23
|
}
|
|
24
|
-
return id;
|
|
25
24
|
}
|
|
26
|
-
async removeJobById(
|
|
27
|
-
await this.db.use(this.model, tx).
|
|
25
|
+
async removeJobById(jobId, tx) {
|
|
26
|
+
await this.db.use(this.model, tx).deleteMany({ _eq: { jobId } });
|
|
27
|
+
}
|
|
28
|
+
async updateJobById(jobId, update, tx) {
|
|
29
|
+
await this.db.use(this.model, tx).updateOne({ _eq: { jobId } }, update);
|
|
28
30
|
}
|
|
29
31
|
}
|
package/dist/job/interfaces.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { ObjectMetadata } from "@clairejs/core";
|
|
2
2
|
export declare const CRON_REQUEST_METHOD = "cron";
|
|
3
|
+
export interface JobRetryOptions {
|
|
4
|
+
retryOnFail?: boolean;
|
|
5
|
+
maxRetry?: number;
|
|
6
|
+
retryDelayMs?: number;
|
|
7
|
+
}
|
|
3
8
|
export interface JobInfoMetadata {
|
|
4
9
|
/**
|
|
5
10
|
* Unique name of job
|
|
6
11
|
*/
|
|
7
12
|
jobName: string;
|
|
13
|
+
/**
|
|
14
|
+
* Retry options
|
|
15
|
+
*/
|
|
16
|
+
retryOptions?: JobRetryOptions;
|
|
8
17
|
/**
|
|
9
18
|
* Run with cron expression, does not support seconds precision
|
|
10
19
|
*/
|
|
@@ -21,19 +30,20 @@ export interface JobInfoMetadata {
|
|
|
21
30
|
export interface JobControllerMetadata extends ObjectMetadata {
|
|
22
31
|
jobs?: JobInfoMetadata[];
|
|
23
32
|
}
|
|
24
|
-
export interface
|
|
25
|
-
|
|
26
|
-
jobName: string;
|
|
27
|
-
at: number;
|
|
28
|
-
params?: any;
|
|
29
|
-
}
|
|
30
|
-
export interface JobInfo {
|
|
31
|
-
id?: string;
|
|
33
|
+
export interface JobSchedulePayload extends JobRetryOptions {
|
|
34
|
+
jobId?: string;
|
|
32
35
|
jobName: string;
|
|
33
36
|
params?: any;
|
|
34
37
|
at?: number;
|
|
35
38
|
cron?: string;
|
|
36
39
|
}
|
|
37
|
-
export interface
|
|
38
|
-
|
|
40
|
+
export interface AbstractJob extends JobSchedulePayload {
|
|
41
|
+
jobId: string;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
execCount?: number;
|
|
44
|
+
failCount?: number;
|
|
45
|
+
retryCount?: number;
|
|
46
|
+
lastError?: string;
|
|
47
|
+
lastRunAt?: number;
|
|
48
|
+
lastSuccessAt?: number;
|
|
39
49
|
}
|
package/dist/job/job.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { AbstractModel } from "@clairejs/core";
|
|
2
|
-
import {
|
|
3
|
-
export declare abstract class
|
|
2
|
+
import { AbstractJob } from "./interfaces";
|
|
3
|
+
export declare abstract class AbstractJobModel extends AbstractModel implements AbstractJob {
|
|
4
|
+
jobId: string;
|
|
4
5
|
jobName: string;
|
|
5
|
-
at
|
|
6
|
+
at?: number;
|
|
7
|
+
cron?: string;
|
|
6
8
|
params?: any;
|
|
9
|
+
retryOnFail?: boolean;
|
|
10
|
+
maxRetry?: number;
|
|
11
|
+
retryCount?: number;
|
|
12
|
+
retryDelayMs?: number;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
execCount?: number;
|
|
15
|
+
failCount?: number;
|
|
16
|
+
lastError?: string;
|
|
17
|
+
lastRunAt?: number;
|
|
18
|
+
lastSuccessAt?: number;
|
|
7
19
|
}
|
package/dist/job/job.js
CHANGED
|
@@ -8,29 +8,93 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
import { AbstractModel, Column } from "@clairejs/core";
|
|
11
|
-
export class
|
|
11
|
+
export class AbstractJobModel extends AbstractModel {
|
|
12
|
+
jobId;
|
|
12
13
|
jobName;
|
|
13
14
|
at;
|
|
15
|
+
cron;
|
|
14
16
|
params;
|
|
17
|
+
retryOnFail;
|
|
18
|
+
maxRetry;
|
|
19
|
+
retryCount;
|
|
20
|
+
retryDelayMs;
|
|
21
|
+
disabled;
|
|
22
|
+
execCount;
|
|
23
|
+
failCount;
|
|
24
|
+
lastError;
|
|
25
|
+
lastRunAt;
|
|
26
|
+
lastSuccessAt;
|
|
15
27
|
}
|
|
28
|
+
__decorate([
|
|
29
|
+
Column({
|
|
30
|
+
description: "Unique id of the job",
|
|
31
|
+
isRequired: true,
|
|
32
|
+
}),
|
|
33
|
+
__metadata("design:type", String)
|
|
34
|
+
], AbstractJobModel.prototype, "jobId", void 0);
|
|
16
35
|
__decorate([
|
|
17
36
|
Column({
|
|
18
37
|
description: "Name of the job",
|
|
19
38
|
isRequired: true,
|
|
20
39
|
}),
|
|
21
40
|
__metadata("design:type", String)
|
|
22
|
-
],
|
|
41
|
+
], AbstractJobModel.prototype, "jobName", void 0);
|
|
23
42
|
__decorate([
|
|
24
43
|
Column({
|
|
25
44
|
description: "Unix timestamp this job will be triggered",
|
|
26
|
-
isRequired: true,
|
|
27
45
|
isTimestamp: true,
|
|
28
46
|
}),
|
|
29
47
|
__metadata("design:type", Number)
|
|
30
|
-
],
|
|
48
|
+
], AbstractJobModel.prototype, "at", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
Column({
|
|
51
|
+
description: "The cron expression to repeat",
|
|
52
|
+
}),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], AbstractJobModel.prototype, "cron", void 0);
|
|
31
55
|
__decorate([
|
|
32
56
|
Column({
|
|
33
57
|
description: "The associated params with this job",
|
|
34
58
|
}),
|
|
35
59
|
__metadata("design:type", Object)
|
|
36
|
-
],
|
|
60
|
+
], AbstractJobModel.prototype, "params", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
Column({}),
|
|
63
|
+
__metadata("design:type", Boolean)
|
|
64
|
+
], AbstractJobModel.prototype, "retryOnFail", void 0);
|
|
65
|
+
__decorate([
|
|
66
|
+
Column({}),
|
|
67
|
+
__metadata("design:type", Number)
|
|
68
|
+
], AbstractJobModel.prototype, "maxRetry", void 0);
|
|
69
|
+
__decorate([
|
|
70
|
+
Column({}),
|
|
71
|
+
__metadata("design:type", Number)
|
|
72
|
+
], AbstractJobModel.prototype, "retryCount", void 0);
|
|
73
|
+
__decorate([
|
|
74
|
+
Column({}),
|
|
75
|
+
__metadata("design:type", Number)
|
|
76
|
+
], AbstractJobModel.prototype, "retryDelayMs", void 0);
|
|
77
|
+
__decorate([
|
|
78
|
+
Column({}),
|
|
79
|
+
__metadata("design:type", Boolean)
|
|
80
|
+
], AbstractJobModel.prototype, "disabled", void 0);
|
|
81
|
+
__decorate([
|
|
82
|
+
Column({}),
|
|
83
|
+
__metadata("design:type", Number)
|
|
84
|
+
], AbstractJobModel.prototype, "execCount", void 0);
|
|
85
|
+
__decorate([
|
|
86
|
+
Column({}),
|
|
87
|
+
__metadata("design:type", Number)
|
|
88
|
+
], AbstractJobModel.prototype, "failCount", void 0);
|
|
89
|
+
__decorate([
|
|
90
|
+
Column({}),
|
|
91
|
+
__metadata("design:type", String)
|
|
92
|
+
], AbstractJobModel.prototype, "lastError", void 0);
|
|
93
|
+
__decorate([
|
|
94
|
+
Column({}),
|
|
95
|
+
__metadata("design:type", Number)
|
|
96
|
+
], AbstractJobModel.prototype, "lastRunAt", void 0);
|
|
97
|
+
__decorate([
|
|
98
|
+
Column({}),
|
|
99
|
+
__metadata("design:type", Number)
|
|
100
|
+
], AbstractJobModel.prototype, "lastSuccessAt", void 0);
|