@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
@@ -0,0 +1,26 @@
1
+ type Labels = Record<string, string>;
2
+ /**
3
+ * Registro minimo di metriche (nessuna dipendenza). `render()` produce il testo Prometheus: il servizio lo aggiunge al suo
4
+ * endpoint delle metriche (o `@generazioneai/telemetry` lo legge). Nomi del contratto: events_outbox_pending,
5
+ * events_outbox_oldest_seconds, events_published_total, events_delivered_total{consumer}, events_nak_total,
6
+ * events_term_total, events_consumer_lag{consumer}.
7
+ *
8
+ * Le stesse serie passano anche all'API di OpenTelemetry (come fa `@generazioneai/jobs`): senza SDK sono no-op, con
9
+ * `@generazioneai/telemetry` le esporta il Collector e finiscono in VictoriaMetrics, dove le regole di allarme
10
+ * (`outbox-in-ritardo`, `consumer-in-ritardo`) le leggono. Nessun servizio deve aggiungere un endpoint.
11
+ */
12
+ export declare class EventsMetrics {
13
+ private counters;
14
+ private gauges;
15
+ private readonly gaugeLabels;
16
+ private readonly otelCounters;
17
+ private readonly otelGauges;
18
+ private readonly meter;
19
+ private static key;
20
+ inc(name: string, labels?: Labels, by?: number): void;
21
+ set(name: string, value: number, labels?: Labels): void;
22
+ get(name: string, labels?: Labels): number;
23
+ render(): string;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAGA,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErC;;;;;;;;;GASG;AACH,qBACa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0C;IACtE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8B;IAC3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoD;IAE1E,OAAO,CAAC,MAAM,CAAC,GAAG;IAQlB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,SAAI,GAAG,IAAI;IAahD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAmBvD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAK1C,MAAM,IAAI,MAAM;CAYjB"}
@@ -0,0 +1,92 @@
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 EventsMetrics_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.EventsMetrics = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const api_1 = require("@opentelemetry/api");
13
+ /**
14
+ * Registro minimo di metriche (nessuna dipendenza). `render()` produce il testo Prometheus: il servizio lo aggiunge al suo
15
+ * endpoint delle metriche (o `@generazioneai/telemetry` lo legge). Nomi del contratto: events_outbox_pending,
16
+ * events_outbox_oldest_seconds, events_published_total, events_delivered_total{consumer}, events_nak_total,
17
+ * events_term_total, events_consumer_lag{consumer}.
18
+ *
19
+ * Le stesse serie passano anche all'API di OpenTelemetry (come fa `@generazioneai/jobs`): senza SDK sono no-op, con
20
+ * `@generazioneai/telemetry` le esporta il Collector e finiscono in VictoriaMetrics, dove le regole di allarme
21
+ * (`outbox-in-ritardo`, `consumer-in-ritardo`) le leggono. Nessun servizio deve aggiungere un endpoint.
22
+ */
23
+ let EventsMetrics = EventsMetrics_1 = class EventsMetrics {
24
+ constructor() {
25
+ this.counters = new Map();
26
+ this.gauges = new Map();
27
+ this.gaugeLabels = new Map();
28
+ this.otelCounters = new Map();
29
+ this.otelGauges = new Set();
30
+ this.meter = api_1.metrics.getMeter('@generazioneai/events');
31
+ }
32
+ static key(l) {
33
+ if (!l)
34
+ return '';
35
+ return Object.keys(l)
36
+ .sort()
37
+ .map((k) => `${k}="${String(l[k]).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`)
38
+ .join(',');
39
+ }
40
+ inc(name, labels, by = 1) {
41
+ const m = this.counters.get(name) ?? new Map();
42
+ const k = EventsMetrics_1.key(labels);
43
+ m.set(k, (m.get(k) ?? 0) + by);
44
+ this.counters.set(name, m);
45
+ let c = this.otelCounters.get(name);
46
+ if (!c) {
47
+ c = this.meter.createCounter(name);
48
+ this.otelCounters.set(name, c);
49
+ }
50
+ c.add(by, labels ?? {});
51
+ }
52
+ set(name, value, labels) {
53
+ const m = this.gauges.get(name) ?? new Map();
54
+ m.set(EventsMetrics_1.key(labels), value);
55
+ this.gauges.set(name, m);
56
+ const k = EventsMetrics_1.key(labels);
57
+ const l = this.gaugeLabels.get(name) ?? new Map();
58
+ l.set(k, labels ?? {});
59
+ this.gaugeLabels.set(name, l);
60
+ if (!this.otelGauges.has(name)) {
61
+ this.otelGauges.add(name);
62
+ // Il callback legge i valori correnti al momento dell'esportazione.
63
+ this.meter.createObservableGauge(name).addCallback((result) => {
64
+ for (const [key, value] of this.gauges.get(name) ?? []) {
65
+ result.observe(value, this.gaugeLabels.get(name)?.get(key) ?? {});
66
+ }
67
+ });
68
+ }
69
+ }
70
+ get(name, labels) {
71
+ const k = EventsMetrics_1.key(labels);
72
+ return this.counters.get(name)?.get(k) ?? this.gauges.get(name)?.get(k) ?? 0;
73
+ }
74
+ render() {
75
+ const out = [];
76
+ const dump = (type, src) => {
77
+ for (const [name, series] of src) {
78
+ out.push(`# TYPE ${name} ${type}`);
79
+ for (const [k, v] of series)
80
+ out.push(`${name}${k ? `{${k}}` : ''} ${v}`);
81
+ }
82
+ };
83
+ dump('counter', this.counters);
84
+ dump('gauge', this.gauges);
85
+ return out.join('\n') + '\n';
86
+ }
87
+ };
88
+ exports.EventsMetrics = EventsMetrics;
89
+ exports.EventsMetrics = EventsMetrics = EventsMetrics_1 = __decorate([
90
+ (0, common_1.Injectable)()
91
+ ], EventsMetrics);
92
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAA4C;AAC5C,4CAAuE;AAIvE;;;;;;;;;GASG;AAEI,IAAM,aAAa,qBAAnB,MAAM,aAAa;IAAnB;QACG,aAAQ,GAAG,IAAI,GAAG,EAA+B,CAAC;QAClD,WAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;QACvC,gBAAW,GAAG,IAAI,GAAG,EAA+B,CAAC;QACrD,iBAAY,GAAG,IAAI,GAAG,EAAmB,CAAC;QAC1C,eAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,UAAK,GAAU,aAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IA2D5E,CAAC;IAzDS,MAAM,CAAC,GAAG,CAAC,CAAU;QAC3B,IAAI,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAClB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;aAClB,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;aAChF,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAED,GAAG,CAAC,IAAY,EAAE,MAAe,EAAE,EAAE,GAAG,CAAC;QACvC,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAkB,CAAC;QAC/D,MAAM,CAAC,GAAG,eAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3B,IAAI,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACjC,CAAC;QACD,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,MAAe;QAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAkB,CAAC;QAC7D,CAAC,CAAC,GAAG,CAAC,eAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,eAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAkB,CAAC;QAClE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;QACvB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC1B,oEAAoE;YACpE,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,EAAE;gBAC5D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;oBACvD,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,GAAG,CAAC,IAAY,EAAE,MAAe;QAC/B,MAAM,CAAC,GAAG,eAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/E,CAAC;IAED,MAAM;QACJ,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,GAAqC,EAAE,EAAE;YACnE,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;gBACjC,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;gBACnC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM;oBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC,CAAC;QACF,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/B,CAAC;CACF,CAAA;AAjEY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;GACA,aAAa,CAiEzB"}
@@ -0,0 +1,24 @@
1
+ import type { EnqueueParams, PrismaTx, ResolvedEventsOptions } from './types';
2
+ export declare const OUTBOX_INSERT = "INSERT INTO outbox_events\n (\"id\",\"subject\",\"aggregateType\",\"aggregateId\",\"aggregateVersion\",\"juridicalId\",\"payload\",\"headers\")\n VALUES ($1::uuid,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb)";
3
+ export declare class OutboxService {
4
+ private readonly opts;
5
+ /** Client di transazione già verificati (un `tx` vive quanto la sua transazione: la prova si fa una volta sola). */
6
+ private readonly verified;
7
+ constructor(opts: ResolvedEventsOptions);
8
+ /**
9
+ * Riconosce una transazione dal lato del database, non dalla forma del client (in Prisma 7 il `tx` ha anch'esso `$transaction`):
10
+ * `SAVEPOINT` esiste solo dentro un blocco di transazione, altrimenti Postgres risponde 25P01. Con il client radice (una query
11
+ * in autocommit) fallisce → `EVENTS_OUTSIDE_TX`. Un `tx` verificato non si riprova.
12
+ */
13
+ private assertInTransaction;
14
+ /**
15
+ * Scrive l'evento nell'outbox, nella stessa transazione del dato. `tx` è il client del callback di
16
+ * `prisma.$transaction(async (tx) => …)`; fuori da una transazione (client radice, con `$transaction`) lancia
17
+ * `EVENTS_OUTSIDE_TX`: niente «pubblica e speriamo».
18
+ *
19
+ * Con `EVENTS_MODE=off` non scrive nulla e restituisce `null` (il controllo della transazione resta, per trovare i bug prima
20
+ * dell'accensione). Altrimenti restituisce l'id dell'evento (UUID v7).
21
+ */
22
+ enqueue<T>(tx: PrismaTx, p: EnqueueParams<T>): Promise<string | null>;
23
+ }
24
+ //# sourceMappingURL=outbox.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.service.d.ts","sourceRoot":"","sources":["../src/outbox.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAG9E,eAAO,MAAM,aAAa,6MAE6B,CAAC;AAExD,qBACa,aAAa;IAIY,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHzD,oHAAoH;IACpH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;gBAEG,IAAI,EAAE,qBAAqB;IAEhF;;;;OAIG;YACW,mBAAmB;IAiBjC;;;;;;;OAOG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CA+B5E"}
@@ -0,0 +1,88 @@
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.OutboxService = exports.OUTBOX_INSERT = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const errors_1 = require("./errors");
18
+ const tokens_1 = require("./tokens");
19
+ const uuid_1 = require("./uuid");
20
+ exports.OUTBOX_INSERT = `INSERT INTO outbox_events
21
+ ("id","subject","aggregateType","aggregateId","aggregateVersion","juridicalId","payload","headers")
22
+ VALUES ($1::uuid,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb)`;
23
+ let OutboxService = class OutboxService {
24
+ constructor(opts) {
25
+ this.opts = opts;
26
+ /** Client di transazione già verificati (un `tx` vive quanto la sua transazione: la prova si fa una volta sola). */
27
+ this.verified = new WeakSet();
28
+ }
29
+ /**
30
+ * Riconosce una transazione dal lato del database, non dalla forma del client (in Prisma 7 il `tx` ha anch'esso `$transaction`):
31
+ * `SAVEPOINT` esiste solo dentro un blocco di transazione, altrimenti Postgres risponde 25P01. Con il client radice (una query
32
+ * in autocommit) fallisce → `EVENTS_OUTSIDE_TX`. Un `tx` verificato non si riprova.
33
+ */
34
+ async assertInTransaction(tx, subject) {
35
+ const outside = (cause) => new errors_1.EventsError('EVENTS_OUTSIDE_TX', `enqueue(${subject}) va chiamato dentro prisma.$transaction(async (tx) => …) passando il tx, non il client${cause ? ` (${cause?.message ?? cause})` : ''}`);
36
+ if (!tx || typeof tx.$executeRawUnsafe !== 'function')
37
+ throw outside();
38
+ if (this.verified.has(tx))
39
+ return;
40
+ try {
41
+ await tx.$executeRawUnsafe('SAVEPOINT genai_events_probe');
42
+ await tx.$executeRawUnsafe('RELEASE SAVEPOINT genai_events_probe');
43
+ }
44
+ catch (e) {
45
+ throw outside(e);
46
+ }
47
+ this.verified.add(tx);
48
+ }
49
+ /**
50
+ * Scrive l'evento nell'outbox, nella stessa transazione del dato. `tx` è il client del callback di
51
+ * `prisma.$transaction(async (tx) => …)`; fuori da una transazione (client radice, con `$transaction`) lancia
52
+ * `EVENTS_OUTSIDE_TX`: niente «pubblica e speriamo».
53
+ *
54
+ * Con `EVENTS_MODE=off` non scrive nulla e restituisce `null` (il controllo della transazione resta, per trovare i bug prima
55
+ * dell'accensione). Altrimenti restituisce l'id dell'evento (UUID v7).
56
+ */
57
+ async enqueue(tx, p) {
58
+ if (this.opts.mode !== 'off')
59
+ await this.assertInTransaction(tx, p?.subject);
60
+ else if (!tx || typeof tx.$executeRawUnsafe !== 'function')
61
+ return null; // off non lancia mai
62
+ const svc = this.opts.service;
63
+ const ok = p.subject?.startsWith(`evt.${svc}.`) || p.subject === `audit.${svc}` || p.subject?.startsWith(`audit.${svc}.`);
64
+ if (!ok) {
65
+ // evt.<svc>.* va sullo stream EVT_<SVC>; audit.<svc>[.*] sullo stream AUDIT (lo sceglie NATS dal subject)
66
+ throw new errors_1.EventsError('EVENTS_INVALID', `il subject «${p.subject}» deve iniziare con evt.${svc}. oppure essere audit.${svc}[.…]`);
67
+ }
68
+ if (!p.aggregate?.type || !p.aggregate?.id || !Number.isInteger(p.aggregate.version)) {
69
+ throw new errors_1.EventsError('EVENTS_INVALID', 'aggregate { type, id, version } è obbligatorio');
70
+ }
71
+ if (this.opts.mode === 'off')
72
+ return null;
73
+ const id = (0, uuid_1.uuidv7)();
74
+ const headers = { occurredAt: new Date().toISOString() };
75
+ const tp = this.opts.traceparent?.();
76
+ if (tp)
77
+ headers.traceparent = tp;
78
+ await tx.$executeRawUnsafe(exports.OUTBOX_INSERT, id, p.subject, p.aggregate.type, p.aggregate.id, p.aggregate.version, p.juridicalId ?? null, JSON.stringify(p.data ?? null), JSON.stringify(headers));
79
+ return id;
80
+ }
81
+ };
82
+ exports.OutboxService = OutboxService;
83
+ exports.OutboxService = OutboxService = __decorate([
84
+ (0, common_1.Injectable)(),
85
+ __param(0, (0, common_1.Inject)(tokens_1.EVENTS_OPTIONS)),
86
+ __metadata("design:paramtypes", [Object])
87
+ ], OutboxService);
88
+ //# sourceMappingURL=outbox.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.service.js","sourceRoot":"","sources":["../src/outbox.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { ModuleRef } from '@nestjs/core';
2
+ import type { EventsModuleOptions, PrismaLike } from './types';
3
+ /** Risolve `PrismaService` quando serve (non alla costruzione: il suo modulo può non essere ancora pronto). */
4
+ export declare class PrismaResolver {
5
+ private readonly ref;
6
+ private readonly raw;
7
+ private cached?;
8
+ constructor(ref: ModuleRef, raw: EventsModuleOptions);
9
+ get(): PrismaLike;
10
+ }
11
+ //# sourceMappingURL=prisma-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-resolver.d.ts","sourceRoot":"","sources":["../src/prisma-resolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE/D,+GAA+G;AAC/G,qBACa,cAAc;IAGvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACQ,OAAO,CAAC,QAAQ,CAAC,GAAG;IAHlD,OAAO,CAAC,MAAM,CAAC,CAAa;gBAET,GAAG,EAAE,SAAS,EACc,GAAG,EAAE,mBAAmB;IAGvE,GAAG,IAAI,UAAU;CAYlB"}
@@ -0,0 +1,46 @@
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.PrismaResolver = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@nestjs/core");
18
+ const errors_1 = require("./errors");
19
+ const tokens_1 = require("./tokens");
20
+ /** Risolve `PrismaService` quando serve (non alla costruzione: il suo modulo può non essere ancora pronto). */
21
+ let PrismaResolver = class PrismaResolver {
22
+ constructor(ref, raw) {
23
+ this.ref = ref;
24
+ this.raw = raw;
25
+ }
26
+ get() {
27
+ if (this.cached)
28
+ return this.cached;
29
+ const p = this.raw.prisma;
30
+ const inst = (typeof p === 'function' || typeof p === 'string' || typeof p === 'symbol'
31
+ ? this.ref.get(p, { strict: false })
32
+ : p);
33
+ if (!inst || typeof inst.$transaction !== 'function') {
34
+ throw new errors_1.EventsError('EVENTS_CONFIG', 'EventsModule.forRoot({ prisma }) non risolve a un PrismaService con $transaction');
35
+ }
36
+ this.cached = inst;
37
+ return inst;
38
+ }
39
+ };
40
+ exports.PrismaResolver = PrismaResolver;
41
+ exports.PrismaResolver = PrismaResolver = __decorate([
42
+ (0, common_1.Injectable)(),
43
+ __param(1, (0, common_1.Inject)(tokens_1.EVENTS_RAW_OPTIONS)),
44
+ __metadata("design:paramtypes", [core_1.ModuleRef, Object])
45
+ ], PrismaResolver);
46
+ //# sourceMappingURL=prisma-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-resolver.js","sourceRoot":"","sources":["../src/prisma-resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,uCAAyC;AACzC,qCAAuC;AACvC,qCAA8C;AAG9C,+GAA+G;AAExG,IAAM,cAAc,GAApB,MAAM,cAAc;IAEzB,YACmB,GAAc,EACc,GAAwB;QADpD,QAAG,GAAH,GAAG,CAAW;QACc,QAAG,GAAH,GAAG,CAAqB;IACpE,CAAC;IAEJ,GAAG;QACD,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QACpC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;QAC1B,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,UAAU,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;YACrF,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YAC3C,CAAC,CAAC,CAAC,CAA2B,CAAC;QACjC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,UAAU,EAAE,CAAC;YACrD,MAAM,IAAI,oBAAW,CAAC,eAAe,EAAE,kFAAkF,CAAC,CAAC;QAC7H,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAnBY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,eAAM,EAAC,2BAAkB,CAAC,CAAA;qCADL,gBAAS;GAHtB,cAAc,CAmB1B"}
@@ -0,0 +1,32 @@
1
+ import { EventsConnection } from './connection';
2
+ import { EventsMetrics } from './metrics';
3
+ import { PrismaResolver } from './prisma-resolver';
4
+ import type { ResolvedEventsOptions } from './types';
5
+ /**
6
+ * Relay dell'outbox: prende fino a `relayBatchSize` righe con `FOR UPDATE SKIP LOCKED` (più repliche non si pestano i piedi),
7
+ * pubblica con `Nats-Msg-Id` = id, aspetta il PubAck e segna `publishedAt`, tutto nella stessa transazione. Se la pubblicazione
8
+ * fallisce si ferma (l'ordine del giro si conserva), conta il tentativo e riprova al giro dopo. L'ordine fra transazioni che
9
+ * committano fuori ordine, e fra repliche, NON è garantito: chi consuma usa `aggregateVersion`.
10
+ */
11
+ export declare class OutboxRelay {
12
+ private readonly opts;
13
+ private readonly conn;
14
+ private readonly prisma;
15
+ private readonly metrics;
16
+ private readonly log;
17
+ private timer?;
18
+ private stopped;
19
+ private running;
20
+ private lastGauges;
21
+ private lastPrune;
22
+ constructor(opts: ResolvedEventsOptions, conn: EventsConnection, prisma: PrismaResolver, metrics: EventsMetrics);
23
+ start(): void;
24
+ stop(): Promise<void>;
25
+ private schedule;
26
+ private tick;
27
+ /** Un giro. Restituisce quante righe ha pubblicato. Usato anche dai test. */
28
+ drainOnce(): Promise<number>;
29
+ private publish;
30
+ private housekeeping;
31
+ }
32
+ //# sourceMappingURL=relay.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.service.d.ts","sourceRoot":"","sources":["../src/relay.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAA2B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAa9E;;;;;GAKG;AACH,qBACa,WAAW;IASI,OAAO,CAAC,QAAQ,CAAC,IAAI;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAX1B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IACjD,OAAO,CAAC,KAAK,CAAC,CAAiB;IAC/B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,SAAS,CAAK;gBAGqB,IAAI,EAAE,qBAAqB,EACnD,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,aAAa;IAGzC,KAAK,IAAI,IAAI;IAMP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,OAAO,CAAC,QAAQ;YAKF,IAAI;IAYlB,6EAA6E;IACvE,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;YA0CpB,OAAO;YAuBP,YAAY;CAsB3B"}
@@ -0,0 +1,153 @@
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.OutboxRelay = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const transport_node_1 = require("@nats-io/transport-node");
18
+ const connection_1 = require("./connection");
19
+ const metrics_1 = require("./metrics");
20
+ const prisma_resolver_1 = require("./prisma-resolver");
21
+ const tokens_1 = require("./tokens");
22
+ /**
23
+ * Relay dell'outbox: prende fino a `relayBatchSize` righe con `FOR UPDATE SKIP LOCKED` (più repliche non si pestano i piedi),
24
+ * pubblica con `Nats-Msg-Id` = id, aspetta il PubAck e segna `publishedAt`, tutto nella stessa transazione. Se la pubblicazione
25
+ * fallisce si ferma (l'ordine del giro si conserva), conta il tentativo e riprova al giro dopo. L'ordine fra transazioni che
26
+ * committano fuori ordine, e fra repliche, NON è garantito: chi consuma usa `aggregateVersion`.
27
+ */
28
+ let OutboxRelay = class OutboxRelay {
29
+ constructor(opts, conn, prisma, metrics) {
30
+ this.opts = opts;
31
+ this.conn = conn;
32
+ this.prisma = prisma;
33
+ this.metrics = metrics;
34
+ this.log = new common_1.Logger('EventsRelay');
35
+ this.stopped = true;
36
+ this.running = false;
37
+ this.lastGauges = 0;
38
+ this.lastPrune = 0;
39
+ }
40
+ start() {
41
+ if (!this.stopped)
42
+ return;
43
+ this.stopped = false;
44
+ this.schedule(0);
45
+ }
46
+ async stop() {
47
+ this.stopped = true;
48
+ if (this.timer)
49
+ clearTimeout(this.timer);
50
+ while (this.running)
51
+ await new Promise((r) => setTimeout(r, 10));
52
+ }
53
+ schedule(ms) {
54
+ if (this.stopped)
55
+ return;
56
+ this.timer = setTimeout(() => void this.tick(), ms);
57
+ }
58
+ async tick() {
59
+ let again = false;
60
+ try {
61
+ const n = await this.drainOnce();
62
+ again = n >= this.opts.relayBatchSize;
63
+ await this.housekeeping();
64
+ }
65
+ catch (e) {
66
+ this.log.warn(`giro del relay fallito: ${e?.message ?? e}`);
67
+ }
68
+ this.schedule(again ? 0 : this.opts.relayIntervalMs);
69
+ }
70
+ /** Un giro. Restituisce quante righe ha pubblicato. Usato anche dai test. */
71
+ async drainOnce() {
72
+ if (this.running)
73
+ return 0;
74
+ this.running = true;
75
+ try {
76
+ if (!this.conn.connected)
77
+ return 0;
78
+ const prisma = this.prisma.get();
79
+ return await prisma.$transaction(async (tx) => {
80
+ const rows = await tx.$queryRawUnsafe(`SELECT "id","subject","aggregateType","aggregateId","aggregateVersion","juridicalId","payload","headers"
81
+ FROM outbox_events WHERE "publishedAt" IS NULL ORDER BY "id" LIMIT ${Math.floor(this.opts.relayBatchSize)}
82
+ FOR UPDATE SKIP LOCKED`);
83
+ let done = 0;
84
+ for (const r of rows) {
85
+ try {
86
+ await this.publish(r);
87
+ }
88
+ catch (e) {
89
+ await tx.$executeRawUnsafe(`UPDATE outbox_events SET "attempts" = "attempts" + 1, "lastError" = $2 WHERE "id" = $1::uuid`, r.id, String(e?.message ?? e).slice(0, 500));
90
+ this.log.warn(`pubblicazione di ${r.subject} (${r.id}) fallita: ${e?.message ?? e}`);
91
+ break;
92
+ }
93
+ await tx.$executeRawUnsafe(`UPDATE outbox_events SET "publishedAt" = CURRENT_TIMESTAMP, "attempts" = "attempts" + 1, "lastError" = NULL WHERE "id" = $1::uuid`, r.id);
94
+ this.metrics.inc('events_published_total', { subject: r.subject });
95
+ done++;
96
+ }
97
+ return done;
98
+ }, { timeout: 60_000, maxWait: 10_000 });
99
+ }
100
+ finally {
101
+ this.running = false;
102
+ }
103
+ }
104
+ async publish(r) {
105
+ const envelope = {
106
+ v: 1,
107
+ id: r.id,
108
+ subject: r.subject,
109
+ aggregateType: r.aggregateType,
110
+ aggregateId: r.aggregateId,
111
+ aggregateVersion: r.aggregateVersion,
112
+ occurredAt: r.headers?.occurredAt ?? new Date().toISOString(),
113
+ ...(r.juridicalId ? { juridicalId: r.juridicalId } : {}),
114
+ data: r.payload,
115
+ };
116
+ const h = (0, transport_node_1.headers)();
117
+ h.set('Nats-Msg-Id', r.id);
118
+ h.set('Gen-Event-Version', '1');
119
+ if (r.juridicalId)
120
+ h.set('Gen-Tenant', r.juridicalId);
121
+ if (r.headers?.traceparent)
122
+ h.set('traceparent', r.headers.traceparent);
123
+ if (this.opts.signatureMode !== 'off' && this.opts.signer) {
124
+ h.set('Gen-Sig', await this.opts.signer({ eventId: r.id, subject: r.subject, juridicalId: r.juridicalId }));
125
+ }
126
+ await this.conn.jetstream.publish(r.subject, JSON.stringify(envelope), { msgID: r.id, headers: h, timeout: 10_000 });
127
+ }
128
+ async housekeeping() {
129
+ const now = Date.now();
130
+ const prisma = this.prisma.get();
131
+ if (now - this.lastGauges > 5_000) {
132
+ this.lastGauges = now;
133
+ const [row] = await prisma.$queryRawUnsafe(`SELECT COUNT(*) AS pending, EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - MIN("createdAt")))::float8 AS oldest
134
+ FROM outbox_events WHERE "publishedAt" IS NULL`);
135
+ this.metrics.set('events_outbox_pending', Number(row?.pending ?? 0));
136
+ this.metrics.set('events_outbox_oldest_seconds', Number(row?.oldest ?? 0));
137
+ }
138
+ if (now - this.lastPrune > 600_000) {
139
+ this.lastPrune = now;
140
+ await prisma.$executeRawUnsafe(`DELETE FROM outbox_events WHERE "publishedAt" IS NOT NULL AND "publishedAt" < CURRENT_TIMESTAMP - make_interval(days => ${Math.floor(this.opts.outboxRetentionDays)})`);
141
+ await prisma.$executeRawUnsafe(`DELETE FROM inbox_processed WHERE "processedAt" < CURRENT_TIMESTAMP - make_interval(days => ${Math.floor(this.opts.inboxRetentionDays)})`);
142
+ }
143
+ }
144
+ };
145
+ exports.OutboxRelay = OutboxRelay;
146
+ exports.OutboxRelay = OutboxRelay = __decorate([
147
+ (0, common_1.Injectable)(),
148
+ __param(0, (0, common_1.Inject)(tokens_1.EVENTS_OPTIONS)),
149
+ __metadata("design:paramtypes", [Object, connection_1.EventsConnection,
150
+ prisma_resolver_1.PrismaResolver,
151
+ metrics_1.EventsMetrics])
152
+ ], OutboxRelay);
153
+ //# sourceMappingURL=relay.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.service.js","sourceRoot":"","sources":["../src/relay.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const EVENTS_OPTIONS: unique symbol;
2
+ export declare const EVENTS_RAW_OPTIONS: unique symbol;
3
+ export declare const ON_EVENT_METADATA = "genai:events:on-event";
4
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,eAAiC,CAAC;AAC7D,eAAO,MAAM,kBAAkB,eAAqC,CAAC;AACrE,eAAO,MAAM,iBAAiB,0BAA0B,CAAC"}
package/dist/tokens.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ON_EVENT_METADATA = exports.EVENTS_RAW_OPTIONS = exports.EVENTS_OPTIONS = void 0;
4
+ exports.EVENTS_OPTIONS = Symbol('GENAI_EVENTS_OPTIONS');
5
+ exports.EVENTS_RAW_OPTIONS = Symbol('GENAI_EVENTS_RAW_OPTIONS');
6
+ exports.ON_EVENT_METADATA = 'genai:events:on-event';
7
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":";;;AAAa,QAAA,cAAc,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;AAChD,QAAA,kBAAkB,GAAG,MAAM,CAAC,0BAA0B,CAAC,CAAC;AACxD,QAAA,iBAAiB,GAAG,uBAAuB,CAAC"}