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,139 @@
1
+ ---
2
+ nextUrl: "/docs/adapters"
3
+ nextTitle: "Adapters"
4
+ ---
5
+
6
+ # Testing
7
+
8
+ ExisJS was built with testing as a first-class citizen. Instead of forcing you to configure bulky external test runners like Jest or Vitest, ExisJS natively wraps the ultra-fast, built-in **Node.js Test Runner** (`node:test`).
9
+
10
+ This means you get blistering fast, zero-dependency testing out-of-the-box.
11
+
12
+ ---
13
+
14
+ ## The Testing Module
15
+
16
+ ExisJS exports everything you need for testing directly from `exisjs/testing`. This includes the test runner functions (`test`, `describe`, `it`, `before`), assertions (`assert`), and the powerful ExisJS `createTestContext`!
17
+
18
+ ### Creating a Test Context
19
+
20
+ When testing an API, you want to boot the app, run HTTP requests against it, and then gracefully shut it down (closing database connections so the test runner doesn't hang). `createTestContext` does exactly this for you automatically! It seamlessly handles both functional `exis({ ... })` applications and Object-Oriented `@Server` decorated classes.
21
+
22
+ ```typescript filename="tests/users.test.ts"
23
+ import { describe, it, expect, createTestContext } from 'exisjs/testing'
24
+ import app from '../src/http/server' // Import your Exis instance or @Server class
25
+
26
+ describe('Users API', () => {
27
+ // Automatically boots the app, runs `onStart`, and gracefully shuts down databases after tests!
28
+ const api = createTestContext(app)
29
+
30
+ it('should create a new user', async () => {
31
+ // `api` exposes a fast, fluid HTTP client
32
+ const res = await api
33
+ .post('/users')
34
+ .send({ name: 'Alice', email: 'alice@example.com' })
35
+ .execute()
36
+
37
+ // Integrated Jest-like expectations
38
+ expect(res.status).toBe(201)
39
+ expect(res.body.success).toBe(true)
40
+ expect(res.body.data.name).toBe('Alice')
41
+ })
42
+
43
+ it('should return 400 on invalid input', async () => {
44
+ const res = await api
45
+ .post('/users')
46
+ .send({ name: 'Bob' }) // Missing email
47
+ .execute()
48
+
49
+ expect(res.status).toBe(400)
50
+ expect(res.body.success).toBe(false)
51
+ })
52
+ })
53
+ ```
54
+
55
+ ### Running Tests
56
+
57
+ Because ExisJS wraps the native Node.js test runner in its powerful CLI, you can run all your tests simply using the standard Exis command!
58
+
59
+ ```bash
60
+ npx exisjs test
61
+ ```
62
+
63
+ Or configure it in your `package.json`:
64
+
65
+ ```json
66
+ {
67
+ "scripts": {
68
+ "test": "exisjs test"
69
+ }
70
+ }
71
+ ```
72
+
73
+ The `exisjs test` command automatically bootstraps your environment, loads your environment variables (`.env`), registers TypeScript (`tsx`), and recursively finds and executes all test files matching standard testing conventions (`*.test.ts`, `*.spec.ts`, etc.) regardless of where they are placed.
74
+
75
+ ### Test Configuration
76
+
77
+ ExisJS allows you to configure your testing environment directly. By default, it will use your project's TypeScript configuration, but you can override this by creating a specific `tsconfig.test.json` file in your project root. The test runner will automatically detect and prioritize it when running tests!
78
+
79
+ ---
80
+
81
+ ## Mocking (Dependency Injection)
82
+
83
+ If you are using ExisJS's built-in **Dependency Injection (DI)** or **Gateways**, mocking is incredibly easy. You don't need magic `jest.mock()` intercepts. You can simply override the providers in your test!
84
+
85
+ ```typescript filename="tests/billing.test.ts"
86
+ import { describe, it, expect, createTestContext } from 'exisjs/testing'
87
+ import app from '../src/http/server'
88
+ import { BillingGateway } from '../src/http/billing/gateway'
89
+
90
+ describe('Billing API', () => {
91
+
92
+ // Override the StripeService strictly for testing!
93
+ BillingGateway.overrideProvider('StripeService', {
94
+ charge: async (amount: number) => ({ success: true, transactionId: 'test_123' })
95
+ })
96
+
97
+ const api = createTestContext(app)
98
+
99
+ it('should process payment', async () => {
100
+ const res = await api
101
+ .post('/billing/charge')
102
+ .send({ amount: 500 })
103
+ .execute()
104
+
105
+ expect(res.status).toBe(200)
106
+ expect(res.body.transactionId).toBe('test_123')
107
+ })
108
+ })
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Test Isolation
114
+
115
+ One of the most common pitfalls when testing backend frameworks is **test bleed**—where the state from one test unexpectedly affects the next. ExisJS provides the tools to prevent this, but you must be intentional about state isolation.
116
+
117
+ ### 1. Dependency Injection Cleanup
118
+ If you rely heavily on singletons in the ExisJS Dependency Injection container, you may want to reset the container's state between tests.
119
+
120
+ ```typescript filename="tests/setup.ts"
121
+ import { beforeEach } from 'exisjs/testing'
122
+ import { getActiveApp } from 'exisjs/app'
123
+
124
+ beforeEach(() => {
125
+ const app = getActiveApp()
126
+ if (app) {
127
+ app.container.clearCache() // Purges all instantiated singletons
128
+ }
129
+ })
130
+ ```
131
+
132
+ ### 2. Zero-Config Singletons (Redis, Prisma, etc.)
133
+ ExisJS provides zero-config integrations (`redis.ts`, `prisma.ts`, etc.) which act as **process-wide singletons**.
134
+ Because they use proxy-based caching at the module level, running multiple test suites concurrently in the same Node.js process means they will all share the **exact same database connections and state**.
135
+
136
+ If your tests write to the database or queue, ensure you either:
137
+ 1. Run your test runner in a mode that isolates processes per file.
138
+ 2. Manually clear database tables or flush Redis in a `beforeEach` block.
139
+ 3. Use database transactions that rollback after each test.
@@ -0,0 +1,249 @@
1
+ ---
2
+ nextUrl: "/docs/errors"
3
+ nextTitle: "Error Handling"
4
+ ---
5
+
6
+ # Validation & Schemas
7
+
8
+ If you are coming from frameworks like Fastify, you are likely used to writing validation schemas using JSON Schema strings and relying on external dependencies like AJV.
9
+
10
+ ExisJS takes a strictly-typed approach. It includes a native validation engine out of the box, powered by Rust for maximum performance. You don't need any external dependencies, and you avoid string-based references.
11
+
12
+ ## The Native Validator (`tex`)
13
+
14
+ ExisJS exports a global validator object `tex` that allows you to fluently build complex validation schemas using pure, typesafe JavaScript.
15
+
16
+ ```typescript filename="example.ts"
17
+ import { tex } from 'exisjs/validator'
18
+
19
+ // Build a strict schema intuitively
20
+ const userSchema = tex.object({
21
+ name: tex.string({ min: 3 }),
22
+ age: tex.number({ optional: true }),
23
+ role: tex.enum(['admin', 'user']),
24
+ email: tex.email()
25
+ })
26
+
27
+ // You can natively parse data directly!
28
+ const safeData = userSchema.parse(rawInput)
29
+ ```
30
+
31
+ ### Validation Primitives
32
+
33
+ ExisJS supports a comprehensive set of strict validation primitives:
34
+
35
+ - `tex.string()` - String validation (supports options like `{ min: 5, max: 10 }`).
36
+ - `tex.email()` - Validates standard email formatting.
37
+ - `tex.number()` - Strict number validation (fails if passed a string).
38
+ - `tex.boolean()` - Strict boolean validation.
39
+ - `tex.number({ coerce: true })` - Automatically coerces incoming string representations into native JavaScript numbers.
40
+ - `tex.boolean({ coerce: true })` - Coerces `"true"`/`"1"` and `"false"`/`"0"` into native booleans.
41
+ - `tex.array(schema)` - Validates an array of items.
42
+ - `tex.object({ ... })` - Validates structured JSON objects.
43
+ - `tex.enum(['A', 'B'])` - Ensures the value matches one of the provided exact strings.
44
+
45
+ All primitives accept an options object where you can pass `{ optional: true }` to allow `undefined`.
46
+
47
+ ### Data Sanitization (Trimming & Formatting)
48
+
49
+ ExisJS allows you to mutate and sanitize incoming data in real-time before it reaches your controller. It includes a native Rust-powered sanitization engine for Strings and Arrays.
50
+
51
+ ```typescript filename="example.ts"
52
+ import { tex } from 'exisjs/validator'
53
+ import { sanitize } from 'exisjs/sanitize'
54
+
55
+ const loginSchema = tex.object({
56
+ // Automatically trim whitespace, convert to lowercase, and prevent HTML injections
57
+ email: tex.email().sanitize(sanitize.trim, sanitize.toLowerCase, sanitize.escapeHtml),
58
+ password: tex.string({ min: 8 })
59
+ })
60
+
61
+ const articleSchema = tex.object({
62
+ title: tex.string().sanitize(sanitize.collapseWhitespace, sanitize.truncate(100)),
63
+ // Auto-generate slugs from the title using sanitize.slugify!
64
+ slug: tex.string().sanitize(sanitize.slugify),
65
+ tags: tex.array(tex.string()).sanitize(sanitize.dedupe, sanitize.compact)
66
+ })
67
+ ```
68
+
69
+ ### Standalone Sanitization Usage
70
+
71
+ Because the sanitization engine is designed as a collection of pure, independent functions, **you don't have to use it with the Validator**. If you ever need to manually sanitize data deep inside your services, background jobs, or generic Node.js scripts, you can import the engine directly:
72
+
73
+ ```typescript filename="src/services/formatter.ts"
74
+ import { sanitize } from 'exisjs/sanitize'
75
+
76
+ export function formatArticleTitle(rawInput: string) {
77
+ let clean = sanitize.stripHtml(rawInput)
78
+ clean = sanitize.collapseWhitespace(clean)
79
+ return sanitize.trim(clean)
80
+ }
81
+ ```
82
+
83
+ ## Schema Composition & Features
84
+
85
+ ExisJS validation provides robust features to handle real-world use cases efficiently and seamlessly.
86
+
87
+ ### Schema Composition
88
+
89
+ You can construct powerful shared schemas easily using composition methods (`.partial()`, `.omit()`, `.pick()`). This prevents schema duplication.
90
+
91
+ ```typescript filename="example.ts"
92
+ const UserSchema = tex.object({
93
+ id: tex.string(),
94
+ name: tex.string(),
95
+ email: tex.email(),
96
+ password: tex.string({ min: 8 })
97
+ })
98
+
99
+ // Create an update schema (all fields optional)
100
+ const UpdateUserSchema = UserSchema.partial()
101
+
102
+ // Create a safe response schema (omit password)
103
+ const PublicUserSchema = UserSchema.omit(['password'])
104
+
105
+ // Create a specialized schema
106
+ const LoginSchema = UserSchema.pick(['email', 'password'])
107
+
108
+ // Chain multiple schemas using tex.union()
109
+ const FlexibleIdSchema = tex.union([tex.string(), tex.number()])
110
+ ```
111
+
112
+ ### Asynchronous Validation
113
+
114
+ Need to check a database during validation? No problem! Use `.refineAsync()` to run complex async logic seamlessly within your validation schemas. The ExisJS router will automatically wait for the async checks to resolve before executing your route handler.
115
+
116
+ ```typescript filename="example.ts"
117
+ const registerSchema = tex.object({
118
+ email: tex.email()
119
+ .refineAsync(async (val) => {
120
+ const exists = await db.users.find({ email: val })
121
+ return !exists // Return false if email is taken
122
+ }, 'Email is already taken'),
123
+ password: tex.string({ min: 8 })
124
+ })
125
+ ```
126
+
127
+ ### Response Stripping & Serialization
128
+
129
+ Because `TexEngine` validates by strictly iterating over its defined schema keys, it naturally **strips** any extra or unknown properties from the resulting object.
130
+
131
+ This provides immense security out of the box when used as a `response` schema, ensuring you never accidentally leak sensitive data (like passwords or internal IDs) to the client. The ExisJS router handles this stripping serialization natively behind the scenes.
132
+
133
+ ### Separating Schemas
134
+
135
+ To keep your route files clean and maintainable, we highly recommend defining your schemas in a dedicated `schema.ts` file alongside your routes.
136
+
137
+ ```typescript filename="src/http/users/schema.ts"
138
+ import { tex } from 'exisjs/validator'
139
+ import type { ResolveSchema } from 'exisjs/validator'
140
+
141
+ export const UserParamsSchema = tex.object({
142
+ id: tex.number({ coerce: true })
143
+ })
144
+
145
+ export type UserParams = ResolveSchema<typeof UserParamsSchema>
146
+ ```
147
+
148
+ To validate incoming HTTP requests, ExisJS provides built-in validation configurations. Whether you use the Functional paradigm or the Class-based (OOP) paradigm, ExisJS validates incoming payloads and types your handlers.
149
+
150
+ #### Functional Paradigm
151
+
152
+ ```typescript filename="src/http/users/route.ts"
153
+ import { controller, route } from 'exisjs/router'
154
+ import { tex } from 'exisjs/validator'
155
+
156
+ export default controller({
157
+ create: route.post('/', {
158
+ // 1. The validation schema is passed directly in the config
159
+ body: tex.object({
160
+ username: tex.string({ min: 4 }),
161
+ password: tex.string({ min: 8 })
162
+ }),
163
+ query: tex.object({
164
+ source: tex.string({ optional: true })
165
+ }),
166
+
167
+ // 2. Your handler only runs if validation succeeds
168
+ async handle({ body, query, req, res }) {
169
+ // body and query are strongly-typed here
170
+ await db.users.create(body)
171
+ res.status(201).json({ success: true })
172
+ }
173
+ })
174
+ })
175
+ ```
176
+
177
+ #### Class-Based (OOP) Paradigm
178
+
179
+ ```typescript filename="src/http/api/books/route.ts"
180
+ import { Controller, Use, Post, Body, Req } from 'exisjs/decorators'
181
+ import { tex } from 'exisjs/validator'
182
+ import type { ResolveSchema } from 'exisjs/validator'
183
+
184
+ // 1. Define your schema
185
+ const CreateBookSchema = tex.object({
186
+ title: tex.string(),
187
+ caption: tex.string(),
188
+ rating: tex.number(),
189
+ image: tex.string(),
190
+ })
191
+
192
+ // 2. Infer the TypeScript type!
193
+ type CreateBookDto = ResolveSchema<typeof CreateBookSchema>
194
+
195
+ @Use(protectRoute)
196
+ @Controller()
197
+ export default class BooksController {
198
+
199
+ // 3. Inject the schema directly into the @Body() decorator!
200
+ @Post('/')
201
+ async create(@Body(CreateBookSchema) body: CreateBookDto, @Req() req: any) {
202
+ // If validation fails, this block is never reached!
203
+ // body is strictly typed as CreateBookDto
204
+
205
+ const newBook = new Book({
206
+ title: body.title,
207
+ caption: body.caption,
208
+ rating: body.rating,
209
+ image: body.image,
210
+ user: req.user._id,
211
+ })
212
+
213
+ await newBook.save()
214
+ return newBook
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Database Integration (Mongoose)
220
+
221
+ Because the ExisJS Validator is native to the framework, it is tightly coupled with our database and documentation adapters. You can generate Mongoose schemas directly from your validation schemas using `.toMongoSchema()`!
222
+
223
+ ```typescript filename="src/models/User.ts"
224
+ import mongoose from 'mongoose'
225
+ import { tex } from 'exisjs/validator'
226
+
227
+ export const UserValidator = tex.object({
228
+ email: tex.email(),
229
+ password: tex.string({ min: 8 }),
230
+ role: tex.enum(['admin', 'user'], { default: 'user' })
231
+ })
232
+
233
+ // Pass the ExisJS Validator directly into Mongoose!
234
+ export const User = mongoose.model('User', new mongoose.Schema(UserValidator.toMongoSchema()))
235
+ ```
236
+
237
+ ## Error Handling
238
+
239
+ If a request fails validation (e.g., missing a required body field), the validation engine will instantly catch it and return a standardized `400 Bad Request` JSON response to the client.
240
+
241
+ ```json filename="Response"
242
+ {
243
+ "statusCode": 400,
244
+ "error": "Bad Request",
245
+ "message": "Validation Error: body.username is required"
246
+ }
247
+ ```
248
+
249
+ Because this is built right into the framework, you don't have to manually catch errors or construct complex schema error formatters. It just works exactly as you expect!
@@ -0,0 +1,289 @@
1
+ ---
2
+ nextUrl: "/docs/queues"
3
+ nextTitle: "Job Queues"
4
+ ---
5
+
6
+ # WebSockets
7
+
8
+ ExisJS provides a built-in WebSocket engine. It includes an integrated Pub/Sub system (rooms), automatic JSON serialization, Socket.io-style event emitters, and built-in presence tracking.
9
+
10
+ You can handle WebSockets using either the **Functional API** or the **Class-Based (OOP) API**.
11
+
12
+ ---
13
+
14
+ ## 1. Functional API
15
+
16
+ For smaller applications or functional paradigms, you can define WebSocket routes directly on a router using `route.ws()`.
17
+
18
+ ```typescript filename="src/http/route.ts"
19
+ import { controller, route } from 'exisjs/router'
20
+
21
+ export default controller({
22
+ chatSocket: route.ws('/ws', {
23
+ handle({ socket }) {
24
+ console.log(`New connection: ${socket.id}`)
25
+
26
+ // Listen for incoming messages
27
+ socket.on('message', (rawData) => {
28
+ const data = JSON.parse(rawData.toString())
29
+
30
+ if (data.action === 'join') {
31
+ // Join a room (Pub/Sub)
32
+ socket.join(data.room)
33
+
34
+ // Emit a named event back to the socket
35
+ socket.emit('chat', {
36
+ type: 'system',
37
+ message: `You joined #${data.room}`,
38
+ })
39
+ } else if (data.action === 'message') {
40
+ // Publish a message to everyone in the room (excluding sender by default)
41
+ socket.publish(data.room, {
42
+ event: 'chat',
43
+ data: { message: data.message },
44
+ })
45
+ }
46
+ })
47
+
48
+ socket.on('close', () => {
49
+ console.log(`Disconnected: ${socket.id}`)
50
+ socket.leaveAll()
51
+ })
52
+ },
53
+ }),
54
+ })
55
+ ```
56
+
57
+ ---
58
+
59
+ ## 2. Class-Based (OOP) API
60
+
61
+ For larger applications, ExisJS provides the `@Ws()` decorator to map WebSocket endpoints inside your Controllers.
62
+
63
+ ```typescript filename="src/http/chat/route.ts"
64
+ import { Controller, Ws, Socket } from 'exisjs/decorators'
65
+ import { ExisWebSocket } from 'exisjs/router'
66
+
67
+ @Controller()
68
+ export default class ChatController {
69
+ @Ws('/ws')
70
+ handleSocket(@Socket() socket: ExisWebSocket) {
71
+ // Enable auto-presence for this server
72
+ socket.server.enablePresence()
73
+
74
+ // Listen for custom JSON events
75
+ socket.onEvent('join', (data) => {
76
+ // Attach custom state to the socket
77
+ socket.data.userId = data.userId
78
+
79
+ // Unsubscribe from previous rooms
80
+ socket.leaveAll()
81
+ socket.join(data.room)
82
+
83
+ // Fetch the roster of active users in the room
84
+ const roster = socket.server.getRoomRoster(data.room)
85
+ socket.emit('roster', { roster })
86
+ })
87
+
88
+ socket.onEvent('message', (data) => {
89
+ // Socket.io style broadcasting!
90
+ socket.to(data.room).emit('chat', {
91
+ userId: socket.data.userId,
92
+ message: data.message,
93
+ })
94
+ })
95
+ }
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## The `ExisWebSocket` API
102
+
103
+ ExisJS wraps the native WebSocket with an `ExisWebSocket` instance that provides Socket.io-like ergonomics without the heavy client-side dependencies.
104
+
105
+ ### Emitting & Sending
106
+
107
+ - **`socket.send(data)`**: Sends raw data. Objects are automatically stringified.
108
+ - **`socket.emit(event, data)`**: Emits a named JSON event (`{ event: "name", data: ... }`).
109
+ - **`socket.emitWithAck(event, data, timeoutMs?)`**: Emits an event and returns a Promise that resolves when the client sends back an acknowledgment with the matching `ackId`.
110
+ - **`socket.volatile.emit(event, data)`**: Emits an event that will be safely dropped if the connection is experiencing backpressure (network saturation).
111
+
112
+ #### Example: Emitting Events
113
+
114
+ **Functional API:**
115
+
116
+ ```typescript
117
+ route.ws('/ws', {
118
+ handle({ socket }) {
119
+ // Send a standard named event
120
+ socket.emit('ping', { timestamp: Date.now() })
121
+
122
+ // Send a volatile event that drops if network is busy
123
+ socket.volatile.emit('stock-tick', { price: 100 })
124
+ },
125
+ })
126
+ ```
127
+
128
+ **Class-Based (OOP) API:**
129
+
130
+ ```typescript
131
+ @Ws('/ws')
132
+ handleSocket(@Socket() socket: ExisWebSocket) {
133
+ // Await an acknowledgment from the client
134
+ socket.onEvent('request-data', async () => {
135
+ try {
136
+ const response = await socket.emitWithAck('data-ready', { id: 1 }, 5000)
137
+ console.log('Client acknowledged:', response)
138
+ } catch (err) {
139
+ console.error('Client failed to ack in 5s')
140
+ }
141
+ })
142
+ }
143
+ ```
144
+
145
+ ### Rooms (Pub/Sub)
146
+
147
+ ExisJS has a built-in Pub/Sub engine that allows sockets to subscribe to topics (rooms).
148
+
149
+ - **`socket.join(room)`** / **`socket.subscribe(room)`**: Subscribes the socket to a room.
150
+ - **`socket.leave(room)`** / **`socket.unsubscribe(room)`**: Unsubscribes from a room.
151
+ - **`socket.leaveAll()`** / **`socket.unsubscribeAll()`**: Unsubscribes from all joined rooms.
152
+ - **`socket.publish(room, data, excludeSelf?)`**: Publishes data to all sockets in a room. By default, it excludes the sender.
153
+
154
+ #### Example: Managing Rooms
155
+
156
+ **Functional API:**
157
+
158
+ ```typescript
159
+ route.ws('/chat', {
160
+ handle({ socket }) {
161
+ socket.onEvent('join-room', (data) => {
162
+ // Leave old rooms before joining a new one
163
+ socket.leaveAll()
164
+ socket.join(data.roomId)
165
+
166
+ socket.publish(data.roomId, {
167
+ event: 'system',
168
+ data: 'A new user joined',
169
+ })
170
+ })
171
+ },
172
+ })
173
+ ```
174
+
175
+ **Class-Based (OOP) API:**
176
+
177
+ ```typescript
178
+ @Ws('/chat')
179
+ handleSocket(@Socket() socket: ExisWebSocket) {
180
+ socket.onEvent('leave-room', (data) => {
181
+ socket.leave(data.roomId)
182
+ })
183
+ }
184
+ ```
185
+
186
+ ### Broadcasting (Socket.io Style)
187
+
188
+ If you prefer Socket.io's chainable broadcasting syntax, ExisJS fully supports it:
189
+
190
+ - **`socket.to(room).emit(event, data)`**: Emits an event to everyone in the specified room _except_ the sender.
191
+ - **`socket.broadcast.emit(event, data)`**: Emits to every connected socket on the server _except_ the sender.
192
+ - **`socket.broadcast.to(room).emit(event, data)`**: Same as `socket.to()`.
193
+
194
+ #### Example: Broadcasting
195
+
196
+ **Functional API:**
197
+
198
+ ```typescript
199
+ route.ws('/game', {
200
+ handle({ socket }) {
201
+ socket.onEvent('player-move', (position) => {
202
+ // Tell everyone ELSE in 'game-1' that this player moved
203
+ socket.to('game-1').emit('player-moved', { id: socket.id, position })
204
+ })
205
+ },
206
+ })
207
+ ```
208
+
209
+ **Class-Based (OOP) API:**
210
+
211
+ ```typescript
212
+ @Ws('/global')
213
+ handleSocket(@Socket() socket: ExisWebSocket) {
214
+ socket.onEvent('global-alert', (alert) => {
215
+ // Broadcast to EVERYONE on the server except the sender
216
+ socket.broadcast.emit('alert', alert)
217
+ })
218
+ }
219
+ ```
220
+
221
+ ### Listening for Events
222
+
223
+ - **`socket.on('message', cb)`**: Listens for raw WebSocket messages.
224
+ - **`socket.onEvent(eventName, cb)`**: Automatically parses incoming JSON and triggers the callback _only_ if the JSON payload matches `{ event: "eventName" }`.
225
+
226
+ #### Example: Event Listeners
227
+
228
+ **Functional API:**
229
+
230
+ ```typescript
231
+ route.ws('/data', {
232
+ handle({ socket }) {
233
+ // Handle raw buffer/string messages manually
234
+ socket.on('message', (raw) => {
235
+ console.log('Raw data received:', raw.toString())
236
+ })
237
+ },
238
+ })
239
+ ```
240
+
241
+ **Class-Based (OOP) API:**
242
+
243
+ ```typescript
244
+ @Ws('/api')
245
+ handleSocket(@Socket() socket: ExisWebSocket) {
246
+ // Handle specific JSON events
247
+ socket.onEvent('auth', (credentials) => {
248
+ console.log('Auth attempt:', credentials.username)
249
+ })
250
+ }
251
+ ```
252
+
253
+ ### State & Presence
254
+
255
+ - **`socket.data`**: An empty object `Record<string, any>` where you can safely attach custom user data (like user IDs, roles, etc.) for the duration of the connection.
256
+ - **`socket.server.enablePresence()`**: Turns on automatic presence tracking. When a socket joins or leaves a room, ExisJS will automatically broadcast `room:join` and `room:leave` events to the room.
257
+ - **`socket.server.getRoomRoster(room)`**: Returns an array of all connected sockets currently in a specific room.
258
+
259
+ #### Example: State & Roster Tracking
260
+
261
+ **Functional API:**
262
+
263
+ ```typescript
264
+ route.ws('/collaboration', {
265
+ handle({ socket }) {
266
+ socket.server.enablePresence()
267
+
268
+ socket.onEvent('identify', (user) => {
269
+ // Safely store state on the socket instance
270
+ socket.data.userId = user.id
271
+ socket.data.role = 'editor'
272
+ })
273
+ },
274
+ })
275
+ ```
276
+
277
+ **Class-Based (OOP) API:**
278
+
279
+ ```typescript
280
+ @Ws('/collaboration')
281
+ handleSocket(@Socket() socket: ExisWebSocket) {
282
+ socket.onEvent('get-users', (room) => {
283
+ // Fetch everyone currently in the room
284
+ const roster = socket.server.getRoomRoster(room)
285
+
286
+ socket.emit('users-list', roster.map(s => s.data.userId))
287
+ })
288
+ }
289
+ ```
@@ -0,0 +1,23 @@
1
+ # ExisJS Integrations Blueprint
2
+
3
+ This directory contains the documentation for integrating third-party services and tools with ExisJS. This `README.md` acts as a **blueprint** to help you understand what each integration guide covers.
4
+
5
+ ## 🗄️ Databases & Storage
6
+ * **`mongo.mdx`**: Connecting to MongoDB using Mongoose, leveraging the DI container, and data modeling.
7
+ * **`postgres.mdx`**: Connecting to PostgreSQL databases (e.g., via Prisma or Drizzle ORM) in a type-safe manner.
8
+ * **`redis.mdx`**: Using Redis for high-speed caching, rate limiting, and powering ExisJS queues.
9
+ * **`s3.mdx`**: Integrating with AWS S3 (or compatible services like Cloudflare R2) for file uploads and object storage.
10
+
11
+ ## 🔐 Auth & Security
12
+ * **`jwt.mdx`**: Implementing JSON Web Token (JWT) authentication, signing, and protecting routes via middleware.
13
+
14
+ ## 🤖 Artificial Intelligence
15
+ * **`openai.mdx`**: Integrating the OpenAI SDK, streaming LLM responses using ExisJS SSE (Server-Sent Events), and managing API keys securely.
16
+
17
+ ## 📧 Communications
18
+ * **`resend.mdx`**: Sending transactional emails (like password resets or welcome emails) using the Resend API, ideally triggered via background Queues.
19
+
20
+ ## 📊 Analytics & Observability
21
+ * **`posthog.mdx`**: Capturing user events, feature flags, and product analytics using PostHog inside your ExisJS controllers.
22
+ * **`observability.mdx`**: Setting up advanced monitoring, tracing (OpenTelemetry), and logging infrastructure.
23
+ * **`swagger.mdx`**: Automatic OpenAPI/Swagger generation for your endpoints based on your validation schemas and route definitions.