exisjs 0.7.4 → 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/build.d.ts +4 -2
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +80 -22
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +229 -64
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +5 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/generate.d.ts +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +25 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +65 -15
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +20 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +118 -17
- 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/env.d.ts.map +1 -1
- package/dist/config/env.js +8 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/decorators/constants.d.ts +2 -0
- package/dist/decorators/constants.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/boundaries.mdx +33 -3
- package/dist/docs/content/cli/commands.mdx +1 -1
- package/dist/docs/content/config.mdx +26 -0
- package/dist/docs/content/controllers.mdx +127 -10
- package/dist/docs/content/decorators.mdx +22 -4
- package/dist/docs/content/errors.mdx +38 -2
- package/dist/docs/content/introduction.mdx +7 -7
- package/dist/docs/content/middleware.mdx +269 -6
- package/dist/docs/content/requests.mdx +217 -10
- package/dist/docs/content/responses.mdx +323 -34
- package/dist/docs/content/routing.mdx +46 -0
- package/dist/docs/content/security.mdx +17 -4
- package/dist/docs/content/structure.mdx +91 -12
- package/dist/docs/content/validation.mdx +226 -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 +49 -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/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +9 -0
- package/dist/middleware/rate-limit.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 +25 -5
- 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 +1 -0
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js +217 -10
- 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 +11 -1
- package/dist/router/types.d.ts.map +1 -1
- package/dist/sanitize/index.d.ts +6 -6
- package/dist/sanitize/index.d.ts.map +1 -1
- package/dist/sanitize/index.js +8 -8
- package/dist/sanitize/index.js.map +1 -1
- package/dist/server/app.d.ts +2 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +14 -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/engines/node-engine.d.ts.map +1 -1
- package/dist/server/engines/node-engine.js +1 -0
- package/dist/server/engines/node-engine.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 +42 -18
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts +6 -0
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +112 -33
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +13 -2
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +250 -10
- 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 +38 -22
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +490 -40
- package/dist/validator/tex.js.map +1 -1
- package/package.json +10 -10
- 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
|
@@ -97,7 +97,9 @@ Handling `multipart/form-data` in Node.js can be difficult. ExisJS provides mult
|
|
|
97
97
|
|
|
98
98
|
### A. Declarative Route Uploads (Recommended)
|
|
99
99
|
|
|
100
|
-
You can declare file upload requirements directly inside your route schema. ExisJS automatically executes the multipart parser, validates MIME types, enforces byte caps, and populates `req.file` or `req.files` along with non-file fields in `req.body`:
|
|
100
|
+
You can declare file upload requirements directly inside your route schema or decorators. ExisJS automatically executes the multipart parser, validates MIME types, enforces byte caps, and populates `req.file` or `req.files` along with non-file fields in `req.body`:
|
|
101
|
+
|
|
102
|
+
#### Functional Paradigm
|
|
101
103
|
|
|
102
104
|
```typescript filename="src/http/upload/route.ts"
|
|
103
105
|
import { controller, route } from 'exisjs/router'
|
|
@@ -128,10 +130,47 @@ export default controller({
|
|
|
128
130
|
})
|
|
129
131
|
```
|
|
130
132
|
|
|
133
|
+
#### Class-Based (OOP) Paradigm
|
|
134
|
+
|
|
135
|
+
```typescript filename="src/http/upload/route.ts"
|
|
136
|
+
import { Controller, Post, UseUpload, UploadedFile, Body } from 'exisjs/decorators'
|
|
137
|
+
import { tex } from 'exisjs/validator'
|
|
138
|
+
import type { UploadedFile as IUploadedFile } from 'exisjs/router'
|
|
139
|
+
|
|
140
|
+
const TitleSchema = tex.object({
|
|
141
|
+
title: tex.string({ min: 1 }),
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
@Controller('/upload')
|
|
145
|
+
export default class UploadController {
|
|
146
|
+
@Post('/')
|
|
147
|
+
@UseUpload({
|
|
148
|
+
field: 'photo',
|
|
149
|
+
maxBytes: 10 * 1024 * 1024,
|
|
150
|
+
mimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
151
|
+
})
|
|
152
|
+
async uploadPhoto(
|
|
153
|
+
@UploadedFile() file: IUploadedFile,
|
|
154
|
+
@Body(TitleSchema) body: { title: string }
|
|
155
|
+
) {
|
|
156
|
+
console.log(`Uploaded ${file.originalname} (${file.size} bytes)`)
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
filename: file.filename,
|
|
161
|
+
mimetype: file.mimetype,
|
|
162
|
+
title: body.title,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
131
168
|
### B. First-Party `fileUpload` Middleware
|
|
132
169
|
|
|
133
170
|
ExisJS exports a dedicated `fileUpload` middleware from `exisjs/middleware` supporting single, multiple, and multi-field uploads:
|
|
134
171
|
|
|
172
|
+
#### Functional Paradigm
|
|
173
|
+
|
|
135
174
|
```typescript filename="src/http/documents/route.ts"
|
|
136
175
|
import { controller, route } from 'exisjs/router'
|
|
137
176
|
import { fileUpload } from 'exisjs/middleware'
|
|
@@ -173,10 +212,44 @@ export default controller({
|
|
|
173
212
|
})
|
|
174
213
|
```
|
|
175
214
|
|
|
215
|
+
#### Class-Based (OOP) Paradigm
|
|
216
|
+
|
|
217
|
+
```typescript filename="src/http/documents/route.ts"
|
|
218
|
+
import { Controller, Post, Use, UploadedFile, UploadedFiles } from 'exisjs/decorators'
|
|
219
|
+
import { fileUpload } from 'exisjs/middleware'
|
|
220
|
+
import type { UploadedFile as IUploadedFile } from 'exisjs/router'
|
|
221
|
+
|
|
222
|
+
@Controller('/documents')
|
|
223
|
+
export default class DocumentsController {
|
|
224
|
+
// Single file upload
|
|
225
|
+
@Post('/single')
|
|
226
|
+
@Use(fileUpload.single('document', { maxBytes: 15 * 1024 * 1024, dest: './uploads/docs' }))
|
|
227
|
+
async uploadSingle(@UploadedFile() file: IUploadedFile) {
|
|
228
|
+
return { file }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Multiple files with the same field name
|
|
232
|
+
@Post('/gallery')
|
|
233
|
+
@Use(fileUpload.array('photos', 5, { mimeTypes: ['image/*'] }))
|
|
234
|
+
async uploadGallery(@UploadedFiles() files: IUploadedFile[]) {
|
|
235
|
+
return { count: files.length, files }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Multiple distinct fields
|
|
239
|
+
@Post('/profile')
|
|
240
|
+
@Use(fileUpload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'cover', maxCount: 1 }]))
|
|
241
|
+
async uploadProfile(@UploadedFiles() files: Record<string, IUploadedFile[]>) {
|
|
242
|
+
return { files }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
176
247
|
### C. Integrating Express/Connect Middleware (e.g. Multer)
|
|
177
248
|
|
|
178
249
|
If your enterprise project relies on existing Express middleware like **Multer** (for custom storage engines, S3 direct-to-cloud streams, or specialized filters), ExisJS provides the `fromExpress()` bridge:
|
|
179
250
|
|
|
251
|
+
#### Functional Paradigm
|
|
252
|
+
|
|
180
253
|
```typescript filename="src/http/legacy/route.ts"
|
|
181
254
|
import { controller, route } from 'exisjs/router'
|
|
182
255
|
import { fromExpress } from 'exisjs/middleware'
|
|
@@ -199,10 +272,36 @@ export default controller({
|
|
|
199
272
|
})
|
|
200
273
|
```
|
|
201
274
|
|
|
275
|
+
#### Class-Based (OOP) Paradigm
|
|
276
|
+
|
|
277
|
+
```typescript filename="src/http/legacy/route.ts"
|
|
278
|
+
import { Controller, Post, Use, Req } from 'exisjs/decorators'
|
|
279
|
+
import { fromExpress } from 'exisjs/middleware'
|
|
280
|
+
import type { Request } from 'exisjs/router'
|
|
281
|
+
import multer from 'multer'
|
|
282
|
+
|
|
283
|
+
const upload = multer({
|
|
284
|
+
storage: multer.memoryStorage(),
|
|
285
|
+
limits: { fileSize: 10 * 1024 * 1024 },
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
@Controller('/legacy')
|
|
289
|
+
export default class LegacyUploadController {
|
|
290
|
+
@Post('/upload')
|
|
291
|
+
@Use(fromExpress(upload.single('file')))
|
|
292
|
+
async upload(@Req() req: Request) {
|
|
293
|
+
const file = (req.raw as any).file || req.file
|
|
294
|
+
return { success: true, originalname: file?.originalname }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
202
299
|
### D. Manual `req.formData()` & Streaming Uploads
|
|
203
300
|
|
|
204
301
|
For manual control over incoming multipart streams without route middleware:
|
|
205
302
|
|
|
303
|
+
#### Functional Paradigm
|
|
304
|
+
|
|
206
305
|
```typescript filename="src/http/upload/route.ts"
|
|
207
306
|
import { controller, route } from 'exisjs/router'
|
|
208
307
|
|
|
@@ -225,6 +324,29 @@ export default controller({
|
|
|
225
324
|
})
|
|
226
325
|
```
|
|
227
326
|
|
|
327
|
+
#### Class-Based (OOP) Paradigm
|
|
328
|
+
|
|
329
|
+
```typescript filename="src/http/upload/route.ts"
|
|
330
|
+
import { Controller, Post, Req } from 'exisjs/decorators'
|
|
331
|
+
import type { Request } from 'exisjs/router'
|
|
332
|
+
|
|
333
|
+
@Controller('/upload')
|
|
334
|
+
export default class ManualUploadController {
|
|
335
|
+
@Post('/avatar')
|
|
336
|
+
async uploadAvatar(@Req() req: Request) {
|
|
337
|
+
const { fields, files } = await req.formData()
|
|
338
|
+
const uploadedFile = files['profile_pic']
|
|
339
|
+
|
|
340
|
+
if (uploadedFile) {
|
|
341
|
+
const savedPath = await uploadedFile.saveToDisk('./uploads/avatars')
|
|
342
|
+
return { success: true, path: savedPath }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return { success: false, error: 'No file uploaded' }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
228
350
|
---
|
|
229
351
|
|
|
230
352
|
## 4. Body Size Limits & HTTP 413
|
|
@@ -260,22 +382,24 @@ ExisJS ships with a **60-second default request timeout** in its `timeout()` mid
|
|
|
260
382
|
|
|
261
383
|
### Dynamic In-Handler Control
|
|
262
384
|
|
|
263
|
-
Handlers can dynamically extend or cancel the active timeout on the fly using `req.setTimeout()` and `req.clearTimeout()`:
|
|
385
|
+
Handlers can dynamically extend or cancel the active timeout on the fly using `req.setTimeout()` and `req.clearTimeout()`, or declaratively via `@Timeout(ms)`:
|
|
386
|
+
|
|
387
|
+
#### Functional Paradigm
|
|
264
388
|
|
|
265
389
|
```typescript filename="src/http/ai/route.ts"
|
|
266
390
|
import { controller, route } from 'exisjs/router'
|
|
267
391
|
|
|
268
392
|
export default controller({
|
|
269
393
|
generateVideo: route.post('/', {
|
|
394
|
+
timeoutMs: 180000, // 1. Declarative 3-minute timeout for this endpoint
|
|
270
395
|
async handle({ req }) {
|
|
271
|
-
//
|
|
272
|
-
req.setTimeout(
|
|
396
|
+
// 2. Or dynamically adjust timeout during processing
|
|
397
|
+
req.setTimeout(240000)
|
|
273
398
|
|
|
274
399
|
try {
|
|
275
400
|
const result = await longRunningAiTask()
|
|
276
401
|
return { success: true, result }
|
|
277
402
|
} finally {
|
|
278
|
-
// 2. Clear timeout once work completes
|
|
279
403
|
req.clearTimeout()
|
|
280
404
|
}
|
|
281
405
|
}
|
|
@@ -283,6 +407,30 @@ export default controller({
|
|
|
283
407
|
})
|
|
284
408
|
```
|
|
285
409
|
|
|
410
|
+
#### Class-Based (OOP) Paradigm
|
|
411
|
+
|
|
412
|
+
```typescript filename="src/http/ai/route.ts"
|
|
413
|
+
import { Controller, Post, Timeout, Req } from 'exisjs/decorators'
|
|
414
|
+
import type { Request } from 'exisjs/router'
|
|
415
|
+
|
|
416
|
+
@Controller('/ai')
|
|
417
|
+
export default class AiTaskController {
|
|
418
|
+
@Post('/generate-video')
|
|
419
|
+
@Timeout(180000) // 1. Declarative 3-minute timeout
|
|
420
|
+
async generateVideo(@Req() req: Request) {
|
|
421
|
+
// 2. Or dynamically adjust on the fly
|
|
422
|
+
req.setTimeout(240000)
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const result = await longRunningAiTask()
|
|
426
|
+
return { success: true, result }
|
|
427
|
+
} finally {
|
|
428
|
+
req.clearTimeout()
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
286
434
|
---
|
|
287
435
|
|
|
288
436
|
## 6. HTTP Headers & Content Negotiation
|
|
@@ -303,19 +451,78 @@ const language = req.acceptsLanguages('en', 'es', 'fr')
|
|
|
303
451
|
|
|
304
452
|
---
|
|
305
453
|
|
|
306
|
-
##
|
|
454
|
+
## 7. Network Information & IPv6 Normalization
|
|
307
455
|
|
|
308
|
-
ExisJS handles trust-proxies. If you are behind Cloudflare, Nginx, or an AWS Load Balancer, simply set `trustProxy: true` in your `exis()` config, and these properties will automatically resolve the true client IP from `X-Forwarded-For`.
|
|
456
|
+
ExisJS handles trust-proxies and dual-stack IP networks automatically. If you are behind Cloudflare, Nginx, or an AWS Load Balancer, simply set `trustProxy: true` in your `exis()` config, and these properties will automatically resolve the true client IP from `X-Forwarded-For`.
|
|
309
457
|
|
|
310
|
-
- **`req.ip`**: The client's IP address.
|
|
311
|
-
- **`req.ips`**: An array of IPs if multiple proxies were traversed.
|
|
458
|
+
- **`req.ip`**: The client's IP address. Automatically normalizes IPv6-mapped IPv4 dual-stack addresses (e.g., `::ffff:192.168.1.50` $\to$ `192.168.1.50`) and converts IPv6 loopback `::1` $\to$ `127.0.0.1` for consistent rate limiting and geo-IP lookups.
|
|
459
|
+
- **`req.ips`**: An array of IPs if multiple proxies were traversed, each normalized cleanly.
|
|
312
460
|
- **`req.hostname`**: The resolved hostname (from `Host` or `X-Forwarded-Host`).
|
|
313
461
|
- **`req.protocol`**: Returns `'http'` or `'https'`.
|
|
314
462
|
- **`req.secure`**: A boolean (true if `req.protocol === 'https'`).
|
|
315
463
|
|
|
316
464
|
---
|
|
317
465
|
|
|
318
|
-
##
|
|
466
|
+
## 8. Request Cancellation & Abort Signals (`req.signal`)
|
|
467
|
+
|
|
468
|
+
ExisJS provides a first-class standard Web API `req.signal: AbortSignal` tied directly to the underlying HTTP connection lifecycle.
|
|
469
|
+
|
|
470
|
+
When a client closes their browser tab, cancels a download, or aborts an HTTP request, `req.signal` immediately triggers an `abort` event. You can pass `req.signal` directly to ORMs, database drivers, and HTTP clients to cancel expensive in-flight operations and eliminate wasted server resources.
|
|
471
|
+
|
|
472
|
+
#### Functional Paradigm
|
|
473
|
+
|
|
474
|
+
```typescript filename="src/http/reports/route.ts"
|
|
475
|
+
import { controller, route } from 'exisjs/router'
|
|
476
|
+
|
|
477
|
+
export default controller({
|
|
478
|
+
heavyReport: route.get('/', {
|
|
479
|
+
async handle({ req }) {
|
|
480
|
+
// 1. Pass req.signal to fetch() — if the client leaves, fetch aborts immediately
|
|
481
|
+
const upstream = await fetch('https://api.external.com/data', {
|
|
482
|
+
signal: req.signal
|
|
483
|
+
})
|
|
484
|
+
const payload = await upstream.json()
|
|
485
|
+
|
|
486
|
+
// 2. Pass req.signal to your database queries (Prisma, Drizzle, pg, etc.)
|
|
487
|
+
const results = await db.query(sql`SELECT * FROM heavy_analytics`, {
|
|
488
|
+
signal: req.signal
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
return { payload, results }
|
|
492
|
+
}
|
|
493
|
+
})
|
|
494
|
+
})
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### Class-Based (OOP) Paradigm
|
|
498
|
+
|
|
499
|
+
```typescript filename="src/http/reports/route.ts"
|
|
500
|
+
import { Controller, Get, Req } from 'exisjs/decorators'
|
|
501
|
+
import type { Request } from 'exisjs/router'
|
|
502
|
+
|
|
503
|
+
@Controller('/reports')
|
|
504
|
+
export default class ReportsController {
|
|
505
|
+
@Get('/')
|
|
506
|
+
async heavyReport(@Req() req: Request) {
|
|
507
|
+
// 1. Pass req.signal to fetch()
|
|
508
|
+
const upstream = await fetch('https://api.external.com/data', {
|
|
509
|
+
signal: req.signal,
|
|
510
|
+
})
|
|
511
|
+
const payload = await upstream.json()
|
|
512
|
+
|
|
513
|
+
// 2. Pass req.signal to your database queries
|
|
514
|
+
const results = await db.query(sql`SELECT * FROM heavy_analytics`, {
|
|
515
|
+
signal: req.signal,
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
return { payload, results }
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 9. Context & Metadata
|
|
319
526
|
|
|
320
527
|
As your request moves through ExisJS Middlewares, various properties are attached to `req` for your convenience:
|
|
321
528
|
|