@generazioneai/events 0.0.1 → 0.1.1

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +106 -1
  3. package/dist/connection.d.ts +23 -0
  4. package/dist/connection.d.ts.map +1 -0
  5. package/dist/connection.js +113 -0
  6. package/dist/connection.js.map +1 -0
  7. package/dist/consumers.service.d.ts +54 -0
  8. package/dist/consumers.service.d.ts.map +1 -0
  9. package/dist/consumers.service.js +346 -0
  10. package/dist/consumers.service.js.map +1 -0
  11. package/dist/decorators.d.ts +12 -0
  12. package/dist/decorators.d.ts.map +1 -0
  13. package/dist/decorators.js +14 -0
  14. package/dist/decorators.js.map +1 -0
  15. package/dist/errors.d.ts +13 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +24 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/events.module.d.ts +20 -0
  20. package/dist/events.module.d.ts.map +1 -0
  21. package/dist/events.module.js +98 -0
  22. package/dist/events.module.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +29 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/metrics.d.ts +26 -0
  28. package/dist/metrics.d.ts.map +1 -0
  29. package/dist/metrics.js +92 -0
  30. package/dist/metrics.js.map +1 -0
  31. package/dist/outbox.service.d.ts +24 -0
  32. package/dist/outbox.service.d.ts.map +1 -0
  33. package/dist/outbox.service.js +88 -0
  34. package/dist/outbox.service.js.map +1 -0
  35. package/dist/prisma-resolver.d.ts +11 -0
  36. package/dist/prisma-resolver.d.ts.map +1 -0
  37. package/dist/prisma-resolver.js +46 -0
  38. package/dist/prisma-resolver.js.map +1 -0
  39. package/dist/relay.service.d.ts +32 -0
  40. package/dist/relay.service.d.ts.map +1 -0
  41. package/dist/relay.service.js +153 -0
  42. package/dist/relay.service.js.map +1 -0
  43. package/dist/tokens.d.ts +4 -0
  44. package/dist/tokens.d.ts.map +1 -0
  45. package/dist/tokens.js +7 -0
  46. package/dist/tokens.js.map +1 -0
  47. package/dist/types.d.ts +116 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +34 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/uuid.d.ts +7 -0
  52. package/dist/uuid.d.ts.map +1 -0
  53. package/dist/uuid.js +32 -0
  54. package/dist/uuid.js.map +1 -0
  55. package/package.json +66 -4
  56. package/prisma/events.prisma +30 -0
  57. package/sql/001-events-outbox-inbox-rientro.sql +3 -0
  58. package/sql/001-events-outbox-inbox.sql +28 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 — 2026-10-31
4
+
5
+ - Le metriche `events_*` passano anche all'API di OpenTelemetry (contatori e gauge osservabili, no-op senza SDK; `@opentelemetry/api` ~1.9.0
6
+ come `@generazioneai/jobs`): con `@generazioneai/telemetry` arrivano al Collector e a VictoriaMetrics senza un endpoint in ogni servizio.
7
+ `EventsMetrics.render()` e i nomi non cambiano. I servizi la prendono alzando la versione, senza altro codice.
8
+
9
+ ## 0.1.0 — 2026-10-25
10
+
11
+ Prima versione (treno 3+4, tappa E1; ADR-009, spec «Fondamenta enterprise» §1).
12
+
13
+ - `EventsModule.forRoot({ service, stream?, prisma, … })`, globale; `EVENTS_MODE` off | outbox | on, `EVENTS_SIGNATURE` off | shadow | enforce,
14
+ `EVENTS_NATS_URL` (ripiego `NATS_URL`).
15
+ - `enqueue` ammette anche `audit.<svc>[.…]`, pubblicato sullo stream `AUDIT`.
16
+ - Fix: il riconoscimento della transazione non guarda più `$transaction` sul client (Prisma 7 lo ha anche nel `tx`) ma usa `SAVEPOINT` lato Postgres; con `EVENTS_MODE=off` `enqueue` non lancia mai. Prove con il client Prisma 7 vero.
17
+ - `OutboxService.enqueue(tx, { subject, aggregate, juridicalId?, data })`: scrive `outbox_events` nella transazione del dato; fuori
18
+ da una `$transaction` lancia `EVENTS_OUTSIDE_TX`.
19
+ - Relay: `FOR UPDATE SKIP LOCKED`, 100 righe, `Nats-Msg-Id` = id, attesa del PubAck; pulizia a 7 giorni.
20
+ - `@OnEvent(subject, { consumer, maxAckPending? })`: consumer durevole pull, transazione + `inbox_processed` + gestore + ack.
21
+ - Busta `EventEnvelope` v1 con UUID v7; intestazioni `Nats-Msg-Id`, `Gen-Tenant`, `Gen-Event-Version`, `Gen-Sig`, `traceparent`.
22
+ - Riprove 10s, 1m, 5m, 30m, 2h, 6h, 12h (8 consegne), poi `dlq.<stream>.<consumer>`; `EventValidationError` va subito in dead letter;
23
+ avviso `MAX_DELIVERIES` per i pod morti a metà.
24
+ - Metriche `events_*` (`EventsMetrics.render()`).
25
+ - `streamMaxBytes` / `EVENTS_STREAM_MAX_BYTES` (default 5 GiB, invariato): il tetto dello stream è riservato sul `max_file_store` di NATS,
26
+ e su un NATS piccolo due stream da 5 GiB non ci stanno (era la causa dei timeout delle prove in CI).
27
+ - `sql/001-events-outbox-inbox.sql` (+ rientro) e `prisma/events.prisma` da copiare nei servizi.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
1
  # @generazioneai/events
2
2
 
3
- Segnaposto. Il pacchetto vero arriva con la 0.1.0.
3
+ Eventi **durevoli** fra i servizi della piattaforma (ADR-009, spec «Fondamenta enterprise» §1): l'evento si scrive nella stessa
4
+ transazione del dato (outbox), un relay lo pubblica su NATS JetStream, chi lo riceve lo applica una volta sola (inbox) con riprove
5
+ e dead letter. Consegna «almeno una volta», effetto «una volta sola». I **comandi** restano su NATS core, invariati.
6
+
7
+ ```bash
8
+ npm i @generazioneai/events
9
+ ```
10
+
11
+ Peer: `@nestjs/common`, `@nestjs/core`, `reflect-metadata`. Client NATS: `@nats-io/transport-node` + `@nats-io/jetstream`, su una connessione
12
+ **propria** (diversa da quella del trasporto Nest).
13
+
14
+ ## Nel servizio
15
+
16
+ 1. Copia nello schema le tabelle: `prisma/events.prisma` (Prisma) o `sql/001-events-outbox-inbox.sql` + `-rientro.sql` (HR: registro SQL).
17
+ Non cambiare i nomi di tabelle e colonne: la libreria li usa in SQL.
18
+ 2. Registra il modulo:
19
+
20
+ ```ts
21
+ EventsModule.forRoot({
22
+ service: 'id', // id | hr | train | ops | notify | ai | sign | bill | audit
23
+ prisma: PrismaService, // la classe (risolta da Nest) o l'istanza
24
+ // facoltativi:
25
+ signer: (p) => signEventToken(ctx, { eventId: p.eventId, subject: p.subject }), // authz 0.7
26
+ verifier: (token, p) => verifyEventToken(token, { eventId: p.eventId, subject: p.subject, mode: p.mode }),
27
+ traceparent: () => currentTraceparent(), // @generazioneai/telemetry
28
+ })
29
+ ```
30
+
31
+ 3. Pubblica **dentro la transazione del dato**:
32
+
33
+ ```ts
34
+ await this.prisma.$transaction(async (tx) => {
35
+ const emp = await tx.employment.update(…);
36
+ await this.outbox.enqueue(tx, { // OutboxService
37
+ subject: 'evt.id.employment.changed', // evt.<servizio>.<entità>.<fatto>, il servizio è il proprio
38
+ aggregate: { type: 'Employment', id: emp.id, version: emp.version },
39
+ juridicalId: emp.juridicalId,
40
+ data: { juridicalId, individualId, juridicalIndividualId, status: 'ENDED' }, // solo id e stati
41
+ });
42
+ });
43
+ ```
44
+
45
+ Fuori da una transazione lancia `EVENTS_OUTSIDE_TX`. Il riconoscimento è fatto dal database, non dalla forma del client (in Prisma 7 anche il `tx` ha `$transaction`):
46
+ la prima `enqueue` su un `tx` esegue `SAVEPOINT` (esiste solo dentro un blocco di transazione, altrimenti Postgres risponde 25P01) e `RELEASE`; il `tx` verificato
47
+ non si riprova. Con `EVENTS_MODE=off` non lancia mai, non scrive nulla e restituisce `null`.
48
+
49
+ 4. Consuma:
50
+
51
+ ```ts
52
+ @OnEvent('evt.id.employment.changed', { consumer: 'hr.employment-changed', maxAckPending: 50 })
53
+ async onEmploymentChanged(evt: EventEnvelope<EmploymentChanged>, tx: PrismaTx) {
54
+ if (!evt.data.individualId) throw new EventValidationError('manca individualId'); // subito in dead letter
55
+ await tx.… // gli effetti nella transazione della libreria
56
+ }
57
+ ```
58
+
59
+ La libreria apre la transazione, inserisce `(consumer, eventId)` in `inbox_processed` (già presente: salta il gestore), chiama il gestore
60
+ col `tx`, fa COMMIT e poi `ack`. Il gestore deve restare dentro il `tx` per gli effetti sul database; gli effetti fuori dal database
61
+ (chiamate NATS, email) devono essere idempotenti.
62
+
63
+ ## Contratto
64
+
65
+ - **Busta** `EventEnvelope<T> = { v: 1, id (UUID v7), subject, aggregateType, aggregateId, aggregateVersion, occurredAt, juridicalId?, data: T }`.
66
+ - **Intestazioni**: `Nats-Msg-Id` (= `id`), `Gen-Tenant`, `Gen-Event-Version` (1), `Gen-Sig` (JWT della firma interna, audience `events`), `traceparent`.
67
+ - **Subject ammessi in `enqueue`**: `evt.<proprio servizio>.…` (stream `EVT_<SVC>`) e `audit.<proprio servizio>[.…]` (stream `AUDIT`, creato da `genai-audit`; NATS sceglie lo stream dal subject, quindi lo stream deve esistere). Altri prefissi: `EVENTS_INVALID`. `@OnEvent` su `audit.…` si lega a `AUDIT`.
68
+ - **Stream** `EVT_<SVC>` (`evt.<svc>.>`): lo crea il proprietario all'avvio (R3, file, 7 giorni o 5 GiB, `discard: old`, `duplicate_window` 10 m; `streamReplicas`
69
+ o `EVENTS_STREAM_REPLICAS` per sviluppo). Il tetto (`streamMaxBytes` o `EVENTS_STREAM_MAX_BYTES`) JetStream lo riserva sul `max_file_store`
70
+ del server: con un NATS piccolo (sviluppo, staging, CI) va abbassato, se no il secondo stream non si crea e l'avvio degli eventi resta a riprovare. `DLQ` (`dlq.>`) e `AUDIT` li crea `genai-audit`: se `DLQ` non esiste il messaggio non va perso, resta in coda.
71
+ - **Consumer**: pull, durevole, `ack_wait` 30 s (con `working()` ogni 3 s mentre il gestore lavora), `max_deliver` 8, `backoff` 10 s, 1 m, 5 m, 30 m, 2 h, 6 h, 12 h,
72
+ `filter_subject` stretto. Il nome del durable è il nome logico con i punti sostituiti da `_` (NATS non li ammette): `hr.employment-changed` → `hr_employment-changed`;
73
+ nella dead letter e nelle metriche resta il nome logico. Lo stream si deduce dal subject (`evt.id.…` → `EVT_ID`).
74
+ - **Riprove e dead letter**: errore del gestore → `nak` con l'attesa crescente; all'ottava consegna (o subito per `EventValidationError`, busta non valida, firma
75
+ rifiutata) copia in `dlq.<stream>.<consumer>` con `Gen-Dlq-Reason`, `Gen-Dlq-Stream`, `Gen-Dlq-Consumer`, `Gen-Dlq-Seq`, `Gen-Dlq-Deliveries`, `Gen-Original-Id`,
76
+ `Gen-Original-Subject` e il **corpo originale**, poi `term`. Per i pod morti a metà ascolta l'avviso `$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.<stream>.>`
77
+ (un solo ricevente per servizio, gruppo di coda) e copia il messaggio per numero di sequenza; il `Nats-Msg-Id` della copia è
78
+ `dlq-<stream>-<consumer>-<seq>`, quindi i due percorsi non duplicano.
79
+ - **Firma** (`EVENTS_SIGNATURE`): off = niente; shadow = firma, verifica e conta gli errori senza rifiutare; enforce = rifiuta (dead letter) le firme assenti o errate
80
+ e richiede `signer` e `verifier` (altrimenti l'avvio fallisce). La libreria non importa authz: `signer` e `verifier` si iniettano (con authz 0.7: `signEventToken`
81
+ / `verifyEventToken`).
82
+ - **Connessione**: `EVENTS_NATS_URL` (ripiego `NATS_URL`); `NATS_NKEY_SEED` → autenticazione nkey; `connection: {…}` per altre opzioni di `connect`.
83
+ Con NATS giù l'app parte lo stesso e l'outbox accumula.
84
+
85
+ ## Garanzie e limiti
86
+
87
+ - Pubblicazione **esattamente una volta** nella finestra di 10 minuti (`Nats-Msg-Id`); oltre, il doppione lo scarta l'inbox di chi consuma.
88
+ - **Ordine**: il relay legge per id (UUID v7), ma transazioni che committano fuori ordine, più repliche (`SKIP LOCKED`) e le riprove non lo garantiscono. Chi
89
+ ha bisogno dell'ordine confronta `aggregateVersion`. I consumer elaborano un messaggio alla volta per consumer.
90
+ - Il relay tiene aperta una transazione mentre pubblica (fino a `relayBatchSize` righe, 100); per questo usa il timeout di 60 s di `$transaction`.
91
+ - `JetStream` non è uno scheduler: le code con regole di dominio restano nei servizi.
92
+
93
+ ## Metriche
94
+
95
+ `EventsMetrics.render()` produce testo Prometheus: `events_outbox_pending`, `events_outbox_oldest_seconds`, `events_published_total{subject}`,
96
+ `events_delivered_total{consumer}`, `events_nak_total{consumer}`, `events_term_total{consumer}`, `events_consumer_lag{consumer}`, più
97
+ `events_duplicates_total{consumer}` e `events_signature_invalid_total{mode}`. Il servizio le aggiunge al suo endpoint (o telemetry le legge).
98
+
99
+ ## Test
100
+
101
+ `npm test -w @generazioneai/events` (prima `npm run test:prepare -w @generazioneai/events`, genera il client Prisma 7 vero delle prove): le prove unitarie girano sempre; quelle di integrazione vogliono `EVENTS_TEST_NATS_URL` (un `nats-server` con JetStream) e
102
+ `EVENTS_TEST_DATABASE_URL` (Postgres), altrimenti sono saltate (con `EVENTS_REQUIRE_INTEGRATION=1` mancano e il test fallisce). In locale:
103
+
104
+ ```bash
105
+ docker run -d --rm --name evt-nats -p 14222:4222 nats:2.12 -js
106
+ docker run -d --rm --name evt-pg -e POSTGRES_PASSWORD=postgres -p 15432:5432 postgres:18
107
+ EVENTS_TEST_NATS_URL=nats://127.0.0.1:14222 EVENTS_TEST_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:15432/postgres npm test -w @generazioneai/events
108
+ ```
@@ -0,0 +1,23 @@
1
+ import { type NatsConnection } from '@nats-io/transport-node';
2
+ import { type JetStreamClient, type JetStreamManager } from '@nats-io/jetstream';
3
+ import type { ResolvedEventsOptions } from './types';
4
+ /** Connessione NATS propria della libreria (diversa da quella del trasporto Nest) + JetStream. */
5
+ export declare class EventsConnection {
6
+ private readonly opts;
7
+ private readonly log;
8
+ private nc?;
9
+ private js?;
10
+ private jsm?;
11
+ private starting?;
12
+ constructor(opts: ResolvedEventsOptions);
13
+ /** Connette (una sola volta). Con NATS giù aspetta: l'app non si ferma, l'outbox accumula. */
14
+ ensure(): Promise<void>;
15
+ get connected(): boolean;
16
+ get client(): NatsConnection;
17
+ get jetstream(): JetStreamClient;
18
+ get manager(): JetStreamManager;
19
+ /** Crea o aggiorna lo stream del servizio (`EVT_<SVC>`, `evt.<svc>.>`): R3, file, 7 giorni o `streamMaxBytes` (5 GiB), `duplicate_window` 10 m. */
20
+ ensureOwnStream(): Promise<void>;
21
+ close(): Promise<void>;
22
+ }
23
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAIrD,kGAAkG;AAClG,qBACa,gBAAgB;IAOS,OAAO,CAAC,QAAQ,CAAC,IAAI;IANzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;IAC5C,OAAO,CAAC,EAAE,CAAC,CAAiB;IAC5B,OAAO,CAAC,EAAE,CAAC,CAAkB;IAC7B,OAAO,CAAC,GAAG,CAAC,CAAmB;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAgB;gBAEoB,IAAI,EAAE,qBAAqB;IAEhF,8FAA8F;IAC9F,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BvB,IAAI,SAAS,IAAI,OAAO,CAEvB;IACD,IAAI,MAAM,IAAI,cAAc,CAG3B;IACD,IAAI,SAAS,IAAI,eAAe,CAG/B;IACD,IAAI,OAAO,IAAI,gBAAgB,CAG9B;IAED,mJAAmJ;IAC7I,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B"}
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.EventsConnection = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const transport_node_1 = require("@nats-io/transport-node");
18
+ const jetstream_1 = require("@nats-io/jetstream");
19
+ const tokens_1 = require("./tokens");
20
+ const NS = 1_000_000;
21
+ /** Connessione NATS propria della libreria (diversa da quella del trasporto Nest) + JetStream. */
22
+ let EventsConnection = class EventsConnection {
23
+ constructor(opts) {
24
+ this.opts = opts;
25
+ this.log = new common_1.Logger('Events');
26
+ }
27
+ /** Connette (una sola volta). Con NATS giù aspetta: l'app non si ferma, l'outbox accumula. */
28
+ ensure() {
29
+ if (!this.starting) {
30
+ this.starting = (async () => {
31
+ const url = this.opts.natsUrl;
32
+ if (!url)
33
+ throw new Error('EVENTS_NATS_URL / NATS_URL non impostato');
34
+ const extra = {};
35
+ if (process.env.NATS_NKEY_SEED)
36
+ extra.authenticator = (0, transport_node_1.nkeyAuthenticator)(new TextEncoder().encode(process.env.NATS_NKEY_SEED));
37
+ this.nc = await (0, transport_node_1.connect)({
38
+ servers: url,
39
+ name: `genai-events-${this.opts.service}`,
40
+ maxReconnectAttempts: -1,
41
+ waitOnFirstConnect: true,
42
+ ...extra,
43
+ ...(this.opts.connection ?? {}),
44
+ });
45
+ this.js = (0, jetstream_1.jetstream)(this.nc);
46
+ this.jsm = await (0, jetstream_1.jetstreamManager)(this.nc);
47
+ this.log.log(`connesso a NATS (${this.opts.service}, stream ${this.opts.stream})`);
48
+ })().catch((e) => {
49
+ this.starting = undefined;
50
+ throw e;
51
+ });
52
+ }
53
+ return this.starting;
54
+ }
55
+ get connected() {
56
+ return !!this.nc && !this.nc.isClosed();
57
+ }
58
+ get client() {
59
+ if (!this.nc)
60
+ throw new Error('NATS non connesso');
61
+ return this.nc;
62
+ }
63
+ get jetstream() {
64
+ if (!this.js)
65
+ throw new Error('NATS non connesso');
66
+ return this.js;
67
+ }
68
+ get manager() {
69
+ if (!this.jsm)
70
+ throw new Error('NATS non connesso');
71
+ return this.jsm;
72
+ }
73
+ /** Crea o aggiorna lo stream del servizio (`EVT_<SVC>`, `evt.<svc>.>`): R3, file, 7 giorni o `streamMaxBytes` (5 GiB), `duplicate_window` 10 m. */
74
+ async ensureOwnStream() {
75
+ if (!this.opts.createStream)
76
+ return;
77
+ const cfg = {
78
+ name: this.opts.stream,
79
+ subjects: [`evt.${this.opts.service}.>`],
80
+ storage: jetstream_1.StorageType.File,
81
+ retention: jetstream_1.RetentionPolicy.Limits,
82
+ discard: jetstream_1.DiscardPolicy.Old,
83
+ num_replicas: this.opts.streamReplicas,
84
+ max_age: 7 * 24 * 3600 * 1000 * NS,
85
+ max_bytes: this.opts.streamMaxBytes,
86
+ duplicate_window: 10 * 60 * 1000 * NS,
87
+ };
88
+ try {
89
+ await this.manager.streams.info(cfg.name);
90
+ await this.manager.streams.update(cfg.name, cfg);
91
+ }
92
+ catch (e) {
93
+ if (e?.code === '404' || /not found/i.test(String(e?.message)))
94
+ await this.manager.streams.add(cfg);
95
+ else
96
+ throw e;
97
+ }
98
+ }
99
+ async close() {
100
+ const nc = this.nc;
101
+ this.nc = this.js = this.jsm = undefined;
102
+ this.starting = undefined;
103
+ if (nc && !nc.isClosed())
104
+ await nc.drain().catch(() => undefined);
105
+ }
106
+ };
107
+ exports.EventsConnection = EventsConnection;
108
+ exports.EventsConnection = EventsConnection = __decorate([
109
+ (0, common_1.Injectable)(),
110
+ __param(0, (0, common_1.Inject)(tokens_1.EVENTS_OPTIONS)),
111
+ __metadata("design:paramtypes", [Object])
112
+ ], EventsConnection);
113
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4D;AAC5D,4DAA0F;AAC1F,kDAQ4B;AAC5B,qCAA0C;AAG1C,MAAM,EAAE,GAAG,SAAS,CAAC;AAErB,kGAAkG;AAE3F,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAO3B,YAAoC,IAA4C;QAA3B,SAAI,GAAJ,IAAI,CAAuB;QAN/D,QAAG,GAAG,IAAI,eAAM,CAAC,QAAQ,CAAC,CAAC;IAMuC,CAAC;IAEpF,8FAA8F;IAC9F,MAAM;QACJ,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;gBAC9B,IAAI,CAAC,GAAG;oBAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;gBACtE,MAAM,KAAK,GAA4B,EAAE,CAAC;gBAC1C,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;oBAAE,KAAK,CAAC,aAAa,GAAG,IAAA,kCAAiB,EAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC;gBAC9H,IAAI,CAAC,EAAE,GAAG,MAAM,IAAA,wBAAO,EAAC;oBACtB,OAAO,EAAE,GAAG;oBACZ,IAAI,EAAE,gBAAgB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;oBACzC,oBAAoB,EAAE,CAAC,CAAC;oBACxB,kBAAkB,EAAE,IAAI;oBACxB,GAAG,KAAK;oBACR,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;iBACzB,CAAC,CAAC;gBACV,IAAI,CAAC,EAAE,GAAG,IAAA,qBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC7B,IAAI,CAAC,GAAG,GAAG,MAAM,IAAA,4BAAgB,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,IAAI,CAAC,OAAO,YAAY,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;YACrF,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;gBACf,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;gBAC1B,MAAM,CAAC,CAAC;YACV,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,IAAI,SAAS;QACX,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,MAAM;QACR,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IACD,IAAI,SAAS;QACX,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IACD,IAAI,OAAO;QACT,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAED,mJAAmJ;IACnJ,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QACpC,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;YACtB,QAAQ,EAAE,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC;YACxC,OAAO,EAAE,uBAAW,CAAC,IAAI;YACzB,SAAS,EAAE,2BAAe,CAAC,MAAM;YACjC,OAAO,EAAE,yBAAa,CAAC,GAAG;YAC1B,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,cAAc;YACtC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE;YAClC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,cAAc;YACnC,gBAAgB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE;SACtC,CAAC;QACF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC1C,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,IAAI,KAAK,KAAK,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBAAE,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;;gBAC/F,MAAM,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE;YAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;CACF,CAAA;AAjFY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAQE,WAAA,IAAA,eAAM,EAAC,uBAAc,CAAC,CAAA;;GAPxB,gBAAgB,CAiF5B"}
@@ -0,0 +1,54 @@
1
+ import { DiscoveryService, MetadataScanner } from '@nestjs/core';
2
+ import { type JsMsg } from '@nats-io/jetstream';
3
+ import { EventsConnection } from './connection';
4
+ import type { OnEventMetadata } from './decorators';
5
+ import { EventsMetrics } from './metrics';
6
+ import { PrismaResolver } from './prisma-resolver';
7
+ import type { EventHandler, ResolvedEventsOptions } from './types';
8
+ interface Registered extends OnEventMetadata {
9
+ handler: EventHandler;
10
+ stream: string;
11
+ durable: string;
12
+ }
13
+ /** Durable di NATS non ammette il punto: `hr.employment-changed` → `hr_employment-changed`. Il nome logico resta quello originale. */
14
+ export declare const durableName: (consumer: string) => string;
15
+ /** `evt.<svc>.…` → `EVT_<SVC>`. */
16
+ export declare function streamForSubject(subject: string): string;
17
+ /** Trova i gestori `@OnEvent`, crea i consumer durevoli e li serve: transazione + inbox + gestore + ack. */
18
+ export declare class EventsConsumers {
19
+ private readonly opts;
20
+ private readonly conn;
21
+ private readonly prisma;
22
+ private readonly metrics;
23
+ private readonly discovery;
24
+ private readonly scanner;
25
+ private readonly log;
26
+ private registered;
27
+ private stopped;
28
+ private loops;
29
+ private advisorySubs;
30
+ private lagTimer?;
31
+ private readonly consumers;
32
+ constructor(opts: ResolvedEventsOptions, conn: EventsConnection, prisma: PrismaResolver, metrics: EventsMetrics, discovery: DiscoveryService, scanner: MetadataScanner);
33
+ /** Cerca i `@OnEvent` fra i provider. Ripetibile (non duplica). */
34
+ discover(): Registered[];
35
+ get handlers(): readonly Registered[];
36
+ /** Registra a mano un gestore (senza decoratore): per i test e per codice non Nest. */
37
+ register(meta: OnEventMetadata, handler: EventHandler): void;
38
+ start(): Promise<void>;
39
+ stop(): Promise<void>;
40
+ private runWithRetry;
41
+ private consumerConfig;
42
+ private ensureConsumer;
43
+ private loop;
44
+ /** Una consegna: verifica, transazione con inbox, ack/nak/term. Pubblico per i test. */
45
+ handle(r: Registered, m: JsMsg): Promise<void>;
46
+ private delayFor;
47
+ private parse;
48
+ private verify;
49
+ private toDlq;
50
+ private onMaxDeliveries;
51
+ private refreshLag;
52
+ }
53
+ export {};
54
+ //# sourceMappingURL=consumers.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumers.service.d.ts","sourceRoot":"","sources":["../src/consumers.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,EAA2C,KAAK,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAiB,YAAY,EAAY,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAK5F,UAAU,UAAW,SAAQ,eAAe;IAC1C,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sIAAsI;AACtI,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,WAAyC,CAAC;AAEtF,mCAAmC;AACnC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,4GAA4G;AAC5G,qBACa,eAAe;IAUA,OAAO,CAAC,QAAQ,CAAC,IAAI;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAd1B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiC;IACrD,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,YAAY,CAAsC;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;gBAGd,IAAI,EAAE,qBAAqB,EACnD,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,eAAe;IAG3C,mEAAmE;IACnE,QAAQ,IAAI,UAAU,EAAE;IA8BxB,IAAI,QAAQ,IAAI,SAAS,UAAU,EAAE,CAEpC;IAED,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI;IAStD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAYb,YAAY;IAa1B,OAAO,CAAC,cAAc;YAcR,cAAc;YAkBd,IAAI;IASlB,wFAAwF;IAClF,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAwDpD,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,KAAK;YAkBC,MAAM;YAmBN,KAAK;YAgBL,eAAe;YASf,UAAU;CAUzB"}