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.
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oauth/core.d.ts +96 -0
- package/dist/auth/oauth/core.d.ts.map +1 -0
- package/dist/auth/oauth/core.js +203 -0
- package/dist/auth/oauth/core.js.map +1 -0
- package/dist/auth/oauth/index.d.ts +45 -0
- package/dist/auth/oauth/index.d.ts.map +1 -0
- package/dist/auth/oauth/index.js +64 -0
- package/dist/auth/oauth/index.js.map +1 -0
- package/dist/auth/oauth/middleware.d.ts +63 -0
- package/dist/auth/oauth/middleware.d.ts.map +1 -0
- package/dist/auth/oauth/middleware.js +164 -0
- package/dist/auth/oauth/middleware.js.map +1 -0
- package/dist/auth/oauth/providers/custom.d.ts +41 -0
- package/dist/auth/oauth/providers/custom.d.ts.map +1 -0
- package/dist/auth/oauth/providers/custom.js +204 -0
- package/dist/auth/oauth/providers/custom.js.map +1 -0
- package/dist/auth/oauth/providers/discord.d.ts +23 -0
- package/dist/auth/oauth/providers/discord.d.ts.map +1 -0
- package/dist/auth/oauth/providers/discord.js +130 -0
- package/dist/auth/oauth/providers/discord.js.map +1 -0
- package/dist/auth/oauth/providers/facebook.d.ts +28 -0
- package/dist/auth/oauth/providers/facebook.d.ts.map +1 -0
- package/dist/auth/oauth/providers/facebook.js +124 -0
- package/dist/auth/oauth/providers/facebook.js.map +1 -0
- package/dist/auth/oauth/providers/github.d.ts +25 -0
- package/dist/auth/oauth/providers/github.d.ts.map +1 -0
- package/dist/auth/oauth/providers/github.js +132 -0
- package/dist/auth/oauth/providers/github.js.map +1 -0
- package/dist/auth/oauth/providers/google.d.ts +23 -0
- package/dist/auth/oauth/providers/google.d.ts.map +1 -0
- package/dist/auth/oauth/providers/google.js +134 -0
- package/dist/auth/oauth/providers/google.js.map +1 -0
- package/dist/auth/oauth/providers/microsoft.d.ts +28 -0
- package/dist/auth/oauth/providers/microsoft.d.ts.map +1 -0
- package/dist/auth/oauth/providers/microsoft.js +136 -0
- package/dist/auth/oauth/providers/microsoft.js.map +1 -0
- package/dist/auth/oauth/types.d.ts +158 -0
- package/dist/auth/oauth/types.d.ts.map +1 -0
- package/dist/auth/oauth/types.js +4 -0
- package/dist/auth/oauth/types.js.map +1 -0
- package/dist/auth/session.d.ts +4 -1
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/auth/session.js +33 -5
- package/dist/auth/session.js.map +1 -1
- package/dist/cache/store.d.ts +11 -3
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +87 -19
- package/dist/cache/store.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +4 -6
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/database/adapters/mongodb.d.ts +58 -0
- package/dist/database/adapters/mongodb.d.ts.map +1 -0
- package/dist/database/adapters/mongodb.js +264 -0
- package/dist/database/adapters/mongodb.js.map +1 -0
- package/dist/database/adapters/mysql.d.ts +27 -0
- package/dist/database/adapters/mysql.d.ts.map +1 -0
- package/dist/database/adapters/mysql.js +188 -0
- package/dist/database/adapters/mysql.js.map +1 -0
- package/dist/database/adapters/postgres.d.ts +28 -0
- package/dist/database/adapters/postgres.d.ts.map +1 -0
- package/dist/database/adapters/postgres.js +174 -0
- package/dist/database/adapters/postgres.js.map +1 -0
- package/dist/database/adapters/sqlite.d.ts +27 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +149 -0
- package/dist/database/adapters/sqlite.js.map +1 -0
- package/dist/database/index.d.ts +42 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +58 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/manager.d.ts +114 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/manager.js +269 -0
- package/dist/database/manager.js.map +1 -0
- package/dist/database/migration.d.ts +57 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +329 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/query-builder.d.ts +89 -0
- package/dist/database/query-builder.d.ts.map +1 -0
- package/dist/database/query-builder.js +353 -0
- package/dist/database/query-builder.js.map +1 -0
- package/dist/database/types.d.ts +184 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +4 -0
- package/dist/database/types.js.map +1 -0
- package/dist/docs/ARCHITECTURE.md +349 -0
- package/dist/docs/BENCHMARKS.md +36 -0
- package/dist/docs/README.md +34 -0
- package/dist/docs/content/README.md +44 -0
- package/dist/docs/content/adapters.mdx +199 -0
- package/dist/docs/content/cache.mdx +239 -0
- package/dist/docs/content/circuit-breaker.mdx +130 -0
- package/dist/docs/content/cli/commands.mdx +214 -0
- package/dist/docs/content/cli/overview.mdx +92 -0
- package/dist/docs/content/cli/usage.mdx +61 -0
- package/dist/docs/content/config.mdx +214 -0
- package/dist/docs/content/controllers.mdx +241 -0
- package/dist/docs/content/cron.mdx +109 -0
- package/dist/docs/content/database.mdx +209 -0
- package/dist/docs/content/dataloader.mdx +170 -0
- package/dist/docs/content/decorators.mdx +204 -0
- package/dist/docs/content/errors.mdx +344 -0
- package/dist/docs/content/gateways.mdx +376 -0
- package/dist/docs/content/installation.mdx +210 -0
- package/dist/docs/content/introduction.mdx +96 -0
- package/dist/docs/content/middleware.mdx +441 -0
- package/dist/docs/content/modules.mdx +144 -0
- package/dist/docs/content/oauth.mdx +179 -0
- package/dist/docs/content/providers.mdx +147 -0
- package/dist/docs/content/queues.mdx +177 -0
- package/dist/docs/content/requests.mdx +293 -0
- package/dist/docs/content/responses.mdx +271 -0
- package/dist/docs/content/routing.mdx +541 -0
- package/dist/docs/content/rpc.mdx +146 -0
- package/dist/docs/content/security.mdx +294 -0
- package/dist/docs/content/sse.mdx +159 -0
- package/dist/docs/content/structure.mdx +116 -0
- package/dist/docs/content/testing.mdx +139 -0
- package/dist/docs/content/validation.mdx +249 -0
- package/dist/docs/content/websockets.mdx +289 -0
- package/dist/docs/integrations/README.md +23 -0
- package/dist/docs/integrations/jwt.mdx +151 -0
- package/dist/docs/integrations/mongo.mdx +183 -0
- package/dist/docs/integrations/observability.mdx +125 -0
- package/dist/docs/integrations/openai.mdx +108 -0
- package/dist/docs/integrations/postgres.mdx +190 -0
- package/dist/docs/integrations/posthog.mdx +122 -0
- package/dist/docs/integrations/redis.mdx +141 -0
- package/dist/docs/integrations/resend.mdx +117 -0
- package/dist/docs/integrations/s3.mdx +122 -0
- package/dist/docs/integrations/swagger.mdx +113 -0
- package/dist/docs/reference/README.md +3 -0
- package/dist/docs/reference/adapters.mdx +130 -0
- package/dist/docs/reference/auth.mdx +183 -0
- package/dist/docs/reference/cache.mdx +132 -0
- package/dist/docs/reference/circuit-breaker.mdx +71 -0
- package/dist/docs/reference/config.mdx +118 -0
- package/dist/docs/reference/context.mdx +168 -0
- package/dist/docs/reference/dataloader.mdx +91 -0
- package/dist/docs/reference/decorators.mdx +293 -0
- package/dist/docs/reference/di.mdx +123 -0
- package/dist/docs/reference/error.mdx +170 -0
- package/dist/docs/reference/exis.mdx +124 -0
- package/dist/docs/reference/middleware.mdx +304 -0
- package/dist/docs/reference/plugin.mdx +113 -0
- package/dist/docs/reference/queue.mdx +155 -0
- package/dist/docs/reference/router.mdx +248 -0
- package/dist/docs/reference/sanitize.mdx +130 -0
- package/dist/docs/reference/storage.mdx +116 -0
- package/dist/docs/reference/swagger.mdx +132 -0
- package/dist/docs/reference/testing.mdx +145 -0
- package/dist/docs/reference/validator.mdx +186 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/backpressure.d.ts.map +1 -1
- package/dist/middleware/backpressure.js +21 -12
- package/dist/middleware/backpressure.js.map +1 -1
- package/dist/middleware/cache.d.ts +6 -0
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +84 -28
- package/dist/middleware/cache.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +4 -1
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +29 -4
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +16 -0
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +29 -12
- package/dist/middleware/rate-limit.js.map +1 -1
- package/dist/queue/drivers/MemoryDriver.d.ts +6 -3
- package/dist/queue/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/queue/drivers/MemoryDriver.js +76 -23
- package/dist/queue/drivers/MemoryDriver.js.map +1 -1
- package/dist/server/bootstrapper.d.ts +3 -0
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +56 -8
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/bun-adapter.d.ts +2 -0
- package/dist/server/bun-adapter.d.ts.map +1 -1
- package/dist/server/bun-adapter.js +23 -12
- package/dist/server/bun-adapter.js.map +1 -1
- package/dist/server/request-handler.d.ts +0 -2
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +2 -24
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +24 -11
- package/dist/server/request.js.map +1 -1
- package/dist/server/uws-adapter.d.ts +80 -0
- package/dist/server/uws-adapter.d.ts.map +1 -0
- package/dist/server/uws-adapter.js +357 -0
- package/dist/server/uws-adapter.js.map +1 -0
- package/dist/server/ws-orchestrator.d.ts +1 -0
- package/dist/server/ws-orchestrator.d.ts.map +1 -1
- package/dist/server/ws-orchestrator.js +83 -0
- package/dist/server/ws-orchestrator.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/validator/tex.d.ts +21 -16
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js.map +1 -1
- package/package.json +11 -3
|
@@ -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.
|