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,241 @@
1
+ ---
2
+ nextUrl: "/docs/providers"
3
+ nextTitle: "Providers"
4
+ ---
5
+
6
+ # Controllers
7
+
8
+ Controllers are responsible for handling incoming requests and sending responses back to the client.
9
+
10
+ A controller's purpose is to handle specific requests for the application. The routing mechanism determines which controller will handle each request. Often, a controller has multiple routes, and each route can perform a different action.
11
+
12
+ While ExisJS natively supports Functional Routing, developers coming from enterprise backgrounds often prefer Object-Oriented patterns. To create a basic OOP controller, we use classes and decorators. Decorators link classes with the necessary metadata, allowing ExisJS to automatically route requests.
13
+
14
+ ## Routing & Prefixes
15
+
16
+ In the following example, we'll use the `@Controller()` decorator, which is required to define a basic OOP controller. We'll specify an optional route path prefix of `users`. Using a path prefix helps group related routes together.
17
+
18
+ ```typescript filename="src/http/users/route.ts"
19
+ import { Controller, Get } from 'exisjs/decorators'
20
+
21
+ @Controller('users')
22
+ export default class UsersController {
23
+ @Get()
24
+ findAll(): any[] {
25
+ return [
26
+ { id: 1, name: 'Alice' },
27
+ { id: 2, name: 'Bob' }
28
+ ]
29
+ }
30
+ }
31
+ ```
32
+
33
+ The `@Get()` HTTP request method decorator placed before the `findAll()` method tells ExisJS to create a handler for GET requests. Because the controller has a prefix of `users`, this endpoint maps to `GET /users`. If we used `@Get('active')`, it would map to `GET /users/active`.
34
+
35
+ ## Request Object
36
+
37
+ Handlers often need access to the client’s request details. You can access the request object by injecting it using the `@Req()` decorator.
38
+
39
+ ```typescript filename="src/http/users/route.ts"
40
+ import { Controller, Get, Req } from 'exisjs/decorators'
41
+ import type { Request } from 'exisjs/router'
42
+
43
+ @Controller('users')
44
+ export default class UsersController {
45
+ @Get()
46
+ findAll(@Req() request: Request): any[] {
47
+ console.log(request.ip, request.headers)
48
+ return []
49
+ }
50
+ }
51
+ ```
52
+
53
+ In most cases, you don't need to manually access the entire request object. Instead, you can use dedicated decorators provided out of the box:
54
+
55
+ <div className="overflow-x-auto mb-6">
56
+ <table className="w-full text-sm text-left border border-border/50 rounded-lg overflow-hidden">
57
+ <thead className="bg-muted/50 text-foreground">
58
+ <tr>
59
+ <th className="px-4 py-3 border-b border-border/50 font-semibold w-1/3">Decorator</th>
60
+ <th className="px-4 py-3 border-b border-border/50 font-semibold">Represents</th>
61
+ </tr>
62
+ </thead>
63
+ <tbody className="divide-y divide-border/50">
64
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
65
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Req()</td>
66
+ <td className="px-4 py-3 text-muted-foreground">The raw HTTP Request object</td>
67
+ </tr>
68
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
69
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Res()</td>
70
+ <td className="px-4 py-3 text-muted-foreground">The raw HTTP Response object</td>
71
+ </tr>
72
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
73
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Param(key?)</td>
74
+ <td className="px-4 py-3 text-muted-foreground">URL Path Parameters (e.g. req.params.id)</td>
75
+ </tr>
76
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
77
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Body(key?)</td>
78
+ <td className="px-4 py-3 text-muted-foreground">Parsed Request Body</td>
79
+ </tr>
80
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
81
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Query(key?)</td>
82
+ <td className="px-4 py-3 text-muted-foreground">URL Query Parameters</td>
83
+ </tr>
84
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
85
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Headers(name?)</td>
86
+ <td className="px-4 py-3 text-muted-foreground">Specific HTTP Header</td>
87
+ </tr>
88
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
89
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>HostParam(name?)</td>
90
+ <td className="px-4 py-3 text-muted-foreground">Subdomain / Host route parameter</td>
91
+ </tr>
92
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
93
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Session()</td>
94
+ <td className="px-4 py-3 text-muted-foreground">The request session object</td>
95
+ </tr>
96
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
97
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Next()</td>
98
+ <td className="px-4 py-3 text-muted-foreground">Next middleware function in pipeline</td>
99
+ </tr>
100
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
101
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Ip()</td>
102
+ <td className="px-4 py-3 text-muted-foreground">The client's IP address</td>
103
+ </tr>
104
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
105
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>UploadedFile()</td>
106
+ <td className="px-4 py-3 text-muted-foreground">A single uploaded file (multipart/form-data)</td>
107
+ </tr>
108
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
109
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>UploadedFiles()</td>
110
+ <td className="px-4 py-3 text-muted-foreground">Array of uploaded files</td>
111
+ </tr>
112
+ </tbody>
113
+ </table>
114
+ </div>
115
+
116
+ ## Payloads & Validation
117
+
118
+ For POST and PUT endpoints, you need to read the incoming JSON payload. Use the `@Body()` decorator to inject it.
119
+
120
+ ```typescript filename="src/http/users/route.ts"
121
+ import { Controller, Post, Body } from 'exisjs/decorators'
122
+
123
+ export class CreateUserDto {
124
+ name: string;
125
+ email: string;
126
+ role: string;
127
+ }
128
+
129
+ @Controller('users')
130
+ export default class UsersController {
131
+ @Post()
132
+ async create(@Body() createUserDto: CreateUserDto) {
133
+ return { id: 3, ...createUserDto }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ## Status Codes & Headers
139
+
140
+ By default, responses have a `200 OK` status (except POST which uses `201`). You can change this declaratively using the `@HttpCode()` and `@Header()` decorators.
141
+
142
+ ```typescript filename="src/http/users/route.ts"
143
+ import { Controller, Post, HttpCode, Header } from 'exisjs/decorators'
144
+
145
+ @Controller('users')
146
+ export default class UsersController {
147
+ @Post()
148
+ @HttpCode(204)
149
+ @Header('Cache-Control', 'no-store')
150
+ create() {
151
+ return null // No Content
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## Advanced Features
157
+
158
+ ExisJS provides routing enhancements like Subdomain Routing, Redirects, and Passthrough Responses. These are supported across both paradigms.
159
+
160
+ ### Functional Paradigm
161
+
162
+ ```typescript filename="src/http/advanced/route.ts"
163
+ import { controller, route } from 'exisjs/router'
164
+
165
+ export default controller({
166
+ // 1. Subdomain Routing
167
+ ping: route.get('/ping', {
168
+ host: ':sub.exis.local',
169
+ handle({ req }) {
170
+ return { subdomain: (req.params as any).sub }
171
+ }
172
+ }),
173
+ // 2. Redirect
174
+ redirect: route.get('/redirect', {
175
+ handle({ res }) {
176
+ res.redirect('https://google.com')
177
+ }
178
+ }),
179
+ // 3. Passthrough Response
180
+ manual: route.get('/manual', {
181
+ handle({ res }) {
182
+ res.setHeader('X-Custom', 'true')
183
+ return { msg: 'This is auto-serialized with custom headers!' }
184
+ }
185
+ })
186
+ })
187
+ ```
188
+
189
+ ### Class-Based (OOP) Paradigm
190
+
191
+ ```typescript filename="src/http/advanced/route.ts"
192
+ import { Controller, Get, HostParam, Redirect, Res } from 'exisjs/decorators'
193
+ import type { Response } from 'exisjs/router'
194
+
195
+ // 1. Subdomain Routing
196
+ @Controller({ host: ':sub.exis.local' })
197
+ export class SubdomainController {
198
+ @Get('/ping')
199
+ ping(@HostParam('sub') sub: string) {
200
+ return { subdomain: sub }
201
+ }
202
+ }
203
+
204
+ @Controller('users')
205
+ export class AdvancedController {
206
+ // 2. Redirect
207
+ @Get('/redirect')
208
+ @Redirect('https://google.com')
209
+ redirect() {}
210
+
211
+ // 3. Passthrough Response
212
+ @Get('/manual')
213
+ manual(@Res({ passthrough: true }) res: Response) {
214
+ res.setHeader('X-Custom', 'true')
215
+ return { msg: 'This is auto-serialized with custom headers!' }
216
+ }
217
+ }
218
+ ```
219
+
220
+ ## Registration & Library-Specific Response
221
+
222
+ Unlike frameworks where controllers must be registered manually in a module file, ExisJS uses Folder-Based Routing. To register a controller, you export default the class inside a `route.ts` file.
223
+
224
+ If you need low-level control over the response, you can inject the native `@Res()` object.
225
+
226
+ ```typescript filename="src/http/users/route.ts"
227
+ import { Controller, Get, Res } from 'exisjs/decorators'
228
+ import type { Response } from 'exisjs/router'
229
+
230
+ @Controller('users')
231
+ export default class UsersController {
232
+ @Get()
233
+ findAll(@Res() res: Response) {
234
+ res.status(200).json({ data: [] })
235
+ }
236
+ }
237
+ ```
238
+
239
+ <Info title="Tip: Safely Returning with @Res">
240
+ In some frameworks, injecting the `@Res()` object disables auto-serialization, causing the request to hang if you forget to call `res.send()`. ExisJS handles this differently: if you return a value from your handler, ExisJS will still safely auto-serialize it to JSON even if you injected `@Res()` to set a custom header.
241
+ </Info>
@@ -0,0 +1,109 @@
1
+ ---
2
+ nextUrl: "/docs/rpc"
3
+ nextTitle: "RPC Client"
4
+ ---
5
+
6
+ # Cron Jobs
7
+
8
+ ExisJS ships with a distributed Cron Scheduler. Because the cron engine is integrated with the [Job Queue](/docs/queues) system, you can turn any standard background job into a recurring cron task with a single line of code.
9
+
10
+ ---
11
+
12
+ ## 1. Defining a Cron Job
13
+
14
+ To schedule a recurring task, add the `cron` property to any Job definition (`defineJob`) or inline `queue()` handler using standard 5-part cron syntax (`minute hour day month day-of-week`).
15
+
16
+ ### Using `defineJob`
17
+ ```typescript filename="src/jobs/cleanup.ts"
18
+ import { defineJob } from 'exisjs/queue'
19
+
20
+ export default defineJob({
21
+ name: 'database-cleanup',
22
+ // Run this job at exactly 2:00 AM every single day
23
+ cron: '0 2 * * *',
24
+
25
+ async handler(job) {
26
+ console.log("Running daily database cleanup...")
27
+ await db.users.deleteInactive()
28
+ }
29
+ })
30
+ ```
31
+
32
+ ### Using Inline Handlers (`queue`)
33
+ If you prefer functional programming, you can define recurring jobs right alongside your application setup:
34
+
35
+ ```typescript filename="src/http/server.ts"
36
+ import { queue } from 'exisjs/queue'
37
+
38
+ queue('sync-metrics', async () => {
39
+ console.log("Syncing metrics to data warehouse...")
40
+ }, {
41
+ cron: '*/15 * * * *', // Run every 15 minutes
42
+ defaultOptions: { attempts: 3 }
43
+ })
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 2. Distributed Locking (Cluster Safe)
49
+
50
+ In modern web architecture, you rarely run a single NodeJS process. If you run your application across 10 load-balanced servers, a traditional `setInterval` or standard node-cron library would execute your scheduled task 10 times simultaneously.
51
+
52
+ ExisJS solves this natively through **Distributed Locking**.
53
+
54
+ When you configure your Queue system to use the `redis` driver, the ExisJS Cron Scheduler automatically utilizes a `SETNX` (Set if Not Exists) Redis lock.
55
+
56
+ ### How it works:
57
+ 1. Every 60 seconds, all 10 of your servers will attempt to trigger the cron job.
58
+ 2. The *very first* server to hit Redis acquires a 55-second lock for that specific minute.
59
+ 3. The other 9 servers see the lock and silently skip execution.
60
+ 4. The winning server instantly enqueues the job into the standard ExisJS Queue.
61
+
62
+ This guarantees that **your cron job will only execute exactly once per minute**, regardless of how many servers or Docker containers you spin up.
63
+
64
+ ---
65
+
66
+ ## 3. Supported Cron Syntax
67
+
68
+ ExisJS implements a zero-dependency cron parser that supports all standard operations:
69
+
70
+ - `*` - Any value (e.g., run every minute)
71
+ - `,` - Value list (e.g., `1,15,30` to run on the 1st, 15th, and 30th minute)
72
+ - `-` - Value range (e.g., `1-5` to run from Monday to Friday)
73
+ - `/` - Step values (e.g., `*/10` to run every 10 minutes)
74
+
75
+ **Examples:**
76
+ - `* * * * *`: Every minute
77
+ - `*/5 * * * *`: Every 5 minutes
78
+ - `0 0 * * *`: Every day at midnight
79
+ - `0 9 * * 1-5`: At 9:00 AM on Monday through Friday
80
+ - `0 12 1,15 * *`: At 12:00 PM on the 1st and 15th of every month
81
+
82
+ ---
83
+
84
+ ## 4. Important Considerations
85
+
86
+ ### Empty Payloads
87
+ Because cron jobs are triggered by the system clock rather than a user action, they are always dispatched with an empty payload (`{}`). Your job handler should not expect dynamic data to be passed into the `data` property when triggered via cron.
88
+
89
+ ### Retries & Error Handling
90
+ Cron jobs are standard background jobs under the hood. This means they fully support your `defaultOptions`. If a cron job throws an error, it will automatically be retried according to the `attempts` and `backoff` settings defined in the job.
91
+
92
+ ```typescript filename="src/jobs/cleanup.ts"
93
+ export default defineJob({
94
+ name: 'database-cleanup',
95
+ cron: '0 2 * * *',
96
+ defaultOptions: {
97
+ attempts: 3,
98
+ backoff: { type: 'exponential', delay: 5000 }
99
+ },
100
+ async handler(job) {
101
+ // If this fails, it will retry 3 times, backing off exponentially
102
+ }
103
+ })
104
+ ```
105
+
106
+ ### Redis Requirement
107
+ Because ExisJS is designed for cluster-safe, distributed deployments, **the Cron Scheduler strictly requires the `redis` queue driver**.
108
+
109
+ If you attempt to run a cron job while using the `memory` driver, ExisJS will skip execution and log an error. This is a deliberate design choice to prevent you from accidentally triggering scheduled jobs multiple times simultaneously across a load-balanced cluster.
@@ -0,0 +1,209 @@
1
+ ---
2
+ title: "Database Layer"
3
+ description: "ExisJS includes a built-in, high-performance database layer with native support for PostgreSQL, MySQL, SQLite, and MongoDB, plus a powerful query builder and migration system."
4
+ ---
5
+
6
+ # Database Layer
7
+
8
+ Unlike other Node.js frameworks that rely entirely on third-party ORMs like Prisma or TypeORM, **ExisJS ships with a fully integrated, high-performance Database Layer.**
9
+
10
+ This provides a massive advantage in developer experience and reliability, as connections, query generation, and migrations are orchestrated directly by the framework.
11
+
12
+ <Info title="Functional vs OOP">
13
+ ExisJS natively supports both a **Functional Paradigm** and a **Class-Based OOP Paradigm** (for dependency injection and decorators). The database layer is seamlessly accessible through both approaches.
14
+ </Info>
15
+
16
+ ## Setup and Connection
17
+
18
+ You configure your database connections directly in your application entry point (`server.ts`). ExisJS manages their lifecycle (including graceful shutdown) automatically.
19
+
20
+ #### Functional Paradigm
21
+ ```typescript filename="src/http/server.ts"
22
+ import { exis, db, createDatabase } from 'exisjs'
23
+
24
+ export default exis({
25
+ async onStart(app) {
26
+ const database = createDatabase({
27
+ dialect: 'postgres',
28
+ url: process.env.DATABASE_URL,
29
+ })
30
+
31
+ // Connect on boot
32
+ await database.connect()
33
+ }
34
+ })
35
+ ```
36
+
37
+ #### Class-Based (OOP) Paradigm
38
+ ```typescript filename="src/http/server.ts"
39
+ import { Server } from 'exisjs/decorators'
40
+ import { createDatabase } from 'exisjs'
41
+
42
+ @Server()
43
+ export default class RootServer {
44
+ async onStart(app: App) {
45
+ const database = createDatabase({
46
+ dialect: 'postgres',
47
+ url: process.env.DATABASE_URL,
48
+ })
49
+
50
+ // Connect on boot
51
+ await database.connect()
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Using the Query Builder in Routes
57
+
58
+ ExisJS includes a powerful, chainable Query Builder that automatically generates the correct, parameterized SQL for your chosen dialect. Here is how you use it in your route controllers or services.
59
+
60
+ #### Functional Paradigm
61
+ In functional routes, you can easily use the global `db` object to fetch data directly, or use the `resolve` function to inject a dedicated Service that wraps the database calls.
62
+
63
+ ```typescript filename="src/http/users/route.ts"
64
+ import { controller, route } from 'exisjs/router'
65
+ import { db } from 'exisjs'
66
+
67
+ export default controller({
68
+ list: route.get('/', {
69
+ async handle() {
70
+ // Query multiple rows
71
+ const users = await db.table('users')
72
+ .select('id', 'name', 'email')
73
+ .where('role', '=', 'admin')
74
+ .orderBy('created_at', 'desc')
75
+ .limit(10)
76
+ .rows()
77
+
78
+ return users
79
+ }
80
+ }),
81
+
82
+ create: route.post('/', {
83
+ async handle({ req }) {
84
+ const { name, email } = req.body
85
+
86
+ // Insert a new row
87
+ const result = await db.table('users').insert({
88
+ name,
89
+ email,
90
+ role: 'user'
91
+ }).execute()
92
+
93
+ return { id: result.insertId, name, email }
94
+ }
95
+ })
96
+ })
97
+ ```
98
+
99
+ #### Class-Based (OOP) Paradigm
100
+ In the OOP paradigm, it is highly recommended to abstract database calls into `@Injectable()` services and inject them into your `@Controller()`.
101
+
102
+ ```typescript filename="src/http/users/service.ts"
103
+ import { Injectable } from 'exisjs/decorators'
104
+ import { db } from 'exisjs'
105
+
106
+ @Injectable()
107
+ export class UsersService {
108
+ async getAdmins() {
109
+ return await db.table('users')
110
+ .select('id', 'name', 'email')
111
+ .where('role', '=', 'admin')
112
+ .orderBy('created_at', 'desc')
113
+ .limit(10)
114
+ .rows()
115
+ }
116
+
117
+ async createUser(data: { name: string, email: string }) {
118
+ const result = await db.table('users').insert({
119
+ ...data,
120
+ role: 'user'
121
+ }).execute()
122
+
123
+ return { id: result.insertId, ...data }
124
+ }
125
+ }
126
+ ```
127
+
128
+ ```typescript filename="src/http/users/route.ts"
129
+ import { Controller, Get, Post, Body } from 'exisjs/decorators'
130
+ import { UsersService } from './service'
131
+
132
+ @Controller()
133
+ export default class UsersController {
134
+ // ExisJS automatically injects the service!
135
+ constructor(private usersService: UsersService) {}
136
+
137
+ @Get('/')
138
+ async list() {
139
+ return await this.usersService.getAdmins()
140
+ }
141
+
142
+ @Post('/')
143
+ async create(@Body() body: any) {
144
+ return await this.usersService.createUser(body)
145
+ }
146
+ }
147
+ ```
148
+
149
+ ## Transactions
150
+
151
+ ExisJS makes it trivial to perform safe database transactions. If the callback throws an error, the transaction is automatically rolled back.
152
+
153
+ ```typescript
154
+ import { db } from 'exisjs'
155
+
156
+ await db.transaction(async (tx) => {
157
+ // Deduct from inventory
158
+ await tx.execute(
159
+ 'UPDATE inventory SET stock = stock - ? WHERE id = ?',
160
+ [1, itemId]
161
+ )
162
+
163
+ // Create order record
164
+ await tx.execute(
165
+ 'INSERT INTO orders (user_id, item_id) VALUES (?, ?)',
166
+ [userId, itemId]
167
+ )
168
+ })
169
+ ```
170
+
171
+ ## Built-in Migrations
172
+
173
+ Managing database schema changes is critical. ExisJS includes a built-in migration system that tracks applied migrations in your database automatically.
174
+
175
+ ```typescript
176
+ import { db } from 'exisjs'
177
+
178
+ const migrator = db.getMigrator({ directory: './migrations' })
179
+
180
+ // Apply all pending migrations
181
+ await migrator.up()
182
+
183
+ // Rollback the last batch of migrations
184
+ await migrator.down()
185
+
186
+ // Check the status of all migrations
187
+ const status = await migrator.status()
188
+ ```
189
+
190
+ ### Creating a Migration
191
+ A migration file simply exports `up` and `down` methods. You write raw SQL inside them for ultimate control.
192
+
193
+ ```typescript filename="migrations/001_create_users.ts"
194
+ module.exports = {
195
+ name: '001_create_users',
196
+ async up(db) {
197
+ await db.execute(`
198
+ CREATE TABLE users (
199
+ id SERIAL PRIMARY KEY,
200
+ name VARCHAR(255) NOT NULL,
201
+ email VARCHAR(255) UNIQUE NOT NULL
202
+ )
203
+ `)
204
+ },
205
+ async down(db) {
206
+ await db.execute('DROP TABLE users')
207
+ },
208
+ }
209
+ ```