exisjs 0.5.10 → 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/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +0 -1
- 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/server/bootstrapper.js +1 -1
- package/dist/server/bootstrapper.js.map +1 -1
- package/package.json +8 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Middleware"
|
|
3
|
+
description: "API Reference for ExisJS built-in Middleware functions."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Middleware (`exisjs/middleware`)
|
|
7
|
+
|
|
8
|
+
ExisJS ships with a massive suite of highly optimized, zero-dependency middleware. Rather than relying on outdated Express middlewares (which are often unmaintained and have heavy dependency trees), ExisJS implements security and utility middleware natively.
|
|
9
|
+
|
|
10
|
+
All middlewares can be applied globally in `server.ts`, directory-wide in `gateway.ts`, or locally on specific controllers/routes.
|
|
11
|
+
|
|
12
|
+
## How to Apply Middleware
|
|
13
|
+
|
|
14
|
+
ExisJS provides first-class support for both Object-Oriented and Functional middleware application.
|
|
15
|
+
|
|
16
|
+
**Class-Based (OOP)**
|
|
17
|
+
```typescript filename="src/http/users/route.ts"
|
|
18
|
+
import { Controller, Get, Use } from 'exisjs/decorators'
|
|
19
|
+
import { cache, ipFilter } from 'exisjs/middleware'
|
|
20
|
+
|
|
21
|
+
@Controller('/users')
|
|
22
|
+
@Use(ipFilter({ allow: ['192.168.1.0/24'] })) // Applies to entire class
|
|
23
|
+
export default class UserController {
|
|
24
|
+
|
|
25
|
+
@Get('/stats')
|
|
26
|
+
@Use(cache(60)) // Applies only to this method
|
|
27
|
+
getStats() {
|
|
28
|
+
return { users: 100 }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Functional**
|
|
34
|
+
```typescript filename="src/http/users/route.ts"
|
|
35
|
+
import { controller, route } from 'exisjs/router'
|
|
36
|
+
import { cache, ipFilter } from 'exisjs/middleware'
|
|
37
|
+
|
|
38
|
+
export default controller({
|
|
39
|
+
// Applies to entire controller block
|
|
40
|
+
middleware: [ipFilter({ allow: ['192.168.1.0/24'] })],
|
|
41
|
+
|
|
42
|
+
getStats: route.get('/stats', {
|
|
43
|
+
// Applies only to this specific route
|
|
44
|
+
middleware: [cache(60)],
|
|
45
|
+
async handle() {
|
|
46
|
+
return { users: 100 }
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Security Middleware
|
|
55
|
+
|
|
56
|
+
### `helmet(options?)`
|
|
57
|
+
A native implementation of the popular Helmet middleware. It automatically sets 14+ strict security headers (HSTS, CSP, XSS Protection, No Sniff, etc.) to protect your application from common web vulnerabilities.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
interface HelmetOptions {
|
|
61
|
+
contentSecurityPolicy?: string
|
|
62
|
+
hsts?: boolean | { maxAge: number; includeSubDomains: boolean }
|
|
63
|
+
noSniff?: boolean
|
|
64
|
+
xssFilter?: boolean
|
|
65
|
+
hidePoweredBy?: boolean
|
|
66
|
+
frameguard?: boolean | { action: 'DENY' | 'SAMEORIGIN' }
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { helmet } from 'exisjs/middleware'
|
|
72
|
+
|
|
73
|
+
@Gateway({
|
|
74
|
+
middleware: [
|
|
75
|
+
helmet({
|
|
76
|
+
hsts: { maxAge: 31536000, includeSubDomains: true },
|
|
77
|
+
xssFilter: true,
|
|
78
|
+
hidePoweredBy: true
|
|
79
|
+
})
|
|
80
|
+
]
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `csrf(options)`
|
|
85
|
+
Implements the Signed Double Submit Cookie pattern for Cross-Site Request Forgery (CSRF) protection. Generates a random token on GET requests, sets it as a signed cookie, and strictly requires the client to send the same unsigned token in a header on all state-changing requests (POST, PUT, DELETE).
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface CsrfOptions {
|
|
89
|
+
secret: string
|
|
90
|
+
cookieName?: string
|
|
91
|
+
headerName?: string
|
|
92
|
+
cookieOptions?: {
|
|
93
|
+
httpOnly?: boolean
|
|
94
|
+
secure?: boolean
|
|
95
|
+
sameSite?: 'Strict' | 'Lax' | 'None'
|
|
96
|
+
path?: string
|
|
97
|
+
maxAge?: number
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { csrf } from 'exisjs/middleware'
|
|
104
|
+
|
|
105
|
+
app.use(csrf({
|
|
106
|
+
secret: process.env.CSRF_SECRET, // Must be 32+ chars
|
|
107
|
+
cookieName: 'csrf-token',
|
|
108
|
+
headerName: 'x-csrf-token'
|
|
109
|
+
}))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `hpp()`
|
|
113
|
+
Protects against HTTP Parameter Pollution attacks. If a client maliciously sends an array of values where a string is expected (e.g., `?id=1&id=2`), `hpp()` will automatically intercept the request and only take the *last* provided value, preventing backend crashes or logic bypasses.
|
|
114
|
+
|
|
115
|
+
### `mongoSanitize()` / `dbSanitize(options)`
|
|
116
|
+
Recursively scans incoming `req.body`, `req.query`, and `req.params` and immediately strips any keys containing dangerous NoSQL characters (like `$` or `.`). This definitively prevents NoSQL Injection attacks.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface DbSanitizeOptions {
|
|
120
|
+
mongo?: boolean // Default: true
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `ipFilter(options)`
|
|
125
|
+
Restricts access based on the client's IP address. Supports exact matches, CIDR blocks, and custom validation functions.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { ipFilter } from 'exisjs/middleware'
|
|
129
|
+
|
|
130
|
+
// Only allow internal network traffic
|
|
131
|
+
app.use(ipFilter({
|
|
132
|
+
allow: ['10.0.0.0/8', '192.168.1.100'],
|
|
133
|
+
message: 'Access denied from external networks'
|
|
134
|
+
}))
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Rate Limiting & Idempotency
|
|
140
|
+
|
|
141
|
+
### `rateLimit(options)`
|
|
142
|
+
Limits the number of requests a single IP (or custom key) can make within a time window.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface RateLimitOptions {
|
|
146
|
+
windowMs?: number // Time window in ms (default: 60000)
|
|
147
|
+
max?: number // Max requests per windowMs (default: 100)
|
|
148
|
+
message?: string // Error message (default: 'Too many requests...')
|
|
149
|
+
statusCode?: number // HTTP Status Code (default: 429)
|
|
150
|
+
keyGenerator?: Function // Custom IP extraction logic
|
|
151
|
+
redis?: any // Optional ioredis client for distributed limits
|
|
152
|
+
prefix?: string // Redis key prefix
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { rateLimit } from 'exisjs/middleware'
|
|
158
|
+
|
|
159
|
+
app.use(rateLimit({
|
|
160
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
161
|
+
max: 100, // Limit each IP to 100 requests per windowMs
|
|
162
|
+
message: 'Too many requests, please try again later.'
|
|
163
|
+
}))
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `idempotent(options)`
|
|
167
|
+
Prevents duplicate identical state-changing requests (like creating a payment or order twice). Requires the client to send an `Idempotency-Key` header. If the same key is sent twice, ExisJS intercepts the second request and returns the exact same response as the first, without hitting your database again.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { idempotent } from 'exisjs/middleware'
|
|
171
|
+
|
|
172
|
+
@Post('/payments')
|
|
173
|
+
@Use(idempotent({ ttlMs: 86400000 })) // Keep keys for 24 hours
|
|
174
|
+
chargeCard() {
|
|
175
|
+
return stripe.charges.create(...)
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Performance & Reliability
|
|
182
|
+
|
|
183
|
+
### `cache(ttl)`
|
|
184
|
+
Caches identical GET request responses in memory (or Redis, depending on the active adapter) for a specified number of seconds (`ttl`). Highly effective for public APIs and computationally expensive routes.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { cache } from 'exisjs/middleware'
|
|
188
|
+
|
|
189
|
+
// Cache this endpoint for 60 seconds
|
|
190
|
+
@Get('/stats')
|
|
191
|
+
@Use(cache(60))
|
|
192
|
+
getStats() {
|
|
193
|
+
return db.computeHeavyStats()
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### `dedupe(ttl)`
|
|
198
|
+
Prevents duplicate identical requests from being processed simultaneously. If 1,000 clients request the exact same expensive resource at the exact same millisecond, `dedupe` forces 999 of them to wait for the 1st request to finish, and then serves the exact same response to all 1,000 clients simultaneously.
|
|
199
|
+
|
|
200
|
+
### `compress()`
|
|
201
|
+
A blazing-fast payload compression middleware natively supporting `gzip`, `deflate`, and `brotli`. Automatically compresses responses based on the client's `Accept-Encoding` header.
|
|
202
|
+
|
|
203
|
+
### `timeout(ms)`
|
|
204
|
+
Automatically times out any request that hangs longer than the specified milliseconds, returning a clean 503 response. Crucial for preventing stalled requests from exhausting the Node.js event loop or database connection pools.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
interface TimeoutOptions {
|
|
208
|
+
ms: number
|
|
209
|
+
message?: string
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { timeout } from 'exisjs/middleware'
|
|
215
|
+
|
|
216
|
+
@Gateway({
|
|
217
|
+
middleware: [timeout(10000)] // 10 second global timeout
|
|
218
|
+
})
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### `backpressure(options)`
|
|
222
|
+
Monitors the Node.js Event Loop Delay in real-time. If the event loop gets too backed up (e.g., CPU is pegged at 100%), it will start returning 503 Service Unavailable to new requests until the CPU catches its breath.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Utility Middleware
|
|
227
|
+
|
|
228
|
+
### `cors(options)`
|
|
229
|
+
Native, zero-dependency Cross-Origin Resource Sharing (CORS) handler. Features full support for preflight (`OPTIONS`), dynamic regex origins, and exposed headers.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { cors } from 'exisjs/middleware'
|
|
233
|
+
|
|
234
|
+
app.use(cors({
|
|
235
|
+
origin: ['https://myapp.com', /localhost:\d+/],
|
|
236
|
+
credentials: true,
|
|
237
|
+
methods: ['GET', 'POST']
|
|
238
|
+
}))
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### `requestId()`
|
|
242
|
+
Injects a unique `X-Request-Id` into every incoming request and attaches it to `req.requestId`. Seamlessly respects incoming tracing headers (`traceparent`, `x-b3-traceid`) for distributed microservice tracing.
|
|
243
|
+
|
|
244
|
+
### `requestLogger(config)`
|
|
245
|
+
A high-performance request logger powered by Pino. Automatically logs method, URL, status code, and response time. Integrates perfectly with `requestId()` to attach trace IDs to every single log line.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { requestLogger } from 'exisjs/middleware'
|
|
249
|
+
|
|
250
|
+
app.use(requestLogger({ level: 'info' }))
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `serveStatic(root, options?)`
|
|
254
|
+
A highly optimized static file server. Supports ETags, `HEAD` requests, conditional GETs, streaming file reads, and deep path resolution. Perfect for serving SPAs or assets.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { serveStatic } from 'exisjs/middleware'
|
|
258
|
+
|
|
259
|
+
app.use('/public', serveStatic('./public', { maxAge: 86400 }))
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Architectural Middleware
|
|
265
|
+
|
|
266
|
+
ExisJS provides utility wrappers that convert NestJS-style architectural patterns into standard Express middlewares, bridging the gap between OOP metadata and pure functional execution.
|
|
267
|
+
|
|
268
|
+
### `guard(fn)`
|
|
269
|
+
Wraps an authorization or validation function. If the guard returns `false`, it throws an automatic `403 Forbidden` response.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { guard } from 'exisjs/middleware'
|
|
273
|
+
|
|
274
|
+
const RequireAdmin = guard((req) => req.user?.role === 'admin')
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### `pipe(fn)`
|
|
278
|
+
A synchronous transformation function. Takes the incoming request data, transforms it, and overwrites the body before passing it down the chain.
|
|
279
|
+
|
|
280
|
+
### `intercept(fn)`
|
|
281
|
+
Wraps the entire request-response lifecycle. Interceptors can manipulate the response right before it gets sent to the client. Highly useful for standardizing JSON response shapes.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import { intercept } from 'exisjs/middleware'
|
|
285
|
+
|
|
286
|
+
// Standardize all responses to { data: <payload> }
|
|
287
|
+
const WrapResponse = intercept((req, res, data) => ({
|
|
288
|
+
data
|
|
289
|
+
}))
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### `catchError(handler)`
|
|
293
|
+
An exception filter wrapper. Catches any errors thrown by the route or previous middlewares and handles them gracefully.
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { catchError } from 'exisjs/middleware'
|
|
297
|
+
|
|
298
|
+
const HandlePrismaErrors = catchError((err, req, res) => {
|
|
299
|
+
if (err.code === 'P2002') {
|
|
300
|
+
return res.status(409).json({ error: 'Unique constraint failed' })
|
|
301
|
+
}
|
|
302
|
+
throw err // Pass to default error handler
|
|
303
|
+
})
|
|
304
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Plugins"
|
|
3
|
+
description: "API Reference for ExisJS Plugins (`exisjs/plugin`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plugins (`exisjs/plugin`)
|
|
7
|
+
|
|
8
|
+
ExisJS utilizes a highly optimized, encapsulated plugin architecture. Every major feature in ExisJS (Routing, WebSockets, Validation, Cron Jobs) is implemented as a Plugin under the hood.
|
|
9
|
+
|
|
10
|
+
The `exisjs/plugin` module exposes everything you need to author, type, and distribute your own ExisJS plugins.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## `definePlugin(plugin)`
|
|
15
|
+
|
|
16
|
+
The `definePlugin` helper is the primary way to construct an ExisJS plugin.
|
|
17
|
+
|
|
18
|
+
It wraps your plugin object and returns a hybrid factory. This allows your users to either register the plugin directly (e.g., `app.register(myPlugin)`) or invoke it as a function to pass options (e.g., `app.register(myPlugin({ key: 'val' }))`).
|
|
19
|
+
|
|
20
|
+
### Basic Usage
|
|
21
|
+
|
|
22
|
+
```typescript filename="src/plugins/logger.ts"
|
|
23
|
+
import { definePlugin } from 'exisjs/plugin'
|
|
24
|
+
|
|
25
|
+
export const loggerPlugin = definePlugin({
|
|
26
|
+
name: 'my-logger-plugin',
|
|
27
|
+
version: '1.0.0',
|
|
28
|
+
|
|
29
|
+
// The register function is called when app.register() is executed
|
|
30
|
+
async register(app) {
|
|
31
|
+
app.use((req, res, next) => {
|
|
32
|
+
console.log(`[${req.method}] ${req.path}`)
|
|
33
|
+
next()
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### With Options (Generics)
|
|
40
|
+
|
|
41
|
+
To make your plugin configurable, pass an interface to `definePlugin<TOptions>`. The options will automatically be injected as the second argument to your `register` function.
|
|
42
|
+
|
|
43
|
+
```typescript filename="src/plugins/database.ts"
|
|
44
|
+
import { definePlugin } from 'exisjs/plugin'
|
|
45
|
+
|
|
46
|
+
interface DatabaseOptions {
|
|
47
|
+
url: string
|
|
48
|
+
poolSize?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const databasePlugin = definePlugin<DatabaseOptions>({
|
|
52
|
+
name: 'my-database-plugin',
|
|
53
|
+
|
|
54
|
+
async register(app, options) {
|
|
55
|
+
if (!options?.url) throw new Error('Database URL is required')
|
|
56
|
+
|
|
57
|
+
const db = new DatabaseConnection(options.url, options.poolSize)
|
|
58
|
+
await db.connect()
|
|
59
|
+
|
|
60
|
+
// Expose the database globally through the DI container
|
|
61
|
+
app.provide('DB_CONNECTION', { useValue: db })
|
|
62
|
+
|
|
63
|
+
// Clean up when the app closes
|
|
64
|
+
app.onClose(async () => {
|
|
65
|
+
await db.disconnect()
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Usage in Application:**
|
|
72
|
+
```typescript filename="src/server.ts"
|
|
73
|
+
import { exis } from 'exisjs'
|
|
74
|
+
import { databasePlugin } from './plugins/database'
|
|
75
|
+
|
|
76
|
+
const app = exis()
|
|
77
|
+
|
|
78
|
+
// The hybrid factory allows passing options directly!
|
|
79
|
+
app.register(databasePlugin({
|
|
80
|
+
url: 'postgres://localhost:5432/mydb',
|
|
81
|
+
poolSize: 10
|
|
82
|
+
}))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## `ExisPlugin<TOptions>` Interface
|
|
88
|
+
|
|
89
|
+
If you prefer building plugins using classes or pure objects without the `definePlugin` helper, you can directly implement the `ExisPlugin` TypeScript interface.
|
|
90
|
+
|
|
91
|
+
### Properties
|
|
92
|
+
|
|
93
|
+
- **`name`** *(string, required)*: The unique identifier for your plugin. ExisJS uses this to prevent duplicate registrations.
|
|
94
|
+
- **`register`** *(function, required)*: The lifecycle method called during application boot. Receives the `App` instance and any options provided by the user. Can be `async`.
|
|
95
|
+
- **`version`** *(string, optional)*: The version of your plugin.
|
|
96
|
+
- **`dependencies`** *(string[], optional)*: An array of plugin names that *must* be registered before this plugin. If a dependency is missing, ExisJS will throw a fatal error during boot.
|
|
97
|
+
- **`encapsulate`** *(boolean, optional)*: Determines if this plugin should create a scoped, isolated child container. (Useful for massive plugins that shouldn't leak routes or DI providers into the global scope).
|
|
98
|
+
|
|
99
|
+
### Example
|
|
100
|
+
|
|
101
|
+
```typescript filename="src/plugins/mailer.ts"
|
|
102
|
+
import type { ExisPlugin } from 'exisjs/plugin'
|
|
103
|
+
import type { App } from 'exisjs'
|
|
104
|
+
|
|
105
|
+
export class MailerPlugin implements ExisPlugin {
|
|
106
|
+
name = 'exisjs-mailer'
|
|
107
|
+
dependencies = ['my-database-plugin'] // Requires DB to load first
|
|
108
|
+
|
|
109
|
+
async register(app: App) {
|
|
110
|
+
app.provide('Mailer', { useClass: SmtpService })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Queues"
|
|
3
|
+
description: "API Reference for ExisJS Background Queues (`exisjs/queue`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Queues (`exisjs/queue`)
|
|
7
|
+
|
|
8
|
+
ExisJS ships with a highly robust, Redis-backed background job queue mechanism. It supports strict payload validation, exponential backoff, delayed execution, and lifecycle observability hooks, seamlessly integrated right into the core framework.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Configuration (`exis.config.ts`)
|
|
13
|
+
|
|
14
|
+
Before using the queue system, you must configure your driver (either `memory` for local development or `redis` for production). This is defined globally in your `exis.config.ts` file.
|
|
15
|
+
|
|
16
|
+
```typescript filename="exis.config.ts"
|
|
17
|
+
import { defineConfig } from 'exisjs/config'
|
|
18
|
+
|
|
19
|
+
export default async (phase: string) => {
|
|
20
|
+
return defineConfig({
|
|
21
|
+
queue: {
|
|
22
|
+
driver: phase === 'development' ? 'memory' : 'redis',
|
|
23
|
+
redisUrl: process.env.REDIS_URL,
|
|
24
|
+
maxConcurrent: 100, // Maximum active workers
|
|
25
|
+
maxQueue: 1000 // Maximum waiting jobs
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Defining Jobs (File-Based Routing)
|
|
34
|
+
|
|
35
|
+
ExisJS uses highly intuitive **File-Based Job Routing**.
|
|
36
|
+
|
|
37
|
+
To create a new background job, simply create a file in the `src/jobs/` directory and `export default defineJob(...)`. The framework will automatically discover, load, and bind the job queue on boot!
|
|
38
|
+
|
|
39
|
+
```typescript filename="src/jobs/SendEmailJob.ts"
|
|
40
|
+
import { defineJob } from 'exisjs/queue'
|
|
41
|
+
import { tex } from 'exisjs/validator'
|
|
42
|
+
|
|
43
|
+
export default defineJob({
|
|
44
|
+
name: 'send-email',
|
|
45
|
+
|
|
46
|
+
// ExisJS will automatically validate incoming queue payloads against this schema!
|
|
47
|
+
schema: tex.object({
|
|
48
|
+
to: tex.email(),
|
|
49
|
+
subject: tex.string(),
|
|
50
|
+
html: tex.string()
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
// Set default retry and backoff behavior
|
|
54
|
+
defaultOptions: {
|
|
55
|
+
attempts: 3,
|
|
56
|
+
backoff: {
|
|
57
|
+
type: 'exponential',
|
|
58
|
+
delay: 5000 // 5s, 25s, 125s...
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Observability Hooks
|
|
63
|
+
onJobFailed(payload, err) {
|
|
64
|
+
console.error(`Job failed on attempt ${payload.attemptsMade}`, err)
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
onJobFailedPermanently(payload, err) {
|
|
68
|
+
// Alert Sentry or Slack!
|
|
69
|
+
console.error(`Job fatally died after all retries!`, err)
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// The actual background execution logic
|
|
73
|
+
async handler(job) {
|
|
74
|
+
// job.data is completely type-safe and strictly validated by the schema!
|
|
75
|
+
const { to, subject, html } = job.data
|
|
76
|
+
|
|
77
|
+
await mailer.send({ to, subject, html })
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Job Definition Properties
|
|
83
|
+
- **`name`** *(string)*: Unique identifier for the queue.
|
|
84
|
+
- **`schema`** *(Validator)*: An ExisJS validator object to validate the payload structure.
|
|
85
|
+
- **`handler`** *(function)*: The logic to execute (optional if using file-based job routing).
|
|
86
|
+
- **`defaultOptions`** *(JobOptions)*: Global options for retry counts and delays.
|
|
87
|
+
- **`cron`** *(string)*: A cron expression (e.g. `'0 * * * *'`) for scheduling the job recursively.
|
|
88
|
+
- **Hooks**: `onJobStart`, `onJobSuccess`, `onJobFailed`, `onJobFailedPermanently`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Enqueuing Jobs
|
|
93
|
+
|
|
94
|
+
You can dispatch a job from anywhere in your application (Controllers, Gateways, Services) using the `enqueue` function.
|
|
95
|
+
|
|
96
|
+
```typescript filename="src/http/users/route.ts"
|
|
97
|
+
import { controller, route } from 'exisjs/router'
|
|
98
|
+
import { enqueue } from 'exisjs/queue'
|
|
99
|
+
import { SendEmailJob } from '../../jobs/SendEmailJob'
|
|
100
|
+
|
|
101
|
+
export default controller({
|
|
102
|
+
register: route.post('/register', {
|
|
103
|
+
async handle(ctx) {
|
|
104
|
+
const user = await db.users.create(ctx.body)
|
|
105
|
+
|
|
106
|
+
// Dispatch the job to the background queue!
|
|
107
|
+
await enqueue('send-email', {
|
|
108
|
+
to: user.email,
|
|
109
|
+
subject: 'Welcome to ExisJS!',
|
|
110
|
+
html: '<h1>Welcome aboard!</h1>'
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return { success: true }
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Delaying Execution
|
|
120
|
+
You can override the `defaultOptions` on a per-dispatch basis. For example, to delay a job by 15 minutes:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
await enqueue('send-email', payload, {
|
|
124
|
+
delay: 15 * 60 * 1000, // 15 minutes in milliseconds
|
|
125
|
+
attempts: 5
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### Cron / Scheduled Jobs
|
|
132
|
+
|
|
133
|
+
Because ExisJS uses file-based routing for jobs, implementing recurring Cron jobs is completely trivial. Just add a `cron` property to your `defineJob` config!
|
|
134
|
+
|
|
135
|
+
```typescript filename="src/jobs/CleanupJob.ts"
|
|
136
|
+
import { defineJob } from 'exisjs/queue'
|
|
137
|
+
|
|
138
|
+
export default defineJob({
|
|
139
|
+
name: 'cleanup-stale-sessions',
|
|
140
|
+
cron: '0 0 * * *', // Run every day at midnight
|
|
141
|
+
|
|
142
|
+
async handler() {
|
|
143
|
+
await db.query('DELETE FROM sessions WHERE expiresAt < NOW()')
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### The `JobPayload` Object
|
|
149
|
+
|
|
150
|
+
When your handler executes, it receives a `JobPayload` containing the following properties:
|
|
151
|
+
- **`id`**: The unique generated UUID for this specific job execution.
|
|
152
|
+
- **`name`**: The name of the queue.
|
|
153
|
+
- **`data`**: The raw, strongly-typed, validated payload.
|
|
154
|
+
- **`attemptsMade`**: How many times this specific job has failed and retried.
|
|
155
|
+
- **`opts`**: The options (delays, backoff strategy) this job is running under.
|