@biorate/nestjs-tools 2.4.0 → 2.5.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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Production‑grade middleware, guards, interceptors, filters, decorators, and HT
8
8
  - **Response time** — Prometheus histogram + `x-response-time` header.
9
9
  - **HTTP proxy** — Prometheus‑instrumented `http-proxy-middleware` wrapper.
10
10
  - **Tracing** — OpenTelemetry span attributes for incoming requests.
11
- - **Exception filter** — structured JSON error responses for HTTP, WS, and RPC.
11
+ - **Exception filter** — structured JSON error responses for HTTP and WebSocket transports (RPC is unsupported).
12
12
  - **Auth** — `@AuthBasic()` decorator + `AuthGuardProvider`.
13
13
  - **Roles** — `@Roles()` decorator + `RolesGuardProvider`.
14
14
  - **CORS** — config‑driven origin validation.
@@ -25,14 +25,14 @@ Peer dependencies: `@nestjs/common`, `@nestjs/core`, `@nestjs/swagger`, `class-v
25
25
 
26
26
  ## Quick start
27
27
 
28
+ `main.ts`:
29
+
28
30
  ```ts
29
31
  import { NestFactory } from '@nestjs/core';
30
32
  import { AppModule } from './app.module';
31
33
  import {
32
34
  AllExceptionsFilter,
33
- BootstrapProvider,
34
- RequestCountMiddleware,
35
- ResponseTimeMiddleware,
35
+ RoutesInterceptor,
36
36
  TracingInterceptor,
37
37
  } from '@biorate/nestjs-tools';
38
38
 
@@ -40,17 +40,38 @@ async function bootstrap() {
40
40
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
41
41
 
42
42
  app.useGlobalFilters(new AllExceptionsFilter());
43
- app.useGlobalInterceptors(new TracingInterceptor());
44
-
45
- // Middleware
46
- app.use(BootstrapProvider.create());
47
- app.use(RequestCountMiddleware.create());
48
- app.use(ResponseTimeMiddleware.create());
43
+ app.useGlobalInterceptors(new RoutesInterceptor(), new TracingInterceptor());
49
44
 
50
45
  await app.listen(3000);
51
46
  }
47
+
48
+ bootstrap();
52
49
  ```
53
50
 
51
+ `app.module.ts`:
52
+
53
+ ```ts
54
+ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
55
+ import {
56
+ RequestCountMiddleware,
57
+ ResponseTimeMiddleware,
58
+ } from '@biorate/nestjs-tools';
59
+
60
+ @Module({ imports: [...] })
61
+ export class AppModule implements NestModule {
62
+ public configure(consumer: MiddlewareConsumer) {
63
+ consumer
64
+ .apply(RequestCountMiddleware, ResponseTimeMiddleware)
65
+ .forRoutes('*');
66
+ }
67
+ }
68
+ ```
69
+
70
+ > Middleware and interceptor classes use `@inject(Types.Config)` from the global
71
+ > `@biorate/inversion` container — bootstrap the app via the `@biorate/launcher`
72
+ > lifecycle with `Types.Config` bound, otherwise requests will fail with
73
+ > `InversionInjectionIsUndefinedError`.
74
+
54
75
  ## Middlewares
55
76
 
56
77
  ### `RequestCountMiddleware`
@@ -59,8 +80,6 @@ Prometheus counter `http_server_requests_seconds_count` with labels `method`, `u
59
80
 
60
81
  ```ts
61
82
  consumer.apply(RequestCountMiddleware).forRoutes('*');
62
- // or via BootstrapProvider
63
- app.use(RequestCountMiddleware.create());
64
83
  ```
65
84
 
66
85
  Uses `RoutesInterceptor` to resolve the actual route path (e.g. `/api/users/:id` instead of `/api/users/123`).
@@ -72,17 +91,17 @@ Config option: `app.middleware.RequestCountMiddleware.log-base-url` (boolean, de
72
91
  Prometheus histogram `http_server_requests_seconds` + `x-response-time` header.
73
92
 
74
93
  ```ts
75
- app.use(ResponseTimeMiddleware.create());
94
+ consumer.apply(ResponseTimeMiddleware).forRoutes('*');
76
95
  ```
77
96
 
78
97
  Config options:
79
98
 
80
- | Path | Default |
81
- |-----------------------------------------------------|-------------|
82
- | `app.middleware.ResponseTimeMiddleware.suffix` | `true` |
83
- | `app.middleware.ResponseTimeMiddleware.header` | `x-response-time` |
84
- | `app.middleware.ResponseTimeMiddleware.digits` | `3` |
85
- | `app.middleware.ResponseTimeMiddleware.log-base-url` | `false` |
99
+ | Path | Default |
100
+ | ---------------------------------------------------- | ----------------------- |
101
+ | `app.middleware.ResponseTimeMiddleware.suffix` | `true` |
102
+ | `app.middleware.ResponseTimeMiddleware.header` | `x-response-time` |
103
+ | `app.middleware.ResponseTimeMiddleware.digits` | `3` |
104
+ | `app.middleware.ResponseTimeMiddleware.log-base-url` | `false` |
86
105
  | `ResponseTimeMiddleware.histogram.buckets` | `[0.005, 0.01, … , 10]` |
87
106
 
88
107
  ### `ProxyPrometheusMiddleware`
@@ -92,13 +111,18 @@ Prometheus‑instrumented HTTP proxy via `http-proxy-middleware`.
92
111
  ```ts
93
112
  import { ProxyPrometheusMiddleware } from '@biorate/nestjs-tools';
94
113
 
95
- consumer.apply(ProxyPrometheusMiddleware.create({
96
- target: 'http://upstream.service:8080',
97
- changeOrigin: true,
98
- })).forRoutes('/api/v2/legacy');
114
+ consumer
115
+ .apply(
116
+ ProxyPrometheusMiddleware.create({
117
+ target: 'http://upstream.service:8080',
118
+ changeOrigin: true,
119
+ }),
120
+ )
121
+ .forRoutes('/api/v2/legacy');
99
122
  ```
100
123
 
101
124
  Creates two metrics:
125
+
102
126
  - `http_proxy_server_requests_seconds_count` (counter)
103
127
  - `http_proxy_server_requests_seconds` (histogram)
104
128
 
@@ -120,7 +144,7 @@ Catches all exceptions and returns a structured JSON response:
120
144
  }
121
145
  ```
122
146
 
123
- Supports HTTP, WebSocket, and RPC transport types. Maps `meta.status` from `@biorate/errors` to HTTP status codes.
147
+ Supports HTTP and WebSocket transports; RPC is unsupported and throws `UnsupportedProtocolError`. Maps `meta.status` from `@biorate/errors` to HTTP status codes.
124
148
 
125
149
  ```ts
126
150
  app.useGlobalFilters(new AllExceptionsFilter());
@@ -145,13 +169,38 @@ incoming.response.data
145
169
  incoming.response.errorCode
146
170
  ```
147
171
 
148
- Config: `TracingInterceptor.excluded` — array of strings or RegExp to skip URLs.
172
+ Config: `TracingInterceptor.excluded` — array of strings or RegExp to skip URLs
173
+ (string entries match by substring inclusion, RegExp entries by test).
174
+
175
+ #### Aspect exclusion
176
+
177
+ Granular control over which span attributes are written:
178
+
179
+ ```json
180
+ {
181
+ "TracingInterceptor": {
182
+ "excluded": ["/health"],
183
+ "excludedAttributes": ["response.data"],
184
+ "excludedByURL": [{ "url": "/files", "attributes": ["body", "response"] }]
185
+ }
186
+ }
187
+ ```
188
+
189
+ - `TracingInterceptor.excludedAttributes` — string[] of aspects to skip globally.
190
+ - `TracingInterceptor.excludedByURL` — array of `{ url: string | RegExp, attributes: string[] }`
191
+ entries; aspects are skipped only for matching URLs (string entries match by
192
+ substring inclusion, RegExp entries by test).
193
+ - An excluded item also matches aspect groups by prefix: `response` excludes
194
+ `response.headers`, `response.statusCode`, `response.data` and `response.errorCode`.
195
+ - Accepted aliases: `url`, `method`, `headers`, `body`, `params`, `query`
196
+ (→ `incoming.request.*`), `statusCode`, `data`, `errorCode` (→ `incoming.response.*`);
197
+ full names (`request.body`, `response.data`, …) work as-is.
149
198
 
150
199
  ### `RoutesInterceptor`
151
200
 
152
201
  Resolves the full route path (controller + handler) and stores it in a `WeakMap<Request, { path }>` for use by `RequestCountMiddleware` and `ResponseTimeMiddleware`.
153
202
 
154
- Applied automatically when using the count/time middlewares.
203
+ Not applied automatically: when using the count/time middlewares, register it globally via `app.useGlobalInterceptors(new RoutesInterceptor())` — otherwise they won't see the resolved route path and metrics fall back to `/`.
155
204
 
156
205
  ## Providers (Guards)
157
206
 
@@ -164,7 +213,9 @@ HTTP Basic Authentication guard. Validates `Authorization: Basic …` against co
164
213
  export class MyController {
165
214
  @Get('/admin')
166
215
  @AuthBasic()
167
- public admin() { return 'secret data'; }
216
+ public admin() {
217
+ return 'secret data';
218
+ }
168
219
  }
169
220
  ```
170
221
 
@@ -173,13 +224,22 @@ export class MyController {
173
224
  Bitmask‑based role guard. Use with `@Roles()` decorator:
174
225
 
175
226
  ```ts
227
+ import { Controller, Get } from '@nestjs/common';
176
228
  import { Roles, UserRoles } from '@biorate/nestjs-tools';
177
229
 
178
- @Roles(UserRoles.Admin | UserRoles.Manager)
179
- @Get('/admin')
180
- public admin() { return 'ok'; }
230
+ @Controller()
231
+ export class AdminController {
232
+ @Roles(UserRoles.Admin, UserRoles.Basic)
233
+ @Get('/admin')
234
+ public admin() {
235
+ return 'ok';
236
+ }
237
+ }
181
238
  ```
182
239
 
240
+ `Roles(...)` accepts a variadic list of roles (combined into a bitmask via
241
+ bitwise OR internally). Built-in `UserRoles`: `Basic` (`1 << 0`), `Admin` (`1 << 1`).
242
+
183
243
  Override `getUser()` to load the user from your database:
184
244
 
185
245
  ```ts
@@ -192,20 +252,12 @@ export class CustomRolesGuard extends RolesGuardProvider {
192
252
  }
193
253
  ```
194
254
 
195
- ### `BootstrapProvider`
196
-
197
- Convenience middleware that initializes the `@biorate/inversion` container lifecycle:
198
-
199
- ```ts
200
- app.use(BootstrapProvider.create());
201
- ```
202
-
203
255
  ## Decorators
204
256
 
205
- | Decorator | Composition |
206
- |---------------|--------------------------------------------------|
207
- | `@Roles(...)` | `SetMetadata(ROLES_KEY, bitmask)` |
208
- | `@AuthBasic()`| `ApiBasicAuth()` + `UseGuards(AuthGuardProvider)` |
257
+ | Decorator | Composition |
258
+ | -------------- | ------------------------------------------------- |
259
+ | `@Roles(...)` | `SetMetadata(ROLES_KEY, bitmask)` |
260
+ | `@AuthBasic()` | `ApiBasicAuth()` + `UseGuards(AuthGuardProvider)` |
209
261
 
210
262
  ## CORS
211
263
 
@@ -219,15 +271,18 @@ In non‑debug environments, validates the origin against `ALLOWED_ORIGIN` confi
219
271
 
220
272
  ## HTTP controllers
221
273
 
274
+ > The `MetricsController`, `ProbeController`, and `LocalesController` in `src/http/controllers` are marked `@deprecated`. Their hexagonal replacements live in `src/app/infrastructure/controllers/` and are **not** exported individually at the package top level — `src/app/infrastructure/index.ts` re-exports them only as the `controllers` namespace (`export * as controllers from './controllers'`), so reach them as `import { controllers } from '@biorate/nestjs-tools'` (e.g. `controllers.MetricsController`).
275
+
222
276
  ### `MetricsController`
223
277
 
224
278
  Exposes `GET /metrics` with Prometheus metrics from the shared registry.
225
279
 
226
280
  ### `ProbeController`
227
281
 
228
- | Route | Purpose |
229
- |-----------------|---------------------|
230
- | `GET /probe` | Liveness/readiness |
282
+ | Route | Purpose |
283
+ | ---------------------- | --------------- |
284
+ | `GET /probe/readiness` | Readiness probe |
285
+ | `GET /probe/healthz` | Healthz probe |
231
286
 
232
287
  ### `LocalesController`
233
288
 
@@ -255,14 +310,12 @@ src/app/
255
310
  └──────────┬───────────────────────────────────────────┘
256
311
  │
257
312
  ┌──────────▼───────────────────────────────────────────┐
258
- │ BootstrapProvider (DI lifecycle init) │
313
+ │ RoutesInterceptor (path resolution) │
259
314
  ├──────────────────────────────────────────────────────┤
260
315
  │ RequestCountMiddleware (counter) │
261
316
  ├──────────────────────────────────────────────────────┤
262
317
  │ ResponseTimeMiddleware (histogram + header) │
263
318
  ├──────────────────────────────────────────────────────┤
264
- │ RoutesInterceptor (path resolution) │
265
- ├──────────────────────────────────────────────────────┤
266
319
  │ TracingInterceptor (OpenTelemetry span attrs) │
267
320
  ├──────────────────────────────────────────────────────┤
268
321
  │ AllExceptionsFilter (structured error JSON) │
@@ -11,6 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
+ var TracingInterceptor_1;
14
15
  Object.defineProperty(exports, "__esModule", { value: true });
15
16
  exports.TracingInterceptor = void 0;
16
17
  const json_stringify_safe_1 = __importDefault(require("json-stringify-safe"));
@@ -18,44 +19,78 @@ const rxjs_1 = require("rxjs");
18
19
  const opentelemetry_1 = require("@biorate/opentelemetry");
19
20
  const common_1 = require("@nestjs/common");
20
21
  const inversion_1 = require("@biorate/inversion");
21
- let TracingInterceptor = class TracingInterceptor {
22
+ const lodash_es_1 = require("lodash-es");
23
+ let TracingInterceptor = TracingInterceptor_1 = class TracingInterceptor {
24
+ static normalizeAspect(name) {
25
+ const aliased = (0, lodash_es_1.get)(TracingInterceptor_1.aspectAliases, name);
26
+ if (aliased)
27
+ return aliased;
28
+ if (name.startsWith('request.'))
29
+ return name.slice('request.'.length);
30
+ return name;
31
+ }
22
32
  stringify(data) {
23
33
  return typeof data === 'object' ? (0, json_stringify_safe_1.default)(data) : String(data);
24
34
  }
25
35
  excluded(url) {
26
36
  const excluded = this.config.get('TracingInterceptor.excluded', []);
27
37
  for (const item of excluded) {
28
- if (typeof item === 'string' && url.startsWith(item))
38
+ if (typeof item === 'string' && url.includes(item))
29
39
  return true;
30
40
  if (item instanceof RegExp && item.test(url))
31
41
  return true;
32
42
  }
33
43
  return false;
34
44
  }
45
+ excludedAttribute(url, aspect) {
46
+ const canonical = TracingInterceptor_1.normalizeAspect(aspect);
47
+ const match = (list) => list.some((item) => {
48
+ const target = TracingInterceptor_1.normalizeAspect(item);
49
+ return canonical === target || canonical.startsWith(`${target}.`);
50
+ });
51
+ if (match(this.config.get('TracingInterceptor.excludedAttributes', [])))
52
+ return true;
53
+ const byUrl = this.config.get('TracingInterceptor.excludedByURL', []);
54
+ for (const entry of byUrl) {
55
+ const hit = typeof entry?.url === 'string'
56
+ ? url.includes(entry.url)
57
+ : entry?.url instanceof RegExp && entry.url.test(url);
58
+ if (!hit)
59
+ continue;
60
+ if (match(entry?.attributes ?? []))
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ attr(span, url, aspect, value) {
66
+ if (this.excludedAttribute(url, aspect))
67
+ return;
68
+ span.setAttribute(TracingInterceptor_1.aspectKeys[aspect], this.stringify(value));
69
+ }
35
70
  http(span, context, next) {
36
71
  const host = context.switchToHttp();
37
72
  const req = host.getRequest();
38
73
  const res = host.getResponse();
39
74
  if (this.excluded(req.url))
40
75
  return next.handle();
41
- span.setAttribute('incoming.request.url', this.stringify(req.url));
42
- span.setAttribute('incoming.request.body', this.stringify(req.body));
43
- span.setAttribute('incoming.request.headers', this.stringify(req.headers));
44
- span.setAttribute('incoming.request.method', this.stringify(req.method));
45
- span.setAttribute('incoming.request.params', this.stringify(req.params));
46
- span.setAttribute('incoming.request.query', this.stringify(req.query));
76
+ this.attr(span, req.url, 'url', req.url);
77
+ this.attr(span, req.url, 'body', req.body);
78
+ this.attr(span, req.url, 'headers', req.headers);
79
+ this.attr(span, req.url, 'method', req.method);
80
+ this.attr(span, req.url, 'params', req.params);
81
+ this.attr(span, req.url, 'query', req.query);
47
82
  return next.handle().pipe((0, rxjs_1.catchError)((e) => {
48
- span.setAttribute('incoming.response.headers', this.stringify(res.headers));
49
- span.setAttribute('incoming.response.statusCode', this.stringify(res.statusCode));
50
- span.setAttribute('incoming.response.errorCode', this.stringify(e.code));
51
- span.setAttribute('incoming.response.data', this.stringify(e?.response?.data));
83
+ this.attr(span, req.url, 'response.headers', res.headers);
84
+ this.attr(span, req.url, 'response.statusCode', res.statusCode);
85
+ this.attr(span, req.url, 'response.errorCode', e.code);
86
+ this.attr(span, req.url, 'response.data', e?.response?.data);
52
87
  span.recordException(e);
53
88
  span.setStatus({ code: opentelemetry_1.SpanStatusCode.ERROR });
54
89
  throw e;
55
90
  }), (0, rxjs_1.tap)((data) => {
56
- span.setAttribute('incoming.response.headers', this.stringify(res.headers));
57
- span.setAttribute('incoming.response.statusCode', this.stringify(res.statusCode));
58
- span.setAttribute('incoming.response.data', this.stringify(data));
91
+ this.attr(span, req.url, 'response.headers', res.headers);
92
+ this.attr(span, req.url, 'response.statusCode', res.statusCode);
93
+ this.attr(span, req.url, 'response.data', data);
59
94
  }));
60
95
  }
61
96
  ws(span, context, next) {
@@ -73,10 +108,33 @@ let TracingInterceptor = class TracingInterceptor {
73
108
  }
74
109
  };
75
110
  exports.TracingInterceptor = TracingInterceptor;
111
+ TracingInterceptor.aspectAliases = {
112
+ 'request.url': 'url',
113
+ 'request.method': 'method',
114
+ 'request.headers': 'headers',
115
+ 'request.body': 'body',
116
+ 'request.params': 'params',
117
+ 'request.query': 'query',
118
+ statusCode: 'response.statusCode',
119
+ data: 'response.data',
120
+ errorCode: 'response.errorCode',
121
+ };
122
+ TracingInterceptor.aspectKeys = {
123
+ url: 'incoming.request.url',
124
+ method: 'incoming.request.method',
125
+ headers: 'incoming.request.headers',
126
+ body: 'incoming.request.body',
127
+ params: 'incoming.request.params',
128
+ query: 'incoming.request.query',
129
+ 'response.headers': 'incoming.response.headers',
130
+ 'response.statusCode': 'incoming.response.statusCode',
131
+ 'response.data': 'incoming.response.data',
132
+ 'response.errorCode': 'incoming.response.errorCode',
133
+ };
76
134
  __decorate([
77
135
  (0, inversion_1.inject)(inversion_1.Types.Config),
78
136
  __metadata("design:type", Object)
79
137
  ], TracingInterceptor.prototype, "config", void 0);
80
- exports.TracingInterceptor = TracingInterceptor = __decorate([
138
+ exports.TracingInterceptor = TracingInterceptor = TracingInterceptor_1 = __decorate([
81
139
  (0, common_1.Injectable)()
82
140
  ], TracingInterceptor);
@@ -7,49 +7,84 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
+ var TracingInterceptor_1;
10
11
  import stringify from 'json-stringify-safe';
11
12
  import { tap, catchError } from 'rxjs';
12
13
  import { trace, SpanStatusCode } from '@biorate/opentelemetry';
13
14
  import { Injectable, } from '@nestjs/common';
14
15
  import { inject, Types } from '@biorate/inversion';
15
- let TracingInterceptor = class TracingInterceptor {
16
+ import { get } from 'lodash-es';
17
+ let TracingInterceptor = TracingInterceptor_1 = class TracingInterceptor {
18
+ static normalizeAspect(name) {
19
+ const aliased = get(TracingInterceptor_1.aspectAliases, name);
20
+ if (aliased)
21
+ return aliased;
22
+ if (name.startsWith('request.'))
23
+ return name.slice('request.'.length);
24
+ return name;
25
+ }
16
26
  stringify(data) {
17
27
  return typeof data === 'object' ? stringify(data) : String(data);
18
28
  }
19
29
  excluded(url) {
20
30
  const excluded = this.config.get('TracingInterceptor.excluded', []);
21
31
  for (const item of excluded) {
22
- if (typeof item === 'string' && url.startsWith(item))
32
+ if (typeof item === 'string' && url.includes(item))
23
33
  return true;
24
34
  if (item instanceof RegExp && item.test(url))
25
35
  return true;
26
36
  }
27
37
  return false;
28
38
  }
39
+ excludedAttribute(url, aspect) {
40
+ const canonical = TracingInterceptor_1.normalizeAspect(aspect);
41
+ const match = (list) => list.some((item) => {
42
+ const target = TracingInterceptor_1.normalizeAspect(item);
43
+ return canonical === target || canonical.startsWith(`${target}.`);
44
+ });
45
+ if (match(this.config.get('TracingInterceptor.excludedAttributes', [])))
46
+ return true;
47
+ const byUrl = this.config.get('TracingInterceptor.excludedByURL', []);
48
+ for (const entry of byUrl) {
49
+ const hit = typeof entry?.url === 'string'
50
+ ? url.includes(entry.url)
51
+ : entry?.url instanceof RegExp && entry.url.test(url);
52
+ if (!hit)
53
+ continue;
54
+ if (match(entry?.attributes ?? []))
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+ attr(span, url, aspect, value) {
60
+ if (this.excludedAttribute(url, aspect))
61
+ return;
62
+ span.setAttribute(TracingInterceptor_1.aspectKeys[aspect], this.stringify(value));
63
+ }
29
64
  http(span, context, next) {
30
65
  const host = context.switchToHttp();
31
66
  const req = host.getRequest();
32
67
  const res = host.getResponse();
33
68
  if (this.excluded(req.url))
34
69
  return next.handle();
35
- span.setAttribute('incoming.request.url', this.stringify(req.url));
36
- span.setAttribute('incoming.request.body', this.stringify(req.body));
37
- span.setAttribute('incoming.request.headers', this.stringify(req.headers));
38
- span.setAttribute('incoming.request.method', this.stringify(req.method));
39
- span.setAttribute('incoming.request.params', this.stringify(req.params));
40
- span.setAttribute('incoming.request.query', this.stringify(req.query));
70
+ this.attr(span, req.url, 'url', req.url);
71
+ this.attr(span, req.url, 'body', req.body);
72
+ this.attr(span, req.url, 'headers', req.headers);
73
+ this.attr(span, req.url, 'method', req.method);
74
+ this.attr(span, req.url, 'params', req.params);
75
+ this.attr(span, req.url, 'query', req.query);
41
76
  return next.handle().pipe(catchError((e) => {
42
- span.setAttribute('incoming.response.headers', this.stringify(res.headers));
43
- span.setAttribute('incoming.response.statusCode', this.stringify(res.statusCode));
44
- span.setAttribute('incoming.response.errorCode', this.stringify(e.code));
45
- span.setAttribute('incoming.response.data', this.stringify(e?.response?.data));
77
+ this.attr(span, req.url, 'response.headers', res.headers);
78
+ this.attr(span, req.url, 'response.statusCode', res.statusCode);
79
+ this.attr(span, req.url, 'response.errorCode', e.code);
80
+ this.attr(span, req.url, 'response.data', e?.response?.data);
46
81
  span.recordException(e);
47
82
  span.setStatus({ code: SpanStatusCode.ERROR });
48
83
  throw e;
49
84
  }), tap((data) => {
50
- span.setAttribute('incoming.response.headers', this.stringify(res.headers));
51
- span.setAttribute('incoming.response.statusCode', this.stringify(res.statusCode));
52
- span.setAttribute('incoming.response.data', this.stringify(data));
85
+ this.attr(span, req.url, 'response.headers', res.headers);
86
+ this.attr(span, req.url, 'response.statusCode', res.statusCode);
87
+ this.attr(span, req.url, 'response.data', data);
53
88
  }));
54
89
  }
55
90
  ws(span, context, next) {
@@ -66,11 +101,34 @@ let TracingInterceptor = class TracingInterceptor {
66
101
  return this[type](span, context, next);
67
102
  }
68
103
  };
104
+ TracingInterceptor.aspectAliases = {
105
+ 'request.url': 'url',
106
+ 'request.method': 'method',
107
+ 'request.headers': 'headers',
108
+ 'request.body': 'body',
109
+ 'request.params': 'params',
110
+ 'request.query': 'query',
111
+ statusCode: 'response.statusCode',
112
+ data: 'response.data',
113
+ errorCode: 'response.errorCode',
114
+ };
115
+ TracingInterceptor.aspectKeys = {
116
+ url: 'incoming.request.url',
117
+ method: 'incoming.request.method',
118
+ headers: 'incoming.request.headers',
119
+ body: 'incoming.request.body',
120
+ params: 'incoming.request.params',
121
+ query: 'incoming.request.query',
122
+ 'response.headers': 'incoming.response.headers',
123
+ 'response.statusCode': 'incoming.response.statusCode',
124
+ 'response.data': 'incoming.response.data',
125
+ 'response.errorCode': 'incoming.response.errorCode',
126
+ };
69
127
  __decorate([
70
128
  inject(Types.Config),
71
129
  __metadata("design:type", Object)
72
130
  ], TracingInterceptor.prototype, "config", void 0);
73
- TracingInterceptor = __decorate([
131
+ TracingInterceptor = TracingInterceptor_1 = __decorate([
74
132
  Injectable()
75
133
  ], TracingInterceptor);
76
134
  export { TracingInterceptor };
@@ -2,9 +2,14 @@ import { Span } from '@biorate/opentelemetry';
2
2
  import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
3
3
  import { IConfig } from '@biorate/config';
4
4
  export declare class TracingInterceptor implements NestInterceptor {
5
+ protected static aspectAliases: Record<string, string>;
6
+ protected static aspectKeys: Record<string, string>;
7
+ protected static normalizeAspect(name: string): string;
5
8
  protected readonly config: IConfig;
6
9
  protected stringify(data: unknown): string;
7
10
  protected excluded(url: string): boolean;
11
+ protected excludedAttribute(url: string, aspect: string): boolean;
12
+ protected attr(span: Span, url: string, aspect: string, value: unknown): void;
8
13
  protected http(span: Span, context: ExecutionContext, next: CallHandler): import("rxjs").Observable<any>;
9
14
  protected ws(span: Span, context: ExecutionContext, next: CallHandler): import("rxjs").Observable<any>;
10
15
  protected rpc(span: Span, context: ExecutionContext, next: CallHandler): import("rxjs").Observable<any>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biorate/nestjs-tools",
3
- "version": "2.4.0",
3
+ "version": "2.5.2",
4
4
  "description": "NestJS tools",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -45,7 +45,7 @@
45
45
  "keywords": [],
46
46
  "author": "llevkin",
47
47
  "license": "MIT",
48
- "gitHead": "09ccd82d8fba90cb37750dccf41469452880b129",
48
+ "gitHead": "53c9b4fbcae2fd034c3ba129a5cbe52f4b80bff3",
49
49
  "peerDependencies": {
50
50
  "@nestjs/common": ">=10.4.15",
51
51
  "@nestjs/core": ">=10.4.15",
@@ -59,13 +59,13 @@
59
59
  "ws": ">=8.18.0"
60
60
  },
61
61
  "dependencies": {
62
- "@biorate/axios": "3.4.0",
63
- "@biorate/config": "3.2.3",
62
+ "@biorate/axios": "3.4.1",
63
+ "@biorate/config": "3.2.4",
64
64
  "@biorate/errors": "3.1.2",
65
- "@biorate/inversion": "3.1.3",
66
- "@biorate/opentelemetry": "2.4.2",
67
- "@biorate/prometheus": "3.1.3",
68
- "@biorate/tools": "3.1.2",
65
+ "@biorate/inversion": "3.1.4",
66
+ "@biorate/opentelemetry": "2.4.3",
67
+ "@biorate/prometheus": "3.1.4",
68
+ "@biorate/tools": "3.1.3",
69
69
  "http-proxy": "1.18.1",
70
70
  "http-proxy-middleware": "2.0.6",
71
71
  "json-stringify-safe": "5.0.1",