exisjs 0.7.5 → 0.7.6
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/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +156 -85
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +54 -16
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +10 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +106 -13
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/resolve-aliases.d.ts +1 -1
- package/dist/cli/resolve-aliases.d.ts.map +1 -1
- package/dist/cli/resolve-aliases.js +30 -13
- package/dist/cli/resolve-aliases.js.map +1 -1
- package/dist/config/config.d.ts +3 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/decorators/middleware.d.ts +65 -2
- package/dist/decorators/middleware.d.ts.map +1 -1
- package/dist/decorators/middleware.js +148 -62
- package/dist/decorators/middleware.js.map +1 -1
- package/dist/decorators/params.d.ts +5 -0
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +11 -1
- package/dist/decorators/params.js.map +1 -1
- package/dist/decorators/response.d.ts.map +1 -1
- package/dist/decorators/response.js +32 -22
- package/dist/decorators/response.js.map +1 -1
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +6 -1
- package/dist/di/container.js.map +1 -1
- package/dist/docs/content/cli/commands.mdx +1 -1
- package/dist/docs/content/controllers.mdx +127 -10
- package/dist/docs/content/decorators.mdx +22 -4
- package/dist/docs/content/introduction.mdx +7 -7
- package/dist/docs/content/middleware.mdx +269 -6
- package/dist/docs/content/requests.mdx +180 -6
- package/dist/docs/content/responses.mdx +283 -32
- package/dist/docs/content/routing.mdx +46 -0
- package/dist/docs/content/validation.mdx +190 -11
- package/dist/docs/reference/context.mdx +11 -0
- package/dist/docs/reference/decorators.mdx +14 -4
- package/dist/docs/reference/error.mdx +44 -0
- package/dist/docs/reference/logger.mdx +175 -43
- package/dist/docs/reference/middleware.mdx +31 -0
- package/dist/docs/reference/router.mdx +22 -0
- package/dist/docs/reference/validator.mdx +83 -23
- package/dist/error/errors.d.ts.map +1 -1
- package/dist/error/errors.js +95 -43
- package/dist/error/errors.js.map +1 -1
- package/dist/error/index.d.ts +1 -0
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +11 -1
- package/dist/error/index.js.map +1 -1
- package/dist/error/overlay.d.ts +42 -0
- package/dist/error/overlay.d.ts.map +1 -0
- package/dist/error/overlay.js +306 -0
- package/dist/error/overlay.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/start-server.js +33 -7
- package/dist/lib/start-server.js.map +1 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +9 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/guard.d.ts +2 -2
- package/dist/middleware/guard.d.ts.map +1 -1
- package/dist/middleware/guard.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts +1 -0
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +2 -0
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/middleware.d.ts +2 -2
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +20 -2
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/security.d.ts +43 -1
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +101 -0
- package/dist/middleware/security.js.map +1 -1
- package/dist/response/index.d.ts +229 -6
- package/dist/response/index.d.ts.map +1 -1
- package/dist/response/index.js +272 -2
- package/dist/response/index.js.map +1 -1
- package/dist/router/boundary.d.ts +17 -9
- package/dist/router/boundary.d.ts.map +1 -1
- package/dist/router/boundary.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +56 -1
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/index.d.ts +2 -2
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +2 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/route-builder.d.ts +70 -36
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js +13 -15
- package/dist/router/route-builder.js.map +1 -1
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js +24 -2
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +35 -17
- package/dist/router/router.js.map +1 -1
- package/dist/router/types.d.ts +8 -0
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/app.d.ts +1 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +10 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +91 -30
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/cluster.d.ts.map +1 -1
- package/dist/server/cluster.js +13 -2
- package/dist/server/cluster.js.map +1 -1
- package/dist/server/context.d.ts +8 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +50 -6
- package/dist/server/context.js.map +1 -1
- package/dist/server/lifecycle.d.ts +22 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/lifecycle.js +106 -0
- package/dist/server/lifecycle.js.map +1 -0
- package/dist/server/request-handler.d.ts +1 -0
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +27 -17
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +18 -0
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +10 -1
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +154 -2
- package/dist/server/response.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +6 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mocks.d.ts.map +1 -1
- package/dist/testing/mocks.js +15 -3
- package/dist/testing/mocks.js.map +1 -1
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +184 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/validator/tex-types.d.ts +2 -1
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/tex-types.js +2 -1
- package/dist/validator/tex-types.js.map +1 -1
- package/dist/validator/tex.d.ts +31 -15
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +280 -42
- package/dist/validator/tex.js.map +1 -1
- package/package.json +4 -4
- package/dist/dev/error-overlay.d.ts +0 -3
- package/dist/dev/error-overlay.d.ts.map +0 -1
- package/dist/dev/error-overlay.js +0 -118
- package/dist/dev/error-overlay.js.map +0 -1
|
@@ -1,47 +1,97 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Logger"
|
|
3
|
-
description: "API Reference for the ExisJS structured logger (exisjs/logger)."
|
|
3
|
+
description: "API Reference and Architecture Guide for the ExisJS structured logger (exisjs/logger)."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Logger (`exisjs/logger`)
|
|
7
7
|
|
|
8
|
-
ExisJS includes a
|
|
9
|
-
|
|
10
|
-
All logger APIs are imported directly from the dedicated subpath `exisjs/logger`.
|
|
8
|
+
ExisJS includes a high-performance, zero-overhead structured logging engine powered by Pino. It provides an **industry-standard single-line terminal format** in development and **ultra-fast structured NDJSON** in production for cloud observability platforms (Datadog, Loki, CloudWatch, OpenTelemetry).
|
|
11
9
|
|
|
12
10
|
```typescript
|
|
13
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
logger,
|
|
13
|
+
configureLogger,
|
|
14
|
+
setLogger,
|
|
15
|
+
createLogger,
|
|
16
|
+
createSilentLogger,
|
|
17
|
+
isLogger,
|
|
18
|
+
formatBytes,
|
|
19
|
+
formatLatency,
|
|
20
|
+
formatMethod,
|
|
21
|
+
formatStatus,
|
|
22
|
+
formatUrl,
|
|
23
|
+
formatIp,
|
|
24
|
+
} from 'exisjs/logger'
|
|
14
25
|
```
|
|
15
26
|
|
|
16
27
|
---
|
|
17
28
|
|
|
18
|
-
## 1.
|
|
29
|
+
## 1. Terminal Request Logging (Development DX)
|
|
30
|
+
|
|
31
|
+
When running your application in development mode (`exis dev` or `pretty: true`), ExisJS automatically formats every incoming HTTP request into a clean, concise, single-line terminal log:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
[22:50:17] [exis] GET / 304 Not Modified 18ms
|
|
35
|
+
[22:50:55] [exis] GET /api/v1/profile 200 OK 1.2ms 240 B
|
|
36
|
+
[22:51:02] [exis] POST /api/v1/users 201 Created 14.5ms 1.4 KB
|
|
37
|
+
[22:51:10] [exis] PATCH /api/v1/users/42 400 Bad Request 3.1ms 120 B
|
|
38
|
+
└─ ⚠ Validation [body]: Field "email" must be a valid email format
|
|
39
|
+
[22:51:15] [exis] GET /api/v1/unknown 404 Not Found 0.8ms 45 B
|
|
40
|
+
[22:51:22] [exis] POST /api/v1/checkout 500 Internal Error 320ms 82 B
|
|
41
|
+
└─ ✖ Error: Database connection timeout
|
|
42
|
+
```
|
|
19
43
|
|
|
20
|
-
|
|
44
|
+
### Visual Features & Formatting Elements
|
|
45
|
+
|
|
46
|
+
| Element | Description | Behavior / Color Coding |
|
|
47
|
+
| :--- | :--- | :--- |
|
|
48
|
+
| **Timestamp** | `[HH:MM:SS]` | Dimmed gray for non-intrusive readability |
|
|
49
|
+
| **Brand Badge** | `[exis]` | Framework brand color (`\x1b[38;2;160;70;255m`), `[warn]` (yellow), `[error]` (red) |
|
|
50
|
+
| **HTTP Method** | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `QUERY`, `SEARCH`, `OPTIONS`, `HEAD`, `WS`, `SSE`, `CONNECT`, `TRACE`, `PURGE` | Fixed-width padded badge with distinct colors (`GET`/`SEARCH` cyan, `POST` green, `PUT`/`PATCH` yellow, `DELETE`/`PURGE` red, `QUERY` blue, `OPTIONS`/`HEAD`/`WS` magenta) |
|
|
51
|
+
| **Path & Query** | `/api/v1/items?page=1` | Path in bright white, query parameters in dimmed gray |
|
|
52
|
+
| **Status Code** | `101 Switching Protocols`, `200 OK`, `201 Created`, `400 Bad Request`, `404 Not Found`, `500 Internal Error` | Color-coded by range: `101` magenta, `2xx` green, `3xx` cyan, `4xx` yellow, `5xx` red |
|
|
53
|
+
| **Response Latency** | `1.2ms`, `18ms`, `1.45s` | Dynamic threshold color coding: `< 50ms` (green), `50ms - 250ms` (yellow), `> 250ms` (red) |
|
|
54
|
+
| **Content Size** | `120 B`, `1.4 KB`, `2.8 MB` | Formatted human-readable byte sizes from `Content-Length` |
|
|
55
|
+
| **Client IP** | `192.168.1.5` | Localhost (`127.0.0.1` / `::1`) is suppressed by default for minimal noise; remote client IPs are cleanly displayed |
|
|
56
|
+
| **Diagnostic Sublines** | `└─ ⚠ Validation: ...` / `└─ ✖ Error: ...` | Indented warning and error summaries printed directly below failed requests |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. Structured Logging in Production (NDJSON)
|
|
61
|
+
|
|
62
|
+
In production (`NODE_ENV === 'production'` or `pretty: false`), the logger outputs single-line structured JSON (NDJSON) directly to `stdout` with zero serialization overhead:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{"level":30,"time":1774286390000,"pid":1234,"hostname":"prod-app-01","requestId":"req-42","method":"GET","url":"/api/v1/profile","statusCode":200,"responseTime":1.2,"contentLength":240,"ip":"10.0.0.4","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7","msg":"GET /api/v1/profile"}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 3. Global Logger Singleton (`logger`)
|
|
71
|
+
|
|
72
|
+
The `logger` singleton is a high-performance proxy that can be imported and used anywhere in your services, repositories, workers, and controllers:
|
|
21
73
|
|
|
22
74
|
```typescript
|
|
23
75
|
import { logger } from 'exisjs/logger'
|
|
24
76
|
|
|
25
|
-
|
|
26
|
-
logger.
|
|
27
|
-
logger.
|
|
28
|
-
logger.
|
|
77
|
+
// Standard level methods
|
|
78
|
+
logger.info('Payment service initialized')
|
|
79
|
+
logger.warn({ ip: req.ip, attempts: 3 }, 'Multiple failed login attempts')
|
|
80
|
+
logger.error({ err, orderId: 'ord_123' }, 'Stripe webhook processing failed')
|
|
81
|
+
logger.debug({ payload }, 'Parsed RPC frame')
|
|
82
|
+
logger.trace('Entering database connection pool checkout')
|
|
83
|
+
logger.fatal({ err }, 'Unrecoverable process state')
|
|
84
|
+
|
|
85
|
+
// Child loggers with bound context
|
|
86
|
+
const workerLogger = logger.child({ workerId: 'worker-1', queue: 'emails' })
|
|
87
|
+
workerLogger.info('Processing email batch')
|
|
29
88
|
```
|
|
30
89
|
|
|
31
|
-
### Methods
|
|
32
|
-
- `logger.info([obj], msg, ...args)`
|
|
33
|
-
- `logger.warn([obj], msg, ...args)`
|
|
34
|
-
- `logger.error([obj], msg, ...args)`
|
|
35
|
-
- `logger.debug([obj], msg, ...args)`
|
|
36
|
-
- `logger.trace([obj], msg, ...args)`
|
|
37
|
-
- `logger.fatal([obj], msg, ...args)`
|
|
38
|
-
- `logger.child(bindings)`: Creates a child logger with bound metadata (e.g. `{ requestId: '123' }`).
|
|
39
|
-
|
|
40
90
|
---
|
|
41
91
|
|
|
42
|
-
##
|
|
92
|
+
## 4. Configuration (`configureLogger`)
|
|
43
93
|
|
|
44
|
-
Use `configureLogger()` to adjust log levels, toggle pretty printing, and customize sensitive field redactions across
|
|
94
|
+
Use `configureLogger()` to adjust log levels, toggle pretty printing, and customize sensitive field redactions across your entire application:
|
|
45
95
|
|
|
46
96
|
```typescript filename="src/http/server.ts"
|
|
47
97
|
import { configureLogger } from 'exisjs/logger'
|
|
@@ -49,21 +99,87 @@ import { configureLogger } from 'exisjs/logger'
|
|
|
49
99
|
configureLogger({
|
|
50
100
|
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
|
|
51
101
|
pretty: process.env.NODE_ENV !== 'production',
|
|
52
|
-
redact: ['*.password', '*.token', '*.secret', 'req.headers.cookie'],
|
|
102
|
+
redact: ['*.password', '*.token', '*.secret', 'req.headers.cookie', '*.creditCard'],
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Options Reference
|
|
107
|
+
|
|
108
|
+
| Option | Type | Default | Description |
|
|
109
|
+
| :--- | :--- | :--- | :--- |
|
|
110
|
+
| `level` | `'fatal' \| 'error' \| 'warn' \| 'info' \| 'debug' \| 'trace' \| 'silent'` | `'info'` | Minimum log level to emit |
|
|
111
|
+
| `pretty` | `boolean` | `NODE_ENV !== 'production'` | Whether to use colored single-line terminal formatting |
|
|
112
|
+
| `redact` | `string[]` | *See Redaction section* | Array of object paths to automatically censor from logs |
|
|
113
|
+
| `replaceRedact` | `boolean` | `false` | When `true`, overrides default redact paths instead of merging |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 5. Security & Sensitive Data Redaction
|
|
118
|
+
|
|
119
|
+
By default, ExisJS automatically censors sensitive fields in all log payloads before they are serialized to disk or terminal:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Built-in default redacted paths:
|
|
123
|
+
[
|
|
124
|
+
'req.headers.authorization',
|
|
125
|
+
'req.headers.cookie',
|
|
126
|
+
'req.body.password',
|
|
127
|
+
'req.body.token',
|
|
128
|
+
'req.body.secret',
|
|
129
|
+
'*.password',
|
|
130
|
+
'*.token',
|
|
131
|
+
'*.secret',
|
|
132
|
+
'password',
|
|
133
|
+
'token',
|
|
134
|
+
'secret'
|
|
135
|
+
]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
When logging objects containing these keys (e.g. `{ username: 'alex', password: 'supersecretpassword' }`), the value is replaced with `"[Redacted]"`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 6. Request-Scoped Logger (`req.log` / `ctx.log`)
|
|
143
|
+
|
|
144
|
+
Every incoming HTTP request in ExisJS provides a zero-allocation lazy child logger bound to `req.log`. It automatically inherits and binds:
|
|
145
|
+
- `requestId` (`req.requestId`)
|
|
146
|
+
- `method` (`req.method`)
|
|
147
|
+
- `url` (`req.path`)
|
|
148
|
+
|
|
149
|
+
```typescript filename="src/http/orders/route.ts"
|
|
150
|
+
import { route, controller } from 'exisjs/router'
|
|
151
|
+
|
|
152
|
+
export default controller({
|
|
153
|
+
create: route.post('/', {
|
|
154
|
+
async handle({ req, body }) {
|
|
155
|
+
// req.log automatically includes { requestId, method, url }
|
|
156
|
+
req.log.info({ orderId: body.id }, 'Creating order record')
|
|
157
|
+
return { success: true }
|
|
158
|
+
}
|
|
159
|
+
})
|
|
53
160
|
})
|
|
54
161
|
```
|
|
55
162
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 7. OpenTelemetry & Distributed Tracing
|
|
166
|
+
|
|
167
|
+
ExisJS automatically integrates with `@opentelemetry/api` when present in your environment. Every log entry emitted via `logger` or `req.log` automatically receives the active `trace_id` and `span_id`:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"level": 30,
|
|
172
|
+
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
173
|
+
"span_id": "00f067aa0ba902b7",
|
|
174
|
+
"msg": "Order created successfully"
|
|
175
|
+
}
|
|
176
|
+
```
|
|
61
177
|
|
|
62
178
|
---
|
|
63
179
|
|
|
64
|
-
##
|
|
180
|
+
## 8. Custom Logger Integration (`setLogger`)
|
|
65
181
|
|
|
66
|
-
|
|
182
|
+
To route ExisJS logs directly into custom transports (such as `pino-loki`, `@google-cloud/logging`, or Elasticsearch), supply your custom instance to `setLogger()`:
|
|
67
183
|
|
|
68
184
|
```typescript filename="src/http/server.ts"
|
|
69
185
|
import { setLogger } from 'exisjs/logger'
|
|
@@ -82,19 +198,35 @@ setLogger(customPino)
|
|
|
82
198
|
|
|
83
199
|
---
|
|
84
200
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
Every incoming HTTP request in ExisJS automatically receives a child logger bound to `req.log`. It automatically includes request context such as `req.requestId`:
|
|
201
|
+
## 9. Formatting Helper Utilities
|
|
88
202
|
|
|
89
|
-
|
|
90
|
-
import { route, controller } from 'exisjs/router'
|
|
203
|
+
ExisJS exports all terminal formatting helpers from `exisjs/logger` so you can use them in custom CLI tools, workers, and plugins:
|
|
91
204
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
205
|
+
```typescript
|
|
206
|
+
import {
|
|
207
|
+
formatBytes,
|
|
208
|
+
formatLatency,
|
|
209
|
+
formatMethod,
|
|
210
|
+
formatStatus,
|
|
211
|
+
formatUrl,
|
|
212
|
+
formatIp,
|
|
213
|
+
extractValidationSummary,
|
|
214
|
+
extractErrorMessage,
|
|
215
|
+
} from 'exisjs/logger'
|
|
216
|
+
|
|
217
|
+
// Byte formatting
|
|
218
|
+
formatBytes(1420) // "1.4 KB"
|
|
219
|
+
|
|
220
|
+
// Latency with color threshold
|
|
221
|
+
formatLatency(14.5) // "\x1b[32m14.5ms\x1b[0m"
|
|
222
|
+
|
|
223
|
+
// Padded color-coded method
|
|
224
|
+
formatMethod('GET') // "\x1b[1;36mGET \x1b[0m"
|
|
225
|
+
|
|
226
|
+
// Status code with standard phrase
|
|
227
|
+
formatStatus(200) // "\x1b[1;32m200 OK\x1b[0m"
|
|
228
|
+
|
|
229
|
+
// Localhost suppression
|
|
230
|
+
formatIp('127.0.0.1') // "" (suppressed)
|
|
231
|
+
formatIp('192.168.1.10') // "\x1b[90m192.168.1.10\x1b[0m"
|
|
100
232
|
```
|
|
@@ -48,6 +48,37 @@ export default controller({
|
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
+
## Middleware Context Inference
|
|
52
|
+
|
|
53
|
+
### `defineMiddleware<TContext>(fn)`
|
|
54
|
+
|
|
55
|
+
Creates a strongly-typed middleware function that attaches custom typed properties (like `user`, `session`, or `tenantId`) to `req`. Downstream route handlers (`route.get`, `route.post`, etc.) automatically inherit and infer this context with full IDE autocomplete and zero manual type casts.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { controller, route, defineMiddleware } from 'exisjs/router'
|
|
59
|
+
|
|
60
|
+
interface AuthContext {
|
|
61
|
+
user: { id: string; role: 'admin' | 'user'; email: string }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const auth = defineMiddleware<AuthContext>((req, res, next) => {
|
|
65
|
+
req.user = { id: 'u1', role: 'admin', email: 'alex@example.com' }
|
|
66
|
+
next()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
export default controller({
|
|
70
|
+
profile: route.get('/profile', {
|
|
71
|
+
middlewares: [auth],
|
|
72
|
+
handle({ req, res }) {
|
|
73
|
+
// req.user has full autocomplete and strict typing:
|
|
74
|
+
return res.json({ id: req.user.id, role: req.user.role })
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
51
82
|
## Security Middleware
|
|
52
83
|
|
|
53
84
|
### `helmet(options?)`
|
|
@@ -248,3 +248,25 @@ ExisJS provides a rich ecosystem of decorators to apply middleware, configure re
|
|
|
248
248
|
- `@Returns(schema)`: Explicitly defines the response schema for OpenAPI generation.
|
|
249
249
|
- `@Idempotent()`: Enforces idempotency keys for mutations (e.g., checkout/payments).
|
|
250
250
|
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 3. Streaming & Backpressure Pipeline
|
|
254
|
+
|
|
255
|
+
The response object supports universal streaming with backpressure management across all route types:
|
|
256
|
+
|
|
257
|
+
- `res.sendStream(source)`: Streams a Node.js `stream.Readable`, Web `ReadableStream`, or `AsyncIterable` directly to the client socket. Pauses consumption when socket buffers fill up and terminates source readers upon client disconnection.
|
|
258
|
+
- `res.stream(source)`: Alias for `sendStream()`.
|
|
259
|
+
- **Automatic Handler Auto-Piping**: Returning a Node.js stream, Web standard `ReadableStream`, or `AsyncIterable` generator directly from `handle()` automatically delegates to `res.sendStream()`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 4. `AppRouter` Client Manifest Contract
|
|
264
|
+
|
|
265
|
+
ExisJS generates a compile-time route manifest in `.exis/types.d.ts` and `.exis/types/index.d.ts`:
|
|
266
|
+
|
|
267
|
+
- `AppRouterPaths`: Flat dictionary mapping URL route patterns (e.g. `'/users/:id'`) to their strongly-typed handler contracts (`(payload?: B, options?: { query?: Q; params?: P }) => Promise<R>`).
|
|
268
|
+
- `AppRouterTree`: Hierarchical dot-notation tree mapping nested folder paths (`users: { ':id': ... }`) for `@exisjs/fetch`'s `createClient<AppRouter>()`.
|
|
269
|
+
- `AppRouter`: Combined intersection type `AppRouterPaths & AppRouterTree`.
|
|
270
|
+
- `RouteDefinition.__type`: Phantom type property on every route definition preserving exact inferred return types, DTO schemas, and Drizzle/Prisma models.
|
|
271
|
+
|
|
272
|
+
|
|
@@ -11,13 +11,23 @@ ExisJS ships with a efficient, integrated validation library out of the box, exp
|
|
|
11
11
|
|
|
12
12
|
## Core Types
|
|
13
13
|
|
|
14
|
-
The `tex` object provides factory functions for every common primitive and data structure
|
|
15
|
-
|
|
16
|
-
- `tex.string()`: Validates a string.
|
|
17
|
-
- `tex.number()`: Validates a
|
|
18
|
-
- `tex.boolean()`: Validates a boolean.
|
|
19
|
-
- `tex.email()`: Validates standard email formatting.
|
|
20
|
-
- `tex.
|
|
14
|
+
The `tex` object provides factory functions for every common primitive and data structure:
|
|
15
|
+
|
|
16
|
+
- `tex.string(options?)`: Validates a string (supports min, max, trim, sanitizers, masking, SQL injection prevention, defaults).
|
|
17
|
+
- `tex.number(options?)`: Validates a numeric value (supports min, max, coercion, defaults).
|
|
18
|
+
- `tex.boolean(options?)`: Validates a boolean value (supports coercion, defaults).
|
|
19
|
+
- `tex.email(options?)`: Validates standard email formatting.
|
|
20
|
+
- `tex.uuid(options?)`: Validates UUID v1 or v4 strings (`{ version: 1 | 4 }`).
|
|
21
|
+
- `tex.cuid(options?)`: Validates CUID identifiers.
|
|
22
|
+
- `tex.password(options?)`: Enforces password complexity rules (`requireNumbers`, `requireSymbols`, `requireUppercase`, `requireLowercase`).
|
|
23
|
+
- `tex.creditCard(options?)`: Validates credit card number format with optional masking (`{ mask: true }`).
|
|
24
|
+
- `tex.date(options?)`: Validates Date instances, or coerces ISO 8601 strings and UNIX timestamps with `minDate` / `maxDate` bounds.
|
|
25
|
+
- `tex.enum(values, options?)`: Validates against a specific set of allowed strings.
|
|
26
|
+
- `tex.literal(value)`: Validates against an exact literal value (`string | number | boolean`).
|
|
27
|
+
- `tex.record(schema)`: Validates a dictionary / map of key-value pairs matching the given value schema.
|
|
28
|
+
- `tex.union(schemas)`: Validates that the input matches at least one of the provided schemas.
|
|
29
|
+
- `tex.any()`: Allows any arbitrary payload value without constraints.
|
|
30
|
+
- `tex.file(options?)`: Validates multipart file uploads (`maxSize`, `mimeTypes`).
|
|
21
31
|
|
|
22
32
|
### Objects & Arrays
|
|
23
33
|
|
|
@@ -114,8 +124,24 @@ All validators accept an options object that allows you to configure validation
|
|
|
114
124
|
- `optional` / `.optional()`: Marks the field as optional (allows `undefined`).
|
|
115
125
|
- `nullable` / `.nullable()`: Marks the field as nullable (allows `null`).
|
|
116
126
|
- `nullish` / `.nullish()`: Marks the field as nullish (allows both `null` and `undefined`).
|
|
127
|
+
- `default`: Accepts a static literal value (`string | number | boolean | ...`) or a lazy factory function (`() => value`). Automatically marks the field as optional in schemas while inferring a non-optional concrete TypeScript return type.
|
|
117
128
|
- `coerce`: Instructs the Rust engine to automatically convert types when possible before validating. For example, converting `"123"` into a number, or `3000` into a string.
|
|
118
129
|
|
|
130
|
+
> [!TIP]
|
|
131
|
+
> **Static vs. Dynamic Defaults**:
|
|
132
|
+
> ```typescript
|
|
133
|
+
> const ConfigSchema = tex.object({
|
|
134
|
+
> // Static defaults
|
|
135
|
+
> PORT: tex.number({ coerce: true, default: 3000 }),
|
|
136
|
+
> THEME: tex.enum(['light', 'dark'] as const, { default: 'light' }),
|
|
137
|
+
> TAGS: tex.array(tex.string(), { default: ['exis', 'framework'] }),
|
|
138
|
+
>
|
|
139
|
+
> // Dynamic defaults (evaluated per parse call)
|
|
140
|
+
> createdAt: tex.date({ default: () => new Date() }),
|
|
141
|
+
> requestId: tex.string({ default: () => crypto.randomUUID() }),
|
|
142
|
+
> })
|
|
143
|
+
> ```
|
|
144
|
+
|
|
119
145
|
> [!NOTE]
|
|
120
146
|
> **Empty String Interop**: If an incoming string is empty (`""`), and the field is configured as `optional`, `nullable`, or `nullish`, ExisJS automatically converts it to `undefined` or `null` before running type checks. This cleanly handles form submissions and query strings without requiring ad-hoc pre-processors.
|
|
121
147
|
|
|
@@ -133,20 +159,28 @@ const EnvSchema = tex.object({
|
|
|
133
159
|
})
|
|
134
160
|
```
|
|
135
161
|
|
|
136
|
-
###
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
### Primitive Options Summary
|
|
163
|
+
|
|
164
|
+
| Primitive | Available Options |
|
|
165
|
+
| :--- | :--- |
|
|
166
|
+
| `tex.string(opts?)` | `min`, `max`, `trim`, `collapseWhitespace`, `toLowerCase`, `toUpperCase`, `escapeHtml`, `stripHtml`, `slugify`, `mask`, `preventSql`, `preventTraversal`, `default`, `optional`, `nullable`, `nullish` |
|
|
167
|
+
| `tex.number(opts?)` | `min`, `max`, `coerce`, `default`, `optional`, `nullable`, `nullish` |
|
|
168
|
+
| `tex.boolean(opts?)` | `coerce`, `default`, `optional`, `nullable`, `nullish` |
|
|
169
|
+
| `tex.date(opts?)` | `coerce`, `minDate`, `maxDate`, `default`, `optional`, `nullable`, `nullish` |
|
|
170
|
+
| `tex.array(schema, opts?)` | `min`, `max`, `dedupe`, `coerce`, `default`, `optional`, `nullable`, `nullish` |
|
|
171
|
+
| `tex.enum(values, opts?)` | `default`, `optional`, `nullable`, `nullish` |
|
|
172
|
+
| `tex.uuid(opts?)` | `version: 1 \| 4`, `default`, `optional`, `nullable`, `nullish` |
|
|
173
|
+
| `tex.password(opts?)` | `min`, `max`, `requireNumbers`, `requireSymbols`, `requireUppercase`, `requireLowercase`, `optional`, `nullable`, `nullish` |
|
|
174
|
+
| `tex.creditCard(opts?)` | `mask: boolean`, `optional`, `nullable`, `nullish` |
|
|
175
|
+
| `tex.file(opts?)` | `maxSize`, `mimeTypes: string[]`, `optional`, `nullable`, `nullish` |
|
|
143
176
|
|
|
144
177
|
```typescript
|
|
145
178
|
const User = tex.object({
|
|
146
179
|
id: tex.string(),
|
|
147
180
|
email: tex.email(),
|
|
148
181
|
password: tex.string({ min: 8, max: 100 }),
|
|
149
|
-
age: tex.number({ min: 13, optional: true })
|
|
182
|
+
age: tex.number({ min: 13, optional: true }),
|
|
183
|
+
role: tex.enum(['admin', 'member'], { default: 'member' })
|
|
150
184
|
})
|
|
151
185
|
```
|
|
152
186
|
|
|
@@ -204,18 +238,44 @@ The `TexEngine` automatically converts validation schemas into strictly complian
|
|
|
204
238
|
|
|
205
239
|
---
|
|
206
240
|
|
|
207
|
-
## Environment Validation
|
|
241
|
+
## Environment Validation (`tex.env`)
|
|
208
242
|
|
|
209
|
-
|
|
243
|
+
Because `process.env` always supplies raw string values (e.g. `"4000"`, `"true"`, `"1"`), ExisJS provides a dedicated `tex.env()` schema parser that automatically applies type coercion (`number`, `boolean`, `date`, `array`) and parses string representations without requiring verbose `{ coerce: true }` flags on every field.
|
|
210
244
|
|
|
211
|
-
```typescript filename="env.ts"
|
|
245
|
+
```typescript filename="src/config/env.ts"
|
|
212
246
|
import { tex } from 'exisjs/validator'
|
|
213
247
|
|
|
214
|
-
// Validates process.env synchronously
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
248
|
+
// Validates process.env synchronously with automatic type coercion on boot.
|
|
249
|
+
export const env = tex.env({
|
|
250
|
+
PORT: tex.number({ default: 3000 }), // Automatically coerced from "3000" -> 3000
|
|
251
|
+
DEBUG: tex.boolean({ default: false }), // Parsed from "true" | "1" | "false" | "0"
|
|
218
252
|
DATABASE_URL: tex.string(),
|
|
219
|
-
JWT_SECRET: tex.string({ min: 32 })
|
|
253
|
+
JWT_SECRET: tex.string({ min: 32 }),
|
|
254
|
+
ALLOWED_ORIGINS: tex.array(tex.string()), // Parsed from "http://localhost:3000,https://app.com"
|
|
255
|
+
NODE_ENV: tex.enum(['development', 'production', 'test'] as const, {
|
|
256
|
+
default: 'development',
|
|
257
|
+
}),
|
|
220
258
|
}).parse(process.env)
|
|
221
259
|
```
|
|
260
|
+
|
|
261
|
+
> [!TIP]
|
|
262
|
+
> `tex.env` automatically applies defaults and lazy factories (`default: () => ...`) when environment variables are omitted, while throwing structured `ValidatorError` diagnostics on boot if required keys are missing.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Validation Error Descriptors (`ValidationErrorDescriptor`)
|
|
267
|
+
|
|
268
|
+
When validation fails, ExisJS emits structured error descriptors passed across the FFI boundary:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
export interface ValidationErrorDescriptor {
|
|
272
|
+
path: string // e.g. "email" or "items[0].sku"
|
|
273
|
+
message: string // Human-readable failure description
|
|
274
|
+
expected: string // Expected constraint or type rule
|
|
275
|
+
received: string // Received value representation
|
|
276
|
+
code: string // Machine-readable code (e.g. MISSING_REQUIRED_FIELD, INVALID_TYPE, VALUE_TOO_SMALL)
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
In HTTP 400 responses, the envelope contains both a legacy `errors: Record<string, string>` dictionary for backward compatibility and a structured `details: ValidationErrorDescriptor[]` array for modern form libraries.
|
|
281
|
+
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/error/errors.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/error/errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAKrD,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAgB,UAAU,EAAE,MAAM,CAAA;IAClC,SAAgB,IAAI,EAAE,MAAM,CAAA;IAC5B,SAAgB,aAAa,EAAE,OAAO,CAAA;IACtC,SAAgB,OAAO,CAAC,EAAE,OAAO,CAAA;gBAG/B,OAAO,EAAE,MAAM,EACf,UAAU,SAAM,EAChB,IAAI,SAAmB,EACvB,OAAO,CAAC,EAAE,OAAO;IAenB,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS;IAIhE,MAAM,CAAC,YAAY,CAAC,OAAO,SAAiB,GAAG,SAAS;IAIxD,MAAM,CAAC,SAAS,CAAC,OAAO,SAAc,GAAG,SAAS;IAIlD,MAAM,CAAC,QAAQ,CAAC,QAAQ,SAAa,GAAG,SAAS;IAIjD,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS;IAI3C,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS;IAInE,MAAM,CAAC,eAAe,CACpB,OAAO,SAAsB,EAC7B,OAAO,CAAC,EAAE,OAAO,GAChB,SAAS;IAIZ,MAAM,CAAC,eAAe,CAAC,OAAO,SAAsB,GAAG,SAAS;IAIhE,MAAM,CAAC,QAAQ,CAAC,OAAO,SAA0B,GAAG,SAAS;IAI7D,MAAM,CAAC,kBAAkB,CAAC,OAAO,SAAwB,GAAG,SAAS;IAIrE,MAAM,CAAC,cAAc,CAAC,OAAO,SAAoB,GAAG,SAAS;IAI7D,MAAM,IAAI,MAAM;CAUjB;AAED,qBAAa,eAAgB,SAAQ,SAAS;gBAChC,OAAO,SAAgB,EAAE,OAAO,CAAC,EAAE,OAAO;CAIvD;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,OAAO,SAAiB;CAIrC;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,SAAc;CAIlC;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,QAAQ,SAAa;CAIlC;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,SAAa;CAIjC;AAED,qBAAa,kBAAmB,SAAQ,SAAS;gBACnC,OAAO,SAAyB,EAAE,OAAO,CAAC,EAAE,OAAO;CAIhE;AAED,qBAAa,oBAAqB,SAAQ,SAAS;gBACrC,OAAO,SAAsB,EAAE,OAAO,CAAC,EAAE,OAAO;CAI7D;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,SAAsB;CAI1C;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,SAA0B;CAI9C;AAED,qBAAa,uBAAwB,SAAQ,SAAS;gBACxC,OAAO,SAAwB;CAI5C;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACpC,OAAO,SAAoB;CAIxC;AAGD,eAAO,MAAM,aAAa,kBAAY,CAAA;AACtC,eAAO,MAAM,mBAAmB,wBAAkB,CAAA;AAClD,eAAO,MAAM,qBAAqB,0BAAoB,CAAA;AACtD,eAAO,MAAM,kBAAkB,uBAAiB,CAAA;AAChD,eAAO,MAAM,iBAAiB,sBAAgB,CAAA;AAC9C,eAAO,MAAM,iBAAiB,sBAAgB,CAAA;AAC9C,eAAO,MAAM,wBAAwB,6BAAuB,CAAA;AAC5D,eAAO,MAAM,sBAAsB,2BAAqB,CAAA;AACxD,eAAO,MAAM,kBAAkB,uBAAiB,CAAA;AAChD,eAAO,MAAM,iBAAiB,sBAAgB,CAAA;AAC9C,eAAO,MAAM,2BAA2B,gCAA0B,CAAA;AAClE,eAAO,MAAM,uBAAuB,4BAAsB,CAAA;AA6G1D,wBAAgB,qBAAqB,CACnC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,GAC5D,MAAM,CA6BR;AAED,wBAAgB,kBAAkB,CAAC,KAAK,UAAQ,GAAG,YAAY,CAoS9D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAIjD"}
|