@stamhoofd/queues 2.49.0 → 2.50.0

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.
@@ -4,22 +4,51 @@ declare class Queue {
4
4
  items: QueueItem<any>[];
5
5
  parallel: number;
6
6
  runCount: number;
7
+ abortSignals: Set<AbortSignal>;
7
8
  constructor(name: string, parallel?: number);
8
9
  addItem(item: QueueItem<any>): void;
10
+ createAbortSignal(): AbortSignal;
11
+ removeAbortSignal(signal: AbortSignal): void;
12
+ abort(error?: Error): void;
9
13
  }
14
+ export type QueueHandlerOptions = {
15
+ abort: AbortSignal;
16
+ };
10
17
  declare class QueueItem<T> {
11
- handler: () => Promise<T>;
18
+ handler: (options: QueueHandlerOptions) => Promise<T>;
12
19
  resolve: (value: T) => void;
13
20
  reject: (reason?: any) => void;
14
21
  }
22
+ export declare function isDebouncedError(error: unknown): boolean;
23
+ export declare function isCanceledError(error: unknown): boolean;
24
+ export declare function isAbortedError(error: unknown): boolean;
25
+ export declare class AbortSignal {
26
+ protected abortWithError: unknown | null;
27
+ protected listeners: ((error: unknown) => Promise<void> | void)[];
28
+ get isAborted(): boolean;
29
+ abort(error?: unknown): void;
30
+ throwIfAborted(cleanup?: () => Promise<void> | void): Promise<void>;
31
+ on(on: 'abort', listener: (error: unknown) => Promise<void> | void): void;
32
+ }
15
33
  /**
16
34
  * Force the usage of a queue to prevent concurrency issues
17
35
  */
18
36
  export declare class QueueHandler {
19
37
  static queues: Map<string, Queue>;
20
38
  static asyncLocalStorage: AsyncLocalStorage<string[]>;
21
- static cancel(queue: string): void;
22
- static schedule<T>(queue: string, handler: () => Promise<T>, parallel?: number): Promise<T>;
39
+ static cancel(queue: string, error?: Error): void;
40
+ /**
41
+ * Abort any running queue items. Same as 'cancel' but will also reject any running items if they support the 'abort' method (otherwise they will just resolve as normal)
42
+ */
43
+ static abort(queue: string, error?: Error): void;
44
+ /**$
45
+ * Waits {timeout} ms before executing the handler, if another call is made to the same queue, the timeout is reset
46
+ * and the other users will receive an error.
47
+ *
48
+ * Note: this will throw if the handler was debounced and not yet executed
49
+ */
50
+ static debounce<T>(queue: string, handler: (options: QueueHandlerOptions) => Promise<T>, timeout: number): Promise<T>;
51
+ static schedule<T>(queue: string, handler: (options: QueueHandlerOptions) => Promise<T>, parallel?: number): Promise<T>;
23
52
  static isRunning(queue: string): boolean;
24
53
  /**
25
54
  * Returns amount of running jobs + pending jobs for a given queue
@@ -1 +1 @@
1
- {"version":3,"file":"QueueHandler.d.ts","sourceRoot":"","sources":["../../src/QueueHandler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,cAAM,KAAK;IACP,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAK;IAC5B,QAAQ,SAAI;IACZ,QAAQ,SAAI;gBAEA,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAI;IAKtC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC;CAG/B;AAED,cAAM,SAAS,CAAC,CAAC;IACb,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;IACzB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;IAC3B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;CACjC;AAED;;GAEG;AACH,qBAAa,YAAY;IACrB,MAAM,CAAC,MAAM,qBAA2B;IACxC,MAAM,CAAC,iBAAiB,8BAAqC;IAE7D,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM;WAYd,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,SAAI,GAAG,OAAO,CAAC,CAAC,CAAC;IAuC5F,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM;IAI9B;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM;mBAKP,OAAO;CAoC/B"}
1
+ {"version":3,"file":"QueueHandler.d.ts","sourceRoot":"","sources":["../../src/QueueHandler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,cAAM,KAAK;IACP,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAK;IAC5B,QAAQ,SAAI;IACZ,QAAQ,SAAI;IACZ,YAAY,EAAE,GAAG,CAAC,WAAW,CAAC,CAAY;gBAE9B,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAI;IAKtC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC;IAI5B,iBAAiB;IAMjB,iBAAiB,CAAC,MAAM,EAAE,WAAW;IAIrC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK;CAMtB;AAED,MAAM,MAAM,mBAAmB,GAAG;IAC9B,KAAK,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,cAAM,SAAS,CAAC,CAAC;IACb,OAAO,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACrD,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;IAC3B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;CACjC;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,WAE9C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,WAE7C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,WAE5C;AAED,qBAAa,WAAW;IACpB,SAAS,CAAC,cAAc,EAAE,OAAO,GAAG,IAAI,CAAO;IAC/C,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAC,IAAI,CAAC,EAAE,CAAK;IAEpE,IAAI,SAAS,YAEZ;IAED,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO;IAgBf,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAC,IAAI;IAUvD,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAC,IAAI;CAGnE;AAED;;GAEG;AACH,qBAAa,YAAY;IACrB,MAAM,CAAC,MAAM,qBAA2B;IACxC,MAAM,CAAC,iBAAiB,8BAAqC;IAE7D,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;IAe1C;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;IAiBzC;;;;;OAKG;WACU,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;WA0B9G,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,SAAI,GAAG,OAAO,CAAC,CAAC,CAAC;IAuCxH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM;IAI9B;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM;mBAKP,OAAO;CAmD/B"}
@@ -1,12 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.QueueHandler = void 0;
3
+ exports.QueueHandler = exports.AbortSignal = void 0;
4
+ exports.isDebouncedError = isDebouncedError;
5
+ exports.isCanceledError = isCanceledError;
6
+ exports.isAbortedError = isAbortedError;
4
7
  const node_async_hooks_1 = require("node:async_hooks");
8
+ const simple_errors_1 = require("@simonbackx/simple-errors");
5
9
  class Queue {
6
10
  name;
7
11
  items = [];
8
12
  parallel = 1;
9
13
  runCount = 0;
14
+ abortSignals = new Set();
10
15
  constructor(name, parallel = 1) {
11
16
  this.name = name;
12
17
  this.parallel = parallel;
@@ -14,44 +19,150 @@ class Queue {
14
19
  addItem(item) {
15
20
  this.items.push(item);
16
21
  }
22
+ createAbortSignal() {
23
+ const signal = new AbortSignal();
24
+ this.abortSignals.add(signal);
25
+ return signal;
26
+ }
27
+ removeAbortSignal(signal) {
28
+ this.abortSignals.delete(signal);
29
+ }
30
+ abort(error) {
31
+ for (const signal of this.abortSignals) {
32
+ signal.abort(error);
33
+ }
34
+ this.abortSignals.clear(); // Avoid swapping error midway the abort
35
+ }
17
36
  }
18
37
  class QueueItem {
19
38
  handler;
20
39
  resolve;
21
40
  reject;
22
41
  }
42
+ function isDebouncedError(error) {
43
+ return (0, simple_errors_1.isSimpleError)(error) && error.code === 'queue-debounced';
44
+ }
45
+ function isCanceledError(error) {
46
+ return (0, simple_errors_1.isSimpleError)(error) && error.code === 'queue-canceled';
47
+ }
48
+ function isAbortedError(error) {
49
+ return (0, simple_errors_1.isSimpleError)(error) && error.code === 'queue-aborted';
50
+ }
51
+ class AbortSignal {
52
+ abortWithError = null;
53
+ listeners = [];
54
+ get isAborted() {
55
+ return this.abortWithError !== null;
56
+ }
57
+ abort(error) {
58
+ if (this.isAborted) {
59
+ return;
60
+ }
61
+ this.abortWithError = error ?? new simple_errors_1.SimpleError({
62
+ code: 'queue-aborted',
63
+ message: 'Queue was aborted',
64
+ statusCode: 500
65
+ });
66
+ for (const listener of this.listeners) {
67
+ listener(this.abortWithError);
68
+ }
69
+ this.listeners = [];
70
+ }
71
+ async throwIfAborted(cleanup) {
72
+ if (this.isAborted) {
73
+ if (cleanup) {
74
+ await cleanup();
75
+ }
76
+ throw this.abortWithError;
77
+ }
78
+ }
79
+ // Add event listeners
80
+ on(on, listener) {
81
+ this.listeners.push(listener);
82
+ }
83
+ }
84
+ exports.AbortSignal = AbortSignal;
23
85
  /**
24
86
  * Force the usage of a queue to prevent concurrency issues
25
87
  */
26
88
  class QueueHandler {
27
89
  static queues = new Map();
28
90
  static asyncLocalStorage = new node_async_hooks_1.AsyncLocalStorage();
29
- static cancel(queue) {
91
+ static cancel(queue, error) {
30
92
  const q = this.queues.get(queue);
31
93
  if (q) {
32
94
  // This doesn't interfere any running items
33
95
  for (const item of q.items) {
34
96
  item.handler = () => {
35
- return Promise.reject(new Error('Queue was cancelled'));
97
+ return Promise.reject(error ?? new simple_errors_1.SimpleError({
98
+ code: 'queue-canceled',
99
+ message: 'Queue ' + queue + ' was canceled',
100
+ }));
36
101
  };
37
102
  }
38
103
  }
39
104
  }
105
+ /**
106
+ * Abort any running queue items. Same as 'cancel' but will also reject any running items if they support the 'abort' method (otherwise they will just resolve as normal)
107
+ */
108
+ static abort(queue, error) {
109
+ const q = this.queues.get(queue);
110
+ if (q) {
111
+ // This doesn't interfere any running items
112
+ for (const item of q.items) {
113
+ item.handler = () => {
114
+ return Promise.reject(error ?? new simple_errors_1.SimpleError({
115
+ code: 'queue-canceled',
116
+ message: 'Queue ' + queue + ' was canceled',
117
+ }));
118
+ };
119
+ }
120
+ q.abort(error);
121
+ }
122
+ }
123
+ /**$
124
+ * Waits {timeout} ms before executing the handler, if another call is made to the same queue, the timeout is reset
125
+ * and the other users will receive an error.
126
+ *
127
+ * Note: this will throw if the handler was debounced and not yet executed
128
+ */
129
+ static async debounce(queue, handler, timeout) {
130
+ // Stop any running items that were not yet executed
131
+ this.abort(queue, new simple_errors_1.SimpleError({
132
+ code: 'queue-debounced',
133
+ message: 'Debounced',
134
+ statusCode: 500
135
+ }));
136
+ // Schedule timeout (will throw if cancelled)
137
+ await this.schedule(queue, ({ abort }) => {
138
+ return new Promise((resolve, reject) => {
139
+ const timeoutId = setTimeout(() => {
140
+ resolve();
141
+ }, timeout);
142
+ abort.on('abort', (error) => {
143
+ clearTimeout(timeoutId);
144
+ reject(error);
145
+ });
146
+ });
147
+ });
148
+ // Run the handler
149
+ return await this.schedule(queue, handler);
150
+ }
40
151
  static async schedule(queue, handler, parallel = 1) {
41
152
  // console.log("[QUEUE] Schedule "+queue)
42
153
  const currentQueues = this.asyncLocalStorage.getStore();
43
154
  if (currentQueues !== undefined && currentQueues.includes(queue)) {
44
155
  console.warn('Recursive usage of queues detected. Ignored running in queue', queue, currentQueues);
45
- return await handler();
156
+ return await handler({ abort: new AbortSignal() });
46
157
  }
47
158
  // We need to save the current AsyncLocalStorage context
48
159
  // otherwise we could run items on the queue with the wrong context
49
160
  const snapshot = node_async_hooks_1.AsyncLocalStorage.snapshot();
50
161
  const item = new QueueItem();
51
- item.handler = () => snapshot(async () => {
162
+ item.handler = (options) => snapshot(async () => {
52
163
  const currentQueues = this.asyncLocalStorage.getStore() ?? [];
53
164
  return await this.asyncLocalStorage.run([...currentQueues, queue], async () => {
54
- return await handler();
165
+ return await handler(options);
55
166
  });
56
167
  });
57
168
  const promise = new Promise((resolve, reject) => {
@@ -95,17 +206,36 @@ class QueueHandler {
95
206
  }
96
207
  q.runCount += 1;
97
208
  // console.log("[QUEUE] ("+q.runCount+"/"+q.parallel+") Executing "+queue+" ("+q.items.length+" remaining)")
209
+ const abort = q.createAbortSignal();
98
210
  try {
99
- next.resolve(await next.handler());
211
+ next.resolve(await next.handler({
212
+ abort
213
+ }));
100
214
  // console.log("[QUEUE] ("+(q.runCount-1)+"/"+q.parallel+") Resolved "+queue+" ("+q.items.length+" remaining)")
101
215
  }
102
216
  catch (e) {
103
217
  next.reject(e);
104
218
  if (STAMHOOFD.environment !== 'test') {
105
- console.log("[QUEUE] (" + (q.runCount - 1) + "/" + q.parallel + ") Rejected " + queue + " (" + q.items.length + " remaining)");
106
- console.error(e);
219
+ if (!isDebouncedError(e) && !isCanceledError(e) && !isAbortedError(e)) {
220
+ console.log("[QUEUE] (" + (q.runCount - 1) + "/" + q.parallel + ") Rejected " + queue + " (" + q.items.length + " remaining)");
221
+ console.error(e);
222
+ }
223
+ else {
224
+ if (isDebouncedError(e)) {
225
+ console.log("[QUEUE] (" + (q.runCount - 1) + "/" + q.parallel + ") Debounced " + queue + " (" + q.items.length + " remaining)");
226
+ }
227
+ else if (isCanceledError(e)) {
228
+ console.log("[QUEUE] (" + (q.runCount - 1) + "/" + q.parallel + ") Canceled " + queue + " (" + q.items.length + " remaining)");
229
+ }
230
+ else if (isAbortedError(e)) {
231
+ console.log("[QUEUE] (" + (q.runCount - 1) + "/" + q.parallel + ") Aborted " + queue + " (" + q.items.length + " remaining)");
232
+ }
233
+ }
107
234
  }
108
235
  }
236
+ finally {
237
+ q.removeAbortSignal(abort);
238
+ }
109
239
  q.runCount -= 1;
110
240
  await this.runNext(queue);
111
241
  }
@@ -1 +1 @@
1
- {"version":3,"file":"QueueHandler.js","sourceRoot":"","sources":["../../src/QueueHandler.ts"],"names":[],"mappings":";;;AACA,uDAAqD;AAErD,MAAM,KAAK;IACP,IAAI,CAAQ;IACZ,KAAK,GAAqB,EAAE,CAAA;IAC5B,QAAQ,GAAG,CAAC,CAAA;IACZ,QAAQ,GAAG,CAAC,CAAA;IAEZ,YAAY,IAAY,EAAE,QAAQ,GAAG,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC5B,CAAC;IAED,OAAO,CAAC,IAAoB;QACxB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;CACJ;AAED,MAAM,SAAS;IACX,OAAO,CAAkB;IACzB,OAAO,CAAoB;IAC3B,MAAM,CAAwB;CACjC;AAED;;GAEG;AACH,MAAa,YAAY;IACrB,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,EAAiB,CAAA;IACxC,MAAM,CAAC,iBAAiB,GAAG,IAAI,oCAAiB,EAAY,CAAC;IAE7D,MAAM,CAAC,MAAM,CAAC,KAAa;QACvB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,CAAC,EAAE,CAAC;YACJ,2CAA2C;YAC3C,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,OAAO,GAAG,GAAG,EAAE;oBAChB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAA;gBAC3D,CAAC,CAAA;YACL,CAAC;QACL,CAAC;IACL,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAI,KAAa,EAAE,OAAyB,EAAE,QAAQ,GAAG,CAAC;QAC3E,yCAAyC;QAEzC,MAAM,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC;QACxD,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,8DAA8D,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;YACnG,OAAO,MAAM,OAAO,EAAE,CAAC;QAC3B,CAAC;QAED,wDAAwD;QACxD,mEAAmE;QACnE,MAAM,QAAQ,GAAG,oCAAiB,CAAC,QAAQ,EAAE,CAAA;QAE7C,MAAM,IAAI,GAAG,IAAI,SAAS,EAAK,CAAA;QAC/B,IAAI,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;YACrC,MAAM,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;YAC9D,OAAO,MAAM,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,GAAG,aAAa,EAAE,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE;gBAC1E,OAAO,MAAM,OAAO,EAAE,CAAC;YAC3B,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC/C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;YACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;YAEpB,6DAA6D;YAC7D,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;YAC9D,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YACf,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;YAEzB,2CAA2C;YAC3C,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;gBAC1B,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,CAAC,CAAC,CAAA;YAC1D,CAAC,CAAC,CAAA;QACN,CAAC,CAAC,CAAA;QAEF,OAAO,OAAO,CAAA;IAClB,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,KAAa;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAClC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,KAAa;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAClD,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,KAAa;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,CAAC,CAAC,EAAE,CAAC;YACL,iEAAiE;YACjE,OAAM;QACV,CAAC;QAED,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC3B,yEAAyE;YACzE,OAAM;QACV,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QAE5B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACrB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACzB,OAAM;QACV,CAAC;QAED,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAA;QACf,4GAA4G;QAE5G,IAAI,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;YAClC,+GAA+G;QACnH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;YACd,IAAI,SAAS,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;gBACnC,OAAO,CAAC,GAAG,CAAC,WAAW,GAAC,CAAC,CAAC,CAAC,QAAQ,GAAC,CAAC,CAAC,GAAC,GAAG,GAAC,CAAC,CAAC,QAAQ,GAAC,aAAa,GAAC,KAAK,GAAC,IAAI,GAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAC,aAAa,CAAC,CAAA;gBAC5G,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACpB,CAAC;QACL,CAAC;QAED,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAA;QACf,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC;;AAtGL,oCAuGC"}
1
+ {"version":3,"file":"QueueHandler.js","sourceRoot":"","sources":["../../src/QueueHandler.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/queues",
3
- "version": "2.49.0",
3
+ "version": "2.50.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "license": "UNLICENCED",
@@ -16,5 +16,5 @@
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
19
- "gitHead": "ee78a53466f94dd4c58e7a3140198422f25ee961"
19
+ "gitHead": "809b483cc6e98291fca589b1ac39aaa3a7fcf6f2"
20
20
  }
@@ -1,11 +1,14 @@
1
1
 
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import { sleep } from '@stamhoofd/utility';
4
+ import { isSimpleError, SimpleError } from '@simonbackx/simple-errors';
3
5
 
4
6
  class Queue {
5
7
  name: string
6
8
  items: QueueItem<any>[] = []
7
9
  parallel = 1
8
10
  runCount = 0
11
+ abortSignals: Set<AbortSignal> = new Set()
9
12
 
10
13
  constructor(name: string, parallel = 1) {
11
14
  this.name = name
@@ -15,14 +18,86 @@ class Queue {
15
18
  addItem(item: QueueItem<any>) {
16
19
  this.items.push(item)
17
20
  }
21
+
22
+ createAbortSignal() {
23
+ const signal = new AbortSignal()
24
+ this.abortSignals.add(signal)
25
+ return signal
26
+ }
27
+
28
+ removeAbortSignal(signal: AbortSignal) {
29
+ this.abortSignals.delete(signal)
30
+ }
31
+
32
+ abort(error?: Error) {
33
+ for (const signal of this.abortSignals) {
34
+ signal.abort(error)
35
+ }
36
+ this.abortSignals.clear() // Avoid swapping error midway the abort
37
+ }
38
+ }
39
+
40
+ export type QueueHandlerOptions = {
41
+ abort: AbortSignal
18
42
  }
19
43
 
20
44
  class QueueItem<T> {
21
- handler: () => Promise<T>
45
+ handler: (options: QueueHandlerOptions) => Promise<T>
22
46
  resolve: (value: T) => void
23
47
  reject: (reason?: any) => void
24
48
  }
25
49
 
50
+ export function isDebouncedError(error: unknown) {
51
+ return isSimpleError(error) && error.code === 'queue-debounced'
52
+ }
53
+
54
+ export function isCanceledError(error: unknown) {
55
+ return isSimpleError(error) && error.code === 'queue-canceled'
56
+ }
57
+
58
+ export function isAbortedError(error: unknown) {
59
+ return isSimpleError(error) && error.code === 'queue-aborted'
60
+ }
61
+
62
+ export class AbortSignal {
63
+ protected abortWithError: unknown | null = null
64
+ protected listeners: ((error: unknown) => Promise<void>|void)[] = []
65
+
66
+ get isAborted() {
67
+ return this.abortWithError !== null
68
+ }
69
+
70
+ abort(error?: unknown) {
71
+ if (this.isAborted) {
72
+ return
73
+ }
74
+ this.abortWithError = error ?? new SimpleError({
75
+ code: 'queue-aborted',
76
+ message: 'Queue was aborted',
77
+ statusCode: 500
78
+ })
79
+
80
+ for (const listener of this.listeners) {
81
+ listener(this.abortWithError)
82
+ }
83
+ this.listeners = []
84
+ }
85
+
86
+ async throwIfAborted(cleanup?: () => Promise<void>|void) {
87
+ if (this.isAborted) {
88
+ if (cleanup) {
89
+ await cleanup()
90
+ }
91
+ throw this.abortWithError
92
+ }
93
+ }
94
+
95
+ // Add event listeners
96
+ on(on: 'abort', listener: (error: unknown) => Promise<void>|void) {
97
+ this.listeners.push(listener)
98
+ }
99
+ }
100
+
26
101
  /**
27
102
  * Force the usage of a queue to prevent concurrency issues
28
103
  */
@@ -30,25 +105,80 @@ export class QueueHandler {
30
105
  static queues = new Map<string, Queue>()
31
106
  static asyncLocalStorage = new AsyncLocalStorage<string[]>();
32
107
 
33
- static cancel(queue: string) {
108
+ static cancel(queue: string, error?: Error) {
109
+ const q = this.queues.get(queue)
110
+ if (q) {
111
+ // This doesn't interfere any running items
112
+ for (const item of q.items) {
113
+ item.handler = () => {
114
+ return Promise.reject(error ?? new SimpleError({
115
+ code: 'queue-canceled',
116
+ message: 'Queue '+queue+' was canceled',
117
+ }))
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Abort any running queue items. Same as 'cancel' but will also reject any running items if they support the 'abort' method (otherwise they will just resolve as normal)
125
+ */
126
+ static abort(queue: string, error?: Error) {
34
127
  const q = this.queues.get(queue)
35
128
  if (q) {
36
129
  // This doesn't interfere any running items
37
130
  for (const item of q.items) {
38
131
  item.handler = () => {
39
- return Promise.reject(new Error('Queue was cancelled'))
132
+ return Promise.reject(error ?? new SimpleError({
133
+ code: 'queue-canceled',
134
+ message: 'Queue '+queue+' was canceled',
135
+ }))
40
136
  }
41
137
  }
138
+
139
+ q.abort(error)
42
140
  }
43
141
  }
44
142
 
45
- static async schedule<T>(queue: string, handler: () => Promise<T>, parallel = 1): Promise<T> {
143
+ /**$
144
+ * Waits {timeout} ms before executing the handler, if another call is made to the same queue, the timeout is reset
145
+ * and the other users will receive an error.
146
+ *
147
+ * Note: this will throw if the handler was debounced and not yet executed
148
+ */
149
+ static async debounce<T>(queue: string, handler: (options: QueueHandlerOptions) => Promise<T>, timeout: number): Promise<T> {
150
+ // Stop any running items that were not yet executed
151
+ this.abort(queue, new SimpleError({
152
+ code: 'queue-debounced',
153
+ message: 'Debounced',
154
+ statusCode: 500
155
+ }));
156
+
157
+ // Schedule timeout (will throw if cancelled)
158
+ await this.schedule(queue, ({abort}) => {
159
+ return new Promise<void>((resolve, reject) => {
160
+ const timeoutId = setTimeout(() => {
161
+ resolve()
162
+ }, timeout)
163
+
164
+ abort.on('abort', (error) => {
165
+ clearTimeout(timeoutId)
166
+ reject(error)
167
+ })
168
+ })
169
+ });
170
+
171
+ // Run the handler
172
+ return await this.schedule(queue, handler)
173
+ }
174
+
175
+ static async schedule<T>(queue: string, handler: (options: QueueHandlerOptions) => Promise<T>, parallel = 1): Promise<T> {
46
176
  // console.log("[QUEUE] Schedule "+queue)
47
177
 
48
178
  const currentQueues = this.asyncLocalStorage.getStore();
49
179
  if (currentQueues !== undefined && currentQueues.includes(queue)) {
50
180
  console.warn('Recursive usage of queues detected. Ignored running in queue', queue, currentQueues);
51
- return await handler();
181
+ return await handler({abort: new AbortSignal()});
52
182
  }
53
183
 
54
184
  // We need to save the current AsyncLocalStorage context
@@ -56,10 +186,10 @@ export class QueueHandler {
56
186
  const snapshot = AsyncLocalStorage.snapshot()
57
187
 
58
188
  const item = new QueueItem<T>()
59
- item.handler = () => snapshot(async () => {
189
+ item.handler = (options) => snapshot(async () => {
60
190
  const currentQueues = this.asyncLocalStorage.getStore() ?? [];
61
191
  return await this.asyncLocalStorage.run([...currentQueues, queue], async () => {
62
- return await handler();
192
+ return await handler(options);
63
193
  });
64
194
  })
65
195
 
@@ -114,16 +244,31 @@ export class QueueHandler {
114
244
 
115
245
  q.runCount += 1
116
246
  // console.log("[QUEUE] ("+q.runCount+"/"+q.parallel+") Executing "+queue+" ("+q.items.length+" remaining)")
247
+ const abort = q.createAbortSignal()
117
248
 
118
249
  try {
119
- next.resolve(await next.handler())
250
+ next.resolve(await next.handler({
251
+ abort
252
+ }))
120
253
  // console.log("[QUEUE] ("+(q.runCount-1)+"/"+q.parallel+") Resolved "+queue+" ("+q.items.length+" remaining)")
121
254
  } catch (e) {
122
255
  next.reject(e)
123
256
  if (STAMHOOFD.environment !== 'test') {
124
- console.log("[QUEUE] ("+(q.runCount-1)+"/"+q.parallel+") Rejected "+queue+" ("+q.items.length+" remaining)")
125
- console.error(e)
257
+ if (!isDebouncedError(e) && !isCanceledError(e) && !isAbortedError(e)) {
258
+ console.log("[QUEUE] ("+(q.runCount-1)+"/"+q.parallel+") Rejected "+queue+" ("+q.items.length+" remaining)")
259
+ console.error(e)
260
+ } else {
261
+ if (isDebouncedError(e)) {
262
+ console.log("[QUEUE] ("+(q.runCount-1)+"/"+q.parallel+") Debounced "+queue+" ("+q.items.length+" remaining)")
263
+ } else if (isCanceledError(e)) {
264
+ console.log("[QUEUE] ("+(q.runCount-1)+"/"+q.parallel+") Canceled "+queue+" ("+q.items.length+" remaining)")
265
+ } else if (isAbortedError(e)) {
266
+ console.log("[QUEUE] ("+(q.runCount-1)+"/"+q.parallel+") Aborted "+queue+" ("+q.items.length+" remaining)")
267
+ }
268
+ }
126
269
  }
270
+ } finally {
271
+ q.removeAbortSignal(abort)
127
272
  }
128
273
 
129
274
  q.runCount -= 1