@dudousxd/nestjs-telescope-bullmq 1.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.
- package/CHANGELOG.md +78 -0
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/dist/bull-mq-queue-manager.d.ts +30 -0
- package/dist/bull-mq-queue-manager.d.ts.map +1 -0
- package/dist/bull-mq-queue-manager.js +155 -0
- package/dist/bull-mq-queue-manager.js.map +1 -0
- package/dist/bullmq-job.watcher.d.ts +52 -0
- package/dist/bullmq-job.watcher.d.ts.map +1 -0
- package/dist/bullmq-job.watcher.js +125 -0
- package/dist/bullmq-job.watcher.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/job-content.d.ts +25 -0
- package/dist/job-content.d.ts.map +1 -0
- package/dist/job-content.js +28 -0
- package/dist/job-content.js.map +1 -0
- package/dist/queue-discovery.d.ts +36 -0
- package/dist/queue-discovery.d.ts.map +1 -0
- package/dist/queue-discovery.js +30 -0
- package/dist/queue-discovery.js.map +1 -0
- package/package.json +68 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @dudousxd/nestjs-telescope-bullmq
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`73b50ad`](https://github.com/DavideCarvalho/nestjs-telescope/commit/73b50ad00193127271fdec36ad080d2858045922) - Add the BullMQ job watcher (`@dudousxd/nestjs-telescope-bullmq`). It discovers
|
|
8
|
+
`@nestjs/bullmq` `WorkerHost` processors and wraps each job in a `'queue'` batch,
|
|
9
|
+
capturing job outcome/duration/attempts and correlating the queries and
|
|
10
|
+
exceptions a job emits to that job. Core now imports `DiscoveryModule` so
|
|
11
|
+
discovery-based watchers can resolve `DiscoveryService`, and the canonical
|
|
12
|
+
`JobContent` gains `id` and `maxAttempts`.
|
|
13
|
+
|
|
14
|
+
- [`a9f517c`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a9f517c076461aef55cfb90d072ec38427ace91b) - Add gated queue **mutation** endpoints on top of the live-queue reads. The core
|
|
15
|
+
controller now exposes `POST /telescope/api/queues/live/:driver/:queue/jobs/:id/:action`
|
|
16
|
+
(`retry` / `remove` / `promote`) and `POST .../actions/:action` (`retry-all`,
|
|
17
|
+
`redrive`), each carrying `:action` so a single `TelescopeActionGuard` authorizes
|
|
18
|
+
them uniformly.
|
|
19
|
+
|
|
20
|
+
Mutations are **default-deny**: they run behind a new `authorizeAction` option
|
|
21
|
+
_in addition to_ the read `authorizer`, and the guard fails closed. Without an
|
|
22
|
+
`authorizeAction` callback every mutation returns `403` — even for callers the
|
|
23
|
+
read authorizer already trusts. `authorizeAction(ctx, { driver, queue, action,
|
|
24
|
+
jobId?, state? })` opts in; returning falsy or throwing denies.
|
|
25
|
+
|
|
26
|
+
`BullMqQueueManager` implements `retry` / `remove` / `promote` / `retryAll`
|
|
27
|
+
against the real BullMQ `Job.retry()` / `Job.remove()` / `Job.promote()` and
|
|
28
|
+
`Queue.retryJobs()` APIs (`retryAll` returns the pre-action count for the state).
|
|
29
|
+
`redrive` remains SQS-only and returns `405` on the bullmq driver.
|
|
30
|
+
|
|
31
|
+
- [`9d8eb65`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9d8eb6562a7584801d5aa8b74491091f0fade5f9) - Let operators **enqueue (send) a new job/message** onto a queue from the dashboard.
|
|
32
|
+
|
|
33
|
+
Core adds an optional `enqueue?(queue, payload, opts, ctx)` method to the
|
|
34
|
+
`QueueManager` SPI and a new `POST /telescope/api/queues/live/:driver/:queue/enqueue`
|
|
35
|
+
route. Unlike the other mutations it carries a JSON body (`{ name?, payload }`),
|
|
36
|
+
so it lives on its own path rather than under `:action` — but it flows through the
|
|
37
|
+
same default-deny `TelescopeActionGuard` as `retry` / `remove` / `redrive`: the
|
|
38
|
+
guard recovers the `enqueue` action from the request path, so without an
|
|
39
|
+
`authorizeAction` callback it returns `403`. The route returns `400` when the
|
|
40
|
+
payload is absent and `404` when the driver is unknown or doesn't implement
|
|
41
|
+
`enqueue`. `enqueue` is added to `QUEUE_ACTIONS` and advertised in the
|
|
42
|
+
`/queues/live` `actionsByDriver` capabilities when a manager implements it.
|
|
43
|
+
|
|
44
|
+
`BullMqQueueManager` implements `enqueue` via the real `Queue.add(name, data)`
|
|
45
|
+
(defaulting the name to `manual`), returning the new job id.
|
|
46
|
+
|
|
47
|
+
The UI gains an "Send message" form on the queue console — shown only when the
|
|
48
|
+
selected driver advertises `enqueue` and mutations are enabled. It parses the
|
|
49
|
+
payload textarea as JSON (invalid JSON surfaces inline without calling the API),
|
|
50
|
+
posts via a new `queueEnqueue` client method, and on success confirms and
|
|
51
|
+
refreshes the queue counts/jobs. A `403` surfaces inline as "Not authorized".
|
|
52
|
+
|
|
53
|
+
- [`418f1f0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/418f1f0421948b40b25f845441a716fa4c6655c2) - Add a driver-agnostic live-queue read layer. Core gains the `QueueManager` SPI
|
|
54
|
+
(`QueueManager`, `QueueManagerContext`, `QueueManagerRegistry`) and its DTO types
|
|
55
|
+
(`QueueState`, `QueueCounts`, `QueueSummary`, `QueueJob`, `QueueJobDetail`,
|
|
56
|
+
`JobPage`), wired through a `queueManagers` option on `TelescopeModule.forRoot`
|
|
57
|
+
and surfaced as read endpoints under the existing authorizer:
|
|
58
|
+
`GET /telescope/api/queues/live`, `…/live/:driver/:queue/counts`,
|
|
59
|
+
`…/live/:driver/:queue/jobs?state=`, and `…/live/:driver/:queue/jobs/:id`.
|
|
60
|
+
|
|
61
|
+
`@dudousxd/nestjs-telescope-bullmq` adds `BullMqQueueManager`, which discovers
|
|
62
|
+
`@nestjs/bullmq` `Queue` instances via `DiscoveryService` (duck-typed, optional
|
|
63
|
+
explicit allow-list) and reads them through the BullMQ `Queue` API to report
|
|
64
|
+
live counts, the jobs in each list, and per-job detail. Job payloads are passed
|
|
65
|
+
through core redaction before leaving the server. Reads only this phase — queue
|
|
66
|
+
actions (retry/remove/promote/redrive) land in Phase 2.
|
|
67
|
+
|
|
68
|
+
- [`de29d2f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/de29d2f519b1f25bc702c9dcc737d99b4751c8c9) - Add Horizon-style queue metrics. A new `GET /telescope/api/queues?window=1h`
|
|
69
|
+
endpoint aggregates captured `job` entries into per-queue throughput, runtime
|
|
70
|
+
and wait-time percentiles, and failure rate (`QueueMetricsService` +
|
|
71
|
+
`aggregateQueueMetrics`). The BullMQ watcher now captures `waitMs`
|
|
72
|
+
(`processedOn − enqueue`) on each job, and the canonical `JobContent` gains a
|
|
73
|
+
`waitMs` field. `durationToMs` is extracted as a shared, exported util.
|
|
74
|
+
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- Updated dependencies [[`73b50ad`](https://github.com/DavideCarvalho/nestjs-telescope/commit/73b50ad00193127271fdec36ad080d2858045922), [`9126bb0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9126bb04777cdaec6af3b0a1c5fe6f91d055ce82), [`1f00e62`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1f00e62c8e60482b64251813680a5f866ef1619a), [`090cd1f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/090cd1ff871dbe46c1c877a26f90496550b5304c), [`b7326b3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b7326b33d8d55b5f1ac5de4256f5e1980278699e), [`bfc0e26`](https://github.com/DavideCarvalho/nestjs-telescope/commit/bfc0e268388b5563d05c24e4de6ff99c74d1201a), [`7797a2a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7797a2a1554aff49bb59f5ca1b204974a7e04a41), [`6817fe6`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6817fe62775b1ff847fdb1038d3298e7709569e0), [`20ceb87`](https://github.com/DavideCarvalho/nestjs-telescope/commit/20ceb878cd4495dfbc7a3c71d882ae216a633757), [`1dd4db0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1dd4db0f3cd46d04b35ac112343cfb424c2d3190), [`a9f517c`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a9f517c076461aef55cfb90d072ec38427ace91b), [`9d8eb65`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9d8eb6562a7584801d5aa8b74491091f0fade5f9), [`418f1f0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/418f1f0421948b40b25f845441a716fa4c6655c2), [`e76980d`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e76980ddd7ee740f1b337a422a98ca98d97a007e), [`80c8f97`](https://github.com/DavideCarvalho/nestjs-telescope/commit/80c8f9769c8ab9ee724635086740910bc4d44ea3), [`e14ac60`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e14ac603551372fc3767c63a349c509582b5e6ab), [`6fa0946`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6fa0946f5543868704864af2e32793eb448ac827), [`d507547`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d507547df3c13e76e90b8a97c4e3e1d8aef25bd1), [`affd07e`](https://github.com/DavideCarvalho/nestjs-telescope/commit/affd07e4cb9ee85cfabaabed424833e8c638d04a), [`6a4d8d5`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6a4d8d56321d3840fe64e646130ccfdafcfb1bdd), [`c1f1ec9`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c1f1ec903d470d6b884924e2713de305b61b7481), [`cad6dae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/cad6dae0dba4f22e476d78c23ce2f74f7f6848e4), [`c8596c8`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c8596c85712880cb235e8cce059a1d93d339e9bd), [`c4222b1`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c4222b16ef4c0fc9c61694eb67033f03369ff24e), [`de29d2f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/de29d2f519b1f25bc702c9dcc737d99b4751c8c9), [`10a3bc2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/10a3bc224f6e0b1a237e1e7631acad70493b4c12), [`abde392`](https://github.com/DavideCarvalho/nestjs-telescope/commit/abde39264effb31b0524cc4fa89a335276c8dccb), [`8ff32a2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8ff32a2cc95775224eea3377460d91674dfda47f), [`5f2eddd`](https://github.com/DavideCarvalho/nestjs-telescope/commit/5f2eddd0ed5d72bf1de323b45870d7ddcaf64349), [`b14a201`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b14a20175eae3d3017e8cbc068d367a03f634175), [`a90ef56`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a90ef569ba12484bece07d8de2045e13ff2ff528), [`593bcc8`](https://github.com/DavideCarvalho/nestjs-telescope/commit/593bcc85ad6558040c62ba66bd1e5e0cbe5a6ac7), [`7b6636b`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7b6636b54438427cd53ea0cbedd186b77d807169), [`e64f35a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/e64f35a1bb7cae15b2ef24404888463d04f81eef), [`4892ef6`](https://github.com/DavideCarvalho/nestjs-telescope/commit/4892ef61e45e5486d34c7ec82764e6767fe8233d)]:
|
|
78
|
+
- @dudousxd/nestjs-telescope@1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# @dudousxd/nestjs-telescope-bullmq
|
|
2
|
+
|
|
3
|
+
BullMQ job watcher for [`@dudousxd/nestjs-telescope`](../../README.md). Captures
|
|
4
|
+
every job (id, name, queue, outcome, duration, attempts) and correlates the
|
|
5
|
+
queries and exceptions a job emits to that job's batch.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @dudousxd/nestjs-telescope-bullmq
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer deps: `@dudousxd/nestjs-telescope`, `@nestjs/bullmq`, `bullmq`,
|
|
14
|
+
`@nestjs/common`, `@nestjs/core`, `reflect-metadata`.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Add the watcher to `TelescopeModule`. No host wiring is required — the watcher
|
|
19
|
+
discovers your `@Processor` (`WorkerHost`) classes and instruments them
|
|
20
|
+
automatically.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
|
|
24
|
+
import { BullMqJobWatcher } from '@dudousxd/nestjs-telescope-bullmq';
|
|
25
|
+
|
|
26
|
+
@Module({
|
|
27
|
+
imports: [
|
|
28
|
+
TelescopeModule.forRoot({
|
|
29
|
+
watchers: [new BullMqJobWatcher({ slowMs: 1000 })],
|
|
30
|
+
}),
|
|
31
|
+
// ...your BullModule.registerQueue(...) and @Processor classes
|
|
32
|
+
],
|
|
33
|
+
})
|
|
34
|
+
export class AppModule {}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Each processed job becomes a `job` entry with `origin: 'queue'`. Queries and
|
|
38
|
+
exceptions emitted while the job runs share the job's `batchId`, so opening a
|
|
39
|
+
job in the dashboard shows everything it caused.
|
|
40
|
+
|
|
41
|
+
With these entries captured, the core endpoint
|
|
42
|
+
`GET /telescope/api/queues?window=1h` reports per-queue throughput, runtime and
|
|
43
|
+
wait-time percentiles, and failure rate (wait time = `processedOn − enqueue`).
|
|
44
|
+
|
|
45
|
+
## Live queue reads (`BullMqQueueManager`)
|
|
46
|
+
|
|
47
|
+
The watcher above records *what jobs did*. `BullMqQueueManager` is the
|
|
48
|
+
complementary read side: it browses your queues' **current** state directly via
|
|
49
|
+
the BullMQ `Queue` API (counts, paused flag, and the jobs sitting in each list),
|
|
50
|
+
so the dashboard can show live queue depth and inspect individual jobs.
|
|
51
|
+
|
|
52
|
+
It is a `QueueManager`, passed via the `queueManagers` option (not `watchers`).
|
|
53
|
+
At bootstrap it discovers every `@nestjs/bullmq` `Queue` instance in the Nest
|
|
54
|
+
container through `DiscoveryService` (duck-typed — no extra wiring):
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
|
|
58
|
+
import { BullMqQueueManager } from '@dudousxd/nestjs-telescope-bullmq';
|
|
59
|
+
|
|
60
|
+
@Module({
|
|
61
|
+
imports: [
|
|
62
|
+
TelescopeModule.forRoot({
|
|
63
|
+
queueManagers: [new BullMqQueueManager()],
|
|
64
|
+
}),
|
|
65
|
+
BullModule.forRoot({ connection }),
|
|
66
|
+
BullModule.registerQueue({ name: 'mail' }),
|
|
67
|
+
],
|
|
68
|
+
})
|
|
69
|
+
export class AppModule {}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Pass an explicit allow-list to expose only some queues:
|
|
73
|
+
`new BullMqQueueManager(['mail', 'reports'])`.
|
|
74
|
+
|
|
75
|
+
This surfaces these read endpoints on the core controller, under the **existing
|
|
76
|
+
Telescope authorizer** (same gate as the rest of the dashboard):
|
|
77
|
+
|
|
78
|
+
| Method & path | Returns |
|
|
79
|
+
|---------------|---------|
|
|
80
|
+
| `GET /telescope/api/queues/live` | `{ queues: QueueSummary[] }` across all drivers — name, per-state counts, `isPaused`. |
|
|
81
|
+
| `GET /telescope/api/queues/live/bullmq/:queue/counts` | `QueueCounts` for one queue. |
|
|
82
|
+
| `GET /telescope/api/queues/live/bullmq/:queue/jobs?state=&cursor=&limit=` | A `JobPage` of jobs in `state` (`waiting`/`active`/`delayed`/`failed`/`completed`/`paused`); `nextCursor` paginates. |
|
|
83
|
+
| `GET /telescope/api/queues/live/bullmq/:queue/jobs/:id` | A `QueueJobDetail` (adds redacted `data`, `opts`, `stacktrace`, `returnValue`). |
|
|
84
|
+
|
|
85
|
+
Job payloads (`data`) are passed through core redaction before they leave the
|
|
86
|
+
server, so secret-keyed fields (e.g. `password`, `token`) are masked.
|
|
87
|
+
|
|
88
|
+
## Queue actions (mutations)
|
|
89
|
+
|
|
90
|
+
`BullMqQueueManager` also implements the action side of the `QueueManager` SPI —
|
|
91
|
+
`retry`, `remove`, `promote`, and `retryAll` — backed by the real BullMQ
|
|
92
|
+
`Job.retry()` / `Job.remove()` / `Job.promote()` and `Queue.retryJobs()` APIs.
|
|
93
|
+
These surface as **POST** endpoints on the core controller:
|
|
94
|
+
|
|
95
|
+
| Method & path | Effect |
|
|
96
|
+
|---------------|--------|
|
|
97
|
+
| `POST /telescope/api/queues/live/bullmq/:queue/jobs/:id/retry` | Re-queue a failed (or completed) job. |
|
|
98
|
+
| `POST /telescope/api/queues/live/bullmq/:queue/jobs/:id/remove` | Delete a job. |
|
|
99
|
+
| `POST /telescope/api/queues/live/bullmq/:queue/jobs/:id/promote` | Promote a delayed job to run now. |
|
|
100
|
+
| `POST /telescope/api/queues/live/bullmq/:queue/actions/retry-all?state=failed` | Bulk re-queue every job in `state`; responds `{ ok: true, count }` (the pre-action count). |
|
|
101
|
+
|
|
102
|
+
`redrive` is **SQS-only** and is not implemented by `BullMqQueueManager`; calling
|
|
103
|
+
`.../actions/redrive` against the bullmq driver returns `405 Method Not Allowed`.
|
|
104
|
+
|
|
105
|
+
### Enabling mutations — `authorizeAction` (default-deny)
|
|
106
|
+
|
|
107
|
+
Mutations are **denied by default**. They run behind a second guard
|
|
108
|
+
(`TelescopeActionGuard`) on top of the read `authorizer`, and that guard fails
|
|
109
|
+
closed: **without an `authorizeAction` callback, every mutation endpoint returns
|
|
110
|
+
`403`** — even for callers the read `authorizer` already trusts. Opt in by
|
|
111
|
+
supplying `authorizeAction` on `TelescopeModule.forRoot`:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
TelescopeModule.forRoot({
|
|
115
|
+
// Reads (browse queues/jobs) — the existing gate:
|
|
116
|
+
authorizer: (ctx) => isAdmin(ctx.request),
|
|
117
|
+
// Mutations (retry/remove/promote/retry-all) — separate, default-deny:
|
|
118
|
+
authorizeAction: (ctx, action) => {
|
|
119
|
+
// action: { driver, queue, action, jobId?, state? }
|
|
120
|
+
return canMutateQueues(ctx.request);
|
|
121
|
+
},
|
|
122
|
+
queueManagers: [new BullMqQueueManager()],
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`authorizeAction` receives the same `AuthorizerContext` plus a typed
|
|
127
|
+
`QueueActionRequest` describing the requested mutation. Returning a falsy value —
|
|
128
|
+
or throwing — denies the request (`403`). Keep it strictly narrower than your
|
|
129
|
+
read gate: browsing a queue should not imply the right to drain it.
|
|
130
|
+
|
|
131
|
+
## Options
|
|
132
|
+
|
|
133
|
+
| Option | Default | Description |
|
|
134
|
+
|--------|---------|-------------|
|
|
135
|
+
| `slowMs` | `1000` | Jobs taking at least this long get a `slow` tag. |
|
|
136
|
+
| `includeJobData` | `true` | Capture `job.data` as the entry payload (core redaction still applies). |
|
|
137
|
+
| `clock` | wall clock | Injectable time source (for tests). |
|
|
138
|
+
|
|
139
|
+
## Not included
|
|
140
|
+
|
|
141
|
+
This watcher captures per-job entries; the aggregate queue metrics above are
|
|
142
|
+
computed in core from those entries (`GET /telescope/api/queues`). Broader health
|
|
143
|
+
rollups — N+1 insights, slowest request/query/job, top exceptions, and a visual
|
|
144
|
+
dashboard — are the concern of `@dudousxd/nestjs-telescope-pulse` and
|
|
145
|
+
`@dudousxd/nestjs-telescope-ui`, not this watcher.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { JobPage, QueueCounts, QueueJobDetail, QueueManager, QueueManagerContext, QueueState, QueueSummary } from '@dudousxd/nestjs-telescope';
|
|
2
|
+
export declare class BullMqQueueManager implements QueueManager {
|
|
3
|
+
private readonly queueNames?;
|
|
4
|
+
readonly driver = "bullmq";
|
|
5
|
+
private queues;
|
|
6
|
+
private redact;
|
|
7
|
+
/** @param queueNames optional explicit allow-list; default = auto-discover all. */
|
|
8
|
+
constructor(queueNames?: string[] | undefined);
|
|
9
|
+
init(ctx: QueueManagerContext): void;
|
|
10
|
+
private requireQueue;
|
|
11
|
+
private countsFor;
|
|
12
|
+
listQueues(): Promise<QueueSummary[]>;
|
|
13
|
+
counts(queue: string): Promise<QueueCounts>;
|
|
14
|
+
listJobs(queue: string, state: QueueState, page: {
|
|
15
|
+
cursor?: string;
|
|
16
|
+
limit?: number;
|
|
17
|
+
}): Promise<JobPage>;
|
|
18
|
+
getJob(queue: string, id: string): Promise<QueueJobDetail | null>;
|
|
19
|
+
retry(queue: string, id: string): Promise<void>;
|
|
20
|
+
remove(queue: string, id: string): Promise<void>;
|
|
21
|
+
promote(queue: string, id: string): Promise<void>;
|
|
22
|
+
enqueue(queue: string, payload: unknown, opts: {
|
|
23
|
+
name?: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
id: string | null;
|
|
26
|
+
}>;
|
|
27
|
+
retryAll(queue: string, state: QueueState): Promise<number>;
|
|
28
|
+
private toJob;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=bull-mq-queue-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bull-mq-queue-manager.d.ts","sourceRoot":"","sources":["../src/bull-mq-queue-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EAEX,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,YAAY,EACb,MAAM,4BAA4B,CAAC;AAuDpC,qBAAa,kBAAmB,YAAW,YAAY;IAMzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IALxC,QAAQ,CAAC,MAAM,YAAY;IAC3B,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,MAAM,CAAiD;IAE/D,mFAAmF;gBACtD,UAAU,CAAC,EAAE,MAAM,EAAE,YAAA;IAElD,IAAI,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI;IAUpC,OAAO,CAAC,YAAY;YAMN,SAAS;IAmBjB,UAAU,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAW3C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAIrC,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACxC,OAAO,CAAC,OAAO,CAAC;IAYb,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAajE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMhD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD,OAAO,CACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GACtB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAS3B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IASjE,OAAO,CAAC,KAAK;CAcd"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { DiscoveryService } from '@nestjs/core';
|
|
2
|
+
import { discoverQueues, hasJobOps } from './queue-discovery.js';
|
|
3
|
+
// BullMQ list names accepted by Queue.getJobs()/getJobCounts(). 'waiting' maps
|
|
4
|
+
// to the internal 'wait' list (BullMQ also accepts 'waiting' as an alias, but
|
|
5
|
+
// 'wait' is the canonical list name). 'paused' jobs live in their own list.
|
|
6
|
+
// Verified against bullmq@5.78.0 (JobType = JobState | 'paused' | 'repeat' | 'wait').
|
|
7
|
+
const STATE_TO_BULL = {
|
|
8
|
+
waiting: 'wait',
|
|
9
|
+
active: 'active',
|
|
10
|
+
delayed: 'delayed',
|
|
11
|
+
failed: 'failed',
|
|
12
|
+
completed: 'completed',
|
|
13
|
+
paused: 'paused',
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_LIMIT = 50;
|
|
16
|
+
function isBullJobLike(value) {
|
|
17
|
+
return typeof value === 'object' && value !== null;
|
|
18
|
+
}
|
|
19
|
+
function readMaxAttempts(opts) {
|
|
20
|
+
if (typeof opts === 'object' && opts !== null) {
|
|
21
|
+
const attempts = opts.attempts;
|
|
22
|
+
if (typeof attempts === 'number')
|
|
23
|
+
return attempts;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** Best-effort state for a single fetched job (detail view, non-critical). */
|
|
28
|
+
function deriveState(job) {
|
|
29
|
+
if (job.finishedOn != null) {
|
|
30
|
+
return job.failedReason != null ? 'failed' : 'completed';
|
|
31
|
+
}
|
|
32
|
+
if (job.processedOn != null)
|
|
33
|
+
return 'active';
|
|
34
|
+
return 'waiting';
|
|
35
|
+
}
|
|
36
|
+
export class BullMqQueueManager {
|
|
37
|
+
queueNames;
|
|
38
|
+
driver = 'bullmq';
|
|
39
|
+
queues = new Map();
|
|
40
|
+
redact = (value) => value;
|
|
41
|
+
/** @param queueNames optional explicit allow-list; default = auto-discover all. */
|
|
42
|
+
constructor(queueNames) {
|
|
43
|
+
this.queueNames = queueNames;
|
|
44
|
+
}
|
|
45
|
+
init(ctx) {
|
|
46
|
+
this.redact = ctx.redact;
|
|
47
|
+
const discovery = ctx.moduleRef.get(DiscoveryService, { strict: false });
|
|
48
|
+
for (const queue of discoverQueues(discovery)) {
|
|
49
|
+
if (!this.queueNames || this.queueNames.includes(queue.name)) {
|
|
50
|
+
this.queues.set(queue.name, queue);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
requireQueue(queue) {
|
|
55
|
+
const found = this.queues.get(queue);
|
|
56
|
+
if (!found)
|
|
57
|
+
throw new Error(`Unknown bullmq queue: ${queue}`);
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
async countsFor(queue) {
|
|
61
|
+
const counts = await queue.getJobCounts('waiting', 'active', 'delayed', 'failed', 'completed', 'paused');
|
|
62
|
+
return {
|
|
63
|
+
waiting: counts.waiting ?? 0,
|
|
64
|
+
active: counts.active ?? 0,
|
|
65
|
+
delayed: counts.delayed ?? 0,
|
|
66
|
+
failed: counts.failed ?? 0,
|
|
67
|
+
completed: counts.completed ?? 0,
|
|
68
|
+
paused: counts.paused ?? 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async listQueues() {
|
|
72
|
+
return Promise.all([...this.queues.values()].map(async (queue) => ({
|
|
73
|
+
driver: this.driver,
|
|
74
|
+
queue: queue.name,
|
|
75
|
+
counts: await this.countsFor(queue),
|
|
76
|
+
isPaused: await queue.isPaused(),
|
|
77
|
+
})));
|
|
78
|
+
}
|
|
79
|
+
counts(queue) {
|
|
80
|
+
return this.countsFor(this.requireQueue(queue));
|
|
81
|
+
}
|
|
82
|
+
async listJobs(queue, state, page) {
|
|
83
|
+
const target = this.requireQueue(queue);
|
|
84
|
+
const limit = page.limit && page.limit > 0 ? page.limit : DEFAULT_LIMIT;
|
|
85
|
+
const start = page.cursor ? Math.max(0, Number.parseInt(page.cursor, 10) || 0) : 0;
|
|
86
|
+
// Fetch one extra (end inclusive: start..start+limit) to detect a next page.
|
|
87
|
+
const raw = await target.getJobs(STATE_TO_BULL[state], start, start + limit, false);
|
|
88
|
+
const bullJobs = raw.filter(isBullJobLike);
|
|
89
|
+
const jobs = bullJobs.slice(0, limit).map((job) => this.toJob(job, state));
|
|
90
|
+
const nextCursor = bullJobs.length > limit ? String(start + limit) : null;
|
|
91
|
+
return { jobs, nextCursor, total: null };
|
|
92
|
+
}
|
|
93
|
+
async getJob(queue, id) {
|
|
94
|
+
const raw = await this.requireQueue(queue).getJob(id);
|
|
95
|
+
if (!isBullJobLike(raw))
|
|
96
|
+
return null;
|
|
97
|
+
const base = this.toJob(raw, deriveState(raw));
|
|
98
|
+
return {
|
|
99
|
+
...base,
|
|
100
|
+
data: this.redact(raw.data),
|
|
101
|
+
opts: raw.opts ?? null,
|
|
102
|
+
stacktrace: Array.isArray(raw.stacktrace) ? raw.stacktrace : null,
|
|
103
|
+
returnValue: raw.returnvalue ?? null,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async retry(queue, id) {
|
|
107
|
+
const job = await this.requireQueue(queue).getJob(id);
|
|
108
|
+
if (!hasJobOps(job))
|
|
109
|
+
throw new Error(`Job ${id} not found in ${queue}`);
|
|
110
|
+
await job.retry();
|
|
111
|
+
}
|
|
112
|
+
async remove(queue, id) {
|
|
113
|
+
const job = await this.requireQueue(queue).getJob(id);
|
|
114
|
+
if (!hasJobOps(job))
|
|
115
|
+
throw new Error(`Job ${id} not found in ${queue}`);
|
|
116
|
+
await job.remove();
|
|
117
|
+
}
|
|
118
|
+
async promote(queue, id) {
|
|
119
|
+
const job = await this.requireQueue(queue).getJob(id);
|
|
120
|
+
if (!hasJobOps(job))
|
|
121
|
+
throw new Error(`Job ${id} not found in ${queue}`);
|
|
122
|
+
await job.promote();
|
|
123
|
+
}
|
|
124
|
+
async enqueue(queue, payload, opts) {
|
|
125
|
+
const target = this.requireQueue(queue);
|
|
126
|
+
if (typeof target.add !== 'function') {
|
|
127
|
+
throw new Error(`bullmq queue ${queue} cannot enqueue`);
|
|
128
|
+
}
|
|
129
|
+
const job = await target.add(opts.name ?? 'manual', payload);
|
|
130
|
+
return { id: job.id ?? null };
|
|
131
|
+
}
|
|
132
|
+
async retryAll(queue, state) {
|
|
133
|
+
const target = this.requireQueue(queue);
|
|
134
|
+
const before = (await this.countsFor(target))[state] ?? 0;
|
|
135
|
+
if (typeof target.retryJobs === 'function') {
|
|
136
|
+
await target.retryJobs({ state: STATE_TO_BULL[state] });
|
|
137
|
+
}
|
|
138
|
+
return before;
|
|
139
|
+
}
|
|
140
|
+
toJob(job, state) {
|
|
141
|
+
return {
|
|
142
|
+
id: String(job.id ?? ''),
|
|
143
|
+
name: job.name ?? '',
|
|
144
|
+
state,
|
|
145
|
+
attemptsMade: job.attemptsMade ?? 0,
|
|
146
|
+
maxAttempts: readMaxAttempts(job.opts),
|
|
147
|
+
timestamp: job.timestamp ?? null,
|
|
148
|
+
processedOn: job.processedOn ?? null,
|
|
149
|
+
finishedOn: job.finishedOn ?? null,
|
|
150
|
+
failedReason: job.failedReason ?? null,
|
|
151
|
+
progress: typeof job.progress === 'number' ? job.progress : null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=bull-mq-queue-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bull-mq-queue-manager.js","sourceRoot":"","sources":["../src/bull-mq-queue-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Watcher, type WatcherContext } from '@dudousxd/nestjs-telescope';
|
|
2
|
+
export interface BullMqJobWatcherOptions {
|
|
3
|
+
/** Jobs whose processing time is >= this (ms) get a 'slow' tag. Default 1000. */
|
|
4
|
+
slowMs?: number;
|
|
5
|
+
/** Capture `job.data` as the entry payload. Default true (core redaction applies). */
|
|
6
|
+
includeJobData?: boolean;
|
|
7
|
+
/** Time source; injectable for tests. Default wall clock. */
|
|
8
|
+
clock?: {
|
|
9
|
+
now(): number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Captures BullMQ jobs and correlates each job's queries/exceptions to its batch.
|
|
14
|
+
*
|
|
15
|
+
* ## How it works
|
|
16
|
+
* At registration the watcher uses NestJS `DiscoveryService` to find every
|
|
17
|
+
* `WorkerHost` provider and replaces its subclass-prototype `process` method
|
|
18
|
+
* with a wrapper that runs the original inside `ctx.runInBatch('queue', ...)`.
|
|
19
|
+
*
|
|
20
|
+
* `@nestjs/bullmq` resolves `instance.process(job, token)` at call-time per job
|
|
21
|
+
* (to support request-scoped processors), and jobs only run after the app has
|
|
22
|
+
* bootstrapped -- so a prototype patch applied during `register()` (which the
|
|
23
|
+
* registrar invokes at `onApplicationBootstrap`) always precedes the first job.
|
|
24
|
+
*
|
|
25
|
+
* The wrapper never swallows the host's error: on failure it records a `failed`
|
|
26
|
+
* job entry and re-throws so BullMQ's own retry/lifecycle is unaffected.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* Processor detection uses `instanceof WorkerHost`. If the host application
|
|
30
|
+
* resolves two distinct copies of `@nestjs/bullmq` in its module tree,
|
|
31
|
+
* processors from the other copy won't match and will be left un-instrumented
|
|
32
|
+
* (surfaced only by the "no processors found" warning). A single, deduped
|
|
33
|
+
* `@nestjs/bullmq` is assumed.
|
|
34
|
+
*/
|
|
35
|
+
export declare class BullMqJobWatcher implements Watcher {
|
|
36
|
+
readonly type: "job";
|
|
37
|
+
private readonly logger;
|
|
38
|
+
private readonly slowMs;
|
|
39
|
+
private readonly includeJobData;
|
|
40
|
+
private readonly clock;
|
|
41
|
+
/** Prototypes already patched, so shared prototypes wrap exactly once. */
|
|
42
|
+
private readonly patched;
|
|
43
|
+
constructor(options?: BullMqJobWatcherOptions);
|
|
44
|
+
register(ctx: WatcherContext): Promise<void>;
|
|
45
|
+
private patchProcess;
|
|
46
|
+
/** Build + hand a job entry to the Recorder, swallowing any failure. Core's
|
|
47
|
+
* record() is already non-throwing; this double-guard keeps the watcher safe
|
|
48
|
+
* even against a custom or regressed WatcherContext, so recording can never
|
|
49
|
+
* alter the host job's outcome. */
|
|
50
|
+
private safeRecord;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=bullmq-job.watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bullmq-job.watcher.d.ts","sourceRoot":"","sources":["../src/bullmq-job.watcher.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,4BAA4B,CAAC;AAMpC,MAAM,WAAW,uBAAuB;IACtC,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,6DAA6D;IAC7D,KAAK,CAAC,EAAE;QAAE,GAAG,IAAI,MAAM,CAAA;KAAE,CAAC;CAC3B;AAWD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,gBAAiB,YAAW,OAAO;IAC9C,QAAQ,CAAC,IAAI,QAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;gBAErC,OAAO,GAAE,uBAA4B;IAM3C,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBlD,OAAO,CAAC,YAAY;IA0BpB;;;wCAGoC;IACpC,OAAO,CAAC,UAAU;CA8BnB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// packages/bullmq/src/bullmq-job.watcher.ts
|
|
2
|
+
import { EntryType, } from '@dudousxd/nestjs-telescope';
|
|
3
|
+
import { WorkerHost } from '@nestjs/bullmq';
|
|
4
|
+
import { Logger } from '@nestjs/common';
|
|
5
|
+
import { DiscoveryService } from '@nestjs/core';
|
|
6
|
+
import { buildJobContent } from './job-content.js';
|
|
7
|
+
/** Narrow an unknown BullMQ job to the structural `JobLike` we read. Every field
|
|
8
|
+
* is accessed defensively in `buildJobContent`, so a non-object degrades to {}. */
|
|
9
|
+
function toJobLike(job) {
|
|
10
|
+
return typeof job === 'object' && job !== null ? job : {};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Captures BullMQ jobs and correlates each job's queries/exceptions to its batch.
|
|
14
|
+
*
|
|
15
|
+
* ## How it works
|
|
16
|
+
* At registration the watcher uses NestJS `DiscoveryService` to find every
|
|
17
|
+
* `WorkerHost` provider and replaces its subclass-prototype `process` method
|
|
18
|
+
* with a wrapper that runs the original inside `ctx.runInBatch('queue', ...)`.
|
|
19
|
+
*
|
|
20
|
+
* `@nestjs/bullmq` resolves `instance.process(job, token)` at call-time per job
|
|
21
|
+
* (to support request-scoped processors), and jobs only run after the app has
|
|
22
|
+
* bootstrapped -- so a prototype patch applied during `register()` (which the
|
|
23
|
+
* registrar invokes at `onApplicationBootstrap`) always precedes the first job.
|
|
24
|
+
*
|
|
25
|
+
* The wrapper never swallows the host's error: on failure it records a `failed`
|
|
26
|
+
* job entry and re-throws so BullMQ's own retry/lifecycle is unaffected.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* Processor detection uses `instanceof WorkerHost`. If the host application
|
|
30
|
+
* resolves two distinct copies of `@nestjs/bullmq` in its module tree,
|
|
31
|
+
* processors from the other copy won't match and will be left un-instrumented
|
|
32
|
+
* (surfaced only by the "no processors found" warning). A single, deduped
|
|
33
|
+
* `@nestjs/bullmq` is assumed.
|
|
34
|
+
*/
|
|
35
|
+
export class BullMqJobWatcher {
|
|
36
|
+
type = EntryType.Job;
|
|
37
|
+
logger = new Logger(BullMqJobWatcher.name);
|
|
38
|
+
slowMs;
|
|
39
|
+
includeJobData;
|
|
40
|
+
clock;
|
|
41
|
+
/** Prototypes already patched, so shared prototypes wrap exactly once. */
|
|
42
|
+
patched = new WeakSet();
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
this.slowMs = options.slowMs ?? 1000;
|
|
45
|
+
this.includeJobData = options.includeJobData ?? true;
|
|
46
|
+
this.clock = options.clock ?? { now: () => Date.now() };
|
|
47
|
+
}
|
|
48
|
+
async register(ctx) {
|
|
49
|
+
const discovery = ctx.moduleRef.get(DiscoveryService, { strict: false });
|
|
50
|
+
let count = 0;
|
|
51
|
+
for (const wrapper of discovery.getProviders()) {
|
|
52
|
+
const instance = wrapper.instance;
|
|
53
|
+
if (!(instance instanceof WorkerHost))
|
|
54
|
+
continue;
|
|
55
|
+
const proto = Object.getPrototypeOf(instance);
|
|
56
|
+
if (this.patched.has(proto))
|
|
57
|
+
continue;
|
|
58
|
+
this.patched.add(proto);
|
|
59
|
+
this.patchProcess(proto, ctx);
|
|
60
|
+
count++;
|
|
61
|
+
}
|
|
62
|
+
if (count === 0) {
|
|
63
|
+
this.logger.warn('BullMqJobWatcher: no @Processor (WorkerHost) providers found. ' +
|
|
64
|
+
'Jobs will not be captured. Ensure your processors are registered before Telescope bootstraps.');
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.logger.log(`BullMqJobWatcher: instrumented ${count} processor class(es).`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
patchProcess(proto, ctx) {
|
|
71
|
+
const original = proto.process;
|
|
72
|
+
if (typeof original !== 'function')
|
|
73
|
+
return;
|
|
74
|
+
const watcher = this;
|
|
75
|
+
proto.process = function patchedProcess(job, token) {
|
|
76
|
+
return ctx.runInBatch('queue', async () => {
|
|
77
|
+
const startedAt = watcher.clock.now();
|
|
78
|
+
try {
|
|
79
|
+
const result = await original.call(this, job, token);
|
|
80
|
+
// safeRecord never throws, so a telescope failure cannot turn a
|
|
81
|
+
// successful job into a failed one.
|
|
82
|
+
watcher.safeRecord(ctx, job, 'completed', watcher.clock.now() - startedAt, undefined);
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
watcher.safeRecord(ctx, job, 'failed', watcher.clock.now() - startedAt, error);
|
|
87
|
+
throw error; // never swallow the host's error
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Build + hand a job entry to the Recorder, swallowing any failure. Core's
|
|
93
|
+
* record() is already non-throwing; this double-guard keeps the watcher safe
|
|
94
|
+
* even against a custom or regressed WatcherContext, so recording can never
|
|
95
|
+
* alter the host job's outcome. */
|
|
96
|
+
safeRecord(ctx, job, status, durationMs, error) {
|
|
97
|
+
try {
|
|
98
|
+
const content = buildJobContent(toJobLike(job), status, error, this.includeJobData);
|
|
99
|
+
const familyHash = [content.queue, content.name].filter(Boolean).join(':') || null;
|
|
100
|
+
const tags = [];
|
|
101
|
+
if (content.queue)
|
|
102
|
+
tags.push(`queue:${content.queue}`);
|
|
103
|
+
if (content.name)
|
|
104
|
+
tags.push(`job:${content.name}`);
|
|
105
|
+
if (status === 'failed')
|
|
106
|
+
tags.push('failed');
|
|
107
|
+
if (durationMs >= this.slowMs)
|
|
108
|
+
tags.push('slow');
|
|
109
|
+
const input = {
|
|
110
|
+
type: EntryType.Job,
|
|
111
|
+
content,
|
|
112
|
+
familyHash,
|
|
113
|
+
durationMs,
|
|
114
|
+
};
|
|
115
|
+
if (tags.length > 0)
|
|
116
|
+
input.tags = tags;
|
|
117
|
+
ctx.record(input);
|
|
118
|
+
}
|
|
119
|
+
catch (recordError) {
|
|
120
|
+
const message = recordError instanceof Error ? recordError.message : String(recordError);
|
|
121
|
+
this.logger.error(`BullMqJobWatcher: failed to record job entry: ${message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=bullmq-job.watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bullmq-job.watcher.js","sourceRoot":"","sources":["../src/bullmq-job.watcher.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { BullMqJobWatcher } from './bullmq-job.watcher.js';
|
|
2
|
+
export type { BullMqJobWatcherOptions } from './bullmq-job.watcher.js';
|
|
3
|
+
export { buildJobContent } from './job-content.js';
|
|
4
|
+
export type { JobLike, JobStatus } from './job-content.js';
|
|
5
|
+
export { BullMqQueueManager } from './bull-mq-queue-manager.js';
|
|
6
|
+
export { discoverQueues, isQueueLike } from './queue-discovery.js';
|
|
7
|
+
export type { QueueLike } from './queue-discovery.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// packages/bullmq/src/index.ts
|
|
2
|
+
export { BullMqJobWatcher } from './bullmq-job.watcher.js';
|
|
3
|
+
export { buildJobContent } from './job-content.js';
|
|
4
|
+
export { BullMqQueueManager } from './bull-mq-queue-manager.js';
|
|
5
|
+
export { discoverQueues, isQueueLike } from './queue-discovery.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { JobContent } from '@dudousxd/nestjs-telescope';
|
|
2
|
+
/** The subset of a BullMQ `Job` this watcher reads. Kept structural so the
|
|
3
|
+
* content builder needs no bullmq runtime import and is trivially testable. */
|
|
4
|
+
export interface JobLike {
|
|
5
|
+
id?: string | number;
|
|
6
|
+
name?: string;
|
|
7
|
+
queueName?: string;
|
|
8
|
+
attemptsMade?: number;
|
|
9
|
+
opts?: {
|
|
10
|
+
attempts?: number;
|
|
11
|
+
};
|
|
12
|
+
data?: unknown;
|
|
13
|
+
/** Epoch ms when the job was enqueued (BullMQ `Job.timestamp`). */
|
|
14
|
+
timestamp?: number;
|
|
15
|
+
/** Epoch ms when the worker began processing (BullMQ `Job.processedOn`). */
|
|
16
|
+
processedOn?: number;
|
|
17
|
+
}
|
|
18
|
+
/** The outcomes this watcher records (a subset of core JobContent['status']). */
|
|
19
|
+
export type JobStatus = 'completed' | 'failed';
|
|
20
|
+
/** Normalize a BullMQ job + outcome into the canonical core `JobContent`.
|
|
21
|
+
* Redaction of `payload` is applied centrally by the core Recorder, not here.
|
|
22
|
+
* When `includeData` is false the payload is nulled (the field stays present
|
|
23
|
+
* so the persisted shape is stable). */
|
|
24
|
+
export declare function buildJobContent(job: JobLike, status: JobStatus, error: unknown, includeData: boolean): JobContent;
|
|
25
|
+
//# sourceMappingURL=job-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-content.d.ts","sourceRoot":"","sources":["../src/job-content.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D;gFACgF;AAChF,MAAM,WAAW,OAAO;IACtB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAa/C;;;yCAGyC;AACzC,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,OAAO,GACnB,UAAU,CAYZ"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function failureMessage(error) {
|
|
2
|
+
if (error instanceof Error)
|
|
3
|
+
return error.message;
|
|
4
|
+
return String(error);
|
|
5
|
+
}
|
|
6
|
+
function waitMsOf(job) {
|
|
7
|
+
return typeof job.processedOn === 'number' && typeof job.timestamp === 'number'
|
|
8
|
+
? job.processedOn - job.timestamp
|
|
9
|
+
: null;
|
|
10
|
+
}
|
|
11
|
+
/** Normalize a BullMQ job + outcome into the canonical core `JobContent`.
|
|
12
|
+
* Redaction of `payload` is applied centrally by the core Recorder, not here.
|
|
13
|
+
* When `includeData` is false the payload is nulled (the field stays present
|
|
14
|
+
* so the persisted shape is stable). */
|
|
15
|
+
export function buildJobContent(job, status, error, includeData) {
|
|
16
|
+
return {
|
|
17
|
+
id: job.id != null ? String(job.id) : null,
|
|
18
|
+
name: job.name ?? '',
|
|
19
|
+
queue: job.queueName ?? '',
|
|
20
|
+
payload: includeData ? (job.data ?? null) : null,
|
|
21
|
+
status,
|
|
22
|
+
attempts: typeof job.attemptsMade === 'number' ? job.attemptsMade : 0,
|
|
23
|
+
maxAttempts: typeof job.opts?.attempts === 'number' ? job.opts.attempts : null,
|
|
24
|
+
waitMs: waitMsOf(job),
|
|
25
|
+
failureReason: status === 'failed' ? failureMessage(error) : null,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=job-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-content.js","sourceRoot":"","sources":["../src/job-content.ts"],"names":[],"mappings":"AAqBA,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IACjD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,QAAQ,CAAC,GAAY;IAC5B,OAAO,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ;QAC7E,CAAC,CAAC,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC,SAAS;QACjC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED;;;yCAGyC;AACzC,MAAM,UAAU,eAAe,CAC7B,GAAY,EACZ,MAAiB,EACjB,KAAc,EACd,WAAoB;IAEpB,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI;QAC1C,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;QACpB,KAAK,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE;QAC1B,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;QAChD,MAAM;QACN,QAAQ,EAAE,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrE,WAAW,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;QAC9E,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC;QACrB,aAAa,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;KAClE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DiscoveryService } from '@nestjs/core';
|
|
2
|
+
/**
|
|
3
|
+
* A duck-typed BullMQ `Queue`. We avoid importing `bullmq`'s `Queue` at runtime
|
|
4
|
+
* (and even type-only) so this helper works whether or not the consumer's
|
|
5
|
+
* esbuild/tsc setup emits the dependency — structural matching is enough to
|
|
6
|
+
* read queues through the public getter API.
|
|
7
|
+
*/
|
|
8
|
+
export interface QueueLike {
|
|
9
|
+
name: string;
|
|
10
|
+
getJobCounts(...types: string[]): Promise<Record<string, number>>;
|
|
11
|
+
getJobs(types: string | string[], start?: number, end?: number, asc?: boolean): Promise<unknown[]>;
|
|
12
|
+
getJob(id: string): Promise<unknown>;
|
|
13
|
+
isPaused(): Promise<boolean>;
|
|
14
|
+
retryJobs?(opts: {
|
|
15
|
+
state?: string;
|
|
16
|
+
count?: number;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
add?(name: string, data: unknown, opts?: unknown): Promise<{
|
|
19
|
+
id?: string | null;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Structural shape of the bullmq `Job` mutation API we drive. We never import
|
|
24
|
+
* `Job` from bullmq; a single structural guard keeps the manager honest about
|
|
25
|
+
* what a fetched job must expose before we mutate it.
|
|
26
|
+
*/
|
|
27
|
+
export interface JobOps {
|
|
28
|
+
retry(state?: string): Promise<void>;
|
|
29
|
+
remove(): Promise<void>;
|
|
30
|
+
promote(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export declare function hasJobOps(value: unknown): value is JobOps;
|
|
33
|
+
export declare function isQueueLike(value: unknown): value is QueueLike;
|
|
34
|
+
/** Find all BullMQ Queue instances registered in the Nest container. */
|
|
35
|
+
export declare function discoverQueues(discovery: DiscoveryService): QueueLike[];
|
|
36
|
+
//# sourceMappingURL=queue-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-discovery.d.ts","sourceRoot":"","sources":["../src/queue-discovery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,OAAO,CACL,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,KAAK,CAAC,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,OAAO,GACZ,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACtB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAG7B,SAAS,CAAC,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CACpF;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAQzD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAU9D;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,EAAE,CASvE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function hasJobOps(value) {
|
|
2
|
+
if (typeof value !== 'object' || value === null)
|
|
3
|
+
return false;
|
|
4
|
+
const candidate = value;
|
|
5
|
+
return (typeof candidate.retry === 'function' &&
|
|
6
|
+
typeof candidate.remove === 'function' &&
|
|
7
|
+
typeof candidate.promote === 'function');
|
|
8
|
+
}
|
|
9
|
+
export function isQueueLike(value) {
|
|
10
|
+
if (typeof value !== 'object' || value === null)
|
|
11
|
+
return false;
|
|
12
|
+
const candidate = value;
|
|
13
|
+
return (typeof candidate.name === 'string' &&
|
|
14
|
+
typeof candidate.getJobCounts === 'function' &&
|
|
15
|
+
typeof candidate.getJobs === 'function' &&
|
|
16
|
+
typeof candidate.getJob === 'function' &&
|
|
17
|
+
typeof candidate.isPaused === 'function');
|
|
18
|
+
}
|
|
19
|
+
/** Find all BullMQ Queue instances registered in the Nest container. */
|
|
20
|
+
export function discoverQueues(discovery) {
|
|
21
|
+
const found = new Map();
|
|
22
|
+
for (const wrapper of discovery.getProviders()) {
|
|
23
|
+
const instance = wrapper.instance;
|
|
24
|
+
if (isQueueLike(instance) && !found.has(instance.name)) {
|
|
25
|
+
found.set(instance.name, instance);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return [...found.values()];
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=queue-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-discovery.js","sourceRoot":"","sources":["../src/queue-discovery.ts"],"names":[],"mappings":"AAqCA,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAgC,CAAC;IACnD,OAAO,CACL,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU;QACrC,OAAO,SAAS,CAAC,MAAM,KAAK,UAAU;QACtC,OAAO,SAAS,CAAC,OAAO,KAAK,UAAU,CACxC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAgC,CAAC;IACnD,OAAO,CACL,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ;QAClC,OAAO,SAAS,CAAC,YAAY,KAAK,UAAU;QAC5C,OAAO,SAAS,CAAC,OAAO,KAAK,UAAU;QACvC,OAAO,SAAS,CAAC,MAAM,KAAK,UAAU;QACtC,OAAO,SAAS,CAAC,QAAQ,KAAK,UAAU,CACzC,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,SAA2B;IACxD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC3C,KAAK,MAAM,OAAO,IAAI,SAAS,CAAC,YAAY,EAAE,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;AAC7B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dudousxd/nestjs-telescope-bullmq",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "BullMQ job watcher for @dudousxd/nestjs-telescope.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/DavideCarvalho/nestjs-telescope.git",
|
|
9
|
+
"directory": "packages/bullmq"
|
|
10
|
+
},
|
|
11
|
+
"author": "Davi Carvalho <davi@goflip.ai>",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"README.md",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@nestjs/bullmq": ">=10.0.0",
|
|
29
|
+
"@nestjs/common": ">=10.0.0",
|
|
30
|
+
"@nestjs/core": ">=10.0.0",
|
|
31
|
+
"bullmq": ">=5.0.0",
|
|
32
|
+
"reflect-metadata": ">=0.1.13",
|
|
33
|
+
"@dudousxd/nestjs-telescope": "1.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@nestjs/bullmq": "^11.0.0",
|
|
37
|
+
"@nestjs/common": "^11.0.0",
|
|
38
|
+
"@nestjs/core": "^11.0.0",
|
|
39
|
+
"@nestjs/testing": "^11.0.0",
|
|
40
|
+
"@types/node": "^20.0.0",
|
|
41
|
+
"@types/supertest": "^6.0.2",
|
|
42
|
+
"bullmq": "^5.0.0",
|
|
43
|
+
"reflect-metadata": "^0.2.2",
|
|
44
|
+
"rxjs": "^7.8.1",
|
|
45
|
+
"supertest": "^7.0.0",
|
|
46
|
+
"typescript": "^5.4.0",
|
|
47
|
+
"vitest": "^3.0.0",
|
|
48
|
+
"@dudousxd/nestjs-telescope": "1.0.0",
|
|
49
|
+
"@dudousxd/nestjs-telescope-testing": "1.0.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"nestjs",
|
|
56
|
+
"telescope",
|
|
57
|
+
"bullmq",
|
|
58
|
+
"queue",
|
|
59
|
+
"jobs",
|
|
60
|
+
"observability"
|
|
61
|
+
],
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsc -p tsconfig.json",
|
|
64
|
+
"test": "vitest run --passWithNoTests",
|
|
65
|
+
"test:watch": "vitest",
|
|
66
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
67
|
+
}
|
|
68
|
+
}
|