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.
Files changed (157) hide show
  1. package/dist/auth/index.d.ts +3 -1
  2. package/dist/auth/index.d.ts.map +1 -1
  3. package/dist/auth/index.js +4 -1
  4. package/dist/auth/index.js.map +1 -1
  5. package/dist/auth/oauth/core.d.ts +96 -0
  6. package/dist/auth/oauth/core.d.ts.map +1 -0
  7. package/dist/auth/oauth/core.js +203 -0
  8. package/dist/auth/oauth/core.js.map +1 -0
  9. package/dist/auth/oauth/index.d.ts +45 -0
  10. package/dist/auth/oauth/index.d.ts.map +1 -0
  11. package/dist/auth/oauth/index.js +64 -0
  12. package/dist/auth/oauth/index.js.map +1 -0
  13. package/dist/auth/oauth/middleware.d.ts +63 -0
  14. package/dist/auth/oauth/middleware.d.ts.map +1 -0
  15. package/dist/auth/oauth/middleware.js +164 -0
  16. package/dist/auth/oauth/middleware.js.map +1 -0
  17. package/dist/auth/oauth/providers/custom.d.ts +41 -0
  18. package/dist/auth/oauth/providers/custom.d.ts.map +1 -0
  19. package/dist/auth/oauth/providers/custom.js +204 -0
  20. package/dist/auth/oauth/providers/custom.js.map +1 -0
  21. package/dist/auth/oauth/providers/discord.d.ts +23 -0
  22. package/dist/auth/oauth/providers/discord.d.ts.map +1 -0
  23. package/dist/auth/oauth/providers/discord.js +130 -0
  24. package/dist/auth/oauth/providers/discord.js.map +1 -0
  25. package/dist/auth/oauth/providers/facebook.d.ts +28 -0
  26. package/dist/auth/oauth/providers/facebook.d.ts.map +1 -0
  27. package/dist/auth/oauth/providers/facebook.js +124 -0
  28. package/dist/auth/oauth/providers/facebook.js.map +1 -0
  29. package/dist/auth/oauth/providers/github.d.ts +25 -0
  30. package/dist/auth/oauth/providers/github.d.ts.map +1 -0
  31. package/dist/auth/oauth/providers/github.js +132 -0
  32. package/dist/auth/oauth/providers/github.js.map +1 -0
  33. package/dist/auth/oauth/providers/google.d.ts +23 -0
  34. package/dist/auth/oauth/providers/google.d.ts.map +1 -0
  35. package/dist/auth/oauth/providers/google.js +134 -0
  36. package/dist/auth/oauth/providers/google.js.map +1 -0
  37. package/dist/auth/oauth/providers/microsoft.d.ts +28 -0
  38. package/dist/auth/oauth/providers/microsoft.d.ts.map +1 -0
  39. package/dist/auth/oauth/providers/microsoft.js +136 -0
  40. package/dist/auth/oauth/providers/microsoft.js.map +1 -0
  41. package/dist/auth/oauth/types.d.ts +158 -0
  42. package/dist/auth/oauth/types.d.ts.map +1 -0
  43. package/dist/auth/oauth/types.js +4 -0
  44. package/dist/auth/oauth/types.js.map +1 -0
  45. package/dist/cli/commands/dev.d.ts.map +1 -1
  46. package/dist/cli/commands/dev.js +0 -1
  47. package/dist/cli/commands/dev.js.map +1 -1
  48. package/dist/database/adapters/mongodb.d.ts +58 -0
  49. package/dist/database/adapters/mongodb.d.ts.map +1 -0
  50. package/dist/database/adapters/mongodb.js +264 -0
  51. package/dist/database/adapters/mongodb.js.map +1 -0
  52. package/dist/database/adapters/mysql.d.ts +27 -0
  53. package/dist/database/adapters/mysql.d.ts.map +1 -0
  54. package/dist/database/adapters/mysql.js +188 -0
  55. package/dist/database/adapters/mysql.js.map +1 -0
  56. package/dist/database/adapters/postgres.d.ts +28 -0
  57. package/dist/database/adapters/postgres.d.ts.map +1 -0
  58. package/dist/database/adapters/postgres.js +174 -0
  59. package/dist/database/adapters/postgres.js.map +1 -0
  60. package/dist/database/adapters/sqlite.d.ts +27 -0
  61. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  62. package/dist/database/adapters/sqlite.js +149 -0
  63. package/dist/database/adapters/sqlite.js.map +1 -0
  64. package/dist/database/index.d.ts +42 -0
  65. package/dist/database/index.d.ts.map +1 -0
  66. package/dist/database/index.js +58 -0
  67. package/dist/database/index.js.map +1 -0
  68. package/dist/database/manager.d.ts +114 -0
  69. package/dist/database/manager.d.ts.map +1 -0
  70. package/dist/database/manager.js +269 -0
  71. package/dist/database/manager.js.map +1 -0
  72. package/dist/database/migration.d.ts +57 -0
  73. package/dist/database/migration.d.ts.map +1 -0
  74. package/dist/database/migration.js +329 -0
  75. package/dist/database/migration.js.map +1 -0
  76. package/dist/database/query-builder.d.ts +89 -0
  77. package/dist/database/query-builder.d.ts.map +1 -0
  78. package/dist/database/query-builder.js +353 -0
  79. package/dist/database/query-builder.js.map +1 -0
  80. package/dist/database/types.d.ts +184 -0
  81. package/dist/database/types.d.ts.map +1 -0
  82. package/dist/database/types.js +4 -0
  83. package/dist/database/types.js.map +1 -0
  84. package/dist/docs/ARCHITECTURE.md +349 -0
  85. package/dist/docs/BENCHMARKS.md +36 -0
  86. package/dist/docs/README.md +34 -0
  87. package/dist/docs/content/README.md +44 -0
  88. package/dist/docs/content/adapters.mdx +199 -0
  89. package/dist/docs/content/cache.mdx +239 -0
  90. package/dist/docs/content/circuit-breaker.mdx +130 -0
  91. package/dist/docs/content/cli/commands.mdx +214 -0
  92. package/dist/docs/content/cli/overview.mdx +92 -0
  93. package/dist/docs/content/cli/usage.mdx +61 -0
  94. package/dist/docs/content/config.mdx +214 -0
  95. package/dist/docs/content/controllers.mdx +241 -0
  96. package/dist/docs/content/cron.mdx +109 -0
  97. package/dist/docs/content/database.mdx +209 -0
  98. package/dist/docs/content/dataloader.mdx +170 -0
  99. package/dist/docs/content/decorators.mdx +204 -0
  100. package/dist/docs/content/errors.mdx +344 -0
  101. package/dist/docs/content/gateways.mdx +376 -0
  102. package/dist/docs/content/installation.mdx +210 -0
  103. package/dist/docs/content/introduction.mdx +96 -0
  104. package/dist/docs/content/middleware.mdx +441 -0
  105. package/dist/docs/content/modules.mdx +144 -0
  106. package/dist/docs/content/oauth.mdx +179 -0
  107. package/dist/docs/content/providers.mdx +147 -0
  108. package/dist/docs/content/queues.mdx +177 -0
  109. package/dist/docs/content/requests.mdx +293 -0
  110. package/dist/docs/content/responses.mdx +271 -0
  111. package/dist/docs/content/routing.mdx +541 -0
  112. package/dist/docs/content/rpc.mdx +146 -0
  113. package/dist/docs/content/security.mdx +294 -0
  114. package/dist/docs/content/sse.mdx +159 -0
  115. package/dist/docs/content/structure.mdx +116 -0
  116. package/dist/docs/content/testing.mdx +139 -0
  117. package/dist/docs/content/validation.mdx +249 -0
  118. package/dist/docs/content/websockets.mdx +289 -0
  119. package/dist/docs/integrations/README.md +23 -0
  120. package/dist/docs/integrations/jwt.mdx +151 -0
  121. package/dist/docs/integrations/mongo.mdx +183 -0
  122. package/dist/docs/integrations/observability.mdx +125 -0
  123. package/dist/docs/integrations/openai.mdx +108 -0
  124. package/dist/docs/integrations/postgres.mdx +190 -0
  125. package/dist/docs/integrations/posthog.mdx +122 -0
  126. package/dist/docs/integrations/redis.mdx +141 -0
  127. package/dist/docs/integrations/resend.mdx +117 -0
  128. package/dist/docs/integrations/s3.mdx +122 -0
  129. package/dist/docs/integrations/swagger.mdx +113 -0
  130. package/dist/docs/reference/README.md +3 -0
  131. package/dist/docs/reference/adapters.mdx +130 -0
  132. package/dist/docs/reference/auth.mdx +183 -0
  133. package/dist/docs/reference/cache.mdx +132 -0
  134. package/dist/docs/reference/circuit-breaker.mdx +71 -0
  135. package/dist/docs/reference/config.mdx +118 -0
  136. package/dist/docs/reference/context.mdx +168 -0
  137. package/dist/docs/reference/dataloader.mdx +91 -0
  138. package/dist/docs/reference/decorators.mdx +293 -0
  139. package/dist/docs/reference/di.mdx +123 -0
  140. package/dist/docs/reference/error.mdx +170 -0
  141. package/dist/docs/reference/exis.mdx +124 -0
  142. package/dist/docs/reference/middleware.mdx +304 -0
  143. package/dist/docs/reference/plugin.mdx +113 -0
  144. package/dist/docs/reference/queue.mdx +155 -0
  145. package/dist/docs/reference/router.mdx +248 -0
  146. package/dist/docs/reference/sanitize.mdx +130 -0
  147. package/dist/docs/reference/storage.mdx +116 -0
  148. package/dist/docs/reference/swagger.mdx +132 -0
  149. package/dist/docs/reference/testing.mdx +145 -0
  150. package/dist/docs/reference/validator.mdx +186 -0
  151. package/dist/index.d.ts +4 -0
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +10 -1
  154. package/dist/index.js.map +1 -1
  155. package/dist/server/bootstrapper.js +1 -1
  156. package/dist/server/bootstrapper.js.map +1 -1
  157. 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.