@russ-b/nestjs-common-tools 2.1.0 → 2.2.0

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 (97) hide show
  1. package/README.md +259 -0
  2. package/dist/common/util/error.util.d.ts +1 -0
  3. package/dist/common/util/error.util.js +10 -0
  4. package/dist/common/util/error.util.js.map +1 -0
  5. package/dist/common/util/index.d.ts +1 -0
  6. package/dist/common/util/index.js +1 -0
  7. package/dist/common/util/index.js.map +1 -1
  8. package/dist/errors/api-error-code.enum.d.ts +11 -0
  9. package/dist/errors/api-error-code.enum.js +16 -0
  10. package/dist/errors/api-error-code.enum.js.map +1 -0
  11. package/dist/errors/api-error-response.type.d.ts +11 -0
  12. package/dist/errors/api-error-response.type.js +3 -0
  13. package/dist/errors/api-error-response.type.js.map +1 -0
  14. package/dist/errors/api-error-root-field.constant.d.ts +1 -0
  15. package/dist/errors/api-error-root-field.constant.js +5 -0
  16. package/dist/errors/api-error-root-field.constant.js.map +1 -0
  17. package/dist/errors/class-validator-exception.factory.d.ts +6 -0
  18. package/dist/errors/class-validator-exception.factory.js +38 -0
  19. package/dist/errors/class-validator-exception.factory.js.map +1 -0
  20. package/dist/errors/error-response.factory.d.ts +10 -0
  21. package/dist/errors/error-response.factory.js +46 -0
  22. package/dist/errors/error-response.factory.js.map +1 -0
  23. package/dist/errors/index.d.ts +5 -0
  24. package/dist/errors/index.js +22 -0
  25. package/dist/errors/index.js.map +1 -0
  26. package/dist/logger/pino/index.d.ts +1 -0
  27. package/dist/{common/filters → logger/pino}/index.js +1 -1
  28. package/dist/logger/pino/index.js.map +1 -0
  29. package/dist/logger/pino-logger.d.ts +13 -0
  30. package/dist/logger/pino-logger.js +62 -0
  31. package/dist/logger/pino-logger.js.map +1 -0
  32. package/dist/modules/index.d.ts +1 -0
  33. package/dist/modules/index.js +1 -0
  34. package/dist/modules/index.js.map +1 -1
  35. package/dist/modules/outbox/entities/index.d.ts +1 -0
  36. package/dist/modules/outbox/entities/index.js +18 -0
  37. package/dist/modules/outbox/entities/index.js.map +1 -0
  38. package/dist/modules/outbox/entities/outbox-event.entity.d.ts +12 -0
  39. package/dist/modules/outbox/entities/outbox-event.entity.js +67 -0
  40. package/dist/modules/outbox/entities/outbox-event.entity.js.map +1 -0
  41. package/dist/modules/outbox/enums/index.d.ts +1 -0
  42. package/dist/modules/outbox/enums/index.js +18 -0
  43. package/dist/modules/outbox/enums/index.js.map +1 -0
  44. package/dist/modules/outbox/enums/outbox-event-status.enum.d.ts +6 -0
  45. package/dist/modules/outbox/enums/outbox-event-status.enum.js +11 -0
  46. package/dist/modules/outbox/enums/outbox-event-status.enum.js.map +1 -0
  47. package/dist/modules/outbox/index.d.ts +6 -0
  48. package/dist/modules/outbox/index.js +23 -0
  49. package/dist/modules/outbox/index.js.map +1 -0
  50. package/dist/modules/outbox/outbox-options.util.d.ts +3 -0
  51. package/dist/modules/outbox/outbox-options.util.js +34 -0
  52. package/dist/modules/outbox/outbox-options.util.js.map +1 -0
  53. package/dist/modules/outbox/outbox.constants.d.ts +2 -0
  54. package/dist/modules/outbox/outbox.constants.js +6 -0
  55. package/dist/modules/outbox/outbox.constants.js.map +1 -0
  56. package/dist/modules/outbox/outbox.module.d.ts +6 -0
  57. package/dist/modules/outbox/outbox.module.js +74 -0
  58. package/dist/modules/outbox/outbox.module.js.map +1 -0
  59. package/dist/modules/outbox/services/base-worker.d.ts +14 -0
  60. package/dist/modules/outbox/services/base-worker.js +91 -0
  61. package/dist/modules/outbox/services/base-worker.js.map +1 -0
  62. package/dist/modules/outbox/services/index.d.ts +3 -0
  63. package/dist/modules/outbox/services/index.js +20 -0
  64. package/dist/modules/outbox/services/index.js.map +1 -0
  65. package/dist/modules/outbox/services/outbox-cleanup.worker.d.ts +9 -0
  66. package/dist/modules/outbox/services/outbox-cleanup.worker.js +59 -0
  67. package/dist/modules/outbox/services/outbox-cleanup.worker.js.map +1 -0
  68. package/dist/modules/outbox/services/outbox.service.d.ts +22 -0
  69. package/dist/modules/outbox/services/outbox.service.js +235 -0
  70. package/dist/modules/outbox/services/outbox.service.js.map +1 -0
  71. package/dist/modules/outbox/types/index.d.ts +1 -0
  72. package/dist/modules/outbox/types/index.js +18 -0
  73. package/dist/modules/outbox/types/index.js.map +1 -0
  74. package/dist/modules/outbox/types/outbox-module-options.interface.d.ts +35 -0
  75. package/dist/modules/outbox/types/outbox-module-options.interface.js +3 -0
  76. package/dist/modules/outbox/types/outbox-module-options.interface.js.map +1 -0
  77. package/dist/typeorm/config/postgres-typeorm-options.factory.d.ts +12 -2
  78. package/dist/typeorm/config/postgres-typeorm-options.factory.js +37 -2
  79. package/dist/typeorm/config/postgres-typeorm-options.factory.js.map +1 -1
  80. package/dist/zod/filters/index.d.ts +3 -0
  81. package/dist/zod/filters/index.js +20 -0
  82. package/dist/zod/filters/index.js.map +1 -0
  83. package/dist/zod/filters/zod-express-exception.filter.d.ts +8 -0
  84. package/dist/zod/filters/zod-express-exception.filter.js +35 -0
  85. package/dist/zod/filters/zod-express-exception.filter.js.map +1 -0
  86. package/dist/zod/filters/zod-fastify-exception.filter.d.ts +8 -0
  87. package/dist/zod/filters/zod-fastify-exception.filter.js +35 -0
  88. package/dist/zod/filters/zod-fastify-exception.filter.js.map +1 -0
  89. package/dist/zod/filters/zod-validation-error-response.d.ts +6 -0
  90. package/dist/zod/filters/zod-validation-error-response.js +24 -0
  91. package/dist/zod/filters/zod-validation-error-response.js.map +1 -0
  92. package/dist/zod/index.d.ts +1 -0
  93. package/dist/zod/index.js +18 -0
  94. package/dist/zod/index.js.map +1 -0
  95. package/package.json +63 -3
  96. package/dist/common/filters/index.d.ts +0 -1
  97. package/dist/common/filters/index.js.map +0 -1
package/README.md CHANGED
@@ -31,13 +31,105 @@ This package uses subpath exports for most features.
31
31
  | `@russ-b/nestjs-common-tools` | root exports such as `services` |
32
32
  | `@russ-b/nestjs-common-tools/class-transformer` | reusable `class-transformer` decorators and helpers |
33
33
  | `@russ-b/nestjs-common-tools/modules` | NestJS modules such as `S3Module` |
34
+ | `@russ-b/nestjs-common-tools/modules/outbox` | PostgreSQL TypeORM outbox module |
34
35
  | `@russ-b/nestjs-common-tools/validators` | validation decorators and constraints |
35
36
  | `@russ-b/nestjs-common-tools/typeorm` | TypeORM filters, helpers, transformers, and types |
36
37
  | `@russ-b/nestjs-common-tools/logger` | logger builder and logger-related interfaces/types |
38
+ | `@russ-b/nestjs-common-tools/logger/pino` | config-first `nestjs-pino` module options helper |
39
+ | `@russ-b/nestjs-common-tools/errors` | API error response helpers and validation exception factories |
40
+ | `@russ-b/nestjs-common-tools/zod` | Zod exception filters for NestJS HTTP apps |
37
41
  | `@russ-b/nestjs-common-tools/pagination` | pagination DTOs, response builders, and errors |
38
42
  | `@russ-b/nestjs-common-tools/common/util` | generic utility helpers |
39
43
  | `@russ-b/nestjs-common-tools/common/filters` | shared filter exports |
40
44
 
45
+ ## Pino logger
46
+
47
+ Import Pino helpers from `@russ-b/nestjs-common-tools/logger/pino`.
48
+
49
+ Install the required peer dependencies in applications that use this entrypoint:
50
+
51
+ ```bash
52
+ npm install nestjs-pino pino pino-http
53
+ ```
54
+
55
+ Install `pino-pretty` as well if you enable pretty logs:
56
+
57
+ ```bash
58
+ npm install pino-pretty
59
+ ```
60
+
61
+ `createPinoLoggerModuleOptions` does not read from `process.env`. Pass application config explicitly, for example from `ConfigService` or your own validated config object.
62
+
63
+ ```typescript
64
+ import { LoggerModule } from 'nestjs-pino';
65
+ import { createPinoLoggerModuleOptions } from '@russ-b/nestjs-common-tools/logger/pino';
66
+
67
+ @Module({
68
+ imports: [
69
+ LoggerModule.forRoot(
70
+ createPinoLoggerModuleOptions({
71
+ appName: 'billing-api',
72
+ level: 'info',
73
+ pretty: false,
74
+ logHttpRequests: true,
75
+ version: '1.2.3',
76
+ environment: 'production',
77
+ }),
78
+ ),
79
+ ],
80
+ })
81
+ export class AppModule {}
82
+ ```
83
+
84
+ ## Error Responses
85
+
86
+ Import error helpers from `@russ-b/nestjs-common-tools/errors`.
87
+
88
+ Use `classValidatorExceptionFactory` with Nest's `ValidationPipe` to return the shared API error response shape for `class-validator` errors:
89
+
90
+ ```typescript
91
+ import { ValidationPipe } from '@nestjs/common';
92
+ import { classValidatorExceptionFactory } from '@russ-b/nestjs-common-tools/errors';
93
+
94
+ app.useGlobalPipes(
95
+ new ValidationPipe({
96
+ exceptionFactory: classValidatorExceptionFactory,
97
+ }),
98
+ );
99
+ ```
100
+
101
+ The shared validation response uses `ApiErrorCode.VALIDATION_FAILED`; root-level errors use `'_root'` as the field name.
102
+
103
+ ## Zod Exception Filters
104
+
105
+ Import Zod filters from `@russ-b/nestjs-common-tools/zod`.
106
+
107
+ Install `zod` in applications that use this entrypoint:
108
+
109
+ ```bash
110
+ npm install zod
111
+ ```
112
+
113
+ Use the filter that matches your NestJS HTTP adapter:
114
+
115
+ ```typescript
116
+ import { Module } from '@nestjs/common';
117
+ import { APP_FILTER } from '@nestjs/core';
118
+ import { ZodExpressExceptionFilter } from '@russ-b/nestjs-common-tools/zod';
119
+
120
+ @Module({
121
+ providers: [
122
+ {
123
+ provide: APP_FILTER,
124
+ useFactory: () => new ZodExpressExceptionFilter(),
125
+ },
126
+ ],
127
+ })
128
+ export class AppModule {}
129
+ ```
130
+
131
+ For Fastify apps, use `ZodFastifyExceptionFilter` instead. Both filters return a `400` response with a `Validation failed` message and mapped Zod issue details.
132
+
41
133
  ## Pagination
42
134
 
43
135
  Import pagination helpers from `@russ-b/nestjs-common-tools/pagination`.
@@ -470,6 +562,126 @@ export class CarPhotoService {
470
562
  }
471
563
  ```
472
564
 
565
+ ## Outbox Module
566
+
567
+ Import outbox helpers from `@russ-b/nestjs-common-tools/modules/outbox`.
568
+
569
+ `OutboxModule` is a PostgreSQL TypeORM implementation of the outbox pattern. It stores events in the database, lets workers claim pending events with `FOR UPDATE SKIP LOCKED`, tracks stale processing attempts, and retries failed handlers.
570
+
571
+ ### Register the module
572
+
573
+ ```typescript
574
+ import { Module } from '@nestjs/common';
575
+ import { TypeOrmModule } from '@nestjs/typeorm';
576
+ import { OutboxModule } from '@russ-b/nestjs-common-tools/modules/outbox';
577
+
578
+ @Module({
579
+ imports: [
580
+ TypeOrmModule.forRoot({
581
+ // your TypeORM options
582
+ }),
583
+ OutboxModule.forRoot({
584
+ operationalPolicy: {
585
+ claimBatchSize: 100,
586
+ maxRetries: 5,
587
+ staleProcessingMinutes: 5,
588
+ maxConcurrentEvents: 10,
589
+ },
590
+ }),
591
+ ],
592
+ })
593
+ export class AppModule {}
594
+ ```
595
+
596
+ If your app has multiple TypeORM data sources, pass the registered data source name:
597
+
598
+ ```typescript
599
+ OutboxModule.forRoot({
600
+ dataSource: 'primary',
601
+ });
602
+ ```
603
+
604
+ ### Create events
605
+
606
+ Create outbox events inside the same transaction as the domain change whenever possible.
607
+
608
+ ```typescript
609
+ await dataSource.transaction(async (manager) => {
610
+ await manager.save(order);
611
+
612
+ await outboxService.createEvent(
613
+ 'order.created',
614
+ { orderId: order.id },
615
+ manager,
616
+ );
617
+ });
618
+ ```
619
+
620
+ ### Process events
621
+
622
+ Extend `BaseWorker` and implement event selection plus handling.
623
+
624
+ ```typescript
625
+ import { Injectable } from '@nestjs/common';
626
+ import {
627
+ BaseWorker,
628
+ OutboxEvent,
629
+ OutboxService,
630
+ } from '@russ-b/nestjs-common-tools/modules/outbox';
631
+
632
+ @Injectable()
633
+ export class OrderCreatedWorker extends BaseWorker {
634
+ constructor(outboxService: OutboxService) {
635
+ super(outboxService);
636
+ }
637
+
638
+ getEvents(): Promise<OutboxEvent[]> {
639
+ return this.outboxService.claimPendingEvents('order.created');
640
+ }
641
+
642
+ async handle(event: OutboxEvent): Promise<void> {
643
+ // Send email, publish message, call another service, etc.
644
+ }
645
+ }
646
+ ```
647
+
648
+ Outbox delivery is at-least-once. Handlers must be idempotent, especially when they call external services or publish messages.
649
+
650
+ ### Cleanup processed events
651
+
652
+ `OutboxCleanupWorker` deletes processed events older than the configured `processedEventRetentionHours`. The library does not schedule it by itself, so wire it to your app scheduler. If retention should come from environment or config, pass it through `OutboxModule.forRootAsync`.
653
+
654
+ ```typescript
655
+ import { Injectable } from '@nestjs/common';
656
+ import { Cron } from '@nestjs/schedule';
657
+ import { OutboxCleanupWorker } from '@russ-b/nestjs-common-tools/modules/outbox';
658
+
659
+ @Injectable()
660
+ export class OutboxCleanupCron {
661
+ constructor(private readonly cleanupWorker: OutboxCleanupWorker) {}
662
+
663
+ @Cron('0 0 3 * * *')
664
+ cleanupProcessedEvents(): Promise<number> {
665
+ return this.cleanupWorker.cleanupProcessedEvents();
666
+ }
667
+ }
668
+ ```
669
+
670
+ ### Operational policy
671
+
672
+ `operationalPolicy` supports:
673
+
674
+ | Option | Default | Description |
675
+ | ------------------------------ | --------- | -------------------------------------------------------------- |
676
+ | `claimBatchSize` | `100` | Default number of events claimed per poll |
677
+ | `maxRetries` | `5` | Failed events become `failed` when this retry count is reached |
678
+ | `staleProcessingMinutes` | `5` | Processing events older than this are reset to pending |
679
+ | `resetStaleProcessingEvents` | `true` | Enables stale processing reset in `BaseWorker` |
680
+ | `maxConcurrentEvents` | unlimited | Limits parallel handler execution inside one worker cycle |
681
+ | `processedEventRetentionHours` | `24` | Default age used by `deleteProcessed()` |
682
+
683
+ The outbox entity uses PostgreSQL-specific column types and requires a nullable `processing_started_at` column for stale processing detection.
684
+
473
685
  ## Entity Validator
474
686
 
475
687
  A custom validator for NestJS that validates if an entity exists in the database using TypeORM.
@@ -598,6 +810,32 @@ export class AppModule {}
598
810
 
599
811
  PostgreSQL support is also exposed directly as `createPostgresTypeormOptions`. Its `schema` option is applied both as TypeORM's schema and as `search_path`, and pool options are passed through the `pg` driver via TypeORM's `extra` config.
600
812
 
813
+ For managed PostgreSQL providers that require SSL with a CA certificate, you can pass atomic connection fields and `sslCa`. This avoids `pg` overwriting the explicit `ssl` object when `sslmode` or `sslrootcert` exists in the connection string.
814
+
815
+ ```typescript
816
+ TypeOrmModule.forRootAsync({
817
+ imports: [ConfigModule],
818
+ inject: [ConfigService],
819
+ useFactory: (config: ConfigService) =>
820
+ createTypeOrmOptions({
821
+ type: 'postgres',
822
+ database: {
823
+ host: config.getOrThrow<string>('DB_HOST'),
824
+ port: config.get<number>('DB_PORT'),
825
+ username: config.getOrThrow<string>('DB_USER'),
826
+ password: config.getOrThrow<string>('DB_PASSWORD'),
827
+ database: config.getOrThrow<string>('DB_NAME'),
828
+ },
829
+ sslCa: config.get<string>('DB_CA_CERT'),
830
+ sync: config.get<string>('TYPEORM_SYNC'),
831
+ logging: config.get<string>('TYPEORM_LOGGING'),
832
+ isProduction: config.get<string>('NODE_ENV') === 'production',
833
+ }),
834
+ });
835
+ ```
836
+
837
+ When `sslCa` is used together with `databaseUrl`, SSL-related query params such as `sslmode` and `sslrootcert` are removed from the URL before TypeORM passes the config to `pg`.
838
+
601
839
  For MySQL, use `type: 'mysql'` or call `createMysqlTypeormOptions` directly:
602
840
 
603
841
  ```typescript
@@ -650,6 +888,27 @@ export class AppModule {}
650
888
 
651
889
  By default, the filter already maps common TypeORM database errors such as unique constraint violations, foreign key violations, and invalid input format to NestJS HTTP exceptions. Custom constraint handlers let you keep those responses specific to your business rules.
652
890
 
891
+ ### Entity Not Found Filter
892
+
893
+ TypeORM throws `EntityNotFoundError` when methods such as `findOneOrFail` cannot find a requested entity. `EntityNotFoundFilter` maps that error to NestJS `NotFoundException` for HTTP requests and rethrows the original error for non-HTTP contexts.
894
+
895
+ ```typescript
896
+ // app.module.ts
897
+ import { Module } from '@nestjs/common';
898
+ import { APP_FILTER } from '@nestjs/core';
899
+ import { EntityNotFoundFilter } from '@russ-b/nestjs-common-tools/typeorm';
900
+
901
+ @Module({
902
+ providers: [
903
+ {
904
+ provide: APP_FILTER,
905
+ useClass: EntityNotFoundFilter,
906
+ },
907
+ ],
908
+ })
909
+ export class AppModule {}
910
+ ```
911
+
653
912
  ## isTypeOrmQueryFailedError
654
913
 
655
914
  Sometimes a global filter is not enough and you want to react differently to one exact database error inside a service. `isTypeOrmQueryFailedError` is useful for that kind of targeted branching without scattering manual `instanceof QueryFailedError` checks and driver casts around the codebase.
@@ -0,0 +1 @@
1
+ export declare function getErrorMessage(error: unknown): string;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getErrorMessage = getErrorMessage;
4
+ function getErrorMessage(error) {
5
+ if (error instanceof Error) {
6
+ return error.message;
7
+ }
8
+ return String(error);
9
+ }
10
+ //# sourceMappingURL=error.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.util.js","sourceRoot":"","sources":["../../../src/common/util/error.util.ts"],"names":[],"mappings":";;AAAA,0CAMC;AAND,SAAgB,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
@@ -1,3 +1,4 @@
1
1
  export * from './collection.util';
2
2
  export * from './sleep.util';
3
3
  export * from './mask-string.util';
4
+ export * from './error.util';
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./collection.util"), exports);
18
18
  __exportStar(require("./sleep.util"), exports);
19
19
  __exportStar(require("./mask-string.util"), exports);
20
+ __exportStar(require("./error.util"), exports);
20
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/common/util/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,+CAA6B;AAC7B,qDAAmC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/common/util/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,+CAA6B;AAC7B,qDAAmC;AACnC,+CAA6B"}
@@ -0,0 +1,11 @@
1
+ export declare enum ApiErrorCode {
2
+ BAD_REQUEST = "BAD_REQUEST",
3
+ CONFLICT = "CONFLICT",
4
+ FORBIDDEN = "FORBIDDEN",
5
+ INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR",
6
+ NOT_FOUND = "NOT_FOUND",
7
+ TOO_MANY_REQUESTS = "TOO_MANY_REQUESTS",
8
+ UNAUTHORIZED = "UNAUTHORIZED",
9
+ UNPROCESSABLE_ENTITY = "UNPROCESSABLE_ENTITY",
10
+ VALIDATION_FAILED = "VALIDATION_FAILED"
11
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiErrorCode = void 0;
4
+ var ApiErrorCode;
5
+ (function (ApiErrorCode) {
6
+ ApiErrorCode["BAD_REQUEST"] = "BAD_REQUEST";
7
+ ApiErrorCode["CONFLICT"] = "CONFLICT";
8
+ ApiErrorCode["FORBIDDEN"] = "FORBIDDEN";
9
+ ApiErrorCode["INTERNAL_SERVER_ERROR"] = "INTERNAL_SERVER_ERROR";
10
+ ApiErrorCode["NOT_FOUND"] = "NOT_FOUND";
11
+ ApiErrorCode["TOO_MANY_REQUESTS"] = "TOO_MANY_REQUESTS";
12
+ ApiErrorCode["UNAUTHORIZED"] = "UNAUTHORIZED";
13
+ ApiErrorCode["UNPROCESSABLE_ENTITY"] = "UNPROCESSABLE_ENTITY";
14
+ ApiErrorCode["VALIDATION_FAILED"] = "VALIDATION_FAILED";
15
+ })(ApiErrorCode || (exports.ApiErrorCode = ApiErrorCode = {}));
16
+ //# sourceMappingURL=api-error-code.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-error-code.enum.js","sourceRoot":"","sources":["../../src/errors/api-error-code.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,YAUX;AAVD,WAAY,YAAY;IACtB,2CAA2B,CAAA;IAC3B,qCAAqB,CAAA;IACrB,uCAAuB,CAAA;IACvB,+DAA+C,CAAA;IAC/C,uCAAuB,CAAA;IACvB,uDAAuC,CAAA;IACvC,6CAA6B,CAAA;IAC7B,6DAA6C,CAAA;IAC7C,uDAAuC,CAAA;AACzC,CAAC,EAVW,YAAY,4BAAZ,YAAY,QAUvB"}
@@ -0,0 +1,11 @@
1
+ export type ApiErrorItem = {
2
+ field: string;
3
+ message: string;
4
+ };
5
+ export type ApiErrorResponse = {
6
+ statusCode: number;
7
+ error: string;
8
+ message: string;
9
+ code: string;
10
+ errors?: ApiErrorItem[];
11
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=api-error-response.type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-error-response.type.js","sourceRoot":"","sources":["../../src/errors/api-error-response.type.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ export declare const API_ERROR_ROOT_FIELD = "_root";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.API_ERROR_ROOT_FIELD = void 0;
4
+ exports.API_ERROR_ROOT_FIELD = '_root';
5
+ //# sourceMappingURL=api-error-root-field.constant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-error-root-field.constant.js","sourceRoot":"","sources":["../../src/errors/api-error-root-field.constant.ts"],"names":[],"mappings":";;;AAAa,QAAA,oBAAoB,GAAG,OAAO,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+ import type { ValidationError } from '@nestjs/common';
3
+ export interface ClassValidatorExceptionFactoryOptions {
4
+ message?: string;
5
+ }
6
+ export declare function classValidatorExceptionFactory(errors: ValidationError[], options?: ClassValidatorExceptionFactoryOptions): BadRequestException;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classValidatorExceptionFactory = classValidatorExceptionFactory;
4
+ const common_1 = require("@nestjs/common");
5
+ const api_error_root_field_constant_1 = require("./api-error-root-field.constant");
6
+ const api_error_code_enum_1 = require("./api-error-code.enum");
7
+ const error_response_factory_1 = require("./error-response.factory");
8
+ const DEFAULT_VALIDATION_ERROR_MESSAGE = 'Validation failed';
9
+ function classValidatorExceptionFactory(errors, options = {}) {
10
+ return new common_1.BadRequestException((0, error_response_factory_1.createApiErrorResponse)({
11
+ statusCode: common_1.HttpStatus.BAD_REQUEST,
12
+ message: options.message ?? DEFAULT_VALIDATION_ERROR_MESSAGE,
13
+ code: api_error_code_enum_1.ApiErrorCode.VALIDATION_FAILED,
14
+ errors: flattenValidationErrors(errors),
15
+ }));
16
+ }
17
+ function flattenValidationErrors(errors, parentPath) {
18
+ const items = [];
19
+ for (const error of errors) {
20
+ const field = formatValidationErrorPath(error.property, parentPath);
21
+ if (error.constraints) {
22
+ for (const message of Object.values(error.constraints)) {
23
+ items.push({ field, message });
24
+ }
25
+ }
26
+ if (error.children?.length) {
27
+ items.push(...flattenValidationErrors(error.children, field));
28
+ }
29
+ }
30
+ return items;
31
+ }
32
+ function formatValidationErrorPath(property, parentPath) {
33
+ if (!property) {
34
+ return parentPath ?? api_error_root_field_constant_1.API_ERROR_ROOT_FIELD;
35
+ }
36
+ return parentPath ? `${parentPath}.${property}` : property;
37
+ }
38
+ //# sourceMappingURL=class-validator-exception.factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class-validator-exception.factory.js","sourceRoot":"","sources":["../../src/errors/class-validator-exception.factory.ts"],"names":[],"mappings":";;AAaA,wEAYC;AAzBD,2CAAiE;AAEjE,mFAAuE;AACvE,+DAAqD;AAErD,qEAAkE;AAMlE,MAAM,gCAAgC,GAAG,mBAAmB,CAAC;AAE7D,SAAgB,8BAA8B,CAC5C,MAAyB,EACzB,UAAiD,EAAE;IAEnD,OAAO,IAAI,4BAAmB,CAC5B,IAAA,+CAAsB,EAAC;QACrB,UAAU,EAAE,mBAAU,CAAC,WAAW;QAClC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,gCAAgC;QAC5D,IAAI,EAAE,kCAAY,CAAC,iBAAiB;QACpC,MAAM,EAAE,uBAAuB,CAAC,MAAM,CAAC;KACxC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAC9B,MAAyB,EACzB,UAAmB;IAEnB,MAAM,KAAK,GAAmB,EAAE,CAAC;IAEjC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,yBAAyB,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAEpE,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;gBACvD,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,yBAAyB,CAChC,QAA4B,EAC5B,UAAmB;IAEnB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,UAAU,IAAI,oDAAoB,CAAC;IAC5C,CAAC;IAED,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { ApiErrorCode } from './api-error-code.enum';
2
+ import type { ApiErrorItem, ApiErrorResponse } from './api-error-response.type';
3
+ export type CreateApiErrorResponseParams = {
4
+ statusCode: number;
5
+ message?: string;
6
+ code?: ApiErrorCode | string;
7
+ error?: string;
8
+ errors?: ApiErrorItem[];
9
+ };
10
+ export declare function createApiErrorResponse(params: CreateApiErrorResponseParams): ApiErrorResponse;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createApiErrorResponse = createApiErrorResponse;
4
+ const common_1 = require("@nestjs/common");
5
+ const api_error_code_enum_1 = require("./api-error-code.enum");
6
+ const HTTP_ERROR_LABELS = {
7
+ [common_1.HttpStatus.BAD_REQUEST]: 'Bad Request',
8
+ [common_1.HttpStatus.UNAUTHORIZED]: 'Unauthorized',
9
+ [common_1.HttpStatus.FORBIDDEN]: 'Forbidden',
10
+ [common_1.HttpStatus.NOT_FOUND]: 'Not Found',
11
+ [common_1.HttpStatus.CONFLICT]: 'Conflict',
12
+ [common_1.HttpStatus.UNPROCESSABLE_ENTITY]: 'Unprocessable Entity',
13
+ [common_1.HttpStatus.TOO_MANY_REQUESTS]: 'Too Many Requests',
14
+ [common_1.HttpStatus.INTERNAL_SERVER_ERROR]: 'Internal Server Error',
15
+ };
16
+ const DEFAULT_ERROR_CODES = {
17
+ [common_1.HttpStatus.BAD_REQUEST]: api_error_code_enum_1.ApiErrorCode.BAD_REQUEST,
18
+ [common_1.HttpStatus.UNAUTHORIZED]: api_error_code_enum_1.ApiErrorCode.UNAUTHORIZED,
19
+ [common_1.HttpStatus.FORBIDDEN]: api_error_code_enum_1.ApiErrorCode.FORBIDDEN,
20
+ [common_1.HttpStatus.NOT_FOUND]: api_error_code_enum_1.ApiErrorCode.NOT_FOUND,
21
+ [common_1.HttpStatus.CONFLICT]: api_error_code_enum_1.ApiErrorCode.CONFLICT,
22
+ [common_1.HttpStatus.UNPROCESSABLE_ENTITY]: api_error_code_enum_1.ApiErrorCode.UNPROCESSABLE_ENTITY,
23
+ [common_1.HttpStatus.TOO_MANY_REQUESTS]: api_error_code_enum_1.ApiErrorCode.TOO_MANY_REQUESTS,
24
+ [common_1.HttpStatus.INTERNAL_SERVER_ERROR]: api_error_code_enum_1.ApiErrorCode.INTERNAL_SERVER_ERROR,
25
+ };
26
+ function createApiErrorResponse(params) {
27
+ const error = params.error ?? HTTP_ERROR_LABELS[params.statusCode] ?? 'Error';
28
+ const message = params.message ?? error;
29
+ const code = params.code ?? DEFAULT_ERROR_CODES[params.statusCode] ?? 'ERROR';
30
+ if (!params.errors?.length) {
31
+ return {
32
+ statusCode: params.statusCode,
33
+ error,
34
+ message,
35
+ code,
36
+ };
37
+ }
38
+ return {
39
+ statusCode: params.statusCode,
40
+ error,
41
+ message,
42
+ code,
43
+ errors: params.errors,
44
+ };
45
+ }
46
+ //# sourceMappingURL=error-response.factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-response.factory.js","sourceRoot":"","sources":["../../src/errors/error-response.factory.ts"],"names":[],"mappings":";;AAkCA,wDAuBC;AAzDD,2CAA4C;AAC5C,+DAAqD;AAGrD,MAAM,iBAAiB,GAA2B;IAChD,CAAC,mBAAU,CAAC,WAAW,CAAC,EAAE,aAAa;IACvC,CAAC,mBAAU,CAAC,YAAY,CAAC,EAAE,cAAc;IACzC,CAAC,mBAAU,CAAC,SAAS,CAAC,EAAE,WAAW;IACnC,CAAC,mBAAU,CAAC,SAAS,CAAC,EAAE,WAAW;IACnC,CAAC,mBAAU,CAAC,QAAQ,CAAC,EAAE,UAAU;IACjC,CAAC,mBAAU,CAAC,oBAAoB,CAAC,EAAE,sBAAsB;IACzD,CAAC,mBAAU,CAAC,iBAAiB,CAAC,EAAE,mBAAmB;IACnD,CAAC,mBAAU,CAAC,qBAAqB,CAAC,EAAE,uBAAuB;CAC5D,CAAC;AAEF,MAAM,mBAAmB,GAAiC;IACxD,CAAC,mBAAU,CAAC,WAAW,CAAC,EAAE,kCAAY,CAAC,WAAW;IAClD,CAAC,mBAAU,CAAC,YAAY,CAAC,EAAE,kCAAY,CAAC,YAAY;IACpD,CAAC,mBAAU,CAAC,SAAS,CAAC,EAAE,kCAAY,CAAC,SAAS;IAC9C,CAAC,mBAAU,CAAC,SAAS,CAAC,EAAE,kCAAY,CAAC,SAAS;IAC9C,CAAC,mBAAU,CAAC,QAAQ,CAAC,EAAE,kCAAY,CAAC,QAAQ;IAC5C,CAAC,mBAAU,CAAC,oBAAoB,CAAC,EAAE,kCAAY,CAAC,oBAAoB;IACpE,CAAC,mBAAU,CAAC,iBAAiB,CAAC,EAAE,kCAAY,CAAC,iBAAiB;IAC9D,CAAC,mBAAU,CAAC,qBAAqB,CAAC,EAAE,kCAAY,CAAC,qBAAqB;CACvE,CAAC;AAUF,SAAgB,sBAAsB,CACpC,MAAoC;IAEpC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC;IAC9E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC;IAE9E,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC3B,OAAO;YACL,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,KAAK;YACL,OAAO;YACP,IAAI;SACL,CAAC;IACJ,CAAC;IAED,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,KAAK;QACL,OAAO;QACP,IAAI;QACJ,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './api-error-code.enum';
2
+ export * from './api-error-response.type';
3
+ export * from './api-error-root-field.constant';
4
+ export * from './class-validator-exception.factory';
5
+ export * from './error-response.factory';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./api-error-code.enum"), exports);
18
+ __exportStar(require("./api-error-response.type"), exports);
19
+ __exportStar(require("./api-error-root-field.constant"), exports);
20
+ __exportStar(require("./class-validator-exception.factory"), exports);
21
+ __exportStar(require("./error-response.factory"), exports);
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wDAAsC;AACtC,4DAA0C;AAC1C,kEAAgD;AAChD,sEAAoD;AACpD,2DAAyC"}
@@ -0,0 +1 @@
1
+ export * from '../pino-logger';
@@ -14,5 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("../../typeorm/filters"), exports);
17
+ __exportStar(require("../pino-logger"), exports);
18
18
  //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/logger/pino/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B"}
@@ -0,0 +1,13 @@
1
+ import { type Params } from 'nestjs-pino';
2
+ export type PinoLogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent';
3
+ export interface PinoLoggerConfig {
4
+ appName: string;
5
+ level?: PinoLogLevel;
6
+ pretty?: boolean;
7
+ logHttpRequests?: boolean;
8
+ version?: string;
9
+ environment?: string;
10
+ redactPaths?: string[];
11
+ base?: Record<string, unknown>;
12
+ }
13
+ export declare function createPinoLoggerModuleOptions(config: PinoLoggerConfig): Params;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createPinoLoggerModuleOptions = createPinoLoggerModuleOptions;
7
+ const node_os_1 = require("node:os");
8
+ const nestjs_pino_1 = require("nestjs-pino");
9
+ const pino_1 = __importDefault(require("pino"));
10
+ const DEFAULT_REDACT_PATHS = [
11
+ 'req.headers.authorization',
12
+ 'req.headers.cookie',
13
+ 'res.headers["set-cookie"]',
14
+ ];
15
+ function createPinoLoggerModuleOptions(config) {
16
+ const pinoHttp = {
17
+ ...nestjs_pino_1.nativeLoggerOptions,
18
+ level: config.level ?? 'debug',
19
+ base: {
20
+ pid: process.pid,
21
+ hostname: (0, node_os_1.hostname)(),
22
+ app: config.appName,
23
+ version: config.version ?? 'unknown',
24
+ env: config.environment ?? 'development',
25
+ ...config.base,
26
+ },
27
+ redact: {
28
+ paths: config.redactPaths ?? DEFAULT_REDACT_PATHS,
29
+ censor: '[REDACTED]',
30
+ },
31
+ autoLogging: config.logHttpRequests ?? false,
32
+ };
33
+ if (config.pretty) {
34
+ delete pinoHttp.formatters;
35
+ return {
36
+ pinoHttp: {
37
+ ...pinoHttp,
38
+ transport: {
39
+ target: 'pino-pretty',
40
+ options: {
41
+ colorize: true,
42
+ ignore: 'pid,hostname,app,version,env',
43
+ messageFormat: '[{context}] {message}',
44
+ messageKey: 'message',
45
+ singleLine: true,
46
+ timestampKey: 'timestamp',
47
+ translateTime: 'SYS:HH:MM:ss.l',
48
+ },
49
+ },
50
+ },
51
+ };
52
+ }
53
+ return {
54
+ pinoHttp: {
55
+ ...pinoHttp,
56
+ stream: pino_1.default.destination({
57
+ sync: false,
58
+ }),
59
+ },
60
+ };
61
+ }
62
+ //# sourceMappingURL=pino-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pino-logger.js","sourceRoot":"","sources":["../../src/logger/pino-logger.ts"],"names":[],"mappings":";;;;;AA8BA,sEAmDC;AAjFD,qCAAmC;AACnC,6CAA+D;AAC/D,gDAAwB;AAsBxB,MAAM,oBAAoB,GAAG;IAC3B,2BAA2B;IAC3B,oBAAoB;IACpB,2BAA2B;CAC5B,CAAC;AAEF,SAAgB,6BAA6B,CAC3C,MAAwB;IAExB,MAAM,QAAQ,GAAG;QACf,GAAG,iCAAmB;QACtB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,OAAO;QAC9B,IAAI,EAAE;YACJ,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,QAAQ,EAAE,IAAA,kBAAQ,GAAE;YACpB,GAAG,EAAE,MAAM,CAAC,OAAO;YACnB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,SAAS;YACpC,GAAG,EAAE,MAAM,CAAC,WAAW,IAAI,aAAa;YACxC,GAAG,MAAM,CAAC,IAAI;SACf;QACD,MAAM,EAAE;YACN,KAAK,EAAE,MAAM,CAAC,WAAW,IAAI,oBAAoB;YACjD,MAAM,EAAE,YAAY;SACrB;QACD,WAAW,EAAE,MAAM,CAAC,eAAe,IAAI,KAAK;KAC7C,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,QAAQ,CAAC,UAAU,CAAC;QAE3B,OAAO;YACL,QAAQ,EAAE;gBACR,GAAG,QAAQ;gBACX,SAAS,EAAE;oBACT,MAAM,EAAE,aAAa;oBACrB,OAAO,EAAE;wBACP,QAAQ,EAAE,IAAI;wBACd,MAAM,EAAE,8BAA8B;wBACtC,aAAa,EAAE,uBAAuB;wBACtC,UAAU,EAAE,SAAS;wBACrB,UAAU,EAAE,IAAI;wBAChB,YAAY,EAAE,WAAW;wBACzB,aAAa,EAAE,gBAAgB;qBAChC;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE;YACR,GAAG,QAAQ;YACX,MAAM,EAAE,cAAI,CAAC,WAAW,CAAC;gBACvB,IAAI,EAAE,KAAK;aACZ,CAAC;SACH;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1,2 @@
1
1
  export * from './s3';
2
+ export * from './outbox';
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./s3"), exports);
18
+ __exportStar(require("./outbox"), exports);
18
19
  //# sourceMappingURL=index.js.map