exisjs 0.5.10 → 0.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +0 -1
- 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/server/bootstrapper.js +1 -1
- package/dist/server/bootstrapper.js.map +1 -1
- package/package.json +8 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Configuration"
|
|
3
|
+
description: "API Reference for the global exis.config.ts file (`exisjs/config`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Configuration (`exisjs/config`)
|
|
7
|
+
|
|
8
|
+
ExisJS is designed to be highly zero-config out of the box. However, for advanced tuning, security, and clustering, you can define an `exis.config.ts` file in the root of your project.
|
|
9
|
+
|
|
10
|
+
This configuration file deeply integrates with the framework's core, managing everything from global middleware (like CORS and Helmet) to underlying Server backends and Worker Threads.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Standard Configuration
|
|
15
|
+
|
|
16
|
+
For most applications, you can simply export a static configuration object typed with `ExisConfig`.
|
|
17
|
+
|
|
18
|
+
```typescript filename="exis.config.ts"
|
|
19
|
+
import type { ExisConfig } from 'exisjs/config'
|
|
20
|
+
|
|
21
|
+
const config: ExisConfig = {
|
|
22
|
+
port: Number(process.env.PORT) || 4000,
|
|
23
|
+
host: '0.0.0.0',
|
|
24
|
+
|
|
25
|
+
cors: {
|
|
26
|
+
origin: process.env.CORS_ORIGIN || '*',
|
|
27
|
+
credentials: true,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
logger: {
|
|
31
|
+
level: 'debug',
|
|
32
|
+
pretty: process.env.NODE_ENV !== 'production',
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
queue: {
|
|
36
|
+
driver: 'memory',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
helmet: { enabled: true },
|
|
40
|
+
compression: true,
|
|
41
|
+
|
|
42
|
+
test: {
|
|
43
|
+
include: ['tests/**/*.test.ts'],
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default config
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Advanced Configuration (Phases)
|
|
53
|
+
|
|
54
|
+
If you need dynamic configuration (such as spawning worker threads only in production or altering middleware depending on the environment), you can use `defineConfig()` and Phase execution.
|
|
55
|
+
|
|
56
|
+
```typescript filename="exis.config.ts"
|
|
57
|
+
import { defineConfig, PHASE_DEVELOPMENT_SERVER } from 'exisjs/config'
|
|
58
|
+
|
|
59
|
+
export default async (phase: string, { defaultConfig }) => {
|
|
60
|
+
const isDev = phase === PHASE_DEVELOPMENT_SERVER
|
|
61
|
+
|
|
62
|
+
return defineConfig({
|
|
63
|
+
port: Number(process.env.PORT) || 4000,
|
|
64
|
+
|
|
65
|
+
// Auto-spawn Worker threads based on CPU cores!
|
|
66
|
+
workers: isDev ? 1 : 'safe',
|
|
67
|
+
|
|
68
|
+
// Globally configure native middleware
|
|
69
|
+
cors: {
|
|
70
|
+
origin: isDev ? '*' : 'https://myapp.com',
|
|
71
|
+
credentials: true
|
|
72
|
+
},
|
|
73
|
+
helmet: { enabled: true },
|
|
74
|
+
logger: isDev ? { level: 'debug', pretty: true } : false,
|
|
75
|
+
|
|
76
|
+
// Native plugin registration
|
|
77
|
+
plugins: [
|
|
78
|
+
// ... your plugins here
|
|
79
|
+
]
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Configuration Phases
|
|
87
|
+
|
|
88
|
+
If you export a function from `exis.config.ts`, ExisJS will inject the current "Phase" string as the first argument, allowing you to dynamically adjust settings based on whether you are running a dev server, running tests, or building for production.
|
|
89
|
+
|
|
90
|
+
- `PHASE_DEVELOPMENT_SERVER`: Emitted during `exis dev`
|
|
91
|
+
- `PHASE_PRODUCTION_BUILD`: Emitted during `exis build`
|
|
92
|
+
- `PHASE_PRODUCTION_SERVER`: Emitted during `exis start`
|
|
93
|
+
- `PHASE_TEST`: Emitted during test runs
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Configuration Options
|
|
98
|
+
|
|
99
|
+
Here is the full list of options supported natively by the ExisJS configuration engine:
|
|
100
|
+
|
|
101
|
+
### Server & Network
|
|
102
|
+
- **`port`** *(number)*: The port the server listens on (default: `4000`).
|
|
103
|
+
- **`host`** *(string)*: The host interface to bind to (default: `'0.0.0.0'`).
|
|
104
|
+
- **`server`** *(`'node' | 'uws' | 'auto'`)*: ExisJS supports running on standard Node `http`, or incredibly fast C++ `uWebSockets.js` (`uws`). Default is `'auto'` (uses `uws` if installed, falls back to `node`).
|
|
105
|
+
- **`workers`** *(number | `'safe'` | `'max'`)*: Number of Node.js Cluster workers to spawn. `'max'` uses all CPU cores. `'safe'` uses all cores minus 1 (to leave room for the OS).
|
|
106
|
+
- **`trustProxy`** *(boolean)*: Enable if running behind a reverse proxy (like Nginx or AWS ALB) to trust the `X-Forwarded-For` headers.
|
|
107
|
+
|
|
108
|
+
### Security & Middlewares
|
|
109
|
+
- **`cors`** *(boolean | object)*: Enable and configure Cross-Origin Resource Sharing globally.
|
|
110
|
+
- **`helmet`** *(boolean | object)*: Enable strict security headers. Enabled by default.
|
|
111
|
+
- **`logger`** *(boolean | object)*: Configure the high-performance Pino request logger.
|
|
112
|
+
- **`compression`** *(boolean | object)*: Enable gzip/brotli response compression.
|
|
113
|
+
- **`bodyLimit`** *(number)*: Maximum JSON/URL-encoded payload size in bytes (default: `1MB`).
|
|
114
|
+
|
|
115
|
+
### Advanced Integrations
|
|
116
|
+
- **`queue`**: Define your background queue driver (`memory` or `redis`) and configure `maxConcurrent` processing limits.
|
|
117
|
+
- **`plugins`**: An array of `ExisPlugin` instances to register natively into the framework.
|
|
118
|
+
- **`test`**: Configure testing environments (`include`, `exclude`, `setupFiles`, `coverage`).
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Context & Requests"
|
|
3
|
+
description: "API Reference for ExisJS Context, Requests, and Async Hooks."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Context & Requests
|
|
7
|
+
|
|
8
|
+
ExisJS leverages Node.js `AsyncLocalStorage` to provide request-scoped state without needing to manually pass request objects down through your entire service layer. This enables globally accessible contextual data anywhere in your application lifecycle.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Global Request & Response
|
|
13
|
+
|
|
14
|
+
In deeply nested utilities (like DataLoaders or generic services), it is often cumbersome to pass the `req` object through every function call. ExisJS allows you to pull the active HTTP Request and Response objects directly from the asynchronous context.
|
|
15
|
+
|
|
16
|
+
### `getRequest()`
|
|
17
|
+
|
|
18
|
+
Returns the active ExisJS `Request` object.
|
|
19
|
+
|
|
20
|
+
```typescript filename="src/loaders/index.ts"
|
|
21
|
+
import { defineLoaders, Dataloader } from 'exisjs/dataloader'
|
|
22
|
+
import { getRequest } from 'exisjs/router'
|
|
23
|
+
|
|
24
|
+
export const { loaderMiddleware, getLoaders } = defineLoaders({
|
|
25
|
+
user: () => new Dataloader(
|
|
26
|
+
async (keys: readonly string[]) => {
|
|
27
|
+
// Access the request deep inside the dataloader!
|
|
28
|
+
const req = getRequest()
|
|
29
|
+
console.log(`Batch fetching users for RequestId: ${req.requestId}`)
|
|
30
|
+
|
|
31
|
+
return keys.map(key => db.users.find(key))
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `getResponse()`
|
|
38
|
+
|
|
39
|
+
Returns the active ExisJS `Response` object.
|
|
40
|
+
|
|
41
|
+
```typescript filename="src/utils/auth.ts"
|
|
42
|
+
import { getResponse } from 'exisjs/router'
|
|
43
|
+
|
|
44
|
+
export function forceLogout() {
|
|
45
|
+
const res = getResponse()
|
|
46
|
+
res.clearCookie('session_id')
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `getActiveApp()`
|
|
51
|
+
|
|
52
|
+
Returns the active ExisJS `App` instance processing the request. This is useful for resolving dependencies or accessing the application logger from deep within a service.
|
|
53
|
+
|
|
54
|
+
```typescript filename="src/services/billing.ts"
|
|
55
|
+
import { getActiveApp } from 'exisjs/app'
|
|
56
|
+
|
|
57
|
+
export function processRefund(transactionId: string) {
|
|
58
|
+
const app = getActiveApp()
|
|
59
|
+
const stripe = app.resolve('StripeClient')
|
|
60
|
+
|
|
61
|
+
app.log.info(`Processing refund for ${transactionId}`)
|
|
62
|
+
return stripe.refunds.create({ charge: transactionId })
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Async Context API
|
|
69
|
+
|
|
70
|
+
The core context API allows you to read and write arbitrary state associated with the current HTTP request.
|
|
71
|
+
|
|
72
|
+
### `setContext(key, value)`
|
|
73
|
+
|
|
74
|
+
Sets a value in the current request's state object. This is typically used by middleware to attach parsed data, user sessions, or tenant IDs.
|
|
75
|
+
|
|
76
|
+
```typescript filename="src/middleware/tenant.ts"
|
|
77
|
+
import { setContext } from 'exisjs/router'
|
|
78
|
+
|
|
79
|
+
export async function tenantMiddleware(req, res, next) {
|
|
80
|
+
const tenantId = req.headers['x-tenant-id']
|
|
81
|
+
|
|
82
|
+
// Store the tenant ID in the async context
|
|
83
|
+
setContext('tenantId', tenantId)
|
|
84
|
+
next()
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `getContext<T>()`
|
|
89
|
+
|
|
90
|
+
Retrieves the current request's state object.
|
|
91
|
+
|
|
92
|
+
```typescript filename="src/services/database.ts"
|
|
93
|
+
import { getContext } from 'exisjs/router'
|
|
94
|
+
|
|
95
|
+
function getTenantDatabase() {
|
|
96
|
+
// Retrieve the state set by the middleware!
|
|
97
|
+
const state = getContext<{ tenantId: string }>()
|
|
98
|
+
|
|
99
|
+
return new DatabaseConnection({ schema: state.tenantId })
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Background Tasks
|
|
106
|
+
|
|
107
|
+
### `after(callback)`
|
|
108
|
+
|
|
109
|
+
Queues a function to execute in the background **after** the response has been completely sent to the client. This is the perfect place to put non-blocking tasks like sending welcome emails, flushing analytics, or writing audit logs.
|
|
110
|
+
|
|
111
|
+
```typescript filename="src/http/users/route.ts"
|
|
112
|
+
import { controller, route } from 'exisjs/router'
|
|
113
|
+
import { after } from 'exisjs/router'
|
|
114
|
+
import { tex } from 'exisjs/validator'
|
|
115
|
+
|
|
116
|
+
export default controller({
|
|
117
|
+
createUser: route.post('/', {
|
|
118
|
+
body: tex.object({ email: tex.email() }),
|
|
119
|
+
async handle({ body }) {
|
|
120
|
+
|
|
121
|
+
// Respond to the client immediately
|
|
122
|
+
after(async () => {
|
|
123
|
+
// This runs after the client has received the HTTP response
|
|
124
|
+
await emailService.sendWelcome(body.email)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return { success: true, email: body.email }
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
> [!NOTE]
|
|
135
|
+
> Tasks queued with `after()` do not block the HTTP request. If the task throws an error, it will be logged by the application logger, but it will not crash the server or affect the already-completed client response.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Class-Based Context (OOP)
|
|
140
|
+
|
|
141
|
+
If you are using the Object-Oriented paradigm, ExisJS provides dedicated decorators to inject the request and response directly into your controller methods. This removes the need to manually call `getRequest()` or `getResponse()` in the controller layer.
|
|
142
|
+
|
|
143
|
+
### `@Req()` & `@Res()`
|
|
144
|
+
|
|
145
|
+
Inject the raw ExisJS request or response objects.
|
|
146
|
+
|
|
147
|
+
```typescript filename="src/http/users/route.ts"
|
|
148
|
+
import { Controller, Get, Req, Res } from 'exisjs/decorators'
|
|
149
|
+
import type { Request, Response } from 'exisjs/router'
|
|
150
|
+
|
|
151
|
+
@Controller('/users')
|
|
152
|
+
export default class UserController {
|
|
153
|
+
|
|
154
|
+
@Get('/ip')
|
|
155
|
+
getIp(@Req() req: Request) {
|
|
156
|
+
return { ip: req.ip }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Get('/logout')
|
|
160
|
+
logout(@Res() res: Response) {
|
|
161
|
+
res.clearCookie('session_id')
|
|
162
|
+
res.json({ success: true })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> [!TIP]
|
|
168
|
+
> For deep services inside an OOP architecture, you can still seamlessly use `getContext()`, `getRequest()`, or `after()` from `exisjs/router` to access the async context without prop-drilling!
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Data Loader"
|
|
3
|
+
description: "Strict API Reference for the N+1 batching utility."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Data Loader (`exisjs/dataloader`)
|
|
7
|
+
|
|
8
|
+
This document provides the strict API signatures and TypeScript interfaces exported by the `exisjs/dataloader` module. For high-level guides on solving N+1 query problems, see the [Data Loader Guide](/docs/dataloader).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## `Dataloader<K, V, C>` Class
|
|
13
|
+
|
|
14
|
+
The `Dataloader` class batches and caches requests to a backend data source.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { Dataloader } from 'exisjs/dataloader'
|
|
18
|
+
|
|
19
|
+
class Dataloader<K, V, C = K> {
|
|
20
|
+
constructor(batchFn: BatchLoadFn<K, V>, options?: DataloaderOptions<K, C>)
|
|
21
|
+
|
|
22
|
+
// Enqueues a key to be batched, returning a Promise that resolves to the value
|
|
23
|
+
load(key: K): Promise<V>
|
|
24
|
+
|
|
25
|
+
// Loads multiple keys, returning an array of Promises
|
|
26
|
+
loadMany(keys: readonly K[]): Promise<V[]>
|
|
27
|
+
|
|
28
|
+
// Clears the value at `key` from the cache
|
|
29
|
+
clear(key: K): this
|
|
30
|
+
|
|
31
|
+
// Clears all values from the cache
|
|
32
|
+
clearAll(): this
|
|
33
|
+
|
|
34
|
+
// Primes the cache with a predefined value or Error for a specific key
|
|
35
|
+
prime(key: K, value: V | Error): this
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `BatchLoadFn<K, V>`
|
|
40
|
+
|
|
41
|
+
The function responsible for loading the batched keys. **It must return an array of values (or Errors) that are precisely the same length and in the exact same order as the provided keys array.**
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
type BatchLoadFn<K, V> = (
|
|
45
|
+
keys: readonly K[]
|
|
46
|
+
) => Promise<readonly (V | Error)[]>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `DataloaderOptions<K, C>`
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface DataloaderOptions<K, C = K> {
|
|
53
|
+
// Whether to cache resolved keys within this Dataloader instance. Default: true
|
|
54
|
+
cache?: boolean
|
|
55
|
+
|
|
56
|
+
// The maximum number of keys to include in a single batch. Default: 1000
|
|
57
|
+
maxBatchSize?: number
|
|
58
|
+
|
|
59
|
+
// A function to generate a unique cache key from the provided load key
|
|
60
|
+
cacheKeyFn?: (key: K) => C
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Context Registry (`defineLoaders`)
|
|
67
|
+
|
|
68
|
+
Because `Dataloader` instances are inherently stateful (they cache data), they **must not** be shared across different HTTP requests, or else users might read each other's cached data.
|
|
69
|
+
|
|
70
|
+
The `defineLoaders` function securely isolates Dataloader instances per-request by binding them to the ExisJS asynchronous context.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { defineLoaders } from 'exisjs/dataloader'
|
|
74
|
+
|
|
75
|
+
function defineLoaders<
|
|
76
|
+
T extends Record<string, () => Dataloader<any, any>>
|
|
77
|
+
>(factories: T): {
|
|
78
|
+
// Middleware to register onto your router/app
|
|
79
|
+
loaderMiddleware: (req, res, next) => void;
|
|
80
|
+
|
|
81
|
+
// Hook to retrieve the isolated loaders anywhere in the request lifecycle
|
|
82
|
+
getLoaders: () => { [K in keyof T]: ReturnType<T[K]> }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Usage Signature
|
|
87
|
+
```typescript
|
|
88
|
+
const { loaderMiddleware, getLoaders } = defineLoaders({
|
|
89
|
+
userLoader: () => new Dataloader(async (keys) => { /* ... */ })
|
|
90
|
+
})
|
|
91
|
+
```
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Decorators"
|
|
3
|
+
description: "API Reference for ExisJS Class-Based Routing and Decorators."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Decorators (OOP)
|
|
7
|
+
|
|
8
|
+
ExisJS provides a robust Object-Oriented Programming (OOP) paradigm via the `exisjs/decorators` module.
|
|
9
|
+
|
|
10
|
+
Unlike many other frameworks (such as NestJS or TypeDI), ExisJS decorators are built completely on standard ECMAScript decorators and **do not require `reflect-metadata`**. This makes them incredibly lightweight, extremely fast, and completely free of legacy metadata bloat.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Controller & Routing
|
|
15
|
+
|
|
16
|
+
### `@Controller(prefixOrOptions?)`
|
|
17
|
+
|
|
18
|
+
Marks a class as a Controller. All route handler methods within the class will be grouped under the specified prefix.
|
|
19
|
+
|
|
20
|
+
```typescript filename="src/http/users/route.ts"
|
|
21
|
+
import { Controller } from 'exisjs/decorators'
|
|
22
|
+
|
|
23
|
+
// Routes will be mounted at /api/users
|
|
24
|
+
@Controller('/api/users')
|
|
25
|
+
export default class UserController {
|
|
26
|
+
// ...
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### HTTP Methods
|
|
31
|
+
Defines an HTTP route handler on a class method. You can optionally pass a schema to validate incoming requests.
|
|
32
|
+
|
|
33
|
+
- `@Get(path?, schema?)`
|
|
34
|
+
- `@Post(path?, schema?)`
|
|
35
|
+
- `@Put(path?, schema?)`
|
|
36
|
+
- `@Patch(path?, schema?)`
|
|
37
|
+
- `@Delete(path?, schema?)`
|
|
38
|
+
- `@Options(path?, schema?)`
|
|
39
|
+
- `@Head(path?, schema?)`
|
|
40
|
+
- `@Connect(path?, schema?)`
|
|
41
|
+
- `@Trace(path?, schema?)`
|
|
42
|
+
- `@All(path?, schema?)`
|
|
43
|
+
- `@Query(path?, schema?)`: Can be used as a method decorator to handle custom `QUERY` method requests (e.g., RPC or GraphQL-like endpoints).
|
|
44
|
+
|
|
45
|
+
```typescript filename="src/http/users/route.ts"
|
|
46
|
+
import { Controller, Get, Post } from 'exisjs/decorators'
|
|
47
|
+
import { tex } from 'exisjs/validator'
|
|
48
|
+
|
|
49
|
+
@Controller('/users')
|
|
50
|
+
export default class UserController {
|
|
51
|
+
@Get('/:id')
|
|
52
|
+
getUser() {
|
|
53
|
+
return { id: 1 }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Post('/', { body: tex.object({ name: tex.string() }) })
|
|
57
|
+
createUser() {
|
|
58
|
+
return { success: true }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Real-Time Methods
|
|
64
|
+
- `@Ws(path?, schema?)`: Maps the handler to a WebSocket connection.
|
|
65
|
+
- `@Sse(path?, schema?)`: Maps the handler to a Server-Sent Events stream.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 2. Parameter Injection
|
|
70
|
+
|
|
71
|
+
Parameter decorators automatically extract data from the incoming request and inject them directly into your method arguments, drastically reducing boilerplate.
|
|
72
|
+
|
|
73
|
+
- `@Body()`: Parses and injects the JSON request body.
|
|
74
|
+
- `@Param(name?)`: Injects a URL path parameter (e.g., `id` from `/:id`).
|
|
75
|
+
- `@Query(name?)`: Injects a query string parameter. (Note: `@Query` doubles as a route decorator if used on a method).
|
|
76
|
+
- `@Headers(name?)`: Injects an HTTP header by name.
|
|
77
|
+
- `@HostParam(name?)`: Injects a subdomain parameter (e.g., extracted from `@Hosts(':tenant.example.com')`).
|
|
78
|
+
- `@Req()`: Injects the raw ExisJS Request object.
|
|
79
|
+
- `@Res(options?)`: Injects the raw ExisJS Response object. Use `@Res({ passthrough: true })` if you want ExisJS to still handle the final response serialization.
|
|
80
|
+
- `@Ip()`: Injects the client's IP address.
|
|
81
|
+
- `@Session()`: Injects the active session object (requires session middleware).
|
|
82
|
+
- `@Next()`: Injects the Next function.
|
|
83
|
+
- `@Socket()`: Injects the active WebSocket instance (for `@Ws`).
|
|
84
|
+
- `@Stream()`: Injects the active SSE instance (for `@Sse`).
|
|
85
|
+
- `@UploadedFile() / @UploadedFiles()`: Injects multipart/form-data files.
|
|
86
|
+
|
|
87
|
+
```typescript filename="src/http/users/route.ts"
|
|
88
|
+
import { Controller, Get, Post, Param, Body, Ip } from 'exisjs/decorators'
|
|
89
|
+
|
|
90
|
+
@Controller('/users')
|
|
91
|
+
export default class UserController {
|
|
92
|
+
@Get('/:id')
|
|
93
|
+
getUser(@Param('id') id: string, @Ip() ip: string) {
|
|
94
|
+
return { id, requestedBy: ip }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Post('/')
|
|
98
|
+
createUser(@Body() body: { email: string }) {
|
|
99
|
+
return { email: body.email }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 3. Middleware & Configuration
|
|
107
|
+
|
|
108
|
+
### `@Use(...middlewares)`
|
|
109
|
+
Applies Express-style middleware functions to a specific route handler or an entire controller class.
|
|
110
|
+
|
|
111
|
+
```typescript filename="src/http/secure/route.ts"
|
|
112
|
+
import { Controller, Get, Use } from 'exisjs/decorators'
|
|
113
|
+
import { authMiddleware } from '../middleware/auth'
|
|
114
|
+
|
|
115
|
+
@Controller('/secure')
|
|
116
|
+
@Use(authMiddleware)
|
|
117
|
+
export default class SecureController {
|
|
118
|
+
@Get('/')
|
|
119
|
+
getSecureData() {
|
|
120
|
+
return { secure: true }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `@Cache(options)`
|
|
126
|
+
Automatically caches `GET` responses in memory or Redis.
|
|
127
|
+
|
|
128
|
+
### `@Idempotent(options)`
|
|
129
|
+
Enforces idempotency based on the `Idempotency-Key` header, preventing duplicate mutations (e.g., duplicate payments).
|
|
130
|
+
|
|
131
|
+
```typescript filename="src/http/payments/route.ts"
|
|
132
|
+
import { Controller, Post, Idempotent } from 'exisjs/decorators'
|
|
133
|
+
|
|
134
|
+
@Controller('/payments')
|
|
135
|
+
export default class PaymentController {
|
|
136
|
+
@Post('/checkout')
|
|
137
|
+
@Idempotent()
|
|
138
|
+
checkout() {
|
|
139
|
+
return { status: 'processing' }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 4. Responses & Metadata
|
|
147
|
+
|
|
148
|
+
### `@HttpCode(code)`
|
|
149
|
+
Forces a specific HTTP status code for successful responses.
|
|
150
|
+
|
|
151
|
+
### `@Header(name, value)`
|
|
152
|
+
Automatically injects a specific response header before sending the response.
|
|
153
|
+
|
|
154
|
+
### `@Redirect(url, statusCode?)`
|
|
155
|
+
Redirects the request to the specified URL.
|
|
156
|
+
|
|
157
|
+
### `@Returns(schema)`
|
|
158
|
+
Explicitly defines the response schema for OpenAPI (Swagger) generation.
|
|
159
|
+
|
|
160
|
+
```typescript filename="src/http/documents/route.ts"
|
|
161
|
+
import { Controller, Post, HttpCode, Header } from 'exisjs/decorators'
|
|
162
|
+
|
|
163
|
+
@Controller('/documents')
|
|
164
|
+
export default class DocumentController {
|
|
165
|
+
@Post('/')
|
|
166
|
+
@HttpCode(201)
|
|
167
|
+
@Header('X-Custom-Status', 'Processed')
|
|
168
|
+
createDoc() {
|
|
169
|
+
return { created: true }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 5. Security & Lifecycle
|
|
177
|
+
|
|
178
|
+
These decorators allow you to attach ExisJS lifecycle hooks to your OOP routes.
|
|
179
|
+
|
|
180
|
+
- `@Permissions(...flags)`: Restricts the route to users with specific permission flags.
|
|
181
|
+
- `@Hosts(...hosts)`: Restricts the route to a specific subdomain/host.
|
|
182
|
+
- `@UseGuards(...guards)`: Attaches Guards (authentication/authorization checks).
|
|
183
|
+
- `@UseInterceptors(...interceptors)`: Attaches Interceptors (request/response mutation).
|
|
184
|
+
- `@UseFilters(...filters)`: Attaches Exception Filters (custom error handling).
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 6. Global Setup
|
|
189
|
+
|
|
190
|
+
### `@Server(options?)`
|
|
191
|
+
Marks a class as the root Server entry point. Used to register global configuration and dependencies.
|
|
192
|
+
|
|
193
|
+
**Options Interface:**
|
|
194
|
+
```typescript
|
|
195
|
+
interface ServerConfig {
|
|
196
|
+
plugins?: any[]
|
|
197
|
+
providers?: any[]
|
|
198
|
+
cron?: any[]
|
|
199
|
+
queue?: any[]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```typescript filename="src/http/server.ts"
|
|
204
|
+
import { Server } from 'exisjs/decorators'
|
|
205
|
+
import { corsPlugin } from 'exisjs/middleware'
|
|
206
|
+
|
|
207
|
+
@Server({
|
|
208
|
+
plugins: [corsPlugin]
|
|
209
|
+
})
|
|
210
|
+
export default class RootServer {
|
|
211
|
+
async onStart(app) {
|
|
212
|
+
app.log.info("App booted!")
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### `@Gateway(options?)`
|
|
218
|
+
Marks a class as a Gateway module. Gateways are used to apply massive configuration blocks across an entire directory of routes (e.g., applying an auth guard to all routes inside `src/http/admin`).
|
|
219
|
+
|
|
220
|
+
**Options Interface:**
|
|
221
|
+
```typescript
|
|
222
|
+
interface GatewayConfig {
|
|
223
|
+
exclude?: any[]
|
|
224
|
+
middleware?: any[]
|
|
225
|
+
filters?: any[]
|
|
226
|
+
guards?: any[]
|
|
227
|
+
interceptors?: any[]
|
|
228
|
+
timeout?: number | ((req: any) => number)
|
|
229
|
+
metadata?: Record<string, any>
|
|
230
|
+
cors?: any
|
|
231
|
+
headers?: Record<string, string>
|
|
232
|
+
imports?: any[]
|
|
233
|
+
providers?: any[]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```typescript filename="src/http/gateway.ts"
|
|
238
|
+
import { Gateway } from 'exisjs/decorators'
|
|
239
|
+
import { helmet, hpp } from 'exisjs/middleware'
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Root Gateway (gatekeeper for the entire application)
|
|
243
|
+
*
|
|
244
|
+
* Gateways apply configuration to all routes within their directory and subdirectories.
|
|
245
|
+
* This acts as the global security and infrastructure layer.
|
|
246
|
+
*/
|
|
247
|
+
@Gateway({
|
|
248
|
+
// 1. Cross-Origin Resource Sharing (CORS)
|
|
249
|
+
cors: {
|
|
250
|
+
origin: ['https://myapp.com', /localhost:\d+/],
|
|
251
|
+
credentials: true,
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// 2. Global Headers
|
|
255
|
+
headers: {
|
|
256
|
+
'X-Powered-By': 'ExisJS',
|
|
257
|
+
'X-Application-Version': '1.0.0',
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// 3. Global Middleware
|
|
261
|
+
middleware: [
|
|
262
|
+
helmet(), // Sets secure HTTP headers
|
|
263
|
+
hpp(), // Prevents HTTP Parameter Pollution
|
|
264
|
+
],
|
|
265
|
+
|
|
266
|
+
// 4. Exclude Paths
|
|
267
|
+
exclude: [{ path: '/health', method: 'GET' }],
|
|
268
|
+
|
|
269
|
+
// 5. Dependency Injection Providers
|
|
270
|
+
providers: [
|
|
271
|
+
['ConfigService', { useValue: { apiKey: process.env.API_KEY || 'default' } }],
|
|
272
|
+
],
|
|
273
|
+
|
|
274
|
+
// 6. Global Metadata
|
|
275
|
+
metadata: {
|
|
276
|
+
tenant: 'root',
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// 7. Request Timeout
|
|
280
|
+
timeout: 10000,
|
|
281
|
+
})
|
|
282
|
+
export default class RootGateway {}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### `@Injectable(options?)`
|
|
286
|
+
Marks a class as a provider for Dependency Injection, meaning it can be automatically injected into controllers or other services by the IoC container.
|
|
287
|
+
|
|
288
|
+
**Options Interface:**
|
|
289
|
+
```typescript
|
|
290
|
+
interface InjectableOptions {
|
|
291
|
+
scope?: 'singleton' | 'request'
|
|
292
|
+
}
|
|
293
|
+
```
|