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,541 @@
1
+ ---
2
+ nextUrl: "/docs/controllers"
3
+ nextTitle: "Controllers"
4
+ ---
5
+
6
+ # Routing
7
+
8
+ Routes are responsible for handling incoming requests and sending responses back to the client. The routing mechanism determines which handler will process each request.
9
+
10
+ <Info title="Functional vs OOP">
11
+ ExisJS natively supports both a **Functional Paradigm** and a **Class-Based OOP Paradigm** (for dependency injection and decorators). You can mix and match them throughout your application.
12
+ </Info>
13
+
14
+ ## Folder-Based Routing
15
+
16
+ ExisJS uses **folder-based file-system routing** instead of programmatic route declaration in a single entry file.
17
+
18
+ For a folder to become an accessible API endpoint, it must contain a `route.ts` file that exports a route definition.
19
+
20
+ In addition to `route.ts`, the ExisJS router utilizes two other special gatekeeper files:
21
+ - **`server.ts`**: Placed at the root of your `http` directory, it is the entry point that boots your application, registers global plugins, and connects to databases.
22
+ - **`gateway.ts`**: Can be placed in any folder. It acts as a gatekeeper, automatically applying middleware, CORS, headers, and dependencies to *all* routes within its directory and subdirectories.
23
+
24
+ ### Routing Conventions
25
+
26
+ The ExisJS file-system router natively supports advanced folder naming conventions to handle dynamic segments and logical grouping:
27
+
28
+ - **Dynamic Parameters (`[param]`)**: A folder named `[id]` creates a dynamic route segment (e.g., `/:id`). The value is accessible in your handler via `ctx.params.id`.
29
+ - **Catch-all Routes (`[...param]`)**: A folder named `[...slug]` catches all subsequent URL segments, matching anything that follows (e.g., `/*slug`).
30
+ - **Route Groups (`(group)`)**: A folder wrapped in parentheses (e.g., `(admin)`) is ignored in the final URL path. This is useful for grouping related routes or applying shared controller-level middleware without adding `/admin` to the URL.
31
+
32
+ ### The Application Entry Point (`server.ts`)
33
+
34
+ The `server.ts` file acts as the root configuration for your application. It provides lifecycle hooks to boot databases, register global plugins, and provide root-level dependencies.
35
+
36
+ #### Functional Paradigm
37
+ ```typescript filename="src/http/server.ts"
38
+ import { exis } from 'exisjs'
39
+
40
+ export default exis({
41
+ async onStart(app) {
42
+ // Boot your database here!
43
+ }
44
+ })
45
+ ```
46
+
47
+ #### Class-Based (OOP) Paradigm
48
+ ```typescript filename="src/http/server.ts"
49
+ import { Server } from 'exisjs/decorators'
50
+ import type { App } from 'exisjs'
51
+
52
+ @Server({
53
+ providers: [['LoggerService', { useValue: console }]]
54
+ })
55
+ export default class RootServer {
56
+ async onStart(app: App) {
57
+ // Boot your database here!
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Folder Gateways (`gateway.ts`)
63
+
64
+ Gateways apply configurations to all routes in their directory and subdirectories.
65
+
66
+ #### Functional Paradigm
67
+ ```typescript filename="src/http/admin/gateway.ts"
68
+ import { defineGateway } from 'exisjs/router'
69
+
70
+ export default defineGateway({
71
+ headers: { 'X-Admin-Area': 'true' }
72
+ })
73
+ ```
74
+
75
+ #### Class-Based (OOP) Paradigm
76
+ ```typescript filename="src/http/admin/gateway.ts"
77
+ import { Gateway } from 'exisjs/decorators'
78
+
79
+ @Gateway({
80
+ headers: { 'X-Admin-Area': 'true' }
81
+ })
82
+ export default class AdminGateway {}
83
+ ```
84
+
85
+ ### Route Definitions (`route.ts`)
86
+
87
+ For example, to create a simple `/health` endpoint, you create the folder `src/http/health/` and place a `route.ts` file inside it:
88
+
89
+ #### Functional Paradigm
90
+
91
+
92
+ ```typescript filename="src/http/health/route.ts"
93
+ import { controller, route } from 'exisjs/router'
94
+
95
+ export default controller({
96
+ check: route.get('/', {
97
+ handle() {
98
+ return { status: 'ok', timestamp: new Date().toISOString() }
99
+ }
100
+ })
101
+ })
102
+ ```
103
+
104
+
105
+ #### Class-Based (OOP) Paradigm
106
+
107
+
108
+ ```typescript filename="src/http/health/route.ts"
109
+ import { Controller, Get } from 'exisjs/decorators'
110
+
111
+ @Controller()
112
+ export default class HealthController {
113
+ @Get('/')
114
+ check() {
115
+ return { status: 'ok', timestamp: new Date().toISOString() }
116
+ }
117
+ }
118
+ ```
119
+
120
+
121
+
122
+ ## Supported Methods
123
+
124
+ ExisJS natively exposes standard HTTP methods alongside advanced routing paradigms like WebSockets and Server-Sent Events. Choose your paradigm below to see the available methods.
125
+
126
+ #### Functional Paradigm
127
+
128
+ <div className="overflow-x-auto mb-6">
129
+ <table className="w-full text-sm text-left border border-border/50 rounded-lg overflow-hidden">
130
+ <thead className="bg-muted/50 text-foreground">
131
+ <tr>
132
+ <th className="px-4 py-3 border-b border-border/50 font-semibold w-1/3">Method</th>
133
+ <th className="px-4 py-3 border-b border-border/50 font-semibold">Description</th>
134
+ </tr>
135
+ </thead>
136
+ <tbody className="divide-y divide-border/50">
137
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
138
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>get() / post() / put()</td>
139
+ <td className="px-4 py-3 text-muted-foreground">Standard HTTP methods. Also includes patch, delete, options, head, connect, and trace.</td>
140
+ </tr>
141
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
142
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>all()</td>
143
+ <td className="px-4 py-3 text-muted-foreground">Matches any HTTP request method for the given path.</td>
144
+ </tr>
145
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
146
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>query()</td>
147
+ <td className="px-4 py-3 text-muted-foreground">The new experimental HTTP QUERY method, solving complex idempotent fetches.</td>
148
+ </tr>
149
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
150
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>ws()</td>
151
+ <td className="px-4 py-3 text-muted-foreground">Exposes a native WebSocket endpoint with automatic pub/sub support.</td>
152
+ </tr>
153
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
154
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>sse()</td>
155
+ <td className="px-4 py-3 text-muted-foreground">Exposes a Server-Sent Events stream for one-way realtime data.</td>
156
+ </tr>
157
+ </tbody>
158
+ </table>
159
+ </div>
160
+
161
+ ```typescript filename="src/http/users/route.ts"
162
+ import { controller, route } from 'exisjs/router'
163
+
164
+ export default controller({
165
+ list: route.get('/', {
166
+ handle() {
167
+ // Fetch all users from database
168
+ return [
169
+ { id: 1, name: 'Alice' },
170
+ { id: 2, name: 'Bob' }
171
+ ]
172
+ }
173
+ }),
174
+ create: route.post('/', {
175
+ handle({ req }) {
176
+ // Create a new user with parsed payload
177
+ const payload = req.body
178
+ return { id: 3, ...payload }
179
+ }
180
+ }),
181
+ preflight: route.options('/', {
182
+ handle({ res }) {
183
+ // Handle custom preflight logic if `preflightContinue: true` is configured in CORS
184
+ res.header('X-Custom-Options', 'true')
185
+ return { allowed: true }
186
+ }
187
+ })
188
+ })
189
+ ```
190
+
191
+ #### Class-Based (OOP) Paradigm
192
+
193
+ <div className="overflow-x-auto mb-6">
194
+ <table className="w-full text-sm text-left border border-border/50 rounded-lg overflow-hidden">
195
+ <thead className="bg-muted/50 text-foreground">
196
+ <tr>
197
+ <th className="px-4 py-3 border-b border-border/50 font-semibold w-1/3">Decorator</th>
198
+ <th className="px-4 py-3 border-b border-border/50 font-semibold">Description</th>
199
+ </tr>
200
+ </thead>
201
+ <tbody className="divide-y divide-border/50">
202
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
203
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Get() / <span className="text-primary">@</span>Post() / <span className="text-primary">@</span>Put()</td>
204
+ <td className="px-4 py-3 text-muted-foreground">Standard HTTP methods. Also includes @Patch, @Delete, @Options, @Head, @Connect, and @Trace.</td>
205
+ </tr>
206
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
207
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>All()</td>
208
+ <td className="px-4 py-3 text-muted-foreground">Matches any HTTP request method for the given path.</td>
209
+ </tr>
210
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
211
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Query()</td>
212
+ <td className="px-4 py-3 text-muted-foreground">The new experimental HTTP QUERY method, solving complex idempotent fetches.</td>
213
+ </tr>
214
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
215
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Ws()</td>
216
+ <td className="px-4 py-3 text-muted-foreground">Exposes a native WebSocket endpoint with automatic pub/sub support.</td>
217
+ </tr>
218
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
219
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Sse()</td>
220
+ <td className="px-4 py-3 text-muted-foreground">Exposes a Server-Sent Events stream for one-way realtime data.</td>
221
+ </tr>
222
+ </tbody>
223
+ </table>
224
+ </div>
225
+
226
+ ```typescript filename="src/http/users/route.ts"
227
+ import { Controller, Get, Post, Body } from 'exisjs/decorators'
228
+
229
+ @Controller()
230
+ export default class UsersController {
231
+ @Get('/')
232
+ list() {
233
+ // Fetch all users from database
234
+ return [
235
+ { id: 1, name: 'Alice' },
236
+ { id: 2, name: 'Bob' }
237
+ ]
238
+ }
239
+
240
+ @Post('/')
241
+ create(@Body() data: any) {
242
+ // Create a new user with parsed payload
243
+ return { id: 3, ...data }
244
+ }
245
+ }
246
+ ```
247
+
248
+
249
+ ## Sub-Paths in a Single File
250
+
251
+ While folder-based routing is standard, you can also define multiple sub-paths in a single `route.ts` file to group related endpoints.
252
+
253
+ #### Functional Paradigm
254
+
255
+
256
+ ```typescript filename="src/http/users/route.ts"
257
+ import { controller, route } from 'exisjs/router'
258
+
259
+ export default controller({
260
+ list: route.get('/', {
261
+ handle() {
262
+ return { message: 'Fetching all users' }
263
+ }
264
+ }),
265
+ getSingle: route.get('/:id', {
266
+ handle({ params }) {
267
+ const { id } = params
268
+ return { message: \`Fetching user \${id}\` }
269
+ }
270
+ })
271
+ })
272
+ ```
273
+
274
+
275
+ #### Class-Based (OOP) Paradigm
276
+
277
+
278
+ ```typescript filename="src/http/users/route.ts"
279
+ import { Controller, Get, Param } from 'exisjs/decorators'
280
+
281
+ @Controller()
282
+ export default class UsersController {
283
+ @Get('/')
284
+ list() {
285
+ return { message: 'Fetching all users' }
286
+ }
287
+
288
+ @Get('/:id')
289
+ getSingle(@Param('id') id: string) {
290
+ return { message: \`Fetching user \${id}\` }
291
+ }
292
+ }
293
+ ```
294
+
295
+
296
+
297
+ ## Route Parameters (Dynamic Routing)
298
+
299
+ Routes with static paths won’t work when you need to accept dynamic data as part of the request (e.g., `GET /users/123`). To create dynamic routes using folders, enclose the folder name in brackets `[]`.
300
+
301
+ #### Functional Paradigm
302
+
303
+
304
+ ```typescript filename="src/http/users/[id]/route.ts"
305
+ // Location: src/http/users/[id]/route.ts
306
+ import { controller, route } from 'exisjs/router'
307
+
308
+ export default controller({
309
+ getSingle: route.get('/', {
310
+ handle({ params }) {
311
+ const { id } = params
312
+ return { message: \`Fetching user \${id}\` }
313
+ }
314
+ })
315
+ })
316
+ ```
317
+
318
+
319
+ #### Class-Based (OOP) Paradigm
320
+
321
+
322
+ ```typescript filename="src/http/users/[id]/route.ts"
323
+ // Location: src/http/users/[id]/route.ts
324
+ import { Controller, Get, Param } from 'exisjs/decorators'
325
+
326
+ @Controller()
327
+ export default class SingleUserController {
328
+ @Get('/')
329
+ getSingle(@Param('id') id: string) {
330
+ return { message: \`Fetching user \${id}\` }
331
+ }
332
+ }
333
+ ```
334
+
335
+
336
+
337
+ ## Request & Response Objects
338
+
339
+ Handlers often need access to the client’s request details, or fine-grained control over the response headers and status codes. Every route handler natively receives powerful `req` and `res` objects.
340
+
341
+ #### Functional Paradigm
342
+
343
+
344
+ ```typescript filename="src/http/profile/route.ts"
345
+ import { controller, route } from 'exisjs/router'
346
+
347
+ export default controller({
348
+ profile: route.get('/profile', {
349
+ handle({ req, res }) {
350
+ // Read Request details
351
+ const ip = req.ip
352
+ const headers = req.headers
353
+
354
+ // Chain Response methods
355
+ res
356
+ .status(202)
357
+ .cookie('session', 'xyz123', { httpOnly: true, secure: true })
358
+ .header('X-Custom-Header', 'Exis')
359
+ .json({ message: 'Accepted' })
360
+ }
361
+ })
362
+ })
363
+ ```
364
+
365
+
366
+ #### Class-Based (OOP) Paradigm
367
+
368
+
369
+ ```typescript filename="src/http/profile/route.ts"
370
+ import { Controller, Get, Req, Res } from 'exisjs/decorators'
371
+ import type { Request, Response } from 'exisjs/router'
372
+
373
+ @Controller()
374
+ export default class ProfileController {
375
+ @Get('/profile')
376
+ profile(@Req() req: Request, @Res() res: Response) {
377
+ // Read Request details
378
+ const ip = req.ip
379
+ const headers = req.headers
380
+
381
+ // Chain Response methods
382
+ res
383
+ .status(202)
384
+ .cookie('session', 'xyz123', { httpOnly: true, secure: true })
385
+ .header('X-Custom-Header', 'Exis')
386
+ .json({ message: 'Accepted' })
387
+ }
388
+ }
389
+ ```
390
+
391
+
392
+
393
+ <Info title="Default Responses">
394
+ When a request handler returns a JavaScript object or array, it is automatically serialized to JSON and sent with a default `200 OK` status code. You only need to use the `res` object when you want to explicitly modify headers, cookies, or status codes!
395
+ </Info>
396
+
397
+ ## Payloads & Query Parameters
398
+
399
+ You can easily extract query strings from the URL or parsed JSON bodies from POST requests directly through the `req` object (or via decorators in OOP).
400
+
401
+ #### Functional Paradigm
402
+
403
+
404
+ ```typescript filename="src/http/search/route.ts"
405
+ import { controller, route } from 'exisjs/router'
406
+
407
+ export default controller({
408
+ search: route.get('/search', {
409
+ handle({ req }) {
410
+ // e.g., GET /search?q=exis&limit=10
411
+ const { q, limit } = req.query
412
+ return { query: q, limit }
413
+ }
414
+ }),
415
+ create: route.post('/create', {
416
+ handle({ req }) {
417
+ // Access parsed JSON body natively
418
+ const data = req.body
419
+ return { received: data }
420
+ }
421
+ })
422
+ })
423
+ ```
424
+
425
+
426
+ #### Class-Based (OOP) Paradigm
427
+
428
+
429
+ ```typescript filename="src/http/search/route.ts"
430
+ import { Controller, Get, Post, Query, Body } from 'exisjs/decorators'
431
+
432
+ @Controller()
433
+ export default class SearchController {
434
+ @Get('/search')
435
+ search(@Query('q') q: string, @Query('limit') limit: number) {
436
+ // e.g., GET /search?q=exis&limit=10
437
+ return { query: q, limit }
438
+ }
439
+
440
+ @Post('/create')
441
+ create(@Body() data: any) {
442
+ // Access parsed JSON body natively
443
+ return { received: data }
444
+ }
445
+ }
446
+ ```
447
+
448
+
449
+
450
+ ## Dependency Injection in Functional Controllers
451
+
452
+ ExisJS natively supports **Request-Scoped Dependency Injection** directly inside functional routes. You don't need to use classes to get the benefits of DI!
453
+
454
+ By destructuring `resolve` from the context, you can dynamically fetch services without relying on global imports. This perfectly solves the "Fat Handler" problem by encouraging you to abstract business logic into clean Service classes, and makes unit testing incredibly easy.
455
+
456
+ ```typescript filename="src/http/users/route.ts"
457
+ import { controller, route } from 'exisjs/router'
458
+ import { UsersService } from './service'
459
+
460
+ export default controller({
461
+ list: route.get('/', {
462
+ async handle({ resolve }) {
463
+ // 1. Resolves UsersService from the IoC Container
464
+ // 2. Maintains request-scope (the same instance is returned throughout this HTTP request)
465
+ const usersService = resolve(UsersService)
466
+
467
+ return usersService.getAll()
468
+ }
469
+ })
470
+ })
471
+ ```
472
+
473
+ ## The Context API (Advanced)
474
+
475
+ For applications where passing `(req, res)` arguments through multiple service layers becomes tedious, ExisJS offers a Next.js-inspired approach: **The Context API**. Powered by Node.js `AsyncLocalStorage`, ExisJS provides hooks to access the current request state from anywhere in the execution context.
476
+
477
+ #### Functional Paradigm
478
+
479
+
480
+ ```typescript filename="src/http/users/route.ts"
481
+ import { controller, route, getContext, getRequest, getResponse, after } from 'exisjs/router'
482
+
483
+ export default controller({
484
+ create: route.post('/', {
485
+ async handle() {
486
+ // Access request/response without prop-drilling
487
+ const req = getRequest()
488
+ const res = getResponse()
489
+
490
+ // Access data injected by gateways
491
+ const { userId } = getContext<{ userId: string }>()
492
+
493
+ // Run background tasks after sending response to client
494
+ after(async () => {
495
+ await sendNotificationEmail(userId)
496
+ })
497
+
498
+ res.status(201)
499
+ return { success: true }
500
+ }
501
+ })
502
+ })
503
+ ```
504
+
505
+
506
+ #### Class-Based (OOP) Paradigm
507
+
508
+
509
+ ```typescript filename="src/http/users/route.ts"
510
+ import { Controller, Post } from 'exisjs/decorators'
511
+ import { getContext, getRequest, getResponse, after } from 'exisjs/router'
512
+
513
+ @Controller()
514
+ export default class OrdersController {
515
+ @Post('/')
516
+ async create() {
517
+ // You can still use the Context API seamlessly in OOP classes!
518
+ const req = getRequest()
519
+ const res = getResponse()
520
+ const { userId } = getContext<{ userId: string }>()
521
+
522
+ after(async () => {
523
+ await sendNotificationEmail(userId)
524
+ })
525
+
526
+ res.status(201)
527
+ return { success: true }
528
+ }
529
+ }
530
+ ```
531
+
532
+
533
+
534
+ ## Modular Architecture
535
+
536
+ While putting all logic in `route.ts` is fine for small apps, ExisJS strongly encourages separating concerns alongside your route definition:
537
+
538
+ - **`schema.ts`**: Defines expected input validation constraints.
539
+ - **`service.ts`**: Pure functions that interact with your database.
540
+ - **`controller.ts`**: Handles business orchestration.
541
+ - **`route.ts`**: Wires it to the HTTP network.
@@ -0,0 +1,146 @@
1
+ ---
2
+ nextUrl: "/docs/decorators"
3
+ nextTitle: "Custom Decorators"
4
+ ---
5
+
6
+ # RPC Client (End-to-End Type Safety)
7
+
8
+ ExisJS comes with a native RPC (Remote Procedure Call) client designed to give you **end-to-end type safety** without needing code generation (like OpenAPI/Swagger) or GraphQL.
9
+
10
+ Because ExisJS infers the types of your backend routes natively, you can share those types directly with your frontend (React, Vue, React Native) and get autocomplete, payload validation, and response typing.
11
+
12
+ ---
13
+
14
+ ## 1. Exporting the AppRouter Type
15
+
16
+ To use the strictly typed client, you simply need to export the inferred type of your ExisJS application from your main backend entry point.
17
+
18
+ ```typescript filename="backend/src/http/server.ts"
19
+ import { exis } from 'exisjs'
20
+
21
+ const app = exis({
22
+ async onStart(app) {
23
+ // Backend boot logic...
24
+ }
25
+ })
26
+
27
+ // Export the inferred router type for the frontend
28
+ export type AppRouter = typeof app
29
+
30
+ export default app
31
+ ```
32
+
33
+ *(Note: Because this is just a TypeScript `type` export, no backend code gets compiled into your frontend bundle.)*
34
+
35
+ ---
36
+
37
+ ## 2. Initializing the Client
38
+
39
+ On your frontend (React, Vue, Svelte, etc.), install the lightweight client package:
40
+
41
+ ```bash
42
+ npm install @exisjs/client
43
+ ```
44
+
45
+ Then, initialize the client using the `AppRouter` type from your backend:
46
+
47
+ ```typescript filename="frontend/src/api.ts"
48
+ import { createClient } from '@exisjs/client'
49
+ import type { AppRouter } from '../../backend/src/http/server'
50
+
51
+ export const client = createClient<AppRouter>({
52
+ baseUrl: 'http://localhost:3000',
53
+
54
+ // Optionally provide default headers (like Auth tokens)
55
+ headers: () => ({
56
+ 'Authorization': `Bearer ${localStorage.getItem('token')}`
57
+ }),
58
+
59
+ // Global error handler
60
+ onError: (err) => {
61
+ if (err.status === 401) {
62
+ window.location.href = '/login'
63
+ }
64
+ }
65
+ })
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 3. Using the Client
71
+
72
+ Because ExisJS uses an ES6 `Proxy` under the hood, the client dynamically maps your chained object calls into HTTP requests. It mirrors your backend's folder structure.
73
+
74
+ If you have a backend route at `src/http/api/users/route.ts` with a `get` handler:
75
+
76
+ ```typescript filename="frontend/src/components/UserProfile.tsx"
77
+ import { client } from '../api'
78
+
79
+ async function fetchUser() {
80
+ // 1. Autocomplete for .api.users.get
81
+ // 2. Strong typing for the payload (e.g. { id: string })
82
+ // 3. The `user` variable is strictly typed to whatever the backend returns
83
+ const user = await client.api.users.get({
84
+ id: "123"
85
+ })
86
+
87
+ console.log(user.name)
88
+ }
89
+ ```
90
+
91
+ ### Passing Query Parameters
92
+
93
+ If you need to pass HTTP query parameters (like `?sort=desc`), you can use the `options` argument (the second parameter):
94
+
95
+ ```typescript filename="frontend/src/components/UserList.tsx"
96
+ const users = await client.api.users.get(
97
+ // The first argument is the JSON payload (Body/Params)
98
+ {},
99
+ // The second argument configures the Request Options
100
+ {
101
+ query: { sort: 'desc', limit: 10 },
102
+ headers: { 'X-Custom-Override': 'true' }
103
+ }
104
+ )
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 4. Advanced Configuration
110
+
111
+ The `createClient` function accepts configuration options to intercept requests and responses globally.
112
+
113
+ ```typescript
114
+ export interface ClientConfig {
115
+ // The base URL of your ExisJS backend
116
+ baseUrl: string
117
+
118
+ // Static headers, or a dynamic function evaluated per-request
119
+ headers?: Record<string, string> | (() => Record<string, string>)
120
+
121
+ // Provide a custom fetch implementation (useful for testing or Polyfills)
122
+ fetch?: typeof fetch
123
+
124
+ // A global hook that runs BEFORE the request is dispatched
125
+ onRequest?: (req: RequestInit, url: string) => void | Promise<void>
126
+
127
+ // A global hook that runs AFTER the request successfully completes
128
+ onResponse?: (res: Response, url: string) => void | Promise<void>
129
+
130
+ // A global hook that catches any failed requests
131
+ onError?: (err: Error) => void | Promise<void>
132
+ }
133
+ ```
134
+
135
+ ### ExisClientError
136
+
137
+ If an HTTP request fails (e.g., the backend returns a `400 Bad Request` or `500 Internal Server Error`), the client throws an `ExisClientError`. This error attaches the status code and parsed JSON response.
138
+
139
+ ```typescript
140
+ try {
141
+ await client.api.users.post({ email: "invalid-email" })
142
+ } catch (err) {
143
+ console.log(err.status) // 400
144
+ console.log(err.data) // { success: false, error: "Validation Failed..." }
145
+ }
146
+ ```