wsp-ms-core 1.0.6 → 1.0.8-7.5

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/dist/index.cjs CHANGED
@@ -1,6 +1,8 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
7
  var __export = (target, all) => {
6
8
  for (var name in all)
@@ -14,31 +16,57 @@ var __copyProps = (to, from, except, desc) => {
14
16
  }
15
17
  return to;
16
18
  };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
17
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
28
 
19
29
  // src/index.ts
20
30
  var src_exports = {};
21
31
  __export(src_exports, {
32
+ BaseEvent: () => BaseEvent,
33
+ BaseObject: () => BaseObject,
34
+ BasicUnitOfWork: () => BasicUnitOfWork,
35
+ BasicUnitOfWorkFactory: () => BasicUnitOfWorkFactory,
36
+ Country: () => Country,
22
37
  Currency: () => Currency,
23
38
  DateTime: () => DateTime,
39
+ DefaultMysqlInboxRunner: () => DefaultMysqlInboxRunner,
40
+ DefaultMysqlOutboxRunner: () => DefaultMysqlOutboxRunner,
24
41
  DomainEntity: () => DomainEntity,
25
42
  DomainError: () => DomainError,
26
43
  DomainEvent: () => DomainEvent,
27
44
  Email: () => Email,
28
45
  ErrorManager: () => ErrorManager,
46
+ EventBus: () => EventBus,
47
+ EventBusMysqlRepository: () => EventBusMysqlRepository,
48
+ EventManager: () => EventManager,
49
+ ExchangeRates: () => ExchangeRates,
29
50
  FatalError: () => FatalError,
30
- HttpErrorController: () => HttpErrorController,
31
51
  HttpHealthCheckController: () => HttpHealthCheckController,
32
52
  HttpNotFoundController: () => HttpNotFoundController,
53
+ InboxRecord: () => InboxRecord,
54
+ IntegrationEvent: () => IntegrationEvent,
33
55
  InternalError: () => InternalError,
56
+ KafkaManager: () => KafkaManager,
34
57
  Language: () => Language,
35
58
  MysqlConnection: () => MysqlConnection,
36
59
  MysqlConnector: () => MysqlConnector,
60
+ OutboxRecord: () => OutboxRecord,
61
+ PaymentGateway: () => PaymentGateway,
62
+ PaymentStatus: () => PaymentStatus,
37
63
  Price: () => Price,
64
+ ProcessStatus: () => ProcessStatus,
65
+ StringVars: () => StringVars,
38
66
  UUID: () => UUID,
39
67
  UsageError: () => UsageError,
40
68
  ValueObject: () => ValueObject,
41
- adaptErrorHandler: () => adaptErrorHandler,
69
+ adaptExpressErrorHandler: () => adaptExpressErrorHandler,
42
70
  adaptExpressRoute: () => adaptExpressRoute
43
71
  });
44
72
  module.exports = __toCommonJS(src_exports);
@@ -52,6 +80,9 @@ var ValueObject = class {
52
80
  this.validate(value);
53
81
  this._value = Object.freeze(value);
54
82
  }
83
+ toProps() {
84
+ return this._value;
85
+ }
55
86
  get value() {
56
87
  return this._value;
57
88
  }
@@ -145,6 +176,21 @@ var _DateTime = class _DateTime extends ValueObject {
145
176
  getWeekdayName(locale = "en") {
146
177
  return this._dt.setLocale(locale).toFormat("cccc");
147
178
  }
179
+ getDayName(locale = "en") {
180
+ return this._dt.setLocale(locale).toFormat("EEEE");
181
+ }
182
+ format(format) {
183
+ if (!_DateTime.FORMATS.hasOwnProperty(format)) {
184
+ throw new Error(`Invalid format: ${format}`);
185
+ }
186
+ const formatString = _DateTime.FORMATS[format];
187
+ return this._dt.toFormat(formatString);
188
+ }
189
+ toPrimitives() {
190
+ return {
191
+ value: this.value
192
+ };
193
+ }
148
194
  static create(input) {
149
195
  if (input === void 0) {
150
196
  return new _DateTime(_DateTime.toUtcFormat(import_luxon.DateTime.now()));
@@ -160,39 +206,106 @@ var _DateTime = class _DateTime extends ValueObject {
160
206
  }
161
207
  return new _DateTime(input);
162
208
  }
209
+ static now() {
210
+ return _DateTime.create();
211
+ }
163
212
  };
164
213
  _DateTime.DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";
214
+ _DateTime.FORMAT = "Y-M-D H:M:S";
215
+ _DateTime.FORMAT_Y_M = "Y-M";
216
+ _DateTime.FORMAT_Y_M_D = "Y-M-D";
217
+ _DateTime.FORMAT_Y_M_D_H_m_s = "Y-M-D H:M:S";
218
+ _DateTime.FORMAT_D_M_Y = "D-M-Y";
219
+ _DateTime.FORMATS = {
220
+ "Y-M": "yyyy-MM",
221
+ "Y-M-D": "yyyy-MM-dd",
222
+ "D/M/Y": "dd/MM/yyyy",
223
+ "Y/M/D": "yyyy/MM/dd",
224
+ "D-M-Y": "dd-MM-yyyy",
225
+ "Y.M.D": "yyyy.MM.dd",
226
+ "D.M.Y": "dd.MM.yyyy",
227
+ "Y-M-D H:M": "yyyy-MM-dd HH:mm",
228
+ "D/M/Y H:M": "dd/MM/yyyy HH:mm",
229
+ "Y/M/D H:M": "yyyy/MM/dd HH:mm",
230
+ "D-M-Y H:M": "dd-MM-yyyy HH:mm",
231
+ "Y.M.D H:M": "yyyy.MM.dd HH:mm",
232
+ "D.M.Y H:M": "dd.MM.yyyy HH:mm",
233
+ "Y-M-D H:M:S": "yyyy-MM-dd HH:mm:ss",
234
+ "Y-M-D H:M:S:SSS": "yyyy-MM-dd HH:mm:ss.SSS",
235
+ "D/M/Y H:M:S": "dd/MM/yyyy HH:mm:ss",
236
+ "Y/M/D H:M:S": "yyyy/MM/dd HH:mm:ss",
237
+ "D-M-Y H:M:S": "dd-MM-yyyy HH:mm:ss",
238
+ "Y.M.D H:M:S": "yyyy.MM.dd HH:mm:ss",
239
+ "D.M.Y H:M:S": "dd.MM.yyyy HH:mm:ss",
240
+ "H:M": "HH:mm",
241
+ "H:M:S": "HH:mm:ss"
242
+ };
165
243
  var DateTime = _DateTime;
166
244
 
245
+ // src/domain/contracts/BaseEvent.ts
246
+ var BaseEvent = class {
247
+ constructor(tenantUuid, version, type, payload) {
248
+ this._tenantUuid = tenantUuid;
249
+ this._version = version;
250
+ this._type = type;
251
+ this._payload = payload;
252
+ this._occurredAt = DateTime.now();
253
+ }
254
+ get tenantUuid() {
255
+ return this._tenantUuid;
256
+ }
257
+ get version() {
258
+ return this._version;
259
+ }
260
+ get type() {
261
+ return this._type;
262
+ }
263
+ get payload() {
264
+ return this._payload;
265
+ }
266
+ get ocurredAt() {
267
+ return this._occurredAt;
268
+ }
269
+ };
270
+
167
271
  // src/domain/contracts/DomainEntity.ts
168
272
  var DomainEntity = class {
169
- constructor(uuid, props, audit) {
170
- this.uuid = uuid;
273
+ constructor(props) {
274
+ this._events = [];
171
275
  this.props = props;
172
- this._createdAt = audit?.createdAt ?? DateTime.create();
173
- this._updatedAt = audit?.updatedAt ?? this.createdAt;
174
- this._deletedAt = audit?.deletedAt;
276
+ }
277
+ recordEvent(event) {
278
+ this._events.push(event);
175
279
  }
176
280
  touch() {
177
- this._updatedAt = DateTime.create();
281
+ this.props.updatedAt = DateTime.now();
282
+ }
283
+ get uuid() {
284
+ return this.props.uuid;
178
285
  }
179
286
  get createdAt() {
180
- return this._createdAt;
287
+ return this.props.createdAt;
181
288
  }
182
289
  get updatedAt() {
183
- return this._updatedAt;
290
+ return this.props.updatedAt;
184
291
  }
185
292
  get deletedAt() {
186
- return this._deletedAt;
293
+ return this.props.deletedAt;
187
294
  }
188
295
  get isDeleted() {
189
- return Boolean(this._deletedAt);
296
+ return Boolean(this.props.deletedAt);
190
297
  }
191
- softDelete() {
192
- if (!this._deletedAt) {
193
- this._deletedAt = DateTime.create();
194
- this.touch();
195
- }
298
+ pullEvents() {
299
+ const events = this._events;
300
+ this._events = [];
301
+ return events;
302
+ }
303
+ };
304
+
305
+ // src/domain/contracts/BaseObject.ts
306
+ var BaseObject = class {
307
+ constructor(props) {
308
+ this.props = props;
196
309
  }
197
310
  };
198
311
 
@@ -204,20 +317,6 @@ var DomainError = class extends Error {
204
317
  }
205
318
  };
206
319
 
207
- // src/domain/contracts/DomainEvent.ts
208
- var DomainEvent = class {
209
- constructor(payload) {
210
- this._payload = payload;
211
- this._occurredAt = DateTime.create();
212
- }
213
- get payload() {
214
- return this._payload;
215
- }
216
- get occurredAt() {
217
- return this._occurredAt;
218
- }
219
- };
220
-
221
320
  // src/domain/errors/FatalError.ts
222
321
  var FatalError = class extends DomainError {
223
322
  constructor(type, message = "") {
@@ -240,6 +339,424 @@ var UsageError = class extends DomainError {
240
339
  }
241
340
  };
242
341
 
342
+ // src/domain/events/DomainEvent.ts
343
+ var DomainEvent = class extends BaseEvent {
344
+ constructor(tenantUuid, version, type, payload, aggregateUuid, aggregateType) {
345
+ super(tenantUuid, version, type, payload);
346
+ this._aggregateUuid = aggregateUuid;
347
+ this._aggregateType = aggregateType;
348
+ }
349
+ get aggregateUuid() {
350
+ return this._aggregateUuid;
351
+ }
352
+ get aggregateType() {
353
+ return this._aggregateType;
354
+ }
355
+ };
356
+
357
+ // src/domain/value-objects/payments/PaymentGateway.ts
358
+ var _PaymentGateway = class _PaymentGateway extends ValueObject {
359
+ constructor(gateway) {
360
+ super(gateway);
361
+ }
362
+ validate(value) {
363
+ if (!_PaymentGateway.SUPPORTED.includes(value)) {
364
+ throw new InternalError(`Payment gateway <${value}> is not supported`);
365
+ }
366
+ }
367
+ isExternal() {
368
+ return _PaymentGateway.EXTERNALS.includes(this.value);
369
+ }
370
+ get isMercadoPago() {
371
+ return this.equals(_PaymentGateway.MERCADOPAGO);
372
+ }
373
+ get isHandy() {
374
+ return this.equals(_PaymentGateway.HANDY);
375
+ }
376
+ get isWonaDebit() {
377
+ return this.equals(_PaymentGateway.WONA_DEBIT);
378
+ }
379
+ get isWonaCard() {
380
+ return this.equals(_PaymentGateway.WONA_CARD);
381
+ }
382
+ get isWonaCash() {
383
+ return this.equals(_PaymentGateway.WONA_CASH);
384
+ }
385
+ get isWonaTransfer() {
386
+ return this.equals(_PaymentGateway.WONA_TRANSFER);
387
+ }
388
+ get isWonaMercadoPago() {
389
+ return this.equals(_PaymentGateway.WONA_MERCADOPAGO);
390
+ }
391
+ toPrimitives() {
392
+ return { value: this.value };
393
+ }
394
+ static create(gateway) {
395
+ return new _PaymentGateway(gateway.trim().toUpperCase());
396
+ }
397
+ };
398
+ _PaymentGateway.SUPPORTED = [
399
+ "MERCADOPAGO",
400
+ "HANDY",
401
+ "WONA_DEBIT",
402
+ "WONA_CARD",
403
+ "WONA_CASH",
404
+ "WONA_TRANSFER",
405
+ "WONA_MERCADOPAGO"
406
+ ];
407
+ _PaymentGateway.EXTERNALS = ["MERCADOPAGO", "STRIPE"];
408
+ _PaymentGateway.MERCADOPAGO = new _PaymentGateway("MERCADOPAGO");
409
+ _PaymentGateway.HANDY = new _PaymentGateway("HANDY");
410
+ _PaymentGateway.WONA_DEBIT = new _PaymentGateway("WONA_DEBIT");
411
+ _PaymentGateway.WONA_CARD = new _PaymentGateway("WONA_CARD");
412
+ _PaymentGateway.WONA_CASH = new _PaymentGateway("WONA_CASH");
413
+ _PaymentGateway.WONA_TRANSFER = new _PaymentGateway("WONA_TRANSFER");
414
+ _PaymentGateway.WONA_MERCADOPAGO = new _PaymentGateway("WONA_MERCADOPAGO");
415
+ var PaymentGateway = _PaymentGateway;
416
+
417
+ // src/domain/value-objects/payments/PaymentStatus.ts
418
+ var _PaymentStatus = class _PaymentStatus extends ValueObject {
419
+ constructor(status) {
420
+ super(status);
421
+ }
422
+ validate(status) {
423
+ if (!_PaymentStatus.SUPPORTED.includes(status)) {
424
+ throw new InternalError(`Payment status <${status}> is not supported`);
425
+ }
426
+ }
427
+ get isDone() {
428
+ return this.value === "DONE";
429
+ }
430
+ get isPending() {
431
+ return this.value === "PENDING";
432
+ }
433
+ get isInProgress() {
434
+ return this.value === "IN_PROGRESS";
435
+ }
436
+ get isFailed() {
437
+ return this.value === "FAILED";
438
+ }
439
+ get isCanceled() {
440
+ return this.value === "CANCELED";
441
+ }
442
+ get isHold() {
443
+ return this.value === "HOLD";
444
+ }
445
+ get isPendingRefund() {
446
+ return this.value === "PENDING_REFUND";
447
+ }
448
+ get isRefunded() {
449
+ return this.value === "REFUNDED";
450
+ }
451
+ toPrimitives() {
452
+ return { value: this.value };
453
+ }
454
+ static create(gateway) {
455
+ return new _PaymentStatus(gateway.trim().toUpperCase());
456
+ }
457
+ };
458
+ _PaymentStatus.SUPPORTED = ["DONE", "PENDING", "FAILED", "CANCELED", "HOLD", "PENDING_REFUND", "REFUNDED", "IN_PROGRESS"];
459
+ _PaymentStatus.DONE = new _PaymentStatus("DONE");
460
+ _PaymentStatus.PENDING = new _PaymentStatus("PENDING");
461
+ _PaymentStatus.IN_PROGRESS = new _PaymentStatus("IN_PROGRESS");
462
+ _PaymentStatus.FAILED = new _PaymentStatus("FAILED");
463
+ _PaymentStatus.CANCELED = new _PaymentStatus("CANCELED");
464
+ _PaymentStatus.HOLD = new _PaymentStatus("HOLD");
465
+ _PaymentStatus.PENDING_REFUND = new _PaymentStatus("PENDING_REFUND");
466
+ _PaymentStatus.REFUNDED = new _PaymentStatus("REFUNDED");
467
+ var PaymentStatus = _PaymentStatus;
468
+
469
+ // src/utils/StringVars.ts
470
+ var StringVars = class {
471
+ static parse(str, ob) {
472
+ const regex = /{{(.*?)}}/g;
473
+ return str.replace(regex, (match, variable) => {
474
+ if (ob.hasOwnProperty(variable.trim())) {
475
+ return ob[variable.trim()];
476
+ } else {
477
+ return match;
478
+ }
479
+ });
480
+ }
481
+ };
482
+
483
+ // src/infrastructure/errors/ErrorManager.ts
484
+ var _ErrorManager = class _ErrorManager {
485
+ constructor(logger = null) {
486
+ this.logger = logger;
487
+ }
488
+ getDefaultMessage(lang) {
489
+ return _ErrorManager.DEFAULT_MESSAGES[lang.value] || _ErrorManager.DEFAULT_MESSAGES[lang.base()] || "error";
490
+ }
491
+ onFatal(err, lang) {
492
+ this.logger?.fatal(err.type, err.message);
493
+ return { status: "ERROR", message: this.getDefaultMessage(lang) };
494
+ }
495
+ onInternal(err, lang) {
496
+ this.logger?.error(err.type, err.message);
497
+ return { status: "ERROR", message: this.getDefaultMessage(lang) };
498
+ }
499
+ onUsage(err, lang) {
500
+ const tmpl = _ErrorManager.TEMPLATES.get(err.type);
501
+ if (!tmpl) {
502
+ this.logger?.error("TEMPLATE_NOT_FOUND", `${err.type}`);
503
+ return { status: "ERROR", message: this.getDefaultMessage(lang) };
504
+ }
505
+ const code = lang.value;
506
+ const base = lang.base();
507
+ const rawMsg = tmpl.languages[code] ?? tmpl.languages[base] ?? this.getDefaultMessage(lang);
508
+ return {
509
+ status: 400,
510
+ message: StringVars.parse(rawMsg, err.vars)
511
+ };
512
+ }
513
+ onUnknown(err, lang) {
514
+ this.logger?.error("UNKNOWN_ERROR", err.message);
515
+ return { status: "ERROR", message: this.getDefaultMessage(lang) };
516
+ }
517
+ handle(err, lang) {
518
+ if (["local", "dev"].includes(process.env.ENVIRONMENT ?? "")) {
519
+ console.log(err);
520
+ }
521
+ if (err instanceof FatalError) {
522
+ return this.onFatal(err, lang);
523
+ }
524
+ if (err instanceof InternalError) {
525
+ return this.onInternal(err, lang);
526
+ }
527
+ if (err instanceof UsageError) {
528
+ return this.onUsage(err, lang);
529
+ }
530
+ return this.onUnknown(err, lang);
531
+ }
532
+ static addTemplate(template) {
533
+ _ErrorManager.TEMPLATES.set(template.type, template);
534
+ }
535
+ };
536
+ _ErrorManager.DEFAULT_MESSAGES = {
537
+ "es": "Ups, hemos encontrado un error. Nuestro equipo ya est\xE1 trabajando para solucionarlo",
538
+ "en": "Ups, we found an error. Our team is working on it.",
539
+ "pt": "Ops, encontramos um bug. Nossa equipe j\xE1 est\xE1 trabalhando para resolver isso."
540
+ };
541
+ _ErrorManager.APP_ERRORS = {
542
+ UNDEFINED: "UNDEFINED_ERROR",
543
+ PROCESS: "PROCESS_ERROR",
544
+ DATABASE: "DATABASE_ERROR"
545
+ };
546
+ _ErrorManager.TEMPLATES = /* @__PURE__ */ new Map();
547
+ var ErrorManager = _ErrorManager;
548
+
549
+ // src/domain/value-objects/Country.ts
550
+ ErrorManager.addTemplate({
551
+ type: "INVALID_COUNTRY",
552
+ languages: {
553
+ "es": "El pa\xEDs <{{country}}> no es v\xE1lido o no est\xE1 soportado",
554
+ "en": "Country <{{country}}> is not valid or not supported"
555
+ }
556
+ });
557
+ ErrorManager.addTemplate({
558
+ type: "COUNTRY_NOT_FOUND_BY_ALPHA2",
559
+ languages: {
560
+ "es": "No se encontr\xF3 pa\xEDs con c\xF3digo alpha2 <{{alpha2}}>",
561
+ "en": "Country not found with alpha2 code <{{alpha2}}>"
562
+ }
563
+ });
564
+ ErrorManager.addTemplate({
565
+ type: "COUNTRY_NOT_FOUND_BY_UUID",
566
+ languages: {
567
+ "es": "No se encontr\xF3 pa\xEDs con UUID <{{uuid}}>",
568
+ "en": "Country not found with UUID <{{uuid}}>"
569
+ }
570
+ });
571
+ var _Country = class _Country extends ValueObject {
572
+ constructor(country) {
573
+ const normalizedCountry = country.toUpperCase().trim();
574
+ super(normalizedCountry);
575
+ this._name = normalizedCountry;
576
+ this._alpha2 = _Country.COUNTRIES[normalizedCountry].alpha2;
577
+ this._alpha3 = _Country.COUNTRIES[normalizedCountry].alpha3;
578
+ this._numeric = _Country.COUNTRIES[normalizedCountry].numeric;
579
+ this._uuid = _Country.COUNTRIES[normalizedCountry].uuid;
580
+ this._phoneCode = _Country.COUNTRIES[normalizedCountry].phoneCode;
581
+ this._url = _Country.COUNTRIES[normalizedCountry].url;
582
+ }
583
+ validate(country) {
584
+ if (!_Country.NAMES.includes(country)) {
585
+ throw new UsageError("INVALID_COUNTRY", { country });
586
+ }
587
+ }
588
+ name() {
589
+ return this._name;
590
+ }
591
+ alpha2() {
592
+ return this._alpha2;
593
+ }
594
+ alpha3() {
595
+ return this._alpha3;
596
+ }
597
+ numeric() {
598
+ return this._numeric;
599
+ }
600
+ uuid() {
601
+ return this._uuid;
602
+ }
603
+ phoneCode() {
604
+ return this._phoneCode;
605
+ }
606
+ url() {
607
+ return this._url;
608
+ }
609
+ static findCountryByAlpha2(alpha2) {
610
+ for (const [country, codes] of Object.entries(_Country.COUNTRIES)) {
611
+ if (codes.alpha2 === alpha2.toUpperCase()) {
612
+ return new _Country(country);
613
+ }
614
+ }
615
+ throw new UsageError("COUNTRY_NOT_FOUND_BY_ALPHA2", { alpha2 });
616
+ }
617
+ static findCountryByUUID(uuid) {
618
+ for (const [country, codes] of Object.entries(_Country.COUNTRIES)) {
619
+ if (codes.uuid === uuid) {
620
+ return new _Country(country);
621
+ }
622
+ }
623
+ throw new UsageError("COUNTRY_NOT_FOUND_BY_UUID", { uuid });
624
+ }
625
+ static create(country) {
626
+ return new _Country(country);
627
+ }
628
+ static createOrDefault(country) {
629
+ try {
630
+ return new _Country(country);
631
+ } catch (error) {
632
+ return _Country.DEFAULT;
633
+ }
634
+ }
635
+ toPrimitives() {
636
+ return {
637
+ value: this.value,
638
+ name: this._name,
639
+ alpha2: this._alpha2,
640
+ alpha3: this._alpha3,
641
+ numeric: this._numeric,
642
+ uuid: this._uuid,
643
+ phoneCode: this._phoneCode,
644
+ url: this._url
645
+ };
646
+ }
647
+ static isValid(country) {
648
+ try {
649
+ _Country.create(country);
650
+ return true;
651
+ } catch {
652
+ return false;
653
+ }
654
+ }
655
+ };
656
+ _Country.COUNTRIES = {
657
+ URUGUAY: {
658
+ alpha2: "UY",
659
+ alpha3: "URY",
660
+ numeric: "858",
661
+ phoneCode: "+598",
662
+ uuid: "5739ecc0-d12b-4db5-8897-e03a04a95c72",
663
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/uruguay.png"
664
+ },
665
+ ARGENTINA: {
666
+ alpha2: "AR",
667
+ alpha3: "ARG",
668
+ numeric: "032",
669
+ phoneCode: "+54",
670
+ uuid: "66663efe-ab7a-4166-b971-9f36fd0ea6b2",
671
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/argentina.png"
672
+ },
673
+ ECUADOR: {
674
+ alpha2: "EC",
675
+ alpha3: "ECU",
676
+ numeric: "218",
677
+ phoneCode: "+593",
678
+ uuid: "ee109239-0150-4e5f-9ff2-a85f270092b1",
679
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/ecuador.png"
680
+ },
681
+ PERU: {
682
+ alpha2: "PE",
683
+ alpha3: "PER",
684
+ numeric: "604",
685
+ phoneCode: "+51",
686
+ uuid: "e4d61ef5-b92d-4f9c-8ec1-23f4beb50abd",
687
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/peru.png"
688
+ },
689
+ BRASIL: {
690
+ alpha2: "BR",
691
+ alpha3: "BRA",
692
+ numeric: "076",
693
+ phoneCode: "+55",
694
+ uuid: "b7b91d72-deaf-4641-957c-a65003e33104",
695
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/brasil.png"
696
+ },
697
+ CHILE: {
698
+ alpha2: "CL",
699
+ alpha3: "CHL",
700
+ numeric: "152",
701
+ phoneCode: "+56",
702
+ uuid: "f69b35f4-d734-4c76-866c-29a18bf000fb",
703
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/chile.png"
704
+ },
705
+ VENEZUELA: {
706
+ alpha2: "VE",
707
+ alpha3: "VEN",
708
+ numeric: "862",
709
+ phoneCode: "+58",
710
+ uuid: "31b6c591-63f6-43db-8ea9-829bb03746c5",
711
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/venezuela.png"
712
+ },
713
+ COLOMBIA: {
714
+ alpha2: "CO",
715
+ alpha3: "COL",
716
+ numeric: "170",
717
+ phoneCode: "+57",
718
+ uuid: "6fdfe34b-6726-4604-96af-665ea5fc9239",
719
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/colombia.png"
720
+ },
721
+ BOLIVIA: {
722
+ alpha2: "BO",
723
+ alpha3: "BOL",
724
+ numeric: "068",
725
+ phoneCode: "+591",
726
+ uuid: "948886db-c280-4ba7-a777-a76c180b295b",
727
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/bolivia.png"
728
+ },
729
+ PARAGUAY: {
730
+ alpha2: "PY",
731
+ alpha3: "PRY",
732
+ numeric: "600",
733
+ phoneCode: "+595",
734
+ uuid: "d67b3472-e38d-4900-8ae3-02d99cd1d884",
735
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/paraguay.png"
736
+ },
737
+ USA: {
738
+ alpha2: "US",
739
+ alpha3: "USA",
740
+ numeric: "840",
741
+ phoneCode: "+1",
742
+ uuid: "16ac3b9b-8f7b-4c54-91d5-d62c7cd74ad4",
743
+ url: "https://dev-wonasports.s3.us-east-2.amazonaws.com/assets/usa.png"
744
+ }
745
+ };
746
+ _Country.NAMES = Object.keys(_Country.COUNTRIES);
747
+ _Country.DEFAULT = new _Country("URUGUAY");
748
+ _Country.ARGENTINA = new _Country("ARGENTINA");
749
+ _Country.ECUADOR = new _Country("ECUADOR");
750
+ _Country.PERU = new _Country("PERU");
751
+ _Country.BRASIL = new _Country("BRASIL");
752
+ _Country.CHILE = new _Country("CHILE");
753
+ _Country.VENEZUELA = new _Country("VENEZUELA");
754
+ _Country.COLOMBIA = new _Country("COLOMBIA");
755
+ _Country.BOLIVIA = new _Country("BOLIVIA");
756
+ _Country.PARAGUAY = new _Country("PARAGUAY");
757
+ _Country.USA = new _Country("USA");
758
+ var Country = _Country;
759
+
243
760
  // src/domain/value-objects/Currency.ts
244
761
  var _Currency = class _Currency extends ValueObject {
245
762
  constructor(alpha) {
@@ -255,6 +772,11 @@ var _Currency = class _Currency extends ValueObject {
255
772
  throw new Error(`Currency <${code}> is not supported`);
256
773
  }
257
774
  }
775
+ toPrimitives() {
776
+ return {
777
+ value: this.value
778
+ };
779
+ }
258
780
  static create(raw) {
259
781
  if (typeof raw === "number" || _Currency.NUM_REGEX.test(raw)) {
260
782
  const num = Number(raw);
@@ -308,6 +830,11 @@ var _Email = class _Email extends ValueObject {
308
830
  throw new Error(`Email <${value}> is not a valid address`);
309
831
  }
310
832
  }
833
+ toPrimitives() {
834
+ return {
835
+ value: this.value
836
+ };
837
+ }
311
838
  static create(raw) {
312
839
  return new _Email(raw);
313
840
  }
@@ -325,12 +852,18 @@ var _Language = class _Language extends ValueObject {
325
852
  }
326
853
  validate(value) {
327
854
  if (!_Language.SUPPORTED.includes(value)) {
328
- throw new Error(`Language <${value}> is not supported`);
855
+ throw new InternalError(`Language <${value}> is not supported`);
329
856
  }
330
857
  }
331
858
  base() {
332
859
  return this.value.split("-")[0];
333
860
  }
861
+ toPrimitives() {
862
+ return {
863
+ base: this.base(),
864
+ value: this.value
865
+ };
866
+ }
334
867
  static create(raw) {
335
868
  const normalized = raw.trim().toLowerCase().replace("_", "-");
336
869
  try {
@@ -411,25 +944,43 @@ _Language.SPANISH_PUERTO_RICO = new _Language("es-pr");
411
944
  var Language = _Language;
412
945
 
413
946
  // src/domain/value-objects/Price.ts
947
+ ErrorManager.addTemplate({
948
+ type: "INVALID_PRICE_AMOUNT",
949
+ languages: {
950
+ "es": "El precio <{{amount}}> no es v\xE1lido",
951
+ "en": "Price amount <{{amount}}> is not a valid number"
952
+ }
953
+ });
954
+ ErrorManager.addTemplate({
955
+ type: "INVALID_PRICE_RANGE",
956
+ languages: {
957
+ "es": "El precio <{{amount}}> debe ser \u2265 {{min}} y \u2264 {{max}}",
958
+ "en": "Price amount <{{amount}}> must be \u2265 {{min}} and \u2264 {{max}}"
959
+ }
960
+ });
414
961
  var _Price = class _Price extends ValueObject {
415
962
  constructor(amount, currency) {
416
963
  super({ amount, currency });
417
- this.amount = amount;
418
- this.currency = currency;
419
964
  }
420
965
  validate(props) {
421
966
  const { amount, currency } = props;
422
967
  if (typeof amount !== "number" || Number.isNaN(amount) || !Number.isFinite(amount)) {
423
- throw new Error(`Price amount <${amount}> is not a valid number`);
968
+ throw new UsageError("INVALID_PRICE_AMOUNT", { amount });
424
969
  }
425
- if (amount < _Price.MIN_AMOUNT) {
426
- throw new Error(`Price amount <${amount}> must be \u2265 ${_Price.MIN_AMOUNT}`);
970
+ if (amount < _Price.MIN_AMOUNT || amount > _Price.MAX_AMOUNT) {
971
+ throw new UsageError("INVALID_PRICE_RANGE", { amount, min: _Price.MIN_AMOUNT, max: _Price.MAX_AMOUNT });
427
972
  }
428
973
  }
974
+ get amount() {
975
+ return this._value.amount;
976
+ }
977
+ get currency() {
978
+ return this._value.currency;
979
+ }
429
980
  equals(other) {
430
981
  if (!other)
431
982
  return false;
432
- return this.amount === other.amount && this.currency.equals(other.currency);
983
+ return this._value.amount === other.amount && this.currency.equals(other.currency);
433
984
  }
434
985
  assertSameCurrency(other) {
435
986
  if (!this.currency.equals(other.currency)) {
@@ -444,190 +995,537 @@ var _Price = class _Price extends ValueObject {
444
995
  this.assertSameCurrency(other);
445
996
  return _Price.create(this.amount - other.amount, this.currency);
446
997
  }
998
+ toPrimitives() {
999
+ return {
1000
+ amount: this.amount,
1001
+ currency: this.currency.value
1002
+ };
1003
+ }
447
1004
  static create(amount, currency) {
448
1005
  const cur = currency instanceof Currency ? currency : Currency.create(currency);
449
1006
  return new _Price(amount, cur);
450
1007
  }
1008
+ static createFromPrimitives(data) {
1009
+ return new _Price(Number(data.amount), Currency.create(String(data.currency)));
1010
+ }
451
1011
  };
452
1012
  _Price.MIN_AMOUNT = -1e6;
1013
+ _Price.MAX_AMOUNT = 1e9;
453
1014
  var Price = _Price;
454
1015
 
1016
+ // src/domain/value-objects/ProcessStatus.ts
1017
+ var _ProcessStatus = class _ProcessStatus extends ValueObject {
1018
+ constructor(status) {
1019
+ super(status.trim().toUpperCase());
1020
+ }
1021
+ validate(value) {
1022
+ if (!_ProcessStatus.SUPPORTED.includes(value)) {
1023
+ throw new InternalError(`Domain event status <${value}> is not supported`);
1024
+ }
1025
+ }
1026
+ toPrimitives() {
1027
+ return void 0;
1028
+ }
1029
+ static create(status) {
1030
+ return new _ProcessStatus(status);
1031
+ }
1032
+ };
1033
+ _ProcessStatus.SUPPORTED = ["PENDING", "PROCESSING", "PROCESSED", "FAILED", "DEAD"];
1034
+ _ProcessStatus.PENDING = new _ProcessStatus("PENDING");
1035
+ _ProcessStatus.PROCESSING = new _ProcessStatus("PROCESSING");
1036
+ _ProcessStatus.PROCESSED = new _ProcessStatus("PROCESSED");
1037
+ _ProcessStatus.FAILED = new _ProcessStatus("FAILED");
1038
+ _ProcessStatus.DEAD = new _ProcessStatus("DEAD");
1039
+ var ProcessStatus = _ProcessStatus;
1040
+
455
1041
  // src/domain/value-objects/UUID.ts
456
- var UUID = class _UUID extends ValueObject {
1042
+ var crypto = __toESM(require("crypto"));
1043
+ var _UUID = class _UUID extends ValueObject {
457
1044
  constructor(value) {
458
1045
  super(value);
459
1046
  }
460
1047
  validate(uuid) {
461
1048
  if (!_UUID.isValid(uuid)) {
462
- throw new Error(`Invalid uuid ${uuid}`);
1049
+ throw new InternalError(`Invalid uuid <${uuid}>`);
463
1050
  }
464
1051
  }
1052
+ toPrimitives() {
1053
+ return { value: this.value };
1054
+ }
465
1055
  static create(uuid) {
466
1056
  return new _UUID(uuid ?? crypto.randomUUID());
467
1057
  }
468
- static isValid(uuid) {
469
- return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid);
1058
+ static version(uuid) {
1059
+ const m = /^[0-9a-f]{8}-[0-9a-f]{4}-([1-8])[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.exec(uuid);
1060
+ return m ? Number(m[1]) : void 0;
1061
+ }
1062
+ static isNil(uuid) {
1063
+ return /^0{8}-0{4}-0{4}-0{4}-0{12}$/i.test(uuid);
1064
+ }
1065
+ static isRFCStyle(uuid) {
1066
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid);
1067
+ }
1068
+ static isValid(uuid, opts = {}) {
1069
+ const allowed = opts.allowedVersions ?? [1, 2, 3, 4, 5, 6, 7, 8];
1070
+ const allowNil = opts.allowNil ?? false;
1071
+ if (allowNil && _UUID.isNil(uuid))
1072
+ return true;
1073
+ if (!_UUID.isRFCStyle(uuid))
1074
+ return false;
1075
+ const v = _UUID.version(uuid);
1076
+ return !!v && allowed.includes(v);
470
1077
  }
471
1078
  };
1079
+ _UUID.NIL = "00000000-0000-0000-0000-000000000000";
1080
+ var UUID = _UUID;
472
1081
 
473
- // src/utils/StringVars.ts
474
- var StringVars = class {
475
- static parse(str, ob) {
476
- const regex = /{{(.*?)}}/g;
477
- return str.replace(regex, (match, variable) => {
478
- if (ob.hasOwnProperty(variable.trim())) {
479
- return ob[variable.trim()];
480
- } else {
481
- return match;
482
- }
483
- });
1082
+ // src/application/contracts/IntegrationEvent.ts
1083
+ var IntegrationEvent = class extends BaseEvent {
1084
+ constructor(tenantUuid, version, type, payload, aggregateUuid, aggregateType) {
1085
+ super(tenantUuid, version, type, payload);
1086
+ this._aggregateUuid = aggregateUuid;
1087
+ this._aggregateType = aggregateType;
1088
+ }
1089
+ get aggregateUuid() {
1090
+ return this._aggregateUuid;
1091
+ }
1092
+ get aggregateType() {
1093
+ return this._aggregateType;
484
1094
  }
485
1095
  };
486
1096
 
487
- // src/infrastructure/errors/ErrorManager.ts
488
- var _ErrorManager = class _ErrorManager {
489
- constructor(logger = null) {
490
- this.logger = logger;
1097
+ // src/application/event-bus/EventBus.ts
1098
+ var _EventBus = class _EventBus {
1099
+ constructor(repository) {
1100
+ this.repository = repository;
1101
+ }
1102
+ async publish(event) {
1103
+ const mapper = _EventBus.MAPPERS.get(event.type);
1104
+ if (!mapper) {
1105
+ throw new InternalError(ErrorManager.APP_ERRORS.PROCESS, `Eventbus mapper not found for <${event.type}>`);
1106
+ }
1107
+ await this.repository.create(mapper(event));
491
1108
  }
492
- getDefaultMessage(lang) {
493
- return _ErrorManager.DEFAULT_MESSAGES[lang.value] || _ErrorManager.DEFAULT_MESSAGES[lang.base()] || "error";
1109
+ async publishMany(events) {
1110
+ for (let event of events) {
1111
+ await this.publish(event);
1112
+ }
494
1113
  }
495
- onFatal(err, lang) {
496
- this.logger?.fatal(err.type, err.message);
497
- return { status: "ERROR", message: this.getDefaultMessage(lang) };
1114
+ static addMapper(eventType, mapper) {
1115
+ _EventBus.MAPPERS.set(eventType, mapper);
498
1116
  }
499
- onInternal(err, lang) {
500
- this.logger?.error(err.type, err.message);
501
- return { status: "ERROR", message: this.getDefaultMessage(lang) };
1117
+ };
1118
+ _EventBus.MAPPERS = /* @__PURE__ */ new Map();
1119
+ var EventBus = _EventBus;
1120
+
1121
+ // src/application/unit-of-work/BasicUnitOfWork.ts
1122
+ var BasicUnitOfWork = class {
1123
+ constructor(conn) {
1124
+ this.connection = conn;
502
1125
  }
503
- onUsage(err, lang) {
504
- const tmpl = _ErrorManager.TEMPLATES.get(err.type);
505
- if (!tmpl) {
506
- this.logger?.error("TEMPLATE_NOT_FOUND", `${err.type}`);
507
- return { status: "ERROR", message: this.getDefaultMessage(lang) };
1126
+ async execute(fn) {
1127
+ await this.connection.begin();
1128
+ try {
1129
+ const result = await fn();
1130
+ await this.connection.commit();
1131
+ return result;
1132
+ } catch (err) {
1133
+ await this.connection.rollback();
1134
+ throw err;
1135
+ } finally {
1136
+ await this.connection.close();
508
1137
  }
509
- const code = lang.value;
510
- const base = lang.base();
511
- const rawMsg = tmpl.languages[code] ?? tmpl.languages[base] ?? this.getDefaultMessage(lang);
512
- return {
513
- status: "ERROR",
514
- message: StringVars.parse(rawMsg, err.vars)
515
- };
516
1138
  }
517
- onUnknown(err, lang) {
518
- this.logger?.error("UNKNOWN_ERROR", err.message);
519
- return { status: "ERROR", message: this.getDefaultMessage(lang) };
1139
+ };
1140
+
1141
+ // src/application/unit-of-work/BasicUnitOfWorkFactory.ts
1142
+ var BasicUnitOfWorkFactory = class {
1143
+ constructor(connector) {
1144
+ this.connector = connector;
520
1145
  }
521
- handle(err, lang) {
522
- if (["local", "dev"].includes(process.env.ENVIRONMENT ?? "")) {
523
- console.log(err);
524
- }
525
- if (err instanceof FatalError) {
526
- return this.onFatal(err, lang);
527
- }
528
- if (err instanceof InternalError) {
529
- return this.onInternal(err, lang);
1146
+ async create() {
1147
+ const conn = await this.connector.getConnection();
1148
+ return new BasicUnitOfWork(conn);
1149
+ }
1150
+ };
1151
+
1152
+ // src/infrastructure/contracts/EventManager.ts
1153
+ var EventManager = class {
1154
+ constructor(connection) {
1155
+ this._connection = connection;
1156
+ this._topics = [];
1157
+ this._callbackList = {};
1158
+ this._onStart = null;
1159
+ this._onConnected = null;
1160
+ this._onSubscribe = null;
1161
+ this._onMessage = null;
1162
+ this._onError = null;
1163
+ this._onCrash = null;
1164
+ this._onReconnect = null;
1165
+ }
1166
+ async execRoute(topic, event) {
1167
+ if (this._callbackList[topic]) {
1168
+ await this._callbackList[topic](event);
530
1169
  }
531
- if (err instanceof UsageError) {
532
- return this.onUsage(err, lang);
1170
+ }
1171
+ async execCallback(callback, data) {
1172
+ if (callback) {
1173
+ await callback(data);
533
1174
  }
534
- return this.onUnknown(err, lang);
535
1175
  }
536
- static addTemplate(template) {
537
- _ErrorManager.TEMPLATES.set(template.type, template);
1176
+ route(topic, callback) {
1177
+ this._topics.push(topic);
1178
+ this._callbackList[topic] = callback;
1179
+ }
1180
+ onStart(callback) {
1181
+ this._onStart = callback;
1182
+ }
1183
+ onConnected(callback) {
1184
+ this._onConnected = callback;
1185
+ }
1186
+ onSubscribe(callback) {
1187
+ this._onSubscribe = callback;
1188
+ }
1189
+ onMessage(callback) {
1190
+ this._onMessage = callback;
1191
+ }
1192
+ onError(callback) {
1193
+ this._onError = callback;
1194
+ }
1195
+ onCrash(callback) {
1196
+ this._onCrash = callback;
1197
+ }
1198
+ onReconnect(callback) {
1199
+ this._onReconnect = callback;
1200
+ }
1201
+ get topics() {
1202
+ return this._topics;
1203
+ }
1204
+ get callbackList() {
1205
+ return this._callbackList;
538
1206
  }
539
1207
  };
540
- _ErrorManager.DEFAULT_MESSAGES = {
541
- "es": "Ups, hemos encontrado un error. Nuestro equipo ya est\xE1 trabajando para solucionarlo",
542
- "en": "Ups, we found an error. Our team is working on it.",
543
- "pt": "Ops, encontramos um bug. Nossa equipe j\xE1 est\xE1 trabalhando para resolver isso."
544
- };
545
- _ErrorManager.APP_ERRORS = {
546
- UNDEFINED: "UNDEFINED_ERROR",
547
- PROCESS: "PROCESS_ERROR",
548
- DATABASE: "DATABASE_ERROR"
549
- };
550
- _ErrorManager.TEMPLATES = /* @__PURE__ */ new Map();
551
- var ErrorManager = _ErrorManager;
552
-
553
- // src/infrastructure/mysql/MysqlConnector.ts
554
- var import_promise = require("mysql2/promise");
555
1208
 
556
- // src/infrastructure/mysql/MysqlConnection.ts
557
- var MysqlConnection = class {
558
- constructor(conn) {
559
- this._conn = conn;
1209
+ // src/infrastructure/contracts/OutboxRecord.ts
1210
+ var OutboxRecord = class _OutboxRecord {
1211
+ constructor(eventUuid, eventType, tenantUuid, aggregateUuid, aggregateType, topic, payload, status, attempts, errorMessage, publishedAt, lastAttempt, createdAt) {
1212
+ this._eventUuid = eventUuid;
1213
+ this._tenantUuid = tenantUuid;
1214
+ this._aggregateUuid = aggregateUuid;
1215
+ this._aggregateType = aggregateType;
1216
+ this._eventType = eventType;
1217
+ this._topic = topic;
1218
+ this._payload = payload;
1219
+ this._status = status;
1220
+ this._attempts = attempts;
1221
+ this._errorMessage = errorMessage;
1222
+ this._publishedAt = publishedAt;
1223
+ this._lastAttempt = lastAttempt;
1224
+ this._createdAt = createdAt ?? DateTime.now();
560
1225
  }
561
- async query(statement, params = []) {
562
- const [rows] = await this._conn.query(statement, params);
563
- return rows;
1226
+ get eventUuid() {
1227
+ return this._eventUuid;
564
1228
  }
565
- async begin() {
566
- await this._conn.beginTransaction();
1229
+ get tenantUuid() {
1230
+ return this._tenantUuid;
567
1231
  }
568
- async commit() {
569
- await this._conn.commit();
1232
+ get aggregateUuid() {
1233
+ return this._aggregateUuid;
570
1234
  }
571
- async rollback() {
572
- await this._conn.rollback();
1235
+ get aggregateType() {
1236
+ return this._aggregateType;
573
1237
  }
574
- async transaction(fn) {
575
- await this.begin();
576
- try {
577
- const result = await fn(this);
578
- await this.commit();
579
- return result;
580
- } catch (err) {
581
- await this.rollback();
582
- throw err;
583
- }
1238
+ get eventType() {
1239
+ return this._eventType;
584
1240
  }
585
- async close() {
586
- this._conn.release();
1241
+ get topic() {
1242
+ return this._topic;
1243
+ }
1244
+ get payload() {
1245
+ return this._payload;
1246
+ }
1247
+ get status() {
1248
+ return this._status;
1249
+ }
1250
+ get attempts() {
1251
+ return this._attempts;
1252
+ }
1253
+ get errorMessage() {
1254
+ return this._errorMessage;
1255
+ }
1256
+ get publishedAt() {
1257
+ return this._publishedAt;
1258
+ }
1259
+ get lastAttempt() {
1260
+ return this._lastAttempt;
1261
+ }
1262
+ get createdAt() {
1263
+ return this._createdAt;
1264
+ }
1265
+ incrementAttempts() {
1266
+ this._attempts++;
1267
+ this._lastAttempt = DateTime.now();
1268
+ }
1269
+ markProcessed() {
1270
+ this._status = ProcessStatus.PROCESSED;
1271
+ this._publishedAt = DateTime.now();
1272
+ }
1273
+ markProcessing() {
1274
+ this.incrementAttempts();
1275
+ this._status = ProcessStatus.PROCESSING;
1276
+ }
1277
+ markWithError(error) {
1278
+ this._status = this.attempts < 5 ? ProcessStatus.FAILED : ProcessStatus.DEAD;
1279
+ this._errorMessage = error;
1280
+ }
1281
+ toPrimitives() {
1282
+ return {
1283
+ eventUuid: this.eventUuid.value,
1284
+ eventType: this.eventType,
1285
+ tenantUuid: this.tenantUuid.value,
1286
+ aggregateUuid: this.aggregateUuid.value,
1287
+ aggregateType: this.aggregateType,
1288
+ topic: this.topic,
1289
+ payload: this.payload,
1290
+ status: this.status.value,
1291
+ attempts: this.attempts,
1292
+ errorMessage: this.errorMessage ?? void 0,
1293
+ publishedAt: this.publishedAt?.value ?? void 0,
1294
+ lastAttempt: this.lastAttempt?.value ?? void 0,
1295
+ createdAt: this.createdAt.value
1296
+ };
1297
+ }
1298
+ static reconstitute(data) {
1299
+ return new _OutboxRecord(
1300
+ UUID.create(data.event_uuid),
1301
+ String(data.event_type),
1302
+ UUID.create(data.tenant_uuid),
1303
+ UUID.create(data.aggregate_uuid),
1304
+ String(data.aggregate_type),
1305
+ String(data.topic),
1306
+ String(data.payload),
1307
+ ProcessStatus.create(data.status),
1308
+ Number(data.attempts),
1309
+ data.error_message ?? void 0,
1310
+ data.published_at ? DateTime.create(data.published_at) : void 0,
1311
+ data.last_attempt ? DateTime.create(data.last_attempt) : void 0,
1312
+ data.created_at ? DateTime.create(data.created_at) : void 0
1313
+ );
587
1314
  }
588
1315
  };
589
1316
 
590
- // src/infrastructure/mysql/MysqlConnector.ts
591
- var _MysqlConnector = class _MysqlConnector {
592
- constructor(pool) {
593
- this._pool = pool ?? (0, import_promise.createPool)({
594
- host: process.env.DB_HOST,
595
- port: Number(process.env.DB_PORT ?? 3306),
596
- user: process.env.DB_USER,
597
- password: process.env.DB_PASSWORD,
598
- database: process.env.DB_NAME,
599
- connectionLimit: Number(process.env.DB_POOL_SIZE) || _MysqlConnector.DEFAULT_POOL_SIZE,
600
- decimalNumbers: true
601
- });
1317
+ // src/infrastructure/contracts/InboxRecord.ts
1318
+ var InboxRecord = class _InboxRecord {
1319
+ constructor(eventUuid, tenantUuid, topic, producer, payload, status, attempts, errorMessage, lastAttempt, processedAt, createdAt) {
1320
+ this._eventUuid = eventUuid;
1321
+ this._tenantUuid = tenantUuid;
1322
+ this._topic = topic;
1323
+ this._producer = producer;
1324
+ this._payload = payload;
1325
+ this._status = status;
1326
+ this._attempts = attempts ?? 0;
1327
+ this._errorMessage = errorMessage ?? void 0;
1328
+ this._lastAttempt = lastAttempt ?? void 0;
1329
+ this._processedAt = processedAt ?? void 0;
1330
+ this._createdAt = createdAt ?? DateTime.now();
602
1331
  }
603
- async wrap(conn) {
604
- return new MysqlConnection(conn);
1332
+ get eventUuid() {
1333
+ return this._eventUuid;
605
1334
  }
606
- async getConnection() {
607
- const conn = await this._pool.getConnection();
608
- return this.wrap(conn);
1335
+ get tenantUuid() {
1336
+ return this._tenantUuid;
609
1337
  }
610
- async closePool() {
611
- await this._pool.end();
1338
+ get topic() {
1339
+ return this._topic;
612
1340
  }
613
- static async ping() {
614
- const connector = new _MysqlConnector();
615
- try {
616
- const conn = await connector._pool.getConnection();
617
- await conn.ping();
618
- conn.release();
619
- return true;
620
- } catch {
621
- return false;
622
- } finally {
623
- await connector.closePool();
624
- }
1341
+ get producer() {
1342
+ return this._producer;
1343
+ }
1344
+ get payload() {
1345
+ return this._payload;
1346
+ }
1347
+ get status() {
1348
+ return this._status;
1349
+ }
1350
+ get attempts() {
1351
+ return this._attempts;
1352
+ }
1353
+ get errorMessage() {
1354
+ return this._errorMessage;
1355
+ }
1356
+ get lastAttempt() {
1357
+ return this._lastAttempt;
1358
+ }
1359
+ get processedAt() {
1360
+ return this._processedAt;
1361
+ }
1362
+ get createdAt() {
1363
+ return this._createdAt;
1364
+ }
1365
+ incrementAttempts() {
1366
+ this._attempts++;
1367
+ this._lastAttempt = DateTime.now();
1368
+ }
1369
+ markProcessed() {
1370
+ this._status = ProcessStatus.PROCESSED;
1371
+ this._processedAt = DateTime.now();
1372
+ }
1373
+ markProcessing() {
1374
+ this.incrementAttempts();
1375
+ this._status = ProcessStatus.PROCESSING;
1376
+ }
1377
+ markWithError(error) {
1378
+ this._status = this.attempts < 5 ? ProcessStatus.FAILED : ProcessStatus.DEAD;
1379
+ this._errorMessage = error;
1380
+ }
1381
+ toPrimitives() {
1382
+ return {
1383
+ eventUuid: this.eventUuid.value,
1384
+ tenantUuid: this.tenantUuid.value,
1385
+ topic: this.topic,
1386
+ producer: this.producer,
1387
+ payload: this.payload,
1388
+ status: this.status.value,
1389
+ attempts: this.attempts,
1390
+ errorMessage: this.errorMessage ?? void 0,
1391
+ lastAttempt: this.lastAttempt?.value ?? void 0,
1392
+ processedAt: this.processedAt?.value ?? void 0,
1393
+ createdAt: this.createdAt.value
1394
+ };
1395
+ }
1396
+ static reconstitute(data) {
1397
+ return new _InboxRecord(
1398
+ UUID.create(data.event_uuid),
1399
+ UUID.create(data.tenant_uuid),
1400
+ String(data.topic),
1401
+ String(data.producer),
1402
+ String(data.payload),
1403
+ ProcessStatus.create(data.status),
1404
+ Number(data.attempts),
1405
+ data.error_message ?? void 0,
1406
+ data.last_attempt ? DateTime.create(data.last_attempt) : void 0,
1407
+ data.processed_at ? DateTime.create(data.processed_at) : void 0,
1408
+ data.created_at ? DateTime.create(data.created_at) : void 0
1409
+ );
1410
+ }
1411
+ };
1412
+
1413
+ // src/infrastructure/event-bus/EventBusMysqlRepository.ts
1414
+ var EventBusMysqlRepository = class {
1415
+ constructor(connection) {
1416
+ this.connection = connection;
1417
+ }
1418
+ recordToRowValues(record) {
1419
+ return [
1420
+ record.eventUuid.value,
1421
+ record.eventType,
1422
+ record.tenantUuid.value,
1423
+ record.aggregateUuid.value,
1424
+ record.aggregateType,
1425
+ record.topic,
1426
+ record.payload,
1427
+ record.status.value,
1428
+ record.attempts,
1429
+ record.errorMessage,
1430
+ record.publishedAt?.value,
1431
+ record.lastAttempt?.value,
1432
+ record.createdAt.value
1433
+ ];
1434
+ }
1435
+ async create(record) {
1436
+ const values = this.recordToRowValues(record);
1437
+ await this.connection.query(
1438
+ `INSERT INTO events_outbox (event_uuid, event_type, tenant_uuid, aggregate_uuid, aggregate_type, topic,
1439
+ payload, status, attempts, error_message, published_at, last_attempt, created_at)
1440
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1441
+ values
1442
+ );
1443
+ }
1444
+ async update(record) {
1445
+ const values = [record.status.value, record.attempts, record.errorMessage, record.publishedAt?.value, record.lastAttempt?.value, record.eventUuid.value];
1446
+ await this.connection.query(
1447
+ `UPDATE events_outbox
1448
+ SET status = ?,
1449
+ attempts = ?,
1450
+ error_message = ?,
1451
+ published_at = ?,
1452
+ last_attempt = ?
1453
+ WHERE event_uuid = ?`,
1454
+ values
1455
+ );
1456
+ }
1457
+ async listPending(limit) {
1458
+ const result = await this.connection.query(
1459
+ `SELECT * FROM events_outbox WHERE status IN ('PENDING','FAILED') AND published_at IS NULL LIMIT ${limit}`,
1460
+ []
1461
+ );
1462
+ return result.length > 0 ? result.map((r) => OutboxRecord.reconstitute(r)) : [];
625
1463
  }
626
1464
  };
627
- _MysqlConnector.DEFAULT_POOL_SIZE = 10;
628
- var MysqlConnector = _MysqlConnector;
629
1465
 
630
1466
  // src/infrastructure/express/ExpressAdapters.ts
1467
+ var BOOL_TRUE = /* @__PURE__ */ new Set(["1", "true", "yes", "y", "on"]);
1468
+ var BOOL_FALSE = /* @__PURE__ */ new Set(["0", "false", "no", "n", "off"]);
1469
+ var NUMERIC_KEY_RE = /^(?:page|qty|limit|offset|total|amount|price|count|rounding|min[A-Z_].*|max[A-Z_].*)$/i;
1470
+ var DATE_KEY_RE = /(At|Date|_at|_date)$/i;
1471
+ function toUtcDateTimeString(raw) {
1472
+ const s = raw.trim();
1473
+ if (/^\d{4}-\d{2}-\d{2}$/.test(s))
1474
+ return `${s} 00:00:00`;
1475
+ if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/.test(s))
1476
+ return s;
1477
+ const d = new Date(s);
1478
+ if (Number.isNaN(d.getTime()))
1479
+ return void 0;
1480
+ const pad = (n) => String(n).padStart(2, "0");
1481
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
1482
+ }
1483
+ function coerceScalar(key, v) {
1484
+ if (v == null)
1485
+ return void 0;
1486
+ if (typeof v !== "string")
1487
+ return v;
1488
+ const s = v.trim();
1489
+ if (s === "")
1490
+ return void 0;
1491
+ if (s.toLowerCase() === "null")
1492
+ return null;
1493
+ const low = s.toLowerCase();
1494
+ if (BOOL_TRUE.has(low))
1495
+ return true;
1496
+ if (BOOL_FALSE.has(low))
1497
+ return false;
1498
+ if (NUMERIC_KEY_RE.test(key) && /^-?\d+(\.\d+)?$/.test(s)) {
1499
+ const n = Number(s);
1500
+ if (Number.isFinite(n))
1501
+ return n;
1502
+ }
1503
+ if (DATE_KEY_RE.test(key)) {
1504
+ return toUtcDateTimeString(s) ?? s;
1505
+ }
1506
+ return s;
1507
+ }
1508
+ function normalizeQuery(q) {
1509
+ const out = {};
1510
+ for (const [key, raw] of Object.entries(q)) {
1511
+ const values = Array.isArray(raw) ? raw : typeof raw === "string" && raw.includes(",") ? raw.split(",").map((s) => s.trim()).filter(Boolean) : [raw];
1512
+ const coerced = values.map((v) => coerceScalar(key, v)).filter((v) => v !== void 0);
1513
+ if (coerced.length === 1)
1514
+ out[key] = coerced[0];
1515
+ else if (coerced.length > 1)
1516
+ out[key] = coerced;
1517
+ }
1518
+ return out;
1519
+ }
1520
+ function isReadableStream(x) {
1521
+ return x && typeof x.pipe === "function";
1522
+ }
1523
+ function hasHeader(headers, name) {
1524
+ if (!headers)
1525
+ return false;
1526
+ const lname = name.toLowerCase();
1527
+ return Object.keys(headers).some((h) => h.toLowerCase() === lname);
1528
+ }
631
1529
  function adaptExpressRoute(Controller) {
632
1530
  return async (req, res, next) => {
633
1531
  const rawLangHeader = req.headers["accept-language"] ?? req.headers["Accept-Language"] ?? "es";
@@ -636,20 +1534,55 @@ function adaptExpressRoute(Controller) {
636
1534
  const httpRequest = {
637
1535
  headers: req.headers,
638
1536
  params: req.params,
639
- query: Object.fromEntries(Object.entries(req.query).map(([k, v]) => [k, String(v)])),
1537
+ query: normalizeQuery(req.query),
640
1538
  lang,
641
1539
  body: req.body
642
1540
  };
643
1541
  try {
644
1542
  const controller = new Controller();
645
- const { statusCode, body } = await controller.handle(httpRequest);
646
- res.status(statusCode).json(body);
1543
+ const { statusCode, body, headers } = await controller.handle(httpRequest);
1544
+ if (headers)
1545
+ res.set(headers);
1546
+ if (isReadableStream(body)) {
1547
+ if (!hasHeader(headers, "Content-Type"))
1548
+ res.set("Content-Type", "application/octet-stream");
1549
+ res.status(statusCode);
1550
+ body.on("error", next);
1551
+ body.pipe(res);
1552
+ return;
1553
+ }
1554
+ const isArrayBuffer = typeof body === "object" && body instanceof ArrayBuffer;
1555
+ const isArrayBufferView = typeof body === "object" && body != null && ArrayBuffer.isView(body);
1556
+ if (Buffer.isBuffer(body) || isArrayBuffer || isArrayBufferView) {
1557
+ let buf;
1558
+ if (Buffer.isBuffer(body)) {
1559
+ buf = body;
1560
+ } else if (isArrayBuffer) {
1561
+ buf = Buffer.from(body);
1562
+ } else {
1563
+ const view = body;
1564
+ buf = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1565
+ }
1566
+ if (!hasHeader(headers, "Content-Type"))
1567
+ res.set("Content-Type", "application/octet-stream");
1568
+ if (!res.getHeader("Content-Length"))
1569
+ res.set("Content-Length", String(buf.length));
1570
+ res.status(statusCode);
1571
+ res.end(buf);
1572
+ return;
1573
+ }
1574
+ const hasCT = hasHeader(headers, "Content-Type");
1575
+ if (typeof body === "string" || hasCT) {
1576
+ res.status(statusCode).send(body);
1577
+ return;
1578
+ }
1579
+ res.status(statusCode).json(body ?? {});
647
1580
  } catch (err) {
648
1581
  next(err);
649
1582
  }
650
1583
  };
651
1584
  }
652
- function adaptErrorHandler(errorManager) {
1585
+ function adaptExpressErrorHandler(errorManager) {
653
1586
  return (err, req, res, next) => {
654
1587
  const raw = req.headers["accept-language"] ?? req.headers["Accept-Language"] ?? "es";
655
1588
  const rawLang = Array.isArray(raw) ? raw[0] : raw ?? "";
@@ -660,75 +1593,465 @@ function adaptErrorHandler(errorManager) {
660
1593
  };
661
1594
  }
662
1595
 
663
- // src/infrastructure/http/HttpNotFoundController.ts
1596
+ // src/infrastructure/http/DefaultController.ts
1597
+ var HttpHealthCheckController = class {
1598
+ async handle(request) {
1599
+ return {
1600
+ statusCode: 200,
1601
+ body: {
1602
+ date: DateTime.create().value,
1603
+ code: 200
1604
+ }
1605
+ };
1606
+ }
1607
+ };
664
1608
  var HttpNotFoundController = class {
665
1609
  async handle(request) {
666
1610
  return {
667
1611
  statusCode: 404,
668
1612
  body: {
669
- status: "NOT_FOUND",
670
1613
  message: `Route ${request.headers.location} not found`
671
1614
  }
672
1615
  };
673
1616
  }
674
1617
  };
675
1618
 
676
- // src/infrastructure/http/HttpHealthCheckController.ts
677
- var HttpHealthCheckController = class {
678
- async handle(request) {
679
- return {
680
- statusCode: 200,
681
- body: {
682
- date: DateTime.create().value,
683
- code: 200,
684
- status: "OK"
1619
+ // src/infrastructure/kafka/KafkaManager.ts
1620
+ var import_kafkajs = require("kafkajs");
1621
+ var KafkaManager = class extends EventManager {
1622
+ constructor(connection) {
1623
+ super(connection);
1624
+ this.kafka = new import_kafkajs.Kafka({
1625
+ brokers: this._connection.brokers,
1626
+ ssl: true,
1627
+ sasl: {
1628
+ mechanism: "scram-sha-256",
1629
+ username: this._connection.userName,
1630
+ password: this._connection.password
1631
+ },
1632
+ logLevel: import_kafkajs.logLevel.ERROR
1633
+ });
1634
+ this.consumer = this.kafka.consumer({ groupId: process.env.KAFKA_GROUP || "default" });
1635
+ this.producer = this.kafka.producer();
1636
+ }
1637
+ async run(autocommit) {
1638
+ const __run = async () => {
1639
+ await this.execCallback(this._onStart, { message: `--- ${this.constructor.name} started ---` });
1640
+ await this.consumer.connect();
1641
+ await this.execCallback(this._onConnected, { message: `--- ${this.constructor.name} connected to ${this._connection.brokers} ---` });
1642
+ for (let topic of this._topics) {
1643
+ try {
1644
+ await this.consumer.subscribe({ topic, fromBeginning: true });
1645
+ await this.execCallback(this._onSubscribe, { message: `--- ${this.constructor.name} subscribed to ${topic} ---` });
1646
+ } catch (error) {
1647
+ await this.execCallback(this._onConnected, { message: `Error on subscribe to kafka topic: ${topic}` });
1648
+ }
685
1649
  }
1650
+ await this.consumer.run({
1651
+ autoCommit: autocommit,
1652
+ // @ts-ignore
1653
+ eachMessage: async ({ topic, partition, message, heartbeat }) => {
1654
+ try {
1655
+ await this.execCallback(this._onMessage, `[New message detected for ${topic}]: ${message.value?.toString()}`);
1656
+ const json = JSON.parse(String(message.value?.toString()));
1657
+ await this.execRoute(topic, {
1658
+ topic: String(json.topic),
1659
+ producer: String(json.producer),
1660
+ tenant: UUID.create(String(json.tenant)),
1661
+ message: json.message
1662
+ });
1663
+ const next = (BigInt(message.offset) + 1n).toString();
1664
+ await this.consumer.commitOffsets([{ topic, partition, offset: next }]);
1665
+ await heartbeat();
1666
+ } catch (error) {
1667
+ await this.execCallback(this._onError, error);
1668
+ }
1669
+ }
1670
+ });
686
1671
  };
1672
+ try {
1673
+ await __run();
1674
+ } catch (error) {
1675
+ await this.execCallback(this._onError, new InternalError(ErrorManager.APP_ERRORS.PROCESS, error.toString()));
1676
+ }
1677
+ }
1678
+ async send(e) {
1679
+ const evt = {
1680
+ date: DateTime.now().value,
1681
+ tenant: e.tenant.value,
1682
+ producer: e.producer,
1683
+ data: JSON.parse(e.message)
1684
+ };
1685
+ try {
1686
+ if (!this.producer) {
1687
+ throw new InternalError(ErrorManager.APP_ERRORS.PROCESS, "Producer not initialized");
1688
+ }
1689
+ await this.producer.connect();
1690
+ await this.producer.send({
1691
+ topic: e.topic,
1692
+ messages: [{ value: JSON.stringify(evt) }]
1693
+ });
1694
+ await this.producer.disconnect();
1695
+ } catch (error) {
1696
+ throw new InternalError(error.toString());
1697
+ }
1698
+ }
1699
+ async start(autocommit = false) {
1700
+ this.consumer.on(this.consumer.events.CRASH, async (error) => {
1701
+ await this.execCallback(this._onError, new InternalError(ErrorManager.APP_ERRORS.PROCESS, error.payload.error.stack));
1702
+ });
1703
+ await this.run(autocommit);
1704
+ }
1705
+ async pause() {
1706
+ }
1707
+ async restart() {
1708
+ await this.consumer.stop();
1709
+ await this.start();
1710
+ }
1711
+ async stop() {
1712
+ await this.consumer.stop();
687
1713
  }
688
1714
  };
689
1715
 
690
- // src/infrastructure/http/HttpErrorController.ts
691
- var HttpErrorController = class {
692
- constructor(error, errorManager) {
693
- this.error = error;
694
- this.errorManager = errorManager;
1716
+ // src/infrastructure/mysql/Mysql.ts
1717
+ var import_promise = require("mysql2/promise");
1718
+ var _MysqlConnector = class _MysqlConnector {
1719
+ constructor(pool) {
1720
+ this._pool = pool ?? (0, import_promise.createPool)({
1721
+ host: process.env.DB_HOST,
1722
+ port: Number(process.env.DB_PORT ?? 3306),
1723
+ user: process.env.DB_USER,
1724
+ password: process.env.DB_PASSWORD,
1725
+ database: process.env.DB_DATABASE,
1726
+ dateStrings: true,
1727
+ connectionLimit: Number(process.env.DB_POOL_SIZE ?? _MysqlConnector.DEFAULT_POOL_SIZE),
1728
+ decimalNumbers: true
1729
+ });
695
1730
  }
696
- async handle(request) {
697
- const result = this.errorManager.handle(
698
- this.error,
699
- Language.create(request.lang)
1731
+ async wrap(conn) {
1732
+ return new MysqlConnection(conn);
1733
+ }
1734
+ async query(sql, params = []) {
1735
+ const [rows] = await this._pool.query(sql, params);
1736
+ return rows;
1737
+ }
1738
+ async getConnection() {
1739
+ const conn = await this._pool.getConnection();
1740
+ return this.wrap(conn);
1741
+ }
1742
+ async closePool() {
1743
+ await this._pool.end();
1744
+ }
1745
+ static async ping() {
1746
+ const connector = new _MysqlConnector();
1747
+ try {
1748
+ const conn = await connector._pool.getConnection();
1749
+ await conn.ping();
1750
+ conn.release();
1751
+ return true;
1752
+ } catch {
1753
+ return false;
1754
+ } finally {
1755
+ await connector.closePool();
1756
+ }
1757
+ }
1758
+ };
1759
+ _MysqlConnector.DEFAULT_POOL_SIZE = 20;
1760
+ var MysqlConnector = _MysqlConnector;
1761
+ var MysqlConnection = class {
1762
+ constructor(conn) {
1763
+ this._conn = conn;
1764
+ }
1765
+ async query(statement, params = []) {
1766
+ const [rows] = await this._conn.query(statement, params);
1767
+ return rows;
1768
+ }
1769
+ async begin() {
1770
+ await this._conn.beginTransaction();
1771
+ }
1772
+ async commit() {
1773
+ await this._conn.commit();
1774
+ }
1775
+ async rollback() {
1776
+ await this._conn.rollback();
1777
+ }
1778
+ async transaction(fn) {
1779
+ await this.begin();
1780
+ try {
1781
+ const result = await fn(this);
1782
+ await this.commit();
1783
+ return result;
1784
+ } catch (err) {
1785
+ await this.rollback();
1786
+ throw err;
1787
+ }
1788
+ }
1789
+ async close() {
1790
+ await this._conn.release();
1791
+ }
1792
+ };
1793
+
1794
+ // src/infrastructure/runners/default-mysql-outbox-runner.ts
1795
+ var DefaultMysqlOutboxRunner = class {
1796
+ constructor(uowFactory, eventManager) {
1797
+ this.errors = [];
1798
+ this.uowFactory = uowFactory;
1799
+ this.eventManager = eventManager;
1800
+ this.interval = Number(process.env.OUTBOX_RUNNER_INTERVAL_MS || 5e3);
1801
+ this.maxEvents = Number(process.env.OUTBOX_RUNNER_MAX_EVENTS || 20);
1802
+ }
1803
+ addError(error) {
1804
+ this.errors.push(error);
1805
+ }
1806
+ logErrors() {
1807
+ if (this.errors.length > 0) {
1808
+ console.error(this.errors);
1809
+ this.errors = [];
1810
+ }
1811
+ }
1812
+ async sleep() {
1813
+ return new Promise((r) => setTimeout(r, this.maxEvents));
1814
+ }
1815
+ async processOutboxRecord(e, eventBusRepository) {
1816
+ try {
1817
+ e.markProcessing();
1818
+ await eventBusRepository.update(e);
1819
+ await this.eventManager.send({
1820
+ topic: e.topic,
1821
+ producer: process.env.NAME && process.env.ENVIRONMENT ? `${process.env.NAME}-${process.env.ENVIRONMENT}` : "unknown",
1822
+ tenant: e.tenantUuid,
1823
+ message: e.payload
1824
+ });
1825
+ e.markProcessed();
1826
+ } catch (error) {
1827
+ const type = String(error.type);
1828
+ e.markWithError(type);
1829
+ throw new Error(type);
1830
+ } finally {
1831
+ await eventBusRepository.update(e);
1832
+ }
1833
+ }
1834
+ async process() {
1835
+ const uow = await this.uowFactory.create();
1836
+ const eventBusRepository = new EventBusMysqlRepository(uow.connection);
1837
+ await uow.execute(async () => {
1838
+ const records = await eventBusRepository.listPending(this.maxEvents);
1839
+ for (let record of records) {
1840
+ try {
1841
+ await this.processOutboxRecord(record, eventBusRepository);
1842
+ } catch (error) {
1843
+ this.addError({ eventUuid: record.eventUuid.value, error: error.toString() });
1844
+ }
1845
+ }
1846
+ this.logErrors();
1847
+ });
1848
+ }
1849
+ async start() {
1850
+ console.log("[outbox-runner]: start");
1851
+ for (; ; ) {
1852
+ await this.process();
1853
+ await this.sleep();
1854
+ }
1855
+ }
1856
+ };
1857
+
1858
+ // src/infrastructure/inbox/InboxMysqlRepository.ts
1859
+ var InboxMysqlRepository = class {
1860
+ constructor(connection) {
1861
+ this.connection = connection;
1862
+ }
1863
+ recordToRowValues(record) {
1864
+ return [
1865
+ record.eventUuid.value,
1866
+ record.tenantUuid.value,
1867
+ record.topic,
1868
+ record.producer,
1869
+ record.payload,
1870
+ record.status.value,
1871
+ record.attempts,
1872
+ record.errorMessage,
1873
+ record.lastAttempt?.value ?? null,
1874
+ record.processedAt?.value ?? null,
1875
+ record.createdAt.value
1876
+ ];
1877
+ }
1878
+ async create(record) {
1879
+ const values = this.recordToRowValues(record);
1880
+ await this.connection.query(
1881
+ `INSERT INTO events_inbox (event_uuid, tenant_uuid, topic, producer,
1882
+ payload, status, attempts, error_message, last_attempt, processed_at, created_at)
1883
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1884
+ values
700
1885
  );
701
- const statusCode = typeof result.status === "number" ? result.status : 500;
702
- return {
703
- statusCode,
704
- body: {
705
- message: result.message
1886
+ }
1887
+ async update(record) {
1888
+ const values = [record.status.value, record.attempts, record.errorMessage, record.processedAt?.value, record.lastAttempt?.value, record.eventUuid.value];
1889
+ await this.connection.query(
1890
+ `UPDATE events_inbox
1891
+ SET status = ?,
1892
+ attempts = ?,
1893
+ error_message = ?,
1894
+ processed_at = ?,
1895
+ last_attempt = ?
1896
+ WHERE event_uuid = ?`,
1897
+ values
1898
+ );
1899
+ }
1900
+ async listPending(limit) {
1901
+ const result = await this.connection.query(
1902
+ `SELECT * FROM events_inbox WHERE status IN ('PENDING','FAILED') AND events_inbox.processed_at IS NULL LIMIT ${limit}`,
1903
+ []
1904
+ );
1905
+ return result.length > 0 ? result.map((r) => InboxRecord.reconstitute(r)) : [];
1906
+ }
1907
+ };
1908
+
1909
+ // src/infrastructure/runners/default-mysql-inbox-runner.ts
1910
+ var DefaultMysqlInboxRunner = class {
1911
+ constructor(uowFactory, eventManager) {
1912
+ this.topics = [];
1913
+ this.uowFactory = uowFactory;
1914
+ this.eventManager = eventManager;
1915
+ this.interval = Number(process.env.OUTBOX_RUNNER_INTERVAL_MS || 5e3);
1916
+ this.maxEvents = Number(process.env.OUTBOX_RUNNER_MAX_EVENTS || 20);
1917
+ }
1918
+ async saveEvent(e, repository) {
1919
+ let record = new InboxRecord(
1920
+ UUID.create(),
1921
+ e.tenant,
1922
+ e.topic,
1923
+ e.producer,
1924
+ JSON.stringify(e.message),
1925
+ ProcessStatus.PENDING
1926
+ );
1927
+ try {
1928
+ await repository.create(record);
1929
+ } catch (error) {
1930
+ const type = String(error.type);
1931
+ record.markWithError(type);
1932
+ throw new Error(type);
1933
+ } finally {
1934
+ await repository.update(record);
1935
+ }
1936
+ }
1937
+ subscribeTo(topic) {
1938
+ this.topics.push(topic);
1939
+ }
1940
+ async process() {
1941
+ const uow = await this.uowFactory.create();
1942
+ const inboxRepository = new InboxMysqlRepository(uow.connection);
1943
+ await uow.execute(async () => {
1944
+ for (let topic of this.topics) {
1945
+ try {
1946
+ this.eventManager.route(topic, async (e) => {
1947
+ await this.saveEvent(e, inboxRepository);
1948
+ });
1949
+ } catch (error) {
1950
+ console.log(error.toString());
1951
+ }
706
1952
  }
1953
+ await this.eventManager.start();
1954
+ });
1955
+ }
1956
+ async start() {
1957
+ console.log("[inbox-runner]: start");
1958
+ for (; ; ) {
1959
+ await this.process();
1960
+ }
1961
+ }
1962
+ };
1963
+
1964
+ // src/utils/ExchangeRates.ts
1965
+ var ExchangeRates = class _ExchangeRates extends BaseObject {
1966
+ constructor(props) {
1967
+ super(props);
1968
+ }
1969
+ getRate(currency) {
1970
+ if (Object.keys(this.props.rates).includes(currency.value)) {
1971
+ return this.props.rates[currency.value];
1972
+ }
1973
+ return null;
1974
+ }
1975
+ get base() {
1976
+ return this.props.base;
1977
+ }
1978
+ get rates() {
1979
+ return this.props.rates;
1980
+ }
1981
+ get date() {
1982
+ return this.props.date;
1983
+ }
1984
+ toProps() {
1985
+ return this.props;
1986
+ }
1987
+ toPrimitives() {
1988
+ return {
1989
+ base: this.props.base.value,
1990
+ rates: this.props.rates,
1991
+ date: this.props.date.value
707
1992
  };
708
1993
  }
1994
+ exchangeToBase(price) {
1995
+ const rate = this.getRate(price.currency);
1996
+ if (!rate) {
1997
+ throw new InternalError("INVALID_EXCHANGE_RATE_CURRENCY", `Avaiable rates: ${this.props.rates} - Base Currency:${this.props.base.value} - Price Currency: ${price.currency.value}`);
1998
+ }
1999
+ if (price.currency.value === this.props.base.value) {
2000
+ return price;
2001
+ }
2002
+ return Price.create(parseFloat((price.amount / rate).toFixed(2)), this.props.base.value);
2003
+ }
2004
+ static create(props) {
2005
+ return new _ExchangeRates(props);
2006
+ }
2007
+ static createFromPrimitives(data) {
2008
+ return _ExchangeRates.create({
2009
+ base: Currency.create(data.base),
2010
+ rates: data.rates ?? [],
2011
+ date: DateTime.create(data.date ?? "")
2012
+ });
2013
+ }
709
2014
  };
710
2015
  // Annotate the CommonJS export names for ESM import in node:
711
2016
  0 && (module.exports = {
2017
+ BaseEvent,
2018
+ BaseObject,
2019
+ BasicUnitOfWork,
2020
+ BasicUnitOfWorkFactory,
2021
+ Country,
712
2022
  Currency,
713
2023
  DateTime,
2024
+ DefaultMysqlInboxRunner,
2025
+ DefaultMysqlOutboxRunner,
714
2026
  DomainEntity,
715
2027
  DomainError,
716
2028
  DomainEvent,
717
2029
  Email,
718
2030
  ErrorManager,
2031
+ EventBus,
2032
+ EventBusMysqlRepository,
2033
+ EventManager,
2034
+ ExchangeRates,
719
2035
  FatalError,
720
- HttpErrorController,
721
2036
  HttpHealthCheckController,
722
2037
  HttpNotFoundController,
2038
+ InboxRecord,
2039
+ IntegrationEvent,
723
2040
  InternalError,
2041
+ KafkaManager,
724
2042
  Language,
725
2043
  MysqlConnection,
726
2044
  MysqlConnector,
2045
+ OutboxRecord,
2046
+ PaymentGateway,
2047
+ PaymentStatus,
727
2048
  Price,
2049
+ ProcessStatus,
2050
+ StringVars,
728
2051
  UUID,
729
2052
  UsageError,
730
2053
  ValueObject,
731
- adaptErrorHandler,
2054
+ adaptExpressErrorHandler,
732
2055
  adaptExpressRoute
733
2056
  });
734
2057
  //# sourceMappingURL=index.cjs.map