@vida-global/core 2.0.1 → 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.
- package/README.md +8 -7
- package/config/newrelic-config.js +2 -0
- package/lib/activeRecord/README.md +220 -111
- package/lib/http/README.md +84 -19
- package/lib/jobQueue/README.md +139 -46
- package/lib/logger/README.md +48 -5
- package/lib/server/README.md +282 -129
- package/lib/server/controllerImporter.js +1 -1
- package/lib/server/controllerMixins/callbacks.js +132 -0
- package/lib/server/controllerMixins/documentation.js +36 -0
- package/lib/server/controllerMixins/renderer.js +315 -0
- package/lib/server/controllerMixins/requestDetails.js +82 -0
- package/lib/server/controllerMixins/routing.js +108 -0
- package/lib/server/controllerMixins/validations.js +114 -0
- package/lib/server/server.js +2 -0
- package/lib/server/serverController.js +68 -672
- package/package.json +8 -5
- package/test/server/serverController.test.js +822 -22
- package/test/apm/agent.test.js +0 -56
- package/test/apm/utils.test.js +0 -121
package/lib/jobQueue/README.md
CHANGED
|
@@ -1,63 +1,156 @@
|
|
|
1
1
|
# JobQueue
|
|
2
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
static
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
To be added in a future release.
|
|
156
|
+
- **Rate limiting.** Pre-job rate limiting is not yet wired up.
|
package/lib/logger/README.md
CHANGED
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
# Logger
|
|
2
|
-
A
|
|
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
|
-
|
|
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
|
-
|
|
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`).
|