exisjs 0.5.10 → 0.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/auth/index.d.ts +3 -1
  2. package/dist/auth/index.d.ts.map +1 -1
  3. package/dist/auth/index.js +4 -1
  4. package/dist/auth/index.js.map +1 -1
  5. package/dist/auth/oauth/core.d.ts +96 -0
  6. package/dist/auth/oauth/core.d.ts.map +1 -0
  7. package/dist/auth/oauth/core.js +203 -0
  8. package/dist/auth/oauth/core.js.map +1 -0
  9. package/dist/auth/oauth/index.d.ts +45 -0
  10. package/dist/auth/oauth/index.d.ts.map +1 -0
  11. package/dist/auth/oauth/index.js +64 -0
  12. package/dist/auth/oauth/index.js.map +1 -0
  13. package/dist/auth/oauth/middleware.d.ts +63 -0
  14. package/dist/auth/oauth/middleware.d.ts.map +1 -0
  15. package/dist/auth/oauth/middleware.js +164 -0
  16. package/dist/auth/oauth/middleware.js.map +1 -0
  17. package/dist/auth/oauth/providers/custom.d.ts +41 -0
  18. package/dist/auth/oauth/providers/custom.d.ts.map +1 -0
  19. package/dist/auth/oauth/providers/custom.js +204 -0
  20. package/dist/auth/oauth/providers/custom.js.map +1 -0
  21. package/dist/auth/oauth/providers/discord.d.ts +23 -0
  22. package/dist/auth/oauth/providers/discord.d.ts.map +1 -0
  23. package/dist/auth/oauth/providers/discord.js +130 -0
  24. package/dist/auth/oauth/providers/discord.js.map +1 -0
  25. package/dist/auth/oauth/providers/facebook.d.ts +28 -0
  26. package/dist/auth/oauth/providers/facebook.d.ts.map +1 -0
  27. package/dist/auth/oauth/providers/facebook.js +124 -0
  28. package/dist/auth/oauth/providers/facebook.js.map +1 -0
  29. package/dist/auth/oauth/providers/github.d.ts +25 -0
  30. package/dist/auth/oauth/providers/github.d.ts.map +1 -0
  31. package/dist/auth/oauth/providers/github.js +132 -0
  32. package/dist/auth/oauth/providers/github.js.map +1 -0
  33. package/dist/auth/oauth/providers/google.d.ts +23 -0
  34. package/dist/auth/oauth/providers/google.d.ts.map +1 -0
  35. package/dist/auth/oauth/providers/google.js +134 -0
  36. package/dist/auth/oauth/providers/google.js.map +1 -0
  37. package/dist/auth/oauth/providers/microsoft.d.ts +28 -0
  38. package/dist/auth/oauth/providers/microsoft.d.ts.map +1 -0
  39. package/dist/auth/oauth/providers/microsoft.js +136 -0
  40. package/dist/auth/oauth/providers/microsoft.js.map +1 -0
  41. package/dist/auth/oauth/types.d.ts +158 -0
  42. package/dist/auth/oauth/types.d.ts.map +1 -0
  43. package/dist/auth/oauth/types.js +4 -0
  44. package/dist/auth/oauth/types.js.map +1 -0
  45. package/dist/cli/commands/dev.d.ts.map +1 -1
  46. package/dist/cli/commands/dev.js +0 -1
  47. package/dist/cli/commands/dev.js.map +1 -1
  48. package/dist/database/adapters/mongodb.d.ts +58 -0
  49. package/dist/database/adapters/mongodb.d.ts.map +1 -0
  50. package/dist/database/adapters/mongodb.js +264 -0
  51. package/dist/database/adapters/mongodb.js.map +1 -0
  52. package/dist/database/adapters/mysql.d.ts +27 -0
  53. package/dist/database/adapters/mysql.d.ts.map +1 -0
  54. package/dist/database/adapters/mysql.js +188 -0
  55. package/dist/database/adapters/mysql.js.map +1 -0
  56. package/dist/database/adapters/postgres.d.ts +28 -0
  57. package/dist/database/adapters/postgres.d.ts.map +1 -0
  58. package/dist/database/adapters/postgres.js +174 -0
  59. package/dist/database/adapters/postgres.js.map +1 -0
  60. package/dist/database/adapters/sqlite.d.ts +27 -0
  61. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  62. package/dist/database/adapters/sqlite.js +149 -0
  63. package/dist/database/adapters/sqlite.js.map +1 -0
  64. package/dist/database/index.d.ts +42 -0
  65. package/dist/database/index.d.ts.map +1 -0
  66. package/dist/database/index.js +58 -0
  67. package/dist/database/index.js.map +1 -0
  68. package/dist/database/manager.d.ts +114 -0
  69. package/dist/database/manager.d.ts.map +1 -0
  70. package/dist/database/manager.js +269 -0
  71. package/dist/database/manager.js.map +1 -0
  72. package/dist/database/migration.d.ts +57 -0
  73. package/dist/database/migration.d.ts.map +1 -0
  74. package/dist/database/migration.js +329 -0
  75. package/dist/database/migration.js.map +1 -0
  76. package/dist/database/query-builder.d.ts +89 -0
  77. package/dist/database/query-builder.d.ts.map +1 -0
  78. package/dist/database/query-builder.js +353 -0
  79. package/dist/database/query-builder.js.map +1 -0
  80. package/dist/database/types.d.ts +184 -0
  81. package/dist/database/types.d.ts.map +1 -0
  82. package/dist/database/types.js +4 -0
  83. package/dist/database/types.js.map +1 -0
  84. package/dist/docs/ARCHITECTURE.md +349 -0
  85. package/dist/docs/BENCHMARKS.md +36 -0
  86. package/dist/docs/README.md +34 -0
  87. package/dist/docs/content/README.md +44 -0
  88. package/dist/docs/content/adapters.mdx +199 -0
  89. package/dist/docs/content/cache.mdx +239 -0
  90. package/dist/docs/content/circuit-breaker.mdx +130 -0
  91. package/dist/docs/content/cli/commands.mdx +214 -0
  92. package/dist/docs/content/cli/overview.mdx +92 -0
  93. package/dist/docs/content/cli/usage.mdx +61 -0
  94. package/dist/docs/content/config.mdx +214 -0
  95. package/dist/docs/content/controllers.mdx +241 -0
  96. package/dist/docs/content/cron.mdx +109 -0
  97. package/dist/docs/content/database.mdx +209 -0
  98. package/dist/docs/content/dataloader.mdx +170 -0
  99. package/dist/docs/content/decorators.mdx +204 -0
  100. package/dist/docs/content/errors.mdx +344 -0
  101. package/dist/docs/content/exisjs-mcp.mdx +97 -0
  102. package/dist/docs/content/gateways.mdx +376 -0
  103. package/dist/docs/content/installation.mdx +210 -0
  104. package/dist/docs/content/introduction.mdx +96 -0
  105. package/dist/docs/content/middleware.mdx +441 -0
  106. package/dist/docs/content/modules.mdx +144 -0
  107. package/dist/docs/content/oauth.mdx +179 -0
  108. package/dist/docs/content/providers.mdx +147 -0
  109. package/dist/docs/content/queues.mdx +177 -0
  110. package/dist/docs/content/requests.mdx +293 -0
  111. package/dist/docs/content/responses.mdx +271 -0
  112. package/dist/docs/content/routing.mdx +541 -0
  113. package/dist/docs/content/rpc.mdx +146 -0
  114. package/dist/docs/content/security.mdx +294 -0
  115. package/dist/docs/content/sse.mdx +159 -0
  116. package/dist/docs/content/structure.mdx +116 -0
  117. package/dist/docs/content/testing.mdx +139 -0
  118. package/dist/docs/content/validation.mdx +249 -0
  119. package/dist/docs/content/websockets.mdx +289 -0
  120. package/dist/docs/integrations/README.md +23 -0
  121. package/dist/docs/integrations/jwt.mdx +151 -0
  122. package/dist/docs/integrations/mongo.mdx +183 -0
  123. package/dist/docs/integrations/observability.mdx +125 -0
  124. package/dist/docs/integrations/openai.mdx +108 -0
  125. package/dist/docs/integrations/postgres.mdx +190 -0
  126. package/dist/docs/integrations/posthog.mdx +122 -0
  127. package/dist/docs/integrations/redis.mdx +141 -0
  128. package/dist/docs/integrations/resend.mdx +117 -0
  129. package/dist/docs/integrations/s3.mdx +122 -0
  130. package/dist/docs/integrations/swagger.mdx +113 -0
  131. package/dist/docs/reference/README.md +3 -0
  132. package/dist/docs/reference/adapters.mdx +130 -0
  133. package/dist/docs/reference/auth.mdx +183 -0
  134. package/dist/docs/reference/cache.mdx +132 -0
  135. package/dist/docs/reference/circuit-breaker.mdx +71 -0
  136. package/dist/docs/reference/config.mdx +118 -0
  137. package/dist/docs/reference/context.mdx +168 -0
  138. package/dist/docs/reference/dataloader.mdx +91 -0
  139. package/dist/docs/reference/decorators.mdx +293 -0
  140. package/dist/docs/reference/di.mdx +123 -0
  141. package/dist/docs/reference/error.mdx +170 -0
  142. package/dist/docs/reference/exis.mdx +124 -0
  143. package/dist/docs/reference/middleware.mdx +304 -0
  144. package/dist/docs/reference/plugin.mdx +113 -0
  145. package/dist/docs/reference/queue.mdx +155 -0
  146. package/dist/docs/reference/router.mdx +248 -0
  147. package/dist/docs/reference/sanitize.mdx +130 -0
  148. package/dist/docs/reference/storage.mdx +116 -0
  149. package/dist/docs/reference/swagger.mdx +132 -0
  150. package/dist/docs/reference/testing.mdx +145 -0
  151. package/dist/docs/reference/validator.mdx +186 -0
  152. package/dist/index.d.ts +4 -0
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +10 -1
  155. package/dist/index.js.map +1 -1
  156. package/dist/middleware/compression.d.ts +12 -0
  157. package/dist/middleware/compression.d.ts.map +1 -1
  158. package/dist/middleware/compression.js +61 -99
  159. package/dist/middleware/compression.js.map +1 -1
  160. package/dist/middleware/middleware.d.ts.map +1 -1
  161. package/dist/middleware/middleware.js +25 -8
  162. package/dist/middleware/middleware.js.map +1 -1
  163. package/dist/middleware/security.d.ts.map +1 -1
  164. package/dist/middleware/security.js +6 -3
  165. package/dist/middleware/security.js.map +1 -1
  166. package/dist/router/router.d.ts.map +1 -1
  167. package/dist/router/router.js +11 -7
  168. package/dist/router/router.js.map +1 -1
  169. package/dist/server/bootstrapper.js +1 -1
  170. package/dist/server/bootstrapper.js.map +1 -1
  171. package/dist/server/bun-adapter.d.ts.map +1 -1
  172. package/dist/server/bun-adapter.js +2 -2
  173. package/dist/server/bun-adapter.js.map +1 -1
  174. package/dist/server/request-handler.d.ts +7 -0
  175. package/dist/server/request-handler.d.ts.map +1 -1
  176. package/dist/server/request-handler.js +37 -2
  177. package/dist/server/request-handler.js.map +1 -1
  178. package/dist/server/request.d.ts.map +1 -1
  179. package/dist/server/request.js +4 -6
  180. package/dist/server/request.js.map +1 -1
  181. package/dist/server/route-scanner.d.ts.map +1 -1
  182. package/dist/server/route-scanner.js +4 -1
  183. package/dist/server/route-scanner.js.map +1 -1
  184. package/dist/server/uws-adapter.d.ts +5 -3
  185. package/dist/server/uws-adapter.d.ts.map +1 -1
  186. package/dist/server/uws-adapter.js +39 -4
  187. package/dist/server/uws-adapter.js.map +1 -1
  188. package/dist/server/ws-orchestrator.d.ts.map +1 -1
  189. package/dist/server/ws-orchestrator.js +4 -13
  190. package/dist/server/ws-orchestrator.js.map +1 -1
  191. package/package.json +17 -10
@@ -0,0 +1,248 @@
1
+ ---
2
+ title: "Router & Controllers"
3
+ description: "API Reference for defining routes and controllers in ExisJS."
4
+ ---
5
+
6
+ # Router & Controllers
7
+
8
+ ExisJS provides two distinct ways to build your API: **Functional Routing** (using `route` and `controller`) and **Class-Based Routing** (using standard decorators). You can use either paradigm, or even mix both in the same application.
9
+
10
+ ---
11
+
12
+ ## 1. Functional Routing
13
+
14
+ The functional router provides a lightweight, type-safe API via the `route` and `controller` exports from `exisjs/router`.
15
+
16
+ These utilities allow you to define HTTP endpoints with built-in validation schemas, automatically typed execution contexts, and file-level configuration.
17
+
18
+ ### `controller()`
19
+
20
+ The `controller` function wraps a collection of routes and configuration options (like middleware or CORS) inside a single file.
21
+
22
+ ### Usage
23
+
24
+ ```typescript
25
+ import { controller, route } from 'exisjs/router'
26
+ import { authMiddleware } from '../../middleware/auth'
27
+
28
+ export default controller({
29
+ // Global config applied to all routes in this controller
30
+ cors: true,
31
+ middleware: [authMiddleware],
32
+
33
+ // Your individual routes
34
+ getUser: route.get('/user', {
35
+ async handle({ req }) {
36
+ return { user: req.user }
37
+ }
38
+ })
39
+ })
40
+ ```
41
+
42
+ ### Options (`ControllerConfig`)
43
+ - `cors` (`boolean | CorsConfig`): Enables or configures CORS for all routes in this file.
44
+ - `middleware` (`Handler[]`): Array of middleware functions applied to all routes in this file.
45
+ - `filters` (`any | any[]`): Route filters applied to all routes.
46
+ - `onError` (`HookError`): A file-level error handler.
47
+ - `onResponse` (`HookResponse`): A file-level response hook.
48
+ - `[key: string]` (`RouteDefinition`): **Any other key** is treated as a route definition (e.g. `getUser: route.get(...)`). This is how you bind routes to the controller!
49
+
50
+ ---
51
+
52
+ ## `route`
53
+
54
+ The `route` object provides factory functions for every HTTP method (e.g., `route.get`, `route.post`, `route.patch`, `route.delete`, `route.ws`, `route.sse`).
55
+
56
+ It takes a path and a `RouteConfig` object.
57
+
58
+ ### Usage
59
+
60
+ ```typescript
61
+ import { controller, route } from 'exisjs/router'
62
+ import { tex } from 'exisjs/validator'
63
+
64
+ export default controller({
65
+ createUser: route.post('/users', {
66
+ body: tex.object({
67
+ email: tex.email(),
68
+ password: tex.string({ min: 8 })
69
+ }),
70
+ async handle(ctx) {
71
+ // ctx.body is strictly typed!
72
+ const { email, password } = ctx.body
73
+ return { success: true, email }
74
+ }
75
+ })
76
+ })
77
+ ```
78
+
79
+ ### Options (`RouteConfig`)
80
+
81
+ - `body`: A validation schema for the request body (e.g., using `v` or Zod).
82
+ - `query`: A validation schema for the query string.
83
+ - `params`: A validation schema for the URL path parameters.
84
+ - `response`: A validation schema to serialize/validate the outgoing response.
85
+ - `middleware` (`Handler[]`): An array of middleware specific to this route.
86
+ - `cors` (`boolean | CorsConfig`): Route-specific CORS overrides.
87
+ - `filters` (`any | any[]`): Route-specific filters (like rate limiters or authorization checks).
88
+ - `permissions` (`string[]`): An array of required permission flags to access this route.
89
+ - `metadata` (`Record<string, any>`): Arbitrary metadata attached to the route for custom plugins/middleware.
90
+ - `host` (`string | string[]`): Restrict this route to a specific subdomain/host (e.g. `api.example.com`).
91
+ - `handle`: The async function that executes your business logic. It receives a `SuperContext`.
92
+
93
+ ---
94
+
95
+ ## `SuperContext`
96
+
97
+ Every `handle` function receives a `SuperContext` object that provides fully-typed access to the request and application environment.
98
+
99
+ ```typescript
100
+ import { controller, route } from '../router' // Import your custom router (see below)
101
+ import { tex } from 'exisjs/validator'
102
+
103
+ export default controller({
104
+ getUserInfo: route.get('/info/:id', {
105
+ params: tex.object({ id: tex.string() }),
106
+ query: tex.object({ debug: tex.boolean({ optional: true }) }),
107
+ async handle({ body, query, params, headers, req, res, app, state, user, workspace }) {
108
+ // 1. `body`, `query`, and `params` are pre-validated and fully typed!
109
+ const id = params.id
110
+
111
+ // 2. `req` and `res` are the raw ExisJS request/response objects
112
+ const ip = req.ip
113
+
114
+ // 3. `app` is the global ExisJS application instance
115
+ const db = app.resolve('Database')
116
+
117
+ // 4. `resolve` is the request-scoped dependency injection resolver!
118
+ const usersService = resolve(UsersService)
119
+
120
+ // 5. `state` is a mutable object shared across middlewares
121
+ const cacheHit = state.cacheHit
122
+
123
+ // 6. `user` and `workspace` are strictly typed from your custom context!
124
+ return { id, email: user.email, workspace }
125
+ }
126
+ })
127
+ })
128
+ ```
129
+
130
+ ---
131
+
132
+ ## `createRouter<T>()`
133
+
134
+ If your application relies heavily on custom context (e.g., passing a `user` or `workspace` object injected via middleware to every route), you can create a strongly-typed custom router using `createRouter<T>()`.
135
+
136
+ This removes the need to constantly cast or redefine types.
137
+
138
+ ### Usage
139
+
140
+ ```typescript filename="src/router.ts"
141
+ import { createRouter } from "exisjs/router";
142
+
143
+ interface User {
144
+ userId: string;
145
+ role: string;
146
+ email: string;
147
+ }
148
+
149
+ interface AppContext {
150
+ user: User;
151
+ workspace: string; // Just an example to prove we can pass any property
152
+ }
153
+
154
+ // Create a completely customized router scoped to this application
155
+ export const { route, controller } = createRouter<AppContext>();
156
+ ```
157
+
158
+ Now, when you use this custom `route` object, the `SuperContext` will automatically infer your custom context fields!
159
+
160
+ ```typescript filename="src/http/users/route.ts"
161
+ import { route, controller } from '../../router'
162
+
163
+ export default controller({
164
+ me: route.get('/me', {
165
+ async handle({ user, workspace }) {
166
+ // `user` and `workspace` are strictly typed!
167
+ return { id: user.userId, workspace }
168
+ }
169
+ })
170
+ })
171
+ ```
172
+
173
+ ---
174
+
175
+ ## 2. Class-Based Routing (OOP)
176
+
177
+ If you prefer an Object-Oriented paradigm, ExisJS provides a robust set of decorators via `exisjs/decorators`. These decorators are built on standard ECMAScript decorators and do not require `reflect-metadata`, making them incredibly fast and lightweight.
178
+
179
+ ### Usage
180
+
181
+ ```typescript filename="src/http/users/route.ts"
182
+ import { Controller, Get, Post, Body, Param } from 'exisjs/decorators'
183
+ import { tex } from 'exisjs/validator'
184
+
185
+ @Controller('/users')
186
+ export default class UserController {
187
+
188
+ @Get('/:id')
189
+ async getUser(@Param('id') id: string) {
190
+ return { id, name: 'Alice' }
191
+ }
192
+
193
+ @Post('/', {
194
+ body: tex.object({ email: tex.email(), password: tex.string() })
195
+ })
196
+ async createUser(@Body() body: any) {
197
+ // body is automatically validated!
198
+ return { success: true, email: body.email }
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Route Decorators
204
+
205
+ - `@Get(path, schema?)`
206
+ - `@Post(path, schema?)`
207
+ - `@Put(path, schema?)`
208
+ - `@Patch(path, schema?)`
209
+ - `@Delete(path, schema?)`
210
+ - `@Ws(path, schema?)`: Defines a WebSocket route.
211
+ - `@Sse(path, schema?)`: Defines a Server-Sent Events stream route.
212
+
213
+ *Note: Just like functional routes, you can pass an optional `RouteSchema` to any of these decorators to automatically validate requests!*
214
+
215
+ ### Parameter Injectors
216
+
217
+ Use parameter decorators to automatically extract data from the incoming request and inject them into your method arguments:
218
+
219
+ - `@Body()`: Injects the parsed JSON request body.
220
+ - `@Param('id')`: Injects a specific URL path parameter.
221
+ - `@Query('search')`: Injects a specific query string parameter.
222
+ - `@Headers('authorization')`: Injects a specific HTTP header.
223
+ - `@Req()`: Injects the raw ExisJS Request object.
224
+ - `@Res()`: Injects the raw ExisJS Response object.
225
+ - `@Next()`: Injects the next function.
226
+ - `@Socket()`: Injects the active WebSocket instance (for `@Ws` routes).
227
+ - `@Stream()`: Injects the active SSE instance (for `@Sse` routes).
228
+
229
+ ### Advanced Decorators
230
+
231
+ ExisJS provides a rich ecosystem of decorators to apply middleware, configure responses, and enforce security on both individual routes and entire controllers:
232
+
233
+ #### Configuration & Middleware
234
+ - `@Use(...middlewares)`: Applies Express-style middleware to a route or entire controller.
235
+ - `@Hosts('api.example.com')`: Restricts the route or controller to specific domains.
236
+
237
+ #### Security & Lifecycle
238
+ - `@Permissions('admin')`: Requires specific permission flags.
239
+ - `@UseGuards(...guards)`: Attaches Guards (like auth checks) to a route/controller.
240
+ - `@UseInterceptors(...interceptors)`: Attaches Interceptors to mutate requests/responses.
241
+ - `@UseFilters(...filters)`: Attaches Exception Filters to handle specific errors.
242
+
243
+ #### Responses & Caching
244
+ - `@HttpCode(201)`: Forces a specific HTTP status code for successful responses.
245
+ - `@Header('X-Custom', 'Value')`: Automatically injects a specific response header.
246
+ - `@Returns(schema)`: Explicitly defines the response schema for OpenAPI generation.
247
+ - `@Cache({ ttlMs: 60000 })`: Automatically caches GET responses.
248
+ - `@Idempotent()`: Enforces idempotency keys for mutations (e.g., checkout/payments).
@@ -0,0 +1,130 @@
1
+ ---
2
+ title: "Sanitization"
3
+ description: "API Reference for ExisJS built-in Sanitization Engine."
4
+ ---
5
+
6
+ # Sanitization (`sanitize`)
7
+
8
+ The `exisjs/sanitize` module provides a comprehensive suite of pure, dependency-free functions for transforming and cleaning data **before** it hits your business logic or database.
9
+
10
+ Sanitizers are designed to be perfectly composable with the [ExisJS Validator](/docs/reference/validator) via the `.sanitize()` modifier, meaning you can strip HTML, trim whitespace, and enforce limits *while* validating request payloads.
11
+
12
+ ---
13
+
14
+ ## Core Sanitizers
15
+
16
+ The `sanitize` object exposes pure functions grouped by their target data type.
17
+
18
+ ### String Sanitizers
19
+
20
+ - `sanitize.trim`: Trims leading and trailing whitespace.
21
+ - `sanitize.toLowerCase`: Converts the string to lower case.
22
+ - `sanitize.toUpperCase`: Converts the string to upper case.
23
+ - `sanitize.collapseWhitespace`: Replaces multiple consecutive spaces, tabs, or newlines with a single space.
24
+ - `sanitize.stripHtml`: Removes all HTML tags from the string.
25
+ - `sanitize.escapeHtml`: Escapes dangerous characters (`&`, `<`, `>`, `"`, `'`) into HTML entities.
26
+ - `sanitize.normalizeUnicode`: Normalizes unicode characters to their canonical form (NFC).
27
+ - `sanitize.slugify`: Converts a string into a URL-friendly slug (e.g., `Hello World!` -> `hello-world`).
28
+ - `sanitize.truncate(length)`: Hard-truncates a string to a specific maximum length.
29
+ - `sanitize.removeNonAlphanumeric`: Removes all characters except letters and numbers.
30
+ - `sanitize.normalizeLineEndings`: Converts Windows line endings (`\r\n`) to Unix line endings (`\n`).
31
+
32
+ ### Number Sanitizers
33
+
34
+ - `sanitize.round`: Rounds the number to the nearest integer.
35
+ - `sanitize.clamp(min, max)`: Restricts a number to fall within a specific range.
36
+ - `sanitize.defaultIfNaN(fallback)`: Returns the fallback value if the provided number is `NaN`.
37
+
38
+ ### Date Sanitizers
39
+
40
+ - `sanitize.normalizeToIsoString`: Converts a Date object into a standard ISO 8601 string.
41
+ - `sanitize.stripTime`: Resets the time portion of a Date object to `00:00:00:000` (midnight local time).
42
+
43
+ ### Array Sanitizers
44
+
45
+ - `sanitize.dedupe`: Removes duplicate elements from the array.
46
+ - `sanitize.compact`: Removes `null`, `undefined`, and empty strings (`''`) from the array.
47
+ - `sanitize.trimElements`: Trims whitespace from all string elements inside the array.
48
+ - `sanitize.limitLength(length)`: Slices the array down to a maximum length.
49
+
50
+ ### Object Sanitizers
51
+
52
+ - `sanitize.stripUnknownKeys(['allowedA', 'allowedB'])`: Strips out any keys from the object that are not explicitly allowed.
53
+ - `sanitize.deepTrimStringValues`: Recursively walks through an object (and nested arrays/objects) and trims all string values found.
54
+ - `sanitize.omit(['secret'])`: Removes specific keys from the object.
55
+ - `sanitize.pick(['publicField'])`: Returns a new object containing only the specified keys.
56
+
57
+ ---
58
+
59
+ ## Using with Validation
60
+
61
+ The most powerful way to use ExisJS sanitizers is to chain them directly into your schemas using the `.sanitize()` modifier.
62
+
63
+ **Note that sanitization runs *before* final validation checks (like `.email()` or `.min()`).**
64
+
65
+ ```typescript filename="src/http/posts/route.ts"
66
+ import { Controller, Post, Body } from 'exisjs/decorators'
67
+ import { tex } from 'exisjs/validator'
68
+ import { sanitize } from 'exisjs/sanitize'
69
+
70
+ const CreatePostSchema = tex.object({
71
+ // Trim the title, then ensure it's still at least 5 characters
72
+ title: tex.string({ min: 5 })
73
+ .sanitize(sanitize.trim, sanitize.collapseWhitespace),
74
+
75
+ // Automatically generate a URL slug from the input string
76
+ slug: tex.string()
77
+ .sanitize(sanitize.slugify),
78
+
79
+ // Strip all HTML before saving to the database
80
+ content: tex.string()
81
+ .sanitize(sanitize.stripHtml),
82
+
83
+ // Deduplicate tags and remove empty strings
84
+ tags: tex.array(tex.string())
85
+ .sanitize(sanitize.dedupe, sanitize.compact)
86
+ })
87
+
88
+ // === Class-Based (OOP) Example ===
89
+ @Controller('/api/posts')
90
+ export default class PostController {
91
+ @Post('/', { body: CreatePostSchema })
92
+ createPost(@Body() data: any) {
93
+ // data.title will be trimmed
94
+ // data.content will have zero HTML tags
95
+ // data.tags will contain no duplicates
96
+ return data
97
+ }
98
+ }
99
+
100
+ // === Functional Example ===
101
+ import { controller, route } from 'exisjs/router'
102
+
103
+ export default controller({
104
+ createPostFn: route.post('/api/posts/functional', {
105
+ body: CreatePostSchema,
106
+ async handle(ctx) {
107
+ // ctx.body is fully sanitized and strongly typed!
108
+ return ctx.body
109
+ }
110
+ })
111
+ })
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Standalone Usage
117
+
118
+ Because the `sanitize` methods are pure functions, you can easily use them independently anywhere in your application.
119
+
120
+ ```typescript
121
+ import { sanitize } from 'exisjs/sanitize'
122
+
123
+ const rawInput = " <script>alert('hack')</script> Hello World! "
124
+
125
+ let safeInput = sanitize.stripHtml(rawInput)
126
+ safeInput = sanitize.collapseWhitespace(safeInput)
127
+ safeInput = sanitize.trim(safeInput)
128
+
129
+ console.log(safeInput) // "Hello World!"
130
+ ```
@@ -0,0 +1,116 @@
1
+ ---
2
+ title: "Storage & Uploads"
3
+ description: "API Reference for ExisJS Storage and File Uploads (`exisjs/storage`)."
4
+ ---
5
+
6
+ # Storage & Uploads (`exisjs/storage`)
7
+
8
+ ExisJS provides native, extremely memory-efficient support for handling `multipart/form-data` file uploads.
9
+
10
+ Instead of buffering large files into RAM (which can easily crash Node.js servers under heavy load), ExisJS streams file chunks directly from the network socket into the disk using a highly optimized Busboy integration.
11
+
12
+ ---
13
+
14
+ ## How it Works
15
+
16
+ When you invoke `streamUpload`, ExisJS takes over the raw socket stream. As bytes come in from the network, they are instantly piped to the disk.
17
+
18
+ Because ExisJS parses the multipart stream on the fly:
19
+ 1. Your Node.js RAM footprint stays incredibly flat, even when users upload massive 10GB video files.
20
+ 2. Form text fields are extracted in real-time. Once the upload finishes, ExisJS automatically merges all text fields directly into `req.body` so your subsequent logic (or validators) can treat them like a normal JSON payload!
21
+
22
+ ---
23
+
24
+ ## 1. Functional Example (`streamUpload`)
25
+
26
+ If you are using the Functional architecture, import the `streamUpload` helper from `exisjs/storage` and pass it your request context.
27
+
28
+ ### Example Usage
29
+
30
+ ```typescript filename="src/http/upload/route.ts"
31
+ import { controller, route } from 'exisjs/router'
32
+ import { streamUpload } from 'exisjs/storage'
33
+ import { HttpError } from 'exisjs/error'
34
+
35
+ export default controller({
36
+ uploadAvatar: route.post('/avatar', {
37
+ async handle(ctx) {
38
+ try {
39
+ const { files, fields } = await streamUpload(ctx.req, {
40
+ dest: './storage/avatars'
41
+ })
42
+
43
+ if (files.length === 0) {
44
+ throw HttpError.badRequest('No file provided')
45
+ }
46
+
47
+ const uploadedFile = files[0]
48
+
49
+ // Log the fields submitted with the form
50
+ console.log('User ID associated with upload:', fields.userId)
51
+
52
+ return {
53
+ success: true,
54
+ filename: uploadedFile.filename,
55
+ savedAt: uploadedFile.destPath,
56
+ size: uploadedFile.size,
57
+ mimetype: uploadedFile.mimetype
58
+ }
59
+ } catch (err) {
60
+ // streamUpload throws HttpErrors if the request isn't valid multipart/form-data
61
+ throw err
62
+ }
63
+ }
64
+ })
65
+ })
66
+ ```
67
+
68
+ ### Return Value
69
+
70
+ `streamUpload` resolves a Promise containing an object with two properties:
71
+
72
+ #### 1. `fields` (`Record<string, string>`)
73
+ An object containing all the non-file text fields submitted in the `multipart/form-data` payload. Note that `streamUpload` automatically populates `ctx.req.body` with these fields as well upon completion.
74
+
75
+ #### 2. `files` (`Array<object>`)
76
+ An array of successfully streamed files. Each object contains:
77
+ - **`fieldname`**: The name of the HTML form field (e.g., `avatar`).
78
+ - **`filename`**: The original name of the file uploaded by the user.
79
+ - **`mimetype`**: The MIME type of the file (e.g., `image/png`).
80
+ - **`destPath`**: The absolute path on your server's disk where the file was safely saved. *Note: ExisJS appends a randomized unique suffix to the filename to prevent overwrites.*
81
+ - **`size`**: The total file size in bytes.
82
+
83
+ ---
84
+
85
+ ## 2. Class-Based Example (`req.streamUpload`)
86
+
87
+ If you are using Object-Oriented `@Controller` classes, ExisJS natively extends the Request object. You do not even need to import `streamUpload`!
88
+
89
+ Simply inject the Request object via the `@Req()` decorator and call `req.streamUpload(dest)` directly.
90
+
91
+ ```typescript filename="src/http/upload/route.ts"
92
+ import { Controller, Post, Req } from 'exisjs/decorators'
93
+ import type { Request } from 'exisjs'
94
+ import { HttpError } from 'exisjs/error'
95
+
96
+ @Controller('/upload')
97
+ export default class UploadController {
98
+
99
+ @Post('/avatar')
100
+ async handleUpload(@Req() req: Request) {
101
+ // Exactly identical to streamUpload(req, { dest: ... })
102
+ // We pass the destination path directly into the Request method
103
+ const { files, fields } = await req.streamUpload('./storage/avatars')
104
+
105
+ if (!files.length) throw HttpError.badRequest('Missing file')
106
+
107
+ // Remember: fields are also populated into req.body!
108
+ console.log(req.body.userId === fields.userId) // true!
109
+
110
+ return {
111
+ message: 'Upload successful',
112
+ files
113
+ }
114
+ }
115
+ }
116
+ ```
@@ -0,0 +1,132 @@
1
+ ---
2
+ title: "Swagger & OpenAPI"
3
+ description: "API Reference for ExisJS auto-generated Swagger UI (`exisjs/swagger`)."
4
+ ---
5
+
6
+ # Swagger / OpenAPI (`exisjs/swagger`)
7
+
8
+ ExisJS ships with native, zero-configuration OpenAPI 3.0 generation.
9
+
10
+ Because ExisJS routes strongly integrate with `exisjs/validator`, the framework automatically introspects your validators (`tex.object`, `tex.string`, etc.) across all controllers and automatically generates a fully interactive Swagger UI!
11
+
12
+ ---
13
+
14
+ ## 1. Global Setup (`exis.config.ts`)
15
+
16
+ Because the Swagger engine needs to read all registered routes at boot time, you mount it globally using a Plugin.
17
+
18
+ Create a new file in your `plugins` directory:
19
+
20
+ ```typescript filename="src/plugins/swagger.ts"
21
+ import { definePlugin } from 'exisjs/plugin'
22
+ import { serveSwagger } from 'exisjs/swagger'
23
+
24
+ export const swaggerPlugin = definePlugin({
25
+ name: 'swagger-ui',
26
+ register(app) {
27
+ serveSwagger(app, {
28
+ path: '/docs', // The URL where the UI will be served
29
+ title: 'My ExisJS API',
30
+ version: '1.0.0',
31
+
32
+ // Optionally attach global OpenAPI components (like Bearer Auth definitions)
33
+ components: {
34
+ securitySchemes: {
35
+ bearerAuth: {
36
+ type: 'http',
37
+ scheme: 'bearer',
38
+ bearerFormat: 'JWT',
39
+ }
40
+ }
41
+ }
42
+ })
43
+ }
44
+ })
45
+ ```
46
+
47
+ Then, natively register your plugin in `exis.config.ts`:
48
+
49
+ ```typescript filename="exis.config.ts"
50
+ import { defineConfig } from 'exisjs/config'
51
+ import { swaggerPlugin } from './src/plugins/swagger'
52
+
53
+ export default defineConfig({
54
+ plugins: [
55
+ swaggerPlugin()
56
+ ]
57
+ })
58
+ ```
59
+
60
+ ---
61
+
62
+ ## 2. Automatic Schema Inference
63
+
64
+ You do not need to write YAML files or manually define your request bodies in Swagger. ExisJS reads the schemas directly from your code!
65
+
66
+ ### Functional Architecture
67
+
68
+ When using the `route.*` functions, anything you pass to `schema.body`, `schema.query`, or `schema.response` is instantly injected into the Swagger documentation.
69
+
70
+ ```typescript filename="src/http/users/route.ts"
71
+ import { controller, route } from 'exisjs/router'
72
+ import { tex } from 'exisjs/validator'
73
+
74
+ export default controller({
75
+ createUser: route.post('/', {
76
+ schema: {
77
+ // ExisJS automatically builds an OpenAPI schema from this validator!
78
+ body: tex.object({
79
+ email: tex.email(),
80
+ age: tex.number({ min: 18 })
81
+ }),
82
+ response: tex.object({
83
+ id: tex.string(),
84
+ success: tex.boolean()
85
+ })
86
+ },
87
+ async handle(ctx) {
88
+ return { id: '123', success: true }
89
+ }
90
+ })
91
+ })
92
+ ```
93
+
94
+ ### Class-Based (OOP) Architecture
95
+
96
+ If you are using OOP Decorators, ExisJS achieves the exact same result by reading your `@Body()`, `@Query()`, and `@Returns()` decorators.
97
+
98
+ ```typescript filename="src/http/users/route.ts"
99
+ import { Controller, Post, Body, Returns } from 'exisjs/decorators'
100
+ import { tex } from 'exisjs/validator'
101
+
102
+ const CreateUserSchema = tex.object({
103
+ email: tex.email(),
104
+ age: tex.number({ min: 18 })
105
+ })
106
+
107
+ const ResponseSchema = tex.object({
108
+ id: tex.string(),
109
+ success: tex.boolean()
110
+ })
111
+
112
+ @Controller('/users')
113
+ export default class UserController {
114
+
115
+ @Post('/')
116
+ @Returns(ResponseSchema)
117
+ async createUser(@Body(CreateUserSchema) body: any) {
118
+ return { id: '123', success: true }
119
+ }
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 3. Accessing the UI
126
+
127
+ Once you boot your application, simply navigate to the `path` you defined in your plugin:
128
+
129
+ - **Swagger UI**: `http://localhost:4000/docs`
130
+ - **Raw OpenAPI JSON**: `http://localhost:4000/docs/json`
131
+
132
+ The generated Swagger UI is fully interactive, meaning you can authenticate and test your API directly from the browser!