exisjs 0.7.9 → 0.7.11
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/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +8 -1
- package/dist/cli/commands/dev.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 +1 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/database/lifecycle.d.ts +2 -1
- package/dist/database/lifecycle.d.ts.map +1 -1
- package/dist/database/lifecycle.js +6 -2
- package/dist/database/lifecycle.js.map +1 -1
- 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 +14 -0
- package/dist/decorators/core.d.ts.map +1 -1
- package/dist/decorators/core.js +156 -41
- package/dist/decorators/core.js.map +1 -1
- package/dist/decorators/filters.d.ts +2 -10
- package/dist/decorators/filters.d.ts.map +1 -1
- package/dist/decorators/filters.js.map +1 -1
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +1 -0
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/lifecycle.d.ts +29 -0
- package/dist/decorators/lifecycle.d.ts.map +1 -1
- package/dist/decorators/params.d.ts +3 -7
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js.map +1 -1
- package/dist/decorators/pipes.d.ts +91 -0
- package/dist/decorators/pipes.d.ts.map +1 -0
- package/dist/decorators/pipes.js +283 -0
- package/dist/decorators/pipes.js.map +1 -0
- package/dist/di/container.d.ts +8 -4
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +186 -68
- package/dist/di/container.js.map +1 -1
- package/dist/di/decorators.d.ts +23 -5
- package/dist/di/decorators.d.ts.map +1 -1
- package/dist/di/decorators.js +83 -20
- package/dist/di/decorators.js.map +1 -1
- package/dist/di/forward-ref.d.ts +23 -0
- package/dist/di/forward-ref.d.ts.map +1 -0
- package/dist/di/forward-ref.js +30 -0
- package/dist/di/forward-ref.js.map +1 -0
- package/dist/di/index.d.ts +3 -2
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/index.js +8 -1
- package/dist/di/index.js.map +1 -1
- package/dist/docs/BENCHMARKS.md +5 -5
- package/dist/docs/content/config.mdx +16 -6
- package/dist/docs/content/controllers.mdx +230 -0
- package/dist/docs/content/errors.mdx +16 -9
- package/dist/docs/content/modules.mdx +23 -4
- package/dist/docs/content/providers.mdx +42 -6
- package/dist/docs/content/requests.mdx +9 -3
- package/dist/docs/content/responses.mdx +19 -5
- package/dist/docs/content/security.mdx +48 -35
- package/dist/docs/content/structure.mdx +119 -93
- package/dist/docs/reference/config.mdx +29 -18
- package/dist/docs/reference/decorators.mdx +15 -4
- package/dist/docs/reference/di.mdx +23 -16
- package/dist/docs/reference/router.mdx +75 -0
- package/dist/docs/reference/validator.mdx +2 -2
- package/dist/error/errors.d.ts.map +1 -1
- package/dist/error/errors.js +12 -0
- package/dist/error/errors.js.map +1 -1
- package/dist/middleware/security.d.ts +18 -0
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +32 -0
- package/dist/middleware/security.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +142 -87
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/radix.d.ts +1 -0
- package/dist/router/radix.d.ts.map +1 -1
- package/dist/router/radix.js +21 -0
- package/dist/router/radix.js.map +1 -1
- package/dist/router/route-builder.d.ts +160 -29
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js +105 -31
- package/dist/router/route-builder.js.map +1 -1
- package/dist/router/route-scanner.d.ts +3 -2
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +44 -0
- package/dist/router/router.js.map +1 -1
- package/dist/router/scanner/functional-compiler.d.ts +1 -1
- package/dist/router/scanner/functional-compiler.d.ts.map +1 -1
- package/dist/router/scanner/functional-compiler.js +403 -64
- package/dist/router/scanner/functional-compiler.js.map +1 -1
- package/dist/router/types.d.ts +4 -2
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/adapters/uws-adapter.d.ts +17 -0
- package/dist/server/adapters/uws-adapter.d.ts.map +1 -1
- package/dist/server/adapters/uws-adapter.js +101 -7
- package/dist/server/adapters/uws-adapter.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 +38 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/helpers/etag.d.ts +1 -0
- package/dist/server/helpers/etag.d.ts.map +1 -1
- package/dist/server/helpers/etag.js +168 -9
- package/dist/server/helpers/etag.js.map +1 -1
- package/dist/server/helpers/ip.d.ts.map +1 -1
- package/dist/server/helpers/ip.js +75 -17
- package/dist/server/helpers/ip.js.map +1 -1
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +35 -19
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts +4 -3
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +26 -8
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +1 -0
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +48 -22
- package/dist/server/response.js.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/swagger/generator.js +66 -22
- package/dist/swagger/generator.js.map +1 -1
- package/dist/testing/client.d.ts +15 -11
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +32 -11
- package/dist/testing/client.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +2 -0
- package/dist/utils/logger.js.map +1 -1
- package/dist/validator/engine.d.ts.map +1 -1
- package/dist/validator/engine.js +45 -0
- package/dist/validator/engine.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
nextUrl:
|
|
3
|
-
nextTitle:
|
|
2
|
+
nextUrl: '/docs/structure'
|
|
3
|
+
nextTitle: 'Project Structure'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Security & Edge Middleware
|
|
@@ -33,7 +33,7 @@ export default exis({
|
|
|
33
33
|
app.use(timeout(10000))
|
|
34
34
|
app.use(hpp())
|
|
35
35
|
app.use(mongoSanitize())
|
|
36
|
-
}
|
|
36
|
+
},
|
|
37
37
|
})
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -52,9 +52,9 @@ export const config = defineBoundary({})
|
|
|
52
52
|
// Apply rate limiting chain step across the boundary
|
|
53
53
|
export const limiter = rateLimit({
|
|
54
54
|
windowMs: 60 * 1000, // 1 minute window
|
|
55
|
-
max: 100,
|
|
55
|
+
max: 100, // 100 requests per window
|
|
56
56
|
message: 'Too many requests from this IP, please try again after a minute',
|
|
57
|
-
keyGenerator: (req) => req.ip // Uses normalized IPv4/IPv6 client IP
|
|
57
|
+
keyGenerator: (req) => req.ip, // Uses normalized IPv4/IPv6 client IP
|
|
58
58
|
})
|
|
59
59
|
```
|
|
60
60
|
|
|
@@ -62,13 +62,13 @@ export const limiter = rateLimit({
|
|
|
62
62
|
|
|
63
63
|
`rateLimit` automatically emits standard IETF draft and legacy rate-limiting headers on every response:
|
|
64
64
|
|
|
65
|
-
| Header
|
|
66
|
-
|
|
|
67
|
-
| `RateLimit-Limit`
|
|
68
|
-
| `RateLimit-Remaining` | Remaining requests permitted within the current window.
|
|
69
|
-
| `RateLimit-Reset`
|
|
70
|
-
| `Retry-After`
|
|
71
|
-
| `X-RateLimit-*`
|
|
65
|
+
| Header | Description | Example |
|
|
66
|
+
| :-------------------- | :------------------------------------------------------------------------- | :---------------------- |
|
|
67
|
+
| `RateLimit-Limit` | Maximum request quota for the configured window. | `100` |
|
|
68
|
+
| `RateLimit-Remaining` | Remaining requests permitted within the current window. | `42` |
|
|
69
|
+
| `RateLimit-Reset` | Time remaining in seconds until the window quota resets. | `18` |
|
|
70
|
+
| `Retry-After` | Included on `429 Too Many Requests` (RFC 6585) indicating seconds to wait. | `18` |
|
|
71
|
+
| `X-RateLimit-*` | Legacy header equivalents for backwards compatibility. | `X-RateLimit-Reset: 18` |
|
|
72
72
|
|
|
73
73
|
> [!NOTE]
|
|
74
74
|
> The core `rateLimit` middleware runs using an in-memory native off-heap store. For distributed, multi-instance rate limiting backed by Redis, use `@exisjs/cache`.
|
|
@@ -77,34 +77,47 @@ export const limiter = rateLimit({
|
|
|
77
77
|
|
|
78
78
|
## 3. Role-Based Access Control (RBAC) Metadata
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#### Functional Paradigm
|
|
80
|
+
---
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
import { controller, route } from 'exisjs/router'
|
|
82
|
+
## 4. Timing-Safe Operations & Authentication
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
deleteItem: route.delete('/:id', {
|
|
89
|
-
permissions: ['admin'],
|
|
90
|
-
handle({ params }) {
|
|
91
|
-
return { success: true, deleted: params.id }
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
```
|
|
84
|
+
To prevent side-channel timing attacks when validating authentication tokens, API keys, HMAC signatures, or webhook secrets, ExisJS provides the `timingSafeEqual` helper:
|
|
96
85
|
|
|
97
|
-
|
|
86
|
+
```typescript filename="src/http/common/guards/auth.guard.ts"
|
|
87
|
+
import { timingSafeEqual } from 'exisjs/middleware'
|
|
88
|
+
import { UnauthorizedException } from 'exisjs/error'
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
export function apiKeyGuard(req: any) {
|
|
91
|
+
const apiKey = req.headers['x-api-key']
|
|
92
|
+
const secretKey = process.env.API_KEY
|
|
101
93
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
@Permissions('admin')
|
|
105
|
-
@Delete('/:id')
|
|
106
|
-
async deleteItem(@Param('id') id: string) {
|
|
107
|
-
return { success: true, deleted: id }
|
|
94
|
+
if (!timingSafeEqual(apiKey, secretKey)) {
|
|
95
|
+
throw new UnauthorizedException('Invalid API Key')
|
|
108
96
|
}
|
|
109
97
|
}
|
|
110
98
|
```
|
|
99
|
+
|
|
100
|
+
`timingSafeEqual` executes in constant time regardless of string lengths or partial matching prefixes, neutralizing timing analysis exploits.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 5. Security Scanner Noise Suppression & Blackhole Handler
|
|
105
|
+
|
|
106
|
+
ExisJS includes built-in protection against automated vulnerability bots scanning for `.env`, `/.git`, `/.DS_Store`, WordPress/PHP configs, and Java actuator dumps:
|
|
107
|
+
|
|
108
|
+
```typescript filename="src/http/server.ts"
|
|
109
|
+
import { exis } from 'exisjs'
|
|
110
|
+
import { blockSuspiciousProbes } from 'exisjs/middleware'
|
|
111
|
+
|
|
112
|
+
export default exis({
|
|
113
|
+
async onStart(app) {
|
|
114
|
+
// Intercepts scanner probes and blackholes them without triggering downstream error logs
|
|
115
|
+
app.use(
|
|
116
|
+
blockSuspiciousProbes({
|
|
117
|
+
blackhole: true, // Terminate immediately with 404/empty body
|
|
118
|
+
silent: true, // Suppress 404 noise from production logs
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
nextUrl:
|
|
3
|
-
nextTitle:
|
|
2
|
+
nextUrl: '/docs/config'
|
|
3
|
+
nextTitle: 'Configuration'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Project Structure
|
|
@@ -22,17 +22,18 @@ my-backend/
|
|
|
22
22
|
├── .exis/ # Internal framework cache and build artifacts
|
|
23
23
|
├── src/
|
|
24
24
|
│ ├── http/
|
|
25
|
-
│ │ ├── server.ts # Application entry point
|
|
25
|
+
│ │ ├── server.ts # Application entry point & lifecycle hooks
|
|
26
26
|
│ │ ├── error.ts # Centralized framework exception handler (onError)
|
|
27
27
|
│ │ ├── route.ts # Root routes (e.g. GET /api)
|
|
28
28
|
│ │ ├── boundary.ts # Root boundary (config + request pipeline)
|
|
29
29
|
│ │ ├── users/
|
|
30
30
|
│ │ │ ├── route.ts # User endpoints
|
|
31
|
-
│ │ │
|
|
31
|
+
│ │ │ ├── schema.ts # Validation schemas (tex) — mandatory alongside route.ts
|
|
32
|
+
│ │ │ └── service.ts # Business logic / data access, DI-injectable
|
|
32
33
|
│ │ ├── posts/
|
|
33
34
|
│ │ │ ├── route.ts
|
|
34
35
|
│ │ │ ├── schema.ts
|
|
35
|
-
│ │ │ └── service.ts
|
|
36
|
+
│ │ │ └── service.ts
|
|
36
37
|
│ │ └── admin/
|
|
37
38
|
│ │ ├── boundary.ts # Applies to /admin/* and everything nested
|
|
38
39
|
│ │ ├── route.ts
|
|
@@ -40,10 +41,16 @@ my-backend/
|
|
|
40
41
|
│ │ ├── route.ts
|
|
41
42
|
│ │ ├── schema.ts
|
|
42
43
|
│ │ └── service.ts
|
|
43
|
-
│ ├──
|
|
44
|
-
│
|
|
45
|
-
│ ├──
|
|
46
|
-
│
|
|
44
|
+
│ ├── cron/ # Scheduled tasks & background cron jobs (auto-discovered)
|
|
45
|
+
│ │ └── cleanup.ts
|
|
46
|
+
│ ├── database/ # Database connection, models/schemas, migrations, or repositories
|
|
47
|
+
│ │ ├── db.ts # Connection client & lifecycle registration (registerDatabase)
|
|
48
|
+
│ │ └── models/ # Database entity models / ORM schemas (e.g. user.model.ts)
|
|
49
|
+
│ ├── common/ # Shared cross-cutting guards, interceptors, or filters
|
|
50
|
+
│ │ └── guards/
|
|
51
|
+
│ │ └── auth.guard.ts
|
|
52
|
+
│ └── config/
|
|
53
|
+
│ └── env.ts # Type-safe environment schema (tex.env)
|
|
47
54
|
├── tests/
|
|
48
55
|
├── .env
|
|
49
56
|
├── exis.config.ts
|
|
@@ -51,8 +58,7 @@ my-backend/
|
|
|
51
58
|
└── tsconfig.json
|
|
52
59
|
```
|
|
53
60
|
|
|
54
|
-
> `src/
|
|
55
|
-
> install `@exisjs/queue` — see that package's docs for its convention.
|
|
61
|
+
> `src/cron/` is natively auto-discovered by the framework at boot. All files inside `src/cron/` or colocated at `src/http/**/cron.ts` are automatically mounted. Database connections and models reside under `src/database/` (or `src/models/`).
|
|
56
62
|
|
|
57
63
|
---
|
|
58
64
|
|
|
@@ -62,18 +68,19 @@ A file's name declares its role. Functional vs. class-based (OOP) only changes
|
|
|
62
68
|
_syntax_, never _responsibility_.
|
|
63
69
|
|
|
64
70
|
ExisJS supports two naming conventions seamlessly:
|
|
71
|
+
|
|
65
72
|
1. **Standard Flat Names** (default): `route.ts`, `schema.ts`, `service.ts`, `boundary.ts`.
|
|
66
73
|
2. **Feature-Prefixed Named Files**: `<name>.route.ts`, `<name>.schema.ts`, `<name>.service.ts`, `<name>.boundary.ts` (e.g., `user.route.ts`, `user.schema.ts`, `user.service.ts`).
|
|
67
74
|
|
|
68
75
|
Both styles are automatically detected by the route scanner, CLI manifest generator, and development watcher with 100% parity.
|
|
69
76
|
|
|
70
|
-
| File Pattern
|
|
71
|
-
|
|
|
72
|
-
| `route.ts` / `*.route.ts`
|
|
73
|
-
| `schema.ts` / `*.schema.ts`
|
|
74
|
-
| `service.ts` / `*.service.ts`
|
|
75
|
-
| `boundary.ts` / `*.boundary.ts` | Folder-scoped config + request pipeline for everything in this folder and below.
|
|
76
|
-
| `error.ts` / `*.error.ts`
|
|
77
|
+
| File Pattern | Role | Never contains |
|
|
78
|
+
| :------------------------------ | :--------------------------------------------------------------------------------------------- | :------------------------------- |
|
|
79
|
+
| `route.ts` / `*.route.ts` | Binds a URL + method to a handler. | Business logic, validation logic |
|
|
80
|
+
| `schema.ts` / `*.schema.ts` | `tex` validation schemas, imported by route files. Mandatory for every route accepting input. | Handlers, routing |
|
|
81
|
+
| `service.ts` / `*.service.ts` | Business logic and data access. DI-injectable. | Routing, response shaping |
|
|
82
|
+
| `boundary.ts` / `*.boundary.ts` | Folder-scoped config + request pipeline for everything in this folder and below. | — |
|
|
83
|
+
| `error.ts` / `*.error.ts` | Centralized exception and validation error handler (`onError`). Auto-mounted by the framework. | Business logic, routes |
|
|
77
84
|
|
|
78
85
|
---
|
|
79
86
|
|
|
@@ -89,18 +96,18 @@ Any `route.ts` (or `.js`) under `src/http/` maps to a URL from its folder path:
|
|
|
89
96
|
### Functional
|
|
90
97
|
|
|
91
98
|
```typescript filename="src/http/users/route.ts"
|
|
92
|
-
import { controller, route } from
|
|
93
|
-
import { UserParamsSchema } from
|
|
94
|
-
import { fetchUser } from
|
|
99
|
+
import { controller, route } from 'exisjs/router'
|
|
100
|
+
import { UserParamsSchema } from './schema'
|
|
101
|
+
import { fetchUser } from './service'
|
|
95
102
|
|
|
96
103
|
export default controller({
|
|
97
|
-
getUser: route.get(
|
|
104
|
+
getUser: route.get('/users/:id', {
|
|
98
105
|
params: UserParamsSchema,
|
|
99
106
|
async handle({ params }) {
|
|
100
|
-
return fetchUser(params.id)
|
|
107
|
+
return fetchUser(params.id)
|
|
101
108
|
},
|
|
102
109
|
}),
|
|
103
|
-
})
|
|
110
|
+
})
|
|
104
111
|
```
|
|
105
112
|
|
|
106
113
|
### Class-based (OOP)
|
|
@@ -108,17 +115,17 @@ export default controller({
|
|
|
108
115
|
The decorated class **is** the route file — no separate controller file:
|
|
109
116
|
|
|
110
117
|
```typescript filename="src/http/users/route.ts"
|
|
111
|
-
import { Controller, Get, Param } from
|
|
112
|
-
import { UserParamsSchema } from
|
|
113
|
-
import { UserService } from
|
|
118
|
+
import { Controller, Get, Param } from 'exisjs/decorators'
|
|
119
|
+
import { UserParamsSchema } from './schema'
|
|
120
|
+
import { UserService } from './service'
|
|
114
121
|
|
|
115
|
-
@Controller(
|
|
122
|
+
@Controller('/users')
|
|
116
123
|
export default class UserRoute {
|
|
117
124
|
constructor(private userService: UserService) {}
|
|
118
125
|
|
|
119
|
-
@Get(
|
|
120
|
-
async getUser(@Param(
|
|
121
|
-
return this.userService.fetchUser(id)
|
|
126
|
+
@Get('/:id', UserParamsSchema)
|
|
127
|
+
async getUser(@Param('id') id: string) {
|
|
128
|
+
return this.userService.fetchUser(id)
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
```
|
|
@@ -132,9 +139,9 @@ export default class UserRoute {
|
|
|
132
139
|
## `src/http/**/schema.ts`
|
|
133
140
|
|
|
134
141
|
```typescript filename="src/http/users/schema.ts"
|
|
135
|
-
import { tex } from
|
|
142
|
+
import { tex } from 'exisjs/validator'
|
|
136
143
|
|
|
137
|
-
export const UserParamsSchema = tex.object({ id: tex.string() })
|
|
144
|
+
export const UserParamsSchema = tex.object({ id: tex.string() })
|
|
138
145
|
```
|
|
139
146
|
|
|
140
147
|
`tex` is the only validation engine. Schemas here are also the source for
|
|
@@ -156,7 +163,7 @@ export async function fetchUser(id: string) {
|
|
|
156
163
|
|
|
157
164
|
```typescript filename="src/http/users/service.ts"
|
|
158
165
|
// OOP
|
|
159
|
-
import { Injectable } from
|
|
166
|
+
import { Injectable } from 'exisjs/decorators'
|
|
160
167
|
|
|
161
168
|
@Injectable()
|
|
162
169
|
export class UserService {
|
|
@@ -178,49 +185,49 @@ two independent halves:
|
|
|
178
185
|
2. **Pipeline & Hooks** — real code, auto-detected by function signature. No imports,
|
|
179
186
|
no manual registration required.
|
|
180
187
|
|
|
181
|
-
| Signature
|
|
182
|
-
|
|
|
183
|
-
| `(req, res, next)`
|
|
184
|
-
| `beforeHandle(req, res)`
|
|
185
|
-
| `afterHandle(req, res, data)`
|
|
186
|
-
| `(ctx, next)` — default export (functional) or `handle(ctx, next)` (OOP) | Pipeline Wrapper
|
|
188
|
+
| Signature | Detected as | Runs |
|
|
189
|
+
| :----------------------------------------------------------------------- | :------------------ | :--------------------------------------------------------------------------------- |
|
|
190
|
+
| `(req, res, next)` | Chain Step | In file/declaration order, before the route handler |
|
|
191
|
+
| `beforeHandle(req, res)` | Boundary Guard Hook | Immediately before route execution. Short-circuits on `false` or when headers sent |
|
|
192
|
+
| `afterHandle(req, res, data)` | Response Hook | Immediately after route execution. Receives returned data to transform or audit |
|
|
193
|
+
| `(ctx, next)` — default export (functional) or `handle(ctx, next)` (OOP) | Pipeline Wrapper | Around all chain steps and the nested route/boundary |
|
|
187
194
|
|
|
188
195
|
### Functional
|
|
189
196
|
|
|
190
197
|
```typescript filename="src/http/admin/boundary.ts"
|
|
191
|
-
import { defineBoundary } from
|
|
192
|
-
import type { Request, Response, Next, BoundaryContext } from
|
|
198
|
+
import { defineBoundary } from 'exisjs/router'
|
|
199
|
+
import type { Request, Response, Next, BoundaryContext } from 'exisjs/router'
|
|
193
200
|
|
|
194
201
|
export const config = defineBoundary({
|
|
195
|
-
cors: { origin: [
|
|
196
|
-
headers: {
|
|
197
|
-
exclude: [{ path:
|
|
202
|
+
cors: { origin: ['https://myapp.com'], credentials: true },
|
|
203
|
+
headers: { 'X-Powered-By': 'ExisJS' },
|
|
204
|
+
exclude: [{ path: '/admin/health', method: 'GET' }],
|
|
198
205
|
|
|
199
206
|
// Pre-handler hook (returns false to block with 403, or sends response)
|
|
200
207
|
beforeHandle(req, res) {
|
|
201
|
-
if (req.headers[
|
|
202
|
-
res.status(403).json({ error:
|
|
203
|
-
return false
|
|
208
|
+
if (req.headers['x-role'] !== 'admin') {
|
|
209
|
+
res.status(403).json({ error: 'Admin role required' })
|
|
210
|
+
return false
|
|
204
211
|
}
|
|
205
212
|
},
|
|
206
213
|
|
|
207
214
|
// Post-handler hook (transforms returned payload or performs async audit)
|
|
208
215
|
afterHandle(req, res, data) {
|
|
209
|
-
res.setHeader(
|
|
210
|
-
return { ...data, auditedAt: Date.now() }
|
|
216
|
+
res.setHeader('X-Audited-By', 'AdminBoundary')
|
|
217
|
+
return { ...data, auditedAt: Date.now() }
|
|
211
218
|
},
|
|
212
|
-
})
|
|
219
|
+
})
|
|
213
220
|
|
|
214
221
|
export function logStep(req: Request, res: Response, next: Next) {
|
|
215
|
-
console.log(`[Admin] ${req.method} ${req.path}`)
|
|
216
|
-
next()
|
|
222
|
+
console.log(`[Admin] ${req.method} ${req.path}`)
|
|
223
|
+
next()
|
|
217
224
|
}
|
|
218
225
|
|
|
219
226
|
export default async function (ctx: BoundaryContext, next: Next) {
|
|
220
227
|
try {
|
|
221
|
-
return { success: true, data: await next() }
|
|
228
|
+
return { success: true, data: await next() }
|
|
222
229
|
} catch (err) {
|
|
223
|
-
return { success: false, error: { message: (err as Error).message } }
|
|
230
|
+
return { success: false, error: { message: (err as Error).message } }
|
|
224
231
|
}
|
|
225
232
|
}
|
|
226
233
|
```
|
|
@@ -228,40 +235,40 @@ export default async function (ctx: BoundaryContext, next: Next) {
|
|
|
228
235
|
### Class-based (OOP)
|
|
229
236
|
|
|
230
237
|
```typescript filename="src/http/admin/boundary.ts"
|
|
231
|
-
import { Boundary } from
|
|
232
|
-
import type { Request, Response, Next, BoundaryContext } from
|
|
238
|
+
import { Boundary } from 'exisjs/decorators'
|
|
239
|
+
import type { Request, Response, Next, BoundaryContext } from 'exisjs/router'
|
|
233
240
|
|
|
234
241
|
@Boundary({
|
|
235
|
-
cors: { origin: [
|
|
236
|
-
headers: {
|
|
237
|
-
exclude: [
|
|
242
|
+
cors: { origin: ['https://myapp.com'], credentials: true },
|
|
243
|
+
headers: { 'X-Powered-By': 'ExisJS' },
|
|
244
|
+
exclude: ['/admin/health'],
|
|
238
245
|
})
|
|
239
246
|
export default class AdminBoundary {
|
|
240
247
|
// Pre-handler hook
|
|
241
248
|
beforeHandle(req: Request, res: Response) {
|
|
242
|
-
if (req.headers[
|
|
243
|
-
res.status(403).json({ error:
|
|
244
|
-
return false
|
|
249
|
+
if (req.headers['x-role'] !== 'admin') {
|
|
250
|
+
res.status(403).json({ error: 'Admin role required' })
|
|
251
|
+
return false
|
|
245
252
|
}
|
|
246
253
|
}
|
|
247
254
|
|
|
248
255
|
// Post-handler hook
|
|
249
256
|
afterHandle(req: Request, res: Response, data: any) {
|
|
250
|
-
return { ...data, auditedAt: Date.now() }
|
|
257
|
+
return { ...data, auditedAt: Date.now() }
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
// Chain step
|
|
254
261
|
logStep(req: Request, res: Response, next: Next) {
|
|
255
|
-
console.log(`[Admin] ${req.method} ${req.path}`)
|
|
256
|
-
next()
|
|
262
|
+
console.log(`[Admin] ${req.method} ${req.path}`)
|
|
263
|
+
next()
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
// Wrapper
|
|
260
267
|
async handle(ctx: BoundaryContext, next: Next) {
|
|
261
268
|
try {
|
|
262
|
-
return { success: true, data: await next() }
|
|
269
|
+
return { success: true, data: await next() }
|
|
263
270
|
} catch (err) {
|
|
264
|
-
return { success: false, error: { message: (err as Error).message } }
|
|
271
|
+
return { success: false, error: { message: (err as Error).message } }
|
|
265
272
|
}
|
|
266
273
|
}
|
|
267
274
|
}
|
|
@@ -276,39 +283,39 @@ ExisJS natively discovers and mounts `src/http/error.ts` (or `error.js`) at boot
|
|
|
276
283
|
### Functional
|
|
277
284
|
|
|
278
285
|
```typescript filename="src/http/error.ts"
|
|
279
|
-
import type { Request, Response } from
|
|
286
|
+
import type { Request, Response } from 'exisjs'
|
|
280
287
|
|
|
281
288
|
export function onError(err: any, req: Request, res: Response) {
|
|
282
|
-
const status = err.statusCode || err.status || 500
|
|
289
|
+
const status = err.statusCode || err.status || 500
|
|
283
290
|
|
|
284
291
|
// Format consistent error response across the entire API
|
|
285
292
|
res.status(status).json({
|
|
286
293
|
success: false,
|
|
287
294
|
error: {
|
|
288
|
-
message: err.message ||
|
|
289
|
-
code: err.code ||
|
|
295
|
+
message: err.message || 'Internal Server Error',
|
|
296
|
+
code: err.code || 'INTERNAL_SERVER_ERROR',
|
|
290
297
|
path: req.path,
|
|
291
298
|
},
|
|
292
|
-
})
|
|
299
|
+
})
|
|
293
300
|
}
|
|
294
301
|
```
|
|
295
302
|
|
|
296
303
|
### Class-Based (OOP)
|
|
297
304
|
|
|
298
305
|
```typescript filename="src/http/error.ts"
|
|
299
|
-
import type { Request, Response } from
|
|
306
|
+
import type { Request, Response } from 'exisjs'
|
|
300
307
|
|
|
301
308
|
export default class GlobalErrorHandler {
|
|
302
309
|
onError(err: any, req: Request, res: Response) {
|
|
303
|
-
const status = err.statusCode || err.status || 500
|
|
310
|
+
const status = err.statusCode || err.status || 500
|
|
304
311
|
res.status(status).json({
|
|
305
312
|
success: false,
|
|
306
313
|
error: {
|
|
307
|
-
message: err.message ||
|
|
308
|
-
code: err.code ||
|
|
314
|
+
message: err.message || 'Internal Server Error',
|
|
315
|
+
code: err.code || 'INTERNAL_ERROR',
|
|
309
316
|
path: req.path,
|
|
310
317
|
},
|
|
311
|
-
})
|
|
318
|
+
})
|
|
312
319
|
}
|
|
313
320
|
}
|
|
314
321
|
```
|
|
@@ -324,32 +331,46 @@ Global infrastructure: port, SSL, CORS defaults, security headers, and
|
|
|
324
331
|
official package registration.
|
|
325
332
|
|
|
326
333
|
```typescript filename="exis.config.ts"
|
|
327
|
-
import { defineConfig } from
|
|
334
|
+
import { defineConfig } from 'exisjs'
|
|
328
335
|
|
|
329
336
|
export default defineConfig({
|
|
330
337
|
port: 3000,
|
|
331
|
-
cors: { origin:
|
|
338
|
+
cors: { origin: '*' },
|
|
332
339
|
// plugins: [] — official packages (@exisjs/database, @exisjs/auth, etc.)
|
|
333
340
|
// are registered here once installed. None are required by default.
|
|
334
|
-
})
|
|
341
|
+
})
|
|
335
342
|
```
|
|
336
343
|
|
|
337
344
|
---
|
|
338
345
|
|
|
339
|
-
## `src/env.ts`
|
|
346
|
+
## `src/config/env.ts`
|
|
340
347
|
|
|
341
|
-
```typescript filename="src/env.ts"
|
|
342
|
-
import { tex } from
|
|
348
|
+
```typescript filename="src/config/env.ts"
|
|
349
|
+
import { tex } from 'exisjs/validator'
|
|
343
350
|
|
|
344
351
|
export const env = tex
|
|
345
|
-
.
|
|
346
|
-
PORT: tex.number({
|
|
347
|
-
NODE_ENV: tex.enum([
|
|
352
|
+
.env({
|
|
353
|
+
PORT: tex.number({ default: 4000 }),
|
|
354
|
+
NODE_ENV: tex.enum(['development', 'production', 'test'] as const, {
|
|
355
|
+
default: 'development',
|
|
356
|
+
}),
|
|
357
|
+
CORS_ORIGIN: tex.string({ default: '*' }),
|
|
348
358
|
})
|
|
349
|
-
.parse(process.env)
|
|
359
|
+
.parse(process.env)
|
|
350
360
|
```
|
|
351
361
|
|
|
352
|
-
Boot fails immediately if a required variable is missing.
|
|
362
|
+
Boot fails immediately with clear diagnostics if a required environment variable is missing or malformed.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## `src/database/`
|
|
367
|
+
|
|
368
|
+
The `src/database/` directory organizes your persistent storage layer:
|
|
369
|
+
|
|
370
|
+
- `src/database/db.ts`: Connection instance, client initialization, and lifecycle registration via `registerDatabase(app, { connect, disconnect, healthCheck })` (or ORMs like Prisma, Drizzle, Mongoose).
|
|
371
|
+
- `src/database/models/` (or `src/models/`): Data models, entity definitions, and database schemas.
|
|
372
|
+
- `src/database/repositories/`: Abstracted data access objects and custom query builders.
|
|
373
|
+
- `src/database/migrations/`: Database migration scripts.
|
|
353
374
|
|
|
354
375
|
---
|
|
355
376
|
|
|
@@ -360,11 +381,16 @@ production. Add to `.gitignore`.
|
|
|
360
381
|
|
|
361
382
|
---
|
|
362
383
|
|
|
363
|
-
## Custom Directories
|
|
384
|
+
## Auto-Discovered vs Custom Directories
|
|
364
385
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
386
|
+
- **Auto-Discovered by Framework**:
|
|
387
|
+
- `src/http/`: Route handlers (`route.ts`), request schemas (`schema.ts`), boundaries (`boundary.ts`), and global error handler (`error.ts`).
|
|
388
|
+
- `src/cron/`: Scheduled tasks and background cron jobs (`cleanup.ts`, `reports.ts`).
|
|
389
|
+
- **Standard Recommended Conventions**:
|
|
390
|
+
- `src/database/`: Database client (`db.ts`), models (`models/`), and migrations.
|
|
391
|
+
- `src/common/`: Shared guards (`guards/`), interceptors, and exception filters.
|
|
392
|
+
- `src/config/`: Environment configuration (`config/env.ts`).
|
|
393
|
+
- `src/services/` or `src/dto/`: Optional shared business logic or global DTOs across multiple routes.
|
|
368
394
|
|
|
369
395
|
---
|
|
370
396
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description:
|
|
2
|
+
title: 'Configuration'
|
|
3
|
+
description: 'API Reference for the global exis.config.ts file (`exisjs/config`).'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Configuration (`exisjs/config`)
|
|
@@ -61,22 +61,22 @@ export default async (phase: string, { defaultConfig }) => {
|
|
|
61
61
|
|
|
62
62
|
return defineConfig({
|
|
63
63
|
port: Number(process.env.PORT) || 4000,
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
// Auto-spawn Worker threads based on CPU cores!
|
|
66
|
-
workers: isDev ? 1 : 'safe',
|
|
67
|
-
|
|
66
|
+
workers: isDev ? 1 : 'safe',
|
|
67
|
+
|
|
68
68
|
// Globally configure native middleware
|
|
69
69
|
cors: {
|
|
70
70
|
origin: isDev ? '*' : 'https://myapp.com',
|
|
71
|
-
credentials: true
|
|
71
|
+
credentials: true,
|
|
72
72
|
},
|
|
73
73
|
helmet: { enabled: true },
|
|
74
74
|
logger: isDev ? { level: 'debug', pretty: true } : false,
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
// Native plugin registration
|
|
77
77
|
plugins: [
|
|
78
78
|
// ... your plugins here
|
|
79
|
-
]
|
|
79
|
+
],
|
|
80
80
|
})
|
|
81
81
|
}
|
|
82
82
|
```
|
|
@@ -99,20 +99,31 @@ If you export a function from `exis.config.ts`, ExisJS will inject the current "
|
|
|
99
99
|
Here is the full list of options supported natively by the ExisJS configuration engine:
|
|
100
100
|
|
|
101
101
|
### Server & Network
|
|
102
|
-
|
|
103
|
-
- **`
|
|
104
|
-
- **`
|
|
105
|
-
- **`
|
|
106
|
-
- **`
|
|
102
|
+
|
|
103
|
+
- **`port`** _(number)_: The port the server listens on (default: `4000`).
|
|
104
|
+
- **`host`** _(string)_: The host interface to bind to (default: `'0.0.0.0'`).
|
|
105
|
+
- **`server`** _(`'node' | 'uws' | 'auto'`)_: ExisJS supports running on standard Node `http`, or incredibly fast C++ `uWebSockets.js` (`uws`). Default is `'auto'` (uses `uws` if installed, falls back to `node`).
|
|
106
|
+
- **`workers`** _(number | `'safe'` | `'max'`)_: Number of Node.js Cluster workers to spawn. `'max'` uses all CPU cores. `'safe'` uses all cores minus 1 (to leave room for the OS).
|
|
107
|
+
- **`trustProxy`** _(boolean | number)_: Enable if running behind reverse proxies or CDNs (Cloudflare, AWS ALB, NGINX, Render) to evaluate `CF-Connecting-IP`, `True-Client-IP`, `X-Real-IP`, and `X-Forwarded-For`. (Can also be enabled via `TRUST_PROXY=true`).
|
|
107
108
|
|
|
108
109
|
### Security & Middlewares
|
|
109
|
-
|
|
110
|
-
- **`
|
|
111
|
-
- **`
|
|
112
|
-
- **`
|
|
113
|
-
- **`
|
|
110
|
+
|
|
111
|
+
- **`cors`** _(boolean | object)_: Enable and configure Cross-Origin Resource Sharing globally.
|
|
112
|
+
- **`helmet`** _(boolean | object)_: Enable strict security headers. Enabled by default.
|
|
113
|
+
- **`logger`** _(boolean | object)_: Configure the efficient Pino request logger.
|
|
114
|
+
- **`compression`** _(boolean | object)_: Enable gzip/brotli response compression.
|
|
115
|
+
- **`etag`** _(boolean)_: Automatic ETag generation for HTTP conditional requests (default: `false` for dynamic API routes).
|
|
116
|
+
- **`bodyLimit`** _(number)_: Maximum JSON/URL-encoded payload size in bytes (default: `10MB`).
|
|
117
|
+
|
|
118
|
+
### Development Server & File Watcher (`dev`)
|
|
119
|
+
|
|
120
|
+
- **`dev.watch`** _(string[])_: Custom directories or files to watch during development (default: `[cwd]`).
|
|
121
|
+
- **`dev.ignored`** _((string | RegExp)[])_: Additional files, extensions, or regex patterns to ignore during hot reload.
|
|
122
|
+
- **`dev.usePolling`** _(boolean)_: Enables chokidar polling fallback (default: `true` on Windows).
|
|
123
|
+
- **`dev.interval`** _(number)_: Polling file system interval in milliseconds (default: `100`).
|
|
114
124
|
|
|
115
125
|
### Extended Integrations
|
|
126
|
+
|
|
116
127
|
- **`queue`**: Define your background queue driver (`memory` or `redis`) and configure `maxConcurrent` processing limits.
|
|
117
128
|
- **`plugins`**: An array of `ExisPlugin` instances to register natively into the framework.
|
|
118
129
|
- **`test`**: Configure testing environments (`include`, `exclude`, `setupFiles`, `coverage`).
|