exisjs 0.7.12 → 0.7.13
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/README.md +195 -39
- package/dist/config/types.d.ts +2 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/decorators/params.d.ts +21 -0
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +21 -1
- package/dist/decorators/params.js.map +1 -1
- package/dist/di/container.d.ts +15 -6
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +26 -3
- package/dist/di/container.js.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/index.js.map +1 -1
- package/dist/di/inject.d.ts +2 -1
- package/dist/di/inject.d.ts.map +1 -1
- package/dist/di/inject.js +2 -1
- package/dist/di/inject.js.map +1 -1
- package/dist/docs/BENCHMARKS.md +4 -4
- package/dist/docs/content/README.md +43 -44
- package/dist/docs/content/boundaries.mdx +138 -61
- package/dist/docs/content/cli/commands.mdx +151 -128
- package/dist/docs/content/cli/overview.mdx +61 -47
- package/dist/docs/content/cli/usage.mdx +107 -30
- package/dist/docs/content/config.mdx +145 -158
- package/dist/docs/content/controllers.mdx +193 -464
- package/dist/docs/content/cron.mdx +183 -69
- package/dist/docs/content/decorators.mdx +449 -154
- package/dist/docs/content/errors.mdx +202 -305
- package/dist/docs/content/exisjs-mcp.mdx +59 -49
- package/dist/docs/content/installation.mdx +243 -157
- package/dist/docs/content/introduction.mdx +220 -48
- package/dist/docs/content/middleware.mdx +171 -497
- package/dist/docs/content/modules.mdx +141 -83
- package/dist/docs/content/providers.mdx +216 -180
- package/dist/docs/content/requests.mdx +168 -488
- package/dist/docs/content/responses.mdx +200 -533
- package/dist/docs/content/routing.mdx +235 -598
- package/dist/docs/content/security.mdx +233 -57
- package/dist/docs/content/structure.mdx +151 -240
- package/dist/docs/content/swagger.mdx +192 -91
- package/dist/docs/content/testing.mdx +205 -74
- package/dist/docs/content/validation.mdx +479 -341
- package/dist/docs/reference/README.md +34 -2
- package/dist/docs/reference/config.mdx +277 -75
- package/dist/docs/reference/context.mdx +197 -106
- package/dist/docs/reference/cron.mdx +266 -152
- package/dist/docs/reference/database.mdx +135 -53
- package/dist/docs/reference/decorators.mdx +234 -116
- package/dist/docs/reference/di.mdx +146 -112
- package/dist/docs/reference/error.mdx +155 -178
- package/dist/docs/reference/exis.mdx +141 -70
- package/dist/docs/reference/logger.mdx +110 -126
- package/dist/docs/reference/middleware.mdx +235 -158
- package/dist/docs/reference/plugin.mdx +136 -64
- package/dist/docs/reference/router.mdx +275 -293
- package/dist/docs/reference/sanitize.mdx +91 -87
- package/dist/docs/reference/swagger.mdx +280 -100
- package/dist/docs/reference/testing.mdx +223 -89
- package/dist/docs/reference/validator.mdx +194 -206
- package/dist/lib/start-server.js +3 -8
- package/dist/lib/start-server.js.map +1 -1
- package/dist/module/module.d.ts +6 -3
- package/dist/module/module.d.ts.map +1 -1
- package/dist/module/module.js +17 -3
- package/dist/module/module.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +26 -4
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/server/app.d.ts +3 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +24 -8
- package/dist/server/app.js.map +1 -1
- package/dist/server/request.d.ts +2 -0
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +11 -0
- package/dist/server/request.js.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/swagger/generator.js +130 -10
- package/dist/swagger/generator.js.map +1 -1
- package/dist/telemetry/index.d.ts +12 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +121 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/testing/client.d.ts +1 -0
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +4 -1
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +11 -4
- package/dist/testing/index.js.map +1 -1
- package/dist/validator/builder.d.ts +26 -1
- package/dist/validator/builder.d.ts.map +1 -1
- package/dist/validator/builder.js +31 -0
- package/dist/validator/builder.js.map +1 -1
- package/dist/validator/engine.d.ts +23 -0
- package/dist/validator/engine.d.ts.map +1 -1
- package/dist/validator/engine.js +228 -31
- package/dist/validator/engine.js.map +1 -1
- package/dist/validator/index.d.ts +8 -4
- package/dist/validator/index.d.ts.map +1 -1
- package/dist/validator/index.js +4 -1
- package/dist/validator/index.js.map +1 -1
- package/dist/validator/tex-types.d.ts +15 -0
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/tex-types.js +102 -0
- package/dist/validator/tex-types.js.map +1 -1
- package/dist/validator/tex.d.ts +1 -1
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +3 -1
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +1 -0
- package/dist/validator/types.d.ts.map +1 -1
- package/package.json +46 -14
- package/dist/docs/content/dataloader.mdx +0 -170
- package/dist/docs/reference/dataloader.mdx +0 -91
|
@@ -1,36 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
---
|
|
2
|
+
title: "Decorators"
|
|
3
|
+
description: "Reference guide for Object-Oriented decorators in ExisJS covering controllers, routing, parameters, pipes, middleware, security, background tasks, caching, transactions, OpenAPI, and JavaScript helpers."
|
|
4
|
+
nextUrl: "/docs/requests"
|
|
5
|
+
nextTitle: "Requests"
|
|
6
|
+
---
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
# Decorators
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
ExisJS provides ECMAScript and TypeScript decorators for building backend applications in the **Object-Oriented Programming (OOP)** paradigm.
|
|
11
|
+
|
|
12
|
+
Decorators attach metadata to classes and methods using JavaScript `Symbol` keys. They do not depend on external reflection packages such as `reflect-metadata`.
|
|
13
|
+
|
|
14
|
+
Import decorators directly from `exisjs/decorators`:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import {
|
|
18
|
+
Controller,
|
|
19
|
+
Get,
|
|
20
|
+
Post,
|
|
21
|
+
Put,
|
|
22
|
+
Patch,
|
|
23
|
+
Delete,
|
|
24
|
+
Body,
|
|
25
|
+
Param,
|
|
26
|
+
Query,
|
|
27
|
+
Headers,
|
|
28
|
+
Injectable,
|
|
29
|
+
Use,
|
|
30
|
+
UseGuards,
|
|
31
|
+
UsePipes,
|
|
32
|
+
ParseIntPipe,
|
|
33
|
+
Cron,
|
|
34
|
+
Interval,
|
|
35
|
+
Cacheable,
|
|
36
|
+
Transactional
|
|
37
|
+
} from 'exisjs/decorators'
|
|
38
|
+
```
|
|
8
39
|
|
|
9
40
|
---
|
|
10
41
|
|
|
11
42
|
## 1. Application & Boundary Decorators
|
|
12
43
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
### `@Server(config)`
|
|
16
|
-
Used in your entry point (e.g. `src/http/server.ts`) to configure plugins and root providers in an OOP style:
|
|
44
|
+
### `@Server(options)`
|
|
45
|
+
Configures root providers, plugins, cron schedules, and lifecycle hooks on an application entry class:
|
|
17
46
|
|
|
18
47
|
```typescript filename="src/http/server.ts"
|
|
19
48
|
import { Server } from 'exisjs/decorators'
|
|
20
49
|
import type { App } from 'exisjs'
|
|
21
50
|
|
|
22
51
|
@Server({
|
|
23
|
-
providers: [['
|
|
52
|
+
providers: [['LOGGER_TOKEN', { useValue: console }]],
|
|
53
|
+
plugins: []
|
|
24
54
|
})
|
|
25
55
|
export default class RootServer {
|
|
26
56
|
async onStart(app: App) {
|
|
27
57
|
console.log('Server started')
|
|
28
58
|
}
|
|
59
|
+
|
|
60
|
+
async onClose(app: App) {
|
|
61
|
+
console.log('Server stopping')
|
|
62
|
+
}
|
|
29
63
|
}
|
|
30
64
|
```
|
|
31
65
|
|
|
32
|
-
### `@Boundary(
|
|
33
|
-
|
|
66
|
+
### `@Boundary(options)`
|
|
67
|
+
Configures directory-scoped middleware chains, CORS policies, response headers, and route exclusions inside `boundary.ts`:
|
|
34
68
|
|
|
35
69
|
```typescript filename="src/http/admin/boundary.ts"
|
|
36
70
|
import { Boundary } from 'exisjs/decorators'
|
|
@@ -38,20 +72,20 @@ import type { Request, Response, Next, BoundaryContext } from 'exisjs/router'
|
|
|
38
72
|
import { UnauthorizedError } from 'exisjs/error'
|
|
39
73
|
|
|
40
74
|
@Boundary({
|
|
41
|
-
cors: { origin: 'https://admin.
|
|
75
|
+
cors: { origin: 'https://admin.example.com', credentials: true },
|
|
42
76
|
headers: { 'X-Admin-Api-Version': '2.0' },
|
|
43
|
-
exclude: ['/admin/health']
|
|
77
|
+
exclude: ['/admin/health']
|
|
44
78
|
})
|
|
45
79
|
export default class AdminBoundary {
|
|
46
|
-
//
|
|
47
|
-
|
|
80
|
+
// Middleware handler identified by (req, res, next) signature
|
|
81
|
+
requireApiKey(req: Request, res: Response, next: Next) {
|
|
48
82
|
if (!req.headers['x-api-key']) {
|
|
49
|
-
throw new UnauthorizedError('Missing API key')
|
|
83
|
+
throw new UnauthorizedError('Missing admin API key')
|
|
50
84
|
}
|
|
51
85
|
next()
|
|
52
86
|
}
|
|
53
87
|
|
|
54
|
-
// Request wrapper
|
|
88
|
+
// Request wrapper identified by handle(ctx, next) signature
|
|
55
89
|
async handle(ctx: BoundaryContext, next: Next) {
|
|
56
90
|
const start = Date.now()
|
|
57
91
|
const result = await next()
|
|
@@ -63,9 +97,10 @@ export default class AdminBoundary {
|
|
|
63
97
|
|
|
64
98
|
---
|
|
65
99
|
|
|
66
|
-
## 2.
|
|
100
|
+
## 2. Controller & Routing Decorators
|
|
67
101
|
|
|
68
|
-
|
|
102
|
+
### `@Controller(prefixOrOptions)`
|
|
103
|
+
Registers a class as a route controller. Accepts a string path prefix or a configuration object:
|
|
69
104
|
|
|
70
105
|
```typescript filename="src/http/users/route.ts"
|
|
71
106
|
import { Controller, Get, Post, Put, Patch, Delete } from 'exisjs/decorators'
|
|
@@ -80,7 +115,7 @@ export default class UsersController {
|
|
|
80
115
|
|
|
81
116
|
@Get('/:id', UserParamsSchema)
|
|
82
117
|
async getUser() {
|
|
83
|
-
return { id: 1 }
|
|
118
|
+
return { id: 1, name: 'Alice' }
|
|
84
119
|
}
|
|
85
120
|
|
|
86
121
|
@Post('/', CreateUserSchema)
|
|
@@ -89,155 +124,324 @@ export default class UsersController {
|
|
|
89
124
|
}
|
|
90
125
|
|
|
91
126
|
@Delete('/:id')
|
|
92
|
-
async deleteUser() {
|
|
127
|
+
async deleteUser() {
|
|
128
|
+
return { deleted: true }
|
|
129
|
+
}
|
|
93
130
|
}
|
|
94
131
|
```
|
|
95
132
|
|
|
96
|
-
###
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
### Routing Methods Table
|
|
134
|
+
|
|
135
|
+
| Decorator | HTTP Method | Arguments | Description |
|
|
136
|
+
| :--- | :--- | :--- | :--- |
|
|
137
|
+
| `@Get(path?, schema?)` | `GET` | `path?: string`, `schema?: RouteSchema` | Maps handler to GET requests. |
|
|
138
|
+
| `@Post(path?, schema?)` | `POST` | `path?: string`, `schema?: RouteSchema` | Maps handler to POST requests. |
|
|
139
|
+
| `@Put(path?, schema?)` | `PUT` | `path?: string`, `schema?: RouteSchema` | Maps handler to PUT requests. |
|
|
140
|
+
| `@Patch(path?, schema?)` | `PATCH` | `path?: string`, `schema?: RouteSchema` | Maps handler to PATCH requests. |
|
|
141
|
+
| `@Delete(path?, schema?)` | `DELETE` | `path?: string`, `schema?: RouteSchema` | Maps handler to DELETE requests. |
|
|
142
|
+
| `@Options(path?, schema?)` | `OPTIONS` | `path?: string`, `schema?: RouteSchema` | Maps handler to OPTIONS requests. |
|
|
143
|
+
| `@Head(path?, schema?)` | `HEAD` | `path?: string`, `schema?: RouteSchema` | Maps handler to HEAD requests. |
|
|
144
|
+
| `@All(path?, schema?)` | Any Verb | `path?: string`, `schema?: RouteSchema` | Catches any HTTP verb matching the path. |
|
|
145
|
+
| `@Connect(path?, schema?)` | `CONNECT` | `path?: string`, `schema?: RouteSchema` | Maps handler to CONNECT requests. |
|
|
146
|
+
| `@Trace(path?, schema?)` | `TRACE` | `path?: string`, `schema?: RouteSchema` | Maps handler to TRACE requests. |
|
|
147
|
+
| `@Query(path?, schema?)` | `QUERY` | `path?: string`, `schema?: RouteSchema` | Maps handler to RFC QUERY requests. |
|
|
107
148
|
|
|
108
149
|
---
|
|
109
150
|
|
|
110
|
-
## 3.
|
|
151
|
+
## 3. Parameter Decorators
|
|
111
152
|
|
|
112
|
-
Parameter decorators extract request
|
|
153
|
+
Parameter decorators extract values from the incoming request and pass them into method arguments:
|
|
113
154
|
|
|
114
155
|
```typescript filename="src/http/users/route.ts"
|
|
115
|
-
import {
|
|
156
|
+
import {
|
|
157
|
+
Controller,
|
|
158
|
+
Post,
|
|
159
|
+
Body,
|
|
160
|
+
Param,
|
|
161
|
+
Query,
|
|
162
|
+
Headers,
|
|
163
|
+
Req,
|
|
164
|
+
Res,
|
|
165
|
+
Ip,
|
|
166
|
+
Session,
|
|
167
|
+
UploadedFile
|
|
168
|
+
} from 'exisjs/decorators'
|
|
169
|
+
import type { Request, Response, ExisFile } from 'exisjs'
|
|
116
170
|
|
|
117
171
|
@Controller('/users')
|
|
118
172
|
export default class UsersController {
|
|
119
|
-
@Post('/:id')
|
|
120
|
-
async
|
|
173
|
+
@Post('/:id/avatar')
|
|
174
|
+
async uploadAvatar(
|
|
121
175
|
@Param('id') id: string,
|
|
122
|
-
@
|
|
176
|
+
@Query('quality') quality: string,
|
|
177
|
+
@Body() metadata: any,
|
|
123
178
|
@Headers('authorization') token: string,
|
|
124
|
-
@
|
|
125
|
-
@
|
|
179
|
+
@UploadedFile() file: ExisFile,
|
|
180
|
+
@Ip() clientIp: string,
|
|
181
|
+
@Req() req: Request,
|
|
182
|
+
@Res({ passthrough: true }) res: Response
|
|
183
|
+
) {
|
|
184
|
+
return { id, quality, metadata, clientIp, file: file?.filename }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Parameter Decorators Reference
|
|
190
|
+
|
|
191
|
+
| Decorator | Injected Value | Example Usage |
|
|
192
|
+
| :--- | :--- | :--- |
|
|
193
|
+
| `@Body(fieldOrPipe?)` | Request body or specific property | `@Body() dto: CreateUserDto`, `@Body('email') email: string` |
|
|
194
|
+
| `@Param(nameOrPipe?)` | Path parameter string | `@Param('id', ParseIntPipe) id: number` |
|
|
195
|
+
| `@Query(nameOrPipe?)` / `@QueryParam(...)` | Parsed query parameter or query map | `@Query('search') search: string` |
|
|
196
|
+
| `@Headers(nameOrPipe?)` | Request header value (case-insensitive) | `@Headers('x-api-key') apiKey: string` |
|
|
197
|
+
| `@Cookies(nameOrPipe?)` / `@Cookie(...)` | Parsed cookie map or specific cookie string | `@Cookie('session_id') sessionId: string` |
|
|
198
|
+
| `@Req()` | The incoming `ExisRequest` instance | `@Req() req: Request` |
|
|
199
|
+
| `@Res(options?)` | The outgoing `ExisResponse` instance (`{ passthrough?: boolean }`) | `@Res({ passthrough: true }) res: Response` |
|
|
200
|
+
| `@Ip()` | Client IP address string (reads `trustProxy` setting) | `@Ip() ip: string` |
|
|
201
|
+
| `@Session()` | Active session state object | `@Session() session: any` |
|
|
202
|
+
| `@Next()` | The `next()` pipeline callback | `@Next() next: Next` |
|
|
203
|
+
| `@State(keyOrPipe?)` | Request context state | `@State('user') user: AuthUser` |
|
|
204
|
+
| `@UploadedFile(nameOrPipe?)` | Single parsed file object | `@UploadedFile('avatar') file: ExisFile` |
|
|
205
|
+
| `@UploadedFiles(nameOrPipe?)` | Array of parsed file objects | `@UploadedFiles('photos') files: ExisFile[]` |
|
|
206
|
+
| `@Fields(nameOrPipe?)` | Non-file multipart form fields | `@Fields() fields: Record<string, any>` |
|
|
207
|
+
| `@HostParam(nameOrPipe?)` | Subdomain or host parameter | `@HostParam('subdomain') domain: string` |
|
|
208
|
+
| `@AppCtx()` | Root Application instance | `@AppCtx() app: App` |
|
|
209
|
+
|
|
210
|
+
### Custom Parameter Decorators (`createParamDecorator`)
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { createParamDecorator } from 'exisjs/decorators'
|
|
214
|
+
|
|
215
|
+
export const CurrentUser = createParamDecorator((data: string | undefined, ctx) => {
|
|
216
|
+
return data ? ctx.req.user?.[data] : ctx.req.user
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 4. Transformation & Validation Pipes
|
|
223
|
+
|
|
224
|
+
Pipes transform parameter inputs or validate input types:
|
|
225
|
+
|
|
226
|
+
```typescript filename="src/http/users/route.ts"
|
|
227
|
+
import {
|
|
228
|
+
Controller,
|
|
229
|
+
Get,
|
|
230
|
+
Param,
|
|
231
|
+
Query,
|
|
232
|
+
UsePipes,
|
|
233
|
+
ParseIntPipe,
|
|
234
|
+
ParseFloatPipe,
|
|
235
|
+
ParseBoolPipe,
|
|
236
|
+
ParseUUIDPipe,
|
|
237
|
+
ParseArrayPipe,
|
|
238
|
+
ValidationPipe
|
|
239
|
+
} from 'exisjs/decorators'
|
|
240
|
+
|
|
241
|
+
@Controller('/users')
|
|
242
|
+
@UsePipes(new ValidationPipe({ transform: true }))
|
|
243
|
+
export default class UsersController {
|
|
244
|
+
@Get('/:id')
|
|
245
|
+
async getUser(@Param('id', ParseIntPipe) id: number) {
|
|
246
|
+
return { id, isNumber: typeof id === 'number' }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@Get('/filter')
|
|
250
|
+
async filter(
|
|
251
|
+
@Query('page', ParseIntPipe) page: number,
|
|
252
|
+
@Query('active', ParseBoolPipe) active: boolean,
|
|
253
|
+
@Query('tags', new ParseArrayPipe({ separator: ',' })) tags: string[]
|
|
126
254
|
) {
|
|
127
|
-
return {
|
|
255
|
+
return { page, active, tags }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@Get('/uuid/:uuid')
|
|
259
|
+
async getByUuid(@Param('uuid', new ParseUUIDPipe({ version: '4' })) uuid: string) {
|
|
260
|
+
return { uuid }
|
|
128
261
|
}
|
|
129
262
|
}
|
|
130
263
|
```
|
|
131
264
|
|
|
132
|
-
###
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- `@HostParam(nameOrPipe?)` — Extracts a host subdomain parameter.
|
|
144
|
-
- `@Req()` — Injects the raw request object.
|
|
145
|
-
- `@Res(options?)` — Injects the response object (e.g. `{ passthrough: true }`).
|
|
146
|
-
- `@Ip()` — Injects the client IP address.
|
|
147
|
-
- `@Next()` — Injects the `next` function.
|
|
148
|
-
- `createParamDecorator(factory)` — Creates custom reusable route parameter decorators:
|
|
149
|
-
```typescript
|
|
150
|
-
export const CurrentUser = createParamDecorator((data: string | undefined, ctx) => {
|
|
151
|
-
return data ? ctx.req.user?.[data] : ctx.req.user
|
|
152
|
-
})
|
|
153
|
-
```
|
|
265
|
+
### Pipes Reference
|
|
266
|
+
|
|
267
|
+
| Pipe Class | Description | Options |
|
|
268
|
+
| :--- | :--- | :--- |
|
|
269
|
+
| `ParseIntPipe` | Converts input string or number to integer using `Math.trunc` | `{ errorHttpStatusCode?: number, exceptionFactory?: (msg) => any }` |
|
|
270
|
+
| `ParseFloatPipe` | Converts input string or number to floating point | `{ errorHttpStatusCode?: number, exceptionFactory?: (msg) => any }` |
|
|
271
|
+
| `ParseBoolPipe` | Converts `'true'`, `'1'`, `true` to `true`, `'false'`, `'0'`, `false` to `false` | `{ errorHttpStatusCode?: number, exceptionFactory?: (msg) => any }` |
|
|
272
|
+
| `ParseUUIDPipe` | Validates UUID strings | `{ version?: '3' \| '4' \| '5' \| 'all', errorHttpStatusCode?: number }` |
|
|
273
|
+
| `ParseArrayPipe` | Parses comma-separated strings or arrays with sub-item transformation | `{ items?: any, separator?: string, optional?: boolean }` |
|
|
274
|
+
| `ValidationPipe` | Validates DTO classes or schemas implementing `validate()` or Standard Schema | `{ transform?: boolean, whitelist?: boolean, errorHttpStatusCode?: number }` |
|
|
275
|
+
| `@UsePipes(...pipes)` | Attaches one or more pipes to a controller class or method handler | Array of Pipe instances or Pipe classes |
|
|
154
276
|
|
|
155
277
|
---
|
|
156
278
|
|
|
157
|
-
##
|
|
279
|
+
## 5. Middleware, Guards & Lifecycle Pipeline Decorators
|
|
158
280
|
|
|
159
|
-
|
|
281
|
+
Attach guards, interceptors, exception filters, timeouts, and traffic policies to classes or route methods:
|
|
160
282
|
|
|
161
283
|
```typescript filename="src/http/checkout/route.ts"
|
|
162
|
-
import {
|
|
163
|
-
|
|
284
|
+
import {
|
|
285
|
+
Controller,
|
|
286
|
+
Post,
|
|
287
|
+
UseGuards,
|
|
288
|
+
UseInterceptors,
|
|
289
|
+
UseFilters,
|
|
290
|
+
RateLimit,
|
|
291
|
+
Timeout,
|
|
292
|
+
Idempotent,
|
|
293
|
+
Cors,
|
|
294
|
+
Compress,
|
|
295
|
+
Dedupe,
|
|
296
|
+
IpFilter,
|
|
297
|
+
BlockProbes
|
|
298
|
+
} from 'exisjs/decorators'
|
|
299
|
+
import { AuthGuard } from '../../common/guards/auth.guard'
|
|
300
|
+
import { LoggingInterceptor } from '../../common/interceptors/logging'
|
|
301
|
+
import { HttpExceptionFilter } from '../../common/filters/http.filter'
|
|
164
302
|
|
|
165
303
|
@Controller('/checkout')
|
|
166
|
-
@
|
|
167
|
-
@
|
|
168
|
-
@
|
|
304
|
+
@UseGuards(AuthGuard)
|
|
305
|
+
@UseInterceptors(LoggingInterceptor)
|
|
306
|
+
@UseFilters(HttpExceptionFilter)
|
|
307
|
+
@Cors({ origin: 'https://pay.example.com', credentials: true })
|
|
308
|
+
@RateLimit({ max: 20, windowMs: 60000 })
|
|
309
|
+
@IpFilter({ allow: ['10.0.0.0/8', '127.0.0.1'] })
|
|
310
|
+
@BlockProbes()
|
|
169
311
|
export default class CheckoutController {
|
|
170
312
|
@Post('/')
|
|
171
|
-
@Timeout(
|
|
172
|
-
@Idempotent({ ttlMs: 86400000 })
|
|
313
|
+
@Timeout(15000)
|
|
314
|
+
@Idempotent({ ttlMs: 86400000 })
|
|
315
|
+
@Dedupe({ ttlMs: 5000 })
|
|
316
|
+
@Compress()
|
|
173
317
|
async processPayment() {
|
|
174
|
-
return {
|
|
318
|
+
return { status: 'processed' }
|
|
175
319
|
}
|
|
176
320
|
}
|
|
177
321
|
```
|
|
178
322
|
|
|
179
|
-
###
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
323
|
+
### Traffic & Middleware Decorators
|
|
324
|
+
|
|
325
|
+
| Decorator | Scope | Description |
|
|
326
|
+
| :--- | :--- | :--- |
|
|
327
|
+
| `@Use(...items)` | Class / Method | Attaches middlewares, guards (`canActivate`), interceptors (`intercept`), or filters (`catch`). |
|
|
328
|
+
| `@UseGuards(...guards)` | Class / Method | Attaches route guards implementing the `CanActivate` interface. |
|
|
329
|
+
| `@UseInterceptors(...interceptors)` | Class / Method | Attaches request/response interceptors implementing `Interceptor`. |
|
|
330
|
+
| `@UseFilters(...filters)` | Class / Method | Attaches exception filters implementing `ExceptionFilter`. |
|
|
331
|
+
| `@Catch(...exceptions)` | Class | Declares the exception types handled by an `ExceptionFilter` class. |
|
|
332
|
+
| `@Timeout(msOrOptions)` | Class / Method | Sets request execution timeout in milliseconds. |
|
|
333
|
+
| `@Idempotent(options?)` | Method / Class | Caches responses matching the `Idempotency-Key` header in an LRU memory store. |
|
|
334
|
+
| `@RateLimit(options?)` | Class / Method | Enforces rate limits using a token bucket algorithm. |
|
|
335
|
+
| `@Cors(options?)` | Class / Method | Applies CORS headers to a specific controller or method. |
|
|
336
|
+
| `@IpFilter(options)` | Class / Method | Restricts access using CIDR IP allowlists and denylists. |
|
|
337
|
+
| `@Compress()` | Class / Method | Enables Gzip and Brotli response compression. |
|
|
338
|
+
| `@Dedupe(options)` | Method / Class | Deduplicates parallel in-flight requests matching the same key. |
|
|
339
|
+
| `@BlockProbes(options?)` / `@BlockSuspiciousProbes()` | Class / Method | Drops vulnerability scanner probes (`.env`, `wp-admin`, `/.git`). |
|
|
340
|
+
| `@Upload(fieldNameOrOptions)` / `@UseUpload(options)` | Method / Parameter | Configures multipart file upload middleware. |
|
|
194
341
|
|
|
195
342
|
---
|
|
196
343
|
|
|
197
|
-
##
|
|
344
|
+
## 6. Security & RBAC Decorators
|
|
198
345
|
|
|
199
|
-
Declare
|
|
346
|
+
Declare role requirements, permission checks, and authentication bypasses:
|
|
200
347
|
|
|
201
|
-
```typescript filename="src/http/roles/route.ts"
|
|
202
|
-
import {
|
|
203
|
-
|
|
348
|
+
```typescript filename="src/http/admin/roles/route.ts"
|
|
349
|
+
import {
|
|
350
|
+
Controller,
|
|
351
|
+
Get,
|
|
352
|
+
Post,
|
|
353
|
+
Body,
|
|
354
|
+
UseGuards,
|
|
355
|
+
Roles,
|
|
356
|
+
Permissions,
|
|
357
|
+
Public
|
|
358
|
+
} from 'exisjs/decorators'
|
|
359
|
+
import { AuthGuard } from '../../common/guards/auth.guard'
|
|
204
360
|
|
|
205
361
|
@Controller('/roles')
|
|
206
|
-
@
|
|
362
|
+
@UseGuards(AuthGuard)
|
|
207
363
|
export default class RolesController {
|
|
208
364
|
@Get('/public')
|
|
209
|
-
@Public() //
|
|
365
|
+
@Public() // Instructs AuthGuard to skip credential verification
|
|
210
366
|
async getPublicInfo() {
|
|
211
|
-
return { status: '
|
|
367
|
+
return { status: 'open' }
|
|
212
368
|
}
|
|
213
369
|
|
|
214
370
|
@Get('/')
|
|
215
|
-
@Roles('admin', '
|
|
216
|
-
async
|
|
217
|
-
return { roles: [] }
|
|
371
|
+
@Roles('admin', 'security_officer')
|
|
372
|
+
async listRoles() {
|
|
373
|
+
return { roles: ['admin', 'manager', 'viewer'] }
|
|
218
374
|
}
|
|
219
375
|
|
|
220
376
|
@Post('/')
|
|
221
|
-
@Permissions('
|
|
377
|
+
@Permissions('roles:create', 'roles:write')
|
|
222
378
|
async createRole(@Body() body: any) {
|
|
223
|
-
return {
|
|
379
|
+
return { created: true }
|
|
224
380
|
}
|
|
225
381
|
}
|
|
226
382
|
```
|
|
227
383
|
|
|
228
|
-
###
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
384
|
+
### Security Decorators Reference
|
|
385
|
+
|
|
386
|
+
| Decorator | Target | Description |
|
|
387
|
+
| :--- | :--- | :--- |
|
|
388
|
+
| `@Roles(...roles: string[])` | Class / Method | Stores required role names in route metadata (`ROLES_METADATA`). |
|
|
389
|
+
| `@Permissions(...perms: string[])` | Class / Method | Stores granular required permissions in route metadata (`PERMISSIONS_METADATA`). |
|
|
390
|
+
| `@Public()` / `@IsPublic()` / `@AllowAnonymous()` | Class / Method | Marks route as unauthenticated. |
|
|
232
391
|
|
|
233
392
|
---
|
|
234
393
|
|
|
235
|
-
##
|
|
394
|
+
## 7. Cron & Scheduled Background Tasks
|
|
395
|
+
|
|
396
|
+
Schedule recurring tasks directly on `@Injectable()` service classes:
|
|
397
|
+
|
|
398
|
+
```typescript filename="src/http/cron/cleanup.service.ts"
|
|
399
|
+
import { Injectable, Cron, Interval, TimeoutTask } from 'exisjs/decorators'
|
|
400
|
+
|
|
401
|
+
@Injectable()
|
|
402
|
+
export class CleanupService {
|
|
403
|
+
// Executes daily at midnight UTC with overlap protection
|
|
404
|
+
@Cron('0 0 * * *', { name: 'nightly-purge', timezone: 'UTC', preventOverlap: true })
|
|
405
|
+
async purgeOldSessions() {
|
|
406
|
+
console.log('Purging expired sessions')
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Executes every 60 seconds
|
|
410
|
+
@Interval(60_000, { name: 'health-heartbeat' })
|
|
411
|
+
async pingHeartbeat() {
|
|
412
|
+
console.log('Heartbeat ping')
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Executes once 5 seconds after server startup
|
|
416
|
+
@TimeoutTask(5_000, { name: 'cache-warmup' })
|
|
417
|
+
async warmupCaches() {
|
|
418
|
+
console.log('Caches warmed up')
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Cron Decorators Table
|
|
236
424
|
|
|
237
|
-
|
|
425
|
+
| Decorator | Schedule Parameter | Description |
|
|
426
|
+
| :--- | :--- | :--- |
|
|
427
|
+
| `@Cron(schedule, options?)` / `@Scheduled(...)` | 5-field cron expression string | Schedules a recurring cron task. |
|
|
428
|
+
| `@Interval(ms, options?)` | Milliseconds interval | Runs repeatedly at the specified interval. |
|
|
429
|
+
| `@TimeoutTask(ms, options?)` / `@ScheduledTimeout(...)` | Milliseconds delay | Runs once after a delay following server start. |
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## 8. Service Enhancements: Caching, Transactions & Profiling
|
|
238
434
|
|
|
239
435
|
```typescript filename="src/http/orders/service.ts"
|
|
240
|
-
import {
|
|
436
|
+
import {
|
|
437
|
+
Injectable,
|
|
438
|
+
Cacheable,
|
|
439
|
+
CacheEvict,
|
|
440
|
+
Transactional,
|
|
441
|
+
Profile,
|
|
442
|
+
type OnInit,
|
|
443
|
+
type OnDestroy
|
|
444
|
+
} from 'exisjs/decorators'
|
|
241
445
|
|
|
242
446
|
@Injectable()
|
|
243
447
|
export class OrderService implements OnInit, OnDestroy {
|
|
@@ -249,14 +453,14 @@ export class OrderService implements OnInit, OnDestroy {
|
|
|
249
453
|
console.log('OrderService destroyed')
|
|
250
454
|
}
|
|
251
455
|
|
|
252
|
-
// Caches result in
|
|
456
|
+
// Caches result in memory for 300 seconds
|
|
253
457
|
@Cacheable({ ttl: 300, key: (id: string) => `order:${id}` })
|
|
254
458
|
@Profile({ thresholdMs: 50, warnSlow: true })
|
|
255
459
|
async getOrder(id: string) {
|
|
256
460
|
return this.db.query('SELECT * FROM orders WHERE id = $1', [id])
|
|
257
461
|
}
|
|
258
462
|
|
|
259
|
-
//
|
|
463
|
+
// Executes inside a database transaction and evicts the cached key
|
|
260
464
|
@Transactional()
|
|
261
465
|
@CacheEvict({ key: (id: string) => `order:${id}` })
|
|
262
466
|
async updateOrder(id: string, data: any) {
|
|
@@ -265,69 +469,129 @@ export class OrderService implements OnInit, OnDestroy {
|
|
|
265
469
|
}
|
|
266
470
|
```
|
|
267
471
|
|
|
268
|
-
###
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
472
|
+
### Service & Lifecycle Reference
|
|
473
|
+
|
|
474
|
+
| Decorator / Interface | Target | Description |
|
|
475
|
+
| :--- | :--- | :--- |
|
|
476
|
+
| `@Cacheable(options?)` | Method | Caches return value. Options: `{ ttl?: number, key?: string \| ((...args) => string) }`. |
|
|
477
|
+
| `@CacheEvict(options)` | Method | Evicts cache keys on method completion. Options: `{ key: string \| ((...args) => string) }`. |
|
|
478
|
+
| `@Transactional(options?)` | Method | Wraps execution in a database transaction (Prisma, Mongoose, Drizzle, SQL). |
|
|
479
|
+
| `@Profile(options?)` | Method | Measures execution time using `performance.now()`. |
|
|
480
|
+
| `OnInit` (`onInit()`) | Class Interface | Invoked once after service instantiation. |
|
|
481
|
+
| `OnDestroy` (`onDestroy()`) | Class Interface | Invoked once during graceful shutdown. |
|
|
482
|
+
| `OnModuleInit` / `OnModuleDestroy` | Class Interface | Invoked during parent module initialization and destruction. |
|
|
483
|
+
| `OnApplicationBootstrap` | Class Interface | Invoked when all modules are initialized and server starts listening. |
|
|
484
|
+
| `OnApplicationShutdown` | Class Interface | Invoked on `SIGTERM` or `SIGINT` signals. |
|
|
275
485
|
|
|
276
486
|
---
|
|
277
487
|
|
|
278
|
-
##
|
|
488
|
+
## 9. Response, Header & OpenAPI Decorators
|
|
489
|
+
|
|
490
|
+
### Response & Host Modifiers
|
|
491
|
+
|
|
492
|
+
```typescript filename="src/http/items/route.ts"
|
|
493
|
+
import {
|
|
494
|
+
Controller,
|
|
495
|
+
Post,
|
|
496
|
+
Get,
|
|
497
|
+
HttpCode,
|
|
498
|
+
Header,
|
|
499
|
+
Redirect,
|
|
500
|
+
Hosts,
|
|
501
|
+
Returns
|
|
502
|
+
} from 'exisjs/decorators'
|
|
503
|
+
import { ItemResponseSchema } from './schema'
|
|
504
|
+
|
|
505
|
+
@Controller('/items')
|
|
506
|
+
@Hosts('api.example.com', 'internal.example.com')
|
|
507
|
+
export default class ItemsController {
|
|
508
|
+
@Post('/')
|
|
509
|
+
@HttpCode(201)
|
|
510
|
+
@Header('X-Resource-Version', '1.0')
|
|
511
|
+
@Returns(ItemResponseSchema)
|
|
512
|
+
async create() {
|
|
513
|
+
return { created: true }
|
|
514
|
+
}
|
|
279
515
|
|
|
280
|
-
|
|
516
|
+
@Get('/legacy')
|
|
517
|
+
@Redirect('/items', 301)
|
|
518
|
+
async legacyRedirect() {}
|
|
519
|
+
}
|
|
520
|
+
```
|
|
281
521
|
|
|
282
|
-
|
|
283
|
-
import { Controller, Post, Get, HttpCode, Header, Redirect, SetMetadata, Reflector } from 'exisjs/decorators'
|
|
522
|
+
### OpenAPI & Swagger Decorators
|
|
284
523
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
524
|
+
All OpenAPI decorators are available from `exisjs/decorators`:
|
|
525
|
+
|
|
526
|
+
```typescript filename="src/http/users/route.ts"
|
|
527
|
+
import {
|
|
528
|
+
Controller,
|
|
529
|
+
Get,
|
|
530
|
+
ApiTags,
|
|
531
|
+
ApiOperation,
|
|
532
|
+
ApiResponse,
|
|
533
|
+
ApiBearerAuth,
|
|
534
|
+
ApiSecurity,
|
|
535
|
+
ApiExclude,
|
|
536
|
+
ApiProperty
|
|
537
|
+
} from 'exisjs/decorators'
|
|
538
|
+
|
|
539
|
+
export class UserDto {
|
|
540
|
+
@ApiProperty({ description: 'User ID', example: 'usr_123', required: true })
|
|
541
|
+
id: string
|
|
542
|
+
|
|
543
|
+
@ApiProperty({ description: 'Full name', example: 'Alice Smith' })
|
|
544
|
+
name: string
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
@ApiTags('Users')
|
|
548
|
+
@ApiBearerAuth()
|
|
549
|
+
@Controller('/users')
|
|
550
|
+
export default class UsersController {
|
|
551
|
+
@Get('/:id')
|
|
552
|
+
@ApiOperation({ summary: 'Get User By ID', description: 'Returns a user record' })
|
|
553
|
+
@ApiResponse({ status: 200, description: 'User retrieved successfully' })
|
|
554
|
+
@ApiResponse({ status: 404, description: 'User not found' })
|
|
555
|
+
async getUser() {
|
|
556
|
+
return { id: 'usr_123', name: 'Alice Smith' }
|
|
294
557
|
}
|
|
295
558
|
|
|
296
|
-
@Get('/
|
|
297
|
-
@
|
|
298
|
-
async
|
|
559
|
+
@Get('/internal/health')
|
|
560
|
+
@ApiExclude()
|
|
561
|
+
async health() {
|
|
562
|
+
return { ok: true }
|
|
563
|
+
}
|
|
299
564
|
}
|
|
300
565
|
```
|
|
301
566
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
567
|
+
| Decorator | Scope | Description |
|
|
568
|
+
| :--- | :--- | :--- |
|
|
569
|
+
| `@ApiTags(...tags)` / `@Tags(...)` | Class / Method | Assigns OpenAPI documentation tags. |
|
|
570
|
+
| `@ApiOperation(options)` / `@Operation(...)` | Method | Configures `summary`, `description`, `operationId`, `deprecated`, `externalDocs`. |
|
|
571
|
+
| `@ApiResponse(options)` | Method | Defines status code documentation (`status`, `description`, `schema`). |
|
|
572
|
+
| `@ApiBearerAuth(name?)` / `@BearerAuth(...)` | Class / Method | Marks route as requiring Bearer JWT authentication in OpenAPI output. |
|
|
573
|
+
| `@ApiSecurity(name, scopes?)` | Class / Method | Declares custom security scheme requirement. |
|
|
574
|
+
| `@ApiExclude()` | Class / Method | Hides route or controller from OpenAPI / Swagger output. |
|
|
575
|
+
| `@ApiProperty(options)` | Property | Annotates class DTO properties with description, examples, types, and enums. |
|
|
310
576
|
|
|
311
577
|
---
|
|
312
578
|
|
|
313
|
-
##
|
|
579
|
+
## 10. Dependency Injection & Modules
|
|
314
580
|
|
|
315
|
-
|
|
581
|
+
### `@Injectable(options?)`
|
|
316
582
|
|
|
317
583
|
```typescript filename="src/http/users/service.ts"
|
|
318
584
|
import { Injectable } from 'exisjs/decorators'
|
|
319
585
|
|
|
320
|
-
@Injectable({ scope: '
|
|
586
|
+
@Injectable({ scope: 'singleton' }) // 'singleton' | 'transient' | 'request'
|
|
321
587
|
export class UserService {
|
|
322
|
-
async
|
|
588
|
+
async findOne(id: string) {
|
|
323
589
|
return { id, name: 'Alice' }
|
|
324
590
|
}
|
|
325
591
|
}
|
|
326
592
|
```
|
|
327
593
|
|
|
328
|
-
###
|
|
329
|
-
|
|
330
|
-
You can inject dependencies using tokens or classes, and mark optional dependencies with `@Optional()`:
|
|
594
|
+
### `@Inject(token)` & `@Optional()`
|
|
331
595
|
|
|
332
596
|
```typescript filename="src/http/users/route.ts"
|
|
333
597
|
import { Controller, Get, Param } from 'exisjs/decorators'
|
|
@@ -344,20 +608,19 @@ export default class UsersController {
|
|
|
344
608
|
|
|
345
609
|
@Get('/:id')
|
|
346
610
|
async getUser(@Param('id') id: string) {
|
|
347
|
-
return this.userService.
|
|
611
|
+
return this.userService.findOne(id)
|
|
348
612
|
}
|
|
349
613
|
}
|
|
350
614
|
```
|
|
351
615
|
|
|
352
|
-
###
|
|
353
|
-
|
|
354
|
-
You can group controllers, providers, and imports into high-level OOP modules using `@Module`:
|
|
616
|
+
### `@Module(options)` & `@Global()`
|
|
355
617
|
|
|
356
618
|
```typescript filename="src/http/users/users.module.ts"
|
|
357
|
-
import { Module } from 'exisjs/
|
|
619
|
+
import { Module, Global } from 'exisjs/decorators'
|
|
358
620
|
import UsersController from './route'
|
|
359
621
|
import { UserService } from './service'
|
|
360
622
|
|
|
623
|
+
@Global()
|
|
361
624
|
@Module({
|
|
362
625
|
controllers: [UsersController],
|
|
363
626
|
providers: [UserService],
|
|
@@ -366,3 +629,35 @@ import { UserService } from './service'
|
|
|
366
629
|
export class UsersModule {}
|
|
367
630
|
```
|
|
368
631
|
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## 11. JavaScript Runtime Utilities (`Decorate` & `BindParam`)
|
|
635
|
+
|
|
636
|
+
For JavaScript projects without a build step or TypeScript compiler, use `Decorate` and `BindParam` to apply decorator metadata programmatically:
|
|
637
|
+
|
|
638
|
+
```javascript filename="src/http/users/route.js"
|
|
639
|
+
import { Decorate, BindParam, Controller, Get, Post, Body, Param } from 'exisjs/decorators'
|
|
640
|
+
|
|
641
|
+
class UsersController {
|
|
642
|
+
list() {
|
|
643
|
+
return []
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
create(body) {
|
|
647
|
+
return { created: body.name }
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export default Decorate(
|
|
652
|
+
UsersController,
|
|
653
|
+
[Controller('/users')],
|
|
654
|
+
{
|
|
655
|
+
list: [Get('/')],
|
|
656
|
+
create: [
|
|
657
|
+
Post('/'),
|
|
658
|
+
BindParam(0, Body()) // Injects @Body() into the first argument (index 0)
|
|
659
|
+
]
|
|
660
|
+
}
|
|
661
|
+
)
|
|
662
|
+
```
|
|
663
|
+
|