@application-kit/observability 0.8.2 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @application-kit/observability
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 504f068: Вход `/http`: `resolveRequestFacts(request, { trustedHeaders })` отдаёт `{ ip, userAgent }`, не определившийся факт — `null`. `firstHeaderValue(value)` теперь в экспорте: первое значение повторяющегося заголовка без краевых пробелов, `null` у отсутствующего или пустого. `resolveClientIp` работает как раньше.
8
+
9
+ ## 0.8.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 3eb414e: Поведение не меняется — исходники проходят линт на `graphql` 17. Ошибка по-прежнему читается из `originalError`: в
14
+ `graphql` 16 из peer-диапазона `cause` у `GraphQLError` нет. Литералы видов узла в метриках GraphQL привязаны к `Kind`
15
+ без шаблонного типа: в `graphql` 17 член `Kind` уже строковый литерал.
16
+
3
17
  ## 0.8.2
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -21,7 +21,7 @@ Peer-зависимости: `@nestjs/common`, `@prometheus-io/client`; оста
21
21
  | `@application-kit/observability/health` | состав чека, пробы `/health/live` и `/health/ready`, `GET /health` | `@nestjs/terminus`, `@nestjs/typeorm`, `typeorm`, `@application-kit/typeorm`, `class-validator`, `class-transformer` |
22
22
  | `@application-kit/observability/health/graphql` | `query { health { status } }` | `/health` + `@nestjs/graphql` |
23
23
  | `@application-kit/observability/health/cli` | CLI-команда `<группа> health` | `/health` + `nestjs-console` |
24
- | `@application-kit/observability/http` | `resolveClientIp` — IP клиента за прокси | ничего |
24
+ | `@application-kit/observability/http` | `resolveClientIp`, `resolveRequestFacts`, `firstHeaderValue` — IP клиента за прокси и факты запроса | ничего |
25
25
  | `@application-kit/observability/logging` | опции nestjs-pino, request-id, support-id, служебные пути, токен логгера | `@opentelemetry/api`, `class-validator`, `class-transformer`; типы — `nestjs-pino`, `pino` |
26
26
  | `@application-kit/observability/metrics` | реестр, гистограммы HTTP и GraphQL, `GET /metrics`, хуки Fastify, сервер `/metrics` | `@nestjs/common`, `@prometheus-io/client`; типы — `fastify`, `@nestjs/platform-fastify` |
27
27
  | `@application-kit/observability/tracing` | `startTracing` — OpenTelemetry NodeSDK для `node --import`, только ESM | `@opentelemetry/sdk-node`, `@opentelemetry/instrumentation`, `@opentelemetry/instrumentation-http`, `@opentelemetry/exporter-trace-otlp-grpc`, `@opentelemetry/semantic-conventions` |
@@ -146,6 +146,18 @@ export class WebModule {}
146
146
  const clientIp = resolveClientIp(request, { trustedHeaders: ['cf-connecting-ip'] })
147
147
  ```
148
148
 
149
+ ## Факты запроса
150
+
151
+ `resolveRequestFacts(request, { trustedHeaders })` — `{ ip, userAgent }` для строки сессии или журнала: адрес берётся из
152
+ `resolveClientIp` с теми же опциями, не определившийся факт — `null`. `firstHeaderValue(value)` — значение заголовка одной
153
+ строкой: первое у повторяющегося, без краевых пробелов, `null` у отсутствующего или пустого.
154
+
155
+ Где лежит текущий запрос и что ещё считать фактами (актор, роль), функция не знает: это решает приложение.
156
+
157
+ ```ts
158
+ const facts = resolveRequestFacts(request, { trustedHeaders: ['cf-connecting-ip'] })
159
+ ```
160
+
149
161
  ## Трейсинг (`/tracing`)
150
162
 
151
163
  `startTracing({ esmHookedModules, instrumentations, resolveClientIp })` поднимает OpenTelemetry NodeSDK: OTLP gRPC-экспортёр,
@@ -0,0 +1 @@
1
+ export declare const firstHeaderValue: (value: string | string[] | undefined) => null | string;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.firstHeaderValue = void 0;
4
+ const firstHeaderValue = (value) => {
5
+ const single = Array.isArray(value) ? value[0] : value;
6
+ const trimmed = (single ?? '').trim();
7
+ return trimmed === '' ? null : trimmed;
8
+ };
9
+ exports.firstHeaderValue = firstHeaderValue;
@@ -1,2 +1,4 @@
1
1
  export * from './client-ip-options.interface.js';
2
+ export * from './first-header-value.js';
2
3
  export * from './resolve-client-ip.js';
4
+ export * from './resolve-request-facts.js';
@@ -15,4 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./client-ip-options.interface.js"), exports);
18
+ __exportStar(require("./first-header-value.js"), exports);
18
19
  __exportStar(require("./resolve-client-ip.js"), exports);
20
+ __exportStar(require("./resolve-request-facts.js"), exports);
@@ -1,13 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveClientIp = void 0;
4
- const firstHeaderValue = (value) => {
5
- const single = Array.isArray(value) ? value[0] : value;
6
- return (single ?? '').trim();
7
- };
4
+ const first_header_value_js_1 = require("./first-header-value.js");
8
5
  const trustedHeaderIp = (request, trustedHeaders) => {
9
- const values = trustedHeaders?.map((header) => firstHeaderValue(request.headers?.[header]));
10
- return values?.find((value) => value !== '') ?? '';
6
+ const values = trustedHeaders?.map((header) => (0, first_header_value_js_1.firstHeaderValue)(request.headers?.[header]));
7
+ return values?.find((value) => value !== null) ?? '';
11
8
  };
12
9
  const rawSocketIp = (request) => request.raw?.socket?.remoteAddress ?? '';
13
10
  const socketIp = (request) => request.socket?.remoteAddress ?? rawSocketIp(request);
@@ -0,0 +1,7 @@
1
+ import type { ClientIpOptionsInterface } from './client-ip-options.interface.js';
2
+ import type { ClientIpSourceInterface } from './resolve-client-ip.js';
3
+ export interface RequestFactsInterface {
4
+ ip: null | string;
5
+ userAgent: null | string;
6
+ }
7
+ export declare const resolveRequestFacts: (request: ClientIpSourceInterface, options?: ClientIpOptionsInterface) => RequestFactsInterface;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRequestFacts = void 0;
4
+ const first_header_value_js_1 = require("./first-header-value.js");
5
+ const resolve_client_ip_js_1 = require("./resolve-client-ip.js");
6
+ const USER_AGENT_HEADER = 'user-agent';
7
+ const resolveRequestFacts = (request, options = {}) => {
8
+ const ip = (0, resolve_client_ip_js_1.resolveClientIp)(request, options);
9
+ return {
10
+ ip: ip === '' ? null : ip,
11
+ userAgent: (0, first_header_value_js_1.firstHeaderValue)(request.headers?.[USER_AGENT_HEADER]),
12
+ };
13
+ };
14
+ exports.resolveRequestFacts = resolveRequestFacts;
@@ -0,0 +1 @@
1
+ export declare const firstHeaderValue: (value: string | string[] | undefined) => null | string;
@@ -0,0 +1,5 @@
1
+ export const firstHeaderValue = (value) => {
2
+ const single = Array.isArray(value) ? value[0] : value;
3
+ const trimmed = (single ?? '').trim();
4
+ return trimmed === '' ? null : trimmed;
5
+ };
@@ -1,2 +1,4 @@
1
1
  export * from './client-ip-options.interface.js';
2
+ export * from './first-header-value.js';
2
3
  export * from './resolve-client-ip.js';
4
+ export * from './resolve-request-facts.js';
@@ -1,2 +1,4 @@
1
1
  export * from './client-ip-options.interface.js';
2
+ export * from './first-header-value.js';
2
3
  export * from './resolve-client-ip.js';
4
+ export * from './resolve-request-facts.js';
@@ -1,10 +1,7 @@
1
- const firstHeaderValue = (value) => {
2
- const single = Array.isArray(value) ? value[0] : value;
3
- return (single ?? '').trim();
4
- };
1
+ import { firstHeaderValue } from './first-header-value.js';
5
2
  const trustedHeaderIp = (request, trustedHeaders) => {
6
3
  const values = trustedHeaders?.map((header) => firstHeaderValue(request.headers?.[header]));
7
- return values?.find((value) => value !== '') ?? '';
4
+ return values?.find((value) => value !== null) ?? '';
8
5
  };
9
6
  const rawSocketIp = (request) => request.raw?.socket?.remoteAddress ?? '';
10
7
  const socketIp = (request) => request.socket?.remoteAddress ?? rawSocketIp(request);
@@ -0,0 +1,7 @@
1
+ import type { ClientIpOptionsInterface } from './client-ip-options.interface.js';
2
+ import type { ClientIpSourceInterface } from './resolve-client-ip.js';
3
+ export interface RequestFactsInterface {
4
+ ip: null | string;
5
+ userAgent: null | string;
6
+ }
7
+ export declare const resolveRequestFacts: (request: ClientIpSourceInterface, options?: ClientIpOptionsInterface) => RequestFactsInterface;
@@ -0,0 +1,10 @@
1
+ import { firstHeaderValue } from './first-header-value.js';
2
+ import { resolveClientIp } from './resolve-client-ip.js';
3
+ const USER_AGENT_HEADER = 'user-agent';
4
+ export const resolveRequestFacts = (request, options = {}) => {
5
+ const ip = resolveClientIp(request, options);
6
+ return {
7
+ ip: ip === '' ? null : ip,
8
+ userAgent: firstHeaderValue(request.headers?.[USER_AGENT_HEADER]),
9
+ };
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@application-kit/observability",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Наблюдаемость NestJS-процесса: метрики Prometheus и RED-гистограммы, health-чеки и пробы, опции pino с request-id и trace-контекстом, логирование CQRS-шин, реальный IP клиента за прокси, трейсинг OpenTelemetry",
5
5
  "keywords": [
6
6
  "cqrs",