exisjs 0.5.10 → 0.5.12

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 (191) 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/exisjs-mcp.mdx +97 -0
  102. package/dist/docs/content/gateways.mdx +376 -0
  103. package/dist/docs/content/installation.mdx +210 -0
  104. package/dist/docs/content/introduction.mdx +96 -0
  105. package/dist/docs/content/middleware.mdx +441 -0
  106. package/dist/docs/content/modules.mdx +144 -0
  107. package/dist/docs/content/oauth.mdx +179 -0
  108. package/dist/docs/content/providers.mdx +147 -0
  109. package/dist/docs/content/queues.mdx +177 -0
  110. package/dist/docs/content/requests.mdx +293 -0
  111. package/dist/docs/content/responses.mdx +271 -0
  112. package/dist/docs/content/routing.mdx +541 -0
  113. package/dist/docs/content/rpc.mdx +146 -0
  114. package/dist/docs/content/security.mdx +294 -0
  115. package/dist/docs/content/sse.mdx +159 -0
  116. package/dist/docs/content/structure.mdx +116 -0
  117. package/dist/docs/content/testing.mdx +139 -0
  118. package/dist/docs/content/validation.mdx +249 -0
  119. package/dist/docs/content/websockets.mdx +289 -0
  120. package/dist/docs/integrations/README.md +23 -0
  121. package/dist/docs/integrations/jwt.mdx +151 -0
  122. package/dist/docs/integrations/mongo.mdx +183 -0
  123. package/dist/docs/integrations/observability.mdx +125 -0
  124. package/dist/docs/integrations/openai.mdx +108 -0
  125. package/dist/docs/integrations/postgres.mdx +190 -0
  126. package/dist/docs/integrations/posthog.mdx +122 -0
  127. package/dist/docs/integrations/redis.mdx +141 -0
  128. package/dist/docs/integrations/resend.mdx +117 -0
  129. package/dist/docs/integrations/s3.mdx +122 -0
  130. package/dist/docs/integrations/swagger.mdx +113 -0
  131. package/dist/docs/reference/README.md +3 -0
  132. package/dist/docs/reference/adapters.mdx +130 -0
  133. package/dist/docs/reference/auth.mdx +183 -0
  134. package/dist/docs/reference/cache.mdx +132 -0
  135. package/dist/docs/reference/circuit-breaker.mdx +71 -0
  136. package/dist/docs/reference/config.mdx +118 -0
  137. package/dist/docs/reference/context.mdx +168 -0
  138. package/dist/docs/reference/dataloader.mdx +91 -0
  139. package/dist/docs/reference/decorators.mdx +293 -0
  140. package/dist/docs/reference/di.mdx +123 -0
  141. package/dist/docs/reference/error.mdx +170 -0
  142. package/dist/docs/reference/exis.mdx +124 -0
  143. package/dist/docs/reference/middleware.mdx +304 -0
  144. package/dist/docs/reference/plugin.mdx +113 -0
  145. package/dist/docs/reference/queue.mdx +155 -0
  146. package/dist/docs/reference/router.mdx +248 -0
  147. package/dist/docs/reference/sanitize.mdx +130 -0
  148. package/dist/docs/reference/storage.mdx +116 -0
  149. package/dist/docs/reference/swagger.mdx +132 -0
  150. package/dist/docs/reference/testing.mdx +145 -0
  151. package/dist/docs/reference/validator.mdx +186 -0
  152. package/dist/index.d.ts +4 -0
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +10 -1
  155. package/dist/index.js.map +1 -1
  156. package/dist/middleware/compression.d.ts +12 -0
  157. package/dist/middleware/compression.d.ts.map +1 -1
  158. package/dist/middleware/compression.js +61 -99
  159. package/dist/middleware/compression.js.map +1 -1
  160. package/dist/middleware/middleware.d.ts.map +1 -1
  161. package/dist/middleware/middleware.js +25 -8
  162. package/dist/middleware/middleware.js.map +1 -1
  163. package/dist/middleware/security.d.ts.map +1 -1
  164. package/dist/middleware/security.js +6 -3
  165. package/dist/middleware/security.js.map +1 -1
  166. package/dist/router/router.d.ts.map +1 -1
  167. package/dist/router/router.js +11 -7
  168. package/dist/router/router.js.map +1 -1
  169. package/dist/server/bootstrapper.js +1 -1
  170. package/dist/server/bootstrapper.js.map +1 -1
  171. package/dist/server/bun-adapter.d.ts.map +1 -1
  172. package/dist/server/bun-adapter.js +2 -2
  173. package/dist/server/bun-adapter.js.map +1 -1
  174. package/dist/server/request-handler.d.ts +7 -0
  175. package/dist/server/request-handler.d.ts.map +1 -1
  176. package/dist/server/request-handler.js +37 -2
  177. package/dist/server/request-handler.js.map +1 -1
  178. package/dist/server/request.d.ts.map +1 -1
  179. package/dist/server/request.js +4 -6
  180. package/dist/server/request.js.map +1 -1
  181. package/dist/server/route-scanner.d.ts.map +1 -1
  182. package/dist/server/route-scanner.js +4 -1
  183. package/dist/server/route-scanner.js.map +1 -1
  184. package/dist/server/uws-adapter.d.ts +5 -3
  185. package/dist/server/uws-adapter.d.ts.map +1 -1
  186. package/dist/server/uws-adapter.js +39 -4
  187. package/dist/server/uws-adapter.js.map +1 -1
  188. package/dist/server/ws-orchestrator.d.ts.map +1 -1
  189. package/dist/server/ws-orchestrator.js +4 -13
  190. package/dist/server/ws-orchestrator.js.map +1 -1
  191. package/package.json +17 -10
@@ -0,0 +1,441 @@
1
+ ---
2
+ nextUrl: "/docs/modules"
3
+ nextTitle: "Modules & Plugins"
4
+ ---
5
+
6
+ # Middleware
7
+
8
+ ExisJS comes with a collection of built-in middleware out of the box.
9
+
10
+ ExisJS provides first-party implementations for features like CORS, Request Logging, Backpressure, and Deduplication.
11
+
12
+ All middleware can be imported directly from the `exisjs/middleware` package.
13
+
14
+ ---
15
+
16
+ ## Where to Apply Middleware
17
+
18
+ ExisJS gives you granular control over where your middleware executes:
19
+
20
+ 1. **Globally (`server.ts`)**: Applies to every request hitting your server.
21
+ 2. **Folder-Scoped (`gateway.ts`)**: Applies only to routes inside that specific folder.
22
+ 3. **Route-Scoped (`route.ts`)**: Applies only to a specific endpoint.
23
+
24
+ ```typescript filename="src/http/admin/gateway.ts"
25
+ import { defineGateway } from 'exisjs/router'
26
+ import { requestLogger } from 'exisjs/middleware'
27
+
28
+ // This logger will ONLY run for requests to /admin/*
29
+ export default defineGateway({
30
+ middlewares: [
31
+ requestLogger({ level: 'debug' })
32
+ ]
33
+ })
34
+ ```
35
+
36
+ ### Middleware vs Inline Execution
37
+
38
+ It is critical to understand that middleware executes **before** your route handler. If you are using [Custom Typed Contexts](/docs/requests#strongly-typed-context--native-destructuring) to destructure variables (e.g., `async handle({ user })`), you **must** use middleware to populate `req.user`.
39
+
40
+ If you try to call an authentication function *inline* inside the handler, the `user` variable will be `undefined` because destructuring occurs at the moment the handler is invoked—before the inline function has a chance to execute!
41
+
42
+ ```typescript
43
+ // ❌ BAD: Destructuring happens before requireAuth mutates the request
44
+ create: route.post('/', {
45
+ async handle({ user, req }) {
46
+ requireAuth(req);
47
+ console.log(user); // undefined
48
+ }
49
+ })
50
+
51
+ // ✅ GOOD: Middleware runs first, so `user` is fully populated and strongly typed
52
+ create: route.post('/', {
53
+ middleware: [requireAuth],
54
+ async handle({ user }) {
55
+ console.log(user); // Populated and typed
56
+ }
57
+ })
58
+ ```
59
+
60
+ #### Functional Paradigm
61
+
62
+ ```typescript filename="src/http/route.ts"
63
+ import { controller, route } from 'exisjs/router'
64
+ import { validate } from 'exisjs/middleware'
65
+
66
+ export default controller({
67
+ // This middleware ONLY runs for this specific endpoint
68
+ create: route.post('/', {
69
+ middlewares: [validate(mySchema)],
70
+ handle(req, res) {
71
+ return { ok: true }
72
+ }
73
+ })
74
+ })
75
+ ```
76
+
77
+ #### Class-Based (OOP) Paradigm
78
+
79
+ ```typescript filename="src/http/route.ts"
80
+ import { Controller, Post, Use } from 'exisjs/decorators'
81
+ import { validate } from 'exisjs/middleware'
82
+
83
+ @Controller()
84
+ export default class MyController {
85
+ // This middleware ONLY runs for this specific endpoint
86
+ @Use(validate(mySchema))
87
+ @Post('/')
88
+ async create() {
89
+ return { ok: true }
90
+ }
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 1. CORS
97
+
98
+ The `cors` middleware handles Cross-Origin Resource Sharing. It is configurable and optimized for low overhead on preflight requests.
99
+
100
+ The recommended practice in ExisJS is to configure CORS globally in your **`exis.config.ts`** file (the Single Source of Truth). The framework will automatically merge and apply it globally for you.
101
+
102
+ ```typescript filename="exis.config.ts"
103
+ import { defineConfig } from 'exisjs/config'
104
+
105
+ export default async () => defineConfig({
106
+ cors: {
107
+ origin: ['https://myapp.com', /localhost:\d+/],
108
+ methods: ['GET', 'POST', 'OPTIONS'],
109
+ credentials: true,
110
+ maxAge: 86400,
111
+ preflightContinue: true // Set this to true if you want OPTIONS requests to hit your custom route.options() handlers!
112
+ }
113
+ })
114
+ ```
115
+
116
+ By default, ExisJS intercepts `OPTIONS` requests at the middleware level and responds with a `204 No Content` (and the appropriate CORS headers) to save server resources. However, if you need to run custom logic on preflight requests—such as hitting a specific `route.options('/', ...)` handler or a `@Options()` decorated controller method—you can set `preflightContinue: true`.
117
+
118
+ You can also configure CORS locally per-route by passing it to your controller config or gateway.
119
+
120
+ ---
121
+
122
+ ## 2. Request Logger
123
+
124
+ ExisJS uses **Pino** under the hood. The `requestLogger` injects a request-scoped logger instance (`req.log`) and times response cycles automatically.
125
+
126
+ ```typescript filename="src/http/server.ts"
127
+ import { requestLogger } from 'exisjs/middleware'
128
+
129
+ app.use(requestLogger({
130
+ level: 'debug',
131
+ pretty: true // Enable beautiful CLI formatting
132
+ }))
133
+ ```
134
+
135
+ #### Functional Paradigm
136
+
137
+ ```typescript filename="src/http/route.ts"
138
+ import { controller, route } from 'exisjs/router'
139
+
140
+ export default controller({
141
+ index: route.get('/', {
142
+ handle(req, res) {
143
+ req.log.info({ userId: 123 }, "User hit the index route")
144
+ return { ok: true }
145
+ }
146
+ })
147
+ })
148
+ ```
149
+
150
+ #### Class-Based (OOP) Paradigm
151
+
152
+ ```typescript filename="src/http/route.ts"
153
+ import { Controller, Get, Req } from 'exisjs/decorators'
154
+
155
+ @Controller()
156
+ export default class MyController {
157
+ @Get('/')
158
+ async index(@Req() req: any) {
159
+ req.log.info({ userId: 123 }, "User hit the index route")
160
+ return { ok: true }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ---
166
+
167
+ ## 3. Request ID
168
+
169
+ Injects a unique `X-Request-Id` into every incoming request and outgoing response header. It natively supports distributed tracing headers (`traceparent`, `x-b3-traceid`).
170
+
171
+ ```typescript filename="src/http/server.ts"
172
+ import { requestId } from 'exisjs/middleware'
173
+
174
+ app.use(requestId())
175
+ ```
176
+
177
+ #### Functional Paradigm
178
+
179
+ ```typescript filename="src/http/route.ts"
180
+ import { controller, route } from 'exisjs/router'
181
+
182
+ export default controller({
183
+ index: route.get('/', {
184
+ handle(req, res) {
185
+ console.log("Trace ID:", req.requestId)
186
+ return { ok: true }
187
+ }
188
+ })
189
+ })
190
+ ```
191
+
192
+ #### Class-Based (OOP) Paradigm
193
+
194
+ ```typescript filename="src/http/route.ts"
195
+ import { Controller, Get, Req } from 'exisjs/decorators'
196
+
197
+ @Controller()
198
+ export default class MyController {
199
+ @Get('/')
200
+ async index(@Req() req: any) {
201
+ console.log("Trace ID:", req.requestId)
202
+ return { ok: true }
203
+ }
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ## 4. Static File Serving
210
+
211
+ A static file server that resolves MIME types, streams files, and provides Cache-Control configuration out of the box.
212
+
213
+ ```typescript
214
+ import { serveStatic } from 'exisjs/middleware'
215
+ import path from 'node:path'
216
+
217
+ app.use('/public', serveStatic(path.join(process.cwd(), 'public'), {
218
+ maxAge: 31536000 // Cache for 1 year
219
+ }))
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 5. Security Middlewares
225
+
226
+ ExisJS bundles several security middlewares. (We will cover these in the Security documentation, but here is a preview):
227
+
228
+ - **`helmet()`**: Automatically sets 14+ secure HTTP headers.
229
+ - **`hpp()`**: Prevents HTTP Parameter Pollution attacks.
230
+ - **`dbSanitize()`**: A universal scraper that protects against NoSQL injection patterns (`$where` and other malicious operators).
231
+ - **`timeout(ms)`**: Kills requests that hang longer than a specified duration.
232
+ - **`ipFilter()`**: Restricts access based on IP Whitelists/Blacklists.
233
+
234
+ ```typescript
235
+ import { helmet, timeout, dbSanitize } from 'exisjs/middleware'
236
+
237
+ app.use(helmet())
238
+ app.use(dbSanitize({ mongo: true }))
239
+ app.use(timeout(5000)) // 5 second timeout
240
+ ```
241
+
242
+ ---
243
+
244
+ ## 6. Advanced Performance Middlewares
245
+
246
+ These middlewares provide additional scaling and performance capabilities.
247
+
248
+ - **`cache()`**: An in-memory route caching engine. Requires a `keyGenerator` to prevent data leaks.
249
+ - **`dedupe()`**: Request deduplication (In-flight request collapsing). If 1,000 users hit a heavy `/stats` route at the exact same millisecond, ExisJS will only compute it **once**, and send the same response to all 1,000 users simultaneously. Requires a `keyGenerator`.
250
+ - **`backpressure()`**: Protects your server from traffic spikes by shedding load or queuing requests when the Node.js Event Loop lags.
251
+
252
+ > [!WARNING]
253
+ > **Authentication Leak Hazard**: When using `cache()` or `dedupe()` on authenticated routes, you **must** include the user's ID or session token in your `keyGenerator`. Failing to do so will result in User B receiving a cached/deduped response generated for User A.
254
+
255
+ ---
256
+
257
+ ## 7. Interceptors & Error Filters
258
+
259
+ As covered in our [Error Handling](/docs/errors) guide, you can centrally mutate responses or catch specific Exception classes using `intercept()` and `catchError()`.
260
+
261
+ ```typescript
262
+ import { intercept, catchError } from 'exisjs/middleware'
263
+
264
+ // Wrap all responses in { data: ... }
265
+ app.use(intercept((data) => ({ data })))
266
+
267
+ // Catch specific errors
268
+ app.use(catchError(DatabaseError, (err, req, res) => {
269
+ res.status(503).json({ error: 'Database Offline' })
270
+ }))
271
+ ```
272
+
273
+ ---
274
+
275
+ ## 8. Schema Validation
276
+
277
+ ExisJS includes a validation engine. You can validate `body`, `query`, or `params` directly at the middleware layer. If validation fails, it automatically rejects the request with a structured HTTP 400 JSON response.
278
+
279
+ #### Functional Paradigm
280
+
281
+ ```typescript filename="src/http/users/route.ts"
282
+ import { controller, route } from 'exisjs/router'
283
+ import { validate } from 'exisjs/middleware'
284
+ import { tex } from 'exisjs/validator'
285
+
286
+ const createUserSchema = {
287
+ body: tex.object({
288
+ email: tex.email(),
289
+ age: tex.number({ min: 18 })
290
+ })
291
+ }
292
+
293
+ export default controller({
294
+ // Automatically rejects invalid payloads with 400 Bad Request
295
+ create: route.post('/', {
296
+ middlewares: [validate(createUserSchema)],
297
+ handle(req, res) {
298
+ // req.body is fully validated, but we also type-cast it safely
299
+ const body = req.body as { email: string, age: number }
300
+
301
+ return { success: true, email: body.email }
302
+ }
303
+ })
304
+ })
305
+ ```
306
+
307
+ #### Class-Based (OOP) Paradigm
308
+
309
+ ```typescript filename="src/http/users/route.ts"
310
+ import { Controller, Post, Use, Body } from 'exisjs/decorators'
311
+ import { validate } from 'exisjs/middleware'
312
+ import { tex } from 'exisjs/validator'
313
+
314
+ const createUserSchema = {
315
+ body: tex.object({
316
+ email: tex.email(),
317
+ age: tex.number({ min: 18 })
318
+ })
319
+ }
320
+
321
+ @Controller()
322
+ export default class UsersController {
323
+ // Automatically rejects invalid payloads with 400 Bad Request
324
+ @Use(validate(createUserSchema))
325
+ @Post('/')
326
+ async create(@Body() body: { email: string, age: number }) {
327
+ return { success: true, email: body.email }
328
+ }
329
+ }
330
+ ```
331
+
332
+ ---
333
+
334
+ ## 9. Idempotency
335
+
336
+ Idempotency ensures that retrying an identical request multiple times (e.g., due to network timeouts) will not result in duplicate operations on the server. ExisJS provides a Redis-backed (or in-memory) idempotency engine.
337
+
338
+ #### Functional Paradigm
339
+
340
+ ```typescript
341
+ import { controller, route } from 'exisjs/router'
342
+ import { IdempotentMiddleware } from 'exisjs/middleware'
343
+
344
+ export default controller({
345
+ checkout: route.post('/checkout', {
346
+ middlewares: [IdempotentMiddleware({ ttl: 86400, headerName: 'Idempotency-Key' })],
347
+ handle(req, res) {
348
+ return { success: true }
349
+ }
350
+ })
351
+ })
352
+ ```
353
+
354
+ #### Class-Based (OOP) Paradigm
355
+
356
+ ```typescript
357
+ import { Controller, Post, Idempotent } from 'exisjs/decorators'
358
+
359
+ @Controller()
360
+ export default class CheckoutController {
361
+ @Idempotent({ ttl: 86400 }) // Caches the response for 24 hours based on the Idempotency-Key header
362
+ @Post('/checkout')
363
+ async checkout() {
364
+ return { success: true }
365
+ }
366
+ }
367
+ ```
368
+
369
+ ---
370
+
371
+ ## 10. Cache Engine
372
+
373
+ ExisJS provides a built-in Cache Engine for `GET` requests. It allows you to reduce database load and response times for frequently requested data.
374
+
375
+ #### Functional Paradigm
376
+
377
+ ```typescript
378
+ import { controller, route } from 'exisjs/router'
379
+ import { cache } from 'exisjs/middleware'
380
+
381
+ export default controller({
382
+ popular: route.get('/popular', {
383
+ middlewares: [
384
+ cache({
385
+ ttlMs: 60000,
386
+ keyGenerator: (req) => req.url // Critical: Define how cache keys are built
387
+ })
388
+ ],
389
+ handle(req, res) {
390
+ return { success: true, books: [] }
391
+ }
392
+ })
393
+ })
394
+ ```
395
+
396
+ #### Class-Based (OOP) Paradigm
397
+
398
+ ```typescript
399
+ import { Controller, Get, Cache } from 'exisjs/decorators'
400
+
401
+ @Controller()
402
+ export default class BooksController {
403
+ @Cache({ ttlMs: 60000, keyGenerator: (req) => req.url }) // Caches the response for 60 seconds
404
+ @Get('/popular')
405
+ async getPopular() {
406
+ return { success: true, books: [] }
407
+ }
408
+ }
409
+ ```
410
+
411
+ ---
412
+
413
+ ## 11. Compression (Gzip / Brotli / Deflate)
414
+
415
+ ExisJS includes a stream-based Compression middleware. It natively hooks into the `uWebSockets` core and compresses JSON, streams, and text on-the-fly, reducing your outbound network bandwidth.
416
+
417
+ The best practice is to configure Compression globally in your **`exis.config.ts`**. By default, if `compression: true` is passed, the framework will add the `compress` middleware for you automatically.
418
+
419
+ ```typescript filename="exis.config.ts"
420
+ import type { ExisConfig } from 'exisjs/config'
421
+
422
+ const config: ExisConfig = {
423
+ // Enables Gzip, Brotli, and Deflate compression globally for all routes
424
+ compression: true,
425
+ }
426
+
427
+ export default config
428
+ ```
429
+
430
+ If you prefer to manually apply it to specific routes or gateways instead of globally, you can import it:
431
+
432
+ ```typescript filename="src/http/api/gateway.ts"
433
+ import { defineGateway } from 'exisjs/router'
434
+ import { compress } from 'exisjs/middleware'
435
+
436
+ export default defineGateway({
437
+ middlewares: [
438
+ compress() // Only compress responses within this Gateway
439
+ ]
440
+ })
441
+ ```
@@ -0,0 +1,144 @@
1
+ ---
2
+ nextUrl: "/docs/cache"
3
+ nextTitle: "Caching"
4
+ ---
5
+
6
+ # Modules & Plugins
7
+
8
+ ExisJS provides a modular architecture. The framework is built on a Plugin Engine that allows you to encapsulate functionality, inject dependencies, and distribute reusable packages across your organization.
9
+
10
+ ExisJS offers two distinct primitives for extension:
11
+ 1. **Modules (`defineModule`)**: Used for grouping domain-level business logic (e.g., `AuthModule`, `UsersModule`).
12
+ 2. **Plugins (`definePlugin`)**: Used for building framework-level extensions that often require configuration options (e.g., `StripePlugin({ apiKey })`).
13
+
14
+ ---
15
+
16
+ ## 1. Domain Modules (`defineModule`)
17
+
18
+ When building large-scale applications, you should avoid placing all your logic into a single file. Modules allow you to encapsulate related dependencies (Providers), sub-modules (Imports), and Routes into a single unit.
19
+
20
+ ### Defining a Module
21
+
22
+ Let's build an `AuthModule` that encapsulates an authentication provider:
23
+
24
+ ```typescript filename="src/modules/auth.module.ts"
25
+ import { defineModule } from 'exisjs/module'
26
+
27
+ export const AuthModule = defineModule({
28
+ name: 'auth-module',
29
+
30
+ // 1. Inject Dependencies
31
+ providers: [
32
+ ['AuthService', { useClass: class AuthService { login() {} } }]
33
+ ],
34
+
35
+ // 2. Import other modules
36
+ imports: [
37
+ DatabaseModule
38
+ ],
39
+
40
+ // 3. Register lifecycle hooks
41
+ async onStart(app) {
42
+ console.log("AuthModule has initialized")
43
+ }
44
+ })
45
+ ```
46
+
47
+ ### Registering Modules
48
+
49
+ Once your module is defined, you can register it globally. The recommended place to register core modules is inside your `exis.config.ts` file, ensuring they boot up synchronously before your server starts accepting traffic.
50
+
51
+ ```typescript filename="exis.config.ts"
52
+ import { defineExisConfig } from 'exisjs/config'
53
+ import { AuthModule } from './src/modules/auth.module'
54
+
55
+ export default defineExisConfig({
56
+ plugins: [
57
+ AuthModule
58
+ ]
59
+ })
60
+ ```
61
+
62
+ Alternatively, you can register it manually inside your `src/http/server.ts`:
63
+
64
+ #### Functional Paradigm
65
+
66
+ ```typescript filename="src/http/server.ts"
67
+ import { exis } from 'exisjs'
68
+ import { AuthModule } from '../modules/auth.module'
69
+
70
+ export default exis({
71
+ async onStart(app) {
72
+ await app.register(AuthModule)
73
+ }
74
+ })
75
+ ```
76
+
77
+ #### Class-Based (OOP) Paradigm
78
+
79
+ ```typescript filename="src/http/server.ts"
80
+ import { Server } from 'exisjs/decorators'
81
+ import { AuthModule } from '../modules/auth.module'
82
+ import type { App } from 'exisjs'
83
+
84
+ @Server({
85
+ plugins: [AuthModule]
86
+ })
87
+ export default class RootServer {
88
+ async onStart(app: App) {
89
+ console.log("Server with AuthModule booted")
90
+ }
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 2. Framework Plugins (`definePlugin`)
97
+
98
+ While `defineModule` is great for internal business logic, `definePlugin` is designed for building distributable, third-party packages that require configuration options.
99
+
100
+ When you use `definePlugin`, ExisJS returns a **Hybrid Object**. This means the plugin can be registered directly if it doesn't need options, or it can be called as a function if you need to pass a configuration object.
101
+
102
+ ### Building a Plugin
103
+
104
+ Let's build a `StripePlugin` that requires an API key:
105
+
106
+ ```typescript filename="src/plugins/stripe.plugin.ts"
107
+ import { definePlugin } from 'exisjs/plugin'
108
+
109
+ interface StripeOptions {
110
+ apiKey: string
111
+ }
112
+
113
+ export const StripePlugin = definePlugin<StripeOptions>({
114
+ name: 'stripe-plugin',
115
+
116
+ async register(app, options) {
117
+ if (!options?.apiKey) {
118
+ throw new Error("Stripe API Key is required")
119
+ }
120
+
121
+ // Initialize Stripe client and inject it globally
122
+ const stripeClient = new Stripe(options.apiKey)
123
+
124
+ app.provide('StripeClient', { useValue: stripeClient })
125
+ }
126
+ })
127
+ ```
128
+
129
+ ### Using the Plugin
130
+
131
+ Because we used `definePlugin`, the framework allows us to pass configuration options directly:
132
+
133
+ ```typescript filename="exis.config.ts"
134
+ import { defineExisConfig } from 'exisjs/config'
135
+ import { StripePlugin } from './src/plugins/stripe.plugin'
136
+ import { env } from './src/env'
137
+
138
+ export default defineExisConfig({
139
+ plugins: [
140
+ // We call the plugin as a function to pass options
141
+ StripePlugin({ apiKey: env.STRIPE_SECRET_KEY })
142
+ ]
143
+ })
144
+ ```