exisjs 0.7.5 → 0.7.7
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.map +1 -1
- package/dist/cli/commands/build.js +12 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/clean.d.ts +5 -0
- package/dist/cli/commands/clean.d.ts.map +1 -0
- package/dist/cli/commands/clean.js +30 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +171 -86
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/generate.d.ts +6 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +57 -2
- 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 +54 -16
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +26 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -0
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +264 -33
- 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 +9 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/cron/expressions.d.ts +45 -0
- package/dist/cron/expressions.d.ts.map +1 -0
- package/dist/cron/expressions.js +87 -0
- package/dist/cron/expressions.js.map +1 -0
- package/dist/cron/index.d.ts +6 -0
- package/dist/cron/index.d.ts.map +1 -0
- package/dist/cron/index.js +22 -0
- package/dist/cron/index.js.map +1 -0
- package/dist/cron/job.d.ts +50 -0
- package/dist/cron/job.d.ts.map +1 -0
- package/dist/cron/job.js +247 -0
- package/dist/cron/job.js.map +1 -0
- package/dist/cron/manager.d.ts +105 -0
- package/dist/cron/manager.d.ts.map +1 -0
- package/dist/cron/manager.js +245 -0
- package/dist/cron/manager.js.map +1 -0
- package/dist/cron/parser.d.ts +22 -0
- package/dist/cron/parser.d.ts.map +1 -0
- package/dist/cron/parser.js +269 -0
- package/dist/cron/parser.js.map +1 -0
- package/dist/cron/types.d.ts +76 -0
- package/dist/cron/types.d.ts.map +1 -0
- package/dist/cron/types.js +3 -0
- package/dist/cron/types.js.map +1 -0
- package/dist/decorators/constants.d.ts +2 -0
- package/dist/decorators/constants.d.ts.map +1 -1
- package/dist/decorators/constants.js +3 -1
- package/dist/decorators/constants.js.map +1 -1
- package/dist/decorators/core.d.ts +29 -11
- package/dist/decorators/core.d.ts.map +1 -1
- package/dist/decorators/core.js +49 -11
- package/dist/decorators/core.js.map +1 -1
- package/dist/decorators/cron.d.ts +57 -0
- package/dist/decorators/cron.d.ts.map +1 -0
- package/dist/decorators/cron.js +143 -0
- package/dist/decorators/cron.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.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 +102 -2
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +105 -3
- 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/decorators/routing.d.ts +81 -16
- package/dist/decorators/routing.d.ts.map +1 -1
- package/dist/decorators/routing.js +81 -16
- package/dist/decorators/routing.js.map +1 -1
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +6 -1
- package/dist/di/container.js.map +1 -1
- package/dist/docs/content/cli/commands.mdx +1 -1
- package/dist/docs/content/controllers.mdx +127 -10
- package/dist/docs/content/cron.mdx +196 -0
- package/dist/docs/content/decorators.mdx +22 -4
- package/dist/docs/content/errors.mdx +58 -9
- package/dist/docs/content/introduction.mdx +7 -7
- package/dist/docs/content/middleware.mdx +269 -6
- package/dist/docs/content/requests.mdx +180 -6
- package/dist/docs/content/responses.mdx +283 -32
- package/dist/docs/content/routing.mdx +46 -0
- package/dist/docs/content/swagger.mdx +228 -0
- package/dist/docs/content/validation.mdx +190 -11
- package/dist/docs/reference/context.mdx +11 -0
- package/dist/docs/reference/cron.mdx +300 -0
- package/dist/docs/reference/decorators.mdx +22 -4
- package/dist/docs/reference/error.mdx +88 -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/swagger.mdx +143 -0
- package/dist/docs/reference/validator.mdx +83 -23
- package/dist/error/errors.d.ts.map +1 -1
- package/dist/error/errors.js +95 -43
- package/dist/error/errors.js.map +1 -1
- package/dist/error/index.d.ts +1 -0
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +11 -1
- package/dist/error/index.js.map +1 -1
- package/dist/error/overlay.d.ts +42 -0
- package/dist/error/overlay.d.ts.map +1 -0
- package/dist/error/overlay.js +306 -0
- package/dist/error/overlay.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/start-server.js +33 -7
- package/dist/lib/start-server.js.map +1 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +9 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/guard.d.ts +2 -2
- package/dist/middleware/guard.d.ts.map +1 -1
- package/dist/middleware/guard.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts +1 -0
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +2 -0
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/middleware.d.ts +2 -2
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +20 -2
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/security.d.ts +43 -1
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +101 -0
- package/dist/middleware/security.js.map +1 -1
- package/dist/response/index.d.ts +229 -6
- package/dist/response/index.d.ts.map +1 -1
- package/dist/response/index.js +272 -2
- package/dist/response/index.js.map +1 -1
- package/dist/router/boundary.d.ts +41 -11
- package/dist/router/boundary.d.ts.map +1 -1
- package/dist/router/boundary.js +24 -2
- package/dist/router/boundary.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +108 -4
- 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 +262 -65
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js +181 -23
- package/dist/router/route-builder.js.map +1 -1
- package/dist/router/route-scanner.d.ts +6 -0
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js +71 -792
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +62 -17
- package/dist/router/router.js.map +1 -1
- package/dist/router/scanner/boundary-loader.d.ts +18 -0
- package/dist/router/scanner/boundary-loader.d.ts.map +1 -0
- package/dist/router/scanner/boundary-loader.js +539 -0
- package/dist/router/scanner/boundary-loader.js.map +1 -0
- package/dist/router/scanner/cron-mounter.d.ts +15 -0
- package/dist/router/scanner/cron-mounter.d.ts.map +1 -0
- package/dist/router/scanner/cron-mounter.js +216 -0
- package/dist/router/scanner/cron-mounter.js.map +1 -0
- package/dist/router/scanner/error-mounter.d.ts +14 -0
- package/dist/router/scanner/error-mounter.d.ts.map +1 -0
- package/dist/router/scanner/error-mounter.js +200 -0
- package/dist/router/scanner/error-mounter.js.map +1 -0
- package/dist/router/scanner/fs-scanner.d.ts +25 -0
- package/dist/router/scanner/fs-scanner.d.ts.map +1 -0
- package/dist/router/scanner/fs-scanner.js +115 -0
- package/dist/router/scanner/fs-scanner.js.map +1 -0
- package/dist/router/scanner/functional-compiler.d.ts +9 -0
- package/dist/router/scanner/functional-compiler.d.ts.map +1 -0
- package/dist/router/scanner/functional-compiler.js +233 -0
- package/dist/router/scanner/functional-compiler.js.map +1 -0
- package/dist/router/scanner/index.d.ts +9 -0
- package/dist/router/scanner/index.d.ts.map +1 -0
- package/dist/router/scanner/index.js +19 -0
- package/dist/router/scanner/index.js.map +1 -0
- package/dist/router/types.d.ts +23 -0
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/app.d.ts +3 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +13 -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/helpers/body-parser.d.ts +18 -0
- package/dist/server/helpers/body-parser.d.ts.map +1 -0
- package/dist/server/helpers/body-parser.js +246 -0
- package/dist/server/helpers/body-parser.js.map +1 -0
- package/dist/server/helpers/cookie.d.ts +4 -0
- package/dist/server/helpers/cookie.d.ts.map +1 -0
- package/dist/server/helpers/cookie.js +61 -0
- package/dist/server/helpers/cookie.js.map +1 -0
- package/dist/server/helpers/etag.d.ts +3 -0
- package/dist/server/helpers/etag.d.ts.map +1 -0
- package/dist/server/helpers/etag.js +25 -0
- package/dist/server/helpers/etag.js.map +1 -0
- package/dist/server/helpers/index.d.ts +6 -0
- package/dist/server/helpers/index.d.ts.map +1 -0
- package/dist/server/helpers/index.js +21 -0
- package/dist/server/helpers/index.js.map +1 -0
- package/dist/server/helpers/ip.d.ts +9 -0
- package/dist/server/helpers/ip.d.ts.map +1 -0
- package/dist/server/helpers/ip.js +62 -0
- package/dist/server/helpers/ip.js.map +1 -0
- package/dist/server/helpers/stream.d.ts +4 -0
- package/dist/server/helpers/stream.d.ts.map +1 -0
- package/dist/server/helpers/stream.js +193 -0
- package/dist/server/helpers/stream.js.map +1 -0
- package/dist/server/lifecycle.d.ts +22 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/lifecycle.js +106 -0
- package/dist/server/lifecycle.js.map +1 -0
- package/dist/server/request-handler.d.ts +1 -0
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +27 -17
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts +1 -139
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +37 -433
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +112 -156
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +129 -279
- package/dist/server/response.js.map +1 -1
- package/dist/swagger/decorators.d.ts +81 -0
- package/dist/swagger/decorators.d.ts.map +1 -0
- package/dist/swagger/decorators.js +172 -0
- package/dist/swagger/decorators.js.map +1 -0
- package/dist/swagger/generator.d.ts +19 -0
- package/dist/swagger/generator.d.ts.map +1 -0
- package/dist/swagger/generator.js +344 -0
- package/dist/swagger/generator.js.map +1 -0
- package/dist/swagger/index.d.ts +31 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/index.js +53 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/swagger/mounter.d.ts +7 -0
- package/dist/swagger/mounter.d.ts.map +1 -0
- package/dist/swagger/mounter.js +56 -0
- package/dist/swagger/mounter.js.map +1 -0
- package/dist/swagger/types.d.ts +190 -0
- package/dist/swagger/types.d.ts.map +1 -0
- package/dist/swagger/types.js +3 -0
- package/dist/swagger/types.js.map +1 -0
- package/dist/swagger/ui.d.ts +6 -0
- package/dist/swagger/ui.d.ts.map +1 -0
- package/dist/swagger/ui.js +118 -0
- package/dist/swagger/ui.js.map +1 -0
- 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/builder.d.ts +223 -0
- package/dist/validator/builder.d.ts.map +1 -0
- package/dist/validator/builder.js +517 -0
- package/dist/validator/builder.js.map +1 -0
- package/dist/validator/engine.d.ts +19 -0
- package/dist/validator/engine.d.ts.map +1 -0
- package/dist/validator/engine.js +568 -0
- package/dist/validator/engine.js.map +1 -0
- package/dist/validator/error.d.ts +12 -0
- package/dist/validator/error.d.ts.map +1 -0
- package/dist/validator/error.js +63 -0
- package/dist/validator/error.js.map +1 -0
- package/dist/validator/pagination.d.ts +40 -0
- package/dist/validator/pagination.d.ts.map +1 -0
- package/dist/validator/pagination.js +60 -0
- package/dist/validator/pagination.js.map +1 -0
- 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 +6 -142
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +14 -733
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +83 -0
- package/dist/validator/types.d.ts.map +1 -0
- package/dist/validator/types.js +3 -0
- package/dist/validator/types.js.map +1 -0
- package/package.json +8 -4
- package/dist/dev/error-overlay.d.ts +0 -3
- package/dist/dev/error-overlay.d.ts.map +0 -1
- package/dist/dev/error-overlay.js +0 -118
- package/dist/dev/error-overlay.js.map +0 -1
|
@@ -32,9 +32,131 @@ export const config = defineBoundary({})
|
|
|
32
32
|
export const logger = requestLogger({ level: 'debug' })
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
### Middleware
|
|
35
|
+
### Middleware Context Type Inference (`defineMiddleware`)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
When custom middlewares attach data to `req` (such as authenticated user details, session data, or tenant tokens), ExisJS supports **End-to-End Middleware Context Type Inference**.
|
|
38
|
+
|
|
39
|
+
By using `defineMiddleware<TContext>()`, downstream route handlers automatically inherit strongly typed properties on `req` with zero manual type casts (`as any`) or boilerplate.
|
|
40
|
+
|
|
41
|
+
#### Local Route-Level Inference (Recommended)
|
|
42
|
+
|
|
43
|
+
```typescript filename="src/http/route.ts"
|
|
44
|
+
import { controller, route, defineMiddleware } from 'exisjs/router'
|
|
45
|
+
|
|
46
|
+
// 1. Define your custom context shape
|
|
47
|
+
interface AuthContext {
|
|
48
|
+
user: {
|
|
49
|
+
id: string
|
|
50
|
+
role: 'admin' | 'user'
|
|
51
|
+
email: string
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Define the middleware with your context
|
|
56
|
+
export const authMiddleware = defineMiddleware<AuthContext>((req, res, next) => {
|
|
57
|
+
req.user = {
|
|
58
|
+
id: 'usr_123',
|
|
59
|
+
role: 'admin',
|
|
60
|
+
email: 'alex@example.com'
|
|
61
|
+
}
|
|
62
|
+
next()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export default controller({
|
|
66
|
+
profile: route.get('/profile', {
|
|
67
|
+
middlewares: [authMiddleware],
|
|
68
|
+
handle({ req, res }) {
|
|
69
|
+
// ✨ req.user is automatically inferred directly from middlewares: [authMiddleware]!
|
|
70
|
+
// Full autocomplete and strict type safety:
|
|
71
|
+
return res.json({
|
|
72
|
+
userId: req.user.id,
|
|
73
|
+
role: req.user.role,
|
|
74
|
+
email: req.user.email
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Multi-Middleware Context Composition
|
|
82
|
+
|
|
83
|
+
When passing multiple middlewares, ExisJS automatically merges all injected contexts:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const authMw = defineMiddleware<{ user: { id: string } }>(...)
|
|
87
|
+
const tenantMw = defineMiddleware<{ tenantId: string }>(...)
|
|
88
|
+
|
|
89
|
+
export default controller({
|
|
90
|
+
data: route.get('/data', {
|
|
91
|
+
middlewares: [authMw, tenantMw],
|
|
92
|
+
handle({ req }) {
|
|
93
|
+
// req has both `req.user` and `req.tenantId` strictly typed!
|
|
94
|
+
return { userId: req.user.id, tenant: req.tenantId }
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Module-Scoped Context (`createRouter<TContext>()`)
|
|
101
|
+
|
|
102
|
+
If an entire file or subsystem shares the same context, use `createRouter<TContext>()`:
|
|
103
|
+
|
|
104
|
+
```typescript filename="src/http/admin/route.ts"
|
|
105
|
+
import { createRouter } from 'exisjs/router'
|
|
106
|
+
|
|
107
|
+
interface AdminContext {
|
|
108
|
+
user: { id: string; role: 'admin' }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { route, controller } = createRouter<AdminContext>()
|
|
112
|
+
|
|
113
|
+
export default controller({
|
|
114
|
+
settings: route.get('/settings', {
|
|
115
|
+
middlewares: [authMiddleware],
|
|
116
|
+
handle({ req }) {
|
|
117
|
+
return { adminId: req.user.id }
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Boundary-Scoped Context (`defineBoundary<TContext>()`)
|
|
124
|
+
|
|
125
|
+
Propagate context types across all routes in a directory tree:
|
|
126
|
+
|
|
127
|
+
```typescript filename="src/http/admin/boundary.ts"
|
|
128
|
+
import { defineBoundary } from 'exisjs/router'
|
|
129
|
+
|
|
130
|
+
export const config = defineBoundary<AdminContext>({
|
|
131
|
+
beforeHandle: async ({ req }) => {
|
|
132
|
+
console.log(`Admin action by: ${req.user.id}`)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Class-Based (OOP) Context Usage
|
|
138
|
+
|
|
139
|
+
In OOP controllers, you can attach typed middlewares via `@Use()` and type the injected `@Req()` object:
|
|
140
|
+
|
|
141
|
+
```typescript filename="src/http/admin/route.ts"
|
|
142
|
+
import { Controller, Get, Use, Req } from 'exisjs/decorators'
|
|
143
|
+
import type { Request } from 'exisjs/router'
|
|
144
|
+
import { authMiddleware } from '../../middleware/auth'
|
|
145
|
+
import type { AuthContext } from '../../middleware/auth'
|
|
146
|
+
|
|
147
|
+
@Controller('/admin')
|
|
148
|
+
@Use(authMiddleware)
|
|
149
|
+
export default class AdminController {
|
|
150
|
+
@Get('/profile')
|
|
151
|
+
getProfile(@Req() req: Request & AuthContext) {
|
|
152
|
+
return {
|
|
153
|
+
userId: req.user.id,
|
|
154
|
+
role: req.user.role,
|
|
155
|
+
email: req.user.email,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
38
160
|
|
|
39
161
|
#### Functional Paradigm
|
|
40
162
|
|
|
@@ -132,15 +254,42 @@ app.use(requestId())
|
|
|
132
254
|
|
|
133
255
|
ExisJS provides high-performance rate limiting powered by Rust native memory:
|
|
134
256
|
|
|
135
|
-
|
|
257
|
+
#### Functional Paradigm
|
|
258
|
+
|
|
259
|
+
```typescript filename="src/http/auth/route.ts"
|
|
260
|
+
import { controller, route } from 'exisjs/router'
|
|
136
261
|
import { rateLimit } from 'exisjs/middleware'
|
|
137
262
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
263
|
+
const authLimiter = rateLimit({ windowMs: 60000, max: 10 })
|
|
264
|
+
|
|
265
|
+
export default controller({
|
|
266
|
+
login: route.post('/login', {
|
|
267
|
+
middlewares: [authLimiter],
|
|
268
|
+
handle({ req }) {
|
|
269
|
+
return { status: 'ok' }
|
|
270
|
+
}
|
|
271
|
+
})
|
|
141
272
|
})
|
|
142
273
|
```
|
|
143
274
|
|
|
275
|
+
#### Class-Based (OOP) Paradigm
|
|
276
|
+
|
|
277
|
+
```typescript filename="src/http/auth/route.ts"
|
|
278
|
+
import { Controller, Post, Use } from 'exisjs/decorators'
|
|
279
|
+
import { rateLimit } from 'exisjs/middleware'
|
|
280
|
+
|
|
281
|
+
const authLimiter = rateLimit({ windowMs: 60000, max: 10 })
|
|
282
|
+
|
|
283
|
+
@Controller('/auth')
|
|
284
|
+
export default class AuthController {
|
|
285
|
+
@Post('/login')
|
|
286
|
+
@Use(authLimiter)
|
|
287
|
+
async login() {
|
|
288
|
+
return { status: 'ok' }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
144
293
|
> [!NOTE]
|
|
145
294
|
> Core rate limiting runs completely off-heap in-memory. For Redis-backed distributed rate limiting across clusters, use `@exisjs/cache`.
|
|
146
295
|
|
|
@@ -150,6 +299,24 @@ export const limiter = rateLimit({
|
|
|
150
299
|
|
|
151
300
|
Idempotency caches responses based on the `Idempotency-Key` header, preventing duplicate mutations:
|
|
152
301
|
|
|
302
|
+
#### Functional Paradigm
|
|
303
|
+
|
|
304
|
+
```typescript filename="src/http/checkout/route.ts"
|
|
305
|
+
import { controller, route } from 'exisjs/router'
|
|
306
|
+
import { idempotent } from 'exisjs/middleware'
|
|
307
|
+
|
|
308
|
+
export default controller({
|
|
309
|
+
checkout: route.post('/', {
|
|
310
|
+
middlewares: [idempotent({ ttlMs: 86400000 })],
|
|
311
|
+
async handle() {
|
|
312
|
+
return { success: true }
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Class-Based (OOP) Paradigm
|
|
319
|
+
|
|
153
320
|
```typescript filename="src/http/checkout/route.ts"
|
|
154
321
|
import { Controller, Post, Idempotent } from 'exisjs/decorators'
|
|
155
322
|
|
|
@@ -169,6 +336,8 @@ export default class CheckoutController {
|
|
|
169
336
|
|
|
170
337
|
If multiple clients request the exact same resource concurrently, `dedupe` collapses identical in-flight requests into a single operation:
|
|
171
338
|
|
|
339
|
+
#### Functional Paradigm
|
|
340
|
+
|
|
172
341
|
```typescript filename="src/http/stats/route.ts"
|
|
173
342
|
import { controller, route } from 'exisjs/router'
|
|
174
343
|
import { dedupe } from 'exisjs/middleware'
|
|
@@ -183,6 +352,22 @@ export default controller({
|
|
|
183
352
|
})
|
|
184
353
|
```
|
|
185
354
|
|
|
355
|
+
#### Class-Based (OOP) Paradigm
|
|
356
|
+
|
|
357
|
+
```typescript filename="src/http/stats/route.ts"
|
|
358
|
+
import { Controller, Get, Use } from 'exisjs/decorators'
|
|
359
|
+
import { dedupe } from 'exisjs/middleware'
|
|
360
|
+
|
|
361
|
+
@Controller('/stats')
|
|
362
|
+
export default class StatsController {
|
|
363
|
+
@Get('/')
|
|
364
|
+
@Use(dedupe({ ttlMs: 5000 }))
|
|
365
|
+
async getStats() {
|
|
366
|
+
return { activeUsers: 1000 }
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
186
371
|
---
|
|
187
372
|
|
|
188
373
|
## 7. Static File Serving
|
|
@@ -351,3 +536,81 @@ export default controller({
|
|
|
351
536
|
})
|
|
352
537
|
```
|
|
353
538
|
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## 10. Security Scanner Noise Suppression & Blackhole (`blockSuspiciousProbes`)
|
|
542
|
+
|
|
543
|
+
In public production deployments, automated vulnerability scanners aggressively probe for `.env`, `/.git/HEAD`, `/.env.backup`, `/.DS_Store`, `wp-config.php`, etc.
|
|
544
|
+
|
|
545
|
+
Each probe generates a `404` error log with structured JSON logging (`{"level":40,"statusCode":404}`), flooding log aggregators and triggering false security alerts.
|
|
546
|
+
|
|
547
|
+
ExisJS provides `blockSuspiciousProbes` (alias `blockProbes`) to immediately intercept known exploit patterns, terminate the request with an empty response (blackhole), and suppress logger noise.
|
|
548
|
+
|
|
549
|
+
### Global Usage (`server.ts` / `exis.config.ts`)
|
|
550
|
+
|
|
551
|
+
```typescript filename="src/http/server.ts"
|
|
552
|
+
import { exis } from 'exisjs'
|
|
553
|
+
import { blockSuspiciousProbes } from 'exisjs/middleware'
|
|
554
|
+
|
|
555
|
+
export default exis({
|
|
556
|
+
async onStart(app) {
|
|
557
|
+
// Intercept probes before downstream routes or loggers
|
|
558
|
+
app.use(blockSuspiciousProbes({
|
|
559
|
+
statusCode: 404, // or 403
|
|
560
|
+
silent: true, // Suppresses level-40 warning logs in requestLogger
|
|
561
|
+
}))
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Or enable directly in `exis.config.ts`:
|
|
567
|
+
|
|
568
|
+
```typescript filename="exis.config.ts"
|
|
569
|
+
import { defineConfig } from 'exisjs/config'
|
|
570
|
+
|
|
571
|
+
export default async () => defineConfig({
|
|
572
|
+
blockProbes: true, // or { statusCode: 404, silent: true }
|
|
573
|
+
})
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Folder-Scoped Boundary (`boundary.ts`)
|
|
577
|
+
|
|
578
|
+
```typescript filename="src/http/boundary.ts"
|
|
579
|
+
import { defineBoundary } from 'exisjs/router'
|
|
580
|
+
|
|
581
|
+
export default defineBoundary({
|
|
582
|
+
blockProbes: {
|
|
583
|
+
statusCode: 404,
|
|
584
|
+
silent: true,
|
|
585
|
+
exclude: ['/.well-known/security.txt'],
|
|
586
|
+
}
|
|
587
|
+
})
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Class-Based (OOP) Decorator
|
|
591
|
+
|
|
592
|
+
```typescript filename="src/http/api/route.ts"
|
|
593
|
+
import { Controller, Get, BlockProbes } from 'exisjs/decorators'
|
|
594
|
+
|
|
595
|
+
@Controller('/api')
|
|
596
|
+
@BlockProbes({ statusCode: 404 })
|
|
597
|
+
export default class ApiController {
|
|
598
|
+
@Get('/health')
|
|
599
|
+
health() {
|
|
600
|
+
return { status: 'healthy' }
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Configuration Options
|
|
606
|
+
|
|
607
|
+
| Option | Type | Default | Description |
|
|
608
|
+
| :--- | :--- | :--- | :--- |
|
|
609
|
+
| `statusCode` | `number` | `404` | HTTP status code to return for blocked probes |
|
|
610
|
+
| `blackhole` | `boolean` | `true` | Immediately terminates connection with empty body without calling downstream middleware |
|
|
611
|
+
| `silent` | `boolean` | `true` | Suppresses `requestLogger` warning logs for blocked probe requests |
|
|
612
|
+
| `patterns` | `(string \| RegExp)[]` | `[]` | Additional custom probe patterns to block |
|
|
613
|
+
| `exclude` | `(string \| RegExp)[]` | `[]` | Whitelist patterns to allow through without blocking |
|
|
614
|
+
| `onProbe` | `(req, res, pattern) => void` | `undefined` | Callback hook when a probe is detected (for SIEM or IP reputation metrics) |
|
|
615
|
+
|
|
616
|
+
|
|
@@ -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
|
|
@@ -321,7 +469,7 @@ ExisJS provides a first-class standard Web API `req.signal: AbortSignal` tied di
|
|
|
321
469
|
|
|
322
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.
|
|
323
471
|
|
|
324
|
-
####
|
|
472
|
+
#### Functional Paradigm
|
|
325
473
|
|
|
326
474
|
```typescript filename="src/http/reports/route.ts"
|
|
327
475
|
import { controller, route } from 'exisjs/router'
|
|
@@ -346,6 +494,32 @@ export default controller({
|
|
|
346
494
|
})
|
|
347
495
|
```
|
|
348
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
|
+
|
|
349
523
|
---
|
|
350
524
|
|
|
351
525
|
## 9. Context & Metadata
|