@unchainedshop/core-worker 4.0.0-rc.9 → 4.0.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/lib/db/WorkQueueCollection.d.ts +2 -2
- package/lib/db/WorkQueueCollection.d.ts.map +1 -1
- package/lib/db/WorkQueueCollection.js +17 -13
- package/lib/db/WorkQueueCollection.js.map +1 -1
- package/lib/db/WorkQueueSchema.d.ts +3 -0
- package/lib/db/WorkQueueSchema.d.ts.map +1 -0
- package/lib/db/WorkQueueSchema.js +20 -0
- package/lib/db/WorkQueueSchema.js.map +1 -0
- package/lib/director/WorkStatus.d.ts +8 -0
- package/lib/director/WorkStatus.d.ts.map +1 -0
- package/lib/director/WorkStatus.js +9 -0
- package/lib/director/WorkStatus.js.map +1 -0
- package/lib/director/WorkerAdapter.d.ts +3 -0
- package/lib/director/WorkerAdapter.d.ts.map +1 -0
- package/lib/director/WorkerAdapter.js +11 -0
- package/lib/director/WorkerAdapter.js.map +1 -0
- package/lib/director/WorkerDirector.d.ts +4 -0
- package/lib/director/WorkerDirector.d.ts.map +1 -0
- package/lib/director/WorkerDirector.js +72 -0
- package/lib/director/WorkerDirector.js.map +1 -0
- package/lib/director/WorkerEventTypes.d.ts +8 -0
- package/lib/director/WorkerEventTypes.d.ts.map +1 -0
- package/lib/director/WorkerEventTypes.js +12 -0
- package/lib/director/WorkerEventTypes.js.map +1 -0
- package/lib/module/configureWorkerModule.d.ts +34 -35
- package/lib/module/configureWorkerModule.d.ts.map +1 -1
- package/lib/module/configureWorkerModule.js +21 -10
- package/lib/module/configureWorkerModule.js.map +1 -1
- package/lib/module/migrations/addMigrations.d.ts +3 -0
- package/lib/module/migrations/addMigrations.d.ts.map +1 -0
- package/lib/module/migrations/addMigrations.js +16 -0
- package/lib/module/migrations/addMigrations.js.map +1 -0
- package/lib/schedulers/FailedRescheduler.d.ts +6 -0
- package/lib/schedulers/FailedRescheduler.d.ts.map +1 -0
- package/lib/schedulers/FailedRescheduler.js +45 -0
- package/lib/schedulers/FailedRescheduler.js.map +1 -0
- package/lib/workers/BaseWorker.d.ts +8 -0
- package/lib/workers/BaseWorker.d.ts.map +1 -0
- package/lib/workers/BaseWorker.js +81 -0
- package/lib/workers/BaseWorker.js.map +1 -0
- package/lib/workers/EventListenerWorker.d.ts +6 -0
- package/lib/workers/EventListenerWorker.d.ts.map +1 -0
- package/lib/workers/EventListenerWorker.js +46 -0
- package/lib/workers/EventListenerWorker.js.map +1 -0
- package/lib/workers/IntervalWorker.d.ts +9 -0
- package/lib/workers/IntervalWorker.d.ts.map +1 -0
- package/lib/workers/IntervalWorker.js +38 -0
- package/lib/workers/IntervalWorker.js.map +1 -0
- package/package.json +3 -3
- package/src/db/WorkQueueCollection.ts +20 -15
- package/src/module/configureWorkerModule.ts +57 -46
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { WorkQueueCollection } from '../../db/WorkQueueCollection.js';
|
|
2
|
+
export default function addMigrations(repository) {
|
|
3
|
+
repository?.register({
|
|
4
|
+
id: 20240905161400,
|
|
5
|
+
name: 'Remove all autoscheduled work items so they get scheduled freshly',
|
|
6
|
+
up: async () => {
|
|
7
|
+
const WorkQueue = await WorkQueueCollection(repository.db);
|
|
8
|
+
await WorkQueue.deleteMany({
|
|
9
|
+
autoscheduled: true,
|
|
10
|
+
started: null,
|
|
11
|
+
deleted: null,
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=addMigrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addMigrations.js","sourceRoot":"","sources":["../../../src/module/migrations/addMigrations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,UAA0C;IAC9E,UAAU,EAAE,QAAQ,CAAC;QACnB,EAAE,EAAE,cAAc;QAClB,IAAI,EAAE,mEAAmE;QACzE,EAAE,EAAE,KAAK,IAAI,EAAE;YACb,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAE3D,MAAM,SAAS,CAAC,UAAU,CAAC;gBACzB,aAAa,EAAE,IAAI;gBACnB,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { IScheduler, WorkData } from '@unchainedshop/types/worker.js';
|
|
2
|
+
export interface FailedReschedulerParams {
|
|
3
|
+
retryInput?: (workData: WorkData, priorInput: Record<string, any>) => Promise<Record<string, any> | null>;
|
|
4
|
+
}
|
|
5
|
+
export declare const FailedRescheduler: IScheduler<FailedReschedulerParams>;
|
|
6
|
+
//# sourceMappingURL=FailedRescheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FailedRescheduler.d.ts","sourceRoot":"","sources":["../../src/schedulers/FailedRescheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAK5E,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,CACX,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,iBAAiB,EAAE,UAAU,CAAC,uBAAuB,CAsDjE,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { log } from '@unchainedshop/logger';
|
|
2
|
+
import { subscribe } from '@unchainedshop/events';
|
|
3
|
+
import { WorkerEventTypes } from '../director/WorkerEventTypes.js';
|
|
4
|
+
export const FailedRescheduler = {
|
|
5
|
+
key: 'shop.unchained.scheduler.failed',
|
|
6
|
+
label: 'Reschedule failed works',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
actions: ({ retryInput }, unchainedAPI) => {
|
|
9
|
+
const handleFinishedWork = async ({ payload: work }) => {
|
|
10
|
+
if (!work.success && work.retries > 0) {
|
|
11
|
+
const now = new Date();
|
|
12
|
+
const workDelayMs = work.scheduled.getTime() - work.created.getTime();
|
|
13
|
+
// In short: Double the delay of the old work or delay for 5 seconds
|
|
14
|
+
const scheduled = workDelayMs > 1000
|
|
15
|
+
? new Date(now.getTime() + workDelayMs * 2)
|
|
16
|
+
: new Date(now.setSeconds(now.getSeconds() + 5));
|
|
17
|
+
log(`${FailedRescheduler.key} -> Reschedule failed work ${work._id} ${work.type} for ${scheduled.toISOString()} (in ${Math.round(workDelayMs / 1000)}). Remaining retries: ${work.retries}`);
|
|
18
|
+
const workData = {
|
|
19
|
+
type: work.type,
|
|
20
|
+
priority: work.priority,
|
|
21
|
+
originalWorkId: work.originalWorkId || work._id,
|
|
22
|
+
retries: work.retries - 1,
|
|
23
|
+
timeout: work.timeout,
|
|
24
|
+
scheduled,
|
|
25
|
+
};
|
|
26
|
+
if (retryInput) {
|
|
27
|
+
workData.input = await retryInput(workData, work.input);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
workData.input = work.input;
|
|
31
|
+
}
|
|
32
|
+
unchainedAPI.modules.worker.addWork(workData);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
start() {
|
|
37
|
+
subscribe(WorkerEventTypes.FINISHED, handleFinishedWork);
|
|
38
|
+
},
|
|
39
|
+
stop() {
|
|
40
|
+
/* */
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=FailedRescheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FailedRescheduler.js","sourceRoot":"","sources":["../../src/schedulers/FailedRescheduler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AASnE,MAAM,CAAC,MAAM,iBAAiB,GAAwC;IACpE,GAAG,EAAE,iCAAiC;IACtC,KAAK,EAAE,yBAAyB;IAChC,OAAO,EAAE,OAAO;IAEhB,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,YAAY,EAAE,EAAE;QACxC,MAAM,kBAAkB,GAAG,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAqB,EAAE,EAAE;YACxE,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;gBACtC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;gBAEtE,oEAAoE;gBACpE,MAAM,SAAS,GACb,WAAW,GAAG,IAAI;oBAChB,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,WAAW,GAAG,CAAC,CAAC;oBAC3C,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBAErD,GAAG,CACD,GAAG,iBAAiB,CAAC,GAAG,8BAA8B,IAAI,CAAC,GAAG,IAC5D,IAAI,CAAC,IACP,QAAQ,SAAS,CAAC,WAAW,EAAE,QAAQ,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,yBACnE,IAAI,CAAC,OACP,EAAE,CACH,CAAC;gBAEF,MAAM,QAAQ,GAAa;oBACzB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,GAAG;oBAC/C,OAAO,EAAE,IAAI,CAAC,OAAO,GAAG,CAAC;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,SAAS;iBACV,CAAC;gBAEF,IAAI,UAAU,EAAE,CAAC;oBACf,QAAQ,CAAC,KAAK,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1D,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;gBAC9B,CAAC;gBAED,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAChD,CAAC;QACH,CAAC,CAAC;QAEF,OAAO;YACL,KAAK;gBACH,SAAS,CAAO,gBAAgB,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;YACjE,CAAC;YAED,IAAI;gBACF,KAAK;YACP,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseWorker.d.ts","sourceRoot":"","sources":["../../src/workers/BaseWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAY,MAAM,gCAAgC,CAAC;AAKnE,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;CACtB;AAED,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,YAAY,CAuF5C,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import later from '@breejs/later';
|
|
2
|
+
import { log } from '@unchainedshop/logger';
|
|
3
|
+
import { WorkerDirector } from '../director/WorkerDirector.js';
|
|
4
|
+
export const BaseWorker = {
|
|
5
|
+
key: 'shop.unchained.worker.base',
|
|
6
|
+
label: 'Base worker. Do not use this directly.',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
type: 'BASE',
|
|
9
|
+
external: false,
|
|
10
|
+
getFloorDate: (date = new Date()) => {
|
|
11
|
+
const floored = new Date(Math.floor(date.getTime() / 1000) * 1000);
|
|
12
|
+
return floored;
|
|
13
|
+
},
|
|
14
|
+
actions: ({ workerId, worker }, unchainedAPI) => {
|
|
15
|
+
log(`${worker.key} -> Initialized ${worker.type}`);
|
|
16
|
+
const workerActions = {
|
|
17
|
+
start() {
|
|
18
|
+
throw new Error(`Not implemented on ${this.constructor.key}`);
|
|
19
|
+
},
|
|
20
|
+
stop() {
|
|
21
|
+
throw new Error(`Not implemented on ${this.constructor.key}`);
|
|
22
|
+
},
|
|
23
|
+
reset: async (referenceDate = new Date()) => {
|
|
24
|
+
await unchainedAPI.modules.worker.markOldWorkAsFailed({
|
|
25
|
+
types: WorkerDirector.getActivePluginTypes({ external: false }),
|
|
26
|
+
worker: workerId,
|
|
27
|
+
referenceDate,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
autorescheduleTypes: async ({ referenceDate }) => {
|
|
31
|
+
return Promise.all(WorkerDirector.getAutoSchedules().map(async ([scheduleId, workConfig]) => {
|
|
32
|
+
if (!workConfig.schedule) {
|
|
33
|
+
await unchainedAPI.modules.worker.ensureNoWork({
|
|
34
|
+
type: workConfig.type,
|
|
35
|
+
priority: workConfig.priority || 0,
|
|
36
|
+
scheduleId,
|
|
37
|
+
});
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const fixedSchedule = { ...workConfig.schedule };
|
|
41
|
+
fixedSchedule.schedules[0].s = [0]; // ignore seconds, always run on second 0
|
|
42
|
+
const nextDate = later.schedule(fixedSchedule).next(1, referenceDate);
|
|
43
|
+
nextDate.setMilliseconds(0);
|
|
44
|
+
const workData = {
|
|
45
|
+
type: workConfig.type,
|
|
46
|
+
scheduleId,
|
|
47
|
+
scheduled: nextDate,
|
|
48
|
+
timeout: workConfig.timeout,
|
|
49
|
+
priority: workConfig.priority || 0,
|
|
50
|
+
retries: workConfig.retries || 0,
|
|
51
|
+
};
|
|
52
|
+
if (workConfig.input) {
|
|
53
|
+
workData.input = await workConfig.input(workData);
|
|
54
|
+
// A work input fn can skip auto scheduling a new record
|
|
55
|
+
// when it explicitly returns a falsish input instead of a dictionary
|
|
56
|
+
if (!workData.input)
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return unchainedAPI.modules.worker.ensureOneWork(workData);
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
process: async ({ maxWorkItemCount, referenceDate }) => {
|
|
63
|
+
await workerActions.autorescheduleTypes({
|
|
64
|
+
referenceDate,
|
|
65
|
+
});
|
|
66
|
+
const processRecursively = async (recursionCounter = 0) => {
|
|
67
|
+
if (maxWorkItemCount && maxWorkItemCount < recursionCounter)
|
|
68
|
+
return null;
|
|
69
|
+
const doneWork = await unchainedAPI.modules.worker.processNextWork(unchainedAPI, workerId);
|
|
70
|
+
if (doneWork)
|
|
71
|
+
return processRecursively(recursionCounter + 1);
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
await processRecursively();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
workerActions.reset();
|
|
78
|
+
return workerActions;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=BaseWorker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseWorker.js","sourceRoot":"","sources":["../../src/workers/BaseWorker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventListenerWorker.d.ts","sourceRoot":"","sources":["../../src/workers/EventListenerWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAgBzD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,yBAAyB,CAyClE,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { subscribe } from '@unchainedshop/events';
|
|
2
|
+
import { WorkerEventTypes } from '../director/WorkerEventTypes.js';
|
|
3
|
+
import { BaseWorker } from './BaseWorker.js';
|
|
4
|
+
function debounce(func, wait) {
|
|
5
|
+
let timeout;
|
|
6
|
+
return (...args) => {
|
|
7
|
+
clearTimeout(timeout);
|
|
8
|
+
timeout = setTimeout(() => {
|
|
9
|
+
timeout = null;
|
|
10
|
+
func(...args);
|
|
11
|
+
}, wait);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export const EventListenerWorker = {
|
|
15
|
+
...BaseWorker,
|
|
16
|
+
key: 'shop.unchained.worker.event-listener',
|
|
17
|
+
label: 'Allocates work on events. This worker does not make sense on multiple containers.',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
type: 'EVENT_LISTENER',
|
|
20
|
+
actions: ({ workerId }, unchainedAPI) => {
|
|
21
|
+
const baseWorkerActions = BaseWorker.actions({ workerId, worker: EventListenerWorker }, unchainedAPI);
|
|
22
|
+
// Debounce in the event of many work queue events conflicting with each other
|
|
23
|
+
const processWorkQueue = debounce(async () => {
|
|
24
|
+
await baseWorkerActions.process({
|
|
25
|
+
maxWorkItemCount: 1, // only one work item at a time, else we could end up in a loop
|
|
26
|
+
referenceDate: EventListenerWorker.getFloorDate(),
|
|
27
|
+
});
|
|
28
|
+
}, 300);
|
|
29
|
+
return {
|
|
30
|
+
...baseWorkerActions,
|
|
31
|
+
start() {
|
|
32
|
+
subscribe(WorkerEventTypes.ADDED, processWorkQueue);
|
|
33
|
+
subscribe(WorkerEventTypes.FINISHED, processWorkQueue);
|
|
34
|
+
setTimeout(async () => {
|
|
35
|
+
await baseWorkerActions.autorescheduleTypes({
|
|
36
|
+
referenceDate: EventListenerWorker.getFloorDate(),
|
|
37
|
+
});
|
|
38
|
+
}, 300);
|
|
39
|
+
},
|
|
40
|
+
stop() {
|
|
41
|
+
/* */
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=EventListenerWorker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventListenerWorker.js","sourceRoot":"","sources":["../../src/workers/EventListenerWorker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,SAAS,QAAQ,CAAkC,IAAO,EAAE,IAAI;IAC9D,IAAI,OAAO,CAAC;IACZ,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE;QACjB,YAAY,CAAC,OAAO,CAAC,CAAC;QACtB,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACxB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAChB,CAAC,EAAE,IAAI,CAAC,CAAC;IACX,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,CAAC,MAAM,mBAAmB,GAAuC;IACrE,GAAG,UAAU;IAEb,GAAG,EAAE,sCAAsC;IAC3C,KAAK,EAAE,mFAAmF;IAC1F,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,gBAAgB;IAEtB,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,EAAE;QACtC,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,CAC1C,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,EACzC,YAAY,CACb,CAAC;QAEF,8EAA8E;QAC9E,MAAM,gBAAgB,GAAG,QAAQ,CAAsB,KAAK,IAAI,EAAE;YAChE,MAAM,iBAAiB,CAAC,OAAO,CAAC;gBAC9B,gBAAgB,EAAE,CAAC,EAAE,+DAA+D;gBACpF,aAAa,EAAE,mBAAmB,CAAC,YAAY,EAAE;aAClD,CAAC,CAAC;QACL,CAAC,EAAE,GAAG,CAAC,CAAC;QAER,OAAO;YACL,GAAG,iBAAiB;YAEpB,KAAK;gBACH,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;gBACpD,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;gBAEvD,UAAU,CAAC,KAAK,IAAI,EAAE;oBACpB,MAAM,iBAAiB,CAAC,mBAAmB,CAAC;wBAC1C,aAAa,EAAE,mBAAmB,CAAC,YAAY,EAAE;qBAClD,CAAC,CAAC;gBACL,CAAC,EAAE,GAAG,CAAC,CAAC;YACV,CAAC;YAED,IAAI;gBACF,KAAK;YACP,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IWorker, WorkerSchedule } from '@unchainedshop/types/worker.js';
|
|
2
|
+
export interface IntervalWorkerParams {
|
|
3
|
+
workerId?: string;
|
|
4
|
+
batchCount?: number;
|
|
5
|
+
schedule?: WorkerSchedule | string;
|
|
6
|
+
}
|
|
7
|
+
export declare const scheduleToInterval: (scheduleRaw: WorkerSchedule | string) => number;
|
|
8
|
+
export declare const IntervalWorker: IWorker<IntervalWorkerParams>;
|
|
9
|
+
//# sourceMappingURL=IntervalWorker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntervalWorker.d.ts","sourceRoot":"","sources":["../../src/workers/IntervalWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAMzE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC;CACpC;AAMD,eAAO,MAAM,kBAAkB,gBAAiB,cAAc,GAAG,MAAM,WAMtE,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,oBAAoB,CA+BxD,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import later from '@breejs/later';
|
|
2
|
+
import { BaseWorker } from './BaseWorker.js';
|
|
3
|
+
const { NODE_ENV } = process.env;
|
|
4
|
+
const defaultSchedule = later.parse.text(NODE_ENV !== 'production' ? 'every 2 seconds' : 'every 30 seconds');
|
|
5
|
+
export const scheduleToInterval = (scheduleRaw) => {
|
|
6
|
+
const referenceDate = new Date(1000);
|
|
7
|
+
const schedule = typeof scheduleRaw === 'string' ? later.parse.text(scheduleRaw) : scheduleRaw;
|
|
8
|
+
const [one, two] = later.schedule(schedule).next(2, referenceDate);
|
|
9
|
+
const diff = new Date(two).getTime() - new Date(one).getTime();
|
|
10
|
+
return Math.min(1000 * 60 * 60, diff); // at least once every hour!
|
|
11
|
+
};
|
|
12
|
+
export const IntervalWorker = {
|
|
13
|
+
...BaseWorker,
|
|
14
|
+
key: 'shop.unchained.worker.cron',
|
|
15
|
+
label: 'Allocates work on fixed intervals with native node setInterval',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
type: 'CRON',
|
|
18
|
+
actions: ({ workerId, batchCount = 0, schedule = defaultSchedule }, unchainedAPI) => {
|
|
19
|
+
const baseWorkerActions = BaseWorker.actions({ workerId, worker: IntervalWorker }, unchainedAPI);
|
|
20
|
+
const intervalDelay = scheduleToInterval(schedule);
|
|
21
|
+
let intervalHandle;
|
|
22
|
+
return {
|
|
23
|
+
...baseWorkerActions,
|
|
24
|
+
start() {
|
|
25
|
+
intervalHandle = setInterval(() => {
|
|
26
|
+
baseWorkerActions.process({
|
|
27
|
+
maxWorkItemCount: batchCount,
|
|
28
|
+
referenceDate: IntervalWorker.getFloorDate(),
|
|
29
|
+
});
|
|
30
|
+
}, intervalDelay);
|
|
31
|
+
},
|
|
32
|
+
stop() {
|
|
33
|
+
clearInterval(intervalHandle);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=IntervalWorker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntervalWorker.js","sourceRoot":"","sources":["../../src/workers/IntervalWorker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;AAQjC,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CACtC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,kBAAkB,CACjD,CAAC;AAEpB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,WAAoC,EAAE,EAAE;IACzE,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IAC/F,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/D,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,4BAA4B;AACrE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAkC;IAC3D,GAAG,UAAU;IAEb,GAAG,EAAE,4BAA4B;IACjC,KAAK,EAAE,gEAAgE;IACvE,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,MAAM;IAEZ,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,GAAG,CAAC,EAAE,QAAQ,GAAG,eAAe,EAAE,EAAE,YAAY,EAAE,EAAE;QAClF,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,YAAY,CAAC,CAAC;QAEjG,MAAM,aAAa,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,cAA8B,CAAC;QAEnC,OAAO;YACL,GAAG,iBAAiB;YAEpB,KAAK;gBACH,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;oBAChC,iBAAiB,CAAC,OAAO,CAAC;wBACxB,gBAAgB,EAAE,UAAU;wBAC5B,aAAa,EAAE,cAAc,CAAC,YAAY,EAAE;qBAC7C,CAAC,CAAC;gBACL,CAAC,EAAE,aAAa,CAAC,CAAC;YACpB,CAAC;YAED,IAAI;gBACF,aAAa,CAAC,cAAc,CAAC,CAAC;YAChC,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/core-worker",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"main": "lib/worker-index.js",
|
|
5
5
|
"types": "lib/worker-index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@unchainedshop/logger": "^4.0.0
|
|
35
|
-
"@unchainedshop/utils": "^4.0.0
|
|
34
|
+
"@unchainedshop/logger": "^4.0.0",
|
|
35
|
+
"@unchainedshop/utils": "^4.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^24.0.13",
|
|
@@ -12,7 +12,7 @@ export enum WorkStatus {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type Work = {
|
|
15
|
-
_id
|
|
15
|
+
_id: string;
|
|
16
16
|
priority: number;
|
|
17
17
|
retries: number;
|
|
18
18
|
scheduled: Date;
|
|
@@ -25,7 +25,7 @@ export type Work = {
|
|
|
25
25
|
started?: Date;
|
|
26
26
|
success?: boolean;
|
|
27
27
|
timeout?: number;
|
|
28
|
-
worker?: string;
|
|
28
|
+
worker?: string | null;
|
|
29
29
|
autoscheduled?: boolean;
|
|
30
30
|
scheduleId?: string;
|
|
31
31
|
} & TimestampFields;
|
|
@@ -33,6 +33,24 @@ export type Work = {
|
|
|
33
33
|
export const WorkQueueCollection = async (db: mongodb.Db) => {
|
|
34
34
|
const WorkQueue = db.collection<Work>('work_queue');
|
|
35
35
|
|
|
36
|
+
if (!isDocumentDBCompatModeEnabled()) {
|
|
37
|
+
await buildDbIndexes<Work>(WorkQueue, [
|
|
38
|
+
{
|
|
39
|
+
index: { originalWorkId: 'text', _id: 'text', worker: 'text', input: 'text', type: 'text' },
|
|
40
|
+
options: {
|
|
41
|
+
weights: {
|
|
42
|
+
_id: 8,
|
|
43
|
+
originalWorkId: 6,
|
|
44
|
+
type: 5,
|
|
45
|
+
worker: 4,
|
|
46
|
+
input: 2,
|
|
47
|
+
},
|
|
48
|
+
name: 'workqueue_fulltext_search',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
await buildDbIndexes<Work>(WorkQueue, [
|
|
37
55
|
{
|
|
38
56
|
index: { created: -1 },
|
|
@@ -44,19 +62,6 @@ export const WorkQueueCollection = async (db: mongodb.Db) => {
|
|
|
44
62
|
{ index: { priority: -1 } },
|
|
45
63
|
{ index: { type: 1 } },
|
|
46
64
|
{ index: { originalWorkId: 1 } },
|
|
47
|
-
!isDocumentDBCompatModeEnabled() && {
|
|
48
|
-
index: { originalWorkId: 'text', _id: 'text', worker: 'text', input: 'text', type: 'text' },
|
|
49
|
-
options: {
|
|
50
|
-
weights: {
|
|
51
|
-
_id: 8,
|
|
52
|
-
originalWorkId: 6,
|
|
53
|
-
type: 5,
|
|
54
|
-
worker: 4,
|
|
55
|
-
input: 2,
|
|
56
|
-
},
|
|
57
|
-
name: 'workqueue_fulltext_search',
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
65
|
]);
|
|
61
66
|
|
|
62
67
|
return WorkQueue;
|
|
@@ -25,8 +25,9 @@ const logger = createLogger('unchained:core-worker');
|
|
|
25
25
|
|
|
26
26
|
export type WorkData = Pick<
|
|
27
27
|
Partial<Work>,
|
|
28
|
-
'input' | 'originalWorkId' | 'priority' | 'retries' | 'timeout' | '
|
|
29
|
-
> &
|
|
28
|
+
'input' | 'originalWorkId' | 'priority' | 'retries' | 'timeout' | 'worker' | 'scheduleId'
|
|
29
|
+
> &
|
|
30
|
+
Pick<Work, 'type' | 'scheduled'>;
|
|
30
31
|
|
|
31
32
|
export interface WorkResult<Result = unknown> {
|
|
32
33
|
success: boolean;
|
|
@@ -58,7 +59,7 @@ export interface WorkerReport {
|
|
|
58
59
|
export interface WorkQueueQuery {
|
|
59
60
|
created?: { end?: Date; start?: Date };
|
|
60
61
|
types?: string[];
|
|
61
|
-
status
|
|
62
|
+
status?: WorkStatus[];
|
|
62
63
|
queryString?: string;
|
|
63
64
|
scheduled?: { end?: Date; start?: Date };
|
|
64
65
|
}
|
|
@@ -162,7 +163,7 @@ const defaultSort: { key: string; value: SortDirection }[] = [
|
|
|
162
163
|
{ key: 'created', value: SortDirection.ASC },
|
|
163
164
|
];
|
|
164
165
|
|
|
165
|
-
const normalizeWorkQueueAggregateResult = (data = []): WorkerReport[] => {
|
|
166
|
+
const normalizeWorkQueueAggregateResult = (data: mongodb.Document[] = []): WorkerReport[] => {
|
|
166
167
|
const statusToFieldMap = {
|
|
167
168
|
NEW: 'newCount',
|
|
168
169
|
ALLOCATED: 'startCount',
|
|
@@ -204,7 +205,7 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
204
205
|
}: {
|
|
205
206
|
types: string[];
|
|
206
207
|
worker: string;
|
|
207
|
-
})
|
|
208
|
+
}) => {
|
|
208
209
|
// Find a work item that is scheduled for now and is not started.
|
|
209
210
|
// Also:
|
|
210
211
|
// - Restrict by types and worker if provided
|
|
@@ -223,10 +224,8 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
223
224
|
{ sort: buildSortOptions(defaultSort), returnDocument: 'after' },
|
|
224
225
|
);
|
|
225
226
|
|
|
226
|
-
if (result)
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
227
|
+
if (!result) return null;
|
|
228
|
+
emit(WorkerEventTypes.ALLOCATED, removePrivateFields(result));
|
|
230
229
|
return result;
|
|
231
230
|
};
|
|
232
231
|
|
|
@@ -244,7 +243,7 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
244
243
|
started?: Date;
|
|
245
244
|
worker?: string;
|
|
246
245
|
},
|
|
247
|
-
)
|
|
246
|
+
) => {
|
|
248
247
|
const workBeforeUpdate = await WorkQueue.findOne(
|
|
249
248
|
buildQuerySelector({ workId, status: [WorkStatus.ALLOCATED] }),
|
|
250
249
|
);
|
|
@@ -267,7 +266,9 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
267
266
|
{ returnDocument: 'after' },
|
|
268
267
|
);
|
|
269
268
|
|
|
270
|
-
|
|
269
|
+
if (!work) return null;
|
|
270
|
+
|
|
271
|
+
const duration = new Date(work.finished!).getTime() - new Date(work.started!).getTime();
|
|
271
272
|
if (work.success) {
|
|
272
273
|
logger.debug(`${work.type} finished with success (${duration}ms)`, {
|
|
273
274
|
workId,
|
|
@@ -295,14 +296,20 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
295
296
|
return typeList.map((t) => t._id as string);
|
|
296
297
|
},
|
|
297
298
|
|
|
298
|
-
findWork: async (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
299
|
+
findWork: async (
|
|
300
|
+
params:
|
|
301
|
+
| {
|
|
302
|
+
workId: string;
|
|
303
|
+
}
|
|
304
|
+
| {
|
|
305
|
+
originalWorkId: string;
|
|
306
|
+
},
|
|
307
|
+
) => {
|
|
308
|
+
if ('workId' in params) {
|
|
309
|
+
return WorkQueue.findOne(generateDbFilterById(params.workId), {});
|
|
310
|
+
}
|
|
311
|
+
return WorkQueue.findOne({ originalWorkId: params.originalWorkId }, {});
|
|
312
|
+
},
|
|
306
313
|
|
|
307
314
|
findWorkQueue: async ({
|
|
308
315
|
limit,
|
|
@@ -397,7 +404,7 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
397
404
|
},
|
|
398
405
|
|
|
399
406
|
// Mutations
|
|
400
|
-
|
|
407
|
+
async addWork({
|
|
401
408
|
type,
|
|
402
409
|
input,
|
|
403
410
|
priority = 0,
|
|
@@ -405,13 +412,14 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
405
412
|
originalWorkId,
|
|
406
413
|
worker = null,
|
|
407
414
|
retries = 20,
|
|
408
|
-
}:
|
|
415
|
+
}: Pick<Work, 'type' | 'originalWorkId' | 'worker'> &
|
|
416
|
+
Pick<Partial<Work>, 'scheduled' | 'priority' | 'input' | 'retries'>): Promise<Work> {
|
|
409
417
|
const created = new Date();
|
|
410
418
|
const { insertedId: workId } = await WorkQueue.insertOne({
|
|
411
419
|
_id: generateDbObjectId(),
|
|
412
420
|
created,
|
|
413
421
|
type,
|
|
414
|
-
input,
|
|
422
|
+
input: input || {},
|
|
415
423
|
priority,
|
|
416
424
|
scheduled: scheduled || created,
|
|
417
425
|
originalWorkId,
|
|
@@ -423,13 +431,13 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
423
431
|
workId,
|
|
424
432
|
});
|
|
425
433
|
|
|
426
|
-
const work = await WorkQueue.findOne(generateDbFilterById(workId), {});
|
|
434
|
+
const work = (await WorkQueue.findOne(generateDbFilterById(workId), {})) as Work;
|
|
427
435
|
emit(WorkerEventTypes.ADDED, removePrivateFields(work));
|
|
428
436
|
|
|
429
437
|
return work;
|
|
430
438
|
},
|
|
431
439
|
|
|
432
|
-
rescheduleWork: async (currentWork: Work, scheduled: Date)
|
|
440
|
+
rescheduleWork: async (currentWork: Work, scheduled: Date) => {
|
|
433
441
|
const work = await WorkQueue.findOneAndUpdate(
|
|
434
442
|
generateDbFilterById(currentWork._id),
|
|
435
443
|
{
|
|
@@ -441,6 +449,7 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
441
449
|
{ returnDocument: 'after' },
|
|
442
450
|
);
|
|
443
451
|
|
|
452
|
+
if (!work) return null;
|
|
444
453
|
emit(WorkerEventTypes.RESCHEDULED, {
|
|
445
454
|
work: removePrivateFields(work),
|
|
446
455
|
oldScheduled: currentWork.scheduled,
|
|
@@ -484,7 +493,7 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
484
493
|
originalWorkId,
|
|
485
494
|
retries = 20,
|
|
486
495
|
scheduleId,
|
|
487
|
-
}: WorkData)
|
|
496
|
+
}: WorkData) => {
|
|
488
497
|
const workId = `${scheduleId}:${scheduled.getTime()}`;
|
|
489
498
|
|
|
490
499
|
const created = new Date();
|
|
@@ -528,7 +537,7 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
528
537
|
},
|
|
529
538
|
);
|
|
530
539
|
|
|
531
|
-
if (!result.lastErrorObject
|
|
540
|
+
if (!result.lastErrorObject) {
|
|
532
541
|
logger.debug(`${type} auto-scheduled @ ${new Date(scheduled).toISOString()}`, {
|
|
533
542
|
workId,
|
|
534
543
|
});
|
|
@@ -547,7 +556,7 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
547
556
|
|
|
548
557
|
finishWork,
|
|
549
558
|
|
|
550
|
-
deleteWork: async (workId: string)
|
|
559
|
+
deleteWork: async (workId: string) => {
|
|
551
560
|
const workBeforeRemoval = await WorkQueue.findOne(
|
|
552
561
|
buildQuerySelector({
|
|
553
562
|
workId,
|
|
@@ -566,8 +575,8 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
566
575
|
{ returnDocument: 'after' },
|
|
567
576
|
);
|
|
568
577
|
|
|
578
|
+
if (!work) return null;
|
|
569
579
|
emit(WorkerEventTypes.DELETED, removePrivateFields(work));
|
|
570
|
-
|
|
571
580
|
return work;
|
|
572
581
|
},
|
|
573
582
|
|
|
@@ -590,21 +599,23 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
590
599
|
{ projection: { _id: true }, sort: { test: 1 } },
|
|
591
600
|
).toArray();
|
|
592
601
|
|
|
593
|
-
return
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
602
|
+
return (
|
|
603
|
+
await Promise.all(
|
|
604
|
+
workQueue.map(({ _id }) =>
|
|
605
|
+
finishWork(_id as string, {
|
|
606
|
+
finished: new Date(),
|
|
607
|
+
result: null,
|
|
608
|
+
success: false,
|
|
609
|
+
error: {
|
|
610
|
+
name: DIRECTOR_MARKED_FAILED_ERROR,
|
|
611
|
+
message:
|
|
612
|
+
'Director marked old work as failed after restart. This work was eventually running at the moment when node.js exited.',
|
|
613
|
+
},
|
|
614
|
+
worker,
|
|
615
|
+
}),
|
|
616
|
+
),
|
|
617
|
+
)
|
|
618
|
+
).filter(Boolean) as Work[];
|
|
608
619
|
},
|
|
609
620
|
|
|
610
621
|
getReport: async ({
|
|
@@ -614,13 +625,13 @@ export const configureWorkerModule = async ({ db, options }: ModuleInput<WorkerS
|
|
|
614
625
|
types?: string[];
|
|
615
626
|
dateRange?: DateFilterInput;
|
|
616
627
|
}): Promise<WorkerReport[]> => {
|
|
617
|
-
const pipeline = [];
|
|
618
|
-
const matchConditions = [];
|
|
628
|
+
const pipeline: mongodb.BSON.Document[] = [];
|
|
629
|
+
const matchConditions: any[] = [];
|
|
619
630
|
// build date filter based on provided values it can be a range if both to and from is supplied
|
|
620
631
|
// a upper or lowe limit if either from or to is provided
|
|
621
632
|
// or all if none is provided
|
|
622
633
|
if (dateRange?.start || dateRange?.end) {
|
|
623
|
-
const dateConditions = [];
|
|
634
|
+
const dateConditions: any[] = [];
|
|
624
635
|
if (dateRange?.start) {
|
|
625
636
|
const fromDate = new Date(dateRange?.start);
|
|
626
637
|
dateConditions.push({
|