@invariant--labs/foundation 1.1.2

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 (214) hide show
  1. package/.pnp.cjs +22192 -0
  2. package/.pnp.loader.mjs +2126 -0
  3. package/.yarnrc.yml +1 -0
  4. package/CHANGELOG.md +527 -0
  5. package/README.md +3 -0
  6. package/eslint.config.mjs +52 -0
  7. package/invariant.json +22 -0
  8. package/jest/jest.config.base.ts +30 -0
  9. package/jest/jest.spec.base.ts +7 -0
  10. package/jest.config.js +49 -0
  11. package/package.json +99 -0
  12. package/src/core/application/index.ts +1 -0
  13. package/src/core/application/persistence/index.ts +3 -0
  14. package/src/core/application/persistence/query-builder.ts +2 -0
  15. package/src/core/application/persistence/read-projection.ts +2 -0
  16. package/src/core/application/persistence/repository.ts +23 -0
  17. package/src/core/domain/entities/aggregate-root.ts +34 -0
  18. package/src/core/domain/entities/entity.spec.ts +43 -0
  19. package/src/core/domain/entities/entity.ts +29 -0
  20. package/src/core/domain/entities/identifier.spec.ts +34 -0
  21. package/src/core/domain/entities/identifier.ts +16 -0
  22. package/src/core/domain/entities/index.ts +5 -0
  23. package/src/core/domain/entities/projection.ts +7 -0
  24. package/src/core/domain/entities/unique-entity-id.ts +9 -0
  25. package/src/core/domain/events/domain-event.ts +7 -0
  26. package/src/core/domain/events/index.ts +1 -0
  27. package/src/core/domain/index.ts +3 -0
  28. package/src/core/domain/value-objects/index.ts +2 -0
  29. package/src/core/domain/value-objects/range.ts +4 -0
  30. package/src/core/domain/value-objects/value-object.spec.ts +45 -0
  31. package/src/core/domain/value-objects/value-object.ts +17 -0
  32. package/src/core/errors/command-failure.error.spec.ts +30 -0
  33. package/src/core/errors/command-failure.error.ts +9 -0
  34. package/src/core/errors/command-filter.error.ts +3 -0
  35. package/src/core/errors/detailed.error.ts +25 -0
  36. package/src/core/errors/domain.error.spec.ts +27 -0
  37. package/src/core/errors/domain.error.ts +9 -0
  38. package/src/core/errors/entity-not-found.error.spec.ts +32 -0
  39. package/src/core/errors/entity-not-found.error.ts +9 -0
  40. package/src/core/errors/fake-implementation.error.spec.ts +27 -0
  41. package/src/core/errors/fake-implementation.error.ts +15 -0
  42. package/src/core/errors/index.ts +8 -0
  43. package/src/core/errors/query-failure.error.ts +8 -0
  44. package/src/core/errors/too-many-results.error.ts +3 -0
  45. package/src/core/index.ts +4 -0
  46. package/src/core/infrastructure/config/config.provider.ts +78 -0
  47. package/src/core/infrastructure/config/config.service.ts +25 -0
  48. package/src/core/infrastructure/config/index.ts +2 -0
  49. package/src/core/infrastructure/messaging/fake/fake-messaging.service.ts +17 -0
  50. package/src/core/infrastructure/messaging/fake/fake-queue-manager.service.ts +9 -0
  51. package/src/core/infrastructure/messaging/fake/fake-queue-messaging.service.ts +9 -0
  52. package/src/core/infrastructure/messaging/fake/index.ts +3 -0
  53. package/src/core/infrastructure/messaging/index.ts +6 -0
  54. package/src/core/infrastructure/messaging/messaging.service.ts +3 -0
  55. package/src/core/infrastructure/messaging/queue-manager.service.ts +6 -0
  56. package/src/core/infrastructure/messaging/queue-messaging.service.ts +3 -0
  57. package/src/core/infrastructure/messaging/rabbitmq/index.ts +2 -0
  58. package/src/core/infrastructure/messaging/rabbitmq/rabbit-messaging.service.ts +11 -0
  59. package/src/core/infrastructure/messaging/rabbitmq/rabbit-queue-messaging.service.ts +11 -0
  60. package/src/core/infrastructure/messaging/types.ts +28 -0
  61. package/src/core/infrastructure/persistence/errors/index.ts +2 -0
  62. package/src/core/infrastructure/persistence/errors/model-to-entity-conversion.error.ts +9 -0
  63. package/src/core/infrastructure/persistence/errors/persistence.error.ts +8 -0
  64. package/src/core/infrastructure/persistence/in-memory/fake.repository.ts +79 -0
  65. package/src/core/infrastructure/persistence/in-memory/in-memory.query-builder.ts +4 -0
  66. package/src/core/infrastructure/persistence/in-memory/in-memory.repository.spec.ts +50 -0
  67. package/src/core/infrastructure/persistence/in-memory/in-memory.repository.ts +81 -0
  68. package/src/core/infrastructure/persistence/in-memory/index.ts +3 -0
  69. package/src/core/infrastructure/persistence/index.ts +4 -0
  70. package/src/core/infrastructure/persistence/read-side/in-memory.query-builder.ts +4 -0
  71. package/src/core/infrastructure/persistence/read-side/index.ts +1 -0
  72. package/src/core/infrastructure/persistence/read-side/knex/index.ts +2 -0
  73. package/src/core/infrastructure/persistence/read-side/knex/knex-types.definition.ts +13 -0
  74. package/src/core/infrastructure/persistence/read-side/knex/knex.query-builder.ts +70 -0
  75. package/src/core/infrastructure/persistence/read-side/knex-query-builder.ts +70 -0
  76. package/src/core/infrastructure/persistence/read-side/knex-types.definition.ts +13 -0
  77. package/src/core/infrastructure/persistence/write-side/aggregate-typeorm-repository.ts +87 -0
  78. package/src/core/infrastructure/persistence/write-side/entity-typeorm-repository.ts +64 -0
  79. package/src/core/infrastructure/persistence/write-side/in-memory.repository.ts +82 -0
  80. package/src/core/infrastructure/persistence/write-side/index.ts +1 -0
  81. package/src/core/infrastructure/persistence/write-side/model-attributes.ts +4 -0
  82. package/src/core/infrastructure/persistence/write-side/orm-embedded-mapper.ts +11 -0
  83. package/src/core/infrastructure/persistence/write-side/orm-mapper.ts +11 -0
  84. package/src/core/infrastructure/persistence/write-side/typeorm/aggregate-typeorm.repository.ts +87 -0
  85. package/src/core/infrastructure/persistence/write-side/typeorm/entity-typeorm.repository.ts +64 -0
  86. package/src/core/infrastructure/persistence/write-side/typeorm/index.ts +5 -0
  87. package/src/core/infrastructure/persistence/write-side/typeorm/model-attributes.ts +4 -0
  88. package/src/core/infrastructure/persistence/write-side/typeorm/orm-embedded.mapper.ts +11 -0
  89. package/src/core/infrastructure/persistence/write-side/typeorm/orm.mapper.ts +11 -0
  90. package/src/core/types/architecture-layer.ts +52 -0
  91. package/src/core/types/array-element.ts +5 -0
  92. package/src/core/types/index.ts +2 -0
  93. package/src/index.ts +30 -0
  94. package/src/modules/config/config.module.ts +9 -0
  95. package/src/modules/config/index.ts +2 -0
  96. package/src/modules/graphql/index.ts +1 -0
  97. package/src/modules/graphql/paginated-response.object-type.ts +23 -0
  98. package/src/modules/healthcheck/healthcheck-out.dto.ts +43 -0
  99. package/src/modules/healthcheck/index.ts +1 -0
  100. package/src/modules/knex/index.ts +3 -0
  101. package/src/modules/knex/knex-core.module.ts +30 -0
  102. package/src/modules/knex/knex.decorator.ts +5 -0
  103. package/src/modules/knex/knex.interface.ts +12 -0
  104. package/src/modules/knex/knex.module.ts +14 -0
  105. package/src/modules/knex/knex.token.ts +2 -0
  106. package/src/modules/logger/app-logger.ts +28 -0
  107. package/src/modules/logger/index.ts +5 -0
  108. package/src/modules/logger/log.ts +26 -0
  109. package/src/modules/logger/logger.module.ts +47 -0
  110. package/src/modules/logger/logger.service.ts +131 -0
  111. package/src/modules/logger/transports/console-color.transport.ts +41 -0
  112. package/src/modules/logger/transports/console-json.transport.ts +23 -0
  113. package/src/modules/logger/transports/console.transport.ts +10 -0
  114. package/src/modules/logger/transports/fake.transport.ts +18 -0
  115. package/src/modules/logger/transports/index.ts +5 -0
  116. package/src/modules/logger/transports/logger-transport.ts +22 -0
  117. package/src/modules/messaging/index.ts +2 -0
  118. package/src/modules/messaging/messaging.module.ts +92 -0
  119. package/src/modules/queue/default-queue-name.resolver.ts +5 -0
  120. package/src/modules/queue/index.ts +3 -0
  121. package/src/modules/queue/queue.module.ts +73 -0
  122. package/src/modules/queue/rabbit-queue-manager.service.ts +67 -0
  123. package/src/nestjs/errors/handlers/error-handler.ts +38 -0
  124. package/src/nestjs/errors/handlers/error-handler.type.ts +23 -0
  125. package/src/nestjs/errors/handlers/generic-error-handler.ts +59 -0
  126. package/src/nestjs/errors/handlers/handle-errors.decorator.ts +43 -0
  127. package/src/nestjs/errors/handlers/index.ts +5 -0
  128. package/src/nestjs/errors/handlers/validation-error-handler.ts +46 -0
  129. package/src/nestjs/errors/index.ts +2 -0
  130. package/src/nestjs/errors/parsers/axios-metadata.parser.ts +21 -0
  131. package/src/nestjs/errors/parsers/error-metadata.definition.ts +1 -0
  132. package/src/nestjs/errors/parsers/index.ts +5 -0
  133. package/src/nestjs/errors/parsers/knex-metadata.parser.ts +18 -0
  134. package/src/nestjs/errors/parsers/typeorm-metadata.parser.ts +19 -0
  135. package/src/nestjs/errors/parsers/workos-metadata.parser.ts +17 -0
  136. package/src/nestjs/http/decorators/index.ts +1 -0
  137. package/src/nestjs/http/decorators/log-app-ctx.decorator.ts +32 -0
  138. package/src/nestjs/http/dtos/date-range.dto.ts +15 -0
  139. package/src/nestjs/http/dtos/index.ts +1 -0
  140. package/src/nestjs/http/filters/all-exceptions.filter.ts +92 -0
  141. package/src/nestjs/http/filters/command-failure.filter.ts +12 -0
  142. package/src/nestjs/http/filters/index.ts +4 -0
  143. package/src/nestjs/http/filters/rpc-exceptions.filter.ts +10 -0
  144. package/src/nestjs/http/filters/too-many-results.filter.ts +12 -0
  145. package/src/nestjs/http/index.ts +6 -0
  146. package/src/nestjs/http/interceptors/index.ts +2 -0
  147. package/src/nestjs/http/interceptors/log-app-ctx.interceptor.ts +109 -0
  148. package/src/nestjs/http/interceptors/serialize-output.interceptor.ts +19 -0
  149. package/src/nestjs/http/middleware/http-logger.middleware.ts +31 -0
  150. package/src/nestjs/http/middleware/index.ts +2 -0
  151. package/src/nestjs/http/middleware/logger.middleware.ts +21 -0
  152. package/src/nestjs/http/swagger/index.ts +1 -0
  153. package/src/nestjs/http/swagger/swagger.ts +44 -0
  154. package/src/nestjs/index.ts +2 -0
  155. package/src/testing/command-bus.stub.ts +33 -0
  156. package/src/testing/event-bus.stub.ts +56 -0
  157. package/src/testing/event-publisher.stub.ts +24 -0
  158. package/src/testing/fake-logger.ts +20 -0
  159. package/src/testing/index.ts +2 -0
  160. package/src/testing/query-bus.stub.ts +27 -0
  161. package/src/testing/stub.spec.ts +250 -0
  162. package/src/testing/stub.ts +170 -0
  163. package/src/testing/stubs/command-bus.stub.ts +33 -0
  164. package/src/testing/stubs/event-bus.stub.ts +56 -0
  165. package/src/testing/stubs/event-publisher.stub.ts +24 -0
  166. package/src/testing/stubs/index.ts +5 -0
  167. package/src/testing/stubs/query-bus.stub.ts +27 -0
  168. package/src/testing/stubs/stub.ts +170 -0
  169. package/src/utils/array.spec.ts +29 -0
  170. package/src/utils/array.ts +10 -0
  171. package/src/utils/base64.spec.ts +18 -0
  172. package/src/utils/base64.ts +6 -0
  173. package/src/utils/collection.ts +4 -0
  174. package/src/utils/common.ts +13 -0
  175. package/src/utils/csv.ts +49 -0
  176. package/src/utils/date/date-range.ts +4 -0
  177. package/src/utils/date/date.spec.ts +100 -0
  178. package/src/utils/date/date.ts +177 -0
  179. package/src/utils/date/diff.spec.ts +66 -0
  180. package/src/utils/date/diffYear.spec.ts +23 -0
  181. package/src/utils/date/fillMissingRangeValues.spec.ts +523 -0
  182. package/src/utils/date/groubBy.spec.ts +183 -0
  183. package/src/utils/date/index.ts +2 -0
  184. package/src/utils/date/isSame.spec.ts +111 -0
  185. package/src/utils/file.spec.ts +66 -0
  186. package/src/utils/file.ts +5 -0
  187. package/src/utils/hash-key.ts +23 -0
  188. package/src/utils/index.ts +14 -0
  189. package/src/utils/invariant.ts +3 -0
  190. package/src/utils/iso-date.ts +11 -0
  191. package/src/utils/object.spec.ts +18 -0
  192. package/src/utils/object.ts +6 -0
  193. package/src/utils/paginated.ts +36 -0
  194. package/src/utils/string.spec.ts +10 -0
  195. package/src/utils/string.ts +19 -0
  196. package/src/utils/type.ts +9 -0
  197. package/src/utils/xml.ts +6 -0
  198. package/src/validation/ensure-array.decorator.ts +5 -0
  199. package/src/validation/index.ts +7 -0
  200. package/src/validation/is-iso-date.decorator.spec.ts +29 -0
  201. package/src/validation/is-iso-date.decorator.ts +30 -0
  202. package/src/validation/is-less-than-or-equal.decorator.spec.ts +30 -0
  203. package/src/validation/is-less-than-or-equal.decorator.ts +52 -0
  204. package/src/validation/is-less-than.decorator.spec.ts +36 -0
  205. package/src/validation/is-less-than.decorator.ts +52 -0
  206. package/src/validation/is-more-than-or-equal.decorator.spec.ts +30 -0
  207. package/src/validation/is-more-than-or-equal.decorator.ts +52 -0
  208. package/src/validation/is-more-than.decorator.spec.ts +36 -0
  209. package/src/validation/is-more-than.decorator.ts +52 -0
  210. package/src/validation/is-time-string.decorator.spec.ts +35 -0
  211. package/src/validation/is-time-string.decorator.ts +29 -0
  212. package/tsconfig.build.json +6 -0
  213. package/tsconfig.json +34 -0
  214. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,23 @@
1
+ import { Type } from "@nestjs/common";
2
+ import { Field, Int, ObjectType } from "@nestjs/graphql";
3
+
4
+ export type PaginatedResponse<TItem> = {
5
+ items: TItem[];
6
+ total: number;
7
+ hasMore: boolean;
8
+ };
9
+
10
+ export function PaginatedResponse<T>(classRef: Type<T>): Type<PaginatedResponse<T>> {
11
+ @ObjectType(`${classRef.name}Edge`, { isAbstract: true })
12
+ abstract class PaginatedType {
13
+ @Field(() => [classRef])
14
+ items: T[];
15
+
16
+ @Field(() => Int)
17
+ total: number;
18
+
19
+ @Field()
20
+ hasMore: boolean;
21
+ }
22
+ return PaginatedType as unknown as Type<PaginatedResponse<T>>;
23
+ }
@@ -0,0 +1,43 @@
1
+ import { Expose, Type } from "class-transformer";
2
+ import { IsOptional } from "class-validator";
3
+
4
+ export class HealthStatus {
5
+ @Expose()
6
+ status: "up" | "down";
7
+ }
8
+
9
+ export class HealthIndicatorResult {
10
+ @Expose()
11
+ @Type(() => HealthStatus)
12
+ @IsOptional()
13
+ database?: HealthStatus;
14
+
15
+ @Expose()
16
+ @Type(() => HealthStatus)
17
+ @IsOptional()
18
+ memory_heap?: HealthStatus;
19
+
20
+ @Expose()
21
+ @Type(() => HealthStatus)
22
+ @IsOptional()
23
+ rabbitmq?: HealthStatus;
24
+ }
25
+
26
+ export class HealthcheckOutDto {
27
+ @Expose()
28
+ status: "error" | "ok" | "shutting_down";
29
+
30
+ @Expose()
31
+ @Type(() => HealthIndicatorResult)
32
+ details: HealthIndicatorResult;
33
+
34
+ @Expose()
35
+ @Type(() => HealthIndicatorResult)
36
+ @IsOptional()
37
+ info?: HealthIndicatorResult;
38
+
39
+ @Expose()
40
+ @Type(() => HealthIndicatorResult)
41
+ @IsOptional()
42
+ error?: HealthIndicatorResult;
43
+ }
@@ -0,0 +1 @@
1
+ export * from "./healthcheck-out.dto";
@@ -0,0 +1,3 @@
1
+ export * from "./knex.decorator";
2
+ export * from "./knex.module";
3
+ export * from "./knex.token";
@@ -0,0 +1,30 @@
1
+ import { DynamicModule, Global, Module, Provider } from "@nestjs/common";
2
+ import knex from "knex";
3
+
4
+ import { KnexModuleAsyncOptions, KnexModuleOptions } from "./knex.interface";
5
+ import { KNEX_CONNECTION_TOKEN, KNEX_OPTIONS_TOKEN } from "./knex.token";
6
+
7
+ @Global()
8
+ @Module({})
9
+ export class KnexCoreModule {
10
+ static forRootAsync(options: KnexModuleAsyncOptions): DynamicModule {
11
+ const knexConnectionProvider: Provider = {
12
+ provide: KNEX_CONNECTION_TOKEN,
13
+ useFactory: (opts: KnexModuleOptions) => knex(opts),
14
+ inject: [KNEX_OPTIONS_TOKEN],
15
+ };
16
+
17
+ const knexOptionsProvider: Provider = {
18
+ provide: KNEX_OPTIONS_TOKEN,
19
+ useFactory: options.useFactory,
20
+ inject: options.inject ?? [],
21
+ };
22
+
23
+ return {
24
+ module: KnexCoreModule,
25
+ imports: options.imports,
26
+ providers: [knexOptionsProvider, knexConnectionProvider],
27
+ exports: [knexConnectionProvider],
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,5 @@
1
+ import { Inject } from "@nestjs/common";
2
+
3
+ import { KNEX_CONNECTION_TOKEN } from "./knex.token";
4
+
5
+ export const InjectKnexConnection = () => Inject(KNEX_CONNECTION_TOKEN);
@@ -0,0 +1,12 @@
1
+ /* nestjs uses any for the inject and useFactory parameters */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import { ModuleMetadata } from "@nestjs/common";
4
+ import knex from "knex";
5
+
6
+ export interface KnexModuleAsyncOptions extends Pick<ModuleMetadata, "imports"> {
7
+ inject: any[];
8
+ useFactory: (...args: any[]) => Promise<KnexModuleOptions> | KnexModuleOptions;
9
+ }
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
12
+ export interface KnexModuleOptions extends knex.Knex.Config {}
@@ -0,0 +1,14 @@
1
+ import { DynamicModule } from "@nestjs/common";
2
+
3
+ import { KnexCoreModule } from "./knex-core.module";
4
+ import { KnexModuleAsyncOptions } from "./knex.interface";
5
+
6
+ export class KnexModule {
7
+ public static forRootAsync(options: KnexModuleAsyncOptions): DynamicModule {
8
+ return {
9
+ module: KnexModule,
10
+ imports: [KnexCoreModule.forRootAsync(options)],
11
+ exports: [KnexCoreModule],
12
+ };
13
+ }
14
+ }
@@ -0,0 +1,2 @@
1
+ export const KNEX_CONNECTION_TOKEN = Symbol("KNEX_CONNECTION_TOKEN");
2
+ export const KNEX_OPTIONS_TOKEN = Symbol("KNEX_OPTIONS_TOKEN");
@@ -0,0 +1,28 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
2
+ import { LoggerService as NestLoggerService } from "@nestjs/common";
3
+
4
+ import { LogLevel } from "./log";
5
+ import { LoggerService } from "./logger.service";
6
+
7
+ export class AppLogger implements NestLoggerService {
8
+ constructor(private readonly loggerService: LoggerService) {}
9
+
10
+ error(message: any, meta?: any) {
11
+ this.runLog("error", message, typeof meta === "string" ? { stack: meta } : meta);
12
+ }
13
+
14
+ log(message: any, meta?: any) { this.runLog("info", message, meta); }
15
+ warn(message: any, meta?: any) { this.runLog("warn", message, meta); }
16
+ debug(message: any, meta?: any) { this.runLog("debug", message, meta); }
17
+ verbose(message: any, meta?: any) { this.runLog("verbose", message, meta); }
18
+
19
+ private runLog(level: LogLevel, message: any, meta?: any) {
20
+ if (typeof message === "string" && typeof meta === "string") {
21
+ this.loggerService.log(level, message, { meta });
22
+ } else if (typeof message === "string") {
23
+ this.loggerService.log(level, message, meta);
24
+ } else {
25
+ this.loggerService.log(level, { message, meta });
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./app-logger";
2
+ export * from "./log";
3
+ export * from "./logger.module";
4
+ export * from "./logger.service";
5
+ export * from "./transports";
@@ -0,0 +1,26 @@
1
+ type RawLog = Record<string, unknown>;
2
+ type MetricsLog = Record<string, number>;
3
+
4
+ export type CtxLog = Record<string, unknown>;
5
+ export type DataLog = Partial<{ metrics: MetricsLog } & RawLog>;
6
+
7
+ export const LOG_LEVELS = ["error", "warn", "info", "http", "verbose", "debug", "silly"] as const;
8
+ export type LogLevel = (typeof LOG_LEVELS)[number];
9
+
10
+ export type Log = {
11
+ service: string;
12
+ level: LogLevel;
13
+ message: string;
14
+ data?: DataLog;
15
+ stack?: string;
16
+ };
17
+
18
+ export const LOG_LEVEL_PRIORITY = {
19
+ error: 0,
20
+ warn: 1,
21
+ info: 2,
22
+ http: 3,
23
+ verbose: 4,
24
+ debug: 5,
25
+ silly: 6,
26
+ } as const satisfies Record<LogLevel, number>;
@@ -0,0 +1,47 @@
1
+ import { MiddlewareConsumer, Module, RequestMethod } from "@nestjs/common";
2
+ import { APP_INTERCEPTOR } from "@nestjs/core";
3
+
4
+ import { ConfigModule } from "../config";
5
+ import { LogAppCtxInterceptor } from "../../nestjs/http/interceptors/log-app-ctx.interceptor";
6
+ import { HttpLoggerMiddleware } from "../../nestjs/http/middleware/http-logger.middleware";
7
+ import { LoggerMiddleware } from "../../nestjs/http/middleware/logger.middleware";
8
+ import { LoggerService } from "./logger.service";
9
+ import { LogLevel } from "./log";
10
+ import { ConsoleJSONTransport } from "./transports/console-json.transport";
11
+ import { ConsoleTransport } from "./transports/console.transport";
12
+ import { LoggerTransport } from "./transports/logger-transport";
13
+
14
+ function createTransports(): LoggerTransport[] {
15
+ const { NODE_ENV, PHARADAY_TRANSPORT_LOGGER, PHARADAY_LEVEL_LOGGER } = process.env;
16
+
17
+ if (PHARADAY_TRANSPORT_LOGGER === "json") {
18
+ return [new ConsoleJSONTransport({ level: (PHARADAY_LEVEL_LOGGER as LogLevel) ?? "info" })];
19
+ }
20
+ if (PHARADAY_TRANSPORT_LOGGER) {
21
+ return [new ConsoleTransport()];
22
+ }
23
+
24
+ if (NODE_ENV === "production") {return [new ConsoleJSONTransport({ level: "info" })];}
25
+ if (NODE_ENV === "test" || NODE_ENV === "ci") {return [];}
26
+ return [new ConsoleTransport()];
27
+ }
28
+
29
+ @Module({
30
+ imports: [ConfigModule],
31
+ providers: [
32
+ {
33
+ provide: LoggerService,
34
+ useFactory: () => new LoggerService({ service: "pharaday-platform", transports: createTransports() }),
35
+ },
36
+ {
37
+ provide: APP_INTERCEPTOR,
38
+ useClass: LogAppCtxInterceptor,
39
+ },
40
+ ],
41
+ exports: [LoggerService],
42
+ })
43
+ export class LoggerModule {
44
+ configure(consumer: MiddlewareConsumer) {
45
+ consumer.apply(LoggerMiddleware, HttpLoggerMiddleware).forRoutes({ path: "*", method: RequestMethod.ALL });
46
+ }
47
+ }
@@ -0,0 +1,131 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
2
+ import * as cls from "cls-hooked";
3
+ import { merge } from "lodash";
4
+
5
+ import { ArchitectureLayer, ArchitectureLevel } from "../../core/types";
6
+ import { LoggerTransport } from "./transports/logger-transport";
7
+ import { CtxLog, DataLog, LogLevel } from "./log";
8
+
9
+ const NSID = "fb8b817a-4824-4abc-a1b1-e7cbb7f420eb";
10
+
11
+ export const SECRET_KEYS = [
12
+ "pass", "password", "token", "pendingToken",
13
+ "authToken", "accessToken", "refreshToken", "secret",
14
+ ] as const;
15
+
16
+ const IGNORED_MESSAGES = ["Successfully connected to RMQ broker"] as const;
17
+
18
+ export class LoggerService {
19
+ private readonly transports: LoggerTransport[];
20
+ private readonly service: string;
21
+
22
+ constructor({ transports, service }: { transports: LoggerTransport[]; service: string }) {
23
+ this.transports = transports;
24
+ this.service = service;
25
+ }
26
+
27
+ log(level: LogLevel, arg1: string | DataLog, arg2?: string | DataLog) {
28
+ const message = typeof arg1 === "string" ? arg1 : "";
29
+ if (IGNORED_MESSAGES.includes(message as any)) {return;}
30
+
31
+ const obj = typeof arg1 === "string" ? arg2 : arg1;
32
+ const { data, stack } = this.extractLogData(obj, message);
33
+ const ctx = this.getCtx();
34
+
35
+ for (const transport of this.transports) {
36
+ transport.add({
37
+ level,
38
+ message,
39
+ service: this.service,
40
+ stack,
41
+ data: { ...data, ...ctx },
42
+ });
43
+ }
44
+ }
45
+
46
+ error(arg1: string | DataLog, arg2?: string | DataLog) { this.log("error", arg1, arg2); }
47
+ warn(arg1: string | DataLog, arg2?: string | DataLog) { this.log("warn", arg1, arg2); }
48
+ info(arg1: string | DataLog, arg2?: string | DataLog) { this.log("info", arg1, arg2); }
49
+ http(arg1: string | DataLog, arg2?: string | DataLog) { this.log("http", arg1, arg2); }
50
+ verbose(arg1: string | DataLog, arg2?: string | DataLog) { this.log("verbose", arg1, arg2); }
51
+ debug(arg1: string | DataLog, arg2?: string | DataLog) { this.log("debug", arg1, arg2); }
52
+ silly(arg1: string | DataLog, arg2?: string | DataLog) { this.log("silly", arg1, arg2); }
53
+
54
+ init(req: any, res: any, next: any) {
55
+ const ns = cls.getNamespace(NSID) ?? cls.createNamespace(NSID);
56
+ ns.bind(req);
57
+ ns.bind(res);
58
+ ns.run(() => next());
59
+ }
60
+
61
+ clearCtx() {
62
+ const ns = cls.getNamespace(NSID);
63
+ if (ns?.active) {ns.set("ctx", {});}
64
+ }
65
+
66
+ ctx(layer: ArchitectureLayer, log?: CtxLog) {
67
+ const ns = cls.getNamespace(NSID);
68
+ const levelKeys = Object.keys(layer) as ArchitectureLevel[];
69
+
70
+ if (levelKeys.length <= 0) {
71
+ this.warn("[Logger] Error: layer has no architecture level, cannot set any logger context");
72
+ return;
73
+ }
74
+
75
+ try {
76
+ const anyLayer = layer as any;
77
+ const level = levelKeys[0] as string;
78
+ const trace = Object.keys(anyLayer[level])[0];
79
+ anyLayer[level][trace].meta = { ...anyLayer[level][trace].meta, ...log };
80
+ } catch {
81
+ this.warn("[Logger] Error: unable to define logger context, logging will be uncoherent", { error: "ctx parse failed" });
82
+ }
83
+
84
+ if (ns?.active) {
85
+ return ns.set("ctx", { ...merge(this.getCtx(), layer) });
86
+ }
87
+ }
88
+
89
+ private getCtx(): CtxLog {
90
+ return cls.getNamespace(NSID)?.active ? cls.getNamespace(NSID)!.get("ctx") : {};
91
+ }
92
+
93
+ private extractLogData(obj: string | DataLog | undefined, message: string) {
94
+ let stack: string | undefined;
95
+ const data: DataLog = {};
96
+
97
+ if (Array.isArray(obj)) {
98
+ data["list"] = this.stringifyValue(obj);
99
+ } else if (obj instanceof Error) {
100
+ stack = obj.stack;
101
+ if (!message) {data["text"] = obj.message;}
102
+ } else if (typeof obj === "object" && obj !== null) {
103
+ for (const [key, value] of Object.entries(obj)) {
104
+ if (key === "metrics") {data.metrics = { ...obj.metrics };}
105
+ else if (key === "stack" && typeof value === "string") {stack = this.stringifyValue(value);}
106
+ else {data[key] = this.sanitizeLog(key, value);}
107
+ }
108
+ } else if (typeof obj === "string") {
109
+ data["text"] = obj;
110
+ }
111
+
112
+ return { data, stack };
113
+ }
114
+
115
+ private stringifyValue(value: any): string {
116
+ try {
117
+ const content = value === undefined ? "" : typeof value === "string" ? value : JSON.stringify(value);
118
+ const secretPattern = new RegExp(`"(${SECRET_KEYS.join("|")})":"([^"]*)"`, "gm");
119
+ return content
120
+ .replace(/Bearer [^ "'\\]*/gm, "Bearer ***")
121
+ .replace(secretPattern, '"$1": "***"')
122
+ .replace(/(\.eyJ[^\s"]+\.[^\s"]+\.[^\s"]+)/, (_: string, match: string) => match.substring(0, 5) + "***");
123
+ } catch {
124
+ return "JSON.stringify did not work";
125
+ }
126
+ }
127
+
128
+ private sanitizeLog(key: string, value: any) {
129
+ return SECRET_KEYS.includes(key as any) ? "***" : this.stringifyValue(value);
130
+ }
131
+ }
@@ -0,0 +1,41 @@
1
+ import { Log } from "../log";
2
+ import { LoggerTransport } from "./logger-transport";
3
+
4
+ const COLORS = {
5
+ Reset: "\x1b[0m",
6
+ Bright: "\x1b[1m",
7
+ Dim: "\x1b[2m",
8
+ FgRed: "\x1b[31m",
9
+ FgGreen: "\x1b[32m",
10
+ FgYellow: "\x1b[33m",
11
+ FgBlue: "\x1b[34m",
12
+ FgCyan: "\x1b[36m",
13
+ FgWhite: "\x1b[37m",
14
+ } as const;
15
+
16
+ const LEVEL_COLOR: Record<string, string> = {
17
+ warn: COLORS.FgYellow,
18
+ info: COLORS.FgCyan,
19
+ error: COLORS.FgRed,
20
+ http: COLORS.FgGreen,
21
+ };
22
+
23
+ export class ConsoleColorTransport extends LoggerTransport {
24
+ send(log: Log): void {
25
+ const level = this.bracket(log.level.toUpperCase(), LEVEL_COLOR[log.level] ?? COLORS.FgBlue);
26
+ const date = `${COLORS.Dim}${new Date().toISOString().substring(11, 19)}${COLORS.Reset}`;
27
+ const message = `${COLORS.Bright}${log.message}`;
28
+ const data = log.data && Object.keys(log.data).length > 0
29
+ ? `\n${COLORS.Dim}${JSON.stringify(log.data, null, 2)}${COLORS.Reset}`
30
+ : "";
31
+ const stack = log.stack
32
+ ? `\n-----\n${COLORS.Reset}${COLORS.Dim}${log.stack}${COLORS.Reset}\n-----\n`
33
+ : "";
34
+ // eslint-disable-next-line no-console
35
+ console.log(`${level} ${date} ${message}${data}${stack}`);
36
+ }
37
+
38
+ private bracket(message: string, color: string) {
39
+ return `${COLORS.FgWhite}[${color}${message}${COLORS.FgWhite}]${COLORS.Reset}`;
40
+ }
41
+ }
@@ -0,0 +1,23 @@
1
+ import { Log } from "../log";
2
+ import { LoggerTransport } from "./logger-transport";
3
+
4
+ export class ConsoleJSONTransport extends LoggerTransport {
5
+ format(log: Log): string {
6
+ const { data = {}, message, service, stack, level } = log;
7
+ const { metrics, ...otherData } = data;
8
+ return JSON.stringify({
9
+ timestamp: new Date().toISOString(),
10
+ level,
11
+ service,
12
+ message,
13
+ data: otherData,
14
+ metrics,
15
+ stack,
16
+ });
17
+ }
18
+
19
+ send(log: Log): void {
20
+ // eslint-disable-next-line no-console
21
+ console.log(this.format(log));
22
+ }
23
+ }
@@ -0,0 +1,10 @@
1
+ /* eslint-disable no-console */
2
+ import { Log } from "../log";
3
+ import { LoggerTransport } from "./logger-transport";
4
+
5
+ export class ConsoleTransport extends LoggerTransport {
6
+ send(log: Log): void {
7
+ console.log(`[${log.level}] ${log.message}`);
8
+ console.log(JSON.stringify(log.data, null, 2));
9
+ }
10
+ }
@@ -0,0 +1,18 @@
1
+ import { Log } from "../log";
2
+ import { LoggerTransport } from "./logger-transport";
3
+
4
+ export class FakeTransport extends LoggerTransport {
5
+ public logs: Log[] = [];
6
+
7
+ send(log: Log) {
8
+ this.logs.push(log);
9
+ }
10
+
11
+ clear(): void {
12
+ this.logs = [];
13
+ }
14
+
15
+ getLastLog(): Log | undefined {
16
+ return this.logs.at(-1);
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./console.transport";
2
+ export * from "./console-color.transport";
3
+ export * from "./console-json.transport";
4
+ export * from "./fake.transport";
5
+ export * from "./logger-transport";
@@ -0,0 +1,22 @@
1
+ import { Log, LogLevel, LOG_LEVEL_PRIORITY } from "../log";
2
+
3
+ export type LoggerTransportOptions = {
4
+ level: LogLevel;
5
+ };
6
+
7
+ export abstract class LoggerTransport {
8
+ readonly levelPriority: number;
9
+
10
+ constructor(public readonly options: LoggerTransportOptions = { level: "silly" }) {
11
+ this.levelPriority = LOG_LEVEL_PRIORITY[options.level];
12
+ }
13
+
14
+ add(log: Log): void {
15
+ if (this.levelPriority < LOG_LEVEL_PRIORITY[log.level]) {
16
+ return;
17
+ }
18
+ this.send(log);
19
+ }
20
+
21
+ abstract send(log: Log): void;
22
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./messaging.module";
2
+ export * from "../../core/infrastructure/messaging";
@@ -0,0 +1,92 @@
1
+ import { DynamicModule, Module } from "@nestjs/common";
2
+ import { ClientProxy, ClientProxyFactory, Transport } from "@nestjs/microservices";
3
+
4
+ import { MessagingService, FakeMessagingService, RabbitMessagingService } from "../../core/infrastructure/messaging";
5
+ import { ConfigModule, ConfigService } from "../config";
6
+ import { LoggerModule, LoggerService } from "../logger";
7
+
8
+ const clientProxyCache = new Map<string, ClientProxy>();
9
+
10
+ function buildRmqUrl(config: ConfigService): string {
11
+ const protocol = config.get("RMQ_PROTOCOL");
12
+ const user = config.get("RMQ_USER");
13
+ const password = config.get("RMQ_PASSWORD");
14
+ const host = config.get("RMQ_HOST");
15
+ const port = config.get("RMQ_PORT");
16
+ return `${protocol}://${user}:${password}@${host}:${port}`;
17
+ }
18
+
19
+ async function closeAllCachedProxies(logger?: { info: (msg: string) => void; error: (msg: string, data?: object) => void }) {
20
+ for (const [queue, proxy] of clientProxyCache) {
21
+ try {
22
+ await proxy.close();
23
+ logger?.info(`Closed cached client proxy for queue: ${queue}`);
24
+ } catch (error) {
25
+ logger?.error(`Error closing cached client proxy for queue ${queue}:`, { error });
26
+ }
27
+ }
28
+ clientProxyCache.clear();
29
+ }
30
+
31
+ @Module({})
32
+ export class MessagingModule {
33
+ static register(queueName: string): DynamicModule {
34
+ return {
35
+ module: MessagingModule,
36
+ imports: [ConfigModule, LoggerModule],
37
+ providers: [
38
+ {
39
+ provide: MessagingService,
40
+ useFactory: (configService: ConfigService, optionalProvider: ClientProxy) => {
41
+ return configService.isTest() ? new FakeMessagingService() : new RabbitMessagingService(optionalProvider);
42
+ },
43
+ inject: [ConfigService, { token: queueName, optional: false }],
44
+ },
45
+ {
46
+ provide: queueName,
47
+ useFactory: async (configService: ConfigService) => {
48
+ if (configService.isTest()) {return {};}
49
+
50
+ const cached = clientProxyCache.get(queueName);
51
+ if (cached) {return cached;}
52
+
53
+ const clientProxy = ClientProxyFactory.create({
54
+ transport: Transport.RMQ,
55
+ options: {
56
+ urls: [buildRmqUrl(configService)],
57
+ queue: queueName,
58
+ queueOptions: {
59
+ durable: configService.getBoolean("RMQ_IS_DURABLE"),
60
+ exclusive: false,
61
+ autoDelete: false,
62
+ },
63
+ prefetchCount: 1,
64
+ socketOptions: {
65
+ heartbeatIntervalInSeconds: 30,
66
+ reconnectTimeInSeconds: 5,
67
+ },
68
+ },
69
+ });
70
+
71
+ await clientProxy.connect();
72
+ clientProxyCache.set(queueName, clientProxy);
73
+ return clientProxy;
74
+ },
75
+ inject: [ConfigService],
76
+ },
77
+ {
78
+ provide: "MESSAGING_MODULE_CLEANUP",
79
+ useFactory: (logger: LoggerService) => ({
80
+ async onModuleDestroy() { await closeAllCachedProxies(logger); },
81
+ }),
82
+ inject: [LoggerService],
83
+ },
84
+ ],
85
+ exports: [MessagingService],
86
+ };
87
+ }
88
+ }
89
+
90
+ export async function cleanupMessagingConnections() {
91
+ await closeAllCachedProxies();
92
+ }
@@ -0,0 +1,5 @@
1
+ import { ConfigService } from "../../core/infrastructure/config";
2
+ import { QueueName, QueueNameResolver } from "../../core/infrastructure/messaging/types";
3
+
4
+ export const defaultQueueNameResolverFactory = (configService: ConfigService): QueueNameResolver =>
5
+ (queueName: QueueName) => configService.get(`${queueName}_QUEUE_RMQ`) ?? "default";
@@ -0,0 +1,3 @@
1
+ export * from "./queue.module";
2
+ export * from "../../core/infrastructure/messaging/types";
3
+ export * from "../../core/infrastructure/messaging/queue-manager.service";