@orion-js/dogs 3.12.0 → 3.13.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/LICENSE +21 -0
- package/lib/defineJob/index.d.ts +2 -0
- package/lib/defineJob/index.js +12 -0
- package/lib/events.test.d.ts +1 -0
- package/lib/events.test.js +133 -0
- package/lib/history.test.d.ts +1 -0
- package/lib/history.test.js +140 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +40 -0
- package/lib/recurrent.test.d.ts +1 -0
- package/lib/recurrent.test.js +47 -0
- package/lib/repos/JobsHistoryRepo.d.ts +7 -0
- package/lib/repos/JobsHistoryRepo.js +67 -0
- package/lib/repos/JobsRepo.d.ts +19 -0
- package/lib/repos/JobsRepo.js +158 -0
- package/lib/service/index.d.ts +10 -0
- package/lib/service/index.js +50 -0
- package/lib/service/index.test.d.ts +1 -0
- package/lib/service/index.test.js +51 -0
- package/lib/services/EventsService.d.ts +5 -0
- package/lib/services/EventsService.js +36 -0
- package/lib/services/Executor.d.ts +20 -0
- package/lib/services/Executor.js +195 -0
- package/lib/services/WorkerService.d.ts +16 -0
- package/lib/services/WorkerService.js +142 -0
- package/lib/services/WorkerService.test.d.ts +1 -0
- package/lib/services/WorkerService.test.js +10 -0
- package/lib/services/getNextRunDate.d.ts +9 -0
- package/lib/services/getNextRunDate.js +19 -0
- package/lib/stale.test.d.ts +1 -0
- package/lib/stale.test.js +108 -0
- package/lib/tests/setup.d.ts +1 -0
- package/lib/tests/setup.js +19 -0
- package/lib/types/Events.d.ts +21 -0
- package/lib/types/Events.js +2 -0
- package/lib/types/HistoryRecord.d.ts +21 -0
- package/lib/types/HistoryRecord.js +83 -0
- package/lib/types/JobRecord.d.ts +13 -0
- package/lib/types/JobRecord.js +59 -0
- package/lib/types/JobsDefinition.d.ts +61 -0
- package/lib/types/JobsDefinition.js +2 -0
- package/lib/types/StartConfig.d.ts +28 -0
- package/lib/types/StartConfig.js +2 -0
- package/lib/types/Worker.d.ts +38 -0
- package/lib/types/Worker.js +2 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/index.js +22 -0
- package/package.json +22 -23
- package/dist/index.cjs +0 -114048
- package/dist/index.d.ts +0 -215
- package/dist/index.js +0 -114022
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EventJobDefinition, JobDefinition, RecurrentJobDefinition } from '../types';
|
|
2
|
+
export declare function Jobs(): ClassDecorator;
|
|
3
|
+
export interface JobsPropertyDescriptor extends Omit<PropertyDecorator, 'value'> {
|
|
4
|
+
value?: JobDefinition['resolve'];
|
|
5
|
+
}
|
|
6
|
+
export declare function RecurrentJob(options: Omit<RecurrentJobDefinition, 'resolve' | 'type'>): (target: any, propertyKey: string, descriptor: JobsPropertyDescriptor) => void;
|
|
7
|
+
export declare function EventJob(options?: Omit<EventJobDefinition, 'resolve' | 'type'>): (target: any, propertyKey: string, descriptor: JobsPropertyDescriptor) => void;
|
|
8
|
+
export declare function getServiceJobs(target: any): {
|
|
9
|
+
[key: string]: JobDefinition;
|
|
10
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Jobs = Jobs;
|
|
4
|
+
exports.RecurrentJob = RecurrentJob;
|
|
5
|
+
exports.EventJob = EventJob;
|
|
6
|
+
exports.getServiceJobs = getServiceJobs;
|
|
7
|
+
const services_1 = require("@orion-js/services");
|
|
8
|
+
const defineJob_1 = require("../defineJob");
|
|
9
|
+
function Jobs() {
|
|
10
|
+
return function (target) {
|
|
11
|
+
(0, services_1.Service)()(target);
|
|
12
|
+
target.prototype.service = target;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function RecurrentJob(options) {
|
|
16
|
+
return function (target, propertyKey, descriptor) {
|
|
17
|
+
if (!descriptor.value)
|
|
18
|
+
throw new Error(`You must pass resolver function to ${propertyKey}`);
|
|
19
|
+
target.echoes = target.echoes || {};
|
|
20
|
+
target.echoes[propertyKey] = (0, defineJob_1.defineJob)({
|
|
21
|
+
...options,
|
|
22
|
+
type: 'recurrent',
|
|
23
|
+
resolve: async (params, viewer) => {
|
|
24
|
+
const instance = (0, services_1.getInstance)(target.service);
|
|
25
|
+
return await instance[propertyKey](params, viewer);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function EventJob(options = {}) {
|
|
31
|
+
return function (target, propertyKey, descriptor) {
|
|
32
|
+
if (!descriptor.value)
|
|
33
|
+
throw new Error(`You must pass resolver function to ${propertyKey}`);
|
|
34
|
+
target.echoes = target.echoes || {};
|
|
35
|
+
target.echoes[propertyKey] = (0, defineJob_1.defineJob)({
|
|
36
|
+
...options,
|
|
37
|
+
type: 'event',
|
|
38
|
+
resolve: async (params, viewer) => {
|
|
39
|
+
const instance = (0, services_1.getInstance)(target.service);
|
|
40
|
+
return await instance[propertyKey](params, viewer);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function getServiceJobs(target) {
|
|
46
|
+
if (!target.prototype) {
|
|
47
|
+
throw new Error('You must pass a class to getServiceRoutes');
|
|
48
|
+
}
|
|
49
|
+
return target.prototype.echoes || {};
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const _1 = require(".");
|
|
13
|
+
describe('Jobs (dogs) with service injections', () => {
|
|
14
|
+
it('Should define a jobs map using services', async () => {
|
|
15
|
+
let ExampleJobsService = class ExampleJobsService {
|
|
16
|
+
async job1() {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
async job2() {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
__decorate([
|
|
24
|
+
(0, _1.EventJob)(),
|
|
25
|
+
__metadata("design:type", Function),
|
|
26
|
+
__metadata("design:paramtypes", []),
|
|
27
|
+
__metadata("design:returntype", Promise)
|
|
28
|
+
], ExampleJobsService.prototype, "job1", null);
|
|
29
|
+
__decorate([
|
|
30
|
+
(0, _1.RecurrentJob)({ runEvery: 1000 }),
|
|
31
|
+
__metadata("design:type", Function),
|
|
32
|
+
__metadata("design:paramtypes", []),
|
|
33
|
+
__metadata("design:returntype", Promise)
|
|
34
|
+
], ExampleJobsService.prototype, "job2", null);
|
|
35
|
+
ExampleJobsService = __decorate([
|
|
36
|
+
(0, _1.Jobs)()
|
|
37
|
+
], ExampleJobsService);
|
|
38
|
+
const jobs = (0, _1.getServiceJobs)(ExampleJobsService);
|
|
39
|
+
expect(jobs).toMatchObject({
|
|
40
|
+
job1: {
|
|
41
|
+
type: 'event',
|
|
42
|
+
resolve: expect.any(Function)
|
|
43
|
+
},
|
|
44
|
+
job2: {
|
|
45
|
+
type: 'recurrent',
|
|
46
|
+
runEvery: 1000,
|
|
47
|
+
resolve: expect.any(Function)
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EventsService = void 0;
|
|
13
|
+
const logger_1 = require("@orion-js/logger");
|
|
14
|
+
const services_1 = require("@orion-js/services");
|
|
15
|
+
const JobsRepo_1 = require("../repos/JobsRepo");
|
|
16
|
+
const getNextRunDate_1 = require("./getNextRunDate");
|
|
17
|
+
let EventsService = class EventsService {
|
|
18
|
+
async scheduleJob(options) {
|
|
19
|
+
logger_1.logger.debug('Scheduling job...', options);
|
|
20
|
+
await this.jobsRepo.scheduleJob({
|
|
21
|
+
name: options.name,
|
|
22
|
+
priority: options.priority || 100,
|
|
23
|
+
nextRunAt: (0, getNextRunDate_1.getNextRunDate)(options),
|
|
24
|
+
params: options.params || null,
|
|
25
|
+
uniqueIdentifier: options.uniqueIdentifier
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
exports.EventsService = EventsService;
|
|
30
|
+
__decorate([
|
|
31
|
+
(0, services_1.Inject)(),
|
|
32
|
+
__metadata("design:type", JobsRepo_1.JobsRepo)
|
|
33
|
+
], EventsService.prototype, "jobsRepo", void 0);
|
|
34
|
+
exports.EventsService = EventsService = __decorate([
|
|
35
|
+
(0, services_1.Service)()
|
|
36
|
+
], EventsService);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PlainObject } from '../types/HistoryRecord';
|
|
2
|
+
import { JobDefinition, JobsDefinition } from '../types/JobsDefinition';
|
|
3
|
+
import { ExecutionContext, JobToRun } from '../types/Worker';
|
|
4
|
+
export declare class Executor {
|
|
5
|
+
private readonly jobsRepo;
|
|
6
|
+
private readonly jobsHistoryRepo;
|
|
7
|
+
getContext(job: JobDefinition, jobToRun: JobToRun, onStale: Function): ExecutionContext;
|
|
8
|
+
getJobDefinition(jobToRun: JobToRun, jobs: JobsDefinition): JobDefinition;
|
|
9
|
+
onError(error: any, job: JobDefinition, jobToRun: JobToRun, context: ExecutionContext): Promise<void>;
|
|
10
|
+
saveExecution(options: {
|
|
11
|
+
startedAt: Date;
|
|
12
|
+
status: 'stale' | 'error' | 'success';
|
|
13
|
+
errorMessage?: string;
|
|
14
|
+
result?: PlainObject;
|
|
15
|
+
job: JobDefinition;
|
|
16
|
+
jobToRun: JobToRun;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
afterExecutionSuccess(job: JobDefinition, jobToRun: JobToRun, context: ExecutionContext): Promise<void>;
|
|
19
|
+
executeJob(jobs: JobsDefinition, jobToRun: JobToRun, respawnWorker: Function): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.Executor = void 0;
|
|
13
|
+
const logger_1 = require("@orion-js/logger");
|
|
14
|
+
const services_1 = require("@orion-js/services");
|
|
15
|
+
const JobsHistoryRepo_1 = require("../repos/JobsHistoryRepo");
|
|
16
|
+
const JobsRepo_1 = require("../repos/JobsRepo");
|
|
17
|
+
const getNextRunDate_1 = require("./getNextRunDate");
|
|
18
|
+
const api_1 = require("@opentelemetry/api");
|
|
19
|
+
let Executor = class Executor {
|
|
20
|
+
getContext(job, jobToRun, onStale) {
|
|
21
|
+
let staleTimeout = setTimeout(() => onStale(), jobToRun.lockTime);
|
|
22
|
+
return {
|
|
23
|
+
definition: job,
|
|
24
|
+
record: jobToRun,
|
|
25
|
+
tries: jobToRun.tries || 0,
|
|
26
|
+
clearStaleTimeout: () => clearTimeout(staleTimeout),
|
|
27
|
+
extendLockTime: async (extraTime) => {
|
|
28
|
+
clearTimeout(staleTimeout);
|
|
29
|
+
staleTimeout = setTimeout(() => onStale(), extraTime);
|
|
30
|
+
await this.jobsRepo.extendLockTime(jobToRun.jobId, extraTime);
|
|
31
|
+
},
|
|
32
|
+
logger: logger_1.logger.addMetadata({
|
|
33
|
+
jobName: jobToRun.name,
|
|
34
|
+
jobId: jobToRun.jobId,
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
getJobDefinition(jobToRun, jobs) {
|
|
39
|
+
const job = jobs[jobToRun.name];
|
|
40
|
+
if (jobToRun.type !== job.type) {
|
|
41
|
+
logger_1.logger.warn(`Job record "${jobToRun.name}" is "${jobToRun.type}" but definition is "${job.type}"`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
return job;
|
|
45
|
+
}
|
|
46
|
+
async onError(error, job, jobToRun, context) {
|
|
47
|
+
const scheduleRecurrent = async () => {
|
|
48
|
+
if (job.type === 'recurrent') {
|
|
49
|
+
await this.jobsRepo.scheduleNextRun({
|
|
50
|
+
jobId: jobToRun.jobId,
|
|
51
|
+
nextRunAt: (0, getNextRunDate_1.getNextRunDate)(job),
|
|
52
|
+
addTries: false,
|
|
53
|
+
priority: job.priority,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
if (!job.onError) {
|
|
58
|
+
context.logger.error(`Error executing job "${jobToRun.name}"`, error);
|
|
59
|
+
await scheduleRecurrent();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
context.logger.info(`Error executing job "${jobToRun.name}"`, error);
|
|
63
|
+
const result = await job.onError(error, jobToRun.params, context);
|
|
64
|
+
if (result.action === 'dismiss') {
|
|
65
|
+
await scheduleRecurrent();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (result.action === 'retry') {
|
|
69
|
+
await this.jobsRepo.scheduleNextRun({
|
|
70
|
+
jobId: jobToRun.jobId,
|
|
71
|
+
nextRunAt: (0, getNextRunDate_1.getNextRunDate)(result),
|
|
72
|
+
addTries: true,
|
|
73
|
+
priority: job.type === 'recurrent' ? job.priority : jobToRun.priority,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async saveExecution(options) {
|
|
78
|
+
const { startedAt, status, errorMessage, result, job, jobToRun } = options;
|
|
79
|
+
const endedAt = new Date();
|
|
80
|
+
if (job.saveExecutionsFor !== 0) {
|
|
81
|
+
const oneWeek = 1000 * 60 * 60 * 24 * 7;
|
|
82
|
+
const saveExecutionsFor = job.saveExecutionsFor || oneWeek;
|
|
83
|
+
await this.jobsHistoryRepo.saveExecution({
|
|
84
|
+
jobId: jobToRun.jobId,
|
|
85
|
+
executionId: jobToRun.executionId,
|
|
86
|
+
jobName: jobToRun.name,
|
|
87
|
+
type: jobToRun.type,
|
|
88
|
+
priority: jobToRun.priority,
|
|
89
|
+
tries: jobToRun.tries,
|
|
90
|
+
uniqueIdentifier: jobToRun.uniqueIdentifier,
|
|
91
|
+
startedAt,
|
|
92
|
+
endedAt,
|
|
93
|
+
duration: endedAt.getTime() - startedAt.getTime(),
|
|
94
|
+
expiresAt: new Date(Date.now() + saveExecutionsFor),
|
|
95
|
+
status,
|
|
96
|
+
errorMessage,
|
|
97
|
+
params: jobToRun.params,
|
|
98
|
+
result,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async afterExecutionSuccess(job, jobToRun, context) {
|
|
103
|
+
if (job.type === 'recurrent') {
|
|
104
|
+
context.logger.debug(`Scheduling next run for recurrent job "${jobToRun.name}"`);
|
|
105
|
+
await this.jobsRepo.scheduleNextRun({
|
|
106
|
+
jobId: jobToRun.jobId,
|
|
107
|
+
nextRunAt: (0, getNextRunDate_1.getNextRunDate)(job),
|
|
108
|
+
addTries: false,
|
|
109
|
+
priority: job.priority,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (job.type === 'event') {
|
|
113
|
+
context.logger.debug(`Removing event job after success "${jobToRun.name}"`);
|
|
114
|
+
await this.jobsRepo.deleteEventJob(jobToRun.jobId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async executeJob(jobs, jobToRun, respawnWorker) {
|
|
118
|
+
const job = this.getJobDefinition(jobToRun, jobs);
|
|
119
|
+
if (!job)
|
|
120
|
+
return;
|
|
121
|
+
const tracer = api_1.trace.getTracer('orionjs.dogs', '1.0');
|
|
122
|
+
await tracer.startActiveSpan(`job.${jobToRun.name}.${jobToRun.executionId}`, async (span) => {
|
|
123
|
+
try {
|
|
124
|
+
const startedAt = new Date();
|
|
125
|
+
const onStale = async () => {
|
|
126
|
+
if (job.onStale) {
|
|
127
|
+
context.logger.info(`Job "${jobToRun.name}" is stale`);
|
|
128
|
+
job.onStale(jobToRun.params, context);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
context.logger.error(`Job "${jobToRun.name}" is stale`);
|
|
132
|
+
}
|
|
133
|
+
await this.jobsRepo.setJobRecordPriority(jobToRun.jobId, 0);
|
|
134
|
+
respawnWorker();
|
|
135
|
+
this.saveExecution({
|
|
136
|
+
startedAt,
|
|
137
|
+
status: 'stale',
|
|
138
|
+
result: null,
|
|
139
|
+
errorMessage: null,
|
|
140
|
+
job,
|
|
141
|
+
jobToRun,
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
const context = this.getContext(job, jobToRun, onStale);
|
|
145
|
+
try {
|
|
146
|
+
const result = await job.resolve(jobToRun.params, context);
|
|
147
|
+
context.clearStaleTimeout();
|
|
148
|
+
this.saveExecution({
|
|
149
|
+
startedAt,
|
|
150
|
+
status: 'success',
|
|
151
|
+
result: result || null,
|
|
152
|
+
errorMessage: null,
|
|
153
|
+
job,
|
|
154
|
+
jobToRun,
|
|
155
|
+
});
|
|
156
|
+
await this.afterExecutionSuccess(job, jobToRun, context);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
context.clearStaleTimeout();
|
|
160
|
+
this.saveExecution({
|
|
161
|
+
startedAt,
|
|
162
|
+
status: 'error',
|
|
163
|
+
result: null,
|
|
164
|
+
errorMessage: error.message,
|
|
165
|
+
job,
|
|
166
|
+
jobToRun,
|
|
167
|
+
});
|
|
168
|
+
await this.onError(error, job, jobToRun, context);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
span.setStatus({
|
|
173
|
+
code: api_1.SpanStatusCode.ERROR,
|
|
174
|
+
message: error.message,
|
|
175
|
+
});
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
span.end();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
exports.Executor = Executor;
|
|
185
|
+
__decorate([
|
|
186
|
+
(0, services_1.Inject)(),
|
|
187
|
+
__metadata("design:type", JobsRepo_1.JobsRepo)
|
|
188
|
+
], Executor.prototype, "jobsRepo", void 0);
|
|
189
|
+
__decorate([
|
|
190
|
+
(0, services_1.Inject)(),
|
|
191
|
+
__metadata("design:type", JobsHistoryRepo_1.JobsHistoryRepo)
|
|
192
|
+
], Executor.prototype, "jobsHistoryRepo", void 0);
|
|
193
|
+
exports.Executor = Executor = __decorate([
|
|
194
|
+
(0, services_1.Service)()
|
|
195
|
+
], Executor);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { JobDefinitionWithName, JobsDefinition } from '../types/JobsDefinition';
|
|
2
|
+
import { StartWorkersConfig } from '../types/StartConfig';
|
|
3
|
+
import { WorkerInstance, WorkersInstance } from '../types/Worker';
|
|
4
|
+
export declare class WorkerService {
|
|
5
|
+
private jobsRepo;
|
|
6
|
+
private executor;
|
|
7
|
+
getJobNames(jobs: JobsDefinition): string[];
|
|
8
|
+
getJobs(jobs: JobsDefinition): JobDefinitionWithName[];
|
|
9
|
+
runWorkerLoop(config: StartWorkersConfig, workerInstance: WorkerInstance): Promise<boolean>;
|
|
10
|
+
startWorker(config: StartWorkersConfig, workerInstance: WorkerInstance): Promise<void>;
|
|
11
|
+
createWorkersInstanceDefinition(config: StartWorkersConfig): WorkersInstance;
|
|
12
|
+
ensureRecords(config: StartWorkersConfig): Promise<void>;
|
|
13
|
+
startANewWorker(config: StartWorkersConfig, workersInstance: WorkersInstance): Promise<void>;
|
|
14
|
+
runWorkers(config: StartWorkersConfig, workersInstance: WorkersInstance): Promise<void>;
|
|
15
|
+
startWorkers(userConfig: Partial<StartWorkersConfig>): WorkersInstance;
|
|
16
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WorkerService = void 0;
|
|
13
|
+
const services_1 = require("@orion-js/services");
|
|
14
|
+
const lodash_1 = require("lodash");
|
|
15
|
+
const JobsRepo_1 = require("../repos/JobsRepo");
|
|
16
|
+
const helpers_1 = require("@orion-js/helpers");
|
|
17
|
+
const Executor_1 = require("./Executor");
|
|
18
|
+
const logger_1 = require("@orion-js/logger");
|
|
19
|
+
let WorkerService = class WorkerService {
|
|
20
|
+
getJobNames(jobs) {
|
|
21
|
+
return Object.keys(jobs);
|
|
22
|
+
}
|
|
23
|
+
getJobs(jobs) {
|
|
24
|
+
return Object.keys(jobs).map(name => {
|
|
25
|
+
return {
|
|
26
|
+
name,
|
|
27
|
+
...jobs[name]
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async runWorkerLoop(config, workerInstance) {
|
|
32
|
+
const names = this.getJobNames(config.jobs);
|
|
33
|
+
logger_1.logger.debug(`Running worker loop [w${workerInstance.workerIndex}] for jobs "${names.join(', ')}"...`);
|
|
34
|
+
const jobToRun = await this.jobsRepo.getJobAndLock(names, config.lockTime);
|
|
35
|
+
if (!jobToRun) {
|
|
36
|
+
logger_1.logger.debug('No job to run');
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
logger_1.logger.debug(`Got job [w${workerInstance.workerIndex}] to run:`, jobToRun);
|
|
40
|
+
await this.executor.executeJob(config.jobs, jobToRun, workerInstance.respawn);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
async startWorker(config, workerInstance) {
|
|
44
|
+
while (true) {
|
|
45
|
+
if (!workerInstance.running) {
|
|
46
|
+
logger_1.logger.info(`Got signal to stop. Stopping worker [w${workerInstance.workerIndex}]...`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const didRun = await this.runWorkerLoop(config, workerInstance);
|
|
51
|
+
if (!didRun)
|
|
52
|
+
await (0, helpers_1.sleep)(config.pollInterval);
|
|
53
|
+
if (didRun)
|
|
54
|
+
await (0, helpers_1.sleep)(config.cooldownPeriod);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logger_1.logger.error(`Error in job runner.`, error);
|
|
58
|
+
await (0, helpers_1.sleep)(config.pollInterval);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
createWorkersInstanceDefinition(config) {
|
|
63
|
+
const workersInstance = {
|
|
64
|
+
running: true,
|
|
65
|
+
workersCount: config.workersCount,
|
|
66
|
+
workers: [],
|
|
67
|
+
stop: async () => {
|
|
68
|
+
logger_1.logger.debug('Stopping workers...', workersInstance.workers);
|
|
69
|
+
workersInstance.running = false;
|
|
70
|
+
const stopingPromises = workersInstance.workers.map(worker => worker.stop());
|
|
71
|
+
await Promise.all(stopingPromises);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return workersInstance;
|
|
75
|
+
}
|
|
76
|
+
async ensureRecords(config) {
|
|
77
|
+
const jobs = this.getJobs(config.jobs);
|
|
78
|
+
await Promise.all(jobs
|
|
79
|
+
.filter(job => job.type === 'recurrent')
|
|
80
|
+
.map(async (job) => {
|
|
81
|
+
logger_1.logger.debug(`Ensuring records for job "${job.name}"...`);
|
|
82
|
+
await this.jobsRepo.ensureJobRecord(job);
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
async startANewWorker(config, workersInstance) {
|
|
86
|
+
const workerIndex = workersInstance.workers.length;
|
|
87
|
+
logger_1.logger.info(`Starting worker [w${workerIndex}]`);
|
|
88
|
+
const workerInstance = {
|
|
89
|
+
running: true,
|
|
90
|
+
workerIndex,
|
|
91
|
+
stop: async () => {
|
|
92
|
+
logger_1.logger.info(`Stopping worker [w${workerIndex}]...`);
|
|
93
|
+
workerInstance.running = false;
|
|
94
|
+
await workerInstance.promise;
|
|
95
|
+
},
|
|
96
|
+
respawn: async () => {
|
|
97
|
+
logger_1.logger.info(`Respawning worker [w${workerIndex}]...`);
|
|
98
|
+
workerInstance.stop();
|
|
99
|
+
await this.startANewWorker(config, workersInstance);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const workerPromise = this.startWorker(config, workerInstance);
|
|
103
|
+
workerInstance.promise = workerPromise;
|
|
104
|
+
workersInstance.workers.push(workerInstance);
|
|
105
|
+
}
|
|
106
|
+
async runWorkers(config, workersInstance) {
|
|
107
|
+
logger_1.logger.debug('Will ensure records for recurrent jobs');
|
|
108
|
+
await this.ensureRecords(config);
|
|
109
|
+
for (const workerIndex of (0, lodash_1.range)(config.workersCount)) {
|
|
110
|
+
this.startANewWorker(config, workersInstance);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
startWorkers(userConfig) {
|
|
114
|
+
const defaultConfig = {
|
|
115
|
+
jobs: {},
|
|
116
|
+
cooldownPeriod: 100,
|
|
117
|
+
pollInterval: 3000,
|
|
118
|
+
workersCount: 4,
|
|
119
|
+
lockTime: 30 * 1000
|
|
120
|
+
};
|
|
121
|
+
const config = {
|
|
122
|
+
...defaultConfig,
|
|
123
|
+
...userConfig
|
|
124
|
+
};
|
|
125
|
+
const workersInstance = this.createWorkersInstanceDefinition(config);
|
|
126
|
+
logger_1.logger.debug('Starting workers', config);
|
|
127
|
+
this.runWorkers(config, workersInstance);
|
|
128
|
+
return workersInstance;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
exports.WorkerService = WorkerService;
|
|
132
|
+
__decorate([
|
|
133
|
+
(0, services_1.Inject)(),
|
|
134
|
+
__metadata("design:type", JobsRepo_1.JobsRepo)
|
|
135
|
+
], WorkerService.prototype, "jobsRepo", void 0);
|
|
136
|
+
__decorate([
|
|
137
|
+
(0, services_1.Inject)(),
|
|
138
|
+
__metadata("design:type", Executor_1.Executor)
|
|
139
|
+
], WorkerService.prototype, "executor", void 0);
|
|
140
|
+
exports.WorkerService = WorkerService = __decorate([
|
|
141
|
+
(0, services_1.Service)()
|
|
142
|
+
], WorkerService);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const services_1 = require("@orion-js/services");
|
|
4
|
+
const WorkerService_1 = require("./WorkerService");
|
|
5
|
+
describe('WorkerService', () => {
|
|
6
|
+
it('should have a startWorker method', () => {
|
|
7
|
+
const workerService = (0, services_1.getInstance)(WorkerService_1.WorkerService);
|
|
8
|
+
expect(workerService.startWorkers).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNextRunDate = void 0;
|
|
4
|
+
const getNextRunDate = (options) => {
|
|
5
|
+
if (options.runIn) {
|
|
6
|
+
return new Date(Date.now() + options.runIn);
|
|
7
|
+
}
|
|
8
|
+
if (options.runEvery) {
|
|
9
|
+
return new Date(Date.now() + options.runEvery);
|
|
10
|
+
}
|
|
11
|
+
if (options.runAt) {
|
|
12
|
+
return options.runAt;
|
|
13
|
+
}
|
|
14
|
+
if (options.getNextRun) {
|
|
15
|
+
return options.getNextRun();
|
|
16
|
+
}
|
|
17
|
+
return new Date();
|
|
18
|
+
};
|
|
19
|
+
exports.getNextRunDate = getNextRunDate;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|