@bymax-one/nest-queue 1.0.9 → 1.0.11

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 CHANGED
@@ -7,6 +7,57 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.0.11] — 2026-08-10
11
+
12
+ **Documentation only.** The shipped declaration comments change (`dts` preserves them); no runtime
13
+ code changed.
14
+
15
+ ### Fixed
16
+
17
+ - The deduplication example — in the `QueueService` JSDoc and in
18
+ `docs/technical_specification.md` — now scopes the dedup `id` by tenant
19
+ (`` `${tenantId}:reindex:${term}` ``). The `id` is a shared key across the queue, so a
20
+ multi-tenant producer keying on the value alone lets one tenant's enqueue suppress another
21
+ tenant's job for the same value; the example previously demonstrated exactly that.
22
+
23
+ ## [1.0.10] — 2026-08-06
24
+
25
+ **Documentation and tooling only.** `dist/` is byte-identical to `1.0.9`; no source file
26
+ changed.
27
+
28
+ ### Fixed
29
+
30
+ - The quality bullet claimed "no suppression comments" while the source carries five
31
+ `// Stryker disable` comments. The claim was about type suppressions and read as false to
32
+ anyone who grepped for it; it now says what is true — no `@ts-ignore` and no
33
+ `eslint-disable`.
34
+
35
+ ### Security
36
+
37
+ - **The OSV scan was passing because it had nothing to read.** The workflow ran
38
+ `osv-scanner` with no `actions/checkout` step, so it walked an empty workspace and
39
+ reported "No package sources found" and "No lockfiles found" before exiting 0. A green
40
+ check meant the scanner found no files, which is indistinguishable from finding no
41
+ vulnerabilities. With the checkout in place it scans the lockfile, which is how the next
42
+ item was found.
43
+ - **`js-yaml` is patched to the fixed release** (GHSA-5p4m-2wfm-xmqj, CVSS 7.5). The
44
+ override floors here already scoped it per major but admitted `3.15.0` and `4.3.0`, both
45
+ covered by the advisory; they are raised to `^3.15.1` and `^4.3.1`. It reaches this repo
46
+ only through `jest` -> `babel-plugin-istanbul` -> `@istanbuljs/load-nyc-config`, and
47
+ `dependencies` is empty, so nothing here ships it and no consumer was exposed. `dist/` is
48
+ unaffected.
49
+
50
+ ### Added
51
+
52
+ - `check:mutants` gate (`scripts/check-mutation-directives.mjs`) — validates every
53
+ `// Stryker` comment against the grammar Stryker's own parser accepts, rejecting a reason
54
+ written after `--` instead of a colon (silently dropped, and the report then shows
55
+ `Ignored using a comment`), a reason wrapped onto a second comment line (the report keeps
56
+ only the first fragment), and a mutator name Stryker does not know, which matches nothing
57
+ and so silences nothing. Wired into CI and `prepublishOnly`.
58
+ - `docs/mutation_testing_plan.md` carries the suppression policy now shared, word for word,
59
+ across the `@bymax-one/nest-*` libraries.
60
+
10
61
  ## [1.0.9] — 2026-08-06
11
62
 
12
63
  **Published-artifact change, not a behavioural one.** `dist/` differs from `1.0.8` — this
@@ -405,6 +456,8 @@ v6 peer range.
405
456
 
406
457
  ---
407
458
 
459
+ [1.0.11]: https://github.com/bymaxone/nest-queue/compare/v1.0.10...v1.0.11
460
+ [1.0.10]: https://github.com/bymaxone/nest-queue/compare/v1.0.9...v1.0.10
408
461
  [1.0.9]: https://github.com/bymaxone/nest-queue/compare/v1.0.8...v1.0.9
409
462
  [1.0.8]: https://github.com/bymaxone/nest-queue/compare/v1.0.7...v1.0.8
410
463
  [1.0.7]: https://github.com/bymaxone/nest-queue/compare/v1.0.6...v1.0.7
package/README.md CHANGED
@@ -87,7 +87,7 @@ peer dependencies, so you control exact versions and the supply-chain surface st
87
87
  - ✅ **Stable error catalog** — namespaced codes exported from both subpaths, so a frontend maps a `code` without importing the server
88
88
  - ✅ **Dual-format output** — ESM + CJS with declarations for each format, verified against the packed tarball on every PR
89
89
  - ✅ **Opt-in observability** — OpenTelemetry passthrough via `bullmq-otel`, plus a `MetricsService` with a configurable TTL cache
90
- - ✅ **Typed end to end** — TypeScript `strict` with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`; zero `any`, no suppression comments
90
+ - ✅ **Typed end to end** — TypeScript `strict` with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`; zero `any`, no `@ts-ignore` and no `eslint-disable`
91
91
 
92
92
  ---
93
93
 
@@ -378,6 +378,11 @@ exports.QueueService = class QueueService {
378
378
  * **producers** and do not change the at-least-once guarantee on the
379
379
  * **consumer** side.
380
380
  *
381
+ * The deduplication `id` is a shared key across the queue, so a multi-tenant
382
+ * producer MUST scope it by tenant: keyed on the value alone
383
+ * (`` `reindex:${term}` ``), tenant A enqueuing a term suppresses tenant B's job
384
+ * for the same term. Include the tenant (`` `${tenantId}:reindex:${term}` ``).
385
+ *
381
386
  * Deduplication modes (`deduplication: { id, ttl?, extend?, replace?, keepLastIfActive? }`):
382
387
  * - Simple `{ id }` — collapse duplicates until the in-flight job completes or fails.
383
388
  * - Throttle `{ id, ttl }` — ignore duplicates for `ttl` milliseconds.
@@ -391,9 +396,10 @@ exports.QueueService = class QueueService {
391
396
  * @param data - Typed job payload.
392
397
  * @param options - Per-job BullMQ options (priority, delay, `jobId`, `deduplication`, ...).
393
398
  * @returns The created job.
394
- * @example Throttle: at most one reindex per term every 5 seconds.
399
+ * @example Throttle: at most one reindex per term every 5 seconds. The dedup key
400
+ * carries the tenant so one tenant's term cannot suppress another's.
395
401
  * await queueService.enqueue('search', 'reindex', { term }, {
396
- * deduplication: { id: `reindex:${term}`, ttl: 5_000 },
402
+ * deduplication: { id: `${tenantId}:reindex:${term}`, ttl: 5_000 },
397
403
  * })
398
404
  */
399
405
  async enqueue(queueName, jobName, data, options) {
@@ -421,6 +421,11 @@ declare class QueueService {
421
421
  * **producers** and do not change the at-least-once guarantee on the
422
422
  * **consumer** side.
423
423
  *
424
+ * The deduplication `id` is a shared key across the queue, so a multi-tenant
425
+ * producer MUST scope it by tenant: keyed on the value alone
426
+ * (`` `reindex:${term}` ``), tenant A enqueuing a term suppresses tenant B's job
427
+ * for the same term. Include the tenant (`` `${tenantId}:reindex:${term}` ``).
428
+ *
424
429
  * Deduplication modes (`deduplication: { id, ttl?, extend?, replace?, keepLastIfActive? }`):
425
430
  * - Simple `{ id }` — collapse duplicates until the in-flight job completes or fails.
426
431
  * - Throttle `{ id, ttl }` — ignore duplicates for `ttl` milliseconds.
@@ -434,9 +439,10 @@ declare class QueueService {
434
439
  * @param data - Typed job payload.
435
440
  * @param options - Per-job BullMQ options (priority, delay, `jobId`, `deduplication`, ...).
436
441
  * @returns The created job.
437
- * @example Throttle: at most one reindex per term every 5 seconds.
442
+ * @example Throttle: at most one reindex per term every 5 seconds. The dedup key
443
+ * carries the tenant so one tenant's term cannot suppress another's.
438
444
  * await queueService.enqueue('search', 'reindex', { term }, {
439
- * deduplication: { id: `reindex:${term}`, ttl: 5_000 },
445
+ * deduplication: { id: `${tenantId}:reindex:${term}`, ttl: 5_000 },
440
446
  * })
441
447
  */
442
448
  enqueue<TData = unknown, TResult = unknown>(queueName: string, jobName: string, data: TData, options?: JobsOptions): Promise<TypedJob<TData, TResult>>;
@@ -421,6 +421,11 @@ declare class QueueService {
421
421
  * **producers** and do not change the at-least-once guarantee on the
422
422
  * **consumer** side.
423
423
  *
424
+ * The deduplication `id` is a shared key across the queue, so a multi-tenant
425
+ * producer MUST scope it by tenant: keyed on the value alone
426
+ * (`` `reindex:${term}` ``), tenant A enqueuing a term suppresses tenant B's job
427
+ * for the same term. Include the tenant (`` `${tenantId}:reindex:${term}` ``).
428
+ *
424
429
  * Deduplication modes (`deduplication: { id, ttl?, extend?, replace?, keepLastIfActive? }`):
425
430
  * - Simple `{ id }` — collapse duplicates until the in-flight job completes or fails.
426
431
  * - Throttle `{ id, ttl }` — ignore duplicates for `ttl` milliseconds.
@@ -434,9 +439,10 @@ declare class QueueService {
434
439
  * @param data - Typed job payload.
435
440
  * @param options - Per-job BullMQ options (priority, delay, `jobId`, `deduplication`, ...).
436
441
  * @returns The created job.
437
- * @example Throttle: at most one reindex per term every 5 seconds.
442
+ * @example Throttle: at most one reindex per term every 5 seconds. The dedup key
443
+ * carries the tenant so one tenant's term cannot suppress another's.
438
444
  * await queueService.enqueue('search', 'reindex', { term }, {
439
- * deduplication: { id: `reindex:${term}`, ttl: 5_000 },
445
+ * deduplication: { id: `${tenantId}:reindex:${term}`, ttl: 5_000 },
440
446
  * })
441
447
  */
442
448
  enqueue<TData = unknown, TResult = unknown>(queueName: string, jobName: string, data: TData, options?: JobsOptions): Promise<TypedJob<TData, TResult>>;
@@ -376,6 +376,11 @@ var QueueService = class {
376
376
  * **producers** and do not change the at-least-once guarantee on the
377
377
  * **consumer** side.
378
378
  *
379
+ * The deduplication `id` is a shared key across the queue, so a multi-tenant
380
+ * producer MUST scope it by tenant: keyed on the value alone
381
+ * (`` `reindex:${term}` ``), tenant A enqueuing a term suppresses tenant B's job
382
+ * for the same term. Include the tenant (`` `${tenantId}:reindex:${term}` ``).
383
+ *
379
384
  * Deduplication modes (`deduplication: { id, ttl?, extend?, replace?, keepLastIfActive? }`):
380
385
  * - Simple `{ id }` — collapse duplicates until the in-flight job completes or fails.
381
386
  * - Throttle `{ id, ttl }` — ignore duplicates for `ttl` milliseconds.
@@ -389,9 +394,10 @@ var QueueService = class {
389
394
  * @param data - Typed job payload.
390
395
  * @param options - Per-job BullMQ options (priority, delay, `jobId`, `deduplication`, ...).
391
396
  * @returns The created job.
392
- * @example Throttle: at most one reindex per term every 5 seconds.
397
+ * @example Throttle: at most one reindex per term every 5 seconds. The dedup key
398
+ * carries the tenant so one tenant's term cannot suppress another's.
393
399
  * await queueService.enqueue('search', 'reindex', { term }, {
394
- * deduplication: { id: `reindex:${term}`, ttl: 5_000 },
400
+ * deduplication: { id: `${tenantId}:reindex:${term}`, ttl: 5_000 },
395
401
  * })
396
402
  */
397
403
  async enqueue(queueName, jobName, data, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-queue",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "NestJS dynamic module wrapping BullMQ — typed jobs, flows, job schedulers, deduplication, OpenTelemetry, graceful shutdown",
5
5
  "author": "Bymax One <support@bymax.one>",
6
6
  "license": "MIT",
@@ -56,15 +56,16 @@
56
56
  "scripts": {
57
57
  "build": "pnpm clean && tsup",
58
58
  "check:exports": "attw --pack .",
59
+ "check:mutants": "node scripts/check-mutation-directives.mjs",
59
60
  "check:published": "node scripts/check-published-surface.mjs",
60
61
  "clean": "rm -rf dist coverage",
61
62
  "lint": "eslint src scripts test",
62
63
  "lint:fix": "eslint src scripts test --fix",
63
64
  "mutation": "stryker run",
65
+ "mutation:full": "node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run",
64
66
  "mutation:dry-run": "stryker run --dryRunOnly",
65
- "mutation:incremental": "stryker run --incremental",
66
67
  "prepare": "husky",
67
- "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published",
68
+ "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published",
68
69
  "release": "npm publish --provenance --access public",
69
70
  "size": "node scripts/check-size.mjs",
70
71
  "smoke": "node scripts/dogfood-smoke-test.mjs",