exisjs 0.7.12 → 0.7.14

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.
Files changed (132) hide show
  1. package/README.md +195 -39
  2. package/dist/cli/commands/run.d.ts.map +1 -1
  3. package/dist/cli/commands/run.js +3 -1
  4. package/dist/cli/commands/run.js.map +1 -1
  5. package/dist/config/types.d.ts +2 -2
  6. package/dist/config/types.d.ts.map +1 -1
  7. package/dist/decorators/params.d.ts +21 -0
  8. package/dist/decorators/params.d.ts.map +1 -1
  9. package/dist/decorators/params.js +21 -1
  10. package/dist/decorators/params.js.map +1 -1
  11. package/dist/di/container.d.ts +15 -6
  12. package/dist/di/container.d.ts.map +1 -1
  13. package/dist/di/container.js +26 -3
  14. package/dist/di/container.js.map +1 -1
  15. package/dist/di/index.d.ts +1 -1
  16. package/dist/di/index.d.ts.map +1 -1
  17. package/dist/di/index.js.map +1 -1
  18. package/dist/di/inject.d.ts +2 -1
  19. package/dist/di/inject.d.ts.map +1 -1
  20. package/dist/di/inject.js +2 -1
  21. package/dist/di/inject.js.map +1 -1
  22. package/dist/docs/ARCHITECTURE.md +130 -78
  23. package/dist/docs/BENCHMARKS.md +4 -4
  24. package/dist/docs/content/README.md +43 -44
  25. package/dist/docs/content/boundaries.mdx +138 -61
  26. package/dist/docs/content/cli/commands.mdx +151 -128
  27. package/dist/docs/content/cli/overview.mdx +61 -47
  28. package/dist/docs/content/cli/usage.mdx +107 -30
  29. package/dist/docs/content/config.mdx +145 -158
  30. package/dist/docs/content/controllers.mdx +193 -464
  31. package/dist/docs/content/cron.mdx +183 -69
  32. package/dist/docs/content/decorators.mdx +449 -154
  33. package/dist/docs/content/errors.mdx +202 -305
  34. package/dist/docs/content/exisjs-mcp.mdx +59 -49
  35. package/dist/docs/content/installation.mdx +243 -157
  36. package/dist/docs/content/introduction.mdx +220 -48
  37. package/dist/docs/content/middleware.mdx +171 -497
  38. package/dist/docs/content/modules.mdx +141 -83
  39. package/dist/docs/content/providers.mdx +216 -180
  40. package/dist/docs/content/requests.mdx +168 -488
  41. package/dist/docs/content/responses.mdx +200 -533
  42. package/dist/docs/content/routing.mdx +235 -598
  43. package/dist/docs/content/security.mdx +233 -57
  44. package/dist/docs/content/structure.mdx +151 -240
  45. package/dist/docs/content/swagger.mdx +192 -91
  46. package/dist/docs/content/testing.mdx +205 -74
  47. package/dist/docs/content/validation.mdx +479 -341
  48. package/dist/docs/reference/README.md +34 -2
  49. package/dist/docs/reference/config.mdx +277 -75
  50. package/dist/docs/reference/context.mdx +197 -106
  51. package/dist/docs/reference/cron.mdx +266 -152
  52. package/dist/docs/reference/database.mdx +135 -53
  53. package/dist/docs/reference/decorators.mdx +234 -116
  54. package/dist/docs/reference/di.mdx +146 -112
  55. package/dist/docs/reference/error.mdx +155 -178
  56. package/dist/docs/reference/exis.mdx +141 -70
  57. package/dist/docs/reference/logger.mdx +110 -126
  58. package/dist/docs/reference/middleware.mdx +235 -158
  59. package/dist/docs/reference/plugin.mdx +136 -64
  60. package/dist/docs/reference/router.mdx +275 -293
  61. package/dist/docs/reference/sanitize.mdx +91 -87
  62. package/dist/docs/reference/swagger.mdx +280 -100
  63. package/dist/docs/reference/testing.mdx +223 -89
  64. package/dist/docs/reference/validator.mdx +194 -206
  65. package/dist/lib/start-server.js +22 -8
  66. package/dist/lib/start-server.js.map +1 -1
  67. package/dist/module/module.d.ts +6 -3
  68. package/dist/module/module.d.ts.map +1 -1
  69. package/dist/module/module.js +17 -3
  70. package/dist/module/module.js.map +1 -1
  71. package/dist/router/controller-registrar.d.ts.map +1 -1
  72. package/dist/router/controller-registrar.js +26 -4
  73. package/dist/router/controller-registrar.js.map +1 -1
  74. package/dist/router/scanner/fs-scanner.d.ts.map +1 -1
  75. package/dist/router/scanner/fs-scanner.js +22 -0
  76. package/dist/router/scanner/fs-scanner.js.map +1 -1
  77. package/dist/router/types.d.ts +6 -4
  78. package/dist/router/types.d.ts.map +1 -1
  79. package/dist/server/app.d.ts +3 -1
  80. package/dist/server/app.d.ts.map +1 -1
  81. package/dist/server/app.js +24 -8
  82. package/dist/server/app.js.map +1 -1
  83. package/dist/server/bootstrapper.d.ts.map +1 -1
  84. package/dist/server/bootstrapper.js +19 -0
  85. package/dist/server/bootstrapper.js.map +1 -1
  86. package/dist/server/request.d.ts +2 -0
  87. package/dist/server/request.d.ts.map +1 -1
  88. package/dist/server/request.js +11 -0
  89. package/dist/server/request.js.map +1 -1
  90. package/dist/server/response.d.ts +10 -5
  91. package/dist/server/response.d.ts.map +1 -1
  92. package/dist/server/response.js +65 -18
  93. package/dist/server/response.js.map +1 -1
  94. package/dist/swagger/generator.d.ts.map +1 -1
  95. package/dist/swagger/generator.js +130 -10
  96. package/dist/swagger/generator.js.map +1 -1
  97. package/dist/telemetry/index.d.ts +12 -0
  98. package/dist/telemetry/index.d.ts.map +1 -0
  99. package/dist/telemetry/index.js +121 -0
  100. package/dist/telemetry/index.js.map +1 -0
  101. package/dist/testing/client.d.ts +1 -0
  102. package/dist/testing/client.d.ts.map +1 -1
  103. package/dist/testing/client.js +4 -1
  104. package/dist/testing/client.js.map +1 -1
  105. package/dist/testing/index.d.ts.map +1 -1
  106. package/dist/testing/index.js +11 -4
  107. package/dist/testing/index.js.map +1 -1
  108. package/dist/validator/builder.d.ts +26 -1
  109. package/dist/validator/builder.d.ts.map +1 -1
  110. package/dist/validator/builder.js +31 -0
  111. package/dist/validator/builder.js.map +1 -1
  112. package/dist/validator/engine.d.ts +23 -0
  113. package/dist/validator/engine.d.ts.map +1 -1
  114. package/dist/validator/engine.js +228 -31
  115. package/dist/validator/engine.js.map +1 -1
  116. package/dist/validator/index.d.ts +8 -4
  117. package/dist/validator/index.d.ts.map +1 -1
  118. package/dist/validator/index.js +4 -1
  119. package/dist/validator/index.js.map +1 -1
  120. package/dist/validator/tex-types.d.ts +19 -4
  121. package/dist/validator/tex-types.d.ts.map +1 -1
  122. package/dist/validator/tex-types.js +102 -0
  123. package/dist/validator/tex-types.js.map +1 -1
  124. package/dist/validator/tex.d.ts +1 -1
  125. package/dist/validator/tex.d.ts.map +1 -1
  126. package/dist/validator/tex.js +3 -1
  127. package/dist/validator/tex.js.map +1 -1
  128. package/dist/validator/types.d.ts +1 -0
  129. package/dist/validator/types.d.ts.map +1 -1
  130. package/package.json +46 -14
  131. package/dist/docs/content/dataloader.mdx +0 -170
  132. package/dist/docs/reference/dataloader.mdx +0 -91
@@ -1,179 +1,270 @@
1
1
  ---
2
- title: "Context & Requests"
3
- description: "API Reference for ExisJS Context, Requests, and Async Hooks."
2
+ title: "exisjs/context"
3
+ description: "API Reference for ExisJS AsyncLocalStorage request context, getRequest, getResponse, getContext, setContext, and after() lifecycle callbacks."
4
+ nextUrl: "/docs/reference/cron"
5
+ nextTitle: "Cron Reference"
4
6
  ---
5
7
 
6
- # Context & Requests
8
+ # `exisjs/context`
7
9
 
8
- ExisJS leverages Node.js `AsyncLocalStorage` to provide request-scoped state without needing to manually pass request objects down through your entire service layer. This enables globally accessible contextual data anywhere in your application lifecycle.
10
+ The `exisjs/context` primitives are built on Node.js `AsyncLocalStorage` (`node:async_hooks`). They provide request-scoped contextual state across asynchronous call stacks without manual parameter passing through deep service, repository, or DataLoader layers.
9
11
 
10
12
  ---
11
13
 
12
- ## Global Request & Response
14
+ ## Module Exports
13
15
 
14
- In deeply nested utilities (like DataLoaders or generic services), it is often cumbersome to pass the `req` object through every function call. ExisJS allows you to pull the active HTTP Request and Response objects directly from the asynchronous context.
16
+ Context functions are accessible from `exisjs/router` and `exisjs/app`:
15
17
 
16
- ### `getRequest()`
18
+ ```typescript
19
+ import {
20
+ getContext,
21
+ setContext,
22
+ getRequest,
23
+ getResponse,
24
+ after,
25
+ } from 'exisjs/router'
17
26
 
18
- Returns the active ExisJS `Request` object.
27
+ import { getActiveApp, getApp } from 'exisjs/app'
28
+ import type { Request, Response } from 'exisjs/router'
29
+ ```
19
30
 
20
- ```typescript filename="src/loaders/index.ts"
21
- import { defineLoaders, Dataloader } from 'exisjs/dataloader'
22
- import { getRequest } from 'exisjs/router'
31
+ ---
23
32
 
24
- export const { loaderMiddleware, getLoaders } = defineLoaders({
25
- user: () => new Dataloader(
26
- async (keys: readonly string[]) => {
27
- // Access the request deep inside the dataloader!
28
- const req = getRequest()
29
- console.log(`Batch fetching users for RequestId: ${req.requestId}`)
30
-
31
- return keys.map(key => db.users.find(key))
32
- }
33
- )
34
- })
33
+ ## API Reference
34
+
35
+ ### `getContext<T>()`
36
+
37
+ Retrieves the mutable request state dictionary stored in the active asynchronous execution context.
38
+
39
+ ```typescript
40
+ function getContext<T = Record<string, any>>(): T
35
41
  ```
36
42
 
37
- ### `getResponse()`
43
+ #### Returns
38
44
 
39
- Returns the active ExisJS `Response` object.
45
+ | Type | Description |
46
+ | :--- | :--- |
47
+ | `T` | The typed request state object attached to the current asynchronous execution context. |
40
48
 
41
- ```typescript filename="src/utils/auth.ts"
42
- import { getResponse } from 'exisjs/router'
49
+ #### Throws
43
50
 
44
- export function forceLogout() {
45
- const res = getResponse()
46
- res.clearCookie('session_id')
47
- }
48
- ```
51
+ Throws an `Error` if invoked outside of an active HTTP request lifecycle.
49
52
 
50
- ### `getActiveApp()`
53
+ #### Example
51
54
 
52
- Returns the active ExisJS `App` instance processing the request. This is useful for resolving dependencies or accessing the application logger from deep within a service.
55
+ ```typescript filename="src/services/tenant.service.ts"
56
+ import { getContext } from 'exisjs/router'
53
57
 
54
- ```typescript filename="src/services/billing.ts"
55
- import { getActiveApp } from 'exisjs/app'
58
+ interface TenantState {
59
+ tenantId: string
60
+ userId: string
61
+ roles: string[]
62
+ }
56
63
 
57
- export function processRefund(transactionId: string) {
58
- const app = getActiveApp()
59
- const stripe = app.resolve('StripeClient')
60
-
61
- app.log.info(`Processing refund for ${transactionId}`)
62
- return stripe.refunds.create({ charge: transactionId })
64
+ export function getCurrentTenant(): string {
65
+ const state = getContext<TenantState>()
66
+ return state.tenantId
63
67
  }
64
68
  ```
65
69
 
66
70
  ---
67
71
 
68
- ## Async Context API
72
+ ### `setContext(key, value)`
69
73
 
70
- The core context API allows you to read and write arbitrary state associated with the current HTTP request.
74
+ Stores a key-value entry into the current request state dictionary.
71
75
 
72
- ### `setContext(key, value)`
76
+ ```typescript
77
+ function setContext(key: string, value: any): void
78
+ ```
79
+
80
+ #### Parameters
81
+
82
+ | Parameter | Type | Description |
83
+ | :--- | :--- | :--- |
84
+ | `key` | `string` | The state key identifier. |
85
+ | `value` | `any` | The value to store in the context. |
73
86
 
74
- Sets a value in the current request's state object. This is typically used by middleware to attach parsed data, user sessions, or tenant IDs.
87
+ #### Example
75
88
 
76
- ```typescript filename="src/middleware/tenant.ts"
89
+ ```typescript filename="src/http/boundary.ts"
90
+ import { defineBoundary } from 'exisjs/router'
77
91
  import { setContext } from 'exisjs/router'
78
92
 
79
- export async function tenantMiddleware(req, res, next) {
80
- const tenantId = req.headers['x-tenant-id']
81
-
82
- // Store the tenant ID in the async context
83
- setContext('tenantId', tenantId)
84
- next()
85
- }
93
+ export default defineBoundary({
94
+ async handle({ req, next }) {
95
+ const tenantId = (req.headers['x-tenant-id'] as string) || 'default'
96
+ setContext('tenantId', tenantId)
97
+ return next()
98
+ },
99
+ })
86
100
  ```
87
101
 
88
- ### `getContext<T>()`
102
+ ---
89
103
 
90
- Retrieves the current request's state object.
104
+ ### `getRequest()`
91
105
 
92
- ```typescript filename="src/services/database.ts"
93
- import { getContext } from 'exisjs/router'
106
+ Retrieves the active HTTP `Request` object from the asynchronous execution context.
94
107
 
95
- function getTenantDatabase() {
96
- // Retrieve the state set by the middleware!
97
- const state = getContext<{ tenantId: string }>()
98
-
99
- return new DatabaseConnection({ schema: state.tenantId })
100
- }
108
+ ```typescript
109
+ function getRequest(): Request
110
+ ```
111
+
112
+ #### Returns
113
+
114
+ | Type | Description |
115
+ | :--- | :--- |
116
+ | `Request` | The current request instance containing `headers`, `params`, `query`, `body`, `ip`, and contextual properties. |
117
+
118
+ #### Example
119
+
120
+ ```typescript filename="src/loaders/user.loader.ts"
121
+ import { Dataloader } from 'exisjs/dataloader'
122
+ import { getRequest } from 'exisjs/router'
123
+
124
+ export const userLoader = new Dataloader(async (userIds: readonly string[]) => {
125
+ const req = getRequest()
126
+ const traceId = req.headers['x-trace-id']
127
+
128
+ return db.users.findMany({
129
+ where: { id: { in: [...userIds] } },
130
+ metadata: { traceId },
131
+ })
132
+ })
101
133
  ```
102
134
 
103
135
  ---
104
136
 
105
- ## Background Tasks
137
+ ### `getResponse()`
138
+
139
+ Retrieves the active HTTP `Response` object from the asynchronous execution context.
140
+
141
+ ```typescript
142
+ function getResponse(): Response
143
+ ```
144
+
145
+ #### Returns
146
+
147
+ | Type | Description |
148
+ | :--- | :--- |
149
+ | `Response` | The current response instance containing response headers, status codes, and streaming controls. |
150
+
151
+ #### Example
152
+
153
+ ```typescript filename="src/services/auth.service.ts"
154
+ import { getResponse } from 'exisjs/router'
155
+
156
+ export function clearAuthCookie(): void {
157
+ const res = getResponse()
158
+ res.clearCookie('access_token', {
159
+ path: '/',
160
+ httpOnly: true,
161
+ secure: process.env.NODE_ENV === 'production',
162
+ })
163
+ }
164
+ ```
165
+
166
+ ---
106
167
 
107
168
  ### `after(callback)`
108
169
 
109
- Queues a function to execute in the background **after** the response has been completely sent to the client. This is the perfect place to put non-blocking tasks like sending welcome emails, flushing analytics, or writing audit logs.
170
+ Registers an asynchronous callback to be executed in the background **after** the HTTP response has been sent to the client.
110
171
 
111
- ```typescript filename="src/http/users/route.ts"
172
+ ```typescript
173
+ function after(callback: () => void | Promise<void>): void
174
+ ```
175
+
176
+ #### Parameters
177
+
178
+ | Parameter | Type | Description |
179
+ | :--- | :--- | :--- |
180
+ | `callback` | `() => void \| Promise<void>` | Non-blocking function to execute after the response finishes. |
181
+
182
+ #### Behavior
183
+
184
+ - Callbacks run asynchronously without delaying the client's HTTP response.
185
+ - If a callback rejects or throws an error, the error is caught and forwarded to the application logger (`logger.error`). It will not crash the Node.js process.
186
+ - All queued callbacks are drained deterministically during request context cleanup.
187
+
188
+ #### Example
189
+
190
+ ```typescript filename="src/http/orders/route.ts"
112
191
  import { controller, route } from 'exisjs/router'
113
192
  import { after } from 'exisjs/router'
114
193
  import { tex } from 'exisjs/validator'
194
+ import { sendOrderConfirmationEmail } from '../../services/mail'
115
195
 
116
196
  export default controller({
117
- createUser: route.post('/', {
118
- body: tex.object({ email: tex.email() }),
197
+ create: route.post('/', {
198
+ body: tex.object({
199
+ orderId: tex.string(),
200
+ email: tex.email(),
201
+ }),
119
202
  async handle({ body }) {
120
-
121
- // Respond to the client immediately
203
+ const order = await db.orders.create({ data: body })
204
+
205
+ // Queue background job after client receives 201 response
122
206
  after(async () => {
123
- // This runs after the client has received the HTTP response
124
- await emailService.sendWelcome(body.email)
207
+ await sendOrderConfirmationEmail(order.id, body.email)
125
208
  })
126
209
 
127
- return { success: true, email: body.email }
128
- }
129
- })
210
+ return { success: true, orderId: order.id }
211
+ },
212
+ }),
130
213
  })
131
214
  ```
132
215
 
216
+ ---
133
217
 
134
- > [!NOTE]
135
- > Tasks queued with `after()` do not block the HTTP request. If the task throws an error, it will be logged by the application logger, but it will not crash the server or affect the already-completed client response.
218
+ ### `getActiveApp()` / `getApp()`
136
219
 
137
- ---
220
+ Retrieves the active ExisJS `App` instance processing the current request lifecycle.
138
221
 
139
- ## Class-Based Context (OOP)
222
+ ```typescript
223
+ function getActiveApp(): App
224
+ function getApp(): App
225
+ ```
140
226
 
141
- If you are using the Object-Oriented paradigm, ExisJS provides dedicated decorators to inject the request and response directly into your controller methods. This removes the need to manually call `getRequest()` or `getResponse()` in the controller layer.
227
+ #### Returns
142
228
 
143
- ### `@Req()` & `@Res()`
229
+ | Type | Description |
230
+ | :--- | :--- |
231
+ | `App` | The running application instance containing the DI container (`app.container`), logger (`app.log`), and configuration. |
144
232
 
145
- Inject the raw ExisJS request or response objects.
233
+ #### Example
146
234
 
147
- ```typescript filename="src/http/users/route.ts"
148
- import { Controller, Get, Req, Res } from 'exisjs/decorators'
149
- import type { Request, Response } from 'exisjs/router'
235
+ ```typescript filename="src/services/audit.service.ts"
236
+ import { getActiveApp } from 'exisjs/app'
150
237
 
151
- @Controller('/users')
152
- export default class UserController {
153
-
154
- @Get('/ip')
155
- getIp(@Req() req: Request) {
156
- return { ip: req.ip }
157
- }
158
-
159
- @Get('/logout')
160
- logout(@Res() res: Response) {
161
- res.clearCookie('session_id')
162
- res.json({ success: true })
163
- }
238
+ export function logAuditEvent(action: string, payload: Record<string, any>): void {
239
+ const app = getActiveApp()
240
+ app.log.info({ action, payload }, 'Audit event recorded')
164
241
  }
165
242
  ```
166
243
 
167
- > [!TIP]
168
- > For deep services inside an OOP architecture, you can still seamlessly use `getContext()`, `getRequest()`, or `after()` from `exisjs/router` to access the async context without prop-drilling!
169
-
170
244
  ---
171
245
 
172
- ## Context Isolation & Memory Leak Safety
246
+ ## Lifecycle & Memory Safety
173
247
 
174
- In high-concurrency micro-benchmarks or long-lived streaming connections (SSE / WebSockets), holding references in `AsyncLocalStorage` without explicit teardown can lead to memory retention. ExisJS enforces a deterministic context cleanup lifecycle:
248
+ ExisJS implements deterministic context lifecycle management to prevent memory retention in long-lived or high-concurrency environments:
175
249
 
176
- 1. **Deterministic Teardown (`cleanupContext`)**: When a response emits the `'finish'` or `'close'` event, ExisJS automatically executes all remaining `after()` callbacks, clears request-scoped dependency injection instances (`diCache`), purges the `state` dictionary, and unlinks circular references (`req = null!`, `res = null!`, `app = null!`).
177
- 2. **`try / finally` Dispatch Boundary**: The router wraps every handler dispatch in a `try ... finally` boundary, guaranteeing that unhandled exceptions or rejected promises cannot leak state into subsequent async continuations.
178
- 3. **Recycling Pool Memory Scrubbing**: When recycled `ExisRequest` and `ExisResponse` instances return to the internal object pool, their internal dependency injection caches and request metadata are scrubbed clean.
250
+ ```
251
+ Incoming Request
252
+ │
253
+ ▼
254
+ ExecutionContext Initialized (AsyncLocalStorage.run)
255
+ │
256
+ ├─► Middleware & Boundaries (setContext)
257
+ ├─► Route Handler Execution (getRequest / getResponse / getContext)
258
+ ├─► Response Dispatched to Client
259
+ │
260
+ ▼
261
+ cleanupContext Execution
262
+ ├─► Drain & execute all after() background callbacks
263
+ ├─► Clear request-scoped DI cache (diCache.clear())
264
+ ├─► Purge request state keys (delete state[key])
265
+ └─► Unlink circular references (req = null, res = null, app = null)
266
+ ```
179
267
 
268
+ 1. **Deterministic Teardown (`cleanupContext`)**: When a response emits the `'finish'` or `'close'` event, ExisJS executes all queued `after()` callbacks, empties the request-scoped dependency injection cache, and clears the context state.
269
+ 2. **Circular Reference Scrubbing**: Circular references between request, response, and app instances are set to `null` on cleanup to allow immediate V8 Garbage Collection.
270
+ 3. **Async Continuation Isolation**: Context state is isolated per request branch, preventing state leakage across concurrent HTTP requests.