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,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/integrations/s3"
|
|
3
|
+
nextTitle: "AWS S3"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Swagger OpenAPI
|
|
7
|
+
|
|
8
|
+
ExisJS comes with a native **Swagger UI & OpenAPI 3.0** generator built directly into the framework. It parses your routes, path parameters, and schema validations to generate an API explorer.
|
|
9
|
+
|
|
10
|
+
You do **not** need to maintain OpenAPI YAML files or write decorators.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
To enable Swagger UI, simply import `serveSwagger` and call it inside the `onStart` hook in your `server.ts`.
|
|
17
|
+
|
|
18
|
+
```typescript filename="src/http/server.ts"
|
|
19
|
+
import { exis } from 'exisjs'
|
|
20
|
+
import { serveSwagger } from 'exisjs/swagger'
|
|
21
|
+
|
|
22
|
+
export default exis({
|
|
23
|
+
async onStart(app) {
|
|
24
|
+
// Generate and mount Swagger UI
|
|
25
|
+
serveSwagger(app, {
|
|
26
|
+
path: '/api-docs', // Defaults to '/docs'
|
|
27
|
+
title: 'My Startup API',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
components: {
|
|
30
|
+
securitySchemes: {
|
|
31
|
+
bearerAuth: {
|
|
32
|
+
type: 'http',
|
|
33
|
+
scheme: 'bearer',
|
|
34
|
+
bearerFormat: 'JWT'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Once your server starts, navigate to `http://localhost:4000/api-docs` to see your auto-generated Swagger UI. The raw OpenAPI JSON payload will be available at `/api-docs/json`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Automatic Schema Generation
|
|
48
|
+
|
|
49
|
+
ExisJS integrates with its internal `v` validator. Any validation schemas attached to your routes (whether Functional or OOP) are automatically translated into OpenAPI specifications.
|
|
50
|
+
|
|
51
|
+
#### Functional Paradigm
|
|
52
|
+
|
|
53
|
+
```typescript filename="src/http/users/route.ts"
|
|
54
|
+
import { controller, route } from 'exisjs/router'
|
|
55
|
+
import { tex } from 'exisjs/validator'
|
|
56
|
+
|
|
57
|
+
export default controller({
|
|
58
|
+
createUser: route.post('/', {
|
|
59
|
+
// This schema is automatically translated to OpenAPI request body definitions
|
|
60
|
+
body: tex.object({
|
|
61
|
+
name: tex.string(),
|
|
62
|
+
age: tex.number({ optional: true })
|
|
63
|
+
}),
|
|
64
|
+
async handle(req) {
|
|
65
|
+
return { success: true, name: req.body.name }
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Class-Based (OOP) Paradigm
|
|
72
|
+
|
|
73
|
+
```typescript filename="src/http/users/route.ts"
|
|
74
|
+
import { Controller, Post, Body } from 'exisjs/decorators'
|
|
75
|
+
import { tex } from 'exisjs/validator'
|
|
76
|
+
|
|
77
|
+
const CreateUserSchema = tex.object({
|
|
78
|
+
name: tex.string(),
|
|
79
|
+
age: tex.number({ optional: true })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
@Controller('/users')
|
|
83
|
+
export default class UsersController {
|
|
84
|
+
@Post('/', { body: CreateUserSchema })
|
|
85
|
+
async createUser(@Body() body: any) {
|
|
86
|
+
return { success: true, name: body.name }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
When you view Swagger UI, the `POST /users` endpoint will show the JSON body required, properly displaying the string/number types and required/optional flags.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Gateway Cascading Metadata
|
|
96
|
+
|
|
97
|
+
If you want to apply OpenAPI tags or security schemas to an entire folder of routes without decorating every endpoint, you can use ExisJS Gateways.
|
|
98
|
+
|
|
99
|
+
```typescript filename="src/http/admin/gateway.ts"
|
|
100
|
+
import { defineGateway } from 'exisjs/router'
|
|
101
|
+
|
|
102
|
+
export default defineGateway({
|
|
103
|
+
// Every route in the /admin folder automatically inherits these OpenAPI tags
|
|
104
|
+
metadata: {
|
|
105
|
+
openapi: {
|
|
106
|
+
tags: ['Admin Management API'],
|
|
107
|
+
security: ['bearerAuth']
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Now, every route under `/admin/*` will be grouped under the "Admin Management API" section in your Swagger UI.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Adapters"
|
|
3
|
+
description: "Strict API Reference for all Serverless and Edge adapters."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adapters (`exisjs/adapters`)
|
|
7
|
+
|
|
8
|
+
This document provides the strict API signatures and TypeScript interfaces exported by the `exisjs/adapters` module. For high-level deployment guides, see the [Adapters Guide](/docs/adapters).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## `aws`
|
|
13
|
+
|
|
14
|
+
Converts API Gateway events into a native Node.js HTTP stream.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
function aws(app: App): (event: APIGatewayEvent) => Promise<APIGatewayResult>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### `APIGatewayEvent`
|
|
21
|
+
```typescript
|
|
22
|
+
interface APIGatewayEvent {
|
|
23
|
+
httpMethod: string;
|
|
24
|
+
path: string;
|
|
25
|
+
headers: Record<string, string | undefined>;
|
|
26
|
+
body: string | null;
|
|
27
|
+
isBase64Encoded: boolean;
|
|
28
|
+
queryStringParameters?: Record<string, string | undefined>;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `APIGatewayResult`
|
|
33
|
+
```typescript
|
|
34
|
+
interface APIGatewayResult {
|
|
35
|
+
statusCode: number;
|
|
36
|
+
headers: Record<string, string>;
|
|
37
|
+
body: string;
|
|
38
|
+
isBase64Encoded: boolean;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## `vercel`
|
|
45
|
+
|
|
46
|
+
Provides a bridge between Vercel's Node.js Serverless runtime and ExisJS.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
function vercel(
|
|
50
|
+
app: App,
|
|
51
|
+
options?: { cwd?: string }
|
|
52
|
+
): (req: VercelRequest, res: VercelResponse) => void | Promise<void>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `VercelRequest` & `VercelResponse`
|
|
56
|
+
These are re-exported from `@vercel/node`. The adapter intercepts these and patches the internal socket state to satisfy the ExisJS pipeline.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## `fetch`
|
|
61
|
+
|
|
62
|
+
The universal Web Standard adapter. Converts standard Web `Request` objects into internal HTTP pipelines and resolves back to a Web `Response`.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function fetch(
|
|
66
|
+
app: App
|
|
67
|
+
): (request: Request, env?: any, ctx?: any) => Promise<Response>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
*Note: The `env` and `ctx` arguments are seamlessly injected into the ExisJS routing context, allowing you to access platform-specific bindings (e.g. Cloudflare KV).*
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## `cloudflare`
|
|
75
|
+
|
|
76
|
+
A dedicated wrapper for Cloudflare Workers that returns the expected ES Module format.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
function cloudflare(
|
|
80
|
+
app: App
|
|
81
|
+
): { fetch: (request: Request, env: any, ctx: ExecutionContext) => Promise<Response> }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## `bun`
|
|
87
|
+
|
|
88
|
+
A dedicated wrapper for the `Bun.serve()` configuration object.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
function bun(
|
|
92
|
+
app: App
|
|
93
|
+
): { fetch: (request: Request) => Promise<Response> }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## `deno`
|
|
99
|
+
|
|
100
|
+
A dedicated wrapper for `Deno.serve()`.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
function deno(
|
|
104
|
+
app: App
|
|
105
|
+
): (request: Request) => Promise<Response>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## `netlify`
|
|
111
|
+
|
|
112
|
+
A dedicated wrapper for Netlify Edge Functions.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
function netlify(
|
|
116
|
+
app: App
|
|
117
|
+
): (request: Request, context: NetlifyContext) => Promise<Response>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## `fastly`
|
|
123
|
+
|
|
124
|
+
A dedicated wrapper for Fastly Compute@Edge which binds to the global `addEventListener` fetch event.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
function fastly(
|
|
128
|
+
app: App
|
|
129
|
+
): (event: FetchEvent) => void
|
|
130
|
+
```
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Authentication"
|
|
3
|
+
description: "API Reference for ExisJS Authentication utilities (`exisjs/auth`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authentication (`exisjs/auth`)
|
|
7
|
+
|
|
8
|
+
ExisJS ships with a completely native, dependency-free `auth` module that covers the most critical security implementations: Scrypt Password Hashing, JWT (JSON Web Tokens), Session Management, and Role-Based Access Control (RBAC).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Password Hashing (Scrypt)
|
|
13
|
+
|
|
14
|
+
ExisJS natively uses Node's highly secure `scrypt` algorithm to hash passwords. It automatically generates a cryptographically secure salt and stores the parameters in a single string format (`scrypt:N:r:p:keylen:salt:hash`) so you never have to manage them separately.
|
|
15
|
+
|
|
16
|
+
```typescript filename="src/services/AuthService.ts"
|
|
17
|
+
import { Password } from 'exisjs/auth'
|
|
18
|
+
|
|
19
|
+
// Hashing a new password
|
|
20
|
+
const hashedPassword = await Password.hashPassword('my-super-secret-password', {
|
|
21
|
+
cost: 16384, // Higher cost = harder to brute force, but slower to compute
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Verifying a login attempt
|
|
25
|
+
const isValid = await Password.verifyPassword('my-super-secret-password', hashedPassword)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. JSON Web Tokens (JWT)
|
|
31
|
+
|
|
32
|
+
You can instantly sign and verify JWTs without needing third-party libraries like `jsonwebtoken`.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { JWT } from 'exisjs/auth'
|
|
36
|
+
|
|
37
|
+
// Sign a token valid for 2 hours
|
|
38
|
+
const token = JWT.signJWT(
|
|
39
|
+
{ userId: '123', role: 'admin' },
|
|
40
|
+
process.env.JWT_SECRET,
|
|
41
|
+
{ expiresIn: 2 * 60 * 60 }
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Verifying token returns the exact typed payload
|
|
46
|
+
const payload = JWT.verifyJWT<{ userId: string, role: string }>(token, process.env.JWT_SECRET)
|
|
47
|
+
console.log(payload.userId)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err.name === 'TokenExpiredError') {
|
|
50
|
+
// Handle expired token
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 3. Session Middleware
|
|
58
|
+
|
|
59
|
+
If you prefer stateful authentication, ExisJS provides extremely robust session middleware out of the box. It leverages signed HTTP-only cookies to track users and automatically synchronizes session state back to the store (Memory, Redis, etc.) when the request ends.
|
|
60
|
+
|
|
61
|
+
### Global Setup via Plugin (`exis.config.ts`)
|
|
62
|
+
|
|
63
|
+
Because ExisJS utilizes a file-based architecture with no manual server entry point, global middlewares like `session` should be attached using a Plugin.
|
|
64
|
+
|
|
65
|
+
```typescript filename="src/plugins/session.ts"
|
|
66
|
+
import { definePlugin } from 'exisjs/plugin'
|
|
67
|
+
import { session } from 'exisjs/auth'
|
|
68
|
+
|
|
69
|
+
export const sessionPlugin = definePlugin({
|
|
70
|
+
name: 'global-session',
|
|
71
|
+
register(app) {
|
|
72
|
+
app.use(session({
|
|
73
|
+
secret: process.env.SESSION_SECRET || 'fallback-secret-minimum-32-chars-long!',
|
|
74
|
+
cookieName: 'exis_sid',
|
|
75
|
+
ttl: 86400 * 1000, // 1 day in milliseconds
|
|
76
|
+
// store: new RedisSessionStore(...)
|
|
77
|
+
}))
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then, natively register your plugin in `exis.config.ts`:
|
|
83
|
+
|
|
84
|
+
```typescript filename="exis.config.ts"
|
|
85
|
+
import { defineConfig } from 'exisjs/config'
|
|
86
|
+
import { sessionPlugin } from './src/plugins/session'
|
|
87
|
+
|
|
88
|
+
export default defineConfig({
|
|
89
|
+
plugins: [
|
|
90
|
+
sessionPlugin()
|
|
91
|
+
]
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Once registered, you can read and write to `req.session` anywhere in your application!
|
|
96
|
+
|
|
97
|
+
### Functional Example
|
|
98
|
+
|
|
99
|
+
```typescript filename="src/http/auth/route.ts"
|
|
100
|
+
import { controller, route } from 'exisjs/router'
|
|
101
|
+
import { HttpError } from 'exisjs/error'
|
|
102
|
+
|
|
103
|
+
export default controller({
|
|
104
|
+
login: route.post('/login', {
|
|
105
|
+
async handle(ctx) {
|
|
106
|
+
const user = await db.users.findByEmail(ctx.body.email)
|
|
107
|
+
if (!user) throw HttpError.unauthorized('Invalid credentials')
|
|
108
|
+
|
|
109
|
+
// Login the user by attaching data to the session
|
|
110
|
+
ctx.req.session.userId = user.id
|
|
111
|
+
ctx.req.session.role = user.role
|
|
112
|
+
|
|
113
|
+
return { success: true }
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Class-Based (OOP) Example
|
|
120
|
+
|
|
121
|
+
```typescript filename="src/http/auth/route.ts"
|
|
122
|
+
import { Controller, Post, Body, Req } from 'exisjs/decorators'
|
|
123
|
+
import type { Request } from 'exisjs'
|
|
124
|
+
|
|
125
|
+
@Controller('/auth')
|
|
126
|
+
export default class AuthController {
|
|
127
|
+
|
|
128
|
+
@Post('/login')
|
|
129
|
+
async login(@Body() body: any, @Req() req: Request) {
|
|
130
|
+
const user = await db.users.findByEmail(body.email)
|
|
131
|
+
|
|
132
|
+
// Attach to session
|
|
133
|
+
req.session.userId = user.id
|
|
134
|
+
req.session.role = user.role
|
|
135
|
+
|
|
136
|
+
return { success: true }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 4. Role-Based Access Control (RBAC)
|
|
144
|
+
|
|
145
|
+
The `requireRole` middleware checks if the current authenticated user has the necessary permissions to access a route.
|
|
146
|
+
|
|
147
|
+
*Note: This middleware expects that `req.user.role` was populated by an earlier authentication middleware (like a JWT Verifier or a Session Restorer).*
|
|
148
|
+
|
|
149
|
+
### Functional Example
|
|
150
|
+
|
|
151
|
+
```typescript filename="src/http/admin/route.ts"
|
|
152
|
+
import { controller, route } from 'exisjs/router'
|
|
153
|
+
import { requireRole } from 'exisjs/auth'
|
|
154
|
+
|
|
155
|
+
export default controller({
|
|
156
|
+
getDashboard: route.get('/dashboard', {
|
|
157
|
+
middleware: [
|
|
158
|
+
// Only users with 'admin' or 'superadmin' roles can access this route
|
|
159
|
+
requireRole(['admin', 'superadmin'])
|
|
160
|
+
],
|
|
161
|
+
async handle() {
|
|
162
|
+
return { metrics: [] }
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Class-Based (OOP) Example
|
|
169
|
+
|
|
170
|
+
```typescript filename="src/http/admin/route.ts"
|
|
171
|
+
import { Controller, Get, Use } from 'exisjs/decorators'
|
|
172
|
+
import { requireRole } from 'exisjs/auth'
|
|
173
|
+
|
|
174
|
+
@Controller('/admin')
|
|
175
|
+
export default class AdminController {
|
|
176
|
+
|
|
177
|
+
@Get('/dashboard')
|
|
178
|
+
@Use(requireRole(['admin', 'superadmin']))
|
|
179
|
+
async getDashboard() {
|
|
180
|
+
return { metrics: [] }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Caching"
|
|
3
|
+
description: "API Reference for ExisJS Caching and Revalidation (`exisjs/cache`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Caching (`exisjs/cache`)
|
|
7
|
+
|
|
8
|
+
ExisJS comes with a highly sophisticated built-in caching system that natively supports **stale-while-revalidate** semantics, intelligent `tags`-based cache invalidation, and multiple backing stores (Memory, FileSystem, and Redis).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## `cacheMiddleware(options)`
|
|
13
|
+
|
|
14
|
+
The primary way to use the cache is by attaching the `cacheMiddleware` directly to your GET routes. It intercepts responses, stores them in the cache, and serves them blazingly fast on subsequent identical requests.
|
|
15
|
+
|
|
16
|
+
ExisJS automatically injects an `X-Exis-Cache: HIT` or `X-Exis-Cache: MISS` header into the HTTP response so you can debug your cache behavior instantly!
|
|
17
|
+
|
|
18
|
+
**Class-Based (OOP)**
|
|
19
|
+
```typescript filename="src/http/stats/route.ts"
|
|
20
|
+
import { Controller, Get, Use } from 'exisjs/decorators'
|
|
21
|
+
import { cacheMiddleware } from 'exisjs/cache'
|
|
22
|
+
|
|
23
|
+
@Controller('/stats')
|
|
24
|
+
export default class StatsController {
|
|
25
|
+
|
|
26
|
+
@Get('/')
|
|
27
|
+
@Use(
|
|
28
|
+
cacheMiddleware({
|
|
29
|
+
ttlMs: 60 * 1000, // 60 seconds
|
|
30
|
+
keyGenerator: (req) => `stats:${req.query.period || 'all'}`
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
async getStats() {
|
|
34
|
+
return db.queryHeavyStats()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Functional**
|
|
40
|
+
```typescript filename="src/http/stats/route.ts"
|
|
41
|
+
import { controller, route } from 'exisjs/router'
|
|
42
|
+
import { cacheMiddleware } from 'exisjs/cache'
|
|
43
|
+
|
|
44
|
+
export default controller({
|
|
45
|
+
getStats: route.get('/', {
|
|
46
|
+
middleware: [
|
|
47
|
+
cacheMiddleware({
|
|
48
|
+
ttlMs: 60 * 1000, // 60 seconds
|
|
49
|
+
|
|
50
|
+
// You MUST provide a keyGenerator to prevent data leaking between users!
|
|
51
|
+
keyGenerator: (req) => `stats:${req.query.period || 'all'}`
|
|
52
|
+
})
|
|
53
|
+
],
|
|
54
|
+
async handle() {
|
|
55
|
+
// This heavy query only runs once every 60 seconds per period
|
|
56
|
+
return db.queryHeavyStats()
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Options
|
|
63
|
+
|
|
64
|
+
- **`keyGenerator`** `(req => string)`: **Required.** Generates a unique cache key for the request. Never use a static key if the response varies by User ID or Query Parameters.
|
|
65
|
+
- **`ttlMs`** `(number)`: Time to live in milliseconds. If omitted, the cache never inherently expires based on time (relies on manual tags instead).
|
|
66
|
+
- **`tags`** `(string[] | function)`: A list of tags to associate with this cache entry, used for Next.js-style on-demand revalidation.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Tag-Based Cache Invalidation
|
|
71
|
+
|
|
72
|
+
Instead of waiting for a TTL to expire, you can invalidate caches instantly using tags. This is exactly how Next.js App Router caching works.
|
|
73
|
+
|
|
74
|
+
When you cache a route, assign it a tag:
|
|
75
|
+
|
|
76
|
+
```typescript filename="src/http/products/route.ts"
|
|
77
|
+
import { cacheMiddleware } from 'exisjs/cache'
|
|
78
|
+
|
|
79
|
+
route.get('/:id', {
|
|
80
|
+
middleware: [
|
|
81
|
+
cacheMiddleware({
|
|
82
|
+
// We tag this cache entry with the specific product ID
|
|
83
|
+
tags: (req) => [`product-${req.params.id}`, 'all-products'],
|
|
84
|
+
keyGenerator: (req) => `product:${req.params.id}`
|
|
85
|
+
})
|
|
86
|
+
],
|
|
87
|
+
async handle(ctx) {
|
|
88
|
+
return db.products.find(ctx.params.id)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Later, when a user updates the product via a `POST` or `PUT` route, you can instantly bust all caches tied to that tag using `revalidateTag()`!
|
|
94
|
+
|
|
95
|
+
```typescript filename="src/http/products/route.ts"
|
|
96
|
+
import { revalidateTag } from 'exisjs/cache'
|
|
97
|
+
|
|
98
|
+
route.put('/:id', {
|
|
99
|
+
async handle(ctx) {
|
|
100
|
+
await db.products.update(ctx.params.id, ctx.body)
|
|
101
|
+
|
|
102
|
+
// Instantly purge the cache across the entire cluster!
|
|
103
|
+
await revalidateTag(`product-${ctx.params.id}`)
|
|
104
|
+
|
|
105
|
+
return { success: true }
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Cache Drivers
|
|
113
|
+
|
|
114
|
+
ExisJS natively provides three driver implementations:
|
|
115
|
+
|
|
116
|
+
1. **`FileSystemCacheStore`**: (Default). Writes cache data to `.exis/cache/*.json` on the disk. Extremely fast for static data and survives server reboots.
|
|
117
|
+
2. **`MemoryCacheStore`**: Keeps caches purely in RAM. Perfect for development or extremely high-throughput, short-TTL data. (Includes automatic broadcast sync if running in an ExisJS Cluster).
|
|
118
|
+
3. **`RedisCacheStore`**: The standard for production. Uses a minimal Redis client to handle distributed caching across massive fleets of servers.
|
|
119
|
+
|
|
120
|
+
### Switching Stores Globally
|
|
121
|
+
|
|
122
|
+
While you can pass a `store` option directly into `cacheMiddleware()`, it is usually better to set the global cache store during application boot:
|
|
123
|
+
|
|
124
|
+
```typescript filename="src/server.ts"
|
|
125
|
+
import { exis } from 'exisjs'
|
|
126
|
+
import { setCacheStore, RedisCacheStore } from 'exisjs/cache'
|
|
127
|
+
|
|
128
|
+
const app = exis()
|
|
129
|
+
|
|
130
|
+
// Use Redis instead of the default FileSystem cache
|
|
131
|
+
setCacheStore(new RedisCacheStore(myRedisClient))
|
|
132
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Circuit Breaker"
|
|
3
|
+
description: "Strict API Reference for the Circuit Breaker resilience utility."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Circuit Breaker (`exisjs/app`)
|
|
7
|
+
|
|
8
|
+
This document provides the strict API signatures and TypeScript interfaces exported for the `CircuitBreaker` utility. For high-level resilience guides, see the [Circuit Breaker Guide](/docs/circuit-breaker).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## `CircuitBreaker` Class
|
|
13
|
+
|
|
14
|
+
The `CircuitBreaker` class is a state machine that monitors the execution of async actions to prevent cascading failures.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { CircuitBreaker } from 'exisjs/app'
|
|
18
|
+
|
|
19
|
+
class CircuitBreaker {
|
|
20
|
+
constructor(options?: CircuitBreakerOptions)
|
|
21
|
+
|
|
22
|
+
// The current state of the breaker
|
|
23
|
+
public state: CircuitState
|
|
24
|
+
|
|
25
|
+
// Executes the provided async action through the breaker
|
|
26
|
+
fire<T>(action: () => Promise<T>): Promise<T>
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### `CircuitBreakerOptions`
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
interface CircuitBreakerOptions {
|
|
34
|
+
/** Maximum number of sequential failures before the breaker transitions to OPEN */
|
|
35
|
+
failureThreshold?: number // Default: 5
|
|
36
|
+
|
|
37
|
+
/** Time to wait (in ms) before transitioning from OPEN to HALF_OPEN to test recovery */
|
|
38
|
+
resetTimeoutMs?: number // Default: 10000 (10 seconds)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## `CircuitState` Enum
|
|
45
|
+
|
|
46
|
+
The circuit breaker operates in one of three states.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
enum CircuitState {
|
|
50
|
+
// Normal operation, all requests pass
|
|
51
|
+
CLOSED = 'CLOSED',
|
|
52
|
+
|
|
53
|
+
// Failing, all requests are instantly rejected
|
|
54
|
+
OPEN = 'OPEN',
|
|
55
|
+
|
|
56
|
+
// Testing recovery, exactly ONE request is allowed through
|
|
57
|
+
HALF_OPEN = 'HALF_OPEN',
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## `CircuitBreakerError`
|
|
64
|
+
|
|
65
|
+
If `fire()` is called while the breaker is `OPEN` (or if it is `HALF_OPEN` and a probe is already in flight), the breaker will instantly reject the promise with a `CircuitBreakerError`.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
class CircuitBreakerError extends Error {
|
|
69
|
+
constructor(message: string)
|
|
70
|
+
}
|
|
71
|
+
```
|