@rsdk/core 4.0.0-next.9 → 4.0.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 (136) hide show
  1. package/CHANGELOG.md +130 -40
  2. package/dist/app-metadata/app-name.validator.js.map +1 -1
  3. package/dist/config/additional-source/additional-source.initializer.js.map +1 -1
  4. package/dist/config/config-reload.indicator.js.map +1 -1
  5. package/dist/config/config.abstract.js.map +1 -1
  6. package/dist/config/config.module.js.map +1 -1
  7. package/dist/config/context/config.context.js.map +1 -1
  8. package/dist/config/metadata/config-metadata.registry.js.map +1 -1
  9. package/dist/config/metadata/decorators/config-section.decorator.js.map +1 -1
  10. package/dist/config/parsers/array.parser.js.map +1 -1
  11. package/dist/config/parsers/path/fspath.parser.js.map +1 -1
  12. package/dist/config/parsers/url/url.parser.js.map +1 -1
  13. package/dist/config/sources/base/config-source.abstract.js.map +1 -1
  14. package/dist/config/sources/base/reloadable-config-source.abstract.js.map +1 -1
  15. package/dist/config/sources/implementations/relodable-json-file.source.js.map +1 -1
  16. package/dist/config/strategy/app-name-strategy.validator.js.map +1 -1
  17. package/dist/config/types.js.map +1 -1
  18. package/dist/config/vars.class.js.map +1 -1
  19. package/dist/context.aggregator.js.map +1 -1
  20. package/dist/exceptions/base/platform-exception.absract.js.map +1 -1
  21. package/dist/exceptions/implementations/bootstrap/double-init.exception.js.map +1 -1
  22. package/dist/exceptions/implementations/bootstrap/no-init.exception.js.map +1 -1
  23. package/dist/exceptions/metadata/exceptions.registry.js.map +1 -1
  24. package/dist/exceptions.handling/global-exceptions.filter.d.ts +3 -1
  25. package/dist/exceptions.handling/global-exceptions.filter.js +10 -4
  26. package/dist/exceptions.handling/global-exceptions.filter.js.map +1 -1
  27. package/dist/exceptions.handling/types.d.ts +4 -11
  28. package/dist/health/autodoc/heath.autodoc-resolver.js.map +1 -1
  29. package/dist/health/health.service.js.map +1 -1
  30. package/dist/health/indicators.abstract/fs-access.indicator.js.map +1 -1
  31. package/dist/index.d.ts +7 -1
  32. package/dist/index.js +22 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/logging/logging.config.js +1 -2
  35. package/dist/logging/logging.config.js.map +1 -1
  36. package/dist/metrics/metadata/autodoc/metrics.autodoc-resolver.js.map +1 -1
  37. package/dist/metrics/metrics.module.js.map +1 -1
  38. package/dist/platform.context.js.map +1 -1
  39. package/dist/platform.module.js +0 -1
  40. package/dist/platform.module.js.map +1 -1
  41. package/dist/plugin/plugin.module.js.map +1 -1
  42. package/dist/rsdk-metadata/config-metadata.extractor.js.map +1 -1
  43. package/dist/tracing/active-span.module.d.ts +5 -0
  44. package/dist/tracing/active-span.module.js +25 -0
  45. package/dist/tracing/active-span.module.js.map +1 -0
  46. package/dist/tracing/constants.d.ts +7 -1
  47. package/dist/tracing/constants.js +8 -2
  48. package/dist/tracing/constants.js.map +1 -1
  49. package/dist/tracing/decorators/span.decorator.d.ts +2 -2
  50. package/dist/tracing/decorators/span.decorator.js +14 -2
  51. package/dist/tracing/decorators/span.decorator.js.map +1 -1
  52. package/dist/tracing/index.d.ts +7 -0
  53. package/dist/tracing/index.js +7 -0
  54. package/dist/tracing/index.js.map +1 -1
  55. package/dist/tracing/request-metadata.module.d.ts +5 -0
  56. package/dist/tracing/request-metadata.module.js +25 -0
  57. package/dist/tracing/request-metadata.module.js.map +1 -0
  58. package/dist/tracing/services/active-span.storage.d.ts +17 -0
  59. package/dist/tracing/services/active-span.storage.js +46 -0
  60. package/dist/tracing/services/active-span.storage.js.map +1 -0
  61. package/dist/tracing/services/index.d.ts +0 -1
  62. package/dist/tracing/services/index.js +0 -1
  63. package/dist/tracing/services/index.js.map +1 -1
  64. package/dist/tracing/services/instrumentation.service.d.ts +2 -8
  65. package/dist/tracing/services/instrumentation.service.js +9 -114
  66. package/dist/tracing/services/instrumentation.service.js.map +1 -1
  67. package/dist/tracing/services/request-metadata.injector.d.ts +6 -0
  68. package/dist/tracing/services/request-metadata.injector.js +122 -0
  69. package/dist/tracing/services/request-metadata.injector.js.map +1 -0
  70. package/dist/tracing/services/request-metadata.storage.d.ts +32 -0
  71. package/dist/tracing/services/request-metadata.storage.js +64 -0
  72. package/dist/tracing/services/request-metadata.storage.js.map +1 -0
  73. package/dist/tracing/services/trace.injector.d.ts +1 -1
  74. package/dist/tracing/services/trace.injector.js +213 -18
  75. package/dist/tracing/services/trace.injector.js.map +1 -1
  76. package/dist/tracing/tracing.interceptor.d.ts +9 -0
  77. package/dist/tracing/tracing.interceptor.js +24 -0
  78. package/dist/tracing/tracing.interceptor.js.map +1 -0
  79. package/dist/tracing/tracing.module.d.ts +1 -1
  80. package/dist/tracing/tracing.module.js +15 -16
  81. package/dist/tracing/tracing.module.js.map +1 -1
  82. package/dist/tracing/utils/create-span.d.ts +10 -0
  83. package/dist/tracing/utils/create-span.js +20 -0
  84. package/dist/tracing/utils/create-span.js.map +1 -0
  85. package/dist/tracing/utils/save-async-hooks-context.d.ts +19 -0
  86. package/dist/tracing/utils/save-async-hooks-context.js +57 -0
  87. package/dist/tracing/utils/save-async-hooks-context.js.map +1 -0
  88. package/dist/transport/get-transport-id.d.ts +5 -0
  89. package/dist/transport/get-transport-id.js +14 -0
  90. package/dist/transport/get-transport-id.js.map +1 -0
  91. package/dist/transport/protocol.detector.d.ts +7 -0
  92. package/dist/transport/protocol.detector.js +41 -0
  93. package/dist/transport/protocol.detector.js.map +1 -0
  94. package/dist/transport/transport.module.js +9 -0
  95. package/dist/transport/transport.module.js.map +1 -1
  96. package/dist/types/index.d.ts +1 -0
  97. package/dist/types/index.js +4 -0
  98. package/dist/types/index.js.map +1 -1
  99. package/dist/types/metadata.js.map +1 -1
  100. package/dist/types/transports.d.ts +12 -0
  101. package/dist/types/transports.js.map +1 -1
  102. package/dist/unhandled-rejection.handler.js.map +1 -1
  103. package/package.json +12 -12
  104. package/src/config/reload/config-reload.events.ts +9 -0
  105. package/src/config/sources/base/config-source.abstract.ts +2 -0
  106. package/src/config/types.ts +3 -0
  107. package/src/exceptions.handling/global-exceptions.filter.ts +6 -2
  108. package/src/exceptions.handling/types.ts +4 -11
  109. package/src/index.ts +25 -1
  110. package/src/logging/logging.config.ts +1 -2
  111. package/src/platform.module.ts +1 -2
  112. package/src/tracing/active-span.module.ts +13 -0
  113. package/src/tracing/constants.ts +8 -1
  114. package/src/tracing/decorators/span.decorator.ts +19 -6
  115. package/src/tracing/index.ts +7 -0
  116. package/src/tracing/request-metadata.module.ts +13 -0
  117. package/src/tracing/services/active-span.storage.ts +32 -0
  118. package/src/tracing/services/index.ts +0 -1
  119. package/src/tracing/services/instrumentation.service.ts +16 -130
  120. package/src/tracing/services/request-metadata.injector.ts +153 -0
  121. package/src/tracing/services/request-metadata.storage.ts +69 -0
  122. package/src/tracing/services/trace.injector.ts +268 -19
  123. package/src/tracing/tracing.interceptor.ts +18 -0
  124. package/src/tracing/tracing.module.ts +14 -14
  125. package/src/tracing/utils/create-span.ts +20 -0
  126. package/src/tracing/utils/save-async-hooks-context.ts +61 -0
  127. package/src/transport/get-transport-id.ts +20 -0
  128. package/src/transport/protocol.detector.ts +38 -0
  129. package/src/transport/transport.module.ts +10 -0
  130. package/src/types/index.ts +2 -0
  131. package/src/types/transports.ts +15 -0
  132. package/tsconfig.json +12 -2
  133. package/dist/tracing/services/metadata.scanner.d.ts +0 -11
  134. package/dist/tracing/services/metadata.scanner.js +0 -50
  135. package/dist/tracing/services/metadata.scanner.js.map +0 -1
  136. package/src/tracing/services/metadata.scanner.ts +0 -40
@@ -1,145 +1,31 @@
1
1
  import { Injectable } from '@nestjs/common';
2
- import {
3
- EXCEPTION_FILTERS_METADATA,
4
- GUARDS_METADATA,
5
- INTERCEPTORS_METADATA,
6
- PIPES_METADATA,
7
- } from '@nestjs/common/constants';
8
- import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
9
- import { ILogger } from '@rsdk/logging';
10
2
 
11
- import { InjectLogger } from '../../logging';
3
+ import { TracingInterceptor } from '../tracing.interceptor';
12
4
 
13
- import { ExtendedMetadataScanner } from './metadata.scanner';
5
+ import { RequestMetadataInjector } from './request-metadata.injector';
14
6
  import { TraceInjector } from './trace.injector';
15
7
 
16
- const types = [
17
- EXCEPTION_FILTERS_METADATA,
18
- GUARDS_METADATA,
19
- INTERCEPTORS_METADATA,
20
- PIPES_METADATA,
21
- ] as const;
22
-
23
8
  @Injectable()
24
9
  export class InstrumentationService {
25
- constructor(
26
- @InjectLogger(InstrumentationService) private readonly logger: ILogger,
27
- protected readonly scanner: ExtendedMetadataScanner,
28
- ) {}
29
-
30
- public inject(): void {
31
- /**
32
- * Для каждого контроллера
33
- */
34
- for (const controller of this.scanner.getControllers()) {
35
- /**
36
- * Перебираем методы контроллера и оборачиваем их.
37
- */
38
- this.wrapMethods(controller.metatype.prototype);
39
-
40
- this.wrapAugmentations(controller.metatype);
41
-
42
- /**
43
- * Для каждого метода контроллера
44
- */
45
- for (const name of this.scanner.getMethods(
46
- controller.metatype.prototype,
47
- )) {
48
- /**
49
- * Во вложенном цикле находим все гарды, интерцепторы, пайпы и фильтры,
50
- * привязанные к методу.
51
- * Уровни вложенности, как в цикле выше
52
- */
53
- const method = controller.metatype.prototype[name];
54
-
55
- this.wrapAugmentations(method);
56
- }
57
- }
58
-
59
- /**
60
- * Глобальные гарды, интерцепторы, пайпы и фильтры находятся
61
- * в общем списке провайдеров по injection-токенам.
62
- */
63
- for (const { metatype, token } of this.scanner.getProviders()) {
64
- if (typeof token !== 'string') {
65
- continue;
66
- }
67
-
68
- if (
69
- token.includes(APP_GUARD) ||
70
- token.includes(APP_PIPE) ||
71
- token.includes(APP_INTERCEPTOR) ||
72
- token.includes(APP_FILTER)
73
- ) {
74
- this.wrapMethods(metatype.prototype);
75
- }
76
- }
77
-
10
+ public injectWrapRequestMetadataInjector(): void {
78
11
  /**
79
- * Глобальные гарды, интерцепторы, пайпы и фильтры находятся
80
- * в общем списке провайдеров по injection-токенам.
12
+ * Оборачиваем интерцептор в логику которая несет с собою асинк локал сторадж с requestId
81
13
  */
82
- for (const {
83
- metatype: { name, prototype },
84
- } of this.scanner.getProviders()) {
85
- if (name.toLowerCase().endsWith('module')) {
86
- continue;
87
- }
88
-
89
- if (
90
- name === 'ModuleRef' ||
91
- name === 'Reflector' ||
92
- name === 'OrphanedReferenceRegistry' ||
93
- name === 'TypeFieldsAccessor' ||
94
- name === 'TypeMapperSevice' ||
95
- name === 'TypeDefinitionsStorage' ||
96
- name === 'FileSystemHelper' ||
97
- name === 'TypeDefinitionsGenerator' ||
98
- name === 'GraphQLSchemaFactory' ||
99
- name === 'AstDefinitionNodeFactory' ||
100
- name === 'UnionDefinitionFactory' ||
101
- name === 'SubscriptionTypeFactory' ||
102
- name === 'RootTypeFactory' ||
103
- name === 'ResolveTypeFactory' ||
104
- name === 'QueryTypeFactory' ||
105
- name === 'OrphanedTypesFactory' ||
106
- name === 'OutputTypeFactory' ||
107
- name === 'ObjectTypeDefinitionFactory' ||
108
- name === 'MutationTypeFactory' ||
109
- name === 'InterfaceDefinitionFactory' ||
110
- name === 'InputTypeFactory' ||
111
- name === 'ArgsFactory' ||
112
- name === 'InputTypeDefinitionFactory' ||
113
- name === 'EnumDefinitionFactory'
114
- ) {
115
- continue;
116
- }
117
-
118
- this.wrapMethods(prototype);
119
- }
14
+ RequestMetadataInjector.wrap(
15
+ TracingInterceptor.prototype,
16
+ TracingInterceptor.prototype['intercept'],
17
+ );
120
18
  }
121
19
 
122
- private wrapAugmentations(metatype: object): void {
20
+ public injectWrapTraceInjector(): void {
123
21
  /**
124
- * Перебираем во вложенных циклах все гарды, интерцепторы, пайпы и фильтры,
125
- * повешенные на переданный прототип (metatype).
126
- * 1-й уровень - типы сущностей: интерцепторы, гарды, пайпы, фильтры
127
- * 2-й уровень - методы сущностей
22
+ * Оборачиваем интерцептор в логику которая несет с собою асинк локал сторадж с traceId, spanId
128
23
  */
129
- for (const metakey of types) {
130
- for (const instance of Reflect.getMetadata(metakey, metatype) || []) {
131
- this.wrapMethods(instance);
132
- }
133
- }
134
- }
135
-
136
- private wrapMethods(obj: any): void {
137
- const prototype = obj.prototype ?? obj;
138
-
139
- this.logger.trace(`Wrapping methods of ${prototype.constructor.name}...`);
140
-
141
- for (const name of this.scanner.getMethods(prototype)) {
142
- TraceInjector.wrap(prototype, prototype[name]);
143
- }
24
+ TraceInjector.wrap(
25
+ TracingInterceptor.prototype,
26
+ TracingInterceptor.prototype['intercept'],
27
+ undefined,
28
+ true,
29
+ );
144
30
  }
145
31
  }
@@ -0,0 +1,153 @@
1
+ import type { ExecutionContext } from '@nestjs/common';
2
+ import { redecorate } from '@rsdk/decorators';
3
+ import { LoggerFactory } from '@rsdk/logging';
4
+ import assert from 'node:assert';
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ import { Constants, X_REQUEST_ID } from '../constants';
8
+
9
+ import { RequestMetadataStorage } from './request-metadata.storage';
10
+
11
+ const logger = LoggerFactory.create('RequestMetadataInjector');
12
+
13
+ export class RequestMetadataInjector {
14
+ // eslint-disable-next-line @typescript-eslint/ban-types
15
+ static wrap(
16
+ cls: any,
17
+ // eslint-disable-next-line @typescript-eslint/ban-types
18
+ original: Function,
19
+ ): void {
20
+ /**
21
+ * Означает, что данный метод уже обёрнут
22
+ */
23
+ if (RequestMetadataInjector.isWrapped(original)) {
24
+ return;
25
+ }
26
+
27
+ logger.trace(`Wrapping method: ${cls.constructor.name}.${original.name}()`);
28
+
29
+ const wrapped = RequestMetadataInjector.createWrapper(original);
30
+
31
+ redecorate(original, wrapped);
32
+
33
+ cls[original.name] = wrapped;
34
+
35
+ /**
36
+ * Отмечает метод, как обёрнутый (чтобы избежать повторного оборачивания)
37
+ */
38
+ RequestMetadataInjector.setWrapped(wrapped);
39
+ }
40
+
41
+ private static createWrapper(original: any): any {
42
+ return {
43
+ [original.name](...args: any[]): any {
44
+ const executionContext: ExecutionContext = args[0];
45
+
46
+ /**
47
+ * Переменный для хранения значений
48
+ */
49
+ let requestId: string | undefined;
50
+
51
+ /**
52
+ * Переменная для хранения Request/виртуального Request
53
+ */
54
+ let req: any;
55
+
56
+ /**
57
+ * В зависимости от типа контекста запускаем парсинг переданной Metadata
58
+ */
59
+ if (executionContext.getType() === 'rpc') {
60
+ const metadata = executionContext.switchToRpc().getContext();
61
+ /**
62
+ * Если мы пришли в GRPC и у нас есть методы для работы с Metadata
63
+ * то мы конвертируем Metadata в некий виртуальный request с заголовками (заголовки выбраны как общий стандарт проброса мета информации)
64
+ */
65
+ if (metadata?.get) {
66
+ req = {
67
+ headers: {
68
+ /**
69
+ * Пробрасываем текущий ид запроса
70
+ */
71
+ ...(metadata.get(X_REQUEST_ID).length > 0
72
+ ? {
73
+ [X_REQUEST_ID]: metadata.get(X_REQUEST_ID)[0],
74
+ }
75
+ : {}),
76
+ },
77
+ };
78
+ }
79
+ }
80
+
81
+ if (executionContext.getType() === 'http') {
82
+ req = executionContext.switchToHttp().getRequest();
83
+ }
84
+
85
+ if (executionContext.getType<string>() === 'graphql') {
86
+ req = executionContext.getArgs()[2]?.req; // аналог GqlExecutionContext.create(executionContext).getContext().req
87
+
88
+ if (req.connectionInitReceived) {
89
+ /**
90
+ * При работе с сабскрипшен через веб сокет, заголовки передаются в опции подключения к сабскрипшен в переменную connectionParams
91
+ * и мы перегоняем эти данные в request заголовки
92
+ */
93
+ req.headers = {
94
+ ...req?.headers,
95
+ ...Object.entries(req?.connectionParams?.headers || {}).reduce(
96
+ (acc, [key, value]) => {
97
+ acc[key] = value;
98
+ return acc;
99
+ },
100
+ {},
101
+ ),
102
+ };
103
+ }
104
+ }
105
+
106
+ if (req?.headers) {
107
+ /**
108
+ * Мы приводим все ключи заголовков к одному **lower_case** регистру, так как у разных транспортов разные регистры в именовании ключей для заголовков
109
+ */
110
+ req.headers = {
111
+ ...Object.entries(req.headers).reduce((acc, [key, value]) => {
112
+ acc[key.toLowerCase()] = req.headers[key.toLowerCase()] || value;
113
+ return acc;
114
+ }, {}),
115
+ };
116
+
117
+ /**
118
+ * Идентификатор запроса, если не передали то генерируем рандомный
119
+ */
120
+ requestId = req.headers[X_REQUEST_ID] || randomUUID();
121
+ }
122
+
123
+ return (
124
+ RequestMetadataStorage.getInstance()?.run({ requestId }, async () => {
125
+ return original.apply(this, args);
126
+ }) ?? original.apply(this, args)
127
+ );
128
+ },
129
+ }[original.name];
130
+ }
131
+
132
+ private static isWrapped(prototype: object): boolean {
133
+ assert.ok(prototype);
134
+
135
+ return Reflect.hasMetadata(
136
+ Constants.REQUEST_METADATA_ASYNC_LOCAL_STORAGE_ACTIVE,
137
+ prototype,
138
+ );
139
+ }
140
+
141
+ private static setWrapped(prototype: object): void {
142
+ assert.ok(prototype);
143
+
144
+ // Value doesn't matter
145
+ const NOOP = 1;
146
+
147
+ Reflect.defineMetadata(
148
+ Constants.REQUEST_METADATA_ASYNC_LOCAL_STORAGE_ACTIVE,
149
+ NOOP,
150
+ prototype,
151
+ );
152
+ }
153
+ }
@@ -0,0 +1,69 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+
4
+ export type RequestMetadata = {
5
+ requestId: string | undefined;
6
+ };
7
+
8
+ /**
9
+ * Обертка вокруг `AsyncLocalStorage`
10
+ * В теории можно запустить с пустым `payload`, для того чтобы добавлять
11
+ * значения в процессе
12
+ */
13
+ @Injectable()
14
+ export class RequestMetadataStorage {
15
+ /**
16
+ * Так как этот сервис используется в разных местах, чтобы не усложнять существующий код путем проксирования модулей и сервисов
17
+ * после создания ложим инстанс в эту статик переменную
18
+ */
19
+ private static instance: RequestMetadataStorage | undefined;
20
+
21
+ private asyncLocalStorage = new AsyncLocalStorage<{
22
+ requestMetadata: RequestMetadata;
23
+ }>();
24
+
25
+ constructor() {
26
+ if (!RequestMetadataStorage.instance) {
27
+ RequestMetadataStorage.instance = this;
28
+ }
29
+ }
30
+
31
+ static getInstance(): RequestMetadataStorage | undefined {
32
+ return RequestMetadataStorage.instance;
33
+ }
34
+
35
+ /**
36
+ * Получает новое значение из контекста
37
+ */
38
+ async getRequestMetadata(): Promise<RequestMetadata> {
39
+ return (
40
+ RequestMetadataStorage.instance?.asyncLocalStorage.getStore()
41
+ ?.requestMetadata ?? {
42
+ requestId: undefined,
43
+ }
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Запускает переданную фунцию в контексте
49
+ * @template TResult
50
+ * @template TArgs
51
+ * @param context Контекст исполнения (данные)
52
+ * @param fn Функция для запуска
53
+ * @param args Аргументы функции
54
+ * @returns Результат выполнения функции
55
+ */
56
+ run<TResult, TArgs extends any[]>(
57
+ requestMetadata: RequestMetadata,
58
+ fn: (...args: TArgs) => TResult,
59
+ ...args: TArgs
60
+ ): TResult {
61
+ return (
62
+ RequestMetadataStorage?.instance?.asyncLocalStorage.run(
63
+ { requestMetadata },
64
+ fn,
65
+ ...args,
66
+ ) ?? fn(...args)
67
+ );
68
+ }
69
+ }