@macss/modular-api 0.1.0 → 0.3.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
@@ -1,131 +1,122 @@
1
- # modular_api
2
-
3
- Use-case centric toolkit for building modular APIs with Express.
4
- Define `UseCase` classes (input → validate → execute → output), connect them to HTTP routes, and get automatic Swagger/OpenAPI documentation.
5
-
6
- > TypeScript port of [modular_api](https://pub.dev/packages/modular_api) (Dart/Shelf)
7
-
8
- ---
9
-
10
- ## Quick start
11
-
12
- ```ts
13
- import { ModularApi, ModuleBuilder } from 'modular_api';
14
-
15
- // ─── Module builder (separate file in real projects) ──────────
16
- function buildGreetingsModule(m: ModuleBuilder): void {
17
- m.usecase('hello', HelloWorld.fromJson);
18
- }
19
-
20
- // ─── Server ───────────────────────────────────────────────────
21
- const api = new ModularApi({ basePath: '/api' });
22
-
23
- api.module('greetings', buildGreetingsModule);
24
-
25
- api.serve({ port: 8080 });
26
- ```
27
-
28
- ```bash
29
- curl -X POST http://localhost:8080/api/greetings/hello \
30
- -H "Content-Type: application/json" \
31
- -d '{"name":"World"}'
32
- ```
33
-
34
- ```json
35
- {"message":"Hello, World!"}
36
- ```
37
-
38
- **Docs** → `http://localhost:8080/docs`
39
- **Health** → `http://localhost:8080/health`
40
-
41
- See `example/example.ts` for the full implementation including Input, Output, UseCase with `validate()`, and the builder.
42
-
43
- ---
44
-
45
- ## Features
46
-
47
- - `UseCase<I, O>` — pure business logic, no HTTP concerns
48
- - `Input` / `Output` — DTOs with `toJson()` and `toSchema()` for automatic OpenAPI
49
- - `Output.statusCode` — custom HTTP status codes per response
50
- - `UseCaseException` — structured error handling (status code, message, error code, details)
51
- - `ModularApi` + `ModuleBuilder` — module registration and routing
52
- - `useCaseTestHandler` — unit test helper (no HTTP server needed)
53
- - `cors()` middleware — built-in CORS support
54
- - Swagger UI at `/docs` — auto-generated from registered use cases
55
- - Health check at `GET /health`
56
- - All endpoints default to `POST` (configurable per use case)
57
- - Full TypeScript declarations (`.d.ts`) included
58
-
59
- ---
60
-
61
- ## Installation
62
-
63
- ```bash
64
- npm install modular_api
65
- ```
66
-
67
- ---
68
-
69
- ## Error handling
70
-
71
- ```ts
72
- async execute() {
73
- const user = await repository.findById(this.input.userId);
74
- if (!user) {
75
- throw new UseCaseException({
76
- statusCode: 404,
77
- message: 'User not found',
78
- errorCode: 'USER_NOT_FOUND',
79
- });
80
- }
81
- this.output = new GetUserOutput(user);
82
- }
83
- ```
84
-
85
- ```json
86
- {"error": "USER_NOT_FOUND", "message": "User not found"}
87
- ```
88
-
89
- ---
90
-
91
- ## Testing
92
-
93
- ```ts
94
- import { useCaseTestHandler } from 'modular_api';
95
-
96
- const handler = useCaseTestHandler(HelloWorld.fromJson);
97
- const response = await handler({ name: 'World' });
98
-
99
- console.log(response.statusCode); // 200
100
- console.log(response.body); // { message: 'Hello, World!' }
101
- ```
102
-
103
- ---
104
-
105
- ## Architecture
106
-
107
- ```
108
- HTTP Request → ModularApi → Module → UseCase → Business Logic → Output → HTTP Response
109
- ```
110
-
111
- - **UseCase layer** — pure logic, independent of HTTP
112
- - **HTTP adapter** — turns a UseCase into an Express RequestHandler
113
- - **Middlewares** — cross-cutting concerns (CORS, logging)
114
- - **Swagger UI** — documentation served automatically
115
-
116
- ---
117
-
118
- ## Dart version
119
-
120
- This is the TypeScript port. The original Dart version is available at:
121
-
122
- - **pub.dev**: [modular_api](https://pub.dev/packages/modular_api)
123
- - **GitHub**: [macss-dev/modular_api](https://github.com/macss-dev/modular_api)
124
-
125
- Both SDKs share the same architecture and API surface at v0.1.0.
126
-
127
- ---
128
-
129
- ## License
130
-
131
- MIT © [ccisne.dev](https://ccisne.dev)
1
+ # modular_api
2
+
3
+ Use-case centric toolkit for building modular APIs with Express.
4
+ Define `UseCase` classes (input → validate → execute → output), connect them to HTTP routes, and get automatic Swagger/OpenAPI documentation.
5
+
6
+ > Also available in **Dart**: [modular_api](https://pub.dev/packages/modular_api)
7
+
8
+ ---
9
+
10
+ ## Quick start
11
+
12
+ ```ts
13
+ import { ModularApi, ModuleBuilder } from 'modular_api';
14
+
15
+ // ─── Module builder (separate file in real projects) ──────────
16
+ function buildGreetingsModule(m: ModuleBuilder): void {
17
+ m.usecase('hello', HelloWorld.fromJson);
18
+ }
19
+
20
+ // ─── Server ───────────────────────────────────────────────────
21
+ const api = new ModularApi({ basePath: '/api' });
22
+
23
+ api.module('greetings', buildGreetingsModule);
24
+
25
+ api.serve({ port: 8080 });
26
+ ```
27
+
28
+ ```bash
29
+ curl -X POST http://localhost:8080/api/greetings/hello \
30
+ -H "Content-Type: application/json" \
31
+ -d '{"name":"World"}'
32
+ ```
33
+
34
+ ```json
35
+ { "message": "Hello, World!" }
36
+ ```
37
+
38
+ **Docs** → `http://localhost:8080/docs`
39
+ **Health** → `http://localhost:8080/health`
40
+
41
+ See `example/example.ts` for the full implementation including Input, Output, UseCase with `validate()`, and the builder.
42
+
43
+ ---
44
+
45
+ ## Features
46
+
47
+ - `UseCase<I, O>` — pure business logic, no HTTP concerns
48
+ - `Input` / `Output` — DTOs with `toJson()` and `toSchema()` for automatic OpenAPI
49
+ - `Output.statusCode` — custom HTTP status codes per response
50
+ - `UseCaseException` — structured error handling (status code, message, error code, details)
51
+ - `ModularApi` + `ModuleBuilder` — module registration and routing
52
+ - `useCaseTestHandler` — unit test helper (no HTTP server needed)
53
+ - `cors()` middleware — built-in CORS support
54
+ - Swagger UI at `/docs` — auto-generated from registered use cases
55
+ - Health check at `GET /health` — [IETF Health Check Response Format](doc/health_check_guide.md)
56
+ - Prometheus metrics at `GET /metrics` [Prometheus exposition format](doc/metrics_guide.md)
57
+ - Structured JSON logging — Loki/Grafana compatible, [request-scoped with trace_id](doc/logger_guide.md)
58
+ - All endpoints default to `POST` (configurable per use case)
59
+ - Full TypeScript declarations (`.d.ts`) included
60
+
61
+ ---
62
+
63
+ ## Installation
64
+
65
+ ```bash
66
+ npm install @macss/modular-api
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Error handling
72
+
73
+ ```ts
74
+ async execute() {
75
+ const user = await repository.findById(this.input.userId);
76
+ if (!user) {
77
+ throw new UseCaseException({
78
+ statusCode: 404,
79
+ message: 'User not found',
80
+ errorCode: 'USER_NOT_FOUND',
81
+ });
82
+ }
83
+ this.output = new GetUserOutput(user);
84
+ }
85
+ ```
86
+
87
+ ```json
88
+ { "error": "USER_NOT_FOUND", "message": "User not found" }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Testing
94
+
95
+ ```ts
96
+ import { useCaseTestHandler } from 'modular_api';
97
+
98
+ const handler = useCaseTestHandler(HelloWorld.fromJson);
99
+ const response = await handler({ name: 'World' });
100
+
101
+ console.log(response.statusCode); // 200
102
+ console.log(response.body); // { message: 'Hello, World!' }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Architecture
108
+
109
+ ```
110
+ HTTP Request → ModularApi → Module → UseCase → Business Logic → Output → HTTP Response
111
+ ```
112
+
113
+ - **UseCase layer** — pure logic, independent of HTTP
114
+ - **HTTP adapter** — turns a UseCase into an Express RequestHandler
115
+ - **Middlewares** — cross-cutting concerns (CORS, logging)
116
+ - **Swagger UI** — documentation served automatically
117
+
118
+ ---
119
+
120
+ ## License
121
+
122
+ MIT © [ccisne.dev](https://ccisne.dev)
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Health status values.
3
+ *
4
+ * - `'pass'` — The component is healthy.
5
+ * - `'warn'` — The component is healthy but has a warning condition.
6
+ * - `'fail'` — The component is unhealthy.
7
+ */
8
+ export type HealthStatus = 'pass' | 'warn' | 'fail';
9
+ /** Compare two statuses and return the worse one. */
10
+ export declare function worstStatus(a: HealthStatus, b: HealthStatus): HealthStatus;
11
+ /**
12
+ * Result returned by a single {@link HealthCheck}.
13
+ */
14
+ export declare class HealthCheckResult {
15
+ readonly status: HealthStatus;
16
+ readonly responseTime?: number;
17
+ readonly output?: string;
18
+ constructor(status: HealthStatus, options?: {
19
+ responseTime?: number;
20
+ output?: string;
21
+ });
22
+ /** Return a copy with `responseTime` set. */
23
+ withResponseTime(ms: number): HealthCheckResult;
24
+ /** Serialize to the IETF JSON structure. Only includes optional fields when defined. */
25
+ toJson(): Record<string, unknown>;
26
+ }
27
+ /**
28
+ * Abstract base for custom health checks.
29
+ *
30
+ * Implementors must provide `name` and `check()`.
31
+ * Override `timeout` to change the default 5 000 ms deadline.
32
+ *
33
+ * ```ts
34
+ * class DatabaseHealthCheck extends HealthCheck {
35
+ * readonly name = 'database';
36
+ * async check() {
37
+ * await db.ping();
38
+ * return new HealthCheckResult('pass');
39
+ * }
40
+ * }
41
+ * ```
42
+ */
43
+ export declare abstract class HealthCheck {
44
+ /** Display name used as the key in the `checks` map. */
45
+ abstract readonly name: string;
46
+ /** Maximum time (ms) before the check is considered failed. Default: 5 000. */
47
+ get timeout(): number;
48
+ /** Execute the health check and return a result. */
49
+ abstract check(): Promise<HealthCheckResult>;
50
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // core/health/health_check.ts
4
+ // Health check types — IETF Health Check Response Format draft.
5
+ // Mirror of health_check.dart in Dart.
6
+ // ============================================================
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.HealthCheck = exports.HealthCheckResult = void 0;
9
+ exports.worstStatus = worstStatus;
10
+ /** Severity order for worst-status-wins aggregation. */
11
+ const SEVERITY = {
12
+ pass: 0,
13
+ warn: 1,
14
+ fail: 2,
15
+ };
16
+ /** Compare two statuses and return the worse one. */
17
+ function worstStatus(a, b) {
18
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
19
+ }
20
+ /**
21
+ * Result returned by a single {@link HealthCheck}.
22
+ */
23
+ class HealthCheckResult {
24
+ constructor(status, options) {
25
+ this.status = status;
26
+ this.responseTime = options?.responseTime;
27
+ this.output = options?.output;
28
+ }
29
+ /** Return a copy with `responseTime` set. */
30
+ withResponseTime(ms) {
31
+ return new HealthCheckResult(this.status, {
32
+ responseTime: ms,
33
+ output: this.output,
34
+ });
35
+ }
36
+ /** Serialize to the IETF JSON structure. Only includes optional fields when defined. */
37
+ toJson() {
38
+ const json = { status: this.status };
39
+ if (this.responseTime !== undefined)
40
+ json.responseTime = this.responseTime;
41
+ if (this.output !== undefined)
42
+ json.output = this.output;
43
+ return json;
44
+ }
45
+ }
46
+ exports.HealthCheckResult = HealthCheckResult;
47
+ /**
48
+ * Abstract base for custom health checks.
49
+ *
50
+ * Implementors must provide `name` and `check()`.
51
+ * Override `timeout` to change the default 5 000 ms deadline.
52
+ *
53
+ * ```ts
54
+ * class DatabaseHealthCheck extends HealthCheck {
55
+ * readonly name = 'database';
56
+ * async check() {
57
+ * await db.ping();
58
+ * return new HealthCheckResult('pass');
59
+ * }
60
+ * }
61
+ * ```
62
+ */
63
+ class HealthCheck {
64
+ /** Maximum time (ms) before the check is considered failed. Default: 5 000. */
65
+ get timeout() {
66
+ return 5000;
67
+ }
68
+ }
69
+ exports.HealthCheck = HealthCheck;
@@ -0,0 +1,9 @@
1
+ import type { RequestHandler } from 'express';
2
+ import type { HealthService } from './health_service';
3
+ /**
4
+ * Creates an Express request handler that responds to `GET /health`
5
+ * with `application/health+json` following the IETF draft.
6
+ *
7
+ * Returns 200 for pass/warn, 503 for fail.
8
+ */
9
+ export declare function healthHandler(service: HealthService): RequestHandler;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // core/health/health_handler.ts
4
+ // Express handler for GET /health — application/health+json.
5
+ // Mirror of health_handler.dart in Dart.
6
+ // ============================================================
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.healthHandler = healthHandler;
9
+ /**
10
+ * Creates an Express request handler that responds to `GET /health`
11
+ * with `application/health+json` following the IETF draft.
12
+ *
13
+ * Returns 200 for pass/warn, 503 for fail.
14
+ */
15
+ function healthHandler(service) {
16
+ return async (_req, res) => {
17
+ const response = await service.evaluate();
18
+ res
19
+ .status(response.httpStatusCode)
20
+ .set('Content-Type', 'application/health+json')
21
+ .json(response.toJson());
22
+ };
23
+ }
@@ -0,0 +1,60 @@
1
+ import { type HealthStatus, HealthCheck, HealthCheckResult } from './health_check';
2
+ /**
3
+ * Aggregated health response following the IETF Health Check Response Format.
4
+ *
5
+ * `httpStatusCode`: 200 for pass/warn, 503 for fail.
6
+ */
7
+ export declare class HealthResponse {
8
+ readonly status: HealthStatus;
9
+ readonly version: string;
10
+ readonly releaseId: string;
11
+ readonly checks: Record<string, HealthCheckResult>;
12
+ constructor(options: {
13
+ status: HealthStatus;
14
+ version: string;
15
+ releaseId: string;
16
+ checks: Record<string, HealthCheckResult>;
17
+ });
18
+ /** HTTP status code: 200 for pass/warn, 503 for fail. */
19
+ get httpStatusCode(): number;
20
+ /** Serialize to the IETF-compliant JSON structure. */
21
+ toJson(): Record<string, any>;
22
+ }
23
+ export interface HealthServiceOptions {
24
+ /** API version string (e.g. '1.0.0'). */
25
+ version: string;
26
+ /**
27
+ * Release identifier. Defaults to `version-debug`.
28
+ * Override via `process.env.RELEASE_ID`.
29
+ */
30
+ releaseId?: string;
31
+ }
32
+ /**
33
+ * Service that manages and evaluates {@link HealthCheck}s.
34
+ *
35
+ * Checks are executed in parallel with per-check timeout.
36
+ * The overall status uses worst-status-wins aggregation: fail > warn > pass.
37
+ *
38
+ * ```ts
39
+ * const service = new HealthService({ version: '1.0.0' });
40
+ * service.addHealthCheck(new DatabaseHealthCheck());
41
+ * const response = await service.evaluate();
42
+ * ```
43
+ */
44
+ export declare class HealthService {
45
+ readonly version: string;
46
+ readonly releaseId: string;
47
+ private readonly checks;
48
+ constructor(options: HealthServiceOptions);
49
+ /** Register a health check to be evaluated on each call to {@link evaluate}. */
50
+ addHealthCheck(check: HealthCheck): void;
51
+ /**
52
+ * Execute all registered checks in parallel and return an aggregated
53
+ * {@link HealthResponse}.
54
+ */
55
+ evaluate(): Promise<HealthResponse>;
56
+ /** Run a single check with timeout and timing. */
57
+ private runCheck;
58
+ /** Execute a check with its configured timeout. */
59
+ private withTimeout;
60
+ }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // core/health/health_service.ts
4
+ // HealthService — evaluates checks in parallel with timeout.
5
+ // Mirror of health_service.dart in Dart.
6
+ // ============================================================
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.HealthService = exports.HealthResponse = void 0;
9
+ const health_check_1 = require("./health_check");
10
+ /**
11
+ * Aggregated health response following the IETF Health Check Response Format.
12
+ *
13
+ * `httpStatusCode`: 200 for pass/warn, 503 for fail.
14
+ */
15
+ class HealthResponse {
16
+ constructor(options) {
17
+ this.status = options.status;
18
+ this.version = options.version;
19
+ this.releaseId = options.releaseId;
20
+ this.checks = options.checks;
21
+ }
22
+ /** HTTP status code: 200 for pass/warn, 503 for fail. */
23
+ get httpStatusCode() {
24
+ return this.status === 'fail' ? 503 : 200;
25
+ }
26
+ /** Serialize to the IETF-compliant JSON structure. */
27
+ toJson() {
28
+ const checksJson = {};
29
+ for (const [key, result] of Object.entries(this.checks)) {
30
+ checksJson[key] = result.toJson();
31
+ }
32
+ return {
33
+ status: this.status,
34
+ version: this.version,
35
+ releaseId: this.releaseId,
36
+ checks: checksJson,
37
+ };
38
+ }
39
+ }
40
+ exports.HealthResponse = HealthResponse;
41
+ /**
42
+ * Service that manages and evaluates {@link HealthCheck}s.
43
+ *
44
+ * Checks are executed in parallel with per-check timeout.
45
+ * The overall status uses worst-status-wins aggregation: fail > warn > pass.
46
+ *
47
+ * ```ts
48
+ * const service = new HealthService({ version: '1.0.0' });
49
+ * service.addHealthCheck(new DatabaseHealthCheck());
50
+ * const response = await service.evaluate();
51
+ * ```
52
+ */
53
+ class HealthService {
54
+ constructor(options) {
55
+ this.checks = [];
56
+ this.version = options.version;
57
+ this.releaseId = options.releaseId ?? process.env.RELEASE_ID ?? `${options.version}-debug`;
58
+ }
59
+ /** Register a health check to be evaluated on each call to {@link evaluate}. */
60
+ addHealthCheck(check) {
61
+ this.checks.push(check);
62
+ }
63
+ /**
64
+ * Execute all registered checks in parallel and return an aggregated
65
+ * {@link HealthResponse}.
66
+ */
67
+ async evaluate() {
68
+ if (this.checks.length === 0) {
69
+ return new HealthResponse({
70
+ status: 'pass',
71
+ version: this.version,
72
+ releaseId: this.releaseId,
73
+ checks: {},
74
+ });
75
+ }
76
+ const entries = await Promise.all(this.checks.map((c) => this.runCheck(c)));
77
+ const checks = {};
78
+ for (const [name, result] of entries) {
79
+ checks[name] = result;
80
+ }
81
+ // Worst-status-wins: fail > warn > pass
82
+ const status = Object.values(checks)
83
+ .map((r) => r.status)
84
+ .reduce(health_check_1.worstStatus);
85
+ return new HealthResponse({
86
+ status,
87
+ version: this.version,
88
+ releaseId: this.releaseId,
89
+ checks,
90
+ });
91
+ }
92
+ /** Run a single check with timeout and timing. */
93
+ async runCheck(check) {
94
+ const start = Date.now();
95
+ try {
96
+ const result = await this.withTimeout(check);
97
+ const elapsed = Date.now() - start;
98
+ return [check.name, result.withResponseTime(elapsed)];
99
+ }
100
+ catch (err) {
101
+ const elapsed = Date.now() - start;
102
+ return [
103
+ check.name,
104
+ new health_check_1.HealthCheckResult('fail', {
105
+ responseTime: elapsed,
106
+ output: String(err),
107
+ }),
108
+ ];
109
+ }
110
+ }
111
+ /** Execute a check with its configured timeout. */
112
+ withTimeout(check) {
113
+ return new Promise((resolve, reject) => {
114
+ const timer = setTimeout(() => {
115
+ resolve(new health_check_1.HealthCheckResult('fail', {
116
+ output: `Health check "${check.name}" timeout after ${check.timeout}ms`,
117
+ }));
118
+ }, check.timeout);
119
+ check
120
+ .check()
121
+ .then((result) => {
122
+ clearTimeout(timer);
123
+ resolve(result);
124
+ })
125
+ .catch((err) => {
126
+ clearTimeout(timer);
127
+ reject(err);
128
+ });
129
+ });
130
+ }
131
+ }
132
+ exports.HealthService = HealthService;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * RFC 5424 log levels in descending severity order.
3
+ *
4
+ * Filtering rule: if configured `logLevel = X`, only messages with
5
+ * `value <= X` are emitted. Higher values produce total silence.
6
+ */
7
+ export declare enum LogLevel {
8
+ emergency = 0,// system unusable
9
+ alert = 1,// immediate action required
10
+ critical = 2,// critical condition
11
+ error = 3,// operation error, 5xx
12
+ warning = 4,// abnormal condition, 4xx
13
+ notice = 5,// normal but significant
14
+ info = 6,// normal flow, 2xx/3xx
15
+ debug = 7
16
+ }
17
+ /**
18
+ * Public logger interface exposed to UseCases.
19
+ *
20
+ * Each method corresponds to an RFC 5424 severity level.
21
+ * `fields` is an optional map of structured data attached to the log entry.
22
+ */
23
+ export interface ModularLogger {
24
+ /** Request-scoped trace ID for correlation. */
25
+ readonly traceId: string;
26
+ emergency(msg: string, fields?: Record<string, unknown>): void;
27
+ alert(msg: string, fields?: Record<string, unknown>): void;
28
+ critical(msg: string, fields?: Record<string, unknown>): void;
29
+ error(msg: string, fields?: Record<string, unknown>): void;
30
+ warning(msg: string, fields?: Record<string, unknown>): void;
31
+ notice(msg: string, fields?: Record<string, unknown>): void;
32
+ info(msg: string, fields?: Record<string, unknown>): void;
33
+ debug(msg: string, fields?: Record<string, unknown>): void;
34
+ }
35
+ /** Function signature for the output sink — defaults to `console.log`. */
36
+ export type WriteFn = (line: string) => void;
37
+ /**
38
+ * Per-request logger that carries `traceId` and respects `logLevel` filtering.
39
+ *
40
+ * Created by `loggingMiddleware` for each incoming HTTP request and injected
41
+ * into the UseCase via the `logger` property.
42
+ *
43
+ * Accepts an optional `writeFn` for output — defaults to `console.log`.
44
+ * In tests, pass a capturing function to inspect output without side-effects.
45
+ */
46
+ export declare class RequestScopedLogger implements ModularLogger {
47
+ readonly traceId: string;
48
+ readonly logLevel: LogLevel;
49
+ readonly serviceName: string;
50
+ private readonly writeFn;
51
+ constructor(traceId: string, logLevel: LogLevel, serviceName: string, writeFn?: WriteFn);
52
+ emergency(msg: string, fields?: Record<string, unknown>): void;
53
+ alert(msg: string, fields?: Record<string, unknown>): void;
54
+ critical(msg: string, fields?: Record<string, unknown>): void;
55
+ error(msg: string, fields?: Record<string, unknown>): void;
56
+ warning(msg: string, fields?: Record<string, unknown>): void;
57
+ notice(msg: string, fields?: Record<string, unknown>): void;
58
+ info(msg: string, fields?: Record<string, unknown>): void;
59
+ debug(msg: string, fields?: Record<string, unknown>): void;
60
+ /** Emits a "request received" log at `info` level. */
61
+ logRequest(opts: {
62
+ method: string;
63
+ route: string;
64
+ }): void;
65
+ /** Emits a "request completed" log at the level determined by `statusCode`. */
66
+ logResponse(opts: {
67
+ method: string;
68
+ route: string;
69
+ statusCode: number;
70
+ durationMs: number;
71
+ }): void;
72
+ /** Emits an "unhandled exception" log at `error` level. No stack trace. */
73
+ logUnhandledException(opts: {
74
+ route: string;
75
+ }): void;
76
+ private log;
77
+ /** Maps HTTP status code → RFC 5424 log level. */
78
+ static levelForStatus(status: number): LogLevel;
79
+ }