@vida-global/core 2.0.0 → 2.0.3

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.
@@ -1,63 +1,156 @@
1
1
  # JobQueue
2
- The `JobQueue` is a general purpose wrapper around an `BullMQ` job queue.
2
+ `JobQueue` is a Vida wrapper around [BullMQ](https://docs.bullmq.io/). It standardizes how jobs are defined, queued, and worked off so that any Vida service can run background work without re-implementing the plumbing.
3
3
 
4
-
5
- ## USAGE
6
- The Node Job Queue has 3 key components: `Queues`, `Workers`, and `Jobs`.
7
4
 
8
-
9
- ### QUEUES ###
10
- A `Queue` object is used by a `Job` to add the `Job` and its associated arguments to the Redis queue for a `Worker` in a different process to dequeue and process. The work of a `Queue` is largely encapsulated by the `Job`, so you will rarely need to interact directly with a `Queue`. The exception is when ending a process. In order for your script to exit, you'll need to close any open `Queues`.
5
+ ## Setup
11
6
 
12
- Closing a queue looks like this:
13
- ```
14
- const queue = getQueue(myQueueName);
15
- await queue.close();
7
+ `Queues` and `Workers` both connect to Redis through the same environment variables:
8
+
9
+ | Variable | Purpose |
10
+ |---|---|
11
+ | `REDIS_BULL_HOST` | Redis host. |
12
+ | `REDIS_BULL_PORT` | Redis port. |
13
+ | `REDIS_BULL_PASSWORD` | Redis password. |
14
+
15
+ Queue names are namespaced by `NODE_ENV` automatically, so a queue declared as `'myQueue'` runs as `myQueue-development` in development and `myQueue-production` in production. This keeps environments isolated when they share a Redis instance.
16
+
17
+
18
+ ## Core Concepts
19
+
20
+ There are three components:
21
+
22
+ - **Queue** — Redis-backed list of pending work. Jobs are added to a queue, workers pull from it.
23
+ - **Worker** — long-running process that listens on a queue and runs the matching job class for each entry.
24
+ - **Job** — a class that extends `AbstractJob` and implements `run`. The worker instantiates it per task.
25
+
26
+
27
+ ## Defining a job
28
+
29
+ Subclass `AbstractJob`, implement an async `run` method, and declare a `static queueName(...)` that returns the queue the job belongs to. Place files under `jobs/` at the repo root so the worker picks them up automatically.
30
+
31
+ ```js
32
+ const { JobQueue } = require('@vida-global/core');
33
+ const { AbstractJob } = JobQueue;
34
+
35
+ class MyJob extends AbstractJob {
36
+ async run(arg1, arg2) {
37
+ this.logger.info(`starting work on ${arg1}`);
38
+ // do work here
39
+
40
+ await this.updateProgress(50); // optional — reports to BullMQ
41
+ // ...
42
+ await this.updateProgress(100);
43
+ }
44
+
45
+ static queueName(arg1, arg2) {
46
+ return 'myQueue';
47
+ }
48
+ }
16
49
  ```
17
50
 
18
- Other less commonly needed `Queue` methods include:
51
+ Inside `run()`:
52
+
53
+ - `this.id` — UUID for the job.
54
+ - `this.logger` — child logger tagged with the job's id.
55
+ - `await this.updateProgress(value)` — pushes progress to BullMQ for observation.
56
+
57
+ Enqueue a job with `queueJob`:
58
+
59
+ ```js
60
+ await MyJob.queueJob(arg1, arg2);
19
61
  ```
20
- await queue.numQueuedJobs(); // returns the number of jobs in the queue
21
- await queue.getQueuedJobs(); // returns an array of job data representing jobs in the queue
22
- await queue.numActiveJobs(); // returns the number of jobs currently being worked on
23
- await queue.getActiveJobs(); // returns an array of job data representing jobs currently being worked on
24
- await queue.numFailedJobs(); // returns the failed jobs since the last failed jobs clear
25
- await queue.getFailedJobs(); // returns an array of job data representing jobs failed since the last failed jobs clear
26
- await queue.clearFailedJobs(); // removes all stored data about failed jobs. This should be done periodically to clear memory in Redis
27
- await queue.clearCompletedJobs(); // removes all stored data about completed jobs. This should be done periodically to clear memory in Redis
28
- await queue.clearQueuedJobs(); // removes all jobs from the queue
62
+
63
+
64
+ ## Retries and backoff
65
+
66
+ Failed jobs are retried with exponential backoff by default. Override the static getters on your job class to tune the behavior.
67
+
68
+ | Override | Default | Meaning |
69
+ |---|---|---|
70
+ | `static get numRetries()` | `3` | Attempts (including the first try). |
71
+ | `static get retryBackoffType()` | `'exponential'` | Backoff strategy passed to BullMQ. |
72
+ | `static get retryBackoffDelay()` | `2000` | Backoff delay in ms. |
73
+
74
+ ```js
75
+ class FlakyJob extends AbstractJob {
76
+ static get numRetries() { return 5; }
77
+ static get retryBackoffDelay() { return 5000; }
78
+ // ...
79
+ }
29
80
  ```
30
81
 
31
- ### WORKERS
32
- A `Worker` is what runs on the server side, listening to Redis for new jobs in its queue. Starting a worker running on a server is simple.
33
- ```
82
+
83
+ ## Running a worker
84
+
85
+ One worker per process; the worker auto-imports every job under `jobs/`.
86
+
87
+ ```js
88
+ const { JobQueue } = require('@vida-global/core');
89
+ const { Worker } = JobQueue;
90
+
34
91
  const worker = new Worker('myQueue');
35
92
  await worker.listen();
36
93
  ```
37
94
 
38
- ### JOBS
39
- A `Job` is the object that contains the code that will be run by the worker. All `Jobs` will subclass `AbstractJob` and implement the `run` and `queueName` methods. `Jobs` should be defined in the `jobs` directory at the top level of the repo. This will allow them to be auto registered by a worker.
40
- ```
41
- class MyJob extends AbstractJob {
42
- async run(arg1, arg2...) {
43
- // do work here
44
- }
45
-
46
- static queueName(arg1, arg2) {
47
- return 'myQueue';
48
- }
49
- }
50
- ```
51
- Once you've defined your job's `run` and `queueName` methods, you can add it to the queue by running `queueJob`
52
- ```
53
- await MyJob.queueJob(arg1, arg2...)
95
+ If `WORKER_QUEUE_NAME` is set in the environment, you can omit the argument and the worker will pick its queue up from there.
96
+
97
+ ### Default job fallback
98
+
99
+ When a queue entry arrives whose name doesn't match any registered job, the worker can fall back to a job that opts in:
100
+
101
+ ```js
102
+ class CatchAllJob extends AbstractJob {
103
+ static defaultFor = 'myQueue';
104
+
105
+ async run(payload) { /* ... */ }
106
+ }
107
+ ```
108
+
109
+
110
+ ## Concurrency
111
+
112
+ A single worker can process multiple jobs in parallel. Set the `WORKER_CONCURRENCY` environment variable to raise the limit (default `1`).
113
+
114
+ ```sh
115
+ WORKER_CONCURRENCY=10 node bin/worker.js
116
+ ```
117
+
118
+
119
+ ## Queue operations
120
+
121
+ Most of the time the queue work happens via `MyJob.queueJob(...)` and you never touch the queue directly. When you do — for monitoring, draining, or graceful shutdown — fetch it with `getQueue`.
122
+
123
+ ```js
124
+ const { JobQueue } = require('@vida-global/core');
125
+ const { getQueue } = JobQueue;
126
+
127
+ const queue = getQueue('myQueue');
128
+
129
+ await queue.close(); // close the connection (call before process exit)
130
+ await queue.pause(); // stop dispatching work
131
+ await queue.resume(); // resume after pause
132
+
133
+ await queue.numQueuedJobs(); // number of jobs waiting
134
+ await queue.getQueuedJobs(); // formatted snapshot of waiting jobs
135
+ await queue.numActiveJobs();
136
+ await queue.getActiveJobs();
137
+ await queue.numFailedJobs();
138
+ await queue.getFailedJobs();
139
+
140
+ await queue.clearFailedJobs(); // drop failed-job data (do this periodically)
141
+ await queue.clearCompletedJobs(); // drop completed-job data
142
+ await queue.clearQueuedJobs(); // drain all queued jobs
143
+
144
+ await queue.numWorkers(); // number of workers currently attached
145
+ ```
146
+
147
+ Each `get*Jobs` result entry has this shape:
148
+
149
+ ```js
150
+ { queueName, name, args, id, attemptsMade, attemptsStarted, progress, failedReason }
54
151
  ```
55
152
 
56
- ## CONCURRENCY
57
- While there should only be one `Worker` per process, each worker can process multiple jobs in parallel. Concurrency settings are defined through environment variables.
58
153
 
59
- ### SIMPLE CONCURRENCY
60
- In order to limit the number of jobs that a worker can run in parallel, simply set the `WORKER_CONCURRENCY` environment variable. If that variable is not set, concurrency will default to 1.
154
+ ## TODO
61
155
 
62
- ### RATE LIMITING
63
- To be added in a future release.
156
+ - **Rate limiting.** Pre-job rate limiting is not yet wired up.
@@ -1,8 +1,41 @@
1
1
  # Logger
2
- A simple logger that support multiple scopes and log levels. Set the `LOG_LEVEL` environment variable to determine what log levels are output.
2
+ A scoped logger with per-request child loggers and environment-driven verbosity. Every log line carries a timestamp, level, the chain of scopes, and (when present) an id, so output stays readable when many requests interleave.
3
3
 
4
- ## Scopes
4
+
5
+ ## Setup
6
+
7
+ Set the `LOG_LEVEL` environment variable to control the minimum level that is printed. Anything at or above the configured level is logged; anything below is suppressed.
8
+
9
+ ```sh
10
+ LOG_LEVEL=debug node server.js
11
+ ```
12
+
13
+ Level priority (lowest → highest): `silly` < `debug` < `verbose` < `info` < `warn` < `error`. The default is `info`.
14
+
15
+
16
+ ## Core Concepts
17
+
18
+ The root `logger` is a `Logger` instance. Call `addScope(name)` to attach a named child scope; that scope behaves like a logger of its own (with its own scope tag in the output) and is exposed as a property on its parent. Scopes nest — adding a scope to a scope produces a deeper tag. Scopes and child loggers are cached internally, so repeated `addScope` / `child` calls with the same key return the same instance.
19
+
20
+
21
+ ## Usage
22
+
23
+ ### Levels
24
+
25
+ ```js
26
+ const { logger } = require('@vida-global/core');
27
+
28
+ logger.silly('rarely useful detail');
29
+ logger.debug('developer-focused trace');
30
+ logger.verbose('one level above debug');
31
+ logger.info('the default');
32
+ logger.warn('something deserves attention');
33
+ logger.error('something is broken');
5
34
  ```
35
+
36
+ ### Scopes
37
+
38
+ ```js
6
39
  logger.addScope('foo');
7
40
  logger.addScope('bar');
8
41
 
@@ -22,12 +55,22 @@ logger.bar.error('my log');
22
55
  // [2026-04-30 16:16:12.797 -0700][ERROR][BAR] my log
23
56
  ```
24
57
 
25
- ## IDs
26
- ```
58
+ ### Child loggers with IDs
59
+
60
+ `child(id)` returns a logger that tags every line with the given id. This is the standard pattern for per-request logging — the server framework already calls `logger.child(requestId)` for every controller instance.
61
+
62
+ ```js
27
63
  logger.addScope('http');
28
64
  const requestId = 123;
29
65
  const requestLogger = logger.http.child(requestId);
30
66
 
31
67
  requestLogger.debug('my log');
32
68
  // [2026-04-30 16:16:12.797 -0700][DEBUG][HTTP][123] my log
33
- ``
69
+ ```
70
+
71
+
72
+ ## Advanced
73
+
74
+ - `addScope` is idempotent: calling it with a name that's already attached returns silently.
75
+ - Scoped and child loggers are memoized by `${scopes.join('|')}:${id}`, so repeated lookups return the same instance — there's no cost to calling `logger.http.child(id)` on every log statement.
76
+ - Colorization, formatting, and transports are configured by overriding the corresponding getters on a `Logger` subclass (`formatters`, `level`, `transports`).