@rango-dev/queue-manager-core 0.0.0-experimental-936229e8-20251208
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/CHANGELOG.md +97 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/dist/manager.d.ts +222 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/persistor.d.ts +21 -0
- package/dist/persistor.d.ts.map +1 -0
- package/dist/queue-manager-core.build.json +1 -0
- package/dist/queue.d.ts +182 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +33 -0
- package/readme.md +4 -0
- package/src/index.ts +4 -0
- package/src/manager.ts +663 -0
- package/src/persistor.ts +75 -0
- package/src/queue.ts +597 -0
- package/src/types.ts +28 -0
package/src/manager.ts
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import type { QueueEventHandlers, TaskEvent } from './queue';
|
|
2
|
+
import type { QueueStorage } from './types';
|
|
3
|
+
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
|
|
6
|
+
import Persistor from './persistor';
|
|
7
|
+
import Queue from './queue';
|
|
8
|
+
import { Status, SYNC_POLLING_INTERVAL } from './types';
|
|
9
|
+
|
|
10
|
+
export type ManagerContext = object;
|
|
11
|
+
export type QueueName = string;
|
|
12
|
+
export type QueueID = string;
|
|
13
|
+
|
|
14
|
+
export type BlockedReason = Record<string, unknown>;
|
|
15
|
+
export type BlockedTask = {
|
|
16
|
+
queue_id: string;
|
|
17
|
+
task_id: string;
|
|
18
|
+
action: string;
|
|
19
|
+
reason: BlockedReason;
|
|
20
|
+
storage: {
|
|
21
|
+
get: () => QueueStorage;
|
|
22
|
+
set: (data: QueueStorage) => QueueStorage;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type SetStorage<T> = (nextStorage: T) => T;
|
|
27
|
+
|
|
28
|
+
export interface ExecuterActions<
|
|
29
|
+
T extends QueueStorage = QueueStorage,
|
|
30
|
+
V extends string = string,
|
|
31
|
+
C = ManagerContext
|
|
32
|
+
> {
|
|
33
|
+
next: () => void;
|
|
34
|
+
retry: () => void;
|
|
35
|
+
failed: () => void;
|
|
36
|
+
schedule: (actionName: V) => void;
|
|
37
|
+
setStorage: SetStorage<T>;
|
|
38
|
+
getStorage: () => T;
|
|
39
|
+
block: (reason: BlockedReason) => void;
|
|
40
|
+
unblock: () => void;
|
|
41
|
+
context: C;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
type WhenTaskBlockedEvent = any;
|
|
46
|
+
export interface QueueDef<
|
|
47
|
+
T extends QueueStorage = QueueStorage,
|
|
48
|
+
V extends string = string,
|
|
49
|
+
C = ManagerContext
|
|
50
|
+
> {
|
|
51
|
+
name: QueueName;
|
|
52
|
+
actions: {
|
|
53
|
+
[K in V]: (actions: ExecuterActions<T, V, C>) => void | Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
events?: Partial<QueueEventHandlers>;
|
|
56
|
+
run: V[];
|
|
57
|
+
whenTaskBlocked?: (
|
|
58
|
+
event: WhenTaskBlockedEvent,
|
|
59
|
+
params: {
|
|
60
|
+
queue_id: string;
|
|
61
|
+
queue: Queue;
|
|
62
|
+
context: C;
|
|
63
|
+
getBlockedTasks: () => BlockedTask[];
|
|
64
|
+
forceExecute: (queue_id: string, data?: object) => void;
|
|
65
|
+
retry: () => void;
|
|
66
|
+
manager: Manager;
|
|
67
|
+
}
|
|
68
|
+
) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface Events {
|
|
72
|
+
onCreateQueue: (queue: QueueInfo & { id: QueueID }) => void;
|
|
73
|
+
onUpdateQueue: (queue_id: QueueID, queue: QueueInfo) => void;
|
|
74
|
+
onCreateTask: (queue_id: QueueID, event: TaskEvent) => void;
|
|
75
|
+
onUpdateTask: (queue_id: QueueID, event: TaskEvent) => void;
|
|
76
|
+
onStorageUpdate: (queue_id: QueueID, data: QueueStorage) => void;
|
|
77
|
+
onTaskBlock: (queue_id: QueueID) => void;
|
|
78
|
+
onDeleteQueue: (queue_id: QueueID) => void;
|
|
79
|
+
onPersistedDataLoaded: (manager: Manager) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ManagerOptions {
|
|
83
|
+
events?: Partial<Events>;
|
|
84
|
+
queuesDefs: QueueDef[];
|
|
85
|
+
context?: ManagerContext;
|
|
86
|
+
isPaused?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
type _Storage = any;
|
|
91
|
+
export interface QueueInfo {
|
|
92
|
+
name: QueueName;
|
|
93
|
+
createdAt: number;
|
|
94
|
+
status: Status;
|
|
95
|
+
list: Queue;
|
|
96
|
+
actions: {
|
|
97
|
+
run: () => void;
|
|
98
|
+
cancel: () => void;
|
|
99
|
+
setStorage: SetStorage<_Storage>;
|
|
100
|
+
getStorage: () => _Storage;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class Manager {
|
|
105
|
+
private queuesDefs = new Map<QueueName, QueueDef>();
|
|
106
|
+
private queues = new Map<QueueID, QueueInfo>();
|
|
107
|
+
private events: Events;
|
|
108
|
+
private persistor: Persistor;
|
|
109
|
+
private context: ManagerContext;
|
|
110
|
+
private isPaused = false;
|
|
111
|
+
// The client won't get any update on pause, We are using a polling mode to fix this issue for now.
|
|
112
|
+
private syncInterval: number | null = null;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* Making an instance, initilize events, setup a persistor and try to recover the last state of the manager.
|
|
117
|
+
*
|
|
118
|
+
*
|
|
119
|
+
*/
|
|
120
|
+
constructor(options: ManagerOptions) {
|
|
121
|
+
const defaultEventHandlers: Events = {
|
|
122
|
+
onCreateQueue: () => {
|
|
123
|
+
// ...
|
|
124
|
+
},
|
|
125
|
+
onCreateTask: () => {
|
|
126
|
+
// ...
|
|
127
|
+
},
|
|
128
|
+
onUpdateQueue: () => {
|
|
129
|
+
// ...
|
|
130
|
+
},
|
|
131
|
+
onUpdateTask: () => {
|
|
132
|
+
// ...
|
|
133
|
+
},
|
|
134
|
+
onStorageUpdate: () => {
|
|
135
|
+
// ...
|
|
136
|
+
},
|
|
137
|
+
onTaskBlock: () => {
|
|
138
|
+
// ...
|
|
139
|
+
},
|
|
140
|
+
onPersistedDataLoaded: () => {
|
|
141
|
+
// ..
|
|
142
|
+
},
|
|
143
|
+
onDeleteQueue: () => {
|
|
144
|
+
// ...
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (options.events) {
|
|
149
|
+
this.events = {
|
|
150
|
+
...defaultEventHandlers,
|
|
151
|
+
...options.events,
|
|
152
|
+
};
|
|
153
|
+
} else {
|
|
154
|
+
this.events = defaultEventHandlers;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
options.queuesDefs.map((qDef) => {
|
|
158
|
+
this.queuesDefs.set(qDef.name, qDef);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.context = options.context || {};
|
|
162
|
+
this.persistor = new Persistor();
|
|
163
|
+
|
|
164
|
+
void this.sync();
|
|
165
|
+
|
|
166
|
+
if (options.isPaused) {
|
|
167
|
+
this.pause();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Create a new queue
|
|
172
|
+
/**
|
|
173
|
+
*
|
|
174
|
+
* Create a new queue by client.
|
|
175
|
+
*
|
|
176
|
+
* It will do the internal things to make a queue from definitions, and running using Manager.
|
|
177
|
+
*
|
|
178
|
+
* Notes:
|
|
179
|
+
* - After creating the queue, it will be run automatically.
|
|
180
|
+
*
|
|
181
|
+
* @returns an ID for queue so it can be used to get the created queue later by client.
|
|
182
|
+
*
|
|
183
|
+
*/
|
|
184
|
+
public async create(
|
|
185
|
+
name: QueueName,
|
|
186
|
+
storage: QueueStorage,
|
|
187
|
+
options?: { id?: QueueID }
|
|
188
|
+
) {
|
|
189
|
+
if (!this.queuesDefs.has(name)) {
|
|
190
|
+
throw new Error('You need to add a queue definition first.');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const def = this.queuesDefs.get(name)!;
|
|
195
|
+
const queue_id: QueueID = options?.id || uuidv4();
|
|
196
|
+
const createdAt = Date.now();
|
|
197
|
+
const list = this.createQueue({
|
|
198
|
+
queue_id: queue_id,
|
|
199
|
+
queue_name: name,
|
|
200
|
+
});
|
|
201
|
+
list.setStorage(storage);
|
|
202
|
+
|
|
203
|
+
const createdQueue = this.add(queue_id, {
|
|
204
|
+
list,
|
|
205
|
+
createdAt,
|
|
206
|
+
name,
|
|
207
|
+
status: Status.PENDING,
|
|
208
|
+
actions: {
|
|
209
|
+
run: () => {
|
|
210
|
+
list.next({
|
|
211
|
+
context: this.getContext(),
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
cancel: () => {
|
|
215
|
+
list.cancel();
|
|
216
|
+
},
|
|
217
|
+
setStorage: (...args) => {
|
|
218
|
+
list.setStorage(...args);
|
|
219
|
+
},
|
|
220
|
+
getStorage: () => {
|
|
221
|
+
return list.getStorage();
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* Persist initial queue
|
|
228
|
+
* Note: we need to first insert the queue, and then it can be updated by internal events.
|
|
229
|
+
*/
|
|
230
|
+
await this.persistor.insertQueue({
|
|
231
|
+
id: queue_id,
|
|
232
|
+
createdAt,
|
|
233
|
+
name: createdQueue.name,
|
|
234
|
+
status: createdQueue.status,
|
|
235
|
+
tasks: list.tasks,
|
|
236
|
+
state: list.state,
|
|
237
|
+
storage: list.getStorage(),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// adding initial tasks
|
|
241
|
+
def.run.forEach((action) => {
|
|
242
|
+
list.createTask(action);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// After creating a new queue, try to run.
|
|
246
|
+
this.execute();
|
|
247
|
+
return queue_id;
|
|
248
|
+
} catch (e) {
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
250
|
+
throw new Error((e as any)?.message);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
*
|
|
255
|
+
* Reading persisted data from storage then brings into memory.
|
|
256
|
+
*
|
|
257
|
+
* Notes:
|
|
258
|
+
* - Reset the memory, so we can call this method whenever we want and not only on the initialize process.
|
|
259
|
+
* - All the events will be tirggered (like onCreateQueue, onCreateTask, onBlock, ....)
|
|
260
|
+
* - Try to `resume` if the status is `running`.
|
|
261
|
+
* - Trigger `onPersistedDataLoaded` event when queues recovered from storage.
|
|
262
|
+
*
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get a queue by its ID.
|
|
267
|
+
*
|
|
268
|
+
* @returns An object includes queue and its state in `Manager`.
|
|
269
|
+
*/
|
|
270
|
+
public get(queue_id: QueueID) {
|
|
271
|
+
return this.queues.get(queue_id);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get all queues from `Manager`
|
|
276
|
+
*
|
|
277
|
+
* @returns a list of queues includes all the queues and their states.
|
|
278
|
+
*/
|
|
279
|
+
public getAll() {
|
|
280
|
+
return this.queues;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
public deleteQueue(queue_id: QueueID) {
|
|
284
|
+
this.queues.delete(queue_id);
|
|
285
|
+
void this.persistor.deleteQueue(queue_id);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public async clearQueue() {
|
|
289
|
+
// Identify queues that are not running
|
|
290
|
+
this.queues.forEach((queue, queue_id) => {
|
|
291
|
+
if (
|
|
292
|
+
queue.status !== Status.RUNNING &&
|
|
293
|
+
queue.status !== Status.PENDING &&
|
|
294
|
+
queue.status !== Status.BLOCKED
|
|
295
|
+
) {
|
|
296
|
+
this.deleteQueue(queue_id);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
*
|
|
303
|
+
* Ask from manager to run pending queues.
|
|
304
|
+
*
|
|
305
|
+
* It only try to run queues with `PENDING` status and ignore all the other statuses.
|
|
306
|
+
*
|
|
307
|
+
*/
|
|
308
|
+
public execute() {
|
|
309
|
+
if (!this.shouldExecute()) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for (const [, q] of Array.from(this.queues)) {
|
|
314
|
+
if (q.status === Status.PENDING) {
|
|
315
|
+
q.actions.run();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
*
|
|
322
|
+
* Try to find queues with `RUNNING` status to run them again.
|
|
323
|
+
*
|
|
324
|
+
* It's useful for recovering the queue at some certain points
|
|
325
|
+
* like running a currepted task (reloaded when it was running) or needs manual trigger from UI.
|
|
326
|
+
*
|
|
327
|
+
* @returns
|
|
328
|
+
*/
|
|
329
|
+
public resume() {
|
|
330
|
+
if (!this.shouldExecute()) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for (const [, q] of Array.from(this.queues)) {
|
|
335
|
+
if (q.status === Status.RUNNING) {
|
|
336
|
+
q.list.resume({
|
|
337
|
+
context: this.getContext(),
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// If there is no running queue, try to run a new queue.
|
|
344
|
+
this.execute();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
*
|
|
349
|
+
* Run all `BLOCKED` queues once again.
|
|
350
|
+
*
|
|
351
|
+
* If a queue has `BLOCKED` status and the last task is `BLOCKED` as well,
|
|
352
|
+
* The task will be run one more time.
|
|
353
|
+
*
|
|
354
|
+
* Useful for scenarios like we are blocking the queue under some conditions in task,
|
|
355
|
+
* We can use this method to ask the queue to run the blocked task one more time and
|
|
356
|
+
* maybe this time condtions are met and the queue can be proceed.
|
|
357
|
+
*
|
|
358
|
+
* @returns
|
|
359
|
+
*/
|
|
360
|
+
public retry() {
|
|
361
|
+
if (!this.shouldExecute()) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
for (const [, q] of Array.from(this.queues)) {
|
|
366
|
+
if (q.status === Status.BLOCKED) {
|
|
367
|
+
q.list.checkBlock();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// If there is no running queue, try to run a new queue.
|
|
372
|
+
this.execute();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
*
|
|
377
|
+
* Run a blocked task on a specific queue with the ability to pass more data.
|
|
378
|
+
*
|
|
379
|
+
* Useful when we have a custom logic for running queue and needs to pass some specific data
|
|
380
|
+
* to the task and try to run it manually with the provided data.
|
|
381
|
+
*
|
|
382
|
+
*/
|
|
383
|
+
public forceExecute(queue_id: string, data?: object) {
|
|
384
|
+
const queue = this.get(queue_id);
|
|
385
|
+
let context = this.getContext();
|
|
386
|
+
if (data) {
|
|
387
|
+
context = {
|
|
388
|
+
...context,
|
|
389
|
+
...data,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
queue?.list.forceRun({
|
|
394
|
+
context,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Active readonly mode for manager, it means it doesn't run anythin,
|
|
400
|
+
* And only can be used to read the data.
|
|
401
|
+
*/
|
|
402
|
+
public pause() {
|
|
403
|
+
if (this.isPaused) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
this.isPaused = true;
|
|
407
|
+
this.syncInterval = window.setInterval(() => {
|
|
408
|
+
void this.sync();
|
|
409
|
+
}, SYNC_POLLING_INTERVAL);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Activate normal mode which means it will be able to run the queuese as well.
|
|
414
|
+
*/
|
|
415
|
+
public run() {
|
|
416
|
+
// If call this method multiple times, it should be run for once.
|
|
417
|
+
if (this.isPaused) {
|
|
418
|
+
this.isPaused = false;
|
|
419
|
+
if (!!this.syncInterval) {
|
|
420
|
+
clearInterval(this.syncInterval);
|
|
421
|
+
this.syncInterval = null;
|
|
422
|
+
}
|
|
423
|
+
void this.sync();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private async sync() {
|
|
428
|
+
try {
|
|
429
|
+
// Reading queues from storage
|
|
430
|
+
const queues = await this.persistor.getAll();
|
|
431
|
+
|
|
432
|
+
// Reset queues, if anything is exist in memory.
|
|
433
|
+
this.queues = new Map();
|
|
434
|
+
|
|
435
|
+
// Brings them into memory
|
|
436
|
+
queues.forEach((q) => {
|
|
437
|
+
const list = this.createQueue({
|
|
438
|
+
queue_id: q.id,
|
|
439
|
+
queue_name: q.name,
|
|
440
|
+
});
|
|
441
|
+
this.add(q.id, {
|
|
442
|
+
list,
|
|
443
|
+
createdAt: q.createdAt,
|
|
444
|
+
name: q.name,
|
|
445
|
+
status: q.status,
|
|
446
|
+
actions: {
|
|
447
|
+
run: () => {
|
|
448
|
+
list.next({
|
|
449
|
+
context: this.getContext(),
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
cancel: () => {
|
|
453
|
+
list.cancel();
|
|
454
|
+
},
|
|
455
|
+
setStorage: (...args) => {
|
|
456
|
+
list.setStorage(...args);
|
|
457
|
+
},
|
|
458
|
+
getStorage: () => {
|
|
459
|
+
return list.getStorage();
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
list.initTasks({
|
|
465
|
+
state: q.state,
|
|
466
|
+
tasks: q.tasks,
|
|
467
|
+
storage: q.storage || {},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
if (q.status === Status.RUNNING && this.shouldExecute()) {
|
|
471
|
+
list.resume({
|
|
472
|
+
context: this.getContext(),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
} catch (e) {
|
|
477
|
+
console.log(`IndexDB Error: ${e}`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Trigger an event to let the subscribers we are done here.
|
|
481
|
+
this.events.onPersistedDataLoaded(this);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
*
|
|
486
|
+
* Making a new instance from `Queue` and adds Manager's event handlers to it.
|
|
487
|
+
*
|
|
488
|
+
* @returns An instance of `Queue` with wrapped event handlers from Manager.
|
|
489
|
+
*
|
|
490
|
+
*/
|
|
491
|
+
private createQueue(info: { queue_id: QueueID; queue_name: QueueName }) {
|
|
492
|
+
const { queue_id, queue_name } = info;
|
|
493
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
494
|
+
const manager = this;
|
|
495
|
+
const def = this.queuesDefs.get(queue_name)!;
|
|
496
|
+
const list = new Queue({
|
|
497
|
+
id: queue_id,
|
|
498
|
+
events: {
|
|
499
|
+
onCreate: (task) => {
|
|
500
|
+
this.events.onCreateTask(queue_id, task);
|
|
501
|
+
this.handleUpdate(queue_id);
|
|
502
|
+
|
|
503
|
+
if (def.events?.onCreate) {
|
|
504
|
+
def.events.onCreate(task);
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
onUpdate: (task) => {
|
|
508
|
+
this.events.onUpdateTask(queue_id, task);
|
|
509
|
+
this.handleUpdate(queue_id);
|
|
510
|
+
|
|
511
|
+
if (def.events?.onUpdate) {
|
|
512
|
+
def.events.onUpdate(task);
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
onUpdateListStatus: (status) => {
|
|
516
|
+
this.queues.set(queue_id, {
|
|
517
|
+
...this.get(queue_id)!,
|
|
518
|
+
status,
|
|
519
|
+
});
|
|
520
|
+
this.events.onUpdateQueue(queue_id, this.get(queue_id)!);
|
|
521
|
+
|
|
522
|
+
this.handleUpdate(queue_id);
|
|
523
|
+
|
|
524
|
+
// After finishing a queue, try to run other queues.
|
|
525
|
+
this.execute();
|
|
526
|
+
if (def.events?.onUpdateListStatus) {
|
|
527
|
+
def.events.onUpdateListStatus(status);
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
onStorageUpdate: (data) => {
|
|
531
|
+
this.events.onStorageUpdate(queue_id, data);
|
|
532
|
+
if (def.events?.onStorageUpdate) {
|
|
533
|
+
def.events.onStorageUpdate(data);
|
|
534
|
+
}
|
|
535
|
+
this.handleUpdate(queue_id);
|
|
536
|
+
},
|
|
537
|
+
onBlock: (event) => {
|
|
538
|
+
// Update queue status
|
|
539
|
+
this.queues.set(queue_id, {
|
|
540
|
+
...this.get(queue_id)!,
|
|
541
|
+
status: Status.BLOCKED,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Trigger event
|
|
545
|
+
this.events.onTaskBlock(queue_id);
|
|
546
|
+
if (def.whenTaskBlocked) {
|
|
547
|
+
def.whenTaskBlocked(event, {
|
|
548
|
+
queue_id: queue_id,
|
|
549
|
+
queue: list,
|
|
550
|
+
context: this.getContext(),
|
|
551
|
+
getBlockedTasks: this.getBlockedTasks.bind(this),
|
|
552
|
+
forceExecute: this.forceExecute.bind(this),
|
|
553
|
+
retry: this.retry.bind(this),
|
|
554
|
+
manager,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Sync
|
|
559
|
+
this.handleUpdate(queue_id);
|
|
560
|
+
},
|
|
561
|
+
onUnblock: () => {
|
|
562
|
+
// Update queue status
|
|
563
|
+
this.queues.set(queue_id, {
|
|
564
|
+
...this.get(queue_id)!,
|
|
565
|
+
status: Status.PENDING,
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Sync
|
|
569
|
+
this.handleUpdate(queue_id);
|
|
570
|
+
|
|
571
|
+
this.execute();
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
actions: def.actions,
|
|
575
|
+
});
|
|
576
|
+
return list;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Go through all tasks (from all queues) and return a list of blocked tasks
|
|
581
|
+
*
|
|
582
|
+
* @returns a list of blocked tasks including enough information to get the queue and mutate the storage.
|
|
583
|
+
*
|
|
584
|
+
*/
|
|
585
|
+
private getBlockedTasks() {
|
|
586
|
+
const queues = this.getAll();
|
|
587
|
+
const blockedTasks: BlockedTask[] = [];
|
|
588
|
+
queues.forEach((q, queue_id) => {
|
|
589
|
+
q.list.tasks.forEach((task) => {
|
|
590
|
+
const state = q.list.state.tasks[task.id];
|
|
591
|
+
if (state.status === Status.BLOCKED) {
|
|
592
|
+
blockedTasks.push({
|
|
593
|
+
task_id: task.id,
|
|
594
|
+
queue_id: queue_id,
|
|
595
|
+
action: task.action,
|
|
596
|
+
reason: state.blockedFor,
|
|
597
|
+
storage: {
|
|
598
|
+
get: () => {
|
|
599
|
+
return q.list.getStorage();
|
|
600
|
+
},
|
|
601
|
+
set: (data) => {
|
|
602
|
+
return q.list.setStorage(data);
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
return blockedTasks;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
*
|
|
615
|
+
* Add a queue to the manager to keep track of the queue and its state.
|
|
616
|
+
*
|
|
617
|
+
* @param id
|
|
618
|
+
* @param queue
|
|
619
|
+
* @returns
|
|
620
|
+
*/
|
|
621
|
+
private add(id: QueueID, queue: QueueInfo) {
|
|
622
|
+
this.queues.set(id, queue);
|
|
623
|
+
const createdQueue = this.get(id)!;
|
|
624
|
+
this.events.onCreateQueue({ ...createdQueue, id });
|
|
625
|
+
|
|
626
|
+
return createdQueue;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
*
|
|
631
|
+
* Sync in-memory state (of `Manager`) with storage (persist).
|
|
632
|
+
*
|
|
633
|
+
* Usually we call this method after a change detected in the state of manager.
|
|
634
|
+
*
|
|
635
|
+
*/
|
|
636
|
+
private handleUpdate(queue_id: QueueID) {
|
|
637
|
+
const queue = this.get(queue_id);
|
|
638
|
+
|
|
639
|
+
if (queue) {
|
|
640
|
+
const status = queue.status;
|
|
641
|
+
const state = queue.list.state;
|
|
642
|
+
const tasks = queue.list.tasks;
|
|
643
|
+
void this.persistor.updateQueue(queue_id, {
|
|
644
|
+
status,
|
|
645
|
+
state,
|
|
646
|
+
tasks,
|
|
647
|
+
storage: queue.list.getStorage(),
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private getContext() {
|
|
653
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
654
|
+
//@ts-ignore
|
|
655
|
+
return this.context?.current || {};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
private shouldExecute() {
|
|
659
|
+
return !this.isPaused;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export { Manager };
|