@pauldeng/node-red-contrib-bullmq 1.0.2 → 2.0.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.
@@ -6,47 +6,93 @@ The package remains a single Node-RED module entry point, but BullMQ behavior is
6
6
 
7
7
  `bull-queue.js` registers:
8
8
 
9
- - `bull-queue-server`
10
- - `bull cmd`
11
- - `bull run`
12
- - `bull job`
13
- - `bull events`
14
- - `bull flow`
9
+ - `bullmq-queue-server`
10
+ - `bullmq cmd`
11
+ - `bullmq run`
12
+ - `bullmq job`
13
+ - `bullmq events`
14
+ - `bullmq flow`
15
15
 
16
16
  The runtime does Node-RED lifecycle work only: creating nodes, wiring input handlers, setting status, and closing resources.
17
17
 
18
+ ## Backends
19
+
20
+ BullMQ v6 reaches its datastore through `IQueueBackend`, and this package selects one per config node with `backend`. Absent or blank means Redis, matching a flow saved before the field existed.
21
+
22
+ The seam is a factory argument on the BullMQ constructors, and its position differs per class: `Queue(name, opts, factory)` and `QueueEvents(name, opts, factory)` take it third, `Worker(name, processor, opts, factory)` fourth, and `FlowProducer(opts, factory)` **second**. Redis is BullMQ's default, so the Redis path passes no factory at all; the PostgreSQL path passes `createPostgresBackend`. `setDefaultBackendFactory` is deliberately unused: a process-wide default would make two config nodes with different backends impossible.
23
+
24
+ What each backend owns is the asymmetry the rest of this document keeps returning to. On Redis this package creates the ioredis connections and hands them over, so it holds a raw client per owner. On PostgreSQL BullMQ owns everything — a `pg` pool per backend plus a dedicated `LISTEN` client for Worker and QueueEvents — so this package creates no connection, tracks the owner with no connection value, and has no raw handle to reach for. `pg` is loaded lazily by BullMQ while constructing the queue, which is why a missing install surfaces synchronously out of the constructor rather than through `waitUntilReady()`.
25
+
26
+ Three operations in BullMQ 6.3.1's PostgreSQL adapter throw `not implemented`: `trimEvents`, `removeDeprecatedPriorityKey`, and `paginate` outside a flow's `:dependencies`/`:processed` keys. None is on a path these nodes use. `publishEvent` accepts but ignores `maxEvents`, so PostgreSQL event rows are never trimmed — recorded in [CONNECTIONS.md](CONNECTIONS.md#what-postgresql-does-not-have) as a standing limitation rather than worked around here.
27
+
18
28
  ## Connections
19
29
 
20
- `bull-queue-server` owns queue name and Redis deployment config. It creates role-specific ioredis connections:
30
+ `bullmq-queue-server` owns queue name and connection config. On Redis it creates role-specific ioredis connections:
21
31
 
22
- - producer connections fail quickly with bounded retries;
23
- - worker and event connections use `maxRetriesPerRequest: null`;
32
+ - producer connections fail fast through `skipWaitingForReady: true` on the BullMQ owner plus `maxRetriesPerRequest: 1` on the socket. Without the first, BullMQ awaits a connection-ready promise that never settles while Redis is unreachable, so a `bullmq cmd` command hangs instead of erroring and the node never calls `done()`. The offline queue is deliberately left enabled so messages emitted during the brief connection window after a deploy are buffered;
33
+ - worker and event connections use `maxRetriesPerRequest: null` and keep the offline queue, because a consumer should wait for the connection to come back rather than fail;
34
+ - every role reconnects with exponential backoff between 1s and 20s, including Cluster and Sentinel discovery retries;
24
35
  - QueueEvents uses a dedicated connection;
25
36
  - Cluster and MemoryDB use `{bull}` by default as the BullMQ prefix.
26
37
 
27
- Connection and resource errors are reported on the consuming runtime node's status (`bull run`, `bull events`, `bull flow`). The shared producer connection and queue used by `bull cmd` report on the config node.
38
+ None of the above applies to PostgreSQL: readiness, retry, and offline-queue behavior are ioredis concepts, `skipWaitingForReady` is never read on that path, and there is no prefix. A PostgreSQL config node instead carries the pool size, schema, and migration switch described in [CONNECTIONS.md](CONNECTIONS.md#postgresql).
39
+
40
+ Each BullMQ owner (`Queue`, `Worker`, `QueueEvents`, or `FlowProducer`) is tracked with its owned ioredis connection, or with no connection at all on PostgreSQL. A runtime node releases its pair on redeploy; config-node shutdown closes independent pairs concurrently. See Shutdown below for how each owner/connection pair actually closes.
41
+
42
+ Connection and resource errors are reported on the consuming runtime node's status (`bullmq run`, `bullmq events`, `bullmq flow`). The config node owns the shared queue and its producer connection. Each `bullmq cmd` mirrors that shared queue's backend (`Queue.getBackend()`, BullMQ's `IQueueBackend`) on its visible status instead of reading the producer connection directly, while Queue errors report on the config node.
43
+
44
+ Secrets are read only from Node-RED credentials.
45
+
46
+ Queue retention defaults are attached directly to `Queue`. For a single tree, `bullmq flow` builds BullMQ `queuesOptions` for every queue name so the same defaults also reach `FlowProducer`. For a bulk array, whose BullMQ API accepts no options argument, it stamps the defaults onto each job's `opts`. Both paths preserve job-level overrides.
47
+
48
+ ## Shutdown
28
49
 
29
- Secrets are read from Node-RED credentials first, with legacy plain fields accepted only for backward compatibility.
50
+ Closing an owner/connection pair (`bull-queue.js`) uses a budget chosen from the resource ownership already recorded in the config node's `resources` map:
51
+
52
+ - A Redis owner with a ready companion ioredis connection gets `GRACEFUL_CLOSE_MS` (10 seconds). `Worker.close()` waits for in-flight jobs, and cutting that off abandons a running job to the stalled checker, which re-runs it and can eventually fail it for exceeding `maxStalledCount`.
53
+ - A Redis owner whose companion connection is not ready gets `CLOSE_GRACE_MS` (1 second), followed by the Redis force-disconnect fallback.
54
+ - A PostgreSQL owner has no companion connection because BullMQ owns its pool. It gets `POSTGRES_CLOSE_MS` (11 seconds), derived as `POSTGRES_CONNECTION_TIMEOUT_MS` (the 10-second `connectionTimeoutMillis` this package fixes in `lib/connections.js`) plus one second of scheduling margin, so raising that timeout cannot leave the budget short. PostgreSQL exposes no raw client that this package can safely force closed, so Node-RED must await pg's own timeout before reporting close complete.
55
+
56
+ Pairs close concurrently, so these budgets bound one resource rather than adding across all resources. They also avoid using the config node's shared `backendStatus`: that status belongs to the shared `Queue`, so a config node backing only a `bullmq run` node may never advance it.
57
+
58
+ Within that budget:
59
+
60
+ 1. The BullMQ owner's own graceful close (`Queue.close()` / `Worker.close()` / `QueueEvents.close()` / `FlowProducer.close()`). This alone succeeds whenever the datastore is reachable.
61
+ 2. If graceful close times out, force-disconnect and stop Worker lock-renewal/stalled-check timers (`forceDisconnect`).
62
+
63
+ Step 2 differs by backend:
64
+
65
+ - **Redis**: force-disconnect the backend's raw ioredis clients (`connection._client` / `blockingConnection._client`), behind an `IQueueBackend` capability check (`typeof resource.getBackend === "function"`). This escalation is deliberately tied to the exact BullMQ 6.3.1 pin: its public `disconnect()` awaits the same never-ready connection promise as `close()`, so calling it would spend a second grace period without improving shutdown. Re-evaluate the fallback whenever the BullMQ pin changes.
66
+ - **PostgreSQL**: no raw-force branch. Against a blackholed host, `PostgresConnection.close()` settles when its configured 10-second connection timeout expires; the 11-second PostgreSQL budget awaits that promise instead of returning while its socket is still active. Worker's lock-renewal and stalled-check timers remain Worker-owned and use the same cleanup path on both backends.
67
+
68
+ A raw ioredis connection (not a BullMQ owner) skips straight to a force-disconnect after its own bounded `quit()`/close attempt. Every step is best-effort — one step's error does not stop the ones after it — which is what keeps Node-RED shutdown and redeploy from hanging when the datastore is unreachable.
30
69
 
31
70
  ## Commands
32
71
 
33
- `lib/commands.js` maps `msg.cmd` to explicit BullMQ calls. It does not expose arbitrary method names. Legacy repeat commands call Job Scheduler APIs.
72
+ `lib/commands.js` maps `msg.cmd` to explicit BullMQ v6 calls. It does not expose arbitrary method names or compatibility aliases.
34
73
 
35
74
  ## Schedulers
36
75
 
37
- `lib/scheduler.js` translates `msg.jobopts.repeat.cron` to `repeat.pattern` and requires a deterministic scheduler id. Scheduler lookup/removal uses exact ids.
76
+ `lib/scheduler.js` serializes Job Scheduler metadata and requires an exact `msg.schedulerId`. Creation uses native `msg.repeat` and `msg.template` inputs.
38
77
 
39
78
  ## Workers And Acknowledgement
40
79
 
41
- `bull run` creates a BullMQ Worker.
80
+ `bullmq run` creates a BullMQ Worker.
42
81
 
43
82
  - Immediate mode sends a Node-RED message and completes the job immediately.
44
- - Manual mode creates an opaque `msg.bull.ackId` and waits for a downstream `bull job` node.
83
+ - Manual mode creates an opaque `msg.bull.ackId` and waits for a downstream `bullmq job` node.
84
+ - Every worker defaults `maxStartedAttempts` to 100 so repeated non-failing transitions cannot reactivate one job forever.
45
85
 
46
86
  The in-process acknowledgement registry (`lib/acknowledgements.js`) stores live jobs and promise settlement functions. Each entry self-removes when it settles (complete, fail, timeout, or run-node close), so the registry does not accumulate finished jobs. Lock tokens are never sent in messages.
47
87
 
88
+ ### Cancellation
89
+
90
+ `bullmq run` builds its processor with three arguments (`async (job, token, signal) => …`), which tells BullMQ to track a per-job `AbortController` for every job it runs, immediate or manual mode alike. `bullmq job`'s `cancelJob`/`cancelAllJobs` actions call `worker.cancelJob()`/`worker.cancelAllJobs()`, which abort that job's signal. `lib/acknowledgements.js` listens for the abort and fails the pending acknowledgement, so the worker's `await acknowledgement.entry.wait()` rejects and the job fails. BullMQ then applies the queue's normal attempts/backoff retry policy — cancellation only fails the current attempt, it does not remove the job.
91
+
92
+ Both actions require the acknowledgement behind `msg.bull.ackId`, so they only reach manual-mode jobs that have not already settled.
93
+
48
94
  ## Events And Flows
49
95
 
50
- `bull events` wraps QueueEvents and emits event messages with `msg.topic`, `msg.payload`, and `msg.bull` metadata.
96
+ `bullmq events` wraps QueueEvents and emits event messages with `msg.topic`, `msg.payload`, and `msg.bull` metadata.
51
97
 
52
- `bull flow` wraps FlowProducer and serializes the returned parent/child tree.
98
+ `bullmq flow` wraps FlowProducer and serializes the returned parent/child tree, or the array of trees returned by `addBulk`.
@@ -1,27 +1,26 @@
1
1
  # Change Workflow
2
2
 
3
+ Constraints that apply to every change live in [RULES.md](RULES.md). This file is the procedure.
4
+
3
5
  ## Behavior Changes
4
6
 
5
- 1. Read `GOAL.md` if present and the relevant docs in `docs/REFERENCE_MAP.md`.
7
+ 1. Read `GOAL.md` if present, then the owning file and test in [REFERENCE_MAP.md](REFERENCE_MAP.md).
6
8
  2. Add or update the smallest failing test.
7
9
  3. Run the focused test and confirm the expected failure.
8
10
  4. Implement the minimal change.
9
- 5. Run the focused test and then `npm test`.
10
- 6. Update README, node help, and docs when public behavior changes.
11
- 7. Record unsupported BullMQ behavior with a reason instead of silently omitting it.
11
+ 5. Run the focused test, then `npm test && npm run format:check`.
12
+ 6. Update the README, the node help text, and the relevant `docs/` file when public behavior changes.
12
13
 
13
14
  ## Connection Changes
14
15
 
15
- Update `docs/CONNECTIONS.md` and tests in `test/connections.test.js`. Do not pass arbitrary ioredis options through messages or editor fields.
16
+ Update [CONNECTIONS.md](CONNECTIONS.md) and `test/connections.test.js`. Run the Docker deployment matrix when it is available (see [TESTING.md](TESTING.md)); it covers the Redis topologies and both PostgreSQL fixtures.
17
+
18
+ A change that touches shared connection code has to be checked on both backends, because they divide ownership differently: Redis connections are created here, PostgreSQL connections are owned by BullMQ. `test/redis-characterization.test.js` exists to catch drift in the Redis descriptors and options and must pass untouched; `npm run test:integration` runs the backend-neutral flows against both stores.
16
19
 
17
20
  ## Scheduler Changes
18
21
 
19
- Update `test/scheduler.test.js`. Legacy repeat behavior must use exact scheduler ids and must not call deprecated repeatable-job APIs.
22
+ Update `test/scheduler.test.js`. Native Job Scheduler lookup and removal must keep using exact scheduler ids.
20
23
 
21
24
  ## Editor Changes
22
25
 
23
- Update `bull-queue.html`, static editor contract tests, and Playwright coverage. Credential fields must not export secrets in flow JSON.
24
-
25
- ## Security
26
-
27
- Never commit MemoryDB credentials, Redis passwords, private keys, or generated TLS private keys.
26
+ Update `bull-queue.html`, the static editor contract test, and Playwright coverage.
package/docs/COMMANDS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Command Reference
2
2
 
3
- `bull cmd` reads `msg.cmd`. It writes the command result to `msg.payload`.
3
+ `bullmq cmd` reads `msg.cmd` and writes the command result to `msg.payload`.
4
4
 
5
5
  ## Jobs
6
6
 
@@ -15,6 +15,8 @@
15
15
 
16
16
  `add` uses `msg.jobName`, `msg.jobData` or `msg.payload`, and `msg.jobopts`.
17
17
 
18
+ `add` and `addBulk` reject repeat options. Use `upsertJobScheduler` for repeating jobs.
19
+
18
20
  ## Delayed Jobs
19
21
 
20
22
  - `getDelayed`
@@ -24,6 +26,66 @@
24
26
 
25
27
  Add a delayed job with `msg.jobopts.delay`.
26
28
 
29
+ One one-off job, delayed by 10 seconds:
30
+
31
+ ```js
32
+ msg.cmd = "add";
33
+ msg.jobName = "delayed";
34
+ msg.jobData = { payload: "first" };
35
+ msg.jobopts = { delay: 10000, removeOnComplete: true };
36
+ return msg;
37
+ ```
38
+
39
+ A series of one-off jobs, each delayed from the time the batch is added:
40
+
41
+ ```js
42
+ msg.cmd = "addBulk";
43
+ msg.payload = [
44
+ {
45
+ name: "delayed-1",
46
+ data: { payload: "first" },
47
+ opts: { delay: 10000, removeOnComplete: true },
48
+ },
49
+ {
50
+ name: "delayed-2",
51
+ data: { payload: "second" },
52
+ opts: { delay: 20000, removeOnComplete: true },
53
+ },
54
+ {
55
+ name: "delayed-3",
56
+ data: { payload: "third" },
57
+ opts: { delay: 30000, removeOnComplete: true },
58
+ },
59
+ ];
60
+ return msg;
61
+ ```
62
+
63
+ These are ordinary jobs, not schedulers. Increasing delays make them eligible in sequence; worker availability and concurrency determine their actual start times.
64
+
65
+ For a series with exact date-times, convert each ISO-8601 timestamp to a delay when enqueueing:
66
+
67
+ ```js
68
+ const schedule = [
69
+ { at: "2030-01-01T09:00:00+11:00", payload: "first" },
70
+ { at: "2030-01-01T09:15:00+11:00", payload: "second" },
71
+ { at: "2030-01-01T09:30:00+11:00", payload: "third" },
72
+ ];
73
+ const now = Date.now();
74
+
75
+ msg.cmd = "addBulk";
76
+ msg.payload = schedule.map(({ at, payload }, index) => ({
77
+ name: `scheduled-${index + 1}`,
78
+ data: { payload, scheduledFor: at },
79
+ opts: {
80
+ delay: Math.max(0, Date.parse(at) - now),
81
+ removeOnComplete: true,
82
+ },
83
+ }));
84
+ return msg;
85
+ ```
86
+
87
+ Use timestamps with an explicit `Z` or numeric UTC offset. A past timestamp becomes immediately eligible. Delayed jobs are eligible at the requested time, but worker availability still determines when processing starts.
88
+
27
89
  ## Priorities
28
90
 
29
91
  - `getPrioritized`
@@ -41,35 +103,34 @@ Add deduplication options through `msg.jobopts.deduplication`.
41
103
 
42
104
  ## Job Schedulers
43
105
 
44
- Native:
45
-
46
106
  - `upsertJobScheduler`
47
107
  - `getJobScheduler`
48
108
  - `getJobSchedulers`
49
109
  - `getJobSchedulersCount`
50
110
  - `removeJobScheduler`
51
111
 
52
- Legacy aliases:
112
+ `upsertJobScheduler` uses `msg.schedulerId`, `msg.repeat`, and `msg.template`. Use `msg.repeat.pattern` for cron expressions and `msg.repeat.tz` for a timezone. Lookup and removal are exact-id based through `msg.schedulerId`.
53
113
 
54
- - `add` with `msg.jobopts.repeat`
55
- - `count`
56
- - `getRepeatableJobs`
57
- - `getRepeatableJobByKey`
58
- - `removeRepeatableByKey`
59
-
60
- Legacy lookup/removal is exact-id based.
114
+ Removed repeatable-job command names fail with an error naming their BullMQ v6 replacement; see [MIGRATION.md](MIGRATION.md).
61
115
 
62
116
  ## Queue Administration
63
117
 
64
118
  - `getJobCounts`
65
119
  - `pause`
66
120
  - `resume`
121
+ - `isPaused`
122
+ - `isMaxed`
67
123
  - `drain`
68
124
  - `clean`
69
125
  - `stopAndRemoveAllJobs`
126
+ - `getVersion`
70
127
 
71
128
  `stopAndRemoveAllJobs` removes schedulers, drains waiting/delayed jobs, and cleans inactive states. It does not claim to safely remove active jobs.
72
129
 
130
+ `getJobCounts` no longer includes a `paused` key in its result: BullMQ v6 removed the `paused` job state, so a paused queue's jobs count as `waiting`. Use `isPaused` to check whether the queue itself is paused.
131
+
132
+ `isPaused` and `isMaxed` each return a boolean for the queue's current state. `getVersion` returns the BullMQ version recorded against this queue in the backing store.
133
+
73
134
  ## Concurrency And Rate Limits
74
135
 
75
136
  - `setGlobalConcurrency`
@@ -1,18 +1,73 @@
1
1
  # Connection Guide
2
2
 
3
+ BullMQ v6 stores a queue in either Redis or PostgreSQL. Each `bullmq-queue-server` config node picks one with **Backend**; everything below the Redis heading applies to the Redis backend only, and the PostgreSQL section states what changes.
4
+
3
5
  ## Standalone Redis
4
6
 
5
7
  Use deployment `single`, host, port, optional database, optional username/password, and optional TLS.
6
8
 
7
- Producer commands use bounded retries so Node-RED input handlers fail instead of hanging forever. Workers and QueueEvents use persistent retry behavior required by BullMQ.
9
+ Producer commands fail fast so a Node-RED input handler errors instead of hanging forever: the BullMQ owner sets `skipWaitingForReady: true` and the socket keeps `maxRetriesPerRequest: 1`, which rejects in about a second when Redis is down. Workers and QueueEvents use the persistent retry behavior BullMQ requires (`maxRetriesPerRequest: null`).
10
+
11
+ The offline queue stays enabled for producers too, which departs from BullMQ's production guide. Disabling it does fail faster, but it also rejects messages emitted during the brief connection window after a Node-RED deploy.
12
+
13
+ Every data connection reconnects with exponential backoff floored at 1s and capped at 20s, which is what [BullMQ's production guide](https://docs.bullmq.io/guide/going-to-production) recommends. Cluster discovery uses the same range through `clusterRetryStrategy`, and Sentinel discovery uses it through `sentinelRetryStrategy`.
14
+
15
+ ## PostgreSQL
16
+
17
+ Select backend `postgres`. The connection uses **Host**, **Port** (5432), **Database Name**, **Username**, the password credential, and three PostgreSQL-only fields: **Schema**, **Pool Max**, and **Migrations**. BullMQ owns every PostgreSQL connection — a pool per backend plus one dedicated `LISTEN` client for each Worker and QueueEvents backend — so this package creates none of its own on that path, and there is no raw client to reach for.
18
+
19
+ A blank **Username** or **Database Name** is not "no value": node-postgres falls back to the `PGUSER`/`PGDATABASE` environment variables and then to the operating-system user name, so a blank field takes its value from the Node-RED process's environment. Set both explicitly unless that fallback is deliberate.
20
+
21
+ `pg` is an optional peer dependency and is not installed with this package. Install it separately (`npm install pg`) before selecting the backend. BullMQ lazily requires it while constructing a queue, so a missing install is reported once per config node on first use, naming the command to run, rather than at load.
22
+
23
+ PostgreSQL 13 is the floor BullMQ enforces and 14 is what it recommends; an older server is rejected with its version named. The server version is checked on connect, so a downgrade is reported rather than discovered mid-job.
24
+
25
+ ### Schema
26
+
27
+ BullMQ's tables live in a schema, `bullmq` by default. Separate schemas keep independent queue sets in one database, and the schema name is validated before any I/O — an invalid name throws out of the queue constructor rather than reaching the server.
28
+
29
+ ### Migrations
30
+
31
+ **Migrations** on (the default) runs BullMQ's migrator when the connection is first established, which issues `CREATE SCHEMA IF NOT EXISTS` and brings the schema to the version this BullMQ release expects. It is safe with several queues, workers, and event listeners starting at once: the migrator takes a PostgreSQL advisory lock, so concurrent starts converge on one migration rather than racing.
32
+
33
+ Turn it off where the database user is not permitted to change the schema, and apply BullMQ's migrations out of band. A queue pointed at a database whose schema is missing or outdated then reports that as an actionable error naming the schema, not a stack trace, and the node stays usable.
34
+
35
+ ### Pool sizing
36
+
37
+ **Pool Max** is the maximum connections in _each_ pool, defaulting to 2. Blank means the default; `0` is rejected, because a pool that can never hand out a connection cannot run a queue.
38
+
39
+ There is one pool per BullMQ resource, not one per config node. BullMQ builds a fresh pool for every `Queue`, `Worker`, `QueueEvents`, and `FlowProducer`; Worker and QueueEvents also establish one dedicated `LISTEN` connection outside their pools. A config node feeding one `bullmq cmd`, one `bullmq run`, one `bullmq events`, and one `bullmq flow` therefore costs up to 4 × **Pool Max** + 2 server connections — 10 at the default.
40
+
41
+ Unlike Redis, PostgreSQL has a hard server-wide ceiling — `max_connections`, commonly 100, shared with every other client. Multiply the figure above by every config node and every Node-RED instance pointed at the database before assuming headroom. Raise `max_connections`, or put a pooler in front, rather than guessing.
42
+
43
+ node-postgres applies the same 10-second connection timeout to waiting for a free pooled connection as it does to opening a new one. A saturated pool can therefore fail an operation with `timeout exceeded when trying to connect` rather than merely running slower. Worker **Concurrency** does not require a one-to-one pool size: each database operation releases its client. Start with the default, raise **Pool Max** only when observed pool waits justify it, then check the total against `max_connections`.
44
+
45
+ ### TLS
46
+
47
+ The shared TLS fields apply: enabling **TLS** builds the `ssl` object node-postgres receives, with the CA, client certificate, client key, and verification settings. One difference from Redis: node-postgres derives the TLS server name from the connection host when that host is a hostname, so **TLS Server Name** takes effect only when **Host** is a literal IP address.
48
+
49
+ ### What PostgreSQL does not have
50
+
51
+ - No Cluster and no Sentinel. Those are Redis topologies; **Deployment** is hidden for PostgreSQL, and high availability is the database's own concern.
52
+ - No key prefix. `prefix` is a Redis key-namespacing concept and is not sent, so the hash-tag rules below do not apply.
53
+ - No raw client access, which is why shutdown differs — see the PostgreSQL step in [ARCHITECTURE.md](ARCHITECTURE.md#shutdown).
54
+ - Three operations in BullMQ 6.3.1's PostgreSQL adapter throw `operation '...' is not implemented yet`: `trimEvents`, `removeDeprecatedPriorityKey`, and `paginate` for anything other than a flow's `:dependencies` and `:processed` keys. None is on a path these nodes use today.
55
+ - Event rows are never trimmed. The adapter's `publishEvent` ignores the `maxEvents` argument, so the events table grows for as long as the queue is used. Prune it out of band if a flow leans on `bullmq events`.
56
+ - BullMQ's own PostgreSQL documentation reports lower job-processing throughput than Redis, with `addBulk` close to parity. Treat Redis as the faster backend for high job rates and PostgreSQL as the one that removes a second datastore from the deployment.
57
+
58
+ ## Production Redis Configuration
59
+
60
+ Redis must use `maxmemory-policy=noeviction`; evicting arbitrary BullMQ keys can corrupt queue behavior. Redis data must also be durable. For self-managed Redis, BullMQ recommends Append Only File (AOF) persistence, commonly with writes flushed once per second. For managed Redis, enable the provider's durable persistence and backup features appropriate to the job-loss tolerance of the deployment.
8
61
 
9
62
  ## Redis Cluster
10
63
 
11
- Use deployment `cluster` and provide startup nodes as comma or newline separated `host:port` values.
64
+ Use deployment `cluster` and provide startup nodes as a comma- or newline-separated endpoint list.
12
65
 
13
- Cluster auth and TLS are applied through ioredis `redisOptions`. The runtime sets a DNS lookup passthrough for TLS-enabled cluster discovery.
66
+ Cluster auth and TLS are applied through ioredis `redisOptions`. The runtime always sets a DNS lookup passthrough for cluster discovery, including non-TLS deployments.
14
67
 
15
- The BullMQ prefix must contain a Redis hash tag, normally `{bull}`. Untagged Cluster prefixes are rejected to prevent `CROSSSLOT` failures.
68
+ The BullMQ prefix must contain a Redis hash tag. Untagged Cluster prefixes are rejected to prevent `CROSSSLOT` failures.
69
+
70
+ `{bull}` is the default. Independent queues may use different tagged prefixes — `{orders}`, `{emails}` — to spread load across cluster nodes. Prefixes used in one `bullmq flow` tree or `FlowProducer.addBulk` batch must instead contain the same Redis hash tag because BullMQ updates those queues atomically and all involved keys must share a slot. Exact prefixes may differ, such as `{flow}:orders` and `{flow}:emails`; each worker must use the exact prefix assigned to its queue in the flow.
16
71
 
17
72
  ## AWS MemoryDB
18
73
 
@@ -41,14 +96,26 @@ Use deployment `sentinel` and configure:
41
96
 
42
97
  Sentinel authentication is separate from Redis data-node authentication.
43
98
 
99
+ ## Cluster And Sentinel Endpoint Lists
100
+
101
+ Cluster startup nodes and Sentinel discovery lists accept host names, `host:port`, bare IPv6 with the default port, bracketed IPv6 such as `[2001:db8::1]:6379`, and `redis://` or `rediss://` URLs. Explicit URL schemes must agree with the applicable TLS setting: Cluster endpoints use `tls`, and Sentinel discovery endpoints use `sentinelTls`.
102
+
103
+ URL credentials are rejected. Store Redis and Sentinel usernames/passwords in the config node credential fields instead of embedding them in an endpoint.
104
+
44
105
  ## TLS
45
106
 
46
- TLS options:
107
+ TLS behavior and options:
47
108
 
48
- - verify unauthorized certificates by default;
109
+ - rejects unauthorized certificates by default;
49
110
  - optional CA;
50
111
  - optional client certificate;
51
112
  - optional client private key;
52
113
  - optional server name.
53
114
 
54
- Disable verification only when the Redis deployment cannot be configured with a trusted CA and the risk is understood.
115
+ These fields serve both backends. Disable verification only when the deployment cannot be configured with a trusted CA and the risk is understood.
116
+
117
+ ## Secrets And Imported Flows
118
+
119
+ Passwords, CA data, client certificates, and private keys are read only from Node-RED credentials.
120
+
121
+ BullMQ stores job names, payloads, results, and failure details in clear text, in Redis or in PostgreSQL alike. Avoid sensitive job data; when it is unavoidable, encrypt sensitive fields before sending the job to `bullmq cmd` or `bullmq flow` and decrypt them only in a trusted worker path.
package/docs/MIGRATION.md CHANGED
@@ -1,37 +1,71 @@
1
- # Migration From Bull v4
1
+ # Migration Guide
2
2
 
3
- ## What Stays Compatible
3
+ Package 2.0.0 is a breaking BullMQ v6 release. It does not register the old Bull node types or accept the old command, job-id, or repeatable-job aliases. Existing Bull v4 and BullMQ v5 queue data is not migrated.
4
4
 
5
- - Node types `bull-queue-server`, `bull cmd`, and `bull run`.
6
- - Message-driven `msg.cmd` command dispatch.
7
- - `msg.payload` compatibility for added jobs and worker output.
8
- - `msg.jobopts.repeat.cron` for scheduled jobs.
5
+ ## Before Upgrading
9
6
 
10
- ## What Changes
7
+ - Drain or otherwise account for Bull v4 jobs.
8
+ - While still running the previous BullMQ package version, remove its repeatable-job definitions.
9
+ - Export flows before changing node types and message shapes.
11
10
 
12
- - Runtime dependency is BullMQ 5.80.2.
13
- - `bull` and `sprintf-js` are removed.
14
- - Repeatable jobs use BullMQ Job Schedulers.
15
- - Scheduled jobs require a stable scheduler id.
16
- - `bull run` uses BullMQ Worker instead of Bull v4 `queue.process`.
11
+ Bull and BullMQ do not provide a supported Redis data migration contract. Do not assume existing delayed, waiting, active, completed, or repeatable keys are usable after this upgrade.
17
12
 
18
- ## Repeat Jobs
13
+ ## Flow Changes
19
14
 
20
- Legacy:
15
+ Rename every node type:
16
+
17
+ | Old type | BullMQ v6 type |
18
+ | ------------------- | --------------------- |
19
+ | `bull-queue-server` | `bullmq-queue-server` |
20
+ | `bull cmd` | `bullmq cmd` |
21
+ | `bull run` | `bullmq run` |
22
+ | `bull job` | `bullmq job` |
23
+ | `bull events` | `bullmq events` |
24
+ | `bull flow` | `bullmq flow` |
25
+
26
+ Replace `msg.command` with `msg.cmd` and `msg.jobid` with `msg.jobId`; the removed fields now produce explicit errors. Removed repeatable aliases have these native replacements:
27
+
28
+ | Removed command | BullMQ v6 command |
29
+ | ------------------------ | ----------------------- |
30
+ | `getRepeatableJobs` | `getJobSchedulers` |
31
+ | `count` | `getJobSchedulersCount` |
32
+ | `getRepeatableJobByKey` | `getJobScheduler` |
33
+ | `removeRepeatableByKey` | `removeJobScheduler` |
34
+ | `add` with `opts.repeat` | `upsertJobScheduler` |
35
+
36
+ Job Scheduler lookup and removal require the exact id in `msg.schedulerId`.
37
+
38
+ ## Scheduler Shape
39
+
40
+ Replace the old repeat shape:
21
41
 
22
42
  ```js
43
+ msg.cmd = "add";
23
44
  msg.jobopts = {
24
- jobId: msg.payload,
25
- repeat: { cron: "30 9,19,29,39,49,59 * * * *" },
45
+ jobId: "heartbeat",
46
+ repeat: { cron: "*/1 * * * *", utc: true },
47
+ };
48
+ ```
49
+
50
+ with BullMQ v6 inputs:
51
+
52
+ ```js
53
+ msg.cmd = "upsertJobScheduler";
54
+ msg.schedulerId = "heartbeat";
55
+ msg.repeat = { pattern: "*/1 * * * *", tz: "UTC" };
56
+ msg.template = {
57
+ name: "heartbeat",
58
+ data: { payload: "scheduled heartbeat" },
26
59
  };
27
60
  ```
28
61
 
29
- Runtime translation:
62
+ Use `pattern`, not `cron`, and `tz`, not `utc`.
30
63
 
31
- - scheduler id: `msg.schedulerId` or `msg.jobopts.jobId`;
32
- - repeat pattern: `msg.jobopts.repeat.pattern`;
33
- - template data: `msg.jobData` or `{ payload: msg.payload }`.
64
+ ## Other BullMQ v6 Changes
34
65
 
35
- ## Data Migration
66
+ - `job.getState()` no longer returns `paused`; jobs in a paused queue report `waiting`. Use `isPaused` for the queue state.
67
+ - `getJobCounts` no longer includes a `paused` key.
68
+ - Flow children without an explicit `opts.jobId` receive UUIDs rather than incremental numeric ids.
69
+ - The default event filter includes `retries-exhausted`.
36
70
 
37
- Bull v4 and BullMQ do not provide a supported Redis data migration contract. Do not assume existing delayed, waiting, active, completed, or repeatable Bull v4 keys will be usable by BullMQ.
71
+ After updating flows, run them against a non-production Redis deployment before upgrading production.
@@ -1,35 +1,50 @@
1
1
  # Node Guide
2
2
 
3
- ## `bull-queue-server`
3
+ ## `bullmq-queue-server`
4
4
 
5
- Configures queue name, Redis deployment, credentials, TLS, and BullMQ prefix.
5
+ Configures queue name, backend, connection, credentials, and TLS.
6
6
 
7
- Deployment modes:
7
+ `backend` selects where BullMQ stores the queue. Absent or blank means `redis`, so a flow saved before the field existed keeps working unchanged.
8
+
9
+ - `redis`: the deployment modes, key prefix, and Redis database number below.
10
+ - `postgres`: `database`, `schema`, `max` (pool size), and `migrate` instead. Needs the optional peer dependency `pg` installed. See [CONNECTIONS.md](CONNECTIONS.md#postgresql).
11
+
12
+ Deployment modes (Redis only; hidden for `postgres`):
8
13
 
9
14
  - `single`: standalone Redis.
10
15
  - `cluster`: Redis Cluster and AWS MemoryDB.
11
16
  - `sentinel`: Redis Sentinel.
12
17
 
13
- Cluster and MemoryDB prefixes must contain a Redis hash tag; use `{bull}` unless you have a tested custom hash tag.
18
+ Redis only. Cluster and MemoryDB prefixes must contain a Redis hash tag. `{bull}` is the default. Independent queues may use different tags — `{orders}`, `{emails}` to spread load across cluster nodes. Prefixes in one `bullmq flow` tree or bulk flow batch must contain the same hash tag so their atomic Redis operations stay in one slot. Each worker must use the exact prefix assigned to its queue in the flow.
19
+
20
+ `removeOnComplete` and `removeOnFail` set queue-level auto-removal as BullMQ `defaultJobOptions`, keeping that many of the newest jobs in each state. New config nodes default to keeping 1000 completed and 5000 failed jobs. A blank field keeps every job, which is BullMQ's own default and grows the backing store without bound.
21
+
22
+ For `bullmq cmd`, `msg.jobopts` overrides these defaults. For `bullmq flow`, the defaults apply to every queue named in the flow tree; `msg.flowopts.queuesOptions[queueName].defaultJobOptions` overrides the config for one queue, and the job's own `opts` has final precedence.
23
+
24
+ Optional OpenTelemetry fields `telemetry`, `telemetryServiceName`, and `telemetryMetrics` are off/blank by default; see [docs/TELEMETRY.md](TELEMETRY.md).
14
25
 
15
- ## `bull cmd`
26
+ ## `bullmq cmd`
16
27
 
17
28
  Input node for producer and administration commands.
18
29
 
19
30
  Input:
20
31
 
21
32
  - `msg.cmd`: command name. Defaults to `add`.
22
- - `msg.payload`: compatibility payload.
33
+ - `msg.payload`: job data for `add` when `msg.jobData` is not supplied, or command input where documented.
23
34
  - `msg.jobData`: full BullMQ job data when supplied.
24
35
  - `msg.jobName`: BullMQ job name. Defaults to `default`.
25
36
  - `msg.jobopts`: BullMQ job options.
26
37
 
38
+ `add` and `addBulk` reject repeat options. Use the native Job Scheduler commands and fields documented in [docs/COMMANDS.md](COMMANDS.md).
39
+
27
40
  Output:
28
41
 
29
42
  - successful result in `msg.payload`;
30
43
  - errors go to `done(err)` or `node.error(err, msg)`.
31
44
 
32
- ## `bull run`
45
+ BullMQ v6 removed the `paused` job state: `getJobState` never returns `"paused"` (a paused queue's jobs report `"waiting"`), and `getJobCounts` no longer has a `paused` key. Use `isPaused` to check the queue itself.
46
+
47
+ ## `bullmq run`
33
48
 
34
49
  Worker node with no input and one output.
35
50
 
@@ -42,9 +57,11 @@ Output message:
42
57
  Completion modes:
43
58
 
44
59
  - `immediate`: complete after sending the message.
45
- - `manual`: wait for downstream `bull job` acknowledgement. Fails the job after the ack timeout; set the timeout to `0` to wait indefinitely.
60
+ - `manual`: wait for downstream `bullmq job` acknowledgement. Fails the job after the ack timeout; set the timeout to `0` to wait indefinitely.
46
61
 
47
- ## `bull job`
62
+ Concurrency and Max Started Attempts must be positive integers. Max Started Attempts defaults to 100 and bounds reprocessing loops from transitions that do not increment `attemptsMade`, including `moveToWait` and `moveToDelayed`. BullMQ fails the job unrecoverably before its processor runs for the 101st time. The optional limiter maximum and duration must either both be blank or both be positive integers.
63
+
64
+ ## `bullmq job`
48
65
 
49
66
  Acts on manual-mode active jobs. Actions can be configured or supplied in `msg.cmd`.
50
67
 
@@ -62,10 +79,27 @@ Non-terminal actions:
62
79
  - `getChildrenValues`
63
80
  - `getFailedChildrenValues`
64
81
  - `removeUnprocessedChildren`
82
+ - `updateData`: replaces the job's stored data with `msg.jobData`, falling back to `msg.payload`. The new data survives a retry, so it is how a flow records which step a job reached.
83
+
84
+ Step and retry transitions. Each hands the job's lock token back to BullMQ, so the job leaves the active state without counting a failed attempt, and the flow's acknowledgement is settled for it:
65
85
 
66
- ## `bull events`
86
+ - `moveToWait`: requeues the job to be picked up again. This is BullMQ's manual-retry pattern.
87
+ - `moveToDelayed`: delays the job by `msg.delay` milliseconds and resumes it later. Requires `msg.delay`.
67
88
 
68
- QueueEvents source node. Empty event filter subscribes to the default documented event list.
89
+ Together with `updateData` these implement BullMQ's process-step-jobs pattern: record the step, then requeue or delay the job, and resume at that step on its next activation. The lock token these need never appears in a Node-RED message.
90
+
91
+ Cancellation actions (BullMQ v6 cooperative cancellation):
92
+
93
+ - `cancelJob`: cancels the active job identified by `msg.bull.ackId`. Reason comes from `msg.reason`, default `"BullMQ job cancelled"`. Outputs `true`. If BullMQ reports the job as no longer cancellable, the node raises an error naming the job id instead of sending a message.
94
+ - `cancelAllJobs`: cancels every active manual-mode job on the `bullmq run` node that owns `msg.bull.ackId`. Same `msg.reason` default. Always outputs `true`.
95
+
96
+ Both actions are acknowledgement-scoped: like every `bullmq job` action, they act on the job behind `msg.bull.ackId`, so they only work for manual-completion jobs that have not yet settled (completed, failed, or timed out). Cancelling aborts the worker's per-job signal, which fails the pending acknowledgement; BullMQ then applies the queue's normal attempts/backoff retry policy to the job. Cancellation does not itself complete or remove the job.
97
+
98
+ ## `bullmq events`
99
+
100
+ QueueEvents source node. An empty event filter subscribes to: `active`, `added`, `cleaned`, `completed`, `deduplicated`, `delayed`, `drained`, `duplicated`, `failed`, `paused`, `progress`, `removed`, `resumed`, `retries-exhausted`, `stalled`, `waiting`, and `waiting-children`.
101
+
102
+ `retries-exhausted` is new in 2.0.0. A flow already deployed with an empty event filter now receives this extra event type without any config change.
69
103
 
70
104
  Output:
71
105
 
@@ -73,11 +107,15 @@ Output:
73
107
  - `msg.payload`: BullMQ event payload;
74
108
  - `msg.bull`: queue, event, and event id metadata.
75
109
 
76
- ## `bull flow`
110
+ ## `bullmq flow`
77
111
 
78
112
  Adds a BullMQ FlowProducer tree.
79
113
 
80
114
  Input:
81
115
 
82
- - `msg.payload`: BullMQ flow tree;
83
- - `msg.flowopts`: optional FlowProducer options.
116
+ - `msg.payload`: a BullMQ flow tree, or an array of flow trees;
117
+ - `msg.flowopts`: optional FlowProducer options, for a single tree.
118
+
119
+ An array uses `FlowProducer.addBulk`, which atomically creates multiple independent root trees: every tree is added or none is. Use one flow tree when jobs are related by parent/child dependencies, even when that tree spans queues. `msg.flowopts` does not apply to an array, because `addBulk` takes no options argument; queue-level auto-removal is stamped onto each job's own `opts` instead, where anything the tree already sets wins. On Redis Cluster, omit each flow job's `prefix` to use the flow node's prefix throughout. If prefixes are set per job, they must contain the same hash tag, and each worker must use its queue's exact prefix. An empty array is an error.
120
+
121
+ A child job in the tree that does not set `opts.jobId` gets a UUID as its job id (BullMQ v6 no longer assigns incremental numeric ids). Set `opts.jobId` on a child explicitly if the flow depends on a stable or predictable id.