exisjs 0.7.11 → 0.7.13
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 +195 -39
- package/dist/cli/commands/dev.d.ts +4 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +136 -32
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/run.d.ts +7 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +99 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.js +29 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +18 -3
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/routes.d.ts +6 -0
- package/dist/cli/routes.d.ts.map +1 -1
- package/dist/cli/routes.js +224 -135
- package/dist/cli/routes.js.map +1 -1
- package/dist/config/config.d.ts +2 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +30 -9
- package/dist/config/types.d.ts.map +1 -1
- package/dist/database/lifecycle.d.ts +6 -1
- package/dist/database/lifecycle.d.ts.map +1 -1
- package/dist/database/lifecycle.js +15 -4
- package/dist/database/lifecycle.js.map +1 -1
- package/dist/decorators/params.d.ts +21 -0
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +21 -1
- package/dist/decorators/params.js.map +1 -1
- package/dist/di/container.d.ts +15 -6
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +26 -3
- package/dist/di/container.js.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/index.js.map +1 -1
- package/dist/di/inject.d.ts +2 -1
- package/dist/di/inject.d.ts.map +1 -1
- package/dist/di/inject.js +2 -1
- package/dist/di/inject.js.map +1 -1
- package/dist/docs/BENCHMARKS.md +4 -4
- package/dist/docs/content/README.md +43 -44
- package/dist/docs/content/boundaries.mdx +141 -48
- package/dist/docs/content/cli/commands.mdx +161 -107
- package/dist/docs/content/cli/overview.mdx +61 -46
- package/dist/docs/content/cli/usage.mdx +107 -30
- package/dist/docs/content/config.mdx +145 -147
- package/dist/docs/content/controllers.mdx +193 -464
- package/dist/docs/content/cron.mdx +183 -69
- package/dist/docs/content/decorators.mdx +449 -154
- package/dist/docs/content/errors.mdx +202 -305
- package/dist/docs/content/exisjs-mcp.mdx +59 -49
- package/dist/docs/content/installation.mdx +243 -157
- package/dist/docs/content/introduction.mdx +220 -48
- package/dist/docs/content/middleware.mdx +171 -497
- package/dist/docs/content/modules.mdx +141 -83
- package/dist/docs/content/providers.mdx +216 -180
- package/dist/docs/content/requests.mdx +168 -488
- package/dist/docs/content/responses.mdx +200 -533
- package/dist/docs/content/routing.mdx +239 -558
- package/dist/docs/content/security.mdx +233 -57
- package/dist/docs/content/structure.mdx +151 -240
- package/dist/docs/content/swagger.mdx +192 -91
- package/dist/docs/content/testing.mdx +205 -74
- package/dist/docs/content/validation.mdx +479 -341
- package/dist/docs/reference/README.md +34 -2
- package/dist/docs/reference/config.mdx +277 -73
- package/dist/docs/reference/context.mdx +197 -106
- package/dist/docs/reference/cron.mdx +266 -152
- package/dist/docs/reference/database.mdx +135 -53
- package/dist/docs/reference/decorators.mdx +234 -116
- package/dist/docs/reference/di.mdx +146 -112
- package/dist/docs/reference/error.mdx +155 -178
- package/dist/docs/reference/exis.mdx +141 -70
- package/dist/docs/reference/logger.mdx +110 -126
- package/dist/docs/reference/middleware.mdx +235 -158
- package/dist/docs/reference/plugin.mdx +136 -64
- package/dist/docs/reference/router.mdx +277 -251
- package/dist/docs/reference/sanitize.mdx +91 -87
- package/dist/docs/reference/swagger.mdx +280 -100
- package/dist/docs/reference/testing.mdx +223 -89
- package/dist/docs/reference/validator.mdx +194 -206
- package/dist/lib/inspect-routes.d.ts +2 -0
- package/dist/lib/inspect-routes.d.ts.map +1 -0
- package/dist/lib/inspect-routes.js +148 -0
- package/dist/lib/inspect-routes.js.map +1 -0
- package/dist/lib/start-server.js +13 -8
- package/dist/lib/start-server.js.map +1 -1
- package/dist/module/module.d.ts +6 -3
- package/dist/module/module.d.ts.map +1 -1
- package/dist/module/module.js +17 -3
- package/dist/module/module.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +26 -4
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/scanner/cron-mounter.d.ts.map +1 -1
- package/dist/router/scanner/cron-mounter.js +9 -2
- package/dist/router/scanner/cron-mounter.js.map +1 -1
- package/dist/server/app.d.ts +3 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +29 -8
- package/dist/server/app.js.map +1 -1
- package/dist/server/define.d.ts.map +1 -1
- package/dist/server/define.js +5 -0
- package/dist/server/define.js.map +1 -1
- package/dist/server/request.d.ts +2 -0
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +11 -0
- package/dist/server/request.js.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/swagger/generator.js +130 -10
- package/dist/swagger/generator.js.map +1 -1
- package/dist/telemetry/index.d.ts +12 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +121 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/testing/client.d.ts +1 -0
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +4 -1
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +20 -4
- package/dist/testing/index.js.map +1 -1
- package/dist/validator/builder.d.ts +26 -1
- package/dist/validator/builder.d.ts.map +1 -1
- package/dist/validator/builder.js +31 -0
- package/dist/validator/builder.js.map +1 -1
- package/dist/validator/engine.d.ts +23 -0
- package/dist/validator/engine.d.ts.map +1 -1
- package/dist/validator/engine.js +228 -31
- package/dist/validator/engine.js.map +1 -1
- package/dist/validator/index.d.ts +8 -4
- package/dist/validator/index.d.ts.map +1 -1
- package/dist/validator/index.js +4 -1
- package/dist/validator/index.js.map +1 -1
- package/dist/validator/tex-types.d.ts +15 -0
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/tex-types.js +102 -0
- package/dist/validator/tex-types.js.map +1 -1
- package/dist/validator/tex.d.ts +1 -1
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +3 -1
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +1 -0
- package/dist/validator/types.d.ts.map +1 -1
- package/package.json +47 -14
- package/dist/docs/content/dataloader.mdx +0 -170
- package/dist/docs/reference/dataloader.mdx +0 -91
|
@@ -1,179 +1,270 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
description: "API Reference for ExisJS
|
|
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
|
-
#
|
|
8
|
+
# `exisjs/context`
|
|
7
9
|
|
|
8
|
-
|
|
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
|
-
##
|
|
14
|
+
## Module Exports
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
Context functions are accessible from `exisjs/router` and `exisjs/app`:
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
```typescript
|
|
19
|
+
import {
|
|
20
|
+
getContext,
|
|
21
|
+
setContext,
|
|
22
|
+
getRequest,
|
|
23
|
+
getResponse,
|
|
24
|
+
after,
|
|
25
|
+
} from 'exisjs/router'
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
import { getActiveApp, getApp } from 'exisjs/app'
|
|
28
|
+
import type { Request, Response } from 'exisjs/router'
|
|
29
|
+
```
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
import { defineLoaders, Dataloader } from 'exisjs/dataloader'
|
|
22
|
-
import { getRequest } from 'exisjs/router'
|
|
31
|
+
---
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
43
|
+
#### Returns
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
| Type | Description |
|
|
46
|
+
| :--- | :--- |
|
|
47
|
+
| `T` | The typed request state object attached to the current asynchronous execution context. |
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
import { getResponse } from 'exisjs/router'
|
|
49
|
+
#### Throws
|
|
43
50
|
|
|
44
|
-
|
|
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
|
-
|
|
53
|
+
#### Example
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
```typescript filename="src/services/tenant.service.ts"
|
|
56
|
+
import { getContext } from 'exisjs/router'
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
interface TenantState {
|
|
59
|
+
tenantId: string
|
|
60
|
+
userId: string
|
|
61
|
+
roles: string[]
|
|
62
|
+
}
|
|
56
63
|
|
|
57
|
-
export function
|
|
58
|
-
const
|
|
59
|
-
|
|
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
|
-
|
|
72
|
+
### `setContext(key, value)`
|
|
69
73
|
|
|
70
|
-
|
|
74
|
+
Stores a key-value entry into the current request state dictionary.
|
|
71
75
|
|
|
72
|
-
|
|
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
|
-
|
|
87
|
+
#### Example
|
|
75
88
|
|
|
76
|
-
```typescript filename="src/
|
|
89
|
+
```typescript filename="src/http/boundary.ts"
|
|
90
|
+
import { defineBoundary } from 'exisjs/router'
|
|
77
91
|
import { setContext } from 'exisjs/router'
|
|
78
92
|
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
102
|
+
---
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
### `getRequest()`
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
import { getContext } from 'exisjs/router'
|
|
106
|
+
Retrieves the active HTTP `Request` object from the asynchronous execution context.
|
|
94
107
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
118
|
-
body: tex.object({
|
|
197
|
+
create: route.post('/', {
|
|
198
|
+
body: tex.object({
|
|
199
|
+
orderId: tex.string(),
|
|
200
|
+
email: tex.email(),
|
|
201
|
+
}),
|
|
119
202
|
async handle({ body }) {
|
|
120
|
-
|
|
121
|
-
|
|
203
|
+
const order = await db.orders.create({ data: body })
|
|
204
|
+
|
|
205
|
+
// Queue background job after client receives 201 response
|
|
122
206
|
after(async () => {
|
|
123
|
-
|
|
124
|
-
await emailService.sendWelcome(body.email)
|
|
207
|
+
await sendOrderConfirmationEmail(order.id, body.email)
|
|
125
208
|
})
|
|
126
209
|
|
|
127
|
-
return { success: true,
|
|
128
|
-
}
|
|
129
|
-
})
|
|
210
|
+
return { success: true, orderId: order.id }
|
|
211
|
+
},
|
|
212
|
+
}),
|
|
130
213
|
})
|
|
131
214
|
```
|
|
132
215
|
|
|
216
|
+
---
|
|
133
217
|
|
|
134
|
-
|
|
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
|
-
|
|
222
|
+
```typescript
|
|
223
|
+
function getActiveApp(): App
|
|
224
|
+
function getApp(): App
|
|
225
|
+
```
|
|
140
226
|
|
|
141
|
-
|
|
227
|
+
#### Returns
|
|
142
228
|
|
|
143
|
-
|
|
229
|
+
| Type | Description |
|
|
230
|
+
| :--- | :--- |
|
|
231
|
+
| `App` | The running application instance containing the DI container (`app.container`), logger (`app.log`), and configuration. |
|
|
144
232
|
|
|
145
|
-
|
|
233
|
+
#### Example
|
|
146
234
|
|
|
147
|
-
```typescript filename="src/
|
|
148
|
-
import {
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
##
|
|
246
|
+
## Lifecycle & Memory Safety
|
|
173
247
|
|
|
174
|
-
|
|
248
|
+
ExisJS implements deterministic context lifecycle management to prevent memory retention in long-lived or high-concurrency environments:
|
|
175
249
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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.
|