@clairejs/server 3.16.13 → 3.16.14
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 +2 -1
- package/dist/job/AbstractJobRepository.d.ts +4 -3
- package/dist/job/AbstractJobScheduler.d.ts +7 -4
- package/dist/job/AbstractJobScheduler.js +24 -9
- package/dist/job/AwsJobScheduler.d.ts +16 -12
- package/dist/job/AwsJobScheduler.js +16 -8
- package/dist/job/LocalJobScheduler.d.ts +5 -2
- package/dist/job/LocalJobScheduler.js +37 -29
- package/dist/job/default-job-repo.d.ts +4 -5
- package/dist/job/default-job-repo.js +17 -11
- package/dist/job/interfaces.d.ts +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import { IQueryProvider } from "@clairejs/orm";
|
|
1
2
|
import { CustomJobInfo } from "./interfaces";
|
|
2
3
|
export declare abstract class AbstractJobRepository {
|
|
3
4
|
/**
|
|
4
5
|
* Return all persisted jobs
|
|
5
6
|
*/
|
|
6
|
-
abstract getJobs(): Promise<CustomJobInfo[]>;
|
|
7
|
+
abstract getJobs(query: IQueryProvider): Promise<CustomJobInfo[]>;
|
|
7
8
|
/**
|
|
8
9
|
* Save the job info and return a unique id
|
|
9
10
|
* @param jobInfo The custom job info to save
|
|
10
11
|
*/
|
|
11
|
-
abstract saveJob(jobInfo: CustomJobInfo): Promise<string>;
|
|
12
|
+
abstract saveJob(jobInfo: CustomJobInfo, query: IQueryProvider): Promise<string>;
|
|
12
13
|
/**
|
|
13
14
|
* Remove job info by id
|
|
14
15
|
* @param jobId Unique id of job
|
|
15
16
|
*/
|
|
16
|
-
abstract removeJobById(jobId: string): Promise<void>;
|
|
17
|
+
abstract removeJobById(jobId: string, query: IQueryProvider): Promise<void>;
|
|
17
18
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { AbstractLogger } from "@clairejs/core";
|
|
2
|
+
import { ITransaction, ITransactionFactory } from "@clairejs/orm";
|
|
2
3
|
import { CustomJobInfo, JobInfo, JobInfoMetadata, ScheduledJob } from "./interfaces";
|
|
3
4
|
interface JobHandlerMetadata extends JobInfoMetadata {
|
|
4
|
-
handlerFn: (
|
|
5
|
+
handlerFn: (job: ScheduledJob, tx: ITransaction) => Promise<ScheduledJob | undefined>;
|
|
5
6
|
}
|
|
6
7
|
export declare abstract class AbstractJobScheduler {
|
|
7
8
|
protected readonly logger: AbstractLogger;
|
|
9
|
+
protected readonly db: ITransactionFactory;
|
|
8
10
|
protected abstract isActiveScheduler(): boolean;
|
|
9
11
|
private _jobs;
|
|
10
|
-
constructor(logger: AbstractLogger);
|
|
12
|
+
constructor(logger: AbstractLogger, db: ITransactionFactory);
|
|
11
13
|
protected getAvailableJobInfo(): Promise<JobHandlerMetadata[]>;
|
|
12
14
|
abstract getAllScheduledJobs(): Promise<ScheduledJob[]>;
|
|
13
15
|
/**
|
|
@@ -15,6 +17,7 @@ export declare abstract class AbstractJobScheduler {
|
|
|
15
17
|
* @param jobInfo the necessary info to launch the job
|
|
16
18
|
*/
|
|
17
19
|
protected abstract scheduleJob(jobInfo: JobInfo): Promise<string>;
|
|
20
|
+
protected abstract afterJob(job: ScheduledJob, tx: ITransaction): Promise<void>;
|
|
18
21
|
/**
|
|
19
22
|
* Sync all jobs to running state. This should be called only at init time.
|
|
20
23
|
*/
|
|
@@ -26,9 +29,9 @@ export declare abstract class AbstractJobScheduler {
|
|
|
26
29
|
scheduleJobAt(jobInfo: CustomJobInfo): Promise<string>;
|
|
27
30
|
/**
|
|
28
31
|
* Remove the scheduled job and prevent if from running in the future
|
|
29
|
-
* @param
|
|
32
|
+
* @param id The job id returned from scheduleJobAt function
|
|
30
33
|
*/
|
|
31
|
-
abstract removeJob(
|
|
34
|
+
abstract removeJob(id: string): Promise<void>;
|
|
32
35
|
/**
|
|
33
36
|
* Execute the scheduled job
|
|
34
37
|
* @param job The schedled job info to execute
|
|
@@ -2,9 +2,11 @@ import { getServiceProvider } from "@clairejs/core";
|
|
|
2
2
|
import { AbstractJobController } from "./AbstractJobController";
|
|
3
3
|
export class AbstractJobScheduler {
|
|
4
4
|
logger;
|
|
5
|
+
db;
|
|
5
6
|
_jobs = null;
|
|
6
|
-
constructor(logger) {
|
|
7
|
+
constructor(logger, db) {
|
|
7
8
|
this.logger = logger;
|
|
9
|
+
this.db = db;
|
|
8
10
|
}
|
|
9
11
|
async getAvailableJobInfo() {
|
|
10
12
|
if (this._jobs === null) {
|
|
@@ -33,15 +35,28 @@ export class AbstractJobScheduler {
|
|
|
33
35
|
//-- run job
|
|
34
36
|
const allJobs = await this.getAvailableJobInfo();
|
|
35
37
|
const jobHandler = allJobs.find((j) => j.jobName === job.jobName);
|
|
36
|
-
if (jobHandler) {
|
|
37
|
-
|
|
38
|
+
if (!jobHandler) {
|
|
39
|
+
//-- remove job
|
|
40
|
+
this.logger.info(`Remove job with id: ${job.id} as handler is not found`);
|
|
41
|
+
await this.removeJob(job.id);
|
|
42
|
+
return;
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
await
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const tx = await this.db.createTransaction();
|
|
45
|
+
try {
|
|
46
|
+
const newJob = await jobHandler.handlerFn({ ...job }, tx);
|
|
47
|
+
if (job.at) {
|
|
48
|
+
if (!newJob) {
|
|
49
|
+
await this.removeJob(job.id);
|
|
50
|
+
this.logger.info(`Remove one-time job ${job.jobName} at timestamp ${job.at}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await this.afterJob(newJob, tx);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
await tx.commit();
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
await tx.rollback();
|
|
45
60
|
}
|
|
46
61
|
return;
|
|
47
62
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { AbstractLogger } from "@clairejs/core";
|
|
2
|
+
import { AbstractDatabaseAdapter, ITransaction } from "@clairejs/orm";
|
|
3
|
+
import aws from "aws-sdk";
|
|
2
4
|
import Redis from "ioredis";
|
|
3
5
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
4
6
|
import { JobInfo, ScheduledJob } from "./interfaces";
|
|
5
7
|
export declare class AwsJobScheduler extends AbstractJobScheduler {
|
|
6
|
-
readonly logger: AbstractLogger;
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
8
|
+
protected readonly logger: AbstractLogger;
|
|
9
|
+
protected readonly db: AbstractDatabaseAdapter;
|
|
10
|
+
protected readonly redisClient: Redis;
|
|
11
|
+
protected readonly uniqueIdKey: string;
|
|
12
|
+
protected readonly apiLambdaFunctionArn: string;
|
|
13
|
+
protected readonly stepFunctionName: string;
|
|
11
14
|
/**
|
|
12
15
|
* This IAM role must have following permissions:
|
|
13
16
|
* - trigger any state machine (for one minute rule - as we don't now the state machine ARN until auto creation)
|
|
@@ -17,13 +20,13 @@ export declare class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
17
20
|
* - create event bridge rules, describe rules, remove rule, list and create rule targets
|
|
18
21
|
* - create state machines, list state machines
|
|
19
22
|
*/
|
|
20
|
-
readonly iamRoleArn: string;
|
|
21
|
-
readonly eventBusName: string;
|
|
22
|
-
readonly jobNamePrefix: string;
|
|
23
|
-
readonly oneMinuteRule: string;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
constructor(logger: AbstractLogger, redisClient: Redis, uniqueIdKey: string, apiLambdaFunctionArn: string, stepFunctionName: string,
|
|
23
|
+
protected readonly iamRoleArn: string;
|
|
24
|
+
protected readonly eventBusName: string;
|
|
25
|
+
protected readonly jobNamePrefix: string;
|
|
26
|
+
protected readonly oneMinuteRule: string;
|
|
27
|
+
protected readonly eventbridge: aws.EventBridge;
|
|
28
|
+
protected readonly stepfunctions: aws.StepFunctions;
|
|
29
|
+
constructor(logger: AbstractLogger, db: AbstractDatabaseAdapter, redisClient: Redis, uniqueIdKey: string, apiLambdaFunctionArn: string, stepFunctionName: string,
|
|
27
30
|
/**
|
|
28
31
|
* This IAM role must have following permissions:
|
|
29
32
|
* - trigger any state machine (for one minute rule - as we don't now the state machine ARN until auto creation)
|
|
@@ -36,6 +39,7 @@ export declare class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
36
39
|
iamRoleArn: string, eventBusName?: string, jobNamePrefix?: string, oneMinuteRule?: string);
|
|
37
40
|
handleInterval(interval: number): Promise<void>;
|
|
38
41
|
handleCron(jobInfo: ScheduledJob): Promise<void>;
|
|
42
|
+
protected afterJob(_job: ScheduledJob, _tx: ITransaction): Promise<void>;
|
|
39
43
|
private generateCronFromTimestamp;
|
|
40
44
|
protected isActiveScheduler(): boolean;
|
|
41
45
|
getAllScheduledJobs(): Promise<ScheduledJob[]>;
|
|
@@ -8,6 +8,7 @@ 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 { AbstractLogger, Errors, LogContext } from "@clairejs/core";
|
|
11
|
+
import { AbstractDatabaseAdapter } from "@clairejs/orm";
|
|
11
12
|
import aws from "aws-sdk";
|
|
12
13
|
import Redis from "ioredis";
|
|
13
14
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
@@ -104,6 +105,7 @@ const oneMinuteFunctionFactory = (lambdaFunctionARN, intervals) => {
|
|
|
104
105
|
};
|
|
105
106
|
let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
106
107
|
logger;
|
|
108
|
+
db;
|
|
107
109
|
redisClient;
|
|
108
110
|
uniqueIdKey;
|
|
109
111
|
apiLambdaFunctionArn;
|
|
@@ -114,7 +116,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
114
116
|
oneMinuteRule;
|
|
115
117
|
eventbridge = new aws.EventBridge();
|
|
116
118
|
stepfunctions = new aws.StepFunctions({ apiVersion: "2016-11-23" });
|
|
117
|
-
constructor(logger, redisClient, uniqueIdKey, apiLambdaFunctionArn, stepFunctionName,
|
|
119
|
+
constructor(logger, db, redisClient, uniqueIdKey, apiLambdaFunctionArn, stepFunctionName,
|
|
118
120
|
/**
|
|
119
121
|
* This IAM role must have following permissions:
|
|
120
122
|
* - trigger any state machine (for one minute rule - as we don't now the state machine ARN until auto creation)
|
|
@@ -125,8 +127,9 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
125
127
|
* - create state machines, list state machines
|
|
126
128
|
*/
|
|
127
129
|
iamRoleArn, eventBusName = "default", jobNamePrefix = "claire-aws-job-", oneMinuteRule = "one-minute-step-function-trigger") {
|
|
128
|
-
super(logger);
|
|
130
|
+
super(logger, db);
|
|
129
131
|
this.logger = logger;
|
|
132
|
+
this.db = db;
|
|
130
133
|
this.redisClient = redisClient;
|
|
131
134
|
this.uniqueIdKey = uniqueIdKey;
|
|
132
135
|
this.apiLambdaFunctionArn = apiLambdaFunctionArn;
|
|
@@ -140,12 +143,16 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
140
143
|
this.logger.debug(`Handle interval`, interval, typeof interval);
|
|
141
144
|
const allJobs = await this.getAvailableJobInfo();
|
|
142
145
|
const timedJobs = allJobs.filter((job) => job.interval && interval % job.interval === 0);
|
|
143
|
-
await Promise.all(timedJobs.map((job) => this.executeJob({ ...job,
|
|
146
|
+
await Promise.all(timedJobs.map((job) => this.executeJob({ ...job, id: job.jobName })));
|
|
144
147
|
}
|
|
145
148
|
async handleCron(jobInfo) {
|
|
146
149
|
this.logger.debug(`Handle cron`, jobInfo);
|
|
147
150
|
await this.executeJob(jobInfo);
|
|
148
151
|
}
|
|
152
|
+
async afterJob(_job, _tx) {
|
|
153
|
+
//-- do nothing
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
149
156
|
generateCronFromTimestamp(at) {
|
|
150
157
|
const date = new Date(at);
|
|
151
158
|
return [
|
|
@@ -186,7 +193,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
186
193
|
//-- concat with interval jobs because we don't register them
|
|
187
194
|
return allJobs
|
|
188
195
|
.filter((job) => !!job)
|
|
189
|
-
.concat(availableJobs.filter((j) => j.interval).map((j) => ({ ...j,
|
|
196
|
+
.concat(availableJobs.filter((j) => j.interval).map((j) => ({ ...j, id: j.jobName })));
|
|
190
197
|
}
|
|
191
198
|
async scheduleJob(jobInfo) {
|
|
192
199
|
this.logger.debug("Scheduling job: ", jobInfo);
|
|
@@ -245,8 +252,8 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
245
252
|
//-- remove job that no more exist
|
|
246
253
|
const nomoreExistJobs = scheduledJobs.filter((job) => !allJobs.find((j) => j.jobName === job.jobName));
|
|
247
254
|
for (const job of nomoreExistJobs) {
|
|
248
|
-
this.logger.info(`Removing stale job: ${job.jobName} of id: ${job.
|
|
249
|
-
await this.removeJob(job.
|
|
255
|
+
this.logger.info(`Removing stale job: ${job.jobName} of id: ${job.id}`);
|
|
256
|
+
await this.removeJob(job.id);
|
|
250
257
|
}
|
|
251
258
|
if (nomoreExistJobs.length) {
|
|
252
259
|
this.logger.info(`Cleaned up: ${nomoreExistJobs.length} stale jobs`);
|
|
@@ -254,7 +261,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
254
261
|
//-- remove scheduled cron jobs
|
|
255
262
|
this.logger.debug("Remove scheduled cron jobs");
|
|
256
263
|
const scheduledCronJobs = scheduledJobs.filter((j) => j.cron);
|
|
257
|
-
await Promise.all(scheduledCronJobs.map((j) => this.removeJob(j.
|
|
264
|
+
await Promise.all(scheduledCronJobs.map((j) => this.removeJob(j.id)));
|
|
258
265
|
//-- reschedule cron & interval jobs because we might have updated the cron expression / interval value
|
|
259
266
|
const cronJobs = allJobs.filter((job) => job.cron || job.interval);
|
|
260
267
|
this.logger.debug("Scheduling cron & interval jobs");
|
|
@@ -265,7 +272,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
265
272
|
interval: job.interval,
|
|
266
273
|
});
|
|
267
274
|
}
|
|
268
|
-
//-- keep at
|
|
275
|
+
//-- keep "at" jobs as is
|
|
269
276
|
}
|
|
270
277
|
async removeJob(jobId) {
|
|
271
278
|
await this.eventbridge
|
|
@@ -400,6 +407,7 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
400
407
|
AwsJobScheduler = __decorate([
|
|
401
408
|
LogContext(),
|
|
402
409
|
__metadata("design:paramtypes", [AbstractLogger,
|
|
410
|
+
AbstractDatabaseAdapter,
|
|
403
411
|
Redis, String, String, String, String, Object, Object, Object])
|
|
404
412
|
], AwsJobScheduler);
|
|
405
413
|
export { AwsJobScheduler };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { AbstractLogger, IInit } from "@clairejs/core";
|
|
2
2
|
import Redis from "ioredis";
|
|
3
|
+
import { AbstractDatabaseAdapter, ITransaction } from "@clairejs/orm";
|
|
3
4
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
4
5
|
import { JobInfo, ScheduledJob } from "./interfaces";
|
|
5
6
|
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
6
7
|
export declare class LocalJobScheduler extends AbstractJobScheduler implements IInit {
|
|
7
8
|
protected readonly logger: AbstractLogger;
|
|
9
|
+
protected readonly db: AbstractDatabaseAdapter;
|
|
8
10
|
protected readonly redisClient: Redis;
|
|
9
11
|
protected readonly subscribeClient: Redis;
|
|
10
12
|
protected readonly jobRepo: AbstractJobRepository;
|
|
@@ -33,7 +35,7 @@ export declare class LocalJobScheduler extends AbstractJobScheduler implements I
|
|
|
33
35
|
private isActive;
|
|
34
36
|
private notifyResolver;
|
|
35
37
|
private jobHolder;
|
|
36
|
-
constructor(logger: AbstractLogger, redisClient: Redis, subscribeClient: Redis, jobRepo: AbstractJobRepository,
|
|
38
|
+
constructor(logger: AbstractLogger, db: AbstractDatabaseAdapter, redisClient: Redis, subscribeClient: Redis, jobRepo: AbstractJobRepository,
|
|
37
39
|
/**
|
|
38
40
|
* Redis lock key to select active scheduler
|
|
39
41
|
*/
|
|
@@ -57,11 +59,12 @@ export declare class LocalJobScheduler extends AbstractJobScheduler implements I
|
|
|
57
59
|
private sendJob;
|
|
58
60
|
private processMessage;
|
|
59
61
|
private extendMutexKey;
|
|
62
|
+
protected afterJob({ at, ...job }: ScheduledJob, tx: ITransaction): Promise<void>;
|
|
60
63
|
init(): Promise<void>;
|
|
61
64
|
exit(): void;
|
|
62
65
|
protected isActiveScheduler(): boolean;
|
|
63
66
|
getAllScheduledJobs(): Promise<ScheduledJob[]>;
|
|
64
67
|
syncJobs(): Promise<void>;
|
|
65
68
|
protected scheduleJob(jobInfo: JobInfo): Promise<string>;
|
|
66
|
-
removeJob(
|
|
69
|
+
removeJob(id: string): Promise<void>;
|
|
67
70
|
}
|
|
@@ -11,9 +11,10 @@ import { AbstractLogger, Errors, Initable, LogContext } from "@clairejs/core";
|
|
|
11
11
|
import Redis from "ioredis";
|
|
12
12
|
import Redlock from "redlock";
|
|
13
13
|
import scheduler from "node-schedule";
|
|
14
|
+
import { AbstractDatabaseAdapter } from "@clairejs/orm";
|
|
15
|
+
import assert from "assert";
|
|
14
16
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
15
17
|
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
16
|
-
import { clearInterval, clearTimeout } from "timers";
|
|
17
18
|
var CommunicationMessage;
|
|
18
19
|
(function (CommunicationMessage) {
|
|
19
20
|
CommunicationMessage["SCHEDULE_JOB"] = "SCHEDULE_JOB";
|
|
@@ -23,6 +24,7 @@ var CommunicationMessage;
|
|
|
23
24
|
})(CommunicationMessage || (CommunicationMessage = {}));
|
|
24
25
|
let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
25
26
|
logger;
|
|
27
|
+
db;
|
|
26
28
|
redisClient;
|
|
27
29
|
subscribeClient;
|
|
28
30
|
jobRepo;
|
|
@@ -36,7 +38,7 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
36
38
|
isActive = false;
|
|
37
39
|
notifyResolver = {};
|
|
38
40
|
jobHolder = {};
|
|
39
|
-
constructor(logger, redisClient, subscribeClient, jobRepo,
|
|
41
|
+
constructor(logger, db, redisClient, subscribeClient, jobRepo,
|
|
40
42
|
/**
|
|
41
43
|
* Redis lock key to select active scheduler
|
|
42
44
|
*/
|
|
@@ -57,8 +59,9 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
57
59
|
* The time to lock active scheduler
|
|
58
60
|
*/
|
|
59
61
|
keyRetentionDurationSecond = 30) {
|
|
60
|
-
super(logger);
|
|
62
|
+
super(logger, db);
|
|
61
63
|
this.logger = logger;
|
|
64
|
+
this.db = db;
|
|
62
65
|
this.redisClient = redisClient;
|
|
63
66
|
this.subscribeClient = subscribeClient;
|
|
64
67
|
this.jobRepo = jobRepo;
|
|
@@ -116,6 +119,10 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
116
119
|
await this.redisClient.setex(this.holdMutexKey, this.keyRetentionDurationSecond, 1);
|
|
117
120
|
this.logger.debug("Scheduler extends mutex key");
|
|
118
121
|
}
|
|
122
|
+
async afterJob({ at, ...job }, tx) {
|
|
123
|
+
assert.ok(at);
|
|
124
|
+
await this.jobRepo.saveJob({ ...job, at }, tx);
|
|
125
|
+
}
|
|
119
126
|
async init() {
|
|
120
127
|
this.logger.debug("LocalJobScheduler init");
|
|
121
128
|
//-- subscribe to multi client channel
|
|
@@ -173,7 +180,7 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
182
|
//-- re-schedule jobs that are stored in repo
|
|
176
|
-
const allPersistedJobs = await this.jobRepo.getJobs();
|
|
183
|
+
const allPersistedJobs = await this.jobRepo.getJobs(this.db);
|
|
177
184
|
//-- run job anyway, expired job will be removed then
|
|
178
185
|
for (const job of allPersistedJobs) {
|
|
179
186
|
await this.scheduleJob(job);
|
|
@@ -193,38 +200,39 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
193
200
|
//-- case each job type
|
|
194
201
|
if (jobInfo.at) {
|
|
195
202
|
//-- create new schedule job
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
const id = jobInfo.id ||
|
|
204
|
+
(await this.jobRepo.saveJob({
|
|
205
|
+
jobName: jobInfo.jobName,
|
|
206
|
+
params: jobInfo.params,
|
|
207
|
+
at: jobInfo.at,
|
|
208
|
+
}, this.db));
|
|
201
209
|
//-- use the lib
|
|
202
|
-
const scheduledJob = { ...jobInfo,
|
|
210
|
+
const scheduledJob = { ...jobInfo, id };
|
|
203
211
|
const timeout = setTimeout(() => {
|
|
204
|
-
this.executeJob(scheduledJob).catch((err) => this.logger.error(`Error execute job ${scheduledJob.jobName} with id: ${scheduledJob.
|
|
212
|
+
this.executeJob(scheduledJob).catch((err) => this.logger.error(`Error execute job ${scheduledJob.jobName} with id: ${scheduledJob.id}`, err));
|
|
205
213
|
}, new Date(jobInfo.at).getTime() - Date.now());
|
|
206
|
-
this.jobHolder[
|
|
207
|
-
return
|
|
214
|
+
this.jobHolder[id] = { jobCanceler: () => clearTimeout(timeout), jobInfo: { ...jobInfo, id } };
|
|
215
|
+
return id;
|
|
208
216
|
}
|
|
209
217
|
else if (jobInfo.interval) {
|
|
210
|
-
const
|
|
218
|
+
const id = jobInfo.jobName;
|
|
211
219
|
//-- set interval and does not need to persist
|
|
212
|
-
const scheduledJob = { ...jobInfo,
|
|
220
|
+
const scheduledJob = { ...jobInfo, id };
|
|
213
221
|
const interval = setInterval(() => {
|
|
214
|
-
this.executeJob(scheduledJob).catch((err) => this.logger.error(`Error execute job ${scheduledJob.jobName} with id: ${scheduledJob.
|
|
222
|
+
this.executeJob(scheduledJob).catch((err) => this.logger.error(`Error execute job ${scheduledJob.jobName} with id: ${scheduledJob.id}`, err));
|
|
215
223
|
}, jobInfo.interval);
|
|
216
|
-
this.jobHolder[
|
|
217
|
-
return
|
|
224
|
+
this.jobHolder[id] = { jobCanceler: () => clearInterval(interval), jobInfo: { ...jobInfo, id } };
|
|
225
|
+
return id;
|
|
218
226
|
}
|
|
219
227
|
else if (jobInfo.cron) {
|
|
220
|
-
const
|
|
228
|
+
const id = jobInfo.jobName;
|
|
221
229
|
//-- set cron and does not need to persist
|
|
222
|
-
const scheduledJob = { ...jobInfo,
|
|
230
|
+
const scheduledJob = { ...jobInfo, id };
|
|
223
231
|
const job = scheduler.scheduleJob(jobInfo.cron, () => {
|
|
224
|
-
this.executeJob(scheduledJob).catch((err) => this.logger.error(`Error execute job ${scheduledJob.jobName} with id: ${scheduledJob.
|
|
232
|
+
this.executeJob(scheduledJob).catch((err) => this.logger.error(`Error execute job ${scheduledJob.jobName} with id: ${scheduledJob.id}`, err));
|
|
225
233
|
});
|
|
226
|
-
this.jobHolder[
|
|
227
|
-
return
|
|
234
|
+
this.jobHolder[id] = { jobCanceler: () => job.cancel(), jobInfo: { ...jobInfo, id } };
|
|
235
|
+
return id;
|
|
228
236
|
}
|
|
229
237
|
else {
|
|
230
238
|
throw Errors.SYSTEM_ERROR(`Job does not have time config: ${jobInfo.jobName}`);
|
|
@@ -239,24 +247,23 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
239
247
|
});
|
|
240
248
|
}
|
|
241
249
|
}
|
|
242
|
-
async removeJob(
|
|
250
|
+
async removeJob(id) {
|
|
243
251
|
if (this.isActive) {
|
|
244
252
|
//-- remove from holder
|
|
245
|
-
const job = this.jobHolder[
|
|
253
|
+
const job = this.jobHolder[id];
|
|
246
254
|
if (job) {
|
|
247
255
|
job.jobCanceler();
|
|
248
|
-
this.jobHolder[
|
|
256
|
+
this.jobHolder[id] = undefined;
|
|
249
257
|
}
|
|
250
258
|
//-- remove from persistence
|
|
251
|
-
await this.jobRepo.removeJobById(
|
|
252
|
-
return;
|
|
259
|
+
await this.jobRepo.removeJobById(id, this.db);
|
|
253
260
|
}
|
|
254
261
|
else {
|
|
255
262
|
//-- get unique message id
|
|
256
263
|
const uniqueMessageId = await this.redisClient.incr(this.uniqueIdKey);
|
|
257
264
|
return new Promise((resolve) => {
|
|
258
265
|
this.notifyResolver[uniqueMessageId] = resolve;
|
|
259
|
-
this.sendJob(CommunicationMessage.REMOVE_JOB, uniqueMessageId,
|
|
266
|
+
this.sendJob(CommunicationMessage.REMOVE_JOB, uniqueMessageId, id);
|
|
260
267
|
});
|
|
261
268
|
}
|
|
262
269
|
}
|
|
@@ -265,6 +272,7 @@ LocalJobScheduler = __decorate([
|
|
|
265
272
|
LogContext(),
|
|
266
273
|
Initable(),
|
|
267
274
|
__metadata("design:paramtypes", [AbstractLogger,
|
|
275
|
+
AbstractDatabaseAdapter,
|
|
268
276
|
Redis,
|
|
269
277
|
Redis,
|
|
270
278
|
AbstractJobRepository, String, String, String, String, Number])
|
|
@@ -5,9 +5,8 @@ import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
|
5
5
|
import { AbstractJob } from "./job";
|
|
6
6
|
export declare class DefaultJobRepository extends AbstractJobRepository {
|
|
7
7
|
protected readonly model: Constructor<AbstractJob>;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
removeJobById(jobId: string): Promise<void>;
|
|
8
|
+
constructor(model: Constructor<AbstractJob>);
|
|
9
|
+
getJobs(query: IQueryProvider): Promise<CustomJobInfo[]>;
|
|
10
|
+
saveJob({ id, ...jobInfo }: CustomJobInfo, query: IQueryProvider): Promise<string>;
|
|
11
|
+
removeJobById(jobId: string, query: IQueryProvider): Promise<void>;
|
|
13
12
|
}
|
|
@@ -2,21 +2,27 @@ import { pickData } from "@clairejs/core";
|
|
|
2
2
|
import { AbstractJobRepository } from "./AbstractJobRepository";
|
|
3
3
|
export class DefaultJobRepository extends AbstractJobRepository {
|
|
4
4
|
model;
|
|
5
|
-
|
|
6
|
-
constructor(model, db) {
|
|
5
|
+
constructor(model) {
|
|
7
6
|
super();
|
|
8
7
|
this.model = model;
|
|
9
|
-
this.db = db;
|
|
10
8
|
}
|
|
11
|
-
async getJobs() {
|
|
12
|
-
const jobs = await
|
|
13
|
-
return jobs.map((job) => pickData(job, ["jobName", "at", "params"]));
|
|
9
|
+
async getJobs(query) {
|
|
10
|
+
const jobs = await query.use(this.model).getRecords();
|
|
11
|
+
return jobs.map((job) => pickData(job, ["jobName", "at", "params", "id"]));
|
|
14
12
|
}
|
|
15
|
-
async saveJob(jobInfo) {
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
async saveJob({ id, ...jobInfo }, query) {
|
|
14
|
+
if (id) {
|
|
15
|
+
//-- update
|
|
16
|
+
await query.use(this.model).updateById(id, jobInfo);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
//-- create new
|
|
20
|
+
const job = await query.use(this.model).createOne(jobInfo);
|
|
21
|
+
id = job.id;
|
|
22
|
+
}
|
|
23
|
+
return id;
|
|
18
24
|
}
|
|
19
|
-
async removeJobById(jobId) {
|
|
20
|
-
await
|
|
25
|
+
async removeJobById(jobId, query) {
|
|
26
|
+
await query.use(this.model).deleteById(jobId);
|
|
21
27
|
}
|
|
22
28
|
}
|
package/dist/job/interfaces.d.ts
CHANGED
|
@@ -34,11 +34,13 @@ export interface JobControllerMetadata extends ObjectMetadata {
|
|
|
34
34
|
jobs?: JobInfoMetadata[];
|
|
35
35
|
}
|
|
36
36
|
export interface CustomJobInfo {
|
|
37
|
+
id?: string;
|
|
37
38
|
jobName: string;
|
|
38
39
|
at: number;
|
|
39
40
|
params?: any[];
|
|
40
41
|
}
|
|
41
42
|
export interface JobInfo {
|
|
43
|
+
id?: string;
|
|
42
44
|
jobName: string;
|
|
43
45
|
params?: any[];
|
|
44
46
|
at?: number;
|
|
@@ -46,5 +48,5 @@ export interface JobInfo {
|
|
|
46
48
|
cron?: string;
|
|
47
49
|
}
|
|
48
50
|
export interface ScheduledJob extends JobInfo {
|
|
49
|
-
|
|
51
|
+
id: string;
|
|
50
52
|
}
|