@biorate/nestjs-tools 2.5.0 → 2.6.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.
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();
49
+ ```
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
+ }
52
68
  ```
53
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,7 +91,7 @@ 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:
@@ -125,7 +144,7 @@ Catches all exceptions and returns a structured JSON response:
125
144
  }
126
145
  ```
127
146
 
128
- 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.
129
148
 
130
149
  ```ts
131
150
  app.useGlobalFilters(new AllExceptionsFilter());
@@ -181,7 +200,7 @@ Granular control over which span attributes are written:
181
200
 
182
201
  Resolves the full route path (controller + handler) and stores it in a `WeakMap<Request, { path }>` for use by `RequestCountMiddleware` and `ResponseTimeMiddleware`.
183
202
 
184
- 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 `/`.
185
204
 
186
205
  ## Providers (Guards)
187
206
 
@@ -233,14 +252,6 @@ export class CustomRolesGuard extends RolesGuardProvider {
233
252
  }
234
253
  ```
235
254
 
236
- ### `BootstrapProvider`
237
-
238
- Convenience middleware that initializes the `@biorate/inversion` container lifecycle:
239
-
240
- ```ts
241
- app.use(BootstrapProvider.create());
242
- ```
243
-
244
255
  ## Decorators
245
256
 
246
257
  | Decorator | Composition |
@@ -260,15 +271,18 @@ In non‑debug environments, validates the origin against `ALLOWED_ORIGIN` confi
260
271
 
261
272
  ## HTTP controllers
262
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
+
263
276
  ### `MetricsController`
264
277
 
265
278
  Exposes `GET /metrics` with Prometheus metrics from the shared registry.
266
279
 
267
280
  ### `ProbeController`
268
281
 
269
- | Route | Purpose |
270
- | ------------ | ------------------ |
271
- | `GET /probe` | Liveness/readiness |
282
+ | Route | Purpose |
283
+ | ---------------------- | --------------- |
284
+ | `GET /probe/readiness` | Readiness probe |
285
+ | `GET /probe/healthz` | Healthz probe |
272
286
 
273
287
  ### `LocalesController`
274
288
 
@@ -296,14 +310,12 @@ src/app/
296
310
  └──────────┬───────────────────────────────────────────┘
297
311
  │
298
312
  ┌──────────▼───────────────────────────────────────────┐
299
- │ BootstrapProvider (DI lifecycle init) │
313
+ │ RoutesInterceptor (path resolution) │
300
314
  ├──────────────────────────────────────────────────────┤
301
315
  │ RequestCountMiddleware (counter) │
302
316
  ├──────────────────────────────────────────────────────┤
303
317
  │ ResponseTimeMiddleware (histogram + header) │
304
318
  ├──────────────────────────────────────────────────────┤
305
- │ RoutesInterceptor (path resolution) │
306
- ├──────────────────────────────────────────────────────┤
307
319
  │ TracingInterceptor (OpenTelemetry span attrs) │
308
320
  ├──────────────────────────────────────────────────────┤
309
321
  │ AllExceptionsFilter (structured error JSON) │
@@ -12,46 +12,44 @@ const common_1 = require("@nestjs/common");
12
12
  const errors_1 = require("../errors");
13
13
  let AllExceptionsFilter = class AllExceptionsFilter {
14
14
  catch(exception, host) {
15
- const type = host.getType();
16
- const method = `_${type}`;
15
+ this.type = host.getType();
16
+ this.exception = exception;
17
+ this.host = host;
18
+ const method = `_${this.type}`;
17
19
  if (!this[method])
18
- throw new errors_1.UnsupportedProtocolError(host.getType());
19
- this[method](exception, host);
20
+ throw new errors_1.UnsupportedProtocolError(this.type);
21
+ this[method]();
20
22
  }
21
- _http(exception, host) {
22
- const ctx = host.switchToHttp();
23
+ _http() {
24
+ const ctx = this.host.switchToHttp();
23
25
  const response = ctx.getResponse();
24
26
  const request = ctx.getRequest();
25
- const meta = exception?.meta;
26
- const status = exception instanceof common_1.HttpException
27
- ? exception.getStatus()
27
+ const meta = this.exception?.meta;
28
+ const status = this.exception instanceof common_1.HttpException
29
+ ? this.exception.getStatus()
28
30
  : meta?.status ?? common_1.HttpStatus.INTERNAL_SERVER_ERROR;
29
- this.log(exception);
30
- response.status(status).json({
31
+ this.log(this.exception);
32
+ response.status(status).json(this.format({
31
33
  status,
32
- code: this.code(exception),
33
- hint: this.hint(exception),
34
34
  path: request.url,
35
35
  meta: meta?.meta,
36
- });
36
+ }));
37
37
  }
38
- _ws(exception, host) {
39
- const ctx = host.switchToWs();
38
+ _ws() {
39
+ const ctx = this.host.switchToWs();
40
40
  const socket = ctx.getClient();
41
- const meta = exception?.meta;
41
+ const meta = this.exception?.meta;
42
42
  const status = meta?.status ?? common_1.HttpStatus.INTERNAL_SERVER_ERROR;
43
- this.log(exception);
43
+ this.log(this.exception);
44
44
  socket.send(JSON.stringify({
45
45
  event: 'error',
46
- data: {
46
+ data: this.format({
47
47
  status,
48
- code: this.code(exception),
49
- hint: this.hint(exception),
50
48
  meta: meta?.meta,
51
- },
49
+ }),
52
50
  }));
53
51
  }
54
- _rpc(exception, host) {
52
+ _rpc() {
55
53
  throw new errors_1.UnsupportedProtocolError('rpc');
56
54
  }
57
55
  code(exception) {
@@ -60,6 +58,13 @@ let AllExceptionsFilter = class AllExceptionsFilter {
60
58
  hint(exception) {
61
59
  return (exception?.getResponse?.()?.message ?? exception.message);
62
60
  }
61
+ format(data) {
62
+ return {
63
+ code: this.code(this.exception),
64
+ hint: this.hint(this.exception),
65
+ ...data,
66
+ };
67
+ }
63
68
  log(exception) {
64
69
  if (exception instanceof axios_1.AxiosError)
65
70
  console.error(new errors_1.AxiosRequestError(exception?.response?.data?.statusCode ?? exception.status, exception?.response?.data?.error ?? exception.code, exception?.response?.data?.message ?? exception.message).stack);
@@ -9,46 +9,44 @@ import { Catch, HttpException, HttpStatus, } from '@nestjs/common';
9
9
  import { AxiosRequestError, UnsupportedProtocolError } from '../errors.js';
10
10
  let AllExceptionsFilter = class AllExceptionsFilter {
11
11
  catch(exception, host) {
12
- const type = host.getType();
13
- const method = `_${type}`;
12
+ this.type = host.getType();
13
+ this.exception = exception;
14
+ this.host = host;
15
+ const method = `_${this.type}`;
14
16
  if (!this[method])
15
- throw new UnsupportedProtocolError(host.getType());
16
- this[method](exception, host);
17
+ throw new UnsupportedProtocolError(this.type);
18
+ this[method]();
17
19
  }
18
- _http(exception, host) {
19
- const ctx = host.switchToHttp();
20
+ _http() {
21
+ const ctx = this.host.switchToHttp();
20
22
  const response = ctx.getResponse();
21
23
  const request = ctx.getRequest();
22
- const meta = exception?.meta;
23
- const status = exception instanceof HttpException
24
- ? exception.getStatus()
24
+ const meta = this.exception?.meta;
25
+ const status = this.exception instanceof HttpException
26
+ ? this.exception.getStatus()
25
27
  : meta?.status ?? HttpStatus.INTERNAL_SERVER_ERROR;
26
- this.log(exception);
27
- response.status(status).json({
28
+ this.log(this.exception);
29
+ response.status(status).json(this.format({
28
30
  status,
29
- code: this.code(exception),
30
- hint: this.hint(exception),
31
31
  path: request.url,
32
32
  meta: meta?.meta,
33
- });
33
+ }));
34
34
  }
35
- _ws(exception, host) {
36
- const ctx = host.switchToWs();
35
+ _ws() {
36
+ const ctx = this.host.switchToWs();
37
37
  const socket = ctx.getClient();
38
- const meta = exception?.meta;
38
+ const meta = this.exception?.meta;
39
39
  const status = meta?.status ?? HttpStatus.INTERNAL_SERVER_ERROR;
40
- this.log(exception);
40
+ this.log(this.exception);
41
41
  socket.send(JSON.stringify({
42
42
  event: 'error',
43
- data: {
43
+ data: this.format({
44
44
  status,
45
- code: this.code(exception),
46
- hint: this.hint(exception),
47
45
  meta: meta?.meta,
48
- },
46
+ }),
49
47
  }));
50
48
  }
51
- _rpc(exception, host) {
49
+ _rpc() {
52
50
  throw new UnsupportedProtocolError('rpc');
53
51
  }
54
52
  code(exception) {
@@ -57,6 +55,13 @@ let AllExceptionsFilter = class AllExceptionsFilter {
57
55
  hint(exception) {
58
56
  return (exception?.getResponse?.()?.message ?? exception.message);
59
57
  }
58
+ format(data) {
59
+ return {
60
+ code: this.code(this.exception),
61
+ hint: this.hint(this.exception),
62
+ ...data,
63
+ };
64
+ }
60
65
  log(exception) {
61
66
  if (exception instanceof AxiosError)
62
67
  console.error(new AxiosRequestError(exception?.response?.data?.statusCode ?? exception.status, exception?.response?.data?.error ?? exception.code, exception?.response?.data?.message ?? exception.message).stack);
@@ -1,10 +1,17 @@
1
- import { ExceptionFilter, ArgumentsHost } from '@nestjs/common';
1
+ import { ExceptionFilter, ArgumentsHost, ContextType } from '@nestjs/common';
2
2
  export declare class AllExceptionsFilter implements ExceptionFilter {
3
+ protected type: ContextType;
4
+ protected exception: Error;
5
+ protected host: ArgumentsHost;
3
6
  catch(exception: Error, host: ArgumentsHost): void;
4
7
  private _http;
5
8
  private _ws;
6
9
  private _rpc;
7
10
  private code;
8
11
  private hint;
12
+ protected format(data: Record<string, unknown>): {
13
+ code: string;
14
+ hint: string | [string];
15
+ };
9
16
  protected log(exception: Error): void;
10
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biorate/nestjs-tools",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
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": "8c6857bfe61de7b03025b0d883c478045c14390a",
48
+ "gitHead": "ba34f02a95796fee72c9dc822e31eaa740e09b4a",
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",