exisjs 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) 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/auth/session.d.ts +4 -1
  46. package/dist/auth/session.d.ts.map +1 -1
  47. package/dist/auth/session.js +33 -5
  48. package/dist/auth/session.js.map +1 -1
  49. package/dist/cache/store.d.ts +11 -3
  50. package/dist/cache/store.d.ts.map +1 -1
  51. package/dist/cache/store.js +87 -19
  52. package/dist/cache/store.js.map +1 -1
  53. package/dist/cli/commands/dev.d.ts.map +1 -1
  54. package/dist/cli/commands/dev.js +4 -6
  55. package/dist/cli/commands/dev.js.map +1 -1
  56. package/dist/database/adapters/mongodb.d.ts +58 -0
  57. package/dist/database/adapters/mongodb.d.ts.map +1 -0
  58. package/dist/database/adapters/mongodb.js +264 -0
  59. package/dist/database/adapters/mongodb.js.map +1 -0
  60. package/dist/database/adapters/mysql.d.ts +27 -0
  61. package/dist/database/adapters/mysql.d.ts.map +1 -0
  62. package/dist/database/adapters/mysql.js +188 -0
  63. package/dist/database/adapters/mysql.js.map +1 -0
  64. package/dist/database/adapters/postgres.d.ts +28 -0
  65. package/dist/database/adapters/postgres.d.ts.map +1 -0
  66. package/dist/database/adapters/postgres.js +174 -0
  67. package/dist/database/adapters/postgres.js.map +1 -0
  68. package/dist/database/adapters/sqlite.d.ts +27 -0
  69. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  70. package/dist/database/adapters/sqlite.js +149 -0
  71. package/dist/database/adapters/sqlite.js.map +1 -0
  72. package/dist/database/index.d.ts +42 -0
  73. package/dist/database/index.d.ts.map +1 -0
  74. package/dist/database/index.js +58 -0
  75. package/dist/database/index.js.map +1 -0
  76. package/dist/database/manager.d.ts +114 -0
  77. package/dist/database/manager.d.ts.map +1 -0
  78. package/dist/database/manager.js +269 -0
  79. package/dist/database/manager.js.map +1 -0
  80. package/dist/database/migration.d.ts +57 -0
  81. package/dist/database/migration.d.ts.map +1 -0
  82. package/dist/database/migration.js +329 -0
  83. package/dist/database/migration.js.map +1 -0
  84. package/dist/database/query-builder.d.ts +89 -0
  85. package/dist/database/query-builder.d.ts.map +1 -0
  86. package/dist/database/query-builder.js +353 -0
  87. package/dist/database/query-builder.js.map +1 -0
  88. package/dist/database/types.d.ts +184 -0
  89. package/dist/database/types.d.ts.map +1 -0
  90. package/dist/database/types.js +4 -0
  91. package/dist/database/types.js.map +1 -0
  92. package/dist/docs/ARCHITECTURE.md +349 -0
  93. package/dist/docs/BENCHMARKS.md +36 -0
  94. package/dist/docs/README.md +34 -0
  95. package/dist/docs/content/README.md +44 -0
  96. package/dist/docs/content/adapters.mdx +199 -0
  97. package/dist/docs/content/cache.mdx +239 -0
  98. package/dist/docs/content/circuit-breaker.mdx +130 -0
  99. package/dist/docs/content/cli/commands.mdx +214 -0
  100. package/dist/docs/content/cli/overview.mdx +92 -0
  101. package/dist/docs/content/cli/usage.mdx +61 -0
  102. package/dist/docs/content/config.mdx +214 -0
  103. package/dist/docs/content/controllers.mdx +241 -0
  104. package/dist/docs/content/cron.mdx +109 -0
  105. package/dist/docs/content/database.mdx +209 -0
  106. package/dist/docs/content/dataloader.mdx +170 -0
  107. package/dist/docs/content/decorators.mdx +204 -0
  108. package/dist/docs/content/errors.mdx +344 -0
  109. package/dist/docs/content/gateways.mdx +376 -0
  110. package/dist/docs/content/installation.mdx +210 -0
  111. package/dist/docs/content/introduction.mdx +96 -0
  112. package/dist/docs/content/middleware.mdx +441 -0
  113. package/dist/docs/content/modules.mdx +144 -0
  114. package/dist/docs/content/oauth.mdx +179 -0
  115. package/dist/docs/content/providers.mdx +147 -0
  116. package/dist/docs/content/queues.mdx +177 -0
  117. package/dist/docs/content/requests.mdx +293 -0
  118. package/dist/docs/content/responses.mdx +271 -0
  119. package/dist/docs/content/routing.mdx +541 -0
  120. package/dist/docs/content/rpc.mdx +146 -0
  121. package/dist/docs/content/security.mdx +294 -0
  122. package/dist/docs/content/sse.mdx +159 -0
  123. package/dist/docs/content/structure.mdx +116 -0
  124. package/dist/docs/content/testing.mdx +139 -0
  125. package/dist/docs/content/validation.mdx +249 -0
  126. package/dist/docs/content/websockets.mdx +289 -0
  127. package/dist/docs/integrations/README.md +23 -0
  128. package/dist/docs/integrations/jwt.mdx +151 -0
  129. package/dist/docs/integrations/mongo.mdx +183 -0
  130. package/dist/docs/integrations/observability.mdx +125 -0
  131. package/dist/docs/integrations/openai.mdx +108 -0
  132. package/dist/docs/integrations/postgres.mdx +190 -0
  133. package/dist/docs/integrations/posthog.mdx +122 -0
  134. package/dist/docs/integrations/redis.mdx +141 -0
  135. package/dist/docs/integrations/resend.mdx +117 -0
  136. package/dist/docs/integrations/s3.mdx +122 -0
  137. package/dist/docs/integrations/swagger.mdx +113 -0
  138. package/dist/docs/reference/README.md +3 -0
  139. package/dist/docs/reference/adapters.mdx +130 -0
  140. package/dist/docs/reference/auth.mdx +183 -0
  141. package/dist/docs/reference/cache.mdx +132 -0
  142. package/dist/docs/reference/circuit-breaker.mdx +71 -0
  143. package/dist/docs/reference/config.mdx +118 -0
  144. package/dist/docs/reference/context.mdx +168 -0
  145. package/dist/docs/reference/dataloader.mdx +91 -0
  146. package/dist/docs/reference/decorators.mdx +293 -0
  147. package/dist/docs/reference/di.mdx +123 -0
  148. package/dist/docs/reference/error.mdx +170 -0
  149. package/dist/docs/reference/exis.mdx +124 -0
  150. package/dist/docs/reference/middleware.mdx +304 -0
  151. package/dist/docs/reference/plugin.mdx +113 -0
  152. package/dist/docs/reference/queue.mdx +155 -0
  153. package/dist/docs/reference/router.mdx +248 -0
  154. package/dist/docs/reference/sanitize.mdx +130 -0
  155. package/dist/docs/reference/storage.mdx +116 -0
  156. package/dist/docs/reference/swagger.mdx +132 -0
  157. package/dist/docs/reference/testing.mdx +145 -0
  158. package/dist/docs/reference/validator.mdx +186 -0
  159. package/dist/index.d.ts +4 -0
  160. package/dist/index.d.ts.map +1 -1
  161. package/dist/index.js +10 -1
  162. package/dist/index.js.map +1 -1
  163. package/dist/middleware/backpressure.d.ts.map +1 -1
  164. package/dist/middleware/backpressure.js +21 -12
  165. package/dist/middleware/backpressure.js.map +1 -1
  166. package/dist/middleware/cache.d.ts +6 -0
  167. package/dist/middleware/cache.d.ts.map +1 -1
  168. package/dist/middleware/cache.js +84 -28
  169. package/dist/middleware/cache.js.map +1 -1
  170. package/dist/middleware/idempotency.d.ts +4 -1
  171. package/dist/middleware/idempotency.d.ts.map +1 -1
  172. package/dist/middleware/idempotency.js +29 -4
  173. package/dist/middleware/idempotency.js.map +1 -1
  174. package/dist/middleware/ip-filter.d.ts.map +1 -1
  175. package/dist/middleware/ip-filter.js +16 -0
  176. package/dist/middleware/ip-filter.js.map +1 -1
  177. package/dist/middleware/rate-limit.d.ts.map +1 -1
  178. package/dist/middleware/rate-limit.js +29 -12
  179. package/dist/middleware/rate-limit.js.map +1 -1
  180. package/dist/queue/drivers/MemoryDriver.d.ts +6 -3
  181. package/dist/queue/drivers/MemoryDriver.d.ts.map +1 -1
  182. package/dist/queue/drivers/MemoryDriver.js +76 -23
  183. package/dist/queue/drivers/MemoryDriver.js.map +1 -1
  184. package/dist/server/bootstrapper.d.ts +3 -0
  185. package/dist/server/bootstrapper.d.ts.map +1 -1
  186. package/dist/server/bootstrapper.js +56 -8
  187. package/dist/server/bootstrapper.js.map +1 -1
  188. package/dist/server/bun-adapter.d.ts +2 -0
  189. package/dist/server/bun-adapter.d.ts.map +1 -1
  190. package/dist/server/bun-adapter.js +23 -12
  191. package/dist/server/bun-adapter.js.map +1 -1
  192. package/dist/server/request-handler.d.ts +0 -2
  193. package/dist/server/request-handler.d.ts.map +1 -1
  194. package/dist/server/request-handler.js +2 -24
  195. package/dist/server/request-handler.js.map +1 -1
  196. package/dist/server/request.d.ts.map +1 -1
  197. package/dist/server/request.js +24 -11
  198. package/dist/server/request.js.map +1 -1
  199. package/dist/server/uws-adapter.d.ts +80 -0
  200. package/dist/server/uws-adapter.d.ts.map +1 -0
  201. package/dist/server/uws-adapter.js +357 -0
  202. package/dist/server/uws-adapter.js.map +1 -0
  203. package/dist/server/ws-orchestrator.d.ts +1 -0
  204. package/dist/server/ws-orchestrator.d.ts.map +1 -1
  205. package/dist/server/ws-orchestrator.js +83 -0
  206. package/dist/server/ws-orchestrator.js.map +1 -1
  207. package/dist/types.d.ts +1 -1
  208. package/dist/types.d.ts.map +1 -1
  209. package/dist/utils/config.js.map +1 -1
  210. package/dist/validator/tex.d.ts +21 -16
  211. package/dist/validator/tex.d.ts.map +1 -1
  212. package/dist/validator/tex.js.map +1 -1
  213. package/package.json +11 -3
@@ -0,0 +1,294 @@
1
+ ---
2
+ nextUrl: "/docs/circuit-breaker"
3
+ nextTitle: "Circuit Breaker"
4
+ ---
5
+
6
+ # Security & Authentication
7
+
8
+ ExisJS includes native implementations for Cryptography, JWT generation, Role-Based Access Control (RBAC), and Edge Security.
9
+
10
+ ---
11
+
12
+ ## 1. Password Hashing (Scrypt)
13
+
14
+ ExisJS includes a password hashing engine powered by Node.js's built-in `crypto.scrypt`.
15
+
16
+ It automatically generates a secure salt, derives the key, and formats the output into a single safely-storable string (`scrypt:cost:blockSize:parallelization:keyLength:salt:hash`).
17
+
18
+ #### Functional Paradigm
19
+
20
+ ```typescript filename="src/http/auth/route.ts"
21
+ import { controller, route } from 'exisjs/router'
22
+ import { Password } from 'exisjs/auth'
23
+
24
+ export default controller({
25
+ register: route.post('/register', {
26
+ async handle(req, res) {
27
+ const plaintext = req.body.password
28
+
29
+ // 1. Hash the password
30
+ // Uses secure defaults (cost: 16384, block: 8)
31
+ const hashedPassword = await Password.hashPassword(plaintext)
32
+
33
+ await db.users.create({ password: hashedPassword })
34
+ return { success: true }
35
+ }
36
+ }),
37
+
38
+ login: route.post('/login', {
39
+ async handle(req, res) {
40
+ const user = await db.users.findByEmail(req.body.email)
41
+
42
+ // 2. Verify the hash (timing-safe)
43
+ const isValid = await Password.verifyPassword(req.body.password, user.password)
44
+
45
+ if (!isValid) throw new Error("Invalid credentials")
46
+ return { success: true }
47
+ }
48
+ })
49
+ })
50
+ ```
51
+
52
+ #### Class-Based (OOP) Paradigm
53
+
54
+ ```typescript filename="src/http/auth/route.ts"
55
+ import { Controller, Post, Body } from 'exisjs/decorators'
56
+ import { Password } from 'exisjs/auth'
57
+
58
+ @Controller()
59
+ export default class AuthController {
60
+ @Post('/register')
61
+ async register(@Body() body: any) {
62
+ const plaintext = body.password
63
+
64
+ // 1. Hash the password
65
+ // Uses secure defaults (cost: 16384, block: 8)
66
+ const hashedPassword = await Password.hashPassword(plaintext)
67
+
68
+ await db.users.create({ password: hashedPassword })
69
+ return { success: true }
70
+ }
71
+
72
+ @Post('/login')
73
+ async login(@Body() body: any) {
74
+ const user = await db.users.findByEmail(body.email)
75
+
76
+ // 2. Verify the hash (timing-safe)
77
+ const isValid = await Password.verifyPassword(body.password, user.password)
78
+
79
+ if (!isValid) throw new Error("Invalid credentials")
80
+ return { success: true }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ---
86
+
87
+ ## 2. JSON Web Tokens (JWT)
88
+
89
+ ExisJS provides a JWT engine using native `createHmac('sha256')`.
90
+
91
+ ```typescript filename="src/http/auth/route.ts"
92
+ import { JWT } from 'exisjs/auth'
93
+
94
+ // Generate a Token (Expires in 24 hours)
95
+ const token = JWT.signJWT({ userId: 123, role: 'admin' }, process.env.JWT_SECRET, {
96
+ expiresIn: 86400
97
+ })
98
+
99
+ // Verify a Token
100
+ // Automatically throws 401 Unauthorized or TokenExpiredError if invalid
101
+ const payload = JWT.verifyJWT<{ userId: number, role: string }>(token, process.env.JWT_SECRET)
102
+ ```
103
+
104
+ ### Creating an Auth Middleware
105
+
106
+ You can wrap this in a gateway middleware to protect entire folders:
107
+
108
+ #### Functional Paradigm
109
+
110
+ ```typescript filename="src/http/admin/gateway.ts"
111
+ import { defineGateway } from 'exisjs/router'
112
+ import { JWT } from 'exisjs/auth'
113
+
114
+ export default defineGateway({
115
+ middlewares: [
116
+ (req, res, next) => {
117
+ const token = req.headers.authorization?.split(' ')[1]
118
+ // Verifies the token and attaches the payload to the request
119
+ req.user = JWT.verifyJWT(token, process.env.JWT_SECRET)
120
+ next()
121
+ }
122
+ ]
123
+ })
124
+ ```
125
+
126
+ #### Class-Based (OOP) Paradigm
127
+
128
+ ```typescript filename="src/http/admin/gateway.ts"
129
+ import { Gateway } from 'exisjs/decorators'
130
+ import { JWT } from 'exisjs/auth'
131
+
132
+ @Gateway({
133
+ middlewares: [
134
+ (req, res, next) => {
135
+ const token = req.headers.authorization?.split(' ')[1]
136
+ req.user = JWT.verifyJWT(token, process.env.JWT_SECRET)
137
+ next()
138
+ }
139
+ ]
140
+ })
141
+ export default class AdminGateway {}
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 3. Role-Based Access Control (RBAC)
147
+
148
+ Once your user is authenticated and their payload is attached to `req.user`, ExisJS provides native Role-Based Access Control right inside the route configuration. You don't need any custom middlewares for this.
149
+
150
+ #### Functional Paradigm
151
+
152
+ ```typescript filename="src/http/admin/route.ts"
153
+ import { controller, route } from 'exisjs/router'
154
+ import { parseUser } from '../middlewares/auth.js'
155
+
156
+ export default controller({
157
+ // Intercepts the request and checks req.user.permissions (or req.user.role)
158
+ // before the handler even executes. Throws 403 Forbidden if missing.
159
+ deleteDatabase: route.delete('/db', {
160
+ permissions: ['superadmin'],
161
+ middleware: [parseUser()],
162
+ handle() {
163
+ return { success: true }
164
+ }
165
+ }),
166
+
167
+ // Accepts an array of allowed permissions
168
+ viewDashboard: route.get('/dashboard', {
169
+ permissions: ['admin', 'superadmin', 'manager'],
170
+ middleware: [parseUser()],
171
+ handle() {
172
+ return { metrics: [] }
173
+ }
174
+ })
175
+ })
176
+ ```
177
+
178
+ #### Class-Based (OOP) Paradigm
179
+
180
+ ```typescript filename="src/http/admin/route.ts"
181
+ import { Controller, Delete, Get, Use, Permissions } from 'exisjs/decorators'
182
+ import { parseUser } from '../middlewares/auth.js'
183
+
184
+ @Use(parseUser())
185
+ @Controller()
186
+ export default class AdminController {
187
+ // Only users with superadmin permission can access this
188
+ @Permissions('superadmin')
189
+ @Delete('/db')
190
+ async deleteDatabase() {
191
+ return { success: true }
192
+ }
193
+
194
+ // Accepts an array of allowed permissions
195
+ @Permissions(['admin', 'superadmin', 'manager'])
196
+ @Get('/dashboard')
197
+ async viewDashboard() {
198
+ return { metrics: [] }
199
+ }
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ## 4. Stateful Sessions
206
+
207
+ If you prefer stateful authentication over JWTs, ExisJS ships with a secure session middleware. It securely signs the session cookie (`exis_sid`) and automatically persists changes to `req.session` back to the active store.
208
+
209
+ By default, it uses a `MemorySessionStore`, but you can plug in any distributed store (like Redis) by implementing the `SessionStore` interface.
210
+
211
+ #### Functional Paradigm
212
+
213
+ ```typescript filename="src/http/server.ts"
214
+ import { exis } from 'exisjs'
215
+ import { session } from 'exisjs/auth'
216
+
217
+ export default exis({
218
+ async onStart(app) {
219
+ // 1. Mount the session globally
220
+ app.use(session({
221
+ secret: process.env.SESSION_SECRET_KEY, // Must be 32+ chars
222
+ ttl: 86400 * 1000 // 1 day
223
+ }))
224
+ }
225
+ })
226
+ ```
227
+
228
+ #### Class-Based (OOP) Paradigm
229
+
230
+ ```typescript filename="src/http/server.ts"
231
+ import { Server } from 'exisjs/decorators'
232
+ import { session } from 'exisjs/auth'
233
+ import type { App } from 'exisjs'
234
+
235
+ @Server()
236
+ export default class RootServer {
237
+ async onStart(app: App) {
238
+ // 1. Mount the session globally
239
+ app.use(session({
240
+ secret: process.env.SESSION_SECRET_KEY, // Must be 32+ chars
241
+ ttl: 86400 * 1000 // 1 day
242
+ }))
243
+ }
244
+ }
245
+ ```
246
+
247
+ #### Functional Paradigm
248
+
249
+ ```typescript filename="src/http/cart/route.ts"
250
+ import { controller, route } from 'exisjs/router'
251
+
252
+ export default controller({
253
+ addItem: route.post('/', {
254
+ handle(req, res) {
255
+ // 2. Read and Write natively to req.session
256
+ // ExisJS automatically saves this back to the Store when the request ends
257
+ if (!req.session.cart) req.session.cart = []
258
+
259
+ req.session.cart.push(req.body.itemId)
260
+ return { cart: req.session.cart }
261
+ }
262
+ })
263
+ })
264
+ ```
265
+
266
+ #### Class-Based (OOP) Paradigm
267
+
268
+ ```typescript filename="src/http/cart/route.ts"
269
+ import { Controller, Post, Req, Body } from 'exisjs/decorators'
270
+
271
+ @Controller()
272
+ export default class CartController {
273
+ @Post('/')
274
+ async addItem(@Req() req: any, @Body() body: any) {
275
+ // 2. Read and Write natively to req.session
276
+ // ExisJS automatically saves this back to the Store when the request ends
277
+ if (!req.session.cart) req.session.cart = []
278
+
279
+ req.session.cart.push(body.itemId)
280
+ return { cart: req.session.cart }
281
+ }
282
+ }
283
+ ```
284
+
285
+ ---
286
+
287
+ ## 5. Edge Security Middleware
288
+
289
+ As mentioned in the [Middleware Guide](/docs/middleware), you should strongly consider registering the following global security middleware in your `server.ts`:
290
+
291
+ - `helmet()`: Enforces Strict-Transport-Security (HSTS), X-Content-Type-Options (nosniff), and prevents Clickjacking (`X-Frame-Options: DENY`).
292
+ - `dbSanitize({ mongo: true })`: A universal scraper that strips NoSQL operators (`$where`) to provide defense-in-depth for MongoDB databases.
293
+ - `csrf({ secret })`: Enforces the Signed Double Submit Cookie pattern to protect state-changing requests (POST, PUT, DELETE) from Cookie Tossing attacks.
294
+ - `hpp()`: HTTP Parameter Pollution protection (removes duplicate array keys).
@@ -0,0 +1,159 @@
1
+ ---
2
+ nextUrl: "/docs/websockets"
3
+ nextTitle: "WebSockets"
4
+ ---
5
+
6
+ # Server-Sent Events (SSE)
7
+
8
+ Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection. Unlike WebSockets, SSE is unidirectional (server-to-client) and works over standard HTTP without requiring complex handshake protocols or specialized load balancer configurations.
9
+
10
+ SSE is the industry standard for Large Language Model (LLM) streaming (e.g., OpenAI, Anthropic) and real-time dashboard updates. ExisJS provides native SSE support.
11
+
12
+ ---
13
+
14
+ ## 1. Creating an SSE Stream
15
+
16
+ When you declare a route as an SSE endpoint using `route.sse()` or `@Sse()`, ExisJS automatically configures the correct HTTP headers (`Content-Type: text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`) and provides an `ExisSSE` stream object.
17
+
18
+ ### Functional Paradigm
19
+
20
+ ```typescript filename="src/http/chat/route.ts"
21
+ import { controller, route } from 'exisjs/router'
22
+ import Groq from 'groq-sdk'
23
+
24
+ export default controller({
25
+ chat: route.sse('/api/chat', {
26
+ async handle({ stream: sse, req }) {
27
+ const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
28
+ const message = req.query.message as string
29
+
30
+ if (!message) {
31
+ sse.send({ error: 'Message is required' }, 'error')
32
+ sse.close()
33
+ return
34
+ }
35
+
36
+ try {
37
+ const aiStream = await groq.chat.completions.create({
38
+ messages: [{ role: 'user', content: message }],
39
+ model: 'llama-3.1-8b-instant',
40
+ stream: true,
41
+ })
42
+
43
+ // Iterate over the async generator provided by the AI SDK
44
+ for await (const chunk of aiStream) {
45
+ // Prevent writing to a closed socket if the client disconnects early
46
+ if (!sse.connected) break
47
+
48
+ const content = chunk.choices[0]?.delta?.content || ''
49
+ if (content) {
50
+ sse.send({ text: content }, 'chunk')
51
+ }
52
+ }
53
+
54
+ sse.send({ text: '' }, 'done')
55
+ } finally {
56
+ // Ensure the connection is always closed when processing completes
57
+ sse.close()
58
+ }
59
+ }
60
+ })
61
+ })
62
+ ```
63
+
64
+ ### Class-Based (OOP) Paradigm
65
+
66
+ ```typescript filename="src/http/chat/route.ts"
67
+ import { Controller, Sse, Stream, Req } from 'exisjs/decorators'
68
+ import type { ExisSSE } from 'exisjs/router'
69
+ import Groq from 'groq-sdk'
70
+
71
+ @Controller()
72
+ export default class AIChatController {
73
+ @Sse('/api/chat')
74
+ async handleChat(@Stream() sse: ExisSSE, @Req() req: any) {
75
+ const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
76
+ const message = req.query.message as string
77
+
78
+ if (!message) {
79
+ sse.send({ error: 'Message is required' }, 'error')
80
+ sse.close()
81
+ return
82
+ }
83
+
84
+ try {
85
+ const aiStream = await groq.chat.completions.create({
86
+ messages: [{ role: 'user', content: message }],
87
+ model: 'llama-3.1-8b-instant',
88
+ stream: true,
89
+ })
90
+
91
+ for await (const chunk of aiStream) {
92
+ if (!sse.connected) break
93
+
94
+ const content = chunk.choices[0]?.delta?.content || ''
95
+ if (content) {
96
+ sse.send({ text: content }, 'chunk')
97
+ }
98
+ }
99
+ sse.send({ text: '' }, 'done')
100
+ } finally {
101
+ sse.close()
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 2. The `ExisSSE` API
110
+
111
+ The injected `sse` object gives you full control over the streaming connection:
112
+
113
+ - **`sse.send(data: string | Record<string, any>, eventName?: string)`**: Serializes the data (if it is an object) to JSON and sends it down the wire. You can optionally tag it with a specific `eventName`.
114
+ - **`sse.connected`**: A boolean that evaluates to `true` while the connection is alive. You should always check this inside long-running `for await` or `while` loops to prevent throwing "Socket Closed" errors when the client disconnects unexpectedly.
115
+ - **`sse.close()`**: Safely closes the stream and terminates the HTTP connection.
116
+
117
+ ```typescript
118
+ // Sending a plain text message
119
+ sse.send("Processing your request...")
120
+
121
+ // Sending a JSON object tagged with a specific event type (e.g., 'metrics')
122
+ sse.send({ cpu_usage: 95, mem_usage: 60 }, 'metrics')
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 3. Client-Side Consumption
128
+
129
+ To consume the SSE stream from the frontend (e.g., in React, Vue, or Vanilla JS), use the native browser `EventSource` API.
130
+
131
+ ```javascript filename="frontend/app.js"
132
+ // Connect to the ExisJS SSE endpoint
133
+ const eventSource = new EventSource('/api/chat?message=Hello')
134
+
135
+ // Listen for the default 'message' event (when no eventName is specified in sse.send)
136
+ eventSource.onmessage = (event) => {
137
+ const data = JSON.parse(event.data)
138
+ console.log("Standard message:", data)
139
+ }
140
+
141
+ // Listen for a custom event type, such as 'chunk'
142
+ eventSource.addEventListener('chunk', (event) => {
143
+ const data = JSON.parse(event.data)
144
+ // Append the streamed text to the DOM
145
+ document.getElementById('output').textContent += data.text
146
+ })
147
+
148
+ // Listen for the 'done' event to close the connection from the client side
149
+ eventSource.addEventListener('done', () => {
150
+ console.log("Stream finished.")
151
+ eventSource.close()
152
+ })
153
+
154
+ // Handle connection errors
155
+ eventSource.onerror = (error) => {
156
+ console.error("SSE Connection Error:", error)
157
+ eventSource.close()
158
+ }
159
+ ```
@@ -0,0 +1,116 @@
1
+ ---
2
+ nextUrl: "/docs/config"
3
+ nextTitle: "Configuration"
4
+ ---
5
+
6
+ # Project Structure
7
+
8
+ When you bootstrap a new ExisJS project using `@exisjs/create`, it generates a clean, opinionated folder structure designed for scalability and maintainability.
9
+
10
+ Unlike frameworks that force you into deeply nested architectural layers, ExisJS relies heavily on native File-System Routing and centralized configurations to keep your project lean.
11
+
12
+ ---
13
+
14
+ ## The Default Structure
15
+
16
+ Here is a look at a standard ExisJS project structure:
17
+
18
+ ```text
19
+ my-backend/
20
+ ├── .exis/ # Internal framework cache and build artifacts
21
+ ├── src/
22
+ │ └── http/
23
+ │ ├── server.ts # The main backend entry point
24
+ │ ├── route.ts # The root API routes (e.g. `GET /`)
25
+ │ └── health/
26
+ │ └── route.ts # Healthcheck endpoints (e.g. `GET /health`)
27
+ ├── .env # Local environment variables
28
+ ├── env.ts # Type-safe environment variable schema
29
+ ├── exis.config.ts # The ExisJS server configuration file
30
+ ├── package.json
31
+ └── tsconfig.json
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Key Files & Directories
37
+
38
+ ### 1. `exis.config.ts`
39
+ This configures the global application infrastructure. Here, you define network ports, SSL certificates, global CORS policies, Helmet security, and register native framework Plugins (like GraphQL or Drizzle).
40
+
41
+ [Read the full Configuration Guide](/docs/config)
42
+
43
+ ### 2. `env.ts`
44
+ ExisJS strongly encourages type-safe environment variables. By defining an `env.ts` file using the built-in validator (`v.env()`), your application will gracefully crash at boot if a required environment variable (like `DATABASE_URL`) is missing.
45
+
46
+ ```typescript filename="env.ts"
47
+ import { tex } from 'exisjs/validator'
48
+
49
+ export const env = tex.object({
50
+ PORT: tex.number({ coerce: true, optional: true }),
51
+ NODE_ENV: tex.enum(['development', 'production', 'test']),
52
+ DATABASE_URL: tex.string()
53
+ }).parse(process.env)
54
+ ```
55
+
56
+ ### 3. `src/http/server.ts`
57
+ This is your application's bootstrap file. Instead of manually invoking `app.listen()`, you export your server configuration. The ExisJS CLI automatically reads this file, connects your plugins, mounts the file-system router, and starts the server.
58
+
59
+ ### Functional Server Setup
60
+
61
+ ```typescript filename="src/http/server.ts"
62
+ import { exis } from 'exisjs'
63
+
64
+ export default exis({
65
+ async onStart(app) {
66
+ // Connect to database, provide global DI services, etc.
67
+ },
68
+ async onClose(app) {
69
+ // Gracefully close connections
70
+ }
71
+ })
72
+ ```
73
+
74
+ ### Class-Based (OOP) Server Setup
75
+
76
+ ```typescript filename="src/http/server.ts"
77
+ import { Server } from 'exisjs/decorators'
78
+ import type { App } from 'exisjs'
79
+
80
+ @Server()
81
+ export default class RootServer {
82
+ async onStart(app: App) {
83
+ // Connect to database, provide global DI services, etc.
84
+ }
85
+ async onClose(app: App) {
86
+ // Gracefully close connections
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### 4. `src/http/**/route.ts`
92
+ ExisJS uses **File-System Routing** out of the box! Any file named `route.ts` inside the `src/http/` directory is automatically mapped to a URL based on its folder path. You can define routes functionally using `controller({...})` or with OOP classes using `@Controller()`.
93
+ - `src/http/route.ts` maps to `/`
94
+ - `src/http/health/route.ts` maps to `/health`
95
+ - `src/http/users/settings/route.ts` maps to `/users/settings`
96
+
97
+ ### 5. `src/http/**/gateway.ts`
98
+ Gateways allow you to apply configurations to entire folders. If you place a `gateway.ts` file inside `src/http/admin/`, the Guards, Middlewares, and Providers defined in that Gateway will automatically apply to every single route inside `/admin/*`. Like servers, these can be built using `defineGateway({...})` or the `@Gateway()` decorator.
99
+
100
+ ### 6. `.exis/`
101
+ This is an internal, hidden directory generated by the ExisJS CLI. It holds compiled router mappings and runtime cache to skip filesystem scanning in production. You should add `.exis/` to your `.gitignore`.
102
+
103
+ ### 7. Custom Directories (e.g., `src/services/` or `src/models/`)
104
+ ExisJS only enforces structure inside `src/http/` (for routing) and `src/jobs/` (for background queues). For the rest of your application logic, you are free to organize it however you see fit. A common pattern is to abstract database schemas into `src/models/` and reusable business logic into `src/services/`, and then inject them into your route controllers using the Dependency Injection container.
105
+
106
+ ---
107
+
108
+ ## The ExisJS CLI
109
+
110
+ Your project is driven entirely by the `exis` CLI tool, which is typically mapped in your `package.json` scripts:
111
+
112
+ - `exis dev`: Starts the development server with Hot-Module Replacement (HMR).
113
+ - `exis build`: Compiles your TypeScript code to JavaScript for production.
114
+ - `exis start`: Starts the production server using the compiled output.
115
+ - `exis routes`: Prints a formatted table of every compiled API endpoint and WebSocket in your application.
116
+ - `exis generate <type>`: Scaffolds new routes, plugins, or middleware boilerplate.