exisjs 0.5.9 → 0.5.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/auth/index.d.ts +3 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oauth/core.d.ts +96 -0
- package/dist/auth/oauth/core.d.ts.map +1 -0
- package/dist/auth/oauth/core.js +203 -0
- package/dist/auth/oauth/core.js.map +1 -0
- package/dist/auth/oauth/index.d.ts +45 -0
- package/dist/auth/oauth/index.d.ts.map +1 -0
- package/dist/auth/oauth/index.js +64 -0
- package/dist/auth/oauth/index.js.map +1 -0
- package/dist/auth/oauth/middleware.d.ts +63 -0
- package/dist/auth/oauth/middleware.d.ts.map +1 -0
- package/dist/auth/oauth/middleware.js +164 -0
- package/dist/auth/oauth/middleware.js.map +1 -0
- package/dist/auth/oauth/providers/custom.d.ts +41 -0
- package/dist/auth/oauth/providers/custom.d.ts.map +1 -0
- package/dist/auth/oauth/providers/custom.js +204 -0
- package/dist/auth/oauth/providers/custom.js.map +1 -0
- package/dist/auth/oauth/providers/discord.d.ts +23 -0
- package/dist/auth/oauth/providers/discord.d.ts.map +1 -0
- package/dist/auth/oauth/providers/discord.js +130 -0
- package/dist/auth/oauth/providers/discord.js.map +1 -0
- package/dist/auth/oauth/providers/facebook.d.ts +28 -0
- package/dist/auth/oauth/providers/facebook.d.ts.map +1 -0
- package/dist/auth/oauth/providers/facebook.js +124 -0
- package/dist/auth/oauth/providers/facebook.js.map +1 -0
- package/dist/auth/oauth/providers/github.d.ts +25 -0
- package/dist/auth/oauth/providers/github.d.ts.map +1 -0
- package/dist/auth/oauth/providers/github.js +132 -0
- package/dist/auth/oauth/providers/github.js.map +1 -0
- package/dist/auth/oauth/providers/google.d.ts +23 -0
- package/dist/auth/oauth/providers/google.d.ts.map +1 -0
- package/dist/auth/oauth/providers/google.js +134 -0
- package/dist/auth/oauth/providers/google.js.map +1 -0
- package/dist/auth/oauth/providers/microsoft.d.ts +28 -0
- package/dist/auth/oauth/providers/microsoft.d.ts.map +1 -0
- package/dist/auth/oauth/providers/microsoft.js +136 -0
- package/dist/auth/oauth/providers/microsoft.js.map +1 -0
- package/dist/auth/oauth/types.d.ts +158 -0
- package/dist/auth/oauth/types.d.ts.map +1 -0
- package/dist/auth/oauth/types.js +4 -0
- package/dist/auth/oauth/types.js.map +1 -0
- package/dist/auth/session.d.ts +4 -1
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/auth/session.js +33 -5
- package/dist/auth/session.js.map +1 -1
- package/dist/cache/store.d.ts +11 -3
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +87 -19
- package/dist/cache/store.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +4 -6
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/database/adapters/mongodb.d.ts +58 -0
- package/dist/database/adapters/mongodb.d.ts.map +1 -0
- package/dist/database/adapters/mongodb.js +264 -0
- package/dist/database/adapters/mongodb.js.map +1 -0
- package/dist/database/adapters/mysql.d.ts +27 -0
- package/dist/database/adapters/mysql.d.ts.map +1 -0
- package/dist/database/adapters/mysql.js +188 -0
- package/dist/database/adapters/mysql.js.map +1 -0
- package/dist/database/adapters/postgres.d.ts +28 -0
- package/dist/database/adapters/postgres.d.ts.map +1 -0
- package/dist/database/adapters/postgres.js +174 -0
- package/dist/database/adapters/postgres.js.map +1 -0
- package/dist/database/adapters/sqlite.d.ts +27 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +149 -0
- package/dist/database/adapters/sqlite.js.map +1 -0
- package/dist/database/index.d.ts +42 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +58 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/manager.d.ts +114 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/manager.js +269 -0
- package/dist/database/manager.js.map +1 -0
- package/dist/database/migration.d.ts +57 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +329 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/query-builder.d.ts +89 -0
- package/dist/database/query-builder.d.ts.map +1 -0
- package/dist/database/query-builder.js +353 -0
- package/dist/database/query-builder.js.map +1 -0
- package/dist/database/types.d.ts +184 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +4 -0
- package/dist/database/types.js.map +1 -0
- package/dist/docs/ARCHITECTURE.md +349 -0
- package/dist/docs/BENCHMARKS.md +36 -0
- package/dist/docs/README.md +34 -0
- package/dist/docs/content/README.md +44 -0
- package/dist/docs/content/adapters.mdx +199 -0
- package/dist/docs/content/cache.mdx +239 -0
- package/dist/docs/content/circuit-breaker.mdx +130 -0
- package/dist/docs/content/cli/commands.mdx +214 -0
- package/dist/docs/content/cli/overview.mdx +92 -0
- package/dist/docs/content/cli/usage.mdx +61 -0
- package/dist/docs/content/config.mdx +214 -0
- package/dist/docs/content/controllers.mdx +241 -0
- package/dist/docs/content/cron.mdx +109 -0
- package/dist/docs/content/database.mdx +209 -0
- package/dist/docs/content/dataloader.mdx +170 -0
- package/dist/docs/content/decorators.mdx +204 -0
- package/dist/docs/content/errors.mdx +344 -0
- package/dist/docs/content/gateways.mdx +376 -0
- package/dist/docs/content/installation.mdx +210 -0
- package/dist/docs/content/introduction.mdx +96 -0
- package/dist/docs/content/middleware.mdx +441 -0
- package/dist/docs/content/modules.mdx +144 -0
- package/dist/docs/content/oauth.mdx +179 -0
- package/dist/docs/content/providers.mdx +147 -0
- package/dist/docs/content/queues.mdx +177 -0
- package/dist/docs/content/requests.mdx +293 -0
- package/dist/docs/content/responses.mdx +271 -0
- package/dist/docs/content/routing.mdx +541 -0
- package/dist/docs/content/rpc.mdx +146 -0
- package/dist/docs/content/security.mdx +294 -0
- package/dist/docs/content/sse.mdx +159 -0
- package/dist/docs/content/structure.mdx +116 -0
- package/dist/docs/content/testing.mdx +139 -0
- package/dist/docs/content/validation.mdx +249 -0
- package/dist/docs/content/websockets.mdx +289 -0
- package/dist/docs/integrations/README.md +23 -0
- package/dist/docs/integrations/jwt.mdx +151 -0
- package/dist/docs/integrations/mongo.mdx +183 -0
- package/dist/docs/integrations/observability.mdx +125 -0
- package/dist/docs/integrations/openai.mdx +108 -0
- package/dist/docs/integrations/postgres.mdx +190 -0
- package/dist/docs/integrations/posthog.mdx +122 -0
- package/dist/docs/integrations/redis.mdx +141 -0
- package/dist/docs/integrations/resend.mdx +117 -0
- package/dist/docs/integrations/s3.mdx +122 -0
- package/dist/docs/integrations/swagger.mdx +113 -0
- package/dist/docs/reference/README.md +3 -0
- package/dist/docs/reference/adapters.mdx +130 -0
- package/dist/docs/reference/auth.mdx +183 -0
- package/dist/docs/reference/cache.mdx +132 -0
- package/dist/docs/reference/circuit-breaker.mdx +71 -0
- package/dist/docs/reference/config.mdx +118 -0
- package/dist/docs/reference/context.mdx +168 -0
- package/dist/docs/reference/dataloader.mdx +91 -0
- package/dist/docs/reference/decorators.mdx +293 -0
- package/dist/docs/reference/di.mdx +123 -0
- package/dist/docs/reference/error.mdx +170 -0
- package/dist/docs/reference/exis.mdx +124 -0
- package/dist/docs/reference/middleware.mdx +304 -0
- package/dist/docs/reference/plugin.mdx +113 -0
- package/dist/docs/reference/queue.mdx +155 -0
- package/dist/docs/reference/router.mdx +248 -0
- package/dist/docs/reference/sanitize.mdx +130 -0
- package/dist/docs/reference/storage.mdx +116 -0
- package/dist/docs/reference/swagger.mdx +132 -0
- package/dist/docs/reference/testing.mdx +145 -0
- package/dist/docs/reference/validator.mdx +186 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/backpressure.d.ts.map +1 -1
- package/dist/middleware/backpressure.js +21 -12
- package/dist/middleware/backpressure.js.map +1 -1
- package/dist/middleware/cache.d.ts +6 -0
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +84 -28
- package/dist/middleware/cache.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +4 -1
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +29 -4
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +16 -0
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +29 -12
- package/dist/middleware/rate-limit.js.map +1 -1
- package/dist/queue/drivers/MemoryDriver.d.ts +6 -3
- package/dist/queue/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/queue/drivers/MemoryDriver.js +76 -23
- package/dist/queue/drivers/MemoryDriver.js.map +1 -1
- package/dist/server/bootstrapper.d.ts +3 -0
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +56 -8
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/bun-adapter.d.ts +2 -0
- package/dist/server/bun-adapter.d.ts.map +1 -1
- package/dist/server/bun-adapter.js +23 -12
- package/dist/server/bun-adapter.js.map +1 -1
- package/dist/server/request-handler.d.ts +0 -2
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +2 -24
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +24 -11
- package/dist/server/request.js.map +1 -1
- package/dist/server/uws-adapter.d.ts +80 -0
- package/dist/server/uws-adapter.d.ts.map +1 -0
- package/dist/server/uws-adapter.js +357 -0
- package/dist/server/uws-adapter.js.map +1 -0
- package/dist/server/ws-orchestrator.d.ts +1 -0
- package/dist/server/ws-orchestrator.d.ts.map +1 -1
- package/dist/server/ws-orchestrator.js +83 -0
- package/dist/server/ws-orchestrator.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/validator/tex.d.ts +21 -16
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Router & Controllers"
|
|
3
|
+
description: "API Reference for defining routes and controllers in ExisJS."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Router & Controllers
|
|
7
|
+
|
|
8
|
+
ExisJS provides two distinct ways to build your API: **Functional Routing** (using `route` and `controller`) and **Class-Based Routing** (using standard decorators). You can use either paradigm, or even mix both in the same application.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Functional Routing
|
|
13
|
+
|
|
14
|
+
The functional router provides a lightweight, type-safe API via the `route` and `controller` exports from `exisjs/router`.
|
|
15
|
+
|
|
16
|
+
These utilities allow you to define HTTP endpoints with built-in validation schemas, automatically typed execution contexts, and file-level configuration.
|
|
17
|
+
|
|
18
|
+
### `controller()`
|
|
19
|
+
|
|
20
|
+
The `controller` function wraps a collection of routes and configuration options (like middleware or CORS) inside a single file.
|
|
21
|
+
|
|
22
|
+
### Usage
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { controller, route } from 'exisjs/router'
|
|
26
|
+
import { authMiddleware } from '../../middleware/auth'
|
|
27
|
+
|
|
28
|
+
export default controller({
|
|
29
|
+
// Global config applied to all routes in this controller
|
|
30
|
+
cors: true,
|
|
31
|
+
middleware: [authMiddleware],
|
|
32
|
+
|
|
33
|
+
// Your individual routes
|
|
34
|
+
getUser: route.get('/user', {
|
|
35
|
+
async handle({ req }) {
|
|
36
|
+
return { user: req.user }
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Options (`ControllerConfig`)
|
|
43
|
+
- `cors` (`boolean | CorsConfig`): Enables or configures CORS for all routes in this file.
|
|
44
|
+
- `middleware` (`Handler[]`): Array of middleware functions applied to all routes in this file.
|
|
45
|
+
- `filters` (`any | any[]`): Route filters applied to all routes.
|
|
46
|
+
- `onError` (`HookError`): A file-level error handler.
|
|
47
|
+
- `onResponse` (`HookResponse`): A file-level response hook.
|
|
48
|
+
- `[key: string]` (`RouteDefinition`): **Any other key** is treated as a route definition (e.g. `getUser: route.get(...)`). This is how you bind routes to the controller!
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## `route`
|
|
53
|
+
|
|
54
|
+
The `route` object provides factory functions for every HTTP method (e.g., `route.get`, `route.post`, `route.patch`, `route.delete`, `route.ws`, `route.sse`).
|
|
55
|
+
|
|
56
|
+
It takes a path and a `RouteConfig` object.
|
|
57
|
+
|
|
58
|
+
### Usage
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { controller, route } from 'exisjs/router'
|
|
62
|
+
import { tex } from 'exisjs/validator'
|
|
63
|
+
|
|
64
|
+
export default controller({
|
|
65
|
+
createUser: route.post('/users', {
|
|
66
|
+
body: tex.object({
|
|
67
|
+
email: tex.email(),
|
|
68
|
+
password: tex.string({ min: 8 })
|
|
69
|
+
}),
|
|
70
|
+
async handle(ctx) {
|
|
71
|
+
// ctx.body is strictly typed!
|
|
72
|
+
const { email, password } = ctx.body
|
|
73
|
+
return { success: true, email }
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Options (`RouteConfig`)
|
|
80
|
+
|
|
81
|
+
- `body`: A validation schema for the request body (e.g., using `v` or Zod).
|
|
82
|
+
- `query`: A validation schema for the query string.
|
|
83
|
+
- `params`: A validation schema for the URL path parameters.
|
|
84
|
+
- `response`: A validation schema to serialize/validate the outgoing response.
|
|
85
|
+
- `middleware` (`Handler[]`): An array of middleware specific to this route.
|
|
86
|
+
- `cors` (`boolean | CorsConfig`): Route-specific CORS overrides.
|
|
87
|
+
- `filters` (`any | any[]`): Route-specific filters (like rate limiters or authorization checks).
|
|
88
|
+
- `permissions` (`string[]`): An array of required permission flags to access this route.
|
|
89
|
+
- `metadata` (`Record<string, any>`): Arbitrary metadata attached to the route for custom plugins/middleware.
|
|
90
|
+
- `host` (`string | string[]`): Restrict this route to a specific subdomain/host (e.g. `api.example.com`).
|
|
91
|
+
- `handle`: The async function that executes your business logic. It receives a `SuperContext`.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## `SuperContext`
|
|
96
|
+
|
|
97
|
+
Every `handle` function receives a `SuperContext` object that provides fully-typed access to the request and application environment.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { controller, route } from '../router' // Import your custom router (see below)
|
|
101
|
+
import { tex } from 'exisjs/validator'
|
|
102
|
+
|
|
103
|
+
export default controller({
|
|
104
|
+
getUserInfo: route.get('/info/:id', {
|
|
105
|
+
params: tex.object({ id: tex.string() }),
|
|
106
|
+
query: tex.object({ debug: tex.boolean({ optional: true }) }),
|
|
107
|
+
async handle({ body, query, params, headers, req, res, app, state, user, workspace }) {
|
|
108
|
+
// 1. `body`, `query`, and `params` are pre-validated and fully typed!
|
|
109
|
+
const id = params.id
|
|
110
|
+
|
|
111
|
+
// 2. `req` and `res` are the raw ExisJS request/response objects
|
|
112
|
+
const ip = req.ip
|
|
113
|
+
|
|
114
|
+
// 3. `app` is the global ExisJS application instance
|
|
115
|
+
const db = app.resolve('Database')
|
|
116
|
+
|
|
117
|
+
// 4. `resolve` is the request-scoped dependency injection resolver!
|
|
118
|
+
const usersService = resolve(UsersService)
|
|
119
|
+
|
|
120
|
+
// 5. `state` is a mutable object shared across middlewares
|
|
121
|
+
const cacheHit = state.cacheHit
|
|
122
|
+
|
|
123
|
+
// 6. `user` and `workspace` are strictly typed from your custom context!
|
|
124
|
+
return { id, email: user.email, workspace }
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## `createRouter<T>()`
|
|
133
|
+
|
|
134
|
+
If your application relies heavily on custom context (e.g., passing a `user` or `workspace` object injected via middleware to every route), you can create a strongly-typed custom router using `createRouter<T>()`.
|
|
135
|
+
|
|
136
|
+
This removes the need to constantly cast or redefine types.
|
|
137
|
+
|
|
138
|
+
### Usage
|
|
139
|
+
|
|
140
|
+
```typescript filename="src/router.ts"
|
|
141
|
+
import { createRouter } from "exisjs/router";
|
|
142
|
+
|
|
143
|
+
interface User {
|
|
144
|
+
userId: string;
|
|
145
|
+
role: string;
|
|
146
|
+
email: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface AppContext {
|
|
150
|
+
user: User;
|
|
151
|
+
workspace: string; // Just an example to prove we can pass any property
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Create a completely customized router scoped to this application
|
|
155
|
+
export const { route, controller } = createRouter<AppContext>();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Now, when you use this custom `route` object, the `SuperContext` will automatically infer your custom context fields!
|
|
159
|
+
|
|
160
|
+
```typescript filename="src/http/users/route.ts"
|
|
161
|
+
import { route, controller } from '../../router'
|
|
162
|
+
|
|
163
|
+
export default controller({
|
|
164
|
+
me: route.get('/me', {
|
|
165
|
+
async handle({ user, workspace }) {
|
|
166
|
+
// `user` and `workspace` are strictly typed!
|
|
167
|
+
return { id: user.userId, workspace }
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 2. Class-Based Routing (OOP)
|
|
176
|
+
|
|
177
|
+
If you prefer an Object-Oriented paradigm, ExisJS provides a robust set of decorators via `exisjs/decorators`. These decorators are built on standard ECMAScript decorators and do not require `reflect-metadata`, making them incredibly fast and lightweight.
|
|
178
|
+
|
|
179
|
+
### Usage
|
|
180
|
+
|
|
181
|
+
```typescript filename="src/http/users/route.ts"
|
|
182
|
+
import { Controller, Get, Post, Body, Param } from 'exisjs/decorators'
|
|
183
|
+
import { tex } from 'exisjs/validator'
|
|
184
|
+
|
|
185
|
+
@Controller('/users')
|
|
186
|
+
export default class UserController {
|
|
187
|
+
|
|
188
|
+
@Get('/:id')
|
|
189
|
+
async getUser(@Param('id') id: string) {
|
|
190
|
+
return { id, name: 'Alice' }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Post('/', {
|
|
194
|
+
body: tex.object({ email: tex.email(), password: tex.string() })
|
|
195
|
+
})
|
|
196
|
+
async createUser(@Body() body: any) {
|
|
197
|
+
// body is automatically validated!
|
|
198
|
+
return { success: true, email: body.email }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Route Decorators
|
|
204
|
+
|
|
205
|
+
- `@Get(path, schema?)`
|
|
206
|
+
- `@Post(path, schema?)`
|
|
207
|
+
- `@Put(path, schema?)`
|
|
208
|
+
- `@Patch(path, schema?)`
|
|
209
|
+
- `@Delete(path, schema?)`
|
|
210
|
+
- `@Ws(path, schema?)`: Defines a WebSocket route.
|
|
211
|
+
- `@Sse(path, schema?)`: Defines a Server-Sent Events stream route.
|
|
212
|
+
|
|
213
|
+
*Note: Just like functional routes, you can pass an optional `RouteSchema` to any of these decorators to automatically validate requests!*
|
|
214
|
+
|
|
215
|
+
### Parameter Injectors
|
|
216
|
+
|
|
217
|
+
Use parameter decorators to automatically extract data from the incoming request and inject them into your method arguments:
|
|
218
|
+
|
|
219
|
+
- `@Body()`: Injects the parsed JSON request body.
|
|
220
|
+
- `@Param('id')`: Injects a specific URL path parameter.
|
|
221
|
+
- `@Query('search')`: Injects a specific query string parameter.
|
|
222
|
+
- `@Headers('authorization')`: Injects a specific HTTP header.
|
|
223
|
+
- `@Req()`: Injects the raw ExisJS Request object.
|
|
224
|
+
- `@Res()`: Injects the raw ExisJS Response object.
|
|
225
|
+
- `@Next()`: Injects the next function.
|
|
226
|
+
- `@Socket()`: Injects the active WebSocket instance (for `@Ws` routes).
|
|
227
|
+
- `@Stream()`: Injects the active SSE instance (for `@Sse` routes).
|
|
228
|
+
|
|
229
|
+
### Advanced Decorators
|
|
230
|
+
|
|
231
|
+
ExisJS provides a rich ecosystem of decorators to apply middleware, configure responses, and enforce security on both individual routes and entire controllers:
|
|
232
|
+
|
|
233
|
+
#### Configuration & Middleware
|
|
234
|
+
- `@Use(...middlewares)`: Applies Express-style middleware to a route or entire controller.
|
|
235
|
+
- `@Hosts('api.example.com')`: Restricts the route or controller to specific domains.
|
|
236
|
+
|
|
237
|
+
#### Security & Lifecycle
|
|
238
|
+
- `@Permissions('admin')`: Requires specific permission flags.
|
|
239
|
+
- `@UseGuards(...guards)`: Attaches Guards (like auth checks) to a route/controller.
|
|
240
|
+
- `@UseInterceptors(...interceptors)`: Attaches Interceptors to mutate requests/responses.
|
|
241
|
+
- `@UseFilters(...filters)`: Attaches Exception Filters to handle specific errors.
|
|
242
|
+
|
|
243
|
+
#### Responses & Caching
|
|
244
|
+
- `@HttpCode(201)`: Forces a specific HTTP status code for successful responses.
|
|
245
|
+
- `@Header('X-Custom', 'Value')`: Automatically injects a specific response header.
|
|
246
|
+
- `@Returns(schema)`: Explicitly defines the response schema for OpenAPI generation.
|
|
247
|
+
- `@Cache({ ttlMs: 60000 })`: Automatically caches GET responses.
|
|
248
|
+
- `@Idempotent()`: Enforces idempotency keys for mutations (e.g., checkout/payments).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Sanitization"
|
|
3
|
+
description: "API Reference for ExisJS built-in Sanitization Engine."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sanitization (`sanitize`)
|
|
7
|
+
|
|
8
|
+
The `exisjs/sanitize` module provides a comprehensive suite of pure, dependency-free functions for transforming and cleaning data **before** it hits your business logic or database.
|
|
9
|
+
|
|
10
|
+
Sanitizers are designed to be perfectly composable with the [ExisJS Validator](/docs/reference/validator) via the `.sanitize()` modifier, meaning you can strip HTML, trim whitespace, and enforce limits *while* validating request payloads.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Core Sanitizers
|
|
15
|
+
|
|
16
|
+
The `sanitize` object exposes pure functions grouped by their target data type.
|
|
17
|
+
|
|
18
|
+
### String Sanitizers
|
|
19
|
+
|
|
20
|
+
- `sanitize.trim`: Trims leading and trailing whitespace.
|
|
21
|
+
- `sanitize.toLowerCase`: Converts the string to lower case.
|
|
22
|
+
- `sanitize.toUpperCase`: Converts the string to upper case.
|
|
23
|
+
- `sanitize.collapseWhitespace`: Replaces multiple consecutive spaces, tabs, or newlines with a single space.
|
|
24
|
+
- `sanitize.stripHtml`: Removes all HTML tags from the string.
|
|
25
|
+
- `sanitize.escapeHtml`: Escapes dangerous characters (`&`, `<`, `>`, `"`, `'`) into HTML entities.
|
|
26
|
+
- `sanitize.normalizeUnicode`: Normalizes unicode characters to their canonical form (NFC).
|
|
27
|
+
- `sanitize.slugify`: Converts a string into a URL-friendly slug (e.g., `Hello World!` -> `hello-world`).
|
|
28
|
+
- `sanitize.truncate(length)`: Hard-truncates a string to a specific maximum length.
|
|
29
|
+
- `sanitize.removeNonAlphanumeric`: Removes all characters except letters and numbers.
|
|
30
|
+
- `sanitize.normalizeLineEndings`: Converts Windows line endings (`\r\n`) to Unix line endings (`\n`).
|
|
31
|
+
|
|
32
|
+
### Number Sanitizers
|
|
33
|
+
|
|
34
|
+
- `sanitize.round`: Rounds the number to the nearest integer.
|
|
35
|
+
- `sanitize.clamp(min, max)`: Restricts a number to fall within a specific range.
|
|
36
|
+
- `sanitize.defaultIfNaN(fallback)`: Returns the fallback value if the provided number is `NaN`.
|
|
37
|
+
|
|
38
|
+
### Date Sanitizers
|
|
39
|
+
|
|
40
|
+
- `sanitize.normalizeToIsoString`: Converts a Date object into a standard ISO 8601 string.
|
|
41
|
+
- `sanitize.stripTime`: Resets the time portion of a Date object to `00:00:00:000` (midnight local time).
|
|
42
|
+
|
|
43
|
+
### Array Sanitizers
|
|
44
|
+
|
|
45
|
+
- `sanitize.dedupe`: Removes duplicate elements from the array.
|
|
46
|
+
- `sanitize.compact`: Removes `null`, `undefined`, and empty strings (`''`) from the array.
|
|
47
|
+
- `sanitize.trimElements`: Trims whitespace from all string elements inside the array.
|
|
48
|
+
- `sanitize.limitLength(length)`: Slices the array down to a maximum length.
|
|
49
|
+
|
|
50
|
+
### Object Sanitizers
|
|
51
|
+
|
|
52
|
+
- `sanitize.stripUnknownKeys(['allowedA', 'allowedB'])`: Strips out any keys from the object that are not explicitly allowed.
|
|
53
|
+
- `sanitize.deepTrimStringValues`: Recursively walks through an object (and nested arrays/objects) and trims all string values found.
|
|
54
|
+
- `sanitize.omit(['secret'])`: Removes specific keys from the object.
|
|
55
|
+
- `sanitize.pick(['publicField'])`: Returns a new object containing only the specified keys.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Using with Validation
|
|
60
|
+
|
|
61
|
+
The most powerful way to use ExisJS sanitizers is to chain them directly into your schemas using the `.sanitize()` modifier.
|
|
62
|
+
|
|
63
|
+
**Note that sanitization runs *before* final validation checks (like `.email()` or `.min()`).**
|
|
64
|
+
|
|
65
|
+
```typescript filename="src/http/posts/route.ts"
|
|
66
|
+
import { Controller, Post, Body } from 'exisjs/decorators'
|
|
67
|
+
import { tex } from 'exisjs/validator'
|
|
68
|
+
import { sanitize } from 'exisjs/sanitize'
|
|
69
|
+
|
|
70
|
+
const CreatePostSchema = tex.object({
|
|
71
|
+
// Trim the title, then ensure it's still at least 5 characters
|
|
72
|
+
title: tex.string({ min: 5 })
|
|
73
|
+
.sanitize(sanitize.trim, sanitize.collapseWhitespace),
|
|
74
|
+
|
|
75
|
+
// Automatically generate a URL slug from the input string
|
|
76
|
+
slug: tex.string()
|
|
77
|
+
.sanitize(sanitize.slugify),
|
|
78
|
+
|
|
79
|
+
// Strip all HTML before saving to the database
|
|
80
|
+
content: tex.string()
|
|
81
|
+
.sanitize(sanitize.stripHtml),
|
|
82
|
+
|
|
83
|
+
// Deduplicate tags and remove empty strings
|
|
84
|
+
tags: tex.array(tex.string())
|
|
85
|
+
.sanitize(sanitize.dedupe, sanitize.compact)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// === Class-Based (OOP) Example ===
|
|
89
|
+
@Controller('/api/posts')
|
|
90
|
+
export default class PostController {
|
|
91
|
+
@Post('/', { body: CreatePostSchema })
|
|
92
|
+
createPost(@Body() data: any) {
|
|
93
|
+
// data.title will be trimmed
|
|
94
|
+
// data.content will have zero HTML tags
|
|
95
|
+
// data.tags will contain no duplicates
|
|
96
|
+
return data
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// === Functional Example ===
|
|
101
|
+
import { controller, route } from 'exisjs/router'
|
|
102
|
+
|
|
103
|
+
export default controller({
|
|
104
|
+
createPostFn: route.post('/api/posts/functional', {
|
|
105
|
+
body: CreatePostSchema,
|
|
106
|
+
async handle(ctx) {
|
|
107
|
+
// ctx.body is fully sanitized and strongly typed!
|
|
108
|
+
return ctx.body
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Standalone Usage
|
|
117
|
+
|
|
118
|
+
Because the `sanitize` methods are pure functions, you can easily use them independently anywhere in your application.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { sanitize } from 'exisjs/sanitize'
|
|
122
|
+
|
|
123
|
+
const rawInput = " <script>alert('hack')</script> Hello World! "
|
|
124
|
+
|
|
125
|
+
let safeInput = sanitize.stripHtml(rawInput)
|
|
126
|
+
safeInput = sanitize.collapseWhitespace(safeInput)
|
|
127
|
+
safeInput = sanitize.trim(safeInput)
|
|
128
|
+
|
|
129
|
+
console.log(safeInput) // "Hello World!"
|
|
130
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Storage & Uploads"
|
|
3
|
+
description: "API Reference for ExisJS Storage and File Uploads (`exisjs/storage`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Storage & Uploads (`exisjs/storage`)
|
|
7
|
+
|
|
8
|
+
ExisJS provides native, extremely memory-efficient support for handling `multipart/form-data` file uploads.
|
|
9
|
+
|
|
10
|
+
Instead of buffering large files into RAM (which can easily crash Node.js servers under heavy load), ExisJS streams file chunks directly from the network socket into the disk using a highly optimized Busboy integration.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## How it Works
|
|
15
|
+
|
|
16
|
+
When you invoke `streamUpload`, ExisJS takes over the raw socket stream. As bytes come in from the network, they are instantly piped to the disk.
|
|
17
|
+
|
|
18
|
+
Because ExisJS parses the multipart stream on the fly:
|
|
19
|
+
1. Your Node.js RAM footprint stays incredibly flat, even when users upload massive 10GB video files.
|
|
20
|
+
2. Form text fields are extracted in real-time. Once the upload finishes, ExisJS automatically merges all text fields directly into `req.body` so your subsequent logic (or validators) can treat them like a normal JSON payload!
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Functional Example (`streamUpload`)
|
|
25
|
+
|
|
26
|
+
If you are using the Functional architecture, import the `streamUpload` helper from `exisjs/storage` and pass it your request context.
|
|
27
|
+
|
|
28
|
+
### Example Usage
|
|
29
|
+
|
|
30
|
+
```typescript filename="src/http/upload/route.ts"
|
|
31
|
+
import { controller, route } from 'exisjs/router'
|
|
32
|
+
import { streamUpload } from 'exisjs/storage'
|
|
33
|
+
import { HttpError } from 'exisjs/error'
|
|
34
|
+
|
|
35
|
+
export default controller({
|
|
36
|
+
uploadAvatar: route.post('/avatar', {
|
|
37
|
+
async handle(ctx) {
|
|
38
|
+
try {
|
|
39
|
+
const { files, fields } = await streamUpload(ctx.req, {
|
|
40
|
+
dest: './storage/avatars'
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (files.length === 0) {
|
|
44
|
+
throw HttpError.badRequest('No file provided')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const uploadedFile = files[0]
|
|
48
|
+
|
|
49
|
+
// Log the fields submitted with the form
|
|
50
|
+
console.log('User ID associated with upload:', fields.userId)
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
filename: uploadedFile.filename,
|
|
55
|
+
savedAt: uploadedFile.destPath,
|
|
56
|
+
size: uploadedFile.size,
|
|
57
|
+
mimetype: uploadedFile.mimetype
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// streamUpload throws HttpErrors if the request isn't valid multipart/form-data
|
|
61
|
+
throw err
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Return Value
|
|
69
|
+
|
|
70
|
+
`streamUpload` resolves a Promise containing an object with two properties:
|
|
71
|
+
|
|
72
|
+
#### 1. `fields` (`Record<string, string>`)
|
|
73
|
+
An object containing all the non-file text fields submitted in the `multipart/form-data` payload. Note that `streamUpload` automatically populates `ctx.req.body` with these fields as well upon completion.
|
|
74
|
+
|
|
75
|
+
#### 2. `files` (`Array<object>`)
|
|
76
|
+
An array of successfully streamed files. Each object contains:
|
|
77
|
+
- **`fieldname`**: The name of the HTML form field (e.g., `avatar`).
|
|
78
|
+
- **`filename`**: The original name of the file uploaded by the user.
|
|
79
|
+
- **`mimetype`**: The MIME type of the file (e.g., `image/png`).
|
|
80
|
+
- **`destPath`**: The absolute path on your server's disk where the file was safely saved. *Note: ExisJS appends a randomized unique suffix to the filename to prevent overwrites.*
|
|
81
|
+
- **`size`**: The total file size in bytes.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 2. Class-Based Example (`req.streamUpload`)
|
|
86
|
+
|
|
87
|
+
If you are using Object-Oriented `@Controller` classes, ExisJS natively extends the Request object. You do not even need to import `streamUpload`!
|
|
88
|
+
|
|
89
|
+
Simply inject the Request object via the `@Req()` decorator and call `req.streamUpload(dest)` directly.
|
|
90
|
+
|
|
91
|
+
```typescript filename="src/http/upload/route.ts"
|
|
92
|
+
import { Controller, Post, Req } from 'exisjs/decorators'
|
|
93
|
+
import type { Request } from 'exisjs'
|
|
94
|
+
import { HttpError } from 'exisjs/error'
|
|
95
|
+
|
|
96
|
+
@Controller('/upload')
|
|
97
|
+
export default class UploadController {
|
|
98
|
+
|
|
99
|
+
@Post('/avatar')
|
|
100
|
+
async handleUpload(@Req() req: Request) {
|
|
101
|
+
// Exactly identical to streamUpload(req, { dest: ... })
|
|
102
|
+
// We pass the destination path directly into the Request method
|
|
103
|
+
const { files, fields } = await req.streamUpload('./storage/avatars')
|
|
104
|
+
|
|
105
|
+
if (!files.length) throw HttpError.badRequest('Missing file')
|
|
106
|
+
|
|
107
|
+
// Remember: fields are also populated into req.body!
|
|
108
|
+
console.log(req.body.userId === fields.userId) // true!
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
message: 'Upload successful',
|
|
112
|
+
files
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Swagger & OpenAPI"
|
|
3
|
+
description: "API Reference for ExisJS auto-generated Swagger UI (`exisjs/swagger`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Swagger / OpenAPI (`exisjs/swagger`)
|
|
7
|
+
|
|
8
|
+
ExisJS ships with native, zero-configuration OpenAPI 3.0 generation.
|
|
9
|
+
|
|
10
|
+
Because ExisJS routes strongly integrate with `exisjs/validator`, the framework automatically introspects your validators (`tex.object`, `tex.string`, etc.) across all controllers and automatically generates a fully interactive Swagger UI!
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Global Setup (`exis.config.ts`)
|
|
15
|
+
|
|
16
|
+
Because the Swagger engine needs to read all registered routes at boot time, you mount it globally using a Plugin.
|
|
17
|
+
|
|
18
|
+
Create a new file in your `plugins` directory:
|
|
19
|
+
|
|
20
|
+
```typescript filename="src/plugins/swagger.ts"
|
|
21
|
+
import { definePlugin } from 'exisjs/plugin'
|
|
22
|
+
import { serveSwagger } from 'exisjs/swagger'
|
|
23
|
+
|
|
24
|
+
export const swaggerPlugin = definePlugin({
|
|
25
|
+
name: 'swagger-ui',
|
|
26
|
+
register(app) {
|
|
27
|
+
serveSwagger(app, {
|
|
28
|
+
path: '/docs', // The URL where the UI will be served
|
|
29
|
+
title: 'My ExisJS API',
|
|
30
|
+
version: '1.0.0',
|
|
31
|
+
|
|
32
|
+
// Optionally attach global OpenAPI components (like Bearer Auth definitions)
|
|
33
|
+
components: {
|
|
34
|
+
securitySchemes: {
|
|
35
|
+
bearerAuth: {
|
|
36
|
+
type: 'http',
|
|
37
|
+
scheme: 'bearer',
|
|
38
|
+
bearerFormat: 'JWT',
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then, natively register your plugin in `exis.config.ts`:
|
|
48
|
+
|
|
49
|
+
```typescript filename="exis.config.ts"
|
|
50
|
+
import { defineConfig } from 'exisjs/config'
|
|
51
|
+
import { swaggerPlugin } from './src/plugins/swagger'
|
|
52
|
+
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
plugins: [
|
|
55
|
+
swaggerPlugin()
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 2. Automatic Schema Inference
|
|
63
|
+
|
|
64
|
+
You do not need to write YAML files or manually define your request bodies in Swagger. ExisJS reads the schemas directly from your code!
|
|
65
|
+
|
|
66
|
+
### Functional Architecture
|
|
67
|
+
|
|
68
|
+
When using the `route.*` functions, anything you pass to `schema.body`, `schema.query`, or `schema.response` is instantly injected into the Swagger documentation.
|
|
69
|
+
|
|
70
|
+
```typescript filename="src/http/users/route.ts"
|
|
71
|
+
import { controller, route } from 'exisjs/router'
|
|
72
|
+
import { tex } from 'exisjs/validator'
|
|
73
|
+
|
|
74
|
+
export default controller({
|
|
75
|
+
createUser: route.post('/', {
|
|
76
|
+
schema: {
|
|
77
|
+
// ExisJS automatically builds an OpenAPI schema from this validator!
|
|
78
|
+
body: tex.object({
|
|
79
|
+
email: tex.email(),
|
|
80
|
+
age: tex.number({ min: 18 })
|
|
81
|
+
}),
|
|
82
|
+
response: tex.object({
|
|
83
|
+
id: tex.string(),
|
|
84
|
+
success: tex.boolean()
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
async handle(ctx) {
|
|
88
|
+
return { id: '123', success: true }
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Class-Based (OOP) Architecture
|
|
95
|
+
|
|
96
|
+
If you are using OOP Decorators, ExisJS achieves the exact same result by reading your `@Body()`, `@Query()`, and `@Returns()` decorators.
|
|
97
|
+
|
|
98
|
+
```typescript filename="src/http/users/route.ts"
|
|
99
|
+
import { Controller, Post, Body, Returns } from 'exisjs/decorators'
|
|
100
|
+
import { tex } from 'exisjs/validator'
|
|
101
|
+
|
|
102
|
+
const CreateUserSchema = tex.object({
|
|
103
|
+
email: tex.email(),
|
|
104
|
+
age: tex.number({ min: 18 })
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const ResponseSchema = tex.object({
|
|
108
|
+
id: tex.string(),
|
|
109
|
+
success: tex.boolean()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
@Controller('/users')
|
|
113
|
+
export default class UserController {
|
|
114
|
+
|
|
115
|
+
@Post('/')
|
|
116
|
+
@Returns(ResponseSchema)
|
|
117
|
+
async createUser(@Body(CreateUserSchema) body: any) {
|
|
118
|
+
return { id: '123', success: true }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 3. Accessing the UI
|
|
126
|
+
|
|
127
|
+
Once you boot your application, simply navigate to the `path` you defined in your plugin:
|
|
128
|
+
|
|
129
|
+
- **Swagger UI**: `http://localhost:4000/docs`
|
|
130
|
+
- **Raw OpenAPI JSON**: `http://localhost:4000/docs/json`
|
|
131
|
+
|
|
132
|
+
The generated Swagger UI is fully interactive, meaning you can authenticate and test your API directly from the browser!
|