@clairejs/server 3.20.2 → 3.21.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/AwsJobScheduler.d.ts +2 -29
- package/dist/job/AwsJobScheduler.js +28 -260
- package/dist/job/LocalJobScheduler.js +1 -11
- package/dist/job/decorators.d.ts +0 -2
- package/dist/job/decorators.js +0 -19
- package/dist/job/interfaces.d.ts +0 -13
- package/dist/job/interfaces.js +0 -9
- package/dist/system/LambdaWrapper.js +1 -5
- package/dist/system/lamba-request-mapper.js +1 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,43 +1,17 @@
|
|
|
1
1
|
import { AbstractLogger } from "@clairejs/core";
|
|
2
2
|
import { AbstractDbAdapter, ITransaction } from "@clairejs/orm";
|
|
3
3
|
import aws from "aws-sdk";
|
|
4
|
-
import Redis from "ioredis";
|
|
5
4
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
6
5
|
import { JobInfo, ScheduledJob } from "./interfaces";
|
|
7
6
|
export declare class AwsJobScheduler extends AbstractJobScheduler {
|
|
8
7
|
protected readonly logger: AbstractLogger;
|
|
9
8
|
protected readonly db: AbstractDbAdapter;
|
|
10
|
-
protected readonly
|
|
11
|
-
protected readonly uniqueIdKey: string;
|
|
9
|
+
protected readonly uniqueJobIdFactory: () => string;
|
|
12
10
|
protected readonly apiLambdaFunctionArn: string;
|
|
13
|
-
protected readonly stepFunctionName: string;
|
|
14
|
-
/**
|
|
15
|
-
* This IAM role must have following permissions:
|
|
16
|
-
* - trigger any state machine (for one minute rule - as we don't now the state machine ARN until auto creation)
|
|
17
|
-
* - trigger lambda function specified in apiLambdaFunctionArn (so state machine can trigger API)
|
|
18
|
-
*
|
|
19
|
-
* In addition, the role which this API server assumes must have following permissions:
|
|
20
|
-
* - create event bridge rules, describe rules, remove rule, list and create rule targets
|
|
21
|
-
* - create state machines, list state machines
|
|
22
|
-
*/
|
|
23
|
-
protected readonly iamRoleArn: string;
|
|
24
11
|
protected readonly eventBusName: string;
|
|
25
12
|
protected readonly jobNamePrefix: string;
|
|
26
|
-
protected readonly oneMinuteRule: string;
|
|
27
13
|
protected readonly eventbridge: aws.EventBridge;
|
|
28
|
-
|
|
29
|
-
constructor(logger: AbstractLogger, db: AbstractDbAdapter, redisClient: Redis, uniqueIdKey: string, apiLambdaFunctionArn: string, stepFunctionName: string,
|
|
30
|
-
/**
|
|
31
|
-
* This IAM role must have following permissions:
|
|
32
|
-
* - trigger any state machine (for one minute rule - as we don't now the state machine ARN until auto creation)
|
|
33
|
-
* - trigger lambda function specified in apiLambdaFunctionArn (so state machine can trigger API)
|
|
34
|
-
*
|
|
35
|
-
* In addition, the role which this API server assumes must have following permissions:
|
|
36
|
-
* - create event bridge rules, describe rules, remove rule, list and create rule targets
|
|
37
|
-
* - create state machines, list state machines
|
|
38
|
-
*/
|
|
39
|
-
iamRoleArn: string, eventBusName?: string, jobNamePrefix?: string, oneMinuteRule?: string);
|
|
40
|
-
handleInterval(interval: number): Promise<void>;
|
|
14
|
+
constructor(logger: AbstractLogger, db: AbstractDbAdapter, uniqueJobIdFactory: () => string, apiLambdaFunctionArn: string, eventBusName?: string, jobNamePrefix?: string);
|
|
41
15
|
handleCron(jobInfo: ScheduledJob): Promise<void>;
|
|
42
16
|
protected afterJob(_job: ScheduledJob, _tx: ITransaction): Promise<void>;
|
|
43
17
|
private generateCronFromTimestamp;
|
|
@@ -46,5 +20,4 @@ export declare class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
46
20
|
protected scheduleJob(jobInfo: JobInfo): Promise<string>;
|
|
47
21
|
syncJobs(): Promise<void>;
|
|
48
22
|
removeJob(jobId: string): Promise<void>;
|
|
49
|
-
private checkIntervalScheduler;
|
|
50
23
|
}
|
|
@@ -10,140 +10,25 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
import { AbstractLogger, Errors, LogContext } from "@clairejs/core";
|
|
11
11
|
import { AbstractDbAdapter } from "@clairejs/orm";
|
|
12
12
|
import aws from "aws-sdk";
|
|
13
|
-
import Redis from "ioredis";
|
|
14
13
|
import { AbstractJobScheduler } from "./AbstractJobScheduler";
|
|
15
|
-
import { JobInterval } from "./interfaces";
|
|
16
|
-
const gcdBetween = (a, b) => {
|
|
17
|
-
const greater = a > b ? a : b;
|
|
18
|
-
const smaller = a > b ? b : a;
|
|
19
|
-
if (greater <= 0 || smaller <= 0) {
|
|
20
|
-
return 1;
|
|
21
|
-
}
|
|
22
|
-
else if (greater % smaller === 0) {
|
|
23
|
-
return smaller;
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
const result = Math.trunc(greater / smaller);
|
|
27
|
-
return gcdBetween(smaller, greater - smaller * result);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
const gcdOf = (val, arr) => {
|
|
31
|
-
if (!arr.length) {
|
|
32
|
-
return val;
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
return gcdOf(gcdBetween(val, arr[0]), arr.slice(1));
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
const getWaitExpression = (interval) => `Wait ${interval} seconds`;
|
|
39
|
-
const oneMinuteFunctionFactory = (lambdaFunctionARN, intervals) => {
|
|
40
|
-
//-- find most greatest common divisor of intervals
|
|
41
|
-
let gcd = JobInterval.EVERY_30S;
|
|
42
|
-
if (intervals.length) {
|
|
43
|
-
//-- re-calculate gcd
|
|
44
|
-
gcd = gcdOf(intervals[0], intervals.slice(1));
|
|
45
|
-
if (gcd < JobInterval.EVERY_5S) {
|
|
46
|
-
gcd = JobInterval.EVERY_5S;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
//-- create loop array
|
|
50
|
-
const itemCount = Math.trunc(60 / gcd);
|
|
51
|
-
const itemArray = [];
|
|
52
|
-
for (let i = 0; i < itemCount; i++) {
|
|
53
|
-
itemArray.push(i * gcd);
|
|
54
|
-
}
|
|
55
|
-
const definition = `
|
|
56
|
-
{
|
|
57
|
-
"StartAt": "Create items",
|
|
58
|
-
"States": {
|
|
59
|
-
"Create items": {
|
|
60
|
-
"Type": "Pass",
|
|
61
|
-
"Next": "Loop",
|
|
62
|
-
"Result": ${JSON.stringify(itemArray)}
|
|
63
|
-
},
|
|
64
|
-
"Loop": {
|
|
65
|
-
"Type": "Map",
|
|
66
|
-
"End": true,
|
|
67
|
-
"Iterator": {
|
|
68
|
-
"StartAt": "${getWaitExpression(gcd)}",
|
|
69
|
-
"States": {
|
|
70
|
-
"${getWaitExpression(gcd)}": {
|
|
71
|
-
"Type": "Wait",
|
|
72
|
-
"Seconds": ${gcd},
|
|
73
|
-
"Next": "Convert time to request object"
|
|
74
|
-
},
|
|
75
|
-
"Convert time to request object": {
|
|
76
|
-
"Type": "Pass",
|
|
77
|
-
"Next": "Lambda Invoke",
|
|
78
|
-
"Result": {
|
|
79
|
-
"requestContext": {
|
|
80
|
-
"intervalScheduler": {
|
|
81
|
-
"time": "$"
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
"Lambda Invoke": {
|
|
87
|
-
"Type": "Task",
|
|
88
|
-
"Resource": "arn:aws:states:::lambda:invoke",
|
|
89
|
-
"Parameters": {
|
|
90
|
-
"Payload.$": "$",
|
|
91
|
-
"FunctionName": "${lambdaFunctionARN}:$LATEST"
|
|
92
|
-
},
|
|
93
|
-
"End": true,
|
|
94
|
-
"OutputPath": "$.Payload"
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
"MaxConcurrency": 1
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
"Comment": "One minute loop to trigger lambda function"
|
|
102
|
-
}
|
|
103
|
-
`;
|
|
104
|
-
return [gcd, definition];
|
|
105
|
-
};
|
|
106
14
|
let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
107
15
|
logger;
|
|
108
16
|
db;
|
|
109
|
-
|
|
110
|
-
uniqueIdKey;
|
|
17
|
+
uniqueJobIdFactory;
|
|
111
18
|
apiLambdaFunctionArn;
|
|
112
|
-
stepFunctionName;
|
|
113
|
-
iamRoleArn;
|
|
114
19
|
eventBusName;
|
|
115
20
|
jobNamePrefix;
|
|
116
|
-
oneMinuteRule;
|
|
117
21
|
eventbridge = new aws.EventBridge();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* This IAM role must have following permissions:
|
|
122
|
-
* - trigger any state machine (for one minute rule - as we don't now the state machine ARN until auto creation)
|
|
123
|
-
* - trigger lambda function specified in apiLambdaFunctionArn (so state machine can trigger API)
|
|
124
|
-
*
|
|
125
|
-
* In addition, the role which this API server assumes must have following permissions:
|
|
126
|
-
* - create event bridge rules, describe rules, remove rule, list and create rule targets
|
|
127
|
-
* - create state machines, list state machines
|
|
128
|
-
*/
|
|
129
|
-
iamRoleArn, eventBusName = "default", jobNamePrefix = "claire-aws-job-", oneMinuteRule = "one-minute-step-function-trigger") {
|
|
22
|
+
constructor(logger, db, uniqueJobIdFactory,
|
|
23
|
+
//-- ARN of the API lambda function, following EventBrige permissions is required: listRules, putRule, deleteRule, listTargetsByRule, putTargets, removeTargets
|
|
24
|
+
apiLambdaFunctionArn, eventBusName = "default", jobNamePrefix = "claire-aws-job-") {
|
|
130
25
|
super(logger, db);
|
|
131
26
|
this.logger = logger;
|
|
132
27
|
this.db = db;
|
|
133
|
-
this.
|
|
134
|
-
this.uniqueIdKey = uniqueIdKey;
|
|
28
|
+
this.uniqueJobIdFactory = uniqueJobIdFactory;
|
|
135
29
|
this.apiLambdaFunctionArn = apiLambdaFunctionArn;
|
|
136
|
-
this.stepFunctionName = stepFunctionName;
|
|
137
|
-
this.iamRoleArn = iamRoleArn;
|
|
138
30
|
this.eventBusName = eventBusName;
|
|
139
31
|
this.jobNamePrefix = jobNamePrefix;
|
|
140
|
-
this.oneMinuteRule = oneMinuteRule;
|
|
141
|
-
}
|
|
142
|
-
async handleInterval(interval) {
|
|
143
|
-
this.logger.debug(`Handle interval`, interval, typeof interval);
|
|
144
|
-
const allJobs = await this.getAvailableJobInfo();
|
|
145
|
-
const timedJobs = allJobs.filter((job) => job.interval && interval % job.interval === 0);
|
|
146
|
-
await Promise.all(timedJobs.map((job) => this.executeJob({ ...job, id: job.jobName })));
|
|
147
32
|
}
|
|
148
33
|
async handleCron(jobInfo) {
|
|
149
34
|
this.logger.debug(`Handle cron`, jobInfo);
|
|
@@ -168,7 +53,6 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
168
53
|
return false;
|
|
169
54
|
}
|
|
170
55
|
async getAllScheduledJobs() {
|
|
171
|
-
const availableJobs = await this.getAvailableJobInfo();
|
|
172
56
|
const allRules = await this.eventbridge
|
|
173
57
|
.listRules({
|
|
174
58
|
EventBusName: this.eventBusName,
|
|
@@ -191,15 +75,12 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
191
75
|
return jobInfo;
|
|
192
76
|
}));
|
|
193
77
|
//-- concat with interval jobs because we don't register them
|
|
194
|
-
return allJobs
|
|
195
|
-
.filter((job) => !!job)
|
|
196
|
-
.concat(availableJobs.filter((j) => j.interval).map((j) => ({ ...j, id: j.jobName })));
|
|
78
|
+
return allJobs.filter((job) => !!job);
|
|
197
79
|
}
|
|
198
80
|
async scheduleJob(jobInfo) {
|
|
199
81
|
this.logger.debug("Scheduling job: ", jobInfo);
|
|
200
82
|
if (jobInfo.cron || jobInfo.at) {
|
|
201
|
-
const
|
|
202
|
-
const jobId = `${this.jobNamePrefix}${uniqueId}`;
|
|
83
|
+
const jobId = `${this.jobNamePrefix}${this.uniqueJobIdFactory()}`;
|
|
203
84
|
//-- generate pattern from cron (add * at the end for year) / at timestamp
|
|
204
85
|
const cronExpression = jobInfo.cron ? `${jobInfo.cron} *` : this.generateCronFromTimestamp(jobInfo.at);
|
|
205
86
|
this.logger.debug("Cron expression", cronExpression);
|
|
@@ -234,23 +115,20 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
234
115
|
.promise();
|
|
235
116
|
return jobId;
|
|
236
117
|
}
|
|
237
|
-
else if (jobInfo.interval) {
|
|
238
|
-
//-- interval job does not need to persist, the step function will take care of it
|
|
239
|
-
return jobInfo.jobName;
|
|
240
|
-
}
|
|
241
118
|
else {
|
|
242
119
|
throw Errors.SYSTEM_ERROR(`Job does not have time config: ${jobInfo.jobName}`);
|
|
243
120
|
}
|
|
244
121
|
}
|
|
245
122
|
async syncJobs() {
|
|
246
|
-
//-- check and init step function: step function must trigger lambda every 5s
|
|
247
|
-
this.logger.debug("Checking interval scheduler");
|
|
248
|
-
await this.checkIntervalScheduler();
|
|
249
123
|
//-- check jobs
|
|
124
|
+
console.log("syncJobs started");
|
|
250
125
|
const scheduledJobs = await this.getAllScheduledJobs();
|
|
126
|
+
console.log("scheduledJobs", scheduledJobs);
|
|
251
127
|
const allJobs = await this.getAvailableJobInfo();
|
|
128
|
+
console.log("allJobs", allJobs);
|
|
252
129
|
//-- remove job that no more exist
|
|
253
130
|
const nomoreExistJobs = scheduledJobs.filter((job) => !allJobs.find((j) => j.jobName === job.jobName));
|
|
131
|
+
console.log("nomoreExistJobs", nomoreExistJobs);
|
|
254
132
|
for (const job of nomoreExistJobs) {
|
|
255
133
|
this.logger.info(`Removing stale job: ${job.jobName} of id: ${job.id}`);
|
|
256
134
|
await this.removeJob(job.id);
|
|
@@ -258,21 +136,26 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
258
136
|
if (nomoreExistJobs.length) {
|
|
259
137
|
this.logger.info(`Cleaned up: ${nomoreExistJobs.length} stale jobs`);
|
|
260
138
|
}
|
|
261
|
-
//-- remove scheduled cron jobs
|
|
139
|
+
//-- remove scheduled cron jobs that diff the cron expression
|
|
262
140
|
this.logger.debug("Remove scheduled cron jobs");
|
|
263
141
|
const scheduledCronJobs = scheduledJobs.filter((j) => j.cron);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
142
|
+
const unmatchedCronJobs = scheduledCronJobs.filter((j) => j.cron && !allJobs.find((job) => job.jobName === j.jobName && job.cron === j.cron));
|
|
143
|
+
if (unmatchedCronJobs.length) {
|
|
144
|
+
await Promise.all(unmatchedCronJobs.map((j) => this.removeJob(j.id)));
|
|
145
|
+
//-- reschedule new cron jobs and those which are not synced
|
|
146
|
+
const resyncCronJobs = allJobs.filter((job) => job.cron &&
|
|
147
|
+
(unmatchedCronJobs.some((j) => j.jobName === job.jobName) ||
|
|
148
|
+
!scheduledCronJobs.some((j) => j.jobName === job.jobName)));
|
|
149
|
+
this.logger.debug("Reschedule cron jobs", resyncCronJobs);
|
|
150
|
+
for (const job of resyncCronJobs) {
|
|
151
|
+
await this.scheduleJob({
|
|
152
|
+
jobName: job.jobName,
|
|
153
|
+
cron: job.cron,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
274
156
|
}
|
|
275
157
|
//-- keep "at" jobs as is
|
|
158
|
+
console.log("syncJobs finished");
|
|
276
159
|
}
|
|
277
160
|
async removeJob(jobId) {
|
|
278
161
|
await this.eventbridge
|
|
@@ -289,125 +172,10 @@ let AwsJobScheduler = class AwsJobScheduler extends AbstractJobScheduler {
|
|
|
289
172
|
})
|
|
290
173
|
.promise();
|
|
291
174
|
}
|
|
292
|
-
async checkIntervalScheduler() {
|
|
293
|
-
//-- check and create the step function
|
|
294
|
-
const allJobs = await this.getAvailableJobInfo();
|
|
295
|
-
const intervalJobs = allJobs.filter((j) => j.interval);
|
|
296
|
-
const allIntervals = intervalJobs.map((j) => j.interval);
|
|
297
|
-
const [interval, oneMinuteFunctionDefinition] = oneMinuteFunctionFactory(this.apiLambdaFunctionArn, allIntervals);
|
|
298
|
-
this.logger.debug("Listing all state machines");
|
|
299
|
-
const allMachines = await this.stepfunctions.listStateMachines({}).promise();
|
|
300
|
-
let oldStateMachineArn = allMachines.stateMachines.find((s) => s.name.includes(this.stepFunctionName))?.stateMachineArn;
|
|
301
|
-
let newStateMachineArn = oldStateMachineArn;
|
|
302
|
-
if (oldStateMachineArn) {
|
|
303
|
-
//-- check definition, if different then clear newStateMachineArn to create new one
|
|
304
|
-
const describeResult = await this.stepfunctions
|
|
305
|
-
.describeStateMachine({
|
|
306
|
-
stateMachineArn: oldStateMachineArn,
|
|
307
|
-
})
|
|
308
|
-
.promise();
|
|
309
|
-
//-- check the interval
|
|
310
|
-
if (!describeResult.definition.includes(getWaitExpression(interval))) {
|
|
311
|
-
this.logger.debug("Step function definition changed, create new state machine");
|
|
312
|
-
newStateMachineArn = "";
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
const awsStateMachineName = `${this.stepFunctionName}-${interval}`;
|
|
316
|
-
if (!newStateMachineArn) {
|
|
317
|
-
this.logger.debug(`Create new step function with interval ${interval} seconds`);
|
|
318
|
-
const result = await this.stepfunctions
|
|
319
|
-
.createStateMachine({
|
|
320
|
-
definition: oneMinuteFunctionDefinition,
|
|
321
|
-
name: awsStateMachineName,
|
|
322
|
-
roleArn: this.iamRoleArn,
|
|
323
|
-
type: "EXPRESS",
|
|
324
|
-
})
|
|
325
|
-
.promise();
|
|
326
|
-
newStateMachineArn = result.stateMachineArn;
|
|
327
|
-
}
|
|
328
|
-
this.logger.debug("Step function ARNs old / new", oldStateMachineArn, newStateMachineArn);
|
|
329
|
-
//-- check and create the one-minute-rule that trigger step function
|
|
330
|
-
this.logger.debug("Getting one minute rule");
|
|
331
|
-
const matchedRules = await this.eventbridge
|
|
332
|
-
.listRules({
|
|
333
|
-
EventBusName: this.eventBusName,
|
|
334
|
-
NamePrefix: this.oneMinuteRule,
|
|
335
|
-
})
|
|
336
|
-
.promise();
|
|
337
|
-
const oneMinuteRule = matchedRules.Rules?.find((r) => r.Name == this.oneMinuteRule);
|
|
338
|
-
if (!oneMinuteRule) {
|
|
339
|
-
this.logger.debug("Create new one minute rule");
|
|
340
|
-
//-- one minute rule does not exist, create new
|
|
341
|
-
await this.eventbridge
|
|
342
|
-
.putRule({
|
|
343
|
-
EventBusName: this.eventBusName,
|
|
344
|
-
Name: this.oneMinuteRule,
|
|
345
|
-
Description: "One minute trigger function for step function",
|
|
346
|
-
ScheduleExpression: "rate(1 minute)",
|
|
347
|
-
State: "ENABLED",
|
|
348
|
-
})
|
|
349
|
-
.promise();
|
|
350
|
-
}
|
|
351
|
-
this.logger.debug("Adding step function state machine as target");
|
|
352
|
-
if (newStateMachineArn !== oldStateMachineArn) {
|
|
353
|
-
//-- if there is old target then remove it
|
|
354
|
-
if (oldStateMachineArn) {
|
|
355
|
-
this.logger.debug("Removing old target", oldStateMachineArn);
|
|
356
|
-
//-- remove old target
|
|
357
|
-
await this.eventbridge
|
|
358
|
-
.removeTargets({
|
|
359
|
-
EventBusName: this.eventBusName,
|
|
360
|
-
Rule: this.oneMinuteRule,
|
|
361
|
-
Ids: [this.stepFunctionName],
|
|
362
|
-
})
|
|
363
|
-
.promise();
|
|
364
|
-
this.logger.debug("Removing old state machine function");
|
|
365
|
-
await this.stepfunctions
|
|
366
|
-
.deleteStateMachine({
|
|
367
|
-
stateMachineArn: oldStateMachineArn,
|
|
368
|
-
})
|
|
369
|
-
.promise();
|
|
370
|
-
}
|
|
371
|
-
//-- add the step function as call target
|
|
372
|
-
this.logger.debug("Adding new target", newStateMachineArn);
|
|
373
|
-
await this.eventbridge
|
|
374
|
-
.putTargets({
|
|
375
|
-
Rule: this.oneMinuteRule,
|
|
376
|
-
Targets: [
|
|
377
|
-
{
|
|
378
|
-
Arn: newStateMachineArn,
|
|
379
|
-
Id: this.stepFunctionName,
|
|
380
|
-
RoleArn: this.iamRoleArn,
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
})
|
|
384
|
-
.promise();
|
|
385
|
-
}
|
|
386
|
-
//-- if we don't have any interval job then disable the one minuter
|
|
387
|
-
if (!intervalJobs.length && oneMinuteRule?.State) {
|
|
388
|
-
this.logger.info("No interval job found, disable one minute rule");
|
|
389
|
-
await this.eventbridge
|
|
390
|
-
.disableRule({
|
|
391
|
-
EventBusName: this.eventBusName,
|
|
392
|
-
Name: this.oneMinuteRule,
|
|
393
|
-
})
|
|
394
|
-
.promise();
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
this.logger.info("Interval job found, enable one minute rule");
|
|
398
|
-
await this.eventbridge
|
|
399
|
-
.enableRule({
|
|
400
|
-
EventBusName: this.eventBusName,
|
|
401
|
-
Name: this.oneMinuteRule,
|
|
402
|
-
})
|
|
403
|
-
.promise();
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
175
|
};
|
|
407
176
|
AwsJobScheduler = __decorate([
|
|
408
177
|
LogContext(),
|
|
409
178
|
__metadata("design:paramtypes", [AbstractLogger,
|
|
410
|
-
AbstractDbAdapter,
|
|
411
|
-
Redis, String, String, String, String, Object, Object, Object])
|
|
179
|
+
AbstractDbAdapter, Function, String, Object, Object])
|
|
412
180
|
], AwsJobScheduler);
|
|
413
181
|
export { AwsJobScheduler };
|
|
@@ -175,7 +175,7 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
175
175
|
//-- schedule all cron & interval jobs
|
|
176
176
|
const allJobs = await this.getAvailableJobInfo();
|
|
177
177
|
for (const job of allJobs) {
|
|
178
|
-
if (job.cron
|
|
178
|
+
if (job.cron) {
|
|
179
179
|
await this.scheduleJob(job);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
@@ -214,16 +214,6 @@ let LocalJobScheduler = class LocalJobScheduler extends AbstractJobScheduler {
|
|
|
214
214
|
this.jobHolder[id] = { jobCanceler: () => clearTimeout(timeout), jobInfo: { ...jobInfo, id } };
|
|
215
215
|
return id;
|
|
216
216
|
}
|
|
217
|
-
else if (jobInfo.interval) {
|
|
218
|
-
const id = jobInfo.jobName;
|
|
219
|
-
//-- set interval and does not need to persist
|
|
220
|
-
const scheduledJob = { ...jobInfo, id };
|
|
221
|
-
const interval = setInterval(() => {
|
|
222
|
-
this.executeJob(scheduledJob).catch((err) => this.logger.error(`Error execute job ${scheduledJob.jobName} with id: ${scheduledJob.id}`, err));
|
|
223
|
-
}, jobInfo.interval);
|
|
224
|
-
this.jobHolder[id] = { jobCanceler: () => clearInterval(interval), jobInfo: { ...jobInfo, id } };
|
|
225
|
-
return id;
|
|
226
|
-
}
|
|
227
217
|
else if (jobInfo.cron) {
|
|
228
218
|
const id = jobInfo.jobName;
|
|
229
219
|
//-- set cron and does not need to persist
|
package/dist/job/decorators.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import { AbstractJobController } from "./AbstractJobController";
|
|
2
|
-
import { JobInterval } from "./interfaces";
|
|
3
|
-
export declare const IntervalJob: (jobName: string, interval: JobInterval) => <T extends AbstractJobController>(prototype: T, propertyKey: keyof T) => void;
|
|
4
2
|
export declare const CronJob: (jobName: string, cron: string) => <T extends AbstractJobController>(prototype: T, propertyKey: keyof T) => void;
|
|
5
3
|
export declare const CustomJob: (jobName: string) => <T extends AbstractJobController>(prototype: T, propertyKey: keyof T) => void;
|
package/dist/job/decorators.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
1
|
import { Errors, initObjectMetadata } from "@clairejs/core";
|
|
2
|
-
export const IntervalJob = (
|
|
3
|
-
/**
|
|
4
|
-
* Unique name of job
|
|
5
|
-
*/
|
|
6
|
-
jobName,
|
|
7
|
-
/**
|
|
8
|
-
* Interval duration in second
|
|
9
|
-
*/
|
|
10
|
-
interval) => (prototype, propertyKey) => {
|
|
11
|
-
const metadata = initObjectMetadata(prototype);
|
|
12
|
-
if (!metadata.jobs) {
|
|
13
|
-
metadata.jobs = [];
|
|
14
|
-
}
|
|
15
|
-
metadata.jobs.push({
|
|
16
|
-
jobName,
|
|
17
|
-
interval,
|
|
18
|
-
handlerName: propertyKey,
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
2
|
export const CronJob = (
|
|
22
3
|
/**
|
|
23
4
|
* Unique name of job
|
package/dist/job/interfaces.d.ts
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import { ObjectMetadata } from "@clairejs/core";
|
|
2
|
-
export declare const INTERVAL_REQUEST_METHOD = "interval";
|
|
3
2
|
export declare const CRON_REQUEST_METHOD = "cron";
|
|
4
|
-
export declare enum JobInterval {
|
|
5
|
-
EVERY_5S = 5,
|
|
6
|
-
EVERY_10S = 10,
|
|
7
|
-
EVERY_15S = 15,
|
|
8
|
-
EVERY_20S = 20,
|
|
9
|
-
EVERY_30S = 30
|
|
10
|
-
}
|
|
11
3
|
export interface JobInfoMetadata {
|
|
12
4
|
/**
|
|
13
5
|
* Unique name of job
|
|
14
6
|
*/
|
|
15
7
|
jobName: string;
|
|
16
|
-
/**
|
|
17
|
-
* Interval in seconds
|
|
18
|
-
*/
|
|
19
|
-
interval?: JobInterval;
|
|
20
8
|
/**
|
|
21
9
|
* Run with cron expression, does not support seconds precision
|
|
22
10
|
*/
|
|
@@ -44,7 +32,6 @@ export interface JobInfo {
|
|
|
44
32
|
jobName: string;
|
|
45
33
|
params?: any;
|
|
46
34
|
at?: number;
|
|
47
|
-
interval?: JobInterval;
|
|
48
35
|
cron?: string;
|
|
49
36
|
}
|
|
50
37
|
export interface ScheduledJob extends JobInfo {
|
package/dist/job/interfaces.js
CHANGED
|
@@ -1,10 +1 @@
|
|
|
1
|
-
export const INTERVAL_REQUEST_METHOD = "interval";
|
|
2
1
|
export const CRON_REQUEST_METHOD = "cron";
|
|
3
|
-
export var JobInterval;
|
|
4
|
-
(function (JobInterval) {
|
|
5
|
-
JobInterval[JobInterval["EVERY_5S"] = 5] = "EVERY_5S";
|
|
6
|
-
JobInterval[JobInterval["EVERY_10S"] = 10] = "EVERY_10S";
|
|
7
|
-
JobInterval[JobInterval["EVERY_15S"] = 15] = "EVERY_15S";
|
|
8
|
-
JobInterval[JobInterval["EVERY_20S"] = 20] = "EVERY_20S";
|
|
9
|
-
JobInterval[JobInterval["EVERY_30S"] = 30] = "EVERY_30S";
|
|
10
|
-
})(JobInterval || (JobInterval = {}));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getServiceProvider, HttpMethod, SocketMethod, Errors } from "@clairejs/core";
|
|
2
2
|
import { AbstractHttpRequestHandler } from "../http/controller/AbstractHttpRequestHandler";
|
|
3
3
|
import { AwsJobScheduler } from "../job/AwsJobScheduler";
|
|
4
|
-
import { CRON_REQUEST_METHOD
|
|
4
|
+
import { CRON_REQUEST_METHOD } from "../job/interfaces";
|
|
5
5
|
import { AbstractServerSocketManager } from "../socket/AbstractServerSocketManager";
|
|
6
6
|
const convertCorsToHeaders = (origin, cors) => {
|
|
7
7
|
const headers = {};
|
|
@@ -99,10 +99,6 @@ export class LambdaWrapper {
|
|
|
99
99
|
if (requestOptions.method === HttpMethod.OPTIONS) {
|
|
100
100
|
return toApiGatewayFormat({ code: 200, headers: corsHeaders, cookies: {} });
|
|
101
101
|
}
|
|
102
|
-
if (requestOptions.method === INTERVAL_REQUEST_METHOD) {
|
|
103
|
-
await this.jobScheduler?.handleInterval(requestOptions.body);
|
|
104
|
-
return toApiGatewayFormat({ code: 200, headers: corsHeaders, cookies: {} });
|
|
105
|
-
}
|
|
106
102
|
if (requestOptions.method === CRON_REQUEST_METHOD) {
|
|
107
103
|
await this.jobScheduler?.handleCron(requestOptions.body);
|
|
108
104
|
return toApiGatewayFormat({ code: 200, headers: corsHeaders, cookies: {} });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SocketMethod } from "@clairejs/core";
|
|
2
|
-
import { CRON_REQUEST_METHOD
|
|
2
|
+
import { CRON_REQUEST_METHOD } from "../job/interfaces";
|
|
3
3
|
const parseCookie = (cookieArray) => {
|
|
4
4
|
let result = {};
|
|
5
5
|
for (const cookie of cookieArray) {
|
|
@@ -21,14 +21,6 @@ export const lambdaRequestMapper = (event) => {
|
|
|
21
21
|
body: method === SocketMethod.CONNECT ? { queries: event.queryStringParameters } : body,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
if (event.requestContext.intervalScheduler) {
|
|
25
|
-
//-- interval scheduler
|
|
26
|
-
return {
|
|
27
|
-
method: INTERVAL_REQUEST_METHOD,
|
|
28
|
-
rawPath: "",
|
|
29
|
-
body: event.requestContext.intervalScheduler.time,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
24
|
if (event.requestContext.cronScheduler) {
|
|
33
25
|
//-- cron scheduler
|
|
34
26
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clairejs/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.21.1",
|
|
4
4
|
"description": "Claire server NodeJs framework written in Typescript.",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"ws": "^7.5.5"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@clairejs/core": "^3.8.
|
|
37
|
+
"@clairejs/core": "^3.8.7",
|
|
38
38
|
"@clairejs/orm": "^3.16.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|