xcally-nest-library 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. package/.eslintrc.js +22 -0
  2. package/.nvmrc +1 -0
  3. package/.prettierrc +5 -0
  4. package/.vscode/settings.json +6 -0
  5. package/README.md +73 -0
  6. package/dist/index.d.ts +9 -0
  7. package/dist/index.js +26 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/src/config/env.validation.d.ts +21 -0
  10. package/dist/src/config/env.validation.js +80 -0
  11. package/dist/src/config/env.validation.js.map +1 -0
  12. package/dist/src/db/typeorm/generic.repository.d.ts +18 -0
  13. package/dist/src/db/typeorm/generic.repository.js +43 -0
  14. package/dist/src/db/typeorm/generic.repository.js.map +1 -0
  15. package/dist/src/decorators/utils.decorators.d.ts +2 -0
  16. package/dist/src/decorators/utils.decorators.js +15 -0
  17. package/dist/src/decorators/utils.decorators.js.map +1 -0
  18. package/dist/src/interceptors/log-request.interceptor.d.ts +8 -0
  19. package/dist/src/interceptors/log-request.interceptor.js +42 -0
  20. package/dist/src/interceptors/log-request.interceptor.js.map +1 -0
  21. package/dist/src/interceptors/serialize.interceptor.d.ts +12 -0
  22. package/dist/src/interceptors/serialize.interceptor.js +24 -0
  23. package/dist/src/interceptors/serialize.interceptor.js.map +1 -0
  24. package/dist/src/modules/logger/winston.decorator.d.ts +1 -0
  25. package/dist/src/modules/logger/winston.decorator.js +17 -0
  26. package/dist/src/modules/logger/winston.decorator.js.map +1 -0
  27. package/dist/src/modules/logger/winston.interface.d.ts +21 -0
  28. package/dist/src/modules/logger/winston.interface.js +3 -0
  29. package/dist/src/modules/logger/winston.interface.js.map +1 -0
  30. package/dist/src/modules/logger/winston.module.d.ts +4 -0
  31. package/dist/src/modules/logger/winston.module.js +95 -0
  32. package/dist/src/modules/logger/winston.module.js.map +1 -0
  33. package/dist/src/modules/logger/winston.service.d.ts +23 -0
  34. package/dist/src/modules/logger/winston.service.js +113 -0
  35. package/dist/src/modules/logger/winston.service.js.map +1 -0
  36. package/dist/src/modules/tracer/tracer.middleware.d.ts +24 -0
  37. package/dist/src/modules/tracer/tracer.middleware.js +41 -0
  38. package/dist/src/modules/tracer/tracer.middleware.js.map +1 -0
  39. package/dist/src/modules/tracer/tracer.module.d.ts +7 -0
  40. package/dist/src/modules/tracer/tracer.module.js +36 -0
  41. package/dist/src/modules/tracer/tracer.module.js.map +1 -0
  42. package/dist/src/types/auth.d.ts +8 -0
  43. package/dist/src/types/auth.js +9 -0
  44. package/dist/src/types/auth.js.map +1 -0
  45. package/dist/src/types/index.d.ts +1 -0
  46. package/dist/src/types/index.js +18 -0
  47. package/dist/src/types/index.js.map +1 -0
  48. package/dist/tsconfig.build.tsbuildinfo +1 -0
  49. package/index.ts +9 -0
  50. package/nest-cli.json +8 -0
  51. package/package.json +89 -0
  52. package/src/config/env.validation.ts +59 -0
  53. package/src/db/typeorm/generic.repository.ts +43 -0
  54. package/src/decorators/utils.decorators.ts +28 -0
  55. package/src/interceptors/log-request.interceptor.ts +30 -0
  56. package/src/interceptors/serialize.interceptor.ts +27 -0
  57. package/src/modules/logger/winston.decorator.ts +14 -0
  58. package/src/modules/logger/winston.interface.ts +22 -0
  59. package/src/modules/logger/winston.module.ts +105 -0
  60. package/src/modules/logger/winston.service.ts +60 -0
  61. package/src/modules/tracer/tracer.middleware.ts +40 -0
  62. package/src/modules/tracer/tracer.module.ts +18 -0
  63. package/src/types/auth.ts +9 -0
  64. package/src/types/index.ts +1 -0
  65. package/test/jest-e2e.json +9 -0
  66. package/tsconfig.build.json +4 -0
  67. package/tsconfig.json +21 -0
package/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from './src/types';
2
+ export * from './src/interceptors/log-request.interceptor';
3
+ export * from './src/interceptors/serialize.interceptor';
4
+ export * from './src/modules/logger/winston.service';
5
+ export * from './src/modules/logger/winston.module';
6
+ export * from './src/modules/tracer/tracer.module';
7
+ export * from './src/db/typeorm/generic.repository';
8
+ export * from './src/decorators/utils.decorators';
9
+ export * from './src/config/env.validation';
package/nest-cli.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "xcally-nest-library",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "author": "",
8
+ "license": "UNLICENSED",
9
+ "scripts": {
10
+ "prepublish": "pnpm build",
11
+ "build": "nest build",
12
+ "build:watch": "rimraf dist && tsc-watch -b -v",
13
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
14
+ "prettier:check": "prettier --check ./**/*.{ts,js,json,*rc}",
15
+ "prettier:write": "prettier --write ./**/*.{ts,js,json,*rc}",
16
+ "start": "nest start",
17
+ "start:dev": "nest start --watch",
18
+ "start:debug": "nest start --debug --watch",
19
+ "start:prod": "node dist/main",
20
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
21
+ "test": "jest",
22
+ "test:watch": "jest --watch",
23
+ "test:cov": "jest --coverage",
24
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
25
+ "test:e2e": "jest --config ./test/jest-e2e.json",
26
+ "link:lib": "pnpm link --global",
27
+ "unlink:lib": "pnpm unlink xcally-nest-libs"
28
+ },
29
+ "dependencies": {
30
+ "@nestjs/axios": "^3.0.2",
31
+ "@nestjs/common": "^10.3.7",
32
+ "@nestjs/config": "^3.2.2",
33
+ "@nestjs/core": "^10.3.7",
34
+ "@nestjs/microservices": "^10.3.7",
35
+ "@nestjs/platform-express": "^10.3.7",
36
+ "axios": "^1.6.8",
37
+ "class-transformer": "^0.5.1",
38
+ "class-validator": "^0.14.1",
39
+ "datadog-winston": "^1.6.0",
40
+ "nest-winston": "^1.9.4",
41
+ "nestjs-grpc-exceptions": "^0.2.2",
42
+ "reflect-metadata": "^0.2.2",
43
+ "rxjs": "^7.8.1",
44
+ "typeorm": "^0.3.20",
45
+ "winston": "^3.13.0",
46
+ "winston-elasticsearch": "^0.18.0"
47
+ },
48
+ "devDependencies": {
49
+ "@nestjs/cli": "^10.3.2",
50
+ "@nestjs/schematics": "^10.1.1",
51
+ "@nestjs/testing": "^10.3.7",
52
+ "@types/express": "^4.17.21",
53
+ "@types/jest": "^29.5.12",
54
+ "@types/node": "^20.12.7",
55
+ "@types/supertest": "^6.0.2",
56
+ "@typescript-eslint/eslint-plugin": "^7.6.0",
57
+ "@typescript-eslint/parser": "^7.6.0",
58
+ "eslint": "^8.57.0",
59
+ "eslint-config-prettier": "^9.1.0",
60
+ "eslint-plugin-prettier": "^5.1.3",
61
+ "jest": "^29.7.0",
62
+ "prettier": "^3.2.5",
63
+ "source-map-support": "^0.5.21",
64
+ "supertest": "^6.3.4",
65
+ "ts-jest": "^29.1.2",
66
+ "ts-loader": "^9.5.1",
67
+ "ts-node": "^10.9.2",
68
+ "tsc-watch": "^6.2.0",
69
+ "tsconfig-paths": "^4.2.0",
70
+ "typescript": "^5.4.5"
71
+ },
72
+ "jest": {
73
+ "moduleFileExtensions": [
74
+ "js",
75
+ "json",
76
+ "ts"
77
+ ],
78
+ "rootDir": "src",
79
+ "testRegex": ".*\\.spec\\.ts$",
80
+ "transform": {
81
+ "^.+\\.(t|j)s$": "ts-jest"
82
+ },
83
+ "collectCoverageFrom": [
84
+ "**/*.(t|j)s"
85
+ ],
86
+ "coverageDirectory": "../coverage",
87
+ "testEnvironment": "node"
88
+ }
89
+ }
@@ -0,0 +1,59 @@
1
+ import { plainToClass } from 'class-transformer';
2
+ import { IsEnum, IsNumber, IsBoolean, IsString, validateSync, NotContains } from 'class-validator';
3
+
4
+ enum EnvironmentType {
5
+ Dev = 'development',
6
+ Prod = 'production',
7
+ Test = 'test',
8
+ Staging = 'staging',
9
+ }
10
+
11
+ class EnvironmentVariables {
12
+ @IsEnum(EnvironmentType)
13
+ NODE_ENV: EnvironmentType;
14
+
15
+ @IsString()
16
+ MYSQL_HOST: string;
17
+
18
+ @IsNumber()
19
+ MYSQL_PORT: number;
20
+
21
+ @IsString()
22
+ @NotContains(' ')
23
+ MYSQL_DATABASE: string;
24
+
25
+ @IsString()
26
+ MYSQL_USERNAME: string;
27
+
28
+ @IsString()
29
+ MYSQL_PASSWORD: string;
30
+
31
+ @IsBoolean()
32
+ MYSQL_SYNCHRONIZE: boolean;
33
+
34
+ @IsNumber()
35
+ HTTP_TIMEOUT: number;
36
+
37
+ @IsNumber()
38
+ HTTP_MAX_REDIRECTS: number;
39
+
40
+ @IsString()
41
+ BASE_MOTION_URL: string;
42
+
43
+ @IsString()
44
+ ACTOR_SERVICE_URL: string;
45
+ }
46
+
47
+ export function validate(configuration: Record<string, unknown>) {
48
+ const finalConfig = plainToClass(EnvironmentVariables, configuration, {
49
+ enableImplicitConversion: true,
50
+ });
51
+
52
+ const errors = validateSync(finalConfig, { skipMissingProperties: true });
53
+
54
+ if (errors.length > 0) {
55
+ throw new Error(errors.toString());
56
+ }
57
+
58
+ return finalConfig;
59
+ }
@@ -0,0 +1,43 @@
1
+ import { Repository, EntityTarget, EntityManager, Equal, FindOptionsWhere, ObjectLiteral, DeepPartial } from 'typeorm';
2
+ import { Injectable } from '@nestjs/common';
3
+
4
+ interface IId {
5
+ id: number;
6
+ }
7
+
8
+ interface IGenericRepository<E> {
9
+ save<T extends DeepPartial<E>>(entity: T): Promise<E>;
10
+ update<T extends DeepPartial<E> & IId>(entity: T): Promise<E>;
11
+ count(options: FindOptionsWhere<E>): Promise<number>;
12
+ }
13
+
14
+ @Injectable()
15
+ export class GenericRepository<E extends ObjectLiteral> implements IGenericRepository<E> {
16
+ protected repository: Repository<E>;
17
+
18
+ constructor(
19
+ readonly entityManager: EntityManager,
20
+ entity: EntityTarget<E>,
21
+ ) {
22
+ this.repository = entityManager.getRepository(entity);
23
+ }
24
+ save<T extends DeepPartial<E>>(entity: T): Promise<E> {
25
+ const res = this.repository.create({ ...entity });
26
+ return this.repository.save(res);
27
+ }
28
+
29
+ async update<T extends DeepPartial<E> & IId>(dto: T): Promise<E> {
30
+ const entity = await this.repository.findOneBy({
31
+ id: Equal(dto.id),
32
+ } as unknown as FindOptionsWhere<E>);
33
+ if (!entity) {
34
+ throw new Error('Entity not found');
35
+ }
36
+ const entityUpdated = await this.repository.save({ id: entity.id, ...dto });
37
+ return Object.assign(entity, entityUpdated);
38
+ }
39
+
40
+ count(options: FindOptionsWhere<E>): Promise<number> {
41
+ return this.repository.count(options);
42
+ }
43
+ }
@@ -0,0 +1,28 @@
1
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
+
3
+ /**
4
+ * Creates a parameter decorator that retrieves the metadata from the execution context.
5
+ *
6
+ * @param data - Additional data passed to the decorator (unused)
7
+ * @param ctx - The execution context
8
+ * @returns The metadata from the execution context
9
+ */
10
+ export const GetMeta = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
11
+ const context = ctx.switchToRpc();
12
+ return context.getContext();
13
+ });
14
+
15
+ /**
16
+ * Creates a parameter decorator that retrieves the trace ID from the execution context.
17
+ * The 'traceId' key should be used when setting this value in the metadata.
18
+ *
19
+ * @param data - Additional data passed to the decorator (unused)
20
+ * @param ctx - The execution context
21
+ * @returns The trace ID
22
+ */
23
+ export const GetTraceId = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
24
+ const context = ctx.switchToRpc();
25
+ const metadata = context.getContext();
26
+ const traceId = metadata.get('traceId')[0];
27
+ return traceId; // 'traceId' should match the key used when setting this value in metadata
28
+ });
@@ -0,0 +1,30 @@
1
+ import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ import { tap } from 'rxjs/operators';
4
+ import { WinstonLoggerService } from '../modules/logger/winston.service';
5
+ // import { CustomLoggerService } from '../modules/logger/winston.service';
6
+ @Injectable()
7
+ export class LogRequestInterceptor implements NestInterceptor {
8
+ constructor(private readonly logger: WinstonLoggerService) {}
9
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
10
+ const req = context.switchToHttp().getRequest();
11
+ this.logger.info('Before Middleware...', {
12
+ traceId: req?.local?.traceId,
13
+ className: 'LogRequestInterceptor',
14
+ });
15
+ const now = Date.now();
16
+
17
+ if (req) {
18
+ const method = req.method;
19
+ const url = req.url;
20
+ return next.handle().pipe(
21
+ tap(() =>
22
+ this.logger.info(`After Middleware... ${method} ${url} ${Date.now() - now}ms`, {
23
+ traceId: req?.local?.traceId,
24
+ className: 'LogRequestInterceptor',
25
+ }),
26
+ ),
27
+ );
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,27 @@
1
+ import { UseInterceptors, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ import { map } from 'rxjs/operators';
4
+ import { plainToInstance } from 'class-transformer';
5
+
6
+ interface ClassConstructor {
7
+ // eslint-disable-next-line @typescript-eslint/ban-types
8
+ new (...args: any[]): {};
9
+ }
10
+
11
+ export function Serialize(dto: ClassConstructor) {
12
+ return UseInterceptors(new SerializeInterceptor(dto));
13
+ }
14
+
15
+ export class SerializeInterceptor implements NestInterceptor {
16
+ constructor(private dto: any) {}
17
+
18
+ intercept(context: ExecutionContext, handler: CallHandler): Observable<any> {
19
+ return handler.handle().pipe(
20
+ map((data: any) => {
21
+ return plainToInstance(this.dto, data, {
22
+ excludeExtraneousValues: true,
23
+ });
24
+ }),
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,14 @@
1
+ export const TraceLog = () => {
2
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
3
+ const originalMethod = descriptor.value;
4
+
5
+ descriptor.value = function (message: string, meta: any) {
6
+ const traceId = meta?.traceId || 'unknown-trace-id';
7
+ const className = meta.className;
8
+ meta = { ...meta, traceId, className };
9
+ originalMethod.call(this, message, meta);
10
+ };
11
+
12
+ return descriptor;
13
+ };
14
+ };
@@ -0,0 +1,22 @@
1
+ import { ElasticsearchTransportOptions } from 'winston-elasticsearch';
2
+ import { DatadogTransportOptions } from 'datadog-winston';
3
+ export interface IWinstonLogger {
4
+ info(message: string, meta?: Record<string, any>): void;
5
+ error(message: string, meta?: Record<string, any>): void;
6
+ warn(message: string, meta?: Record<string, any>): void;
7
+ help(message: string, meta?: Record<string, any>): void;
8
+ data(message: string, meta?: Record<string, any>): void;
9
+ debug(message: string, meta?: Record<string, any>): void;
10
+ prompt(message: string, meta?: Record<string, any>): void;
11
+ http(message: string, meta?: Record<string, any>): void;
12
+ verbose(message: string, meta?: Record<string, any>): void;
13
+ input(message: string, meta?: Record<string, any>): void;
14
+ silly(message: string, meta?: Record<string, any>): void;
15
+ }
16
+
17
+ export interface WinstonLoggerModuleOptions {
18
+ transportFile?: boolean;
19
+ transportConsole?: boolean;
20
+ transportElasticsearch?: ElasticsearchTransportOptions;
21
+ transportDatadog?: DatadogTransportOptions;
22
+ }
@@ -0,0 +1,105 @@
1
+ // winston.module.ts
2
+ import { Module, DynamicModule, Global } from '@nestjs/common';
3
+ import { createLogger, transports, format, Logger } from 'winston';
4
+ // import * as Elasticsearch from 'winston-elasticsearch';
5
+ import { ConfigModule, ConfigService } from '@nestjs/config';
6
+ import { WinstonLoggerService } from './winston.service';
7
+ import { ElasticsearchTransport } from 'winston-elasticsearch';
8
+ import DatadogWinston = require('datadog-winston');
9
+
10
+ const myFormat = format.printf(({ level = 'info', message, timestamp, err, ...metadata }) => {
11
+ return `${timestamp} [${level}]: ${message} ${err ? err.stack : ''} ${JSON.stringify(metadata)} `;
12
+ });
13
+
14
+ @Global()
15
+ @Module({})
16
+ export class WinstonModule {
17
+ static forRoot(): DynamicModule {
18
+ const winstonProvider = {
19
+ provide: 'WINSTON_LOGGER',
20
+ useFactory: (configService: ConfigService): Logger => {
21
+ const transportType = configService.getOrThrow<string>('LOG_TRANSPORTS');
22
+ // Split the string by commas and check if at least one entry exists.
23
+ const transportsArray = transportType.split(',');
24
+ if (transportsArray.length === 0 || transportsArray.every((item) => item.trim() === '')) {
25
+ throw new Error('LOG_TRANSPORTS must contain at least one transport method.');
26
+ }
27
+ const selectedTransport = [];
28
+ for (const iterator of transportsArray) {
29
+ if (iterator === 'elasticsearch') {
30
+ selectedTransport.push(
31
+ new ElasticsearchTransport({
32
+ clientOpts: {
33
+ node: configService.getOrThrow<string>('ELASTICSEARCH_sURL'),
34
+ },
35
+ }),
36
+ );
37
+ }
38
+ if (iterator === 'datadog') {
39
+ selectedTransport.push(
40
+ new DatadogWinston({
41
+ apiKey: configService.getOrThrow<string>('DATADOG_APIKEY'),
42
+ hostname: configService.getOrThrow<string>('DATADOG_HOSTNAME'),
43
+ service: configService.getOrThrow<string>('DATADOG_SERVICE'), // 'service',
44
+ ddsource: configService.getOrThrow<string>('DATADOG_DDSOURCE'), // 'nodejs',
45
+ ddtags: configService.getOrThrow<string>('DATADOG_DDTAGS'), //'foo:bar,boo:baz',
46
+ intakeRegion: configService.getOrThrow<string>('DATADOG_INTAKEREGION'), // 'eu'
47
+ }),
48
+ );
49
+ }
50
+ if (iterator === 'file') {
51
+ selectedTransport.push(
52
+ new transports.File({
53
+ filename: 'logs/error.log',
54
+ level: 'error',
55
+ format: format.combine(format.timestamp(), format.json()),
56
+ }),
57
+ new transports.File({
58
+ filename: 'logs/combined.log',
59
+ format: format.combine(format.timestamp(), format.json(), myFormat),
60
+ }),
61
+ );
62
+ }
63
+ if (iterator === 'console') {
64
+ selectedTransport.push(
65
+ new transports.Console({
66
+ format: format.combine(
67
+ format.colorize(),
68
+ format.splat(),
69
+ format.errors({ stack: true }),
70
+ format.json(),
71
+ format.timestamp({
72
+ format: 'YYYY-MM-DD HH:mm:ss',
73
+ }),
74
+ myFormat,
75
+ ),
76
+ }),
77
+ );
78
+ }
79
+ }
80
+
81
+ return createLogger({
82
+ level: configService.get<string>('LOG_LEVEL') || 'info',
83
+ defaultMeta: {
84
+ service: configService.getOrThrow<string>('NAME_SERVICE'),
85
+ },
86
+ transports: selectedTransport,
87
+ });
88
+ },
89
+ inject: [ConfigService],
90
+ };
91
+
92
+ const loggerServiceProvider = {
93
+ provide: WinstonLoggerService,
94
+ useFactory: (logger: Logger) => new WinstonLoggerService(logger),
95
+ inject: ['WINSTON_LOGGER'],
96
+ };
97
+
98
+ return {
99
+ module: WinstonModule,
100
+ imports: [ConfigModule],
101
+ providers: [winstonProvider, loggerServiceProvider],
102
+ exports: [winstonProvider, loggerServiceProvider],
103
+ };
104
+ }
105
+ }
@@ -0,0 +1,60 @@
1
+ // winston-logger.service.ts
2
+ import { Logger } from 'winston';
3
+ import { IWinstonLogger } from './winston.interface';
4
+ import { TraceLog } from './winston.decorator';
5
+
6
+ export interface Meta {
7
+ traceId: string; // Ensures traceId is always present
8
+ className?: string;
9
+ methodName?: string;
10
+ [key: string]: any; // Allows for any number of additional properties
11
+ }
12
+ export class WinstonLoggerService implements IWinstonLogger {
13
+ constructor(private readonly logger: Logger) {}
14
+
15
+ @TraceLog()
16
+ info(message: string, meta: Meta): void {
17
+ this.logger.info(message, meta);
18
+ }
19
+ @TraceLog()
20
+ warn(message: string, meta: Meta): void {
21
+ this.logger.warn(message, meta);
22
+ }
23
+ @TraceLog()
24
+ help(message: string, meta: Meta): void {
25
+ this.logger.help(message, meta);
26
+ }
27
+ @TraceLog()
28
+ data(message: string, meta: Meta): void {
29
+ this.logger.data(message, meta);
30
+ }
31
+ @TraceLog()
32
+ debug(message: string, meta: Meta): void {
33
+ this.logger.debug(message, meta);
34
+ }
35
+ prompt(message: string, meta: Meta): void {
36
+ this.logger.prompt(message, meta);
37
+ }
38
+ @TraceLog()
39
+ http(message: string, meta: Meta): void {
40
+ this.logger.http(message, meta);
41
+ }
42
+ @TraceLog()
43
+ verbose(message: string, meta: Meta): void {
44
+ this.logger.verbose(message, meta);
45
+ }
46
+ @TraceLog()
47
+ input(message: string, meta: Meta): void {
48
+ this.logger.input(message, meta);
49
+ }
50
+ @TraceLog()
51
+ silly(message: string, meta: Meta): void {
52
+ this.logger.silly(message, meta);
53
+ }
54
+ @TraceLog()
55
+ error(message: string, meta: Meta): void {
56
+ this.logger.error(message, meta);
57
+ }
58
+
59
+ // Add other methods for different logging levels as needed
60
+ }
@@ -0,0 +1,40 @@
1
+ import { Inject, Injectable, NestMiddleware } from '@nestjs/common';
2
+ import { randomUUID } from 'crypto';
3
+ import { NextFunction, Request, Response } from 'express';
4
+ export const X_REQUEST_ID_HEADER = 'X-Request-ID';
5
+ export const X_RESPONSE_ID_HEADER = 'X-Response-ID';
6
+ import { ConfigurableModuleBuilder } from '@nestjs/common';
7
+ import { RouteInfo } from '@nestjs/common/interfaces';
8
+
9
+ interface CtxLocal {
10
+ local: CtxLocalTraceID;
11
+ }
12
+ interface CtxLocalTraceID {
13
+ traceId: string;
14
+ }
15
+ type CtxLocalRequest = Request & CtxLocal;
16
+
17
+ export interface TracingModuleOptions {
18
+ routes: (string | RouteInfo)[];
19
+ excludedRoutes?: (string | RouteInfo)[];
20
+ onRequest?(uuid: string, next: NextFunction): void;
21
+ }
22
+
23
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } =
24
+ new ConfigurableModuleBuilder<TracingModuleOptions>().build();
25
+
26
+ @Injectable()
27
+ export class TracerMiddleware implements NestMiddleware {
28
+ constructor(
29
+ @Inject(MODULE_OPTIONS_TOKEN)
30
+ protected readonly options: typeof OPTIONS_TYPE,
31
+ ) {}
32
+
33
+ async use(req: CtxLocalRequest & CtxLocal, res: Response, next: NextFunction) {
34
+ const { onRequest = (_, next) => next() } = this.options;
35
+ const uuid = req.header(X_REQUEST_ID_HEADER) ?? randomUUID();
36
+ req.local = { traceId: uuid };
37
+ res.setHeader(X_RESPONSE_ID_HEADER, uuid);
38
+ onRequest(uuid, next);
39
+ }
40
+ }
@@ -0,0 +1,18 @@
1
+ import { Inject, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
2
+ import { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, TracerMiddleware } from './tracer.middleware';
3
+
4
+ @Module({})
5
+ export class TracerModule extends ConfigurableModuleClass implements NestModule {
6
+ constructor(
7
+ @Inject(MODULE_OPTIONS_TOKEN)
8
+ protected readonly options: typeof OPTIONS_TYPE,
9
+ ) {
10
+ super();
11
+ }
12
+
13
+ configure(consumer: MiddlewareConsumer) {
14
+ const config = consumer.apply(TracerMiddleware);
15
+ if (this.options.excludedRoutes) config.exclude(...this.options.excludedRoutes);
16
+ config.forRoutes(...this.options.routes);
17
+ }
18
+ }
@@ -0,0 +1,9 @@
1
+ export type User = {
2
+ roles: ERoles[];
3
+ name: string;
4
+ };
5
+
6
+ export enum ERoles {
7
+ admin = 'admin',
8
+ user = 'user',
9
+ }
@@ -0,0 +1 @@
1
+ export * from './auth';
@@ -0,0 +1,9 @@
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ }
9
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "removeComments": true,
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "target": "ES2021",
10
+ "sourceMap": true,
11
+ "outDir": "./dist",
12
+ "baseUrl": "./",
13
+ "incremental": true,
14
+ "skipLibCheck": true,
15
+ "strictNullChecks": false,
16
+ "noImplicitAny": false,
17
+ "strictBindCallApply": false,
18
+ "forceConsistentCasingInFileNames": false,
19
+ "noFallthroughCasesInSwitch": false
20
+ }
21
+ }