@arkstack/queue 0.18.3 → 0.18.4

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.
@@ -145,6 +145,13 @@ var QueueContract = class {
145
145
  this.connectionName = name;
146
146
  return this;
147
147
  }
148
+ /**
149
+ * The queue this connection works when none is named. Drivers with a
150
+ * configurable queue override it.
151
+ */
152
+ getDefaultQueue() {
153
+ return "default";
154
+ }
148
155
  };
149
156
  //#endregion
150
157
  //#region src/drivers/DatabaseQueue.ts
@@ -167,6 +174,10 @@ var DatabaseQueue = class extends QueueContract {
167
174
  this.options = options;
168
175
  }
169
176
  get defaultQueue() {
177
+ return this.getDefaultQueue();
178
+ }
179
+ /** The configured queue this connection works when none is named. */
180
+ getDefaultQueue() {
170
181
  return this.options.queue ?? "default";
171
182
  }
172
183
  async database() {
@@ -263,6 +274,10 @@ var RedisQueue = class extends QueueContract {
263
274
  this.options = options;
264
275
  }
265
276
  get defaultQueue() {
277
+ return this.getDefaultQueue();
278
+ }
279
+ /** The configured queue this connection works when none is named. */
280
+ getDefaultQueue() {
266
281
  return this.options.queue ?? "default";
267
282
  }
268
283
  get retryAfter() {
@@ -415,6 +430,7 @@ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
415
430
  var Worker = class {
416
431
  connection;
417
432
  shouldStop = false;
433
+ handlers = {};
418
434
  constructor(connection) {
419
435
  this.connection = connection;
420
436
  }
@@ -423,6 +439,16 @@ var Worker = class {
423
439
  this.shouldStop = true;
424
440
  }
425
441
  /**
442
+ * Observe what the worker does with each job. Handlers merge, so they can be
443
+ * registered in more than one call.
444
+ *
445
+ * @param handlers The callbacks to add.
446
+ */
447
+ on(handlers) {
448
+ Object.assign(this.handlers, handlers);
449
+ return this;
450
+ }
451
+ /**
426
452
  * Pop and process the next job. Returns `true` if a job was processed,
427
453
  * `false` when the queue was empty.
428
454
  */
@@ -437,8 +463,10 @@ var Worker = class {
437
463
  */
438
464
  async process(job) {
439
465
  try {
466
+ await this.handlers.onProcessing?.(job);
440
467
  await (await resolveJob(job.payload)).handle();
441
468
  await job.delete();
469
+ await this.handlers.onProcessed?.(job);
442
470
  } catch (error) {
443
471
  await this.handleFailure(job, error);
444
472
  }
@@ -469,9 +497,11 @@ var Worker = class {
469
497
  await (await resolveJob(job.payload)).failed?.(error);
470
498
  } catch {}
471
499
  await job.delete();
500
+ await this.handlers.onFailed?.(job, error, false);
472
501
  return;
473
502
  }
474
503
  await job.release(job.backoff());
504
+ await this.handlers.onFailed?.(job, error, true);
475
505
  }
476
506
  };
477
507
  //#endregion
@@ -1,4 +1,4 @@
1
- import { t as Queue } from "../QueueManager-C1GIAwK2.js";
1
+ import { t as Queue } from "../QueueManager-Dm9FLISk.js";
2
2
  import { Command } from "@h3ravel/musket";
3
3
  import { bootArkorm } from "@arkstack/database";
4
4
  //#region src/commands/QueueClearCommand.ts
@@ -7,6 +7,20 @@ declare class QueueWorkCommand extends Command {
7
7
  protected signature: string;
8
8
  protected description: string;
9
9
  handle(): Promise<void>;
10
+ /**
11
+ * Read a multi-word flag. Musket hands over the parsed options camelCased,
12
+ * so `--stop-when-empty` arrives as `stopWhenEmpty`; the kebab-case name is
13
+ * accepted too so the flag can't go quietly missing again.
14
+ *
15
+ * @param camel The camelCase key.
16
+ * @param kebab The flag as written in the signature.
17
+ */
18
+ private flag;
19
+ /**
20
+ * Register the application's job classes with `@arkstack/jobs` when it is
21
+ * installed. Without it a payload cannot be turned back into a job.
22
+ */
23
+ private registerJobs;
10
24
  }
11
25
  //#endregion
12
26
  export { QueueWorkCommand };
@@ -1,7 +1,9 @@
1
- import { t as Queue } from "../QueueManager-C1GIAwK2.js";
1
+ import { r as Worker, t as Queue } from "../QueueManager-Dm9FLISk.js";
2
2
  import { Command } from "@h3ravel/musket";
3
3
  import { bootArkorm } from "@arkstack/database";
4
4
  //#region src/commands/QueueWorkCommand.ts
5
+ /** The message of a thrown value, whatever it is. */
6
+ const reason = (error) => error instanceof Error ? error.message : String(error);
5
7
  /**
6
8
  * Process jobs from a queue connection.
7
9
  */
@@ -16,25 +18,54 @@ var QueueWorkCommand = class extends Command {
16
18
  `;
17
19
  description = "Start processing jobs on the queue as a daemon.";
18
20
  async handle() {
19
- const connection = this.argument("connection");
20
- const worker = Queue.worker(connection);
21
- const queue = this.option("queue");
22
- if (this.option("once")) {
23
- const handled = await worker.runNextJob(queue);
24
- this.info(handled ? "Processed one job." : "No jobs available.");
25
- return;
26
- }
27
- this.info(`Processing jobs from [${connection ?? "default"}] connection.`);
21
+ const connection = Queue.connection(this.argument("connection"));
22
+ const worker = new Worker(connection).on({
23
+ onProcessed: (job) => {
24
+ this.success(`Processed: ${job.name()}`);
25
+ },
26
+ onFailed: (job, error, released) => {
27
+ this.error(released ? `Failed (attempt ${job.attempts()}, retrying): ${job.name()} — ${reason(error)}` : `Failed permanently: ${job.name()} — ${reason(error)}`);
28
+ }
29
+ });
30
+ const queue = this.option("queue") ?? connection.getDefaultQueue();
28
31
  try {
29
32
  bootArkorm();
30
33
  } catch {}
34
+ await this.registerJobs();
35
+ if (this.option("once")) {
36
+ if (!await worker.runNextJob(queue)) this.info("No jobs available.");
37
+ return;
38
+ }
39
+ this.info(`Processing jobs from the [${connection.getConnectionName()}] connection on the [${queue}] queue.`);
31
40
  await worker.daemon({
32
41
  queue,
33
42
  sleep: Number(this.option("sleep") ?? 3),
34
- maxJobs: Number(this.option("max-jobs") ?? 0),
35
- stopWhenEmpty: Boolean(this.option("stop-when-empty"))
43
+ maxJobs: Number(this.flag("maxJobs", "max-jobs") ?? 0),
44
+ stopWhenEmpty: Boolean(this.flag("stopWhenEmpty", "stop-when-empty"))
36
45
  });
37
46
  }
47
+ /**
48
+ * Read a multi-word flag. Musket hands over the parsed options camelCased,
49
+ * so `--stop-when-empty` arrives as `stopWhenEmpty`; the kebab-case name is
50
+ * accepted too so the flag can't go quietly missing again.
51
+ *
52
+ * @param camel The camelCase key.
53
+ * @param kebab The flag as written in the signature.
54
+ */
55
+ flag(camel, kebab) {
56
+ return this.option(camel) ?? this.option(kebab);
57
+ }
58
+ /**
59
+ * Register the application's job classes with `@arkstack/jobs` when it is
60
+ * installed. Without it a payload cannot be turned back into a job.
61
+ */
62
+ async registerJobs() {
63
+ try {
64
+ const { loadJobs } = await import("@arkstack/jobs");
65
+ const names = await loadJobs();
66
+ if (names.length) this.line(`Loaded ${names.length} job class(es).`);
67
+ } catch {}
68
+ }
38
69
  };
39
70
  //#endregion
40
71
  export { QueueWorkCommand };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,11 @@ declare abstract class QueueContract {
67
67
  /** The name this connection was resolved under. */
68
68
  getConnectionName(): string;
69
69
  setConnectionName(name: string): this;
70
+ /**
71
+ * The queue this connection works when none is named. Drivers with a
72
+ * configurable queue override it.
73
+ */
74
+ getDefaultQueue(): string;
70
75
  /**
71
76
  * Push a job onto the queue. Returns the job id.
72
77
  */
@@ -241,6 +246,22 @@ interface WorkerOptions {
241
246
  /** Stop the daemon as soon as the queue drains. */
242
247
  stopWhenEmpty?: boolean;
243
248
  }
249
+ /**
250
+ * Observers for what a worker does with each job. A worker executes jobs the
251
+ * caller can't see — a dedicated worker process reports what happened through
252
+ * these, so a job that keeps failing isn't indistinguishable from an idle queue.
253
+ */
254
+ interface WorkerHandlers {
255
+ /** A job has been reserved and is about to run. */
256
+ onProcessing?: (job: Job) => void | Promise<void>;
257
+ /** A job completed and has been removed from the queue. */
258
+ onProcessed?: (job: Job) => void | Promise<void>;
259
+ /**
260
+ * A job threw. `released` is `true` when it goes back on the queue for
261
+ * another attempt, `false` when it has exhausted its tries and failed.
262
+ */
263
+ onFailed?: (job: Job, error: unknown, released: boolean) => void | Promise<void>;
264
+ }
244
265
  /**
245
266
  * Pulls jobs from a connection and executes them.
246
267
  *
@@ -251,9 +272,17 @@ interface WorkerOptions {
251
272
  declare class Worker {
252
273
  private readonly connection;
253
274
  private shouldStop;
275
+ private readonly handlers;
254
276
  constructor(connection: QueueContract);
255
277
  /** Signal a running {@link daemon} loop to stop after the current job. */
256
278
  stop(): void;
279
+ /**
280
+ * Observe what the worker does with each job. Handlers merge, so they can be
281
+ * registered in more than one call.
282
+ *
283
+ * @param handlers The callbacks to add.
284
+ */
285
+ on(handlers: WorkerHandlers): this;
257
286
  /**
258
287
  * Pop and process the next job. Returns `true` if a job was processed,
259
288
  * `false` when the queue was empty.
@@ -400,6 +429,8 @@ declare class DatabaseQueue extends QueueContract {
400
429
  private db?;
401
430
  constructor(options: DatabaseConnectionConfig);
402
431
  private get defaultQueue();
432
+ /** The configured queue this connection works when none is named. */
433
+ getDefaultQueue(): string;
403
434
  private database;
404
435
  push(job: Queueable, queue?: string): Promise<string>;
405
436
  pushRaw(payload: JobPayload, queue?: string, availableAt?: number): Promise<string>;
@@ -432,6 +463,8 @@ declare class RedisQueue extends QueueContract {
432
463
  private client?;
433
464
  constructor(options: RedisConnectionConfig);
434
465
  private get defaultQueue();
466
+ /** The configured queue this connection works when none is named. */
467
+ getDefaultQueue(): string;
435
468
  private get retryAfter();
436
469
  private key;
437
470
  private connection;
@@ -449,4 +482,4 @@ declare class RedisQueue extends QueueContract {
449
482
  private migrate;
450
483
  }
451
484
  //#endregion
452
- export { CustomQueueConnectionRegistry, DatabaseConnectionConfig, DatabaseFacade, DatabaseQueue, Job, JobHandlers, JobPayload, JobResolver, JobRow, JobSerializer, Query, Queue, QueueConfig, QueueConnectionConfig, QueueConnectionFactory, QueueContract, Queueable, RedisClient, RedisConnectionConfig, RedisQueue, SyncConnectionConfig, SyncQueue, Worker, WorkerOptions, configure, resetSerialization, resolveJob, serializeJob, setResolver, setSerializer };
485
+ export { CustomQueueConnectionRegistry, DatabaseConnectionConfig, DatabaseFacade, DatabaseQueue, Job, JobHandlers, JobPayload, JobResolver, JobRow, JobSerializer, Query, Queue, QueueConfig, QueueConnectionConfig, QueueConnectionFactory, QueueContract, Queueable, RedisClient, RedisConnectionConfig, RedisQueue, SyncConnectionConfig, SyncQueue, Worker, WorkerHandlers, WorkerOptions, configure, resetSerialization, resolveJob, serializeJob, setResolver, setSerializer };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as RedisQueue, c as Job, d as serializeJob, f as setResolver, i as SyncQueue, l as resetSerialization, n as configure, o as DatabaseQueue, p as setSerializer, r as Worker, s as QueueContract, t as Queue, u as resolveJob } from "./QueueManager-C1GIAwK2.js";
1
+ import { a as RedisQueue, c as Job, d as serializeJob, f as setResolver, i as SyncQueue, l as resetSerialization, n as configure, o as DatabaseQueue, p as setSerializer, r as Worker, s as QueueContract, t as Queue, u as resolveJob } from "./QueueManager-Dm9FLISk.js";
2
2
  export { DatabaseQueue, Job, Queue, QueueContract, RedisQueue, SyncQueue, Worker, configure, resetSerialization, resolveJob, serializeJob, setResolver, setSerializer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/queue",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "type": "module",
5
5
  "description": "Queue module for Arkstack, providing a driver based queue transport and worker for background processing.",
6
6
  "homepage": "https://arkstack.toneflix.net/guide/queue",
@@ -34,13 +34,13 @@
34
34
  "./package.json": "./package.json"
35
35
  },
36
36
  "dependencies": {
37
- "@arkstack/common": "^0.18.3"
37
+ "@arkstack/common": "^0.18.4"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@h3ravel/musket": "^2.2.13",
41
41
  "ioredis": "^5.4.1",
42
- "@arkstack/contract": "^0.18.3",
43
- "@arkstack/database": "^0.18.3"
42
+ "@arkstack/contract": "^0.18.4",
43
+ "@arkstack/database": "^0.18.4"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "@arkstack/database": {