@venizia/ignis-docs 0.2.0 → 0.2.1-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 +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +6 -2
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +182 -93
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +57 -218
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Request Tracker
|
|
2
2
|
|
|
3
|
-
Automatic request logging middleware
|
|
3
|
+
Automatic request logging middleware that assigns a UUID request ID to every request, then logs method, path, client IP, and timing on the way in and out.
|
|
4
4
|
|
|
5
5
|
> [!IMPORTANT]
|
|
6
6
|
> This component is **auto-registered** by `BaseApplication` during `initialize()`. No manual registration is needed.
|
|
@@ -20,225 +20,112 @@ Automatic request logging middleware with unique request IDs, client IP detectio
|
|
|
20
20
|
import { RequestTrackerComponent } from '@venizia/ignis';
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## In one example
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
No configuration binding is required. The component has no user-facing configuration options.
|
|
28
|
-
|
|
29
|
-
### Step 2: Register Component
|
|
30
|
-
|
|
31
|
-
This happens automatically inside `BaseApplication.initialize()`:
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
// Internal to BaseApplication -- shown for reference only
|
|
35
|
-
this.component(RequestTrackerComponent);
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
The component registers two Hono middlewares on the application server during its `binding()` phase:
|
|
39
|
-
1. `requestId()` from `hono/request-id` -- generates a UUID and stores it on the Hono context under the key `'requestId'`
|
|
40
|
-
2. `RequestSpyMiddleware` -- logs request start/end with IP, method, path, query, body, and timing
|
|
41
|
-
|
|
42
|
-
### Step 3: Use
|
|
43
|
-
|
|
44
|
-
No injection or manual usage is needed. Once the application starts, every incoming request is automatically logged with a unique request ID.
|
|
45
|
-
|
|
46
|
-
A sample log output in **non-production** mode looks like this:
|
|
25
|
+
Nothing to configure - once the application starts, every request is logged automatically.
|
|
47
26
|
|
|
48
27
|
```
|
|
49
28
|
[SpyMW] [<request-id>][127.0.0.1][=>] GET /hello | query: {} | body: null
|
|
50
29
|
[SpyMW] [<request-id>][127.0.0.1][<=] GET /hello | Took: 1.23 (ms)
|
|
51
30
|
```
|
|
52
31
|
|
|
53
|
-
In **production**
|
|
32
|
+
In **production** (`NODE_ENV=production`), the body is omitted; query is still logged:
|
|
54
33
|
|
|
55
34
|
```
|
|
56
35
|
[SpyMW] [<request-id>][127.0.0.1][=>] GET /hello | query: {}
|
|
57
36
|
[SpyMW] [<request-id>][127.0.0.1][<=] GET /hello | Took: 1.23 (ms)
|
|
58
37
|
```
|
|
59
38
|
|
|
60
|
-
The log format follows this structure:
|
|
61
|
-
|
|
62
39
|
| Direction | Format |
|
|
63
40
|
|-----------|--------|
|
|
64
41
|
| Incoming (`=>`) | `[requestId][clientIp][=>] METHOD path \| query: {...} \| body: {...}` |
|
|
65
42
|
| Outgoing (`<=`) | `[requestId][clientIp][<=] METHOD path \| Took: X.XX (ms)` |
|
|
66
43
|
|
|
67
|
-
The HTTP method is padded to 8 characters for consistent alignment
|
|
68
|
-
|
|
69
|
-
> [!TIP]
|
|
70
|
-
> The request ID is also available in the framework's error handlers (`notFoundHandler`, `appErrorHandler`), making it easy to correlate error logs with the original request.
|
|
71
|
-
|
|
72
|
-
## Configuration
|
|
73
|
-
|
|
74
|
-
The Request Tracker component has no user-configurable options. Its behavior is fully automatic.
|
|
75
|
-
|
|
76
|
-
| Behavior | Description |
|
|
77
|
-
|----------|-------------|
|
|
78
|
-
| **Request ID** | Generated automatically via `hono/request-id` middleware (UUID), stored on context as `'requestId'` |
|
|
79
|
-
| **IP Detection** | Priority: (1) connection info via `getIncomingIp()`, (2) `x-real-ip` header, (3) `x-forwarded-for` header |
|
|
80
|
-
| **Body Logging** | Logs request body in non-production environments only. Query is always logged |
|
|
81
|
-
| **Timing** | Measures and logs request duration in milliseconds (2 decimal places) via `performance.now()` |
|
|
82
|
-
| **Scope** | Registered as a singleton provider |
|
|
83
|
-
|
|
84
|
-
> [!NOTE]
|
|
85
|
-
> In **production** (`NODE_ENV=production`), only the request **body** is excluded from log output to prevent sensitive data exposure. Query parameters are still logged in all environments.
|
|
86
|
-
|
|
87
|
-
## Internals
|
|
88
|
-
|
|
89
|
-
### RequestTrackerComponent
|
|
90
|
-
|
|
91
|
-
The component class extends `BaseComponent`. It receives `BaseApplication` via `@inject({ key: CoreBindings.APPLICATION_INSTANCE })` in its constructor.
|
|
44
|
+
The HTTP method is padded to 8 characters for consistent alignment.
|
|
92
45
|
|
|
93
|
-
|
|
46
|
+
## How it works
|
|
94
47
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
The binding key is constructed as `BindingNamespaces.MIDDLEWARE + '.' + RequestSpyMiddleware.name`, which resolves to `'middlewares.RequestSpyMiddleware'`.
|
|
102
|
-
|
|
103
|
-
#### Component Lifecycle
|
|
104
|
-
|
|
105
|
-
1. **`constructor()`** -- Receives `BaseApplication` via DI. Defines the middleware binding as a singleton provider.
|
|
106
|
-
2. **`binding()`** -- Registers `requestId()` middleware on the server. Resolves the `RequestSpyMiddleware` binding from the DI container. Throws if the middleware cannot be resolved. Registers the resolved middleware on the server.
|
|
107
|
-
|
|
108
|
-
### RequestSpyMiddleware
|
|
109
|
-
|
|
110
|
-
The middleware class extends `BaseHelper` with scope `'SpyMW'` and implements `IProvider<MiddlewareHandler>`.
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
class RequestSpyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
|
|
114
|
-
static readonly REQUEST_ID_KEY = 'requestId';
|
|
115
|
-
private isDebugMode: boolean;
|
|
116
|
-
// ...
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
#### IProvider Pattern
|
|
121
|
-
|
|
122
|
-
`RequestSpyMiddleware` implements the `IProvider<T>` interface from `@venizia/ignis-inversion`. This interface requires a single method:
|
|
48
|
+
- **Two middlewares, one component.** `binding()` registers Hono's own `requestId()` (from `hono/request-id`) first, then resolves `RequestSpyMiddleware` from the DI container and registers it - `requestId()` must run first so the spy can read the ID off the context.
|
|
49
|
+
- **IP resolution is best-effort, never fatal.** The middleware tries `getIncomingIp()` (runtime connection info), then `x-real-ip`, then `x-forwarded-for`; if none resolve it logs `'unknown'` instead of failing the request - this middleware observes traffic, it does not gate it.
|
|
50
|
+
- **Body logging is environment-gated.** `RequestSpyMiddleware` reads `NODE_ENV` once in its constructor: any value other than `'production'` logs the body; `'production'` logs query only. Query is always logged in every environment.
|
|
51
|
+
- **Body parsing follows Content-Type.** JSON, multipart, and URL-encoded bodies use Hono's own parsers; `application/octet-stream` returns the raw stream; everything else is read as text. A parse failure throws `'Malformed Body Payload'` (HTTP 400).
|
|
52
|
+
- **The middleware is an `IProvider`, not a plain function.** `RequestSpyMiddleware implements IProvider<MiddlewareHandler>` from `@venizia/ignis-inversion` - the container instantiates the class (so it can hold `isDebugMode` state) and calls `.value()` to obtain the actual Hono handler.
|
|
123
53
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
value(container: Container): T;
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
When the DI container resolves the binding (via `.toProvider(RequestSpyMiddleware)`), it instantiates the class and calls `value()` to obtain the actual `MiddlewareHandler`. This pattern allows the middleware to hold state (like `isDebugMode`) while producing a clean middleware function.
|
|
54
|
+
> [!TIP]
|
|
55
|
+
> The request ID is also available in the framework's error handlers (`notFoundHandler`, `AppErrorMiddleware`), making it easy to correlate error logs with the original request.
|
|
131
56
|
|
|
132
|
-
|
|
57
|
+
## Common tasks
|
|
133
58
|
|
|
134
|
-
|
|
59
|
+
### Correlate logs with a request
|
|
60
|
+
Read the `requestId` context value inside your own handlers or middleware - the same ID appears in every `[SpyMW]` log line for that request.
|
|
135
61
|
|
|
136
62
|
```typescript
|
|
137
|
-
|
|
138
|
-
super({ scope: 'SpyMW' });
|
|
139
|
-
const env = process.env.NODE_ENV?.toLowerCase();
|
|
140
|
-
this.isDebugMode = env !== Environment.PRODUCTION;
|
|
141
|
-
}
|
|
63
|
+
const requestId = context.get('requestId');
|
|
142
64
|
```
|
|
143
65
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
#### value() -- Middleware Handler
|
|
147
|
-
|
|
148
|
-
The `value()` method returns a Hono middleware created via `createMiddleware()` from `hono/factory`. The middleware performs the following steps:
|
|
149
|
-
|
|
150
|
-
1. Starts a performance timer via `performance.now()`
|
|
151
|
-
2. Extracts the request ID from the Hono context (set by `requestId()` middleware)
|
|
152
|
-
3. Resolves the client IP using the priority chain (see IP Detection below)
|
|
153
|
-
4. Throws `'Malformed Connection Info'` (400) if both `incomingIp` and `forwardedIp` are `null`
|
|
154
|
-
5. Extracts method, path, and query from the request
|
|
155
|
-
6. Parses the request body via `parseBody()`
|
|
156
|
-
7. Logs the incoming request with `[=>]` direction marker
|
|
157
|
-
8. Calls `await next()` to proceed to the next middleware/handler
|
|
158
|
-
9. Calculates duration and logs the outgoing response with `[<=]` direction marker
|
|
159
|
-
|
|
160
|
-
#### parseBody()
|
|
161
|
-
|
|
162
|
-
A public method that parses the request body based on `Content-Type` and `Content-Length` headers.
|
|
66
|
+
### Reuse `parseBody` for your own middleware
|
|
67
|
+
`parseBody` is a public method - reuse it wherever you need the same Content-Type-aware parsing.
|
|
163
68
|
|
|
164
69
|
```typescript
|
|
165
70
|
async parseBody(opts: { req: TContext['req'] }): Promise<unknown>
|
|
166
71
|
```
|
|
167
72
|
|
|
168
|
-
|
|
73
|
+
### Understand the client IP resolution order
|
|
74
|
+
| Priority | Source | Notes |
|
|
75
|
+
|----------|--------|-------|
|
|
76
|
+
| 1 | `getIncomingIp(context)` | Native connection info - `hono/bun` on Bun, `@hono/node-server/conninfo` on Node.js |
|
|
77
|
+
| 2 | `x-real-ip` header | Set by reverse proxies (e.g., Nginx `proxy_set_header X-Real-IP`) |
|
|
78
|
+
| 3 | `x-forwarded-for` header | Standard proxy header |
|
|
79
|
+
| 4 | `'unknown'` | Logged when none of the above resolve - the request still proceeds |
|
|
169
80
|
|
|
81
|
+
### Understand body-parsing outcomes
|
|
170
82
|
| Condition | Result |
|
|
171
83
|
|-----------|--------|
|
|
172
|
-
| No `Content-Type` header |
|
|
173
|
-
|
|
|
174
|
-
| `Content-Type` includes `application/json` |
|
|
175
|
-
| `Content-Type` includes `multipart/form-data` |
|
|
176
|
-
| `Content-Type`
|
|
177
|
-
| Any other `Content-Type` (text, html, xml, etc.) |
|
|
178
|
-
| Parsing
|
|
179
|
-
|
|
180
|
-
### getIncomingIp() Utility
|
|
181
|
-
|
|
182
|
-
A utility function that attempts to extract the client IP address from the Hono context using runtime-specific connection info.
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
const getIncomingIp = (context: Context): string | null
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
**Runtime detection:**
|
|
189
|
-
- Uses `RuntimeModules.isBun()` from `@venizia/ignis-helpers` to detect the runtime
|
|
190
|
-
- On **Bun**: imports `getConnInfo` from `hono/bun`
|
|
191
|
-
- On **Node.js**: imports `getConnInfo` from `@hono/node-server/conninfo`
|
|
192
|
-
- Returns `connInfo.remote.address` if available, `null` otherwise
|
|
193
|
-
- Returns `null` if `getConnInfo` is unavailable or throws
|
|
194
|
-
|
|
195
|
-
#### IP Detection Priority
|
|
196
|
-
|
|
197
|
-
The middleware resolves the client IP using a three-step fallback chain:
|
|
198
|
-
|
|
199
|
-
| Priority | Source | Description |
|
|
200
|
-
|----------|--------|-------------|
|
|
201
|
-
| 1 | `getIncomingIp(context)` | Direct connection info from the runtime (Bun or Node.js) |
|
|
202
|
-
| 2 | `x-real-ip` header | Set by reverse proxies (e.g., Nginx `proxy_set_header X-Real-IP`) |
|
|
203
|
-
| 3 | `x-forwarded-for` header | Standard proxy header with original client IP |
|
|
84
|
+
| No `Content-Type` header | `null` |
|
|
85
|
+
| `Content-Length` is `'0'`, or no body stream present | `null` |
|
|
86
|
+
| `Content-Type` includes `application/json` | `req.json()` |
|
|
87
|
+
| `Content-Type` includes `multipart/form-data` or `application/x-www-form-urlencoded` | `req.parseBody()` |
|
|
88
|
+
| `Content-Type` is `application/octet-stream` | Raw body stream |
|
|
89
|
+
| Any other `Content-Type` (text, html, xml, etc.) | `req.text()` |
|
|
90
|
+
| Parsing throws for any content type | `'Malformed Body Payload'` (HTTP 400) |
|
|
204
91
|
|
|
205
|
-
|
|
92
|
+
## Reference
|
|
206
93
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
## Binding Keys
|
|
94
|
+
### Configuration
|
|
95
|
+
No user-configurable options - behavior is fully automatic.
|
|
210
96
|
|
|
97
|
+
### Binding keys
|
|
211
98
|
| Key | Constant | Type | Required | Default |
|
|
212
99
|
|-----|----------|------|----------|---------|
|
|
213
|
-
| `middlewares.RequestSpyMiddleware` | `RequestTrackerComponent.REQUEST_TRACKER_MW_BINDING_KEY` | `MiddlewareHandler` | Auto |
|
|
214
|
-
|
|
215
|
-
The key is constructed from `BindingNamespaces.MIDDLEWARE` (`'middlewares'`) + `RequestSpyMiddleware.name` (`'RequestSpyMiddleware'`). The component binds `RequestSpyMiddleware` as a singleton provider at this key during construction.
|
|
216
|
-
|
|
217
|
-
## Troubleshooting
|
|
218
|
-
|
|
219
|
-
### "Invalid middleware to init request tracker | Please check again binding value"
|
|
100
|
+
| `middlewares.RequestSpyMiddleware` | `RequestTrackerComponent.REQUEST_TRACKER_MW_BINDING_KEY` | `MiddlewareHandler` | Auto | Singleton provider, registered by the constructor |
|
|
220
101
|
|
|
221
|
-
|
|
102
|
+
The key is built as `BindingNamespaces.MIDDLEWARE` (`'middlewares'`) + `.` + `RequestSpyMiddleware.name`.
|
|
222
103
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
104
|
+
### RequestSpyMiddleware
|
|
105
|
+
```typescript
|
|
106
|
+
class RequestSpyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
|
|
107
|
+
static readonly REQUEST_ID_KEY = 'requestId';
|
|
108
|
+
private isDebugMode: boolean;
|
|
109
|
+
// ...
|
|
110
|
+
}
|
|
111
|
+
```
|
|
230
112
|
|
|
231
|
-
|
|
113
|
+
- Extends `BaseHelper` with scope `'SpyMW'`
|
|
114
|
+
- Constructor sets `isDebugMode = process.env.NODE_ENV?.toLowerCase() !== Environment.PRODUCTION`
|
|
115
|
+
- `value()` returns a Hono middleware built via `createMiddleware()` from `hono/factory`
|
|
232
116
|
|
|
233
|
-
|
|
117
|
+
### Component lifecycle
|
|
118
|
+
1. **`constructor()`** - Receives `BaseApplication` via DI. Defines the middleware binding as a singleton provider.
|
|
119
|
+
2. **`binding()`** - Registers `requestId()` on the server. Resolves the `RequestSpyMiddleware` binding, throwing if it cannot be resolved. Registers the resolved middleware on the server.
|
|
234
120
|
|
|
235
|
-
|
|
236
|
-
- `x-real-ip`
|
|
237
|
-
- `x-forwarded-for`
|
|
121
|
+
## Troubleshooting
|
|
238
122
|
|
|
239
|
-
|
|
123
|
+
| Symptom | Cause | Fix |
|
|
124
|
+
|---------|-------|-----|
|
|
125
|
+
| `Invalid middleware to init request tracker \| Please check again binding value` | `RequestSpyMiddleware` binding was unbound or overwritten before `binding()` ran | Don't unbind or replace `middlewares.RequestSpyMiddleware`; extend the component instead of removing its binding |
|
|
126
|
+
| `Malformed Body Payload` (400) | Body content didn't match its declared `Content-Type` (e.g., invalid JSON with `application/json`) | Ensure clients send body content that matches the `Content-Type` header |
|
|
240
127
|
|
|
241
|
-
## See
|
|
128
|
+
## See also
|
|
242
129
|
|
|
243
130
|
- **Guides:**
|
|
244
131
|
- [Components Overview](/guides/core-concepts/components) - Component system basics
|
|
@@ -253,3 +140,9 @@ If running without a proxy, ensure the runtime provides connection info (Bun doe
|
|
|
253
140
|
- **Best Practices:**
|
|
254
141
|
- [Troubleshooting Tips](/best-practices/troubleshooting-tips) - Debugging with request IDs
|
|
255
142
|
- [Deployment Strategies](/best-practices/deployment-strategies) - Production logging
|
|
143
|
+
|
|
144
|
+
**Files:**
|
|
145
|
+
|
|
146
|
+
- [`packages/core/src/components/request-tracker/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/request-tracker/component.ts) - `RequestTrackerComponent`
|
|
147
|
+
- [`packages/core/src/base/middlewares/request-spy/request-spy.middleware.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/middlewares/request-spy/request-spy.middleware.ts) - `RequestSpyMiddleware`
|
|
148
|
+
- [`packages/core/src/utilities/network.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/utilities/network.utility.ts) - `getIncomingIp()`
|
|
@@ -159,6 +159,177 @@ async function createNodeSocketIOHelper(opts: {
|
|
|
159
159
|
}
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
+
## Configuration Reference
|
|
163
|
+
|
|
164
|
+
### Default Server Options
|
|
165
|
+
|
|
166
|
+
The component applies these defaults if `SocketIOBindingKeys.SERVER_OPTIONS` is not bound or partially overridden:
|
|
167
|
+
|
|
168
|
+
| Option | Default | Description |
|
|
169
|
+
|--------|---------|-------------|
|
|
170
|
+
| `identifier` | `'SOCKET_IO_SERVER'` | Unique identifier for the helper instance |
|
|
171
|
+
| `path` | `'/io'` | URL path for Socket.IO handshake/polling |
|
|
172
|
+
| `cors.origin` | `'*'` | Allowed origins (restrict in production!) |
|
|
173
|
+
| `cors.methods` | `['GET', 'POST']` | Allowed HTTP methods for CORS preflight |
|
|
174
|
+
| `cors.preflightContinue` | `false` | Pass preflight to next handler |
|
|
175
|
+
| `cors.optionsSuccessStatus` | `204` | Status code for successful OPTIONS requests |
|
|
176
|
+
| `cors.credentials` | `true` | Allow cookies/auth headers |
|
|
177
|
+
| `perMessageDeflate.threshold` | `4096` | Minimum message size to compress (bytes) |
|
|
178
|
+
| `perMessageDeflate.concurrencyLimit` | `20` | Max concurrent compression operations |
|
|
179
|
+
| `perMessageDeflate.clientNoContextTakeover` | `true` | Client releases compression context after each message |
|
|
180
|
+
| `perMessageDeflate.serverNoContextTakeover` | `true` | Server releases compression context after each message |
|
|
181
|
+
| `perMessageDeflate.serverMaxWindowBits` | `10` | Server-side maximum window size (2^10 = 1KB) |
|
|
182
|
+
|
|
183
|
+
> [!WARNING]
|
|
184
|
+
> The default `cors.origin: '*'` is suitable for development only. In production, restrict this to your specific domains.
|
|
185
|
+
|
|
186
|
+
#### Full `DEFAULT_SERVER_OPTIONS`
|
|
187
|
+
```typescript
|
|
188
|
+
const DEFAULT_SERVER_OPTIONS: Partial<IServerOptions> = {
|
|
189
|
+
identifier: 'SOCKET_IO_SERVER',
|
|
190
|
+
path: '/io',
|
|
191
|
+
cors: {
|
|
192
|
+
origin: '*',
|
|
193
|
+
methods: ['GET', 'POST'],
|
|
194
|
+
preflightContinue: false,
|
|
195
|
+
optionsSuccessStatus: 204,
|
|
196
|
+
credentials: true,
|
|
197
|
+
},
|
|
198
|
+
perMessageDeflate: {
|
|
199
|
+
threshold: 4096,
|
|
200
|
+
zlibDeflateOptions: { chunkSize: 10 * 1024 },
|
|
201
|
+
zlibInflateOptions: { windowBits: 12, memLevel: 8 },
|
|
202
|
+
clientNoContextTakeover: true,
|
|
203
|
+
serverNoContextTakeover: true,
|
|
204
|
+
serverMaxWindowBits: 10,
|
|
205
|
+
concurrencyLimit: 20,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Custom Configuration
|
|
211
|
+
|
|
212
|
+
Bind custom server options before registering the component:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { SocketIOBindingKeys } from '@venizia/ignis/socket-io';
|
|
216
|
+
import type { ServerOptions } from 'socket.io';
|
|
217
|
+
|
|
218
|
+
const customOptions: Partial<ServerOptions> = {
|
|
219
|
+
path: '/socket.io',
|
|
220
|
+
cors: {
|
|
221
|
+
origin: ['https://myapp.com', 'https://admin.myapp.com'],
|
|
222
|
+
methods: ['GET', 'POST'],
|
|
223
|
+
credentials: true,
|
|
224
|
+
},
|
|
225
|
+
pingTimeout: 60000,
|
|
226
|
+
pingInterval: 25000,
|
|
227
|
+
maxHttpBufferSize: 1e6, // 1MB
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
this.bind<Partial<ServerOptions>>({
|
|
231
|
+
key: SocketIOBindingKeys.SERVER_OPTIONS,
|
|
232
|
+
}).toValue(customOptions);
|
|
233
|
+
|
|
234
|
+
this.component(SocketIOComponent);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
> [!NOTE]
|
|
238
|
+
> `identifier` is part of the component's `IServerOptions` interface (which extends `ServerOptions`), not Socket.IO's native options. Set it by including it in the bound options object.
|
|
239
|
+
|
|
240
|
+
### Binding Keys
|
|
241
|
+
|
|
242
|
+
All binding keys are available on `SocketIOBindingKeys`:
|
|
243
|
+
|
|
244
|
+
| Binding Key | Constant | Type | Required | Default |
|
|
245
|
+
|------------|----------|------|----------|---------|
|
|
246
|
+
| `@app/socket-io/server-options` | `SERVER_OPTIONS` | `Partial<ServerOptions>` | No | See defaults above |
|
|
247
|
+
| `@app/socket-io/redis-connection` | `REDIS_CONNECTION` | `IRedisHelper` (`RedisSingleHelper` / `RedisClusterHelper` / `RedisSentinelHelper`) | **Yes** | `null` |
|
|
248
|
+
| `@app/socket-io/authenticate-handler` | `AUTHENTICATE_HANDLER` | `TSocketIOAuthenticateFn` | **Yes** | `null` |
|
|
249
|
+
| `@app/socket-io/validate-room-handler` | `VALIDATE_ROOM_HANDLER` | `TSocketIOValidateRoomFn` | No | `null` |
|
|
250
|
+
| `@app/socket-io/client-connected-handler` | `CLIENT_CONNECTED_HANDLER` | `TSocketIOClientConnectedFn` | No | `null` |
|
|
251
|
+
| `@app/socket-io/instance` | `SOCKET_IO_INSTANCE` | `SocketIOServerHelper` | -- | *Set by component* |
|
|
252
|
+
|
|
253
|
+
> [!NOTE]
|
|
254
|
+
> `SOCKET_IO_INSTANCE` is **not** set by you -- the component creates and binds it automatically after the server starts. Inject it in services/controllers via a lazy getter (see [Usage & Examples](./usage#server-side-usage)).
|
|
255
|
+
|
|
256
|
+
### System Events and Rooms
|
|
257
|
+
|
|
258
|
+
Constants are exported from `@venizia/ignis-helpers/socket-io` as `SocketIOConstants` and used internally by both the component and the helper.
|
|
259
|
+
|
|
260
|
+
| Constant | Value | Description |
|
|
261
|
+
|----------|-------|--------------|
|
|
262
|
+
| `EVENT_PING` | `'ping'` | Keep-alive ping emitted at `pingInterval` (default: 30s) |
|
|
263
|
+
| `EVENT_CONNECT` | `'connection'` | New client connected (server-side event) |
|
|
264
|
+
| `EVENT_DISCONNECT` | `'disconnect'` | Client disconnected |
|
|
265
|
+
| `EVENT_JOIN` | `'join'` | Client requests to join room(s) |
|
|
266
|
+
| `EVENT_LEAVE` | `'leave'` | Client requests to leave room(s) |
|
|
267
|
+
| `EVENT_AUTHENTICATE` | `'authenticate'` | Client sends auth credentials |
|
|
268
|
+
| `EVENT_AUTHENTICATED` | `'authenticated'` | Auth success response sent to client |
|
|
269
|
+
| `EVENT_UNAUTHENTICATE` | `'unauthenticated'` | Auth failure response sent to client |
|
|
270
|
+
| `ROOM_DEFAULT` | `'io-default'` | Default room all authenticated clients join |
|
|
271
|
+
| `ROOM_NOTIFICATION` | `'io-notification'` | Notification broadcast room |
|
|
272
|
+
|
|
273
|
+
> [!TIP]
|
|
274
|
+
> Override the default rooms via the `defaultRooms` option on `SocketIOServerHelper` -- the values above are only the fallback.
|
|
275
|
+
|
|
276
|
+
#### Internal Constants (Server Helper)
|
|
277
|
+
|
|
278
|
+
Defined at module scope in the server helper, not exported, but they govern default behavior:
|
|
279
|
+
|
|
280
|
+
| Constant | Value | Description |
|
|
281
|
+
|----------|-------|--------------|
|
|
282
|
+
| `CLIENT_AUTHENTICATE_TIMEOUT` | `10_000` (10s) | Time allowed for a client to authenticate before forced disconnect |
|
|
283
|
+
| `CLIENT_PING_INTERVAL` | `30_000` (30s) | Interval between server-to-client ping emissions |
|
|
284
|
+
|
|
285
|
+
Both are overridable via the `authenticateTimeout` and `pingInterval` constructor options on `SocketIOServerHelper`.
|
|
286
|
+
|
|
287
|
+
### Client States
|
|
288
|
+
|
|
289
|
+
Each connected client tracks an authentication state that governs what actions are permitted:
|
|
290
|
+
|
|
291
|
+
| State | Constant | Description |
|
|
292
|
+
|-------|----------|--------------|
|
|
293
|
+
| `unauthorized` | `SocketIOClientStates.UNAUTHORIZED` | Initial state -- client must emit `authenticate` within the timeout (default: 10s) |
|
|
294
|
+
| `authenticating` | `SocketIOClientStates.AUTHENTICATING` | Auth in progress -- `authenticateFn` is executing |
|
|
295
|
+
| `authenticated` | `SocketIOClientStates.AUTHENTICATED` | Auth successful -- client can send/receive events and join rooms |
|
|
296
|
+
|
|
297
|
+
#### State Machine Diagram
|
|
298
|
+
```
|
|
299
|
+
+------------------+
|
|
300
|
+
connect ---------->| unauthorized |
|
|
301
|
+
+--------+---------+
|
|
302
|
+
| emit('authenticate')
|
|
303
|
+
+--------v---------+
|
|
304
|
+
| authenticating |
|
|
305
|
+
+---+----------+---+
|
|
306
|
+
success | | failure
|
|
307
|
+
+---------v--+ +-------v-----------+
|
|
308
|
+
|authenticated| | unauthorized |--> disconnect
|
|
309
|
+
+-------------+ +------------------+
|
|
310
|
+
^
|
|
311
|
+
timeout (10s)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### `SocketIOClientStates` Source
|
|
315
|
+
```typescript
|
|
316
|
+
export class SocketIOClientStates {
|
|
317
|
+
static readonly UNAUTHORIZED = 'unauthorized';
|
|
318
|
+
static readonly AUTHENTICATING = 'authenticating';
|
|
319
|
+
static readonly AUTHENTICATED = 'authenticated';
|
|
320
|
+
|
|
321
|
+
static readonly SCHEME_SET = new Set([
|
|
322
|
+
this.UNAUTHORIZED,
|
|
323
|
+
this.AUTHENTICATING,
|
|
324
|
+
this.AUTHENTICATED,
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
static isValid(input: string): input is TConstValue<typeof SocketIOClientStates> {
|
|
328
|
+
return this.SCHEME_SET.has(input);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
162
333
|
## Server Helper API Reference
|
|
163
334
|
|
|
164
335
|
### `SocketIOServerHelper` Constructor
|
|
@@ -244,9 +415,7 @@ Returns the `@socket.io/bun-engine` instance. **Throws** if the runtime is Node.
|
|
|
244
415
|
#### `getClients()`
|
|
245
416
|
|
|
246
417
|
```typescript
|
|
247
|
-
|
|
248
|
-
getClients(): Map<string, ISocketIOClient>
|
|
249
|
-
getClients(opts: { id: string }): ISocketIOClient | undefined
|
|
418
|
+
getClients(opts?: { id?: string }): ISocketIOClient | Map<string, ISocketIOClient> | undefined
|
|
250
419
|
```
|
|
251
420
|
|
|
252
421
|
When called without arguments, returns the full client map. When called with `{ id }`, returns the specific client entry or `undefined` if not found.
|
|
@@ -290,7 +459,7 @@ Disconnects a specific client and cleans up resources:
|
|
|
290
459
|
3. Removes the client from the `clients` map
|
|
291
460
|
4. Calls `socket.disconnect()` on the underlying Socket.IO socket
|
|
292
461
|
|
|
293
|
-
If
|
|
462
|
+
If `socket` is falsy, the method returns immediately -- no cleanup, no `socket.disconnect()` call. If the socket exists but is not tracked in the `clients` map, steps 1-3 are skipped but `socket.disconnect()` is still called.
|
|
294
463
|
|
|
295
464
|
#### `onClientConnect()`
|
|
296
465
|
|
|
@@ -338,7 +507,7 @@ send(opts: {
|
|
|
338
507
|
data: any; // Event payload
|
|
339
508
|
};
|
|
340
509
|
doLog?: boolean; // Log the emission (default: false)
|
|
341
|
-
|
|
510
|
+
callback?: () => void; // Executed via setImmediate after emit
|
|
342
511
|
})
|
|
343
512
|
```
|
|
344
513
|
|
|
@@ -347,7 +516,7 @@ Key behaviors:
|
|
|
347
516
|
- All messages are **compressed** via `emitter.compress(true)`
|
|
348
517
|
- If `destination` is provided and non-empty, sends via `sender.to(destination).emit(topic, data)`
|
|
349
518
|
- If `destination` is omitted/empty, broadcasts to **all** connected clients via `sender.emit(topic, data)`
|
|
350
|
-
-
|
|
519
|
+
- `callback` is executed asynchronously via `setImmediate()`, not after delivery confirmation
|
|
351
520
|
- Logging is opt-in (`doLog: true`) to avoid noise in high-throughput scenarios
|
|
352
521
|
|
|
353
522
|
#### `send()` Silent Failure Behavior
|
|
@@ -575,8 +744,8 @@ Manually disconnects the socket. No-op with an info log if the client is not ini
|
|
|
575
744
|
emit<T = unknown>(opts: {
|
|
576
745
|
topic: string;
|
|
577
746
|
data: T;
|
|
578
|
-
doLog?: boolean;
|
|
579
|
-
|
|
747
|
+
doLog?: boolean; // default: false
|
|
748
|
+
callback?: () => void;
|
|
580
749
|
}): void
|
|
581
750
|
```
|
|
582
751
|
|
|
@@ -586,7 +755,7 @@ Emits an event to the server.
|
|
|
586
755
|
- The socket is not connected (`statusCode: 400`, message: `"Invalid socket client state to emit"`)
|
|
587
756
|
- The `topic` is falsy (`statusCode: 400`, message: `"Topic is required to emit"`)
|
|
588
757
|
|
|
589
|
-
If `
|
|
758
|
+
If `callback` is provided, it is executed via `setImmediate()` (asynchronously, not after server acknowledgment). If `doLog` is `true`, logs the topic and data.
|
|
590
759
|
|
|
591
760
|
### `joinRooms()`
|
|
592
761
|
|
|
@@ -796,7 +965,7 @@ interface ISocketIOClient {
|
|
|
796
965
|
socket: IOSocket;
|
|
797
966
|
state: TSocketIOClientState; // 'unauthorized' | 'authenticating' | 'authenticated'
|
|
798
967
|
interval?: NodeJS.Timeout; // Ping interval (set after auth)
|
|
799
|
-
authenticateTimeout
|
|
968
|
+
authenticateTimeout?: NodeJS.Timeout; // Auth deadline (undefined after it's cleared)
|
|
800
969
|
}
|
|
801
970
|
```
|
|
802
971
|
|
|
@@ -853,7 +1022,7 @@ interface ISocketIOClient {
|
|
|
853
1022
|
socket: IOSocket;
|
|
854
1023
|
state: TSocketIOClientState;
|
|
855
1024
|
interval?: NodeJS.Timeout;
|
|
856
|
-
authenticateTimeout
|
|
1025
|
+
authenticateTimeout?: NodeJS.Timeout;
|
|
857
1026
|
}
|
|
858
1027
|
|
|
859
1028
|
// Redis client type alias
|
|
@@ -892,6 +1061,19 @@ type TSocketIOClientState = TConstValue<typeof SocketIOClientStates>;
|
|
|
892
1061
|
### Callback Types
|
|
893
1062
|
|
|
894
1063
|
```typescript
|
|
1064
|
+
// Handshake payload passed to the authenticate handler
|
|
1065
|
+
interface IHandshake {
|
|
1066
|
+
headers: IncomingHttpHeaders;
|
|
1067
|
+
time: string;
|
|
1068
|
+
address: string;
|
|
1069
|
+
xdomain: boolean;
|
|
1070
|
+
secure: boolean;
|
|
1071
|
+
issued: number;
|
|
1072
|
+
url: string;
|
|
1073
|
+
query: ParsedUrlQuery;
|
|
1074
|
+
auth: { [key: string]: any };
|
|
1075
|
+
}
|
|
1076
|
+
|
|
895
1077
|
// Server authentication handler
|
|
896
1078
|
type TSocketIOAuthenticateFn = (args: IHandshake) => ValueOrPromise<boolean>;
|
|
897
1079
|
|
|
@@ -1046,6 +1228,6 @@ clientHelper.shutdown()
|
|
|
1046
1228
|
|
|
1047
1229
|
## See Also
|
|
1048
1230
|
|
|
1049
|
-
- [Setup & Configuration](./) -- Quick reference,
|
|
1050
|
-
- [Usage & Examples](./usage) --
|
|
1231
|
+
- [Setup & Configuration](./) -- Quick reference, required bindings, how it works
|
|
1232
|
+
- [Usage & Examples](./usage) -- Full setup steps, server-side usage, client helper, advanced patterns
|
|
1051
1233
|
- [Error Reference](./errors) -- Error conditions and troubleshooting
|
|
@@ -116,6 +116,6 @@ socket.on('authenticated', (data) => {
|
|
|
116
116
|
|
|
117
117
|
## See Also
|
|
118
118
|
|
|
119
|
-
- [Setup & Configuration](./) -- Quick reference,
|
|
120
|
-
- [Usage & Examples](./usage) --
|
|
121
|
-
- [API Reference](./api) -- Architecture, method signatures, internals, types
|
|
119
|
+
- [Setup & Configuration](./) -- Quick reference, required bindings, how it works
|
|
120
|
+
- [Usage & Examples](./usage) -- Full setup steps, server-side usage, client helper, advanced patterns
|
|
121
|
+
- [API Reference](./api) -- Architecture, configuration reference, method signatures, internals, types
|