@servicelabsco/nestjs-utility-services 3.0.2 → 3.0.4

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
@@ -1,7 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ### Changed
6
+
7
+ - **`@SubscribeFor` supports multiple jobs per entity.** `SubscriberRegistry` stores
8
+ `Map<Entity, Job[]>`. `DispatcherSubscriber` fans out `afterInsert`/`afterUpdate` to every
9
+ bound job (registration order, sequential enqueue). `beforeInsert`/`beforeUpdate` still run on
10
+ the **first** registered job only. Standalone TypeORM subscribers remain forbidden on entities
11
+ that already have `@SubscribeFor` job(s). Consuming apps can add e.g. `LocalDocumentJob` with
12
+ `@SubscribeFor(DocumentEntity)` without wrapping platform `DocumentJob`.
13
+
3
14
  ## 3.0.0 — audit remediation (BREAKING)
4
15
 
16
+
5
17
  This is a **major** release. `2.0.15` was published from the `production` branch; `main` had since
6
18
  accumulated breaking changes that were sitting under an already-consumed patch version. Everything
7
19
  below lands at once.
package/README.md CHANGED
@@ -39,6 +39,11 @@ checklist (`instrument.ts`, `AppModule`, env vars, workers).
39
39
  breaker-by-breaker migration guide (internal auth, SSRF, PDF URLs, scripts, access checks,
40
40
  sessions, SES, migrations) and which escape hatches exist.
41
41
 
42
+ ### `@SubscribeFor` (entity → job bindings)
43
+
44
+ Multiple jobs may bind the same entity; after-hooks fan out, before-hooks stay on the first job.
45
+ See **[docs/subscribe-for.md](docs/subscribe-for.md)**.
46
+
42
47
  ## Installation
43
48
 
44
49
  ```bash
@@ -48,7 +48,8 @@ declare const es6Classes: {
48
48
  exceptions: (typeof NoLoggedUserException)[];
49
49
  libraries: (typeof CommonJob | typeof CachedProperty | typeof GenericIndexParser | typeof GenericShowParser | typeof DataManager | typeof ListManager | typeof RecordManager | typeof ReportDataManager | typeof ReportListManager | {
50
50
  register(entity: new (...args: any[]) => any, job: new (...args: any[]) => any): void;
51
- entries(): IterableIterator<[new (...args: any[]) => any, new (...args: any[]) => any]>;
51
+ jobsFor(entity: new (...args: any[]) => any): (new (...args: any[]) => any)[];
52
+ entries(): IterableIterator<[new (...args: any[]) => any, (new (...args: any[]) => any)[]]>;
52
53
  size(): number;
53
54
  clear(): void;
54
55
  } | typeof BaseMigrationUtility | typeof CommonConsumer | typeof DatabaseEventEvaluator | typeof Money | typeof SeederUtility)[];
@@ -1,7 +1,8 @@
1
1
  type ClassRef = new (...args: any[]) => any;
2
2
  export declare const SubscriberRegistry: {
3
3
  register(entity: ClassRef, job: ClassRef): void;
4
- entries(): IterableIterator<[ClassRef, ClassRef]>;
4
+ jobsFor(entity: ClassRef): ClassRef[];
5
+ entries(): IterableIterator<[ClassRef, ClassRef[]]>;
5
6
  size(): number;
6
7
  clear(): void;
7
8
  };
@@ -5,13 +5,17 @@ exports.SubscribeFor = SubscribeFor;
5
5
  const map = new Map();
6
6
  exports.SubscriberRegistry = {
7
7
  register(entity, job) {
8
- if (map.has(entity)) {
9
- const existing = map.get(entity);
10
- if (existing === job)
8
+ const existing = map.get(entity);
9
+ if (existing) {
10
+ if (existing.includes(job))
11
11
  return;
12
- throw new Error(`SubscribeFor: ${entity.name} is already bound to ${existing.name}; ` + `cannot also bind to ${job.name}.`);
12
+ existing.push(job);
13
+ return;
13
14
  }
14
- map.set(entity, job);
15
+ map.set(entity, [job]);
16
+ },
17
+ jobsFor(entity) {
18
+ return map.get(entity) ?? [];
15
19
  },
16
20
  entries() {
17
21
  return map.entries();
@@ -1 +1 @@
1
- {"version":3,"file":"subscriber.registry.js","sourceRoot":"","sources":["../../../src/common/libraries/subscriber.registry.ts"],"names":[],"mappings":";;;AA8CA,oCAIC;AAxCD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;AAE7B,QAAA,kBAAkB,GAAG;IAC9B,QAAQ,CAAC,MAAgB,EAAE,GAAa;QACpC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClB,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;YAKlC,IAAI,QAAQ,KAAK,GAAG;gBAAE,OAAO;YAC7B,MAAM,IAAI,KAAK,CAAC,iBAAiB,MAAM,CAAC,IAAI,wBAAwB,QAAQ,CAAC,IAAI,IAAI,GAAG,uBAAuB,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;QAChI,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzB,CAAC;IACD,OAAO;QACH,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IACD,IAAI;QACA,OAAO,GAAG,CAAC,IAAI,CAAC;IACpB,CAAC;IAED,KAAK;QACD,GAAG,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;CACJ,CAAC;AAWF,SAAgB,YAAY,CAAC,MAAgB;IACzC,OAAO,CAAC,MAAM,EAAE,EAAE;QACd,0BAAkB,CAAC,QAAQ,CAAC,MAAM,EAAE,MAA6B,CAAC,CAAC;IACvE,CAAC,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"subscriber.registry.js","sourceRoot":"","sources":["../../../src/common/libraries/subscriber.registry.ts"],"names":[],"mappings":";;;AAiEA,oCAIC;AAtDD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAwB,CAAC;AAE/B,QAAA,kBAAkB,GAAG;IAC9B,QAAQ,CAAC,MAAgB,EAAE,GAAa;QACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,QAAQ,EAAE,CAAC;YAGX,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,OAAO;YACnC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO;QACX,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,CAAC;IAKD,OAAO,CAAC,MAAgB;QACpB,OAAO,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC;IACD,OAAO;QACH,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IACD,IAAI;QACA,OAAO,GAAG,CAAC,IAAI,CAAC;IACpB,CAAC;IAED,KAAK;QACD,GAAG,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;CACJ,CAAC;AAmBF,SAAgB,YAAY,CAAC,MAAgB;IACzC,OAAO,CAAC,MAAM,EAAE,EAAE;QACd,0BAAkB,CAAC,QAAQ,CAAC,MAAM,EAAE,MAA6B,CAAC,CAAC;IACvE,CAAC,CAAC;AACN,CAAC"}
@@ -24,14 +24,18 @@ let DispatcherSubscriber = class DispatcherSubscriber extends common_subscriber_
24
24
  dataSource.subscribers.push(this);
25
25
  }
26
26
  async onModuleInit() {
27
- for (const [entityClass, jobClass] of subscriber_registry_1.SubscriberRegistry.entries()) {
28
- const job = this.moduleRef.get(jobClass, { strict: false });
29
- if (!job) {
30
- throw new Error(`DispatcherSubscriber: failed to resolve job ${jobClass.name} ` +
31
- `for entity ${entityClass.name} via ModuleRef. ` +
32
- `Is the job registered as a provider in its module's es6.classes.ts?`);
27
+ for (const [entityClass, jobClasses] of subscriber_registry_1.SubscriberRegistry.entries()) {
28
+ const resolved = [];
29
+ for (const jobClass of jobClasses) {
30
+ const job = this.moduleRef.get(jobClass, { strict: false });
31
+ if (!job) {
32
+ throw new Error(`DispatcherSubscriber: failed to resolve job ${jobClass.name} ` +
33
+ `for entity ${entityClass.name} via ModuleRef. ` +
34
+ `Is the job registered as a provider in its module's es6.classes.ts?`);
35
+ }
36
+ resolved.push(job);
33
37
  }
34
- this.jobs.set(entityClass, job);
38
+ this.jobs.set(entityClass, resolved);
35
39
  }
36
40
  this.assertNoConflicts();
37
41
  }
@@ -49,15 +53,18 @@ let DispatcherSubscriber = class DispatcherSubscriber extends common_subscriber_
49
53
  }
50
54
  async routeDispatch(evt) {
51
55
  const target = evt.metadata.target;
52
- const job = this.jobs.get(target);
53
- if (!job)
56
+ const jobs = this.jobs.get(target);
57
+ if (!jobs?.length)
54
58
  return;
55
59
  const data = this.getEventData(evt);
56
- return this.enqueueDispatch(evt.queryRunner, job, data);
60
+ for (const job of jobs) {
61
+ await this.enqueueDispatch(evt.queryRunner, job, data);
62
+ }
57
63
  }
58
64
  async routeBeforeHook(evt, hook) {
59
65
  const target = evt.metadata.target;
60
- const job = this.jobs.get(target);
66
+ const jobs = this.jobs.get(target);
67
+ const job = jobs?.[0];
61
68
  if (!job || typeof job[hook] !== 'function')
62
69
  return;
63
70
  return job[hook](evt);
@@ -73,8 +80,8 @@ let DispatcherSubscriber = class DispatcherSubscriber extends common_subscriber_
73
80
  if (listened && this.jobs.has(listened)) {
74
81
  const standaloneName = sub.constructor?.name ?? 'unknown subscriber';
75
82
  throw new Error(`DispatcherSubscriber: ${listened.name} is both decorated with @SubscribeFor ` +
76
- `and has a standalone subscriber (${standaloneName}). Pick one — delete the standalone ` +
77
- `subscriber file, or remove @SubscribeFor from the job.`);
83
+ `job(s) and has a standalone subscriber (${standaloneName}). Pick one — delete the standalone ` +
84
+ `subscriber file, or remove @SubscribeFor from the job(s).`);
78
85
  }
79
86
  }
80
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.subscriber.js","sourceRoot":"","sources":["../../../src/platformUtility/subscribers/dispatcher.subscriber.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dispatcher.subscriber.js","sourceRoot":"","sources":["../../../src/platformUtility/subscribers/dispatcher.subscriber.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ # `@SubscribeFor` — multi-job entity bindings
2
+
3
+ `@SubscribeFor(EntityClass)` binds a `CommonJob` (or subclass) to TypeORM
4
+ insert/update events for that entity. `DispatcherSubscriber` routes those
5
+ events; you do **not** need a standalone `*.subscriber.ts` for pure
6
+ after-persist side effects.
7
+
8
+ ## Multiple jobs per entity
9
+
10
+ More than one job may decorate the same entity. Typical pattern: keep the
11
+ platform job (e.g. `DocumentJob`) and add an app-local job for tenant-specific
12
+ side effects.
13
+
14
+ ```ts
15
+ import { Injectable } from '@nestjs/common';
16
+ import { CommonJob, DatabaseEventDto, DocumentEntity, SubscribeFor } from '@servicelabsco/nestjs-utility-services';
17
+
18
+ @SubscribeFor(DocumentEntity)
19
+ @Injectable()
20
+ export class LocalDocumentJob extends CommonJob {
21
+ async handle(evt: DatabaseEventDto<DocumentEntity>) {
22
+ // app-local side effects — do not wrap DocumentJob.handle
23
+ }
24
+ }
25
+ ```
26
+
27
+ Register `LocalDocumentJob` as a Nest provider (your module’s `es6.classes` /
28
+ providers). Each job keeps its own BullMQ hash / queue identity.
29
+
30
+ ### Behaviour
31
+
32
+ | Hook | Behaviour |
33
+ | --- | --- |
34
+ | `afterInsert` / `afterUpdate` | Fan-out to **all** bound jobs, registration order (sequential enqueue). |
35
+ | `beforeInsert` / `beforeUpdate` | **First** registered job only (single owner for pre-persist mutation). |
36
+ | Standalone `CommonSubscriber` on the same entity | Still **throws at boot** — pick `@SubscribeFor` **or** a standalone subscriber, not both. |
37
+
38
+ Platform modules usually load before the app, so NU’s job is typically first in
39
+ the list when both bind the same entity.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicelabsco/nestjs-utility-services",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "NestJS generic packages to support development",
5
5
  "author": "Hemant Kumar Sah <hemantanshu@gmail.com>",
6
6
  "license": "MIT",
@@ -12,7 +12,8 @@
12
12
  "CHANGELOG.md",
13
13
  "LICENSE",
14
14
  "docs/sentry-reporting.md",
15
- "docs/upgrade-3.0-backward-compatibility.md"
15
+ "docs/upgrade-3.0-backward-compatibility.md",
16
+ "docs/subscribe-for.md"
16
17
  ],
17
18
  "types": "dist/index.d.ts",
18
19
  "scripts": {
@@ -1,13 +0,0 @@
1
- {
2
- "javascript.preferences.importModuleSpecifier": "relative",
3
- // "javascript.preferences.importModuleSpecifierEnding": "minimal",
4
- "javascript.preferences.quoteStyle": "single",
5
- "path-intellisense.absolutePathToWorkspace": false,
6
- "workbench.editor.limit.enabled": true,
7
- "workbench.editor.limit.value": 5,
8
- "git.ignoreLimitWarning": true,
9
- "sonarlint.connectedMode.project": {
10
- "connectionId": "https-sq-servicelabs-co",
11
- "projectKey": "servicelabsco_nestjs-utility-services_AYbKa4hwTzQzhdLPPxIE"
12
- }
13
- }