exisjs 0.7.11 → 0.7.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +195 -39
  2. package/dist/cli/commands/dev.d.ts +4 -0
  3. package/dist/cli/commands/dev.d.ts.map +1 -1
  4. package/dist/cli/commands/dev.js +136 -32
  5. package/dist/cli/commands/dev.js.map +1 -1
  6. package/dist/cli/commands/run.d.ts +7 -0
  7. package/dist/cli/commands/run.d.ts.map +1 -0
  8. package/dist/cli/commands/run.js +99 -0
  9. package/dist/cli/commands/run.js.map +1 -0
  10. package/dist/cli/index.js +29 -13
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/manifest.d.ts.map +1 -1
  13. package/dist/cli/manifest.js +18 -3
  14. package/dist/cli/manifest.js.map +1 -1
  15. package/dist/cli/routes.d.ts +6 -0
  16. package/dist/cli/routes.d.ts.map +1 -1
  17. package/dist/cli/routes.js +224 -135
  18. package/dist/cli/routes.js.map +1 -1
  19. package/dist/config/config.d.ts +2 -1
  20. package/dist/config/config.d.ts.map +1 -1
  21. package/dist/config/config.js.map +1 -1
  22. package/dist/config/types.d.ts +30 -9
  23. package/dist/config/types.d.ts.map +1 -1
  24. package/dist/database/lifecycle.d.ts +6 -1
  25. package/dist/database/lifecycle.d.ts.map +1 -1
  26. package/dist/database/lifecycle.js +15 -4
  27. package/dist/database/lifecycle.js.map +1 -1
  28. package/dist/decorators/params.d.ts +21 -0
  29. package/dist/decorators/params.d.ts.map +1 -1
  30. package/dist/decorators/params.js +21 -1
  31. package/dist/decorators/params.js.map +1 -1
  32. package/dist/di/container.d.ts +15 -6
  33. package/dist/di/container.d.ts.map +1 -1
  34. package/dist/di/container.js +26 -3
  35. package/dist/di/container.js.map +1 -1
  36. package/dist/di/index.d.ts +1 -1
  37. package/dist/di/index.d.ts.map +1 -1
  38. package/dist/di/index.js.map +1 -1
  39. package/dist/di/inject.d.ts +2 -1
  40. package/dist/di/inject.d.ts.map +1 -1
  41. package/dist/di/inject.js +2 -1
  42. package/dist/di/inject.js.map +1 -1
  43. package/dist/docs/BENCHMARKS.md +4 -4
  44. package/dist/docs/content/README.md +43 -44
  45. package/dist/docs/content/boundaries.mdx +141 -48
  46. package/dist/docs/content/cli/commands.mdx +161 -107
  47. package/dist/docs/content/cli/overview.mdx +61 -46
  48. package/dist/docs/content/cli/usage.mdx +107 -30
  49. package/dist/docs/content/config.mdx +145 -147
  50. package/dist/docs/content/controllers.mdx +193 -464
  51. package/dist/docs/content/cron.mdx +183 -69
  52. package/dist/docs/content/decorators.mdx +449 -154
  53. package/dist/docs/content/errors.mdx +202 -305
  54. package/dist/docs/content/exisjs-mcp.mdx +59 -49
  55. package/dist/docs/content/installation.mdx +243 -157
  56. package/dist/docs/content/introduction.mdx +220 -48
  57. package/dist/docs/content/middleware.mdx +171 -497
  58. package/dist/docs/content/modules.mdx +141 -83
  59. package/dist/docs/content/providers.mdx +216 -180
  60. package/dist/docs/content/requests.mdx +168 -488
  61. package/dist/docs/content/responses.mdx +200 -533
  62. package/dist/docs/content/routing.mdx +239 -558
  63. package/dist/docs/content/security.mdx +233 -57
  64. package/dist/docs/content/structure.mdx +151 -240
  65. package/dist/docs/content/swagger.mdx +192 -91
  66. package/dist/docs/content/testing.mdx +205 -74
  67. package/dist/docs/content/validation.mdx +479 -341
  68. package/dist/docs/reference/README.md +34 -2
  69. package/dist/docs/reference/config.mdx +277 -73
  70. package/dist/docs/reference/context.mdx +197 -106
  71. package/dist/docs/reference/cron.mdx +266 -152
  72. package/dist/docs/reference/database.mdx +135 -53
  73. package/dist/docs/reference/decorators.mdx +234 -116
  74. package/dist/docs/reference/di.mdx +146 -112
  75. package/dist/docs/reference/error.mdx +155 -178
  76. package/dist/docs/reference/exis.mdx +141 -70
  77. package/dist/docs/reference/logger.mdx +110 -126
  78. package/dist/docs/reference/middleware.mdx +235 -158
  79. package/dist/docs/reference/plugin.mdx +136 -64
  80. package/dist/docs/reference/router.mdx +277 -251
  81. package/dist/docs/reference/sanitize.mdx +91 -87
  82. package/dist/docs/reference/swagger.mdx +280 -100
  83. package/dist/docs/reference/testing.mdx +223 -89
  84. package/dist/docs/reference/validator.mdx +194 -206
  85. package/dist/lib/inspect-routes.d.ts +2 -0
  86. package/dist/lib/inspect-routes.d.ts.map +1 -0
  87. package/dist/lib/inspect-routes.js +148 -0
  88. package/dist/lib/inspect-routes.js.map +1 -0
  89. package/dist/lib/start-server.js +13 -8
  90. package/dist/lib/start-server.js.map +1 -1
  91. package/dist/module/module.d.ts +6 -3
  92. package/dist/module/module.d.ts.map +1 -1
  93. package/dist/module/module.js +17 -3
  94. package/dist/module/module.js.map +1 -1
  95. package/dist/router/controller-registrar.d.ts.map +1 -1
  96. package/dist/router/controller-registrar.js +26 -4
  97. package/dist/router/controller-registrar.js.map +1 -1
  98. package/dist/router/scanner/cron-mounter.d.ts.map +1 -1
  99. package/dist/router/scanner/cron-mounter.js +9 -2
  100. package/dist/router/scanner/cron-mounter.js.map +1 -1
  101. package/dist/server/app.d.ts +3 -1
  102. package/dist/server/app.d.ts.map +1 -1
  103. package/dist/server/app.js +29 -8
  104. package/dist/server/app.js.map +1 -1
  105. package/dist/server/define.d.ts.map +1 -1
  106. package/dist/server/define.js +5 -0
  107. package/dist/server/define.js.map +1 -1
  108. package/dist/server/request.d.ts +2 -0
  109. package/dist/server/request.d.ts.map +1 -1
  110. package/dist/server/request.js +11 -0
  111. package/dist/server/request.js.map +1 -1
  112. package/dist/swagger/generator.d.ts.map +1 -1
  113. package/dist/swagger/generator.js +130 -10
  114. package/dist/swagger/generator.js.map +1 -1
  115. package/dist/telemetry/index.d.ts +12 -0
  116. package/dist/telemetry/index.d.ts.map +1 -0
  117. package/dist/telemetry/index.js +121 -0
  118. package/dist/telemetry/index.js.map +1 -0
  119. package/dist/testing/client.d.ts +1 -0
  120. package/dist/testing/client.d.ts.map +1 -1
  121. package/dist/testing/client.js +4 -1
  122. package/dist/testing/client.js.map +1 -1
  123. package/dist/testing/index.d.ts.map +1 -1
  124. package/dist/testing/index.js +20 -4
  125. package/dist/testing/index.js.map +1 -1
  126. package/dist/validator/builder.d.ts +26 -1
  127. package/dist/validator/builder.d.ts.map +1 -1
  128. package/dist/validator/builder.js +31 -0
  129. package/dist/validator/builder.js.map +1 -1
  130. package/dist/validator/engine.d.ts +23 -0
  131. package/dist/validator/engine.d.ts.map +1 -1
  132. package/dist/validator/engine.js +228 -31
  133. package/dist/validator/engine.js.map +1 -1
  134. package/dist/validator/index.d.ts +8 -4
  135. package/dist/validator/index.d.ts.map +1 -1
  136. package/dist/validator/index.js +4 -1
  137. package/dist/validator/index.js.map +1 -1
  138. package/dist/validator/tex-types.d.ts +15 -0
  139. package/dist/validator/tex-types.d.ts.map +1 -1
  140. package/dist/validator/tex-types.js +102 -0
  141. package/dist/validator/tex-types.js.map +1 -1
  142. package/dist/validator/tex.d.ts +1 -1
  143. package/dist/validator/tex.d.ts.map +1 -1
  144. package/dist/validator/tex.js +3 -1
  145. package/dist/validator/tex.js.map +1 -1
  146. package/dist/validator/types.d.ts +1 -0
  147. package/dist/validator/types.d.ts.map +1 -1
  148. package/package.json +47 -14
  149. package/dist/docs/content/dataloader.mdx +0 -170
  150. package/dist/docs/reference/dataloader.mdx +0 -91
@@ -1,86 +1,88 @@
1
1
  ---
2
- nextUrl: "/docs/validation"
3
- nextTitle: "Validation"
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 (Config & Request Pipeline)
8
+ # Boundaries & Middleware Cascading (`boundary.ts`)
7
9
 
8
- A `boundary.ts` acts as a folder-scoped configuration and request pipeline in your file-system routing architecture.
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 in its folder and all subfolders recursively. Instead of manually registering middleware chains or route guards, placing a `boundary.ts` configures **CORS**, **Headers**, **Excluded Paths**, **Dependency Injection**, and an **auto-detected request pipeline** cleanly scoped to that sub-tree.
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` has two distinct, independent parts:
18
+ A `boundary.ts` consists of two distinct components:
17
19
 
18
- 1. **Config** — pure data, declared via `defineBoundary()` (functional) or the `@Boundary()` class decorator (OOP). It configures CORS, headers, DI providers, timeout, and exclusion rules.
19
- 2. **Pipeline** — real code, auto-detected by function signature. No manual pipeline registration is needed.
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
- | Signature | Detected as | Execution timing |
22
- |---|---|---|
23
- | `(req, res, next)` | Chain Step | Runs in file/declaration order, before the route |
24
- | `beforeHandle(req, res)` | Boundary Guard Hook | Immediately before the route handler. Short-circuits on `false` or when sending headers |
25
- | `afterHandle(req, res, data)` | Response Hook | Immediately after the route handler. Mutates returned payload or logs audit data |
26
- | `(ctx, next)` — default export (functional) or class method `handle(ctx, next)` (OOP) | Request Wrapper | Wraps around all chain steps and the nested route/boundary |
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
- In functional style, export `config = defineBoundary(...)`, export named middleware functions `(req, res, next)` for chain steps, and optionally provide a `default export` function `(ctx, next)` as an onion-style wrapper. You can also configure `beforeHandle` and `afterHandle` hooks directly inside `defineBoundary({ ... })`.
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. Configuration (CORS, Headers, Exclusions, Hooks, DI Providers)
41
+ // 1. Declarative Boundary Configuration
40
42
  export const config = defineBoundary({
41
43
  cors: {
42
- origin: ['https://admin.yourdomain.com'],
44
+ origin: ['https://admin.example.com'],
43
45
  credentials: true,
44
46
  },
45
47
  headers: {
46
- 'X-Admin-Api-Version': '2.0',
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: Return false to reject with 403, or send custom response
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 hook: Transform outgoing response payload or perform async audit
63
+ // Post-handler response transformation hook
62
64
  afterHandle(req, res, data) {
63
- res.setHeader('X-Audited', 'true')
64
- return { ...data, auditedAt: Date.now() }
65
+ res.setHeader('X-Audited-At', new Date().toISOString())
66
+ return { ...data, audited: true }
65
67
  },
66
68
  })
67
69
 
68
- // 2. Chain Step (auto-detected by (req, res, next) signature)
69
- export function requireAuth(req: Request, res: Response, next: Next) {
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 (auto-detected as default export with (ctx, next) signature)
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 { success: true, data: result }
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
- In OOP style, decorate a class with `@Boundary(...)`. Named methods taking `(req, res, next)` are auto-detected as chain steps, `beforeHandle(req, res)` and `afterHandle(req, res, data)` act as lifecycle hooks, and a method named `handle(ctx, next)` serves as the request wrapper.
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.yourdomain.com'],
108
+ origin: ['https://admin.example.com'],
107
109
  credentials: true,
108
110
  },
109
111
  headers: {
110
- 'X-Admin-Api-Version': '2.0',
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', 'true')
126
- return { ...data, auditedAt: Date.now() }
127
+ res.setHeader('X-Audited-At', new Date().toISOString())
128
+ return { ...data, audited: true }
127
129
  }
128
130
 
129
- // Chain step
130
- requireAuth(req: Request, res: Response, next: Next) {
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
- // Wrapper
139
+ // Pipeline wrapper
138
140
  async handle(ctx: BoundaryContext, next: Next) {
139
141
  const start = Date.now()
140
142
  const result = await next()
@@ -146,28 +148,110 @@ export default class AdminBoundary {
146
148
 
147
149
  ---
148
150
 
149
- ## Cascading & Inheritance
151
+ ## Boundary Cascading & Execution Order
150
152
 
151
- Boundaries cascade down the directory tree. For instance:
153
+ Boundaries cascade top-down through the folder structure:
152
154
 
153
155
  ```text
154
156
  src/http/
155
- ├── boundary.ts # Root Boundary (applies to /api/*)
157
+ ├── boundary.ts # Root Boundary (applies to all routes)
156
158
  └── admin/
157
- ├── boundary.ts # Admin Boundary (applies to /api/admin/*)
159
+ ├── boundary.ts # Admin Boundary (applies to /admin/*)
158
160
  └── users/
159
- └── route.ts # Handled by both Root Boundary and Admin Boundary
161
+ └── route.ts # Handled by both Root Boundary and Admin Boundary
162
+ ```
163
+
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
190
+
191
+ Response headers defined across boundaries are deep-merged from root to child. If header keys conflict, the innermost boundary value takes precedence:
192
+
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
+ })
203
+
204
+ // Endpoints under /v2 receive:
205
+ // X-Framework: ExisJS
206
+ // X-Api-Version: v2
207
+ // X-Environment: v2-canary (overwritten by nested boundary)
208
+ ```
209
+
210
+ ---
211
+
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',
223
+
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
+ })
160
232
  ```
161
233
 
162
- 1. **Wrappers & Chain Steps**: Outer boundaries execute first. Root wrappers wrap nested wrappers, and root chain steps run before nested chain steps.
163
- 2. **CORS & Headers**: Sub-boundaries inherit and merge or override parent headers and CORS policies.
164
- 3. **Exclusions**: When a route matches an exclusion pattern (e.g. `/admin/health` in `exclude`), that boundary's chain steps and wrappers are bypassed for the request.
234
+ When an incoming request matches an exclusion rule, the boundary's chain steps and `beforeHandle` hooks are bypassed.
165
235
 
166
236
  ---
167
237
 
168
- ## Dependency Injection via Providers
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:
169
241
 
170
- Boundaries also act as IoC module scopes. You can scope providers to a directory tree using `providers`:
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:
171
255
 
172
256
  ```typescript filename="src/http/users/boundary.ts"
173
257
  import { defineBoundary } from 'exisjs/router'
@@ -179,3 +263,12 @@ export const config = defineBoundary({
179
263
  ],
180
264
  })
181
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.