exisjs 0.7.12 → 0.7.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -39
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +3 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/config/types.d.ts +2 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/decorators/params.d.ts +21 -0
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +21 -1
- package/dist/decorators/params.js.map +1 -1
- package/dist/di/container.d.ts +15 -6
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +26 -3
- package/dist/di/container.js.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/index.js.map +1 -1
- package/dist/di/inject.d.ts +2 -1
- package/dist/di/inject.d.ts.map +1 -1
- package/dist/di/inject.js +2 -1
- package/dist/di/inject.js.map +1 -1
- package/dist/docs/ARCHITECTURE.md +130 -78
- package/dist/docs/BENCHMARKS.md +4 -4
- package/dist/docs/content/README.md +43 -44
- package/dist/docs/content/boundaries.mdx +138 -61
- package/dist/docs/content/cli/commands.mdx +151 -128
- package/dist/docs/content/cli/overview.mdx +61 -47
- package/dist/docs/content/cli/usage.mdx +107 -30
- package/dist/docs/content/config.mdx +145 -158
- package/dist/docs/content/controllers.mdx +193 -464
- package/dist/docs/content/cron.mdx +183 -69
- package/dist/docs/content/decorators.mdx +449 -154
- package/dist/docs/content/errors.mdx +202 -305
- package/dist/docs/content/exisjs-mcp.mdx +59 -49
- package/dist/docs/content/installation.mdx +243 -157
- package/dist/docs/content/introduction.mdx +220 -48
- package/dist/docs/content/middleware.mdx +171 -497
- package/dist/docs/content/modules.mdx +141 -83
- package/dist/docs/content/providers.mdx +216 -180
- package/dist/docs/content/requests.mdx +168 -488
- package/dist/docs/content/responses.mdx +200 -533
- package/dist/docs/content/routing.mdx +235 -598
- package/dist/docs/content/security.mdx +233 -57
- package/dist/docs/content/structure.mdx +151 -240
- package/dist/docs/content/swagger.mdx +192 -91
- package/dist/docs/content/testing.mdx +205 -74
- package/dist/docs/content/validation.mdx +479 -341
- package/dist/docs/reference/README.md +34 -2
- package/dist/docs/reference/config.mdx +277 -75
- package/dist/docs/reference/context.mdx +197 -106
- package/dist/docs/reference/cron.mdx +266 -152
- package/dist/docs/reference/database.mdx +135 -53
- package/dist/docs/reference/decorators.mdx +234 -116
- package/dist/docs/reference/di.mdx +146 -112
- package/dist/docs/reference/error.mdx +155 -178
- package/dist/docs/reference/exis.mdx +141 -70
- package/dist/docs/reference/logger.mdx +110 -126
- package/dist/docs/reference/middleware.mdx +235 -158
- package/dist/docs/reference/plugin.mdx +136 -64
- package/dist/docs/reference/router.mdx +275 -293
- package/dist/docs/reference/sanitize.mdx +91 -87
- package/dist/docs/reference/swagger.mdx +280 -100
- package/dist/docs/reference/testing.mdx +223 -89
- package/dist/docs/reference/validator.mdx +194 -206
- package/dist/lib/start-server.js +22 -8
- package/dist/lib/start-server.js.map +1 -1
- package/dist/module/module.d.ts +6 -3
- package/dist/module/module.d.ts.map +1 -1
- package/dist/module/module.js +17 -3
- package/dist/module/module.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +26 -4
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/scanner/fs-scanner.d.ts.map +1 -1
- package/dist/router/scanner/fs-scanner.js +22 -0
- package/dist/router/scanner/fs-scanner.js.map +1 -1
- package/dist/router/types.d.ts +6 -4
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/app.d.ts +3 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +24 -8
- package/dist/server/app.js.map +1 -1
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +19 -0
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/request.d.ts +2 -0
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +11 -0
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +10 -5
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +65 -18
- package/dist/server/response.js.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/swagger/generator.js +130 -10
- package/dist/swagger/generator.js.map +1 -1
- package/dist/telemetry/index.d.ts +12 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +121 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/testing/client.d.ts +1 -0
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +4 -1
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +11 -4
- package/dist/testing/index.js.map +1 -1
- package/dist/validator/builder.d.ts +26 -1
- package/dist/validator/builder.d.ts.map +1 -1
- package/dist/validator/builder.js +31 -0
- package/dist/validator/builder.js.map +1 -1
- package/dist/validator/engine.d.ts +23 -0
- package/dist/validator/engine.d.ts.map +1 -1
- package/dist/validator/engine.js +228 -31
- package/dist/validator/engine.js.map +1 -1
- package/dist/validator/index.d.ts +8 -4
- package/dist/validator/index.d.ts.map +1 -1
- package/dist/validator/index.js +4 -1
- package/dist/validator/index.js.map +1 -1
- package/dist/validator/tex-types.d.ts +19 -4
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/tex-types.js +102 -0
- package/dist/validator/tex-types.js.map +1 -1
- package/dist/validator/tex.d.ts +1 -1
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +3 -1
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +1 -0
- package/dist/validator/types.d.ts.map +1 -1
- package/package.json +46 -14
- package/dist/docs/content/dataloader.mdx +0 -170
- package/dist/docs/reference/dataloader.mdx +0 -91
|
@@ -1,86 +1,88 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
title: 'Boundaries & Middleware Cascading'
|
|
3
|
+
description: 'Folder-scoped middleware cascading, security guards, CORS inheritance hierarchy, exclusion rules, and error handling with boundary.ts.'
|
|
4
|
+
nextUrl: '/docs/validation'
|
|
5
|
+
nextTitle: 'Validation & Schemas'
|
|
4
6
|
---
|
|
5
7
|
|
|
6
|
-
# Boundaries
|
|
8
|
+
# Boundaries & Middleware Cascading (`boundary.ts`)
|
|
7
9
|
|
|
8
|
-
A `boundary.ts`
|
|
10
|
+
A `boundary.ts` (or `<name>.boundary.ts`) file defines a folder-scoped request pipeline and configuration module in the ExisJS file-system routing hierarchy.
|
|
9
11
|
|
|
10
|
-
Any `boundary.ts` applies to every route
|
|
12
|
+
Any `boundary.ts` automatically applies to every route within its directory and all descendant subdirectories recursively, providing scoped **CORS policies**, **custom headers**, **security guards**, **lifecycle hooks**, **route exclusions**, and **dependency injection scoping**.
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
## Anatomy of a Boundary
|
|
15
17
|
|
|
16
|
-
A `boundary.ts`
|
|
18
|
+
A `boundary.ts` consists of two distinct components:
|
|
17
19
|
|
|
18
|
-
1. **
|
|
19
|
-
2. **Pipeline
|
|
20
|
+
1. **Configuration**: Pure declarative options declared via `defineBoundary()` (functional) or `@Boundary()` (OOP). It sets CORS, response headers, timeout limits, exclusion patterns, probe blackholing, and DI providers.
|
|
21
|
+
2. **Pipeline Handlers**: Functions and lifecycle hooks that run before, during, or after request execution:
|
|
20
22
|
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
| `(req, res, next)` |
|
|
24
|
-
| `beforeHandle(req, res)` |
|
|
25
|
-
| `afterHandle(req, res, data)` |
|
|
26
|
-
| `(ctx, next)`
|
|
23
|
+
| Handler Type | Signature | Description |
|
|
24
|
+
| :--- | :--- | :--- |
|
|
25
|
+
| **Chain Step** | `(req, res, next)` | Standard middleware step running in declaration order before route handlers |
|
|
26
|
+
| **Guard Hook** | `beforeHandle(req, res)` | Runs immediately before route handler execution; aborts if returning `false` |
|
|
27
|
+
| **Audit Hook** | `afterHandle(req, res, data)` | Runs immediately after route handler returns; can transform response data |
|
|
28
|
+
| **Pipeline Wrapper** | `(ctx, next)` / `handle(ctx, next)` | Onion-style wrapper around all steps and downstream routes in the boundary |
|
|
27
29
|
|
|
28
30
|
---
|
|
29
31
|
|
|
30
32
|
## 1. Functional Paradigm
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
Export `config = defineBoundary(...)`, named middleware functions `(req, res, next)`, and optionally a default export `(ctx, next)` as a request wrapper:
|
|
33
35
|
|
|
34
36
|
```typescript filename="src/http/admin/boundary.ts"
|
|
35
37
|
import { defineBoundary } from 'exisjs/router'
|
|
36
38
|
import type { Request, Response, Next, BoundaryContext } from 'exisjs/router'
|
|
37
39
|
import { UnauthorizedError } from 'exisjs/error'
|
|
38
40
|
|
|
39
|
-
// 1.
|
|
41
|
+
// 1. Declarative Boundary Configuration
|
|
40
42
|
export const config = defineBoundary({
|
|
41
43
|
cors: {
|
|
42
|
-
origin: ['https://admin.
|
|
44
|
+
origin: ['https://admin.example.com'],
|
|
43
45
|
credentials: true,
|
|
44
46
|
},
|
|
45
47
|
headers: {
|
|
46
|
-
'X-Admin-
|
|
48
|
+
'X-Admin-Scope': 'active',
|
|
47
49
|
},
|
|
48
50
|
exclude: [
|
|
49
51
|
'/admin/health',
|
|
50
|
-
{ path: '/admin/public/*', method: 'GET' }
|
|
52
|
+
{ path: '/admin/public/*', method: 'GET' },
|
|
51
53
|
],
|
|
52
54
|
|
|
53
|
-
// Pre-handler hook
|
|
55
|
+
// Pre-handler guard hook
|
|
54
56
|
beforeHandle(req, res) {
|
|
55
57
|
if (!req.headers['x-admin-token']) {
|
|
56
58
|
res.status(401).json({ error: 'Missing admin access token' })
|
|
57
|
-
return false
|
|
59
|
+
return false // Aborts downstream execution
|
|
58
60
|
}
|
|
59
61
|
},
|
|
60
62
|
|
|
61
|
-
// Post-handler
|
|
63
|
+
// Post-handler response transformation hook
|
|
62
64
|
afterHandle(req, res, data) {
|
|
63
|
-
res.setHeader('X-Audited',
|
|
64
|
-
return { ...data,
|
|
65
|
+
res.setHeader('X-Audited-At', new Date().toISOString())
|
|
66
|
+
return { ...data, audited: true }
|
|
65
67
|
},
|
|
66
68
|
})
|
|
67
69
|
|
|
68
|
-
// 2. Chain Step
|
|
69
|
-
export function
|
|
70
|
+
// 2. Named Chain Step Middleware
|
|
71
|
+
export function verifyApiKey(req: Request, res: Response, next: Next) {
|
|
70
72
|
if (!req.headers['x-api-key']) {
|
|
71
|
-
throw new UnauthorizedError('Missing API key')
|
|
73
|
+
throw new UnauthorizedError('Missing required API key')
|
|
72
74
|
}
|
|
73
75
|
next()
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
// 3. Wrapper (
|
|
78
|
+
// 3. Pipeline Wrapper (Default Export)
|
|
77
79
|
export default async function (ctx: BoundaryContext, next: Next) {
|
|
78
80
|
const start = Date.now()
|
|
79
81
|
try {
|
|
80
82
|
const result = await next()
|
|
81
83
|
const duration = Date.now() - start
|
|
82
84
|
ctx.res.setHeader('X-Response-Time', `${duration}ms`)
|
|
83
|
-
return
|
|
85
|
+
return result
|
|
84
86
|
} catch (err: any) {
|
|
85
87
|
return {
|
|
86
88
|
success: false,
|
|
@@ -94,7 +96,7 @@ export default async function (ctx: BoundaryContext, next: Next) {
|
|
|
94
96
|
|
|
95
97
|
## 2. Class-Based (OOP) Paradigm
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
Decorate a class with `@Boundary(...)`. Class methods taking `(req, res, next)` become chain steps, `beforeHandle` / `afterHandle` serve as lifecycle hooks, and a method named `handle(ctx, next)` acts as the pipeline wrapper:
|
|
98
100
|
|
|
99
101
|
```typescript filename="src/http/admin/boundary.ts"
|
|
100
102
|
import { Boundary } from 'exisjs/decorators'
|
|
@@ -103,11 +105,11 @@ import { UnauthorizedError } from 'exisjs/error'
|
|
|
103
105
|
|
|
104
106
|
@Boundary({
|
|
105
107
|
cors: {
|
|
106
|
-
origin: ['https://admin.
|
|
108
|
+
origin: ['https://admin.example.com'],
|
|
107
109
|
credentials: true,
|
|
108
110
|
},
|
|
109
111
|
headers: {
|
|
110
|
-
'X-Admin-
|
|
112
|
+
'X-Admin-Scope': 'active',
|
|
111
113
|
},
|
|
112
114
|
exclude: ['/admin/health'],
|
|
113
115
|
})
|
|
@@ -122,19 +124,19 @@ export default class AdminBoundary {
|
|
|
122
124
|
|
|
123
125
|
// Post-handler hook
|
|
124
126
|
afterHandle(req: Request, res: Response, data: any) {
|
|
125
|
-
res.setHeader('X-Audited',
|
|
126
|
-
return { ...data,
|
|
127
|
+
res.setHeader('X-Audited-At', new Date().toISOString())
|
|
128
|
+
return { ...data, audited: true }
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
+
// Named chain step
|
|
132
|
+
verifyApiKey(req: Request, res: Response, next: Next) {
|
|
131
133
|
if (!req.headers['x-api-key']) {
|
|
132
|
-
throw new UnauthorizedError('Missing API key')
|
|
134
|
+
throw new UnauthorizedError('Missing required API key')
|
|
133
135
|
}
|
|
134
136
|
next()
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
//
|
|
139
|
+
// Pipeline wrapper
|
|
138
140
|
async handle(ctx: BoundaryContext, next: Next) {
|
|
139
141
|
const start = Date.now()
|
|
140
142
|
const result = await next()
|
|
@@ -146,44 +148,110 @@ export default class AdminBoundary {
|
|
|
146
148
|
|
|
147
149
|
---
|
|
148
150
|
|
|
149
|
-
## Cascading &
|
|
151
|
+
## Boundary Cascading & Execution Order
|
|
150
152
|
|
|
151
|
-
Boundaries cascade down the
|
|
153
|
+
Boundaries cascade top-down through the folder structure:
|
|
152
154
|
|
|
153
155
|
```text
|
|
154
156
|
src/http/
|
|
155
|
-
├── boundary.ts
|
|
157
|
+
├── boundary.ts # Root Boundary (applies to all routes)
|
|
156
158
|
└── admin/
|
|
157
|
-
├── boundary.ts
|
|
159
|
+
├── boundary.ts # Admin Boundary (applies to /admin/*)
|
|
158
160
|
└── users/
|
|
159
|
-
└── route.ts
|
|
161
|
+
└── route.ts # Handled by both Root Boundary and Admin Boundary
|
|
160
162
|
```
|
|
161
163
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
### Execution Order
|
|
165
|
+
|
|
166
|
+
1. **Wrappers**: Outer wrappers wrap inner wrappers (onion architecture).
|
|
167
|
+
2. **Chain Steps**: Root chain steps run before nested boundary chain steps.
|
|
168
|
+
3. **Guard Hooks (`beforeHandle`)**: Run immediately before the route handler, evaluated from root boundary down to the innermost boundary.
|
|
169
|
+
4. **Response Hooks (`afterHandle`)**: Run in reverse order (innermost boundary up to root boundary).
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## CORS Inheritance & Precedence
|
|
174
|
+
|
|
175
|
+
CORS configuration follows a 4-tier hierarchy:
|
|
176
|
+
|
|
177
|
+
| Priority | Level | Where Defined | Description |
|
|
178
|
+
| :---: | :--- | :--- | :--- |
|
|
179
|
+
| 1 | **Route Override** | `route.get('/', { cors: ... })` or `@Cors(...)` | Specific to a single endpoint; overrides all boundaries and global configs |
|
|
180
|
+
| 2 | **Innermost Boundary** | `src/http/admin/boundary.ts` | Scoped to subfolder tree; overrides parent boundaries |
|
|
181
|
+
| 3 | **Parent / Root Boundary** | `src/http/boundary.ts` | Scoped to all routes in `src/http/`; overrides global defaults |
|
|
182
|
+
| 4 | **Global Configuration** | `exis.config.ts` (`cors`) | Fallback baseline for endpoints without boundary or route CORS |
|
|
183
|
+
|
|
184
|
+
> [!NOTE]
|
|
185
|
+
> If a sub-boundary omits `cors`, it inherits the CORS settings of the nearest ancestor boundary (or `exis.config.ts`). Setting `cors: false` on a boundary explicitly disables CORS for that entire subtree.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Headers Merging
|
|
170
190
|
|
|
171
|
-
|
|
191
|
+
Response headers defined across boundaries are deep-merged from root to child. If header keys conflict, the innermost boundary value takes precedence:
|
|
172
192
|
|
|
173
|
-
|
|
193
|
+
```typescript
|
|
194
|
+
// src/http/boundary.ts (Root)
|
|
195
|
+
export const config = defineBoundary({
|
|
196
|
+
headers: { 'X-Framework': 'ExisJS', 'X-Environment': 'production' },
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// src/http/v2/boundary.ts (Nested)
|
|
200
|
+
export const config = defineBoundary({
|
|
201
|
+
headers: { 'X-Api-Version': 'v2', 'X-Environment': 'v2-canary' },
|
|
202
|
+
})
|
|
174
203
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
| **Global Config** | `exis.config.ts` (`defineConfig({ cors })`) | **Base** | Fallback baseline for any route not covered by a boundary or route override. |
|
|
204
|
+
// Endpoints under /v2 receive:
|
|
205
|
+
// X-Framework: ExisJS
|
|
206
|
+
// X-Api-Version: v2
|
|
207
|
+
// X-Environment: v2-canary (overwritten by nested boundary)
|
|
208
|
+
```
|
|
181
209
|
|
|
182
210
|
---
|
|
183
211
|
|
|
184
|
-
##
|
|
212
|
+
## Route Exclusion Rules
|
|
213
|
+
|
|
214
|
+
To bypass boundary middleware for specific routes (e.g. public webhooks or health checks), add rules to `exclude`:
|
|
215
|
+
|
|
216
|
+
```typescript filename="src/http/boundary.ts"
|
|
217
|
+
import { defineBoundary } from 'exisjs/router'
|
|
218
|
+
|
|
219
|
+
export const config = defineBoundary({
|
|
220
|
+
exclude: [
|
|
221
|
+
// 1. Exact path string
|
|
222
|
+
'/health',
|
|
185
223
|
|
|
186
|
-
|
|
224
|
+
// 2. Wildcard pattern matching any HTTP method
|
|
225
|
+
'/public/*',
|
|
226
|
+
|
|
227
|
+
// 3. Path with specific HTTP method restriction
|
|
228
|
+
{ path: '/webhooks/stripe', method: 'POST' },
|
|
229
|
+
{ path: '/docs/*', methods: ['GET', 'HEAD'] },
|
|
230
|
+
],
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
When an incoming request matches an exclusion rule, the boundary's chain steps and `beforeHandle` hooks are bypassed.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Noise Suppression & Exploit Probe Blackholing
|
|
239
|
+
|
|
240
|
+
ExisJS can automatically intercept and drop automated vulnerability scanner probes (e.g. `/.env`, `/.git`, `/.DS_Store`, `/wp-admin`) at the boundary level without flooding application logs:
|
|
241
|
+
|
|
242
|
+
```typescript filename="src/http/boundary.ts"
|
|
243
|
+
import { defineBoundary } from 'exisjs/router'
|
|
244
|
+
|
|
245
|
+
export const config = defineBoundary({
|
|
246
|
+
blockSuspiciousProbes: true, // Blackholes malicious probe paths immediately
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Dependency Injection Scoping
|
|
253
|
+
|
|
254
|
+
Boundaries can register scoped providers into the IoC container for all routes within their sub-tree:
|
|
187
255
|
|
|
188
256
|
```typescript filename="src/http/users/boundary.ts"
|
|
189
257
|
import { defineBoundary } from 'exisjs/router'
|
|
@@ -195,3 +263,12 @@ export const config = defineBoundary({
|
|
|
195
263
|
],
|
|
196
264
|
})
|
|
197
265
|
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Next Steps
|
|
270
|
+
|
|
271
|
+
- **[Validation Guide](/docs/validation)**: Building request schemas with `tex`.
|
|
272
|
+
- **[Dependency Injection](/docs/providers)**: IoC containers, tokens, and provider lifecycles.
|
|
273
|
+
- **[Routing](/docs/routing)**: File-system route parameters and handler definitions.
|
|
274
|
+
- **[Controllers](/docs/controllers)**: Class-based routing and parameter decorators.
|