exisjs 0.7.6 → 0.7.8
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/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +12 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/clean.d.ts +5 -0
- package/dist/cli/commands/clean.d.ts.map +1 -0
- package/dist/cli/commands/clean.js +30 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +15 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/generate.d.ts +6 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +57 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +16 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -0
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +161 -23
- package/dist/cli/manifest.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +9 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/cron/expressions.d.ts +45 -0
- package/dist/cron/expressions.d.ts.map +1 -0
- package/dist/cron/expressions.js +87 -0
- package/dist/cron/expressions.js.map +1 -0
- package/dist/cron/index.d.ts +6 -0
- package/dist/cron/index.d.ts.map +1 -0
- package/dist/cron/index.js +22 -0
- package/dist/cron/index.js.map +1 -0
- package/dist/cron/job.d.ts +50 -0
- package/dist/cron/job.d.ts.map +1 -0
- package/dist/cron/job.js +247 -0
- package/dist/cron/job.js.map +1 -0
- package/dist/cron/manager.d.ts +105 -0
- package/dist/cron/manager.d.ts.map +1 -0
- package/dist/cron/manager.js +245 -0
- package/dist/cron/manager.js.map +1 -0
- package/dist/cron/parser.d.ts +22 -0
- package/dist/cron/parser.d.ts.map +1 -0
- package/dist/cron/parser.js +269 -0
- package/dist/cron/parser.js.map +1 -0
- package/dist/cron/types.d.ts +76 -0
- package/dist/cron/types.d.ts.map +1 -0
- package/dist/cron/types.js +3 -0
- package/dist/cron/types.js.map +1 -0
- package/dist/decorators/constants.d.ts +2 -0
- package/dist/decorators/constants.d.ts.map +1 -1
- package/dist/decorators/constants.js +3 -1
- package/dist/decorators/constants.js.map +1 -1
- package/dist/decorators/core.d.ts +29 -11
- package/dist/decorators/core.d.ts.map +1 -1
- package/dist/decorators/core.js +49 -11
- package/dist/decorators/core.js.map +1 -1
- package/dist/decorators/cron.d.ts +57 -0
- package/dist/decorators/cron.d.ts.map +1 -0
- package/dist/decorators/cron.js +143 -0
- package/dist/decorators/cron.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/params.d.ts +97 -2
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +94 -2
- package/dist/decorators/params.js.map +1 -1
- package/dist/decorators/routing.d.ts +81 -16
- package/dist/decorators/routing.d.ts.map +1 -1
- package/dist/decorators/routing.js +81 -16
- package/dist/decorators/routing.js.map +1 -1
- package/dist/docs/content/cron.mdx +196 -0
- package/dist/docs/content/errors.mdx +58 -9
- package/dist/docs/content/structure.mdx +1 -1
- package/dist/docs/content/swagger.mdx +289 -0
- package/dist/docs/reference/cron.mdx +300 -0
- package/dist/docs/reference/decorators.mdx +8 -0
- package/dist/docs/reference/error.mdx +44 -0
- package/dist/docs/reference/swagger.mdx +226 -0
- package/dist/docs/reference/validator.mdx +1 -1
- package/dist/router/boundary.d.ts +24 -2
- package/dist/router/boundary.d.ts.map +1 -1
- package/dist/router/boundary.js +24 -2
- package/dist/router/boundary.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +52 -3
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/route-builder.d.ts +196 -33
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js +171 -11
- package/dist/router/route-builder.js.map +1 -1
- package/dist/router/route-scanner.d.ts +6 -0
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js +69 -812
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +27 -0
- package/dist/router/router.js.map +1 -1
- package/dist/router/scanner/boundary-loader.d.ts +18 -0
- package/dist/router/scanner/boundary-loader.d.ts.map +1 -0
- package/dist/router/scanner/boundary-loader.js +539 -0
- package/dist/router/scanner/boundary-loader.js.map +1 -0
- package/dist/router/scanner/cron-mounter.d.ts +15 -0
- package/dist/router/scanner/cron-mounter.d.ts.map +1 -0
- package/dist/router/scanner/cron-mounter.js +216 -0
- package/dist/router/scanner/cron-mounter.js.map +1 -0
- package/dist/router/scanner/error-mounter.d.ts +14 -0
- package/dist/router/scanner/error-mounter.d.ts.map +1 -0
- package/dist/router/scanner/error-mounter.js +200 -0
- package/dist/router/scanner/error-mounter.js.map +1 -0
- package/dist/router/scanner/fs-scanner.d.ts +25 -0
- package/dist/router/scanner/fs-scanner.d.ts.map +1 -0
- package/dist/router/scanner/fs-scanner.js +115 -0
- package/dist/router/scanner/fs-scanner.js.map +1 -0
- package/dist/router/scanner/functional-compiler.d.ts +9 -0
- package/dist/router/scanner/functional-compiler.d.ts.map +1 -0
- package/dist/router/scanner/functional-compiler.js +233 -0
- package/dist/router/scanner/functional-compiler.js.map +1 -0
- package/dist/router/scanner/index.d.ts +9 -0
- package/dist/router/scanner/index.d.ts.map +1 -0
- package/dist/router/scanner/index.js +19 -0
- package/dist/router/scanner/index.js.map +1 -0
- package/dist/router/types.d.ts +15 -0
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/app.d.ts +2 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +3 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/helpers/body-parser.d.ts +18 -0
- package/dist/server/helpers/body-parser.d.ts.map +1 -0
- package/dist/server/helpers/body-parser.js +246 -0
- package/dist/server/helpers/body-parser.js.map +1 -0
- package/dist/server/helpers/cookie.d.ts +4 -0
- package/dist/server/helpers/cookie.d.ts.map +1 -0
- package/dist/server/helpers/cookie.js +61 -0
- package/dist/server/helpers/cookie.js.map +1 -0
- package/dist/server/helpers/etag.d.ts +3 -0
- package/dist/server/helpers/etag.d.ts.map +1 -0
- package/dist/server/helpers/etag.js +25 -0
- package/dist/server/helpers/etag.js.map +1 -0
- package/dist/server/helpers/index.d.ts +6 -0
- package/dist/server/helpers/index.d.ts.map +1 -0
- package/dist/server/helpers/index.js +21 -0
- package/dist/server/helpers/index.js.map +1 -0
- package/dist/server/helpers/ip.d.ts +9 -0
- package/dist/server/helpers/ip.d.ts.map +1 -0
- package/dist/server/helpers/ip.js +62 -0
- package/dist/server/helpers/ip.js.map +1 -0
- package/dist/server/helpers/stream.d.ts +4 -0
- package/dist/server/helpers/stream.d.ts.map +1 -0
- package/dist/server/helpers/stream.js +193 -0
- package/dist/server/helpers/stream.js.map +1 -0
- package/dist/server/request.d.ts +1 -139
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +19 -433
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +105 -158
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +121 -423
- package/dist/server/response.js.map +1 -1
- package/dist/swagger/decorators.d.ts +81 -0
- package/dist/swagger/decorators.d.ts.map +1 -0
- package/dist/swagger/decorators.js +172 -0
- package/dist/swagger/decorators.js.map +1 -0
- package/dist/swagger/generator.d.ts +19 -0
- package/dist/swagger/generator.d.ts.map +1 -0
- package/dist/swagger/generator.js +344 -0
- package/dist/swagger/generator.js.map +1 -0
- package/dist/swagger/index.d.ts +31 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/index.js +53 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/swagger/mounter.d.ts +7 -0
- package/dist/swagger/mounter.d.ts.map +1 -0
- package/dist/swagger/mounter.js +56 -0
- package/dist/swagger/mounter.js.map +1 -0
- package/dist/swagger/types.d.ts +190 -0
- package/dist/swagger/types.d.ts.map +1 -0
- package/dist/swagger/types.js +3 -0
- package/dist/swagger/types.js.map +1 -0
- package/dist/swagger/ui.d.ts +6 -0
- package/dist/swagger/ui.d.ts.map +1 -0
- package/dist/swagger/ui.js +118 -0
- package/dist/swagger/ui.js.map +1 -0
- package/dist/validator/builder.d.ts +223 -0
- package/dist/validator/builder.d.ts.map +1 -0
- package/dist/validator/builder.js +517 -0
- package/dist/validator/builder.js.map +1 -0
- package/dist/validator/engine.d.ts +19 -0
- package/dist/validator/engine.d.ts.map +1 -0
- package/dist/validator/engine.js +568 -0
- package/dist/validator/engine.js.map +1 -0
- package/dist/validator/error.d.ts +12 -0
- package/dist/validator/error.d.ts.map +1 -0
- package/dist/validator/error.js +63 -0
- package/dist/validator/error.js.map +1 -0
- package/dist/validator/pagination.d.ts +40 -0
- package/dist/validator/pagination.d.ts.map +1 -0
- package/dist/validator/pagination.js +60 -0
- package/dist/validator/pagination.js.map +1 -0
- package/dist/validator/tex.d.ts +6 -158
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +14 -971
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +83 -0
- package/dist/validator/types.d.ts.map +1 -0
- package/dist/validator/types.js +3 -0
- package/dist/validator/types.js.map +1 -0
- package/package.json +9 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/reference/swagger"
|
|
3
|
+
nextTitle: "Swagger API Reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Swagger & OpenAPI 3.1 Documentation
|
|
7
|
+
|
|
8
|
+
ExisJS comes with a native, built-in **Swagger & OpenAPI 3.1** documentation engine. It automatically discovers all registered routes, introspects **Tex** and **Zod** validation schemas, and serves interactive API documentation at `/docs`.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Enable Swagger documentation in your `exis.config.ts`:
|
|
15
|
+
|
|
16
|
+
```typescript filename="exis.config.ts"
|
|
17
|
+
import { defineConfig } from 'exisjs/config'
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
docs: {
|
|
21
|
+
enabled: true, // Mounts UI at /docs and spec at /docs/json
|
|
22
|
+
title: 'Store API Documentation',
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
description: 'Auto-generated API documentation with native schema validation',
|
|
25
|
+
ui: 'swagger-ui', // 'swagger-ui' | 'scalar'
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Once enabled:
|
|
31
|
+
* Visit `http://localhost:3000/docs` to view the interactive documentation UI.
|
|
32
|
+
* Visit `http://localhost:3000/docs/json` (or `/docs/openapi.json`) for the raw OpenAPI 3.1 specification.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Interactive Documentation UIs
|
|
37
|
+
|
|
38
|
+
ExisJS supports two built-in UI renderers:
|
|
39
|
+
|
|
40
|
+
### 1. Swagger UI (`ui: 'swagger-ui'`, default)
|
|
41
|
+
The industry-standard interactive Swagger explorer featuring **Try it out**, live request execution, and parameter inspection.
|
|
42
|
+
|
|
43
|
+
### 2. Scalar API Reference (`ui: 'scalar'`)
|
|
44
|
+
A modern, high-performance, dark-mode-first API reference documentation viewer with integrated code snippets and fast search.
|
|
45
|
+
|
|
46
|
+
```typescript filename="exis.config.ts"
|
|
47
|
+
import { defineConfig } from 'exisjs/config'
|
|
48
|
+
|
|
49
|
+
export default defineConfig({
|
|
50
|
+
docs: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
ui: 'scalar', // Switches UI to modern Scalar
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Documenting Functional Routes
|
|
60
|
+
|
|
61
|
+
In ExisJS folder-based routing, each endpoint folder contains a `route.ts` file that exports a default `controller({ ... })`. You can add OpenAPI metadata directly to each route definition:
|
|
62
|
+
|
|
63
|
+
```typescript filename="src/http/users/route.ts"
|
|
64
|
+
import { controller, route } from 'exisjs/router'
|
|
65
|
+
import { tex, getPaginationSkip } from 'exisjs/validator'
|
|
66
|
+
|
|
67
|
+
export default controller({
|
|
68
|
+
list: route.get('/', {
|
|
69
|
+
summary: 'List active users',
|
|
70
|
+
description: 'Returns a paginated list of all active users in the system.',
|
|
71
|
+
tags: ['Users'],
|
|
72
|
+
query: tex.pagination({ defaultLimit: 25, maxLimit: 100 }),
|
|
73
|
+
responses: {
|
|
74
|
+
200: {
|
|
75
|
+
description: 'Paginated user list',
|
|
76
|
+
schema: tex.object({
|
|
77
|
+
data: tex.array(tex.object({ id: tex.uuid(), name: tex.string() })),
|
|
78
|
+
total: tex.number(),
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
400: { description: 'Invalid query parameters' },
|
|
82
|
+
},
|
|
83
|
+
async handle({ query }) {
|
|
84
|
+
const { skip, limit } = getPaginationSkip(query)
|
|
85
|
+
return {
|
|
86
|
+
data: [
|
|
87
|
+
{ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Alice' },
|
|
88
|
+
{ id: '550e8400-e29b-41d4-a716-446655440001', name: 'Bob' },
|
|
89
|
+
],
|
|
90
|
+
total: 2,
|
|
91
|
+
skip,
|
|
92
|
+
limit,
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
create: route.post('/', {
|
|
98
|
+
summary: 'Create a new user',
|
|
99
|
+
tags: ['Users'],
|
|
100
|
+
body: tex.object({
|
|
101
|
+
name: tex.string({ min: 2 }),
|
|
102
|
+
email: tex.email(),
|
|
103
|
+
}),
|
|
104
|
+
responses: {
|
|
105
|
+
201: { description: 'User successfully created' },
|
|
106
|
+
400: { description: 'Validation error' },
|
|
107
|
+
},
|
|
108
|
+
async handle({ body }) {
|
|
109
|
+
return { id: '550e8400-e29b-41d4-a716-446655440002', ...body }
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
|
|
113
|
+
getById: route.get('/:id', {
|
|
114
|
+
summary: 'Get user by UUID',
|
|
115
|
+
tags: ['Users'],
|
|
116
|
+
params: tex.object({ id: tex.uuid() }),
|
|
117
|
+
responses: {
|
|
118
|
+
200: { description: 'User details found' },
|
|
119
|
+
404: { description: 'User not found' },
|
|
120
|
+
},
|
|
121
|
+
async handle({ params }) {
|
|
122
|
+
return { id: params.id, name: 'Alice' }
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Documenting Class-Based Controllers (OOP)
|
|
131
|
+
|
|
132
|
+
For class-based controllers in `route.ts`, use OpenAPI decorators from `exisjs/swagger` alongside standard routing decorators:
|
|
133
|
+
|
|
134
|
+
```typescript filename="src/http/products/route.ts"
|
|
135
|
+
import { Controller, Get, Post, Body, Param, Query } from 'exisjs/decorators'
|
|
136
|
+
import {
|
|
137
|
+
ApiTags,
|
|
138
|
+
ApiOperation,
|
|
139
|
+
ApiResponse,
|
|
140
|
+
ApiBearerAuth,
|
|
141
|
+
ApiExclude,
|
|
142
|
+
} from 'exisjs/swagger'
|
|
143
|
+
|
|
144
|
+
@ApiTags('Products')
|
|
145
|
+
@ApiBearerAuth()
|
|
146
|
+
@Controller()
|
|
147
|
+
export default class ProductsController {
|
|
148
|
+
@Get('/')
|
|
149
|
+
@ApiOperation({
|
|
150
|
+
summary: 'List products',
|
|
151
|
+
description: 'Fetches paginated list of catalog products',
|
|
152
|
+
})
|
|
153
|
+
@ApiResponse({ status: 200, description: 'List of products' })
|
|
154
|
+
async list(@Query('page') page: number = 1) {
|
|
155
|
+
return [
|
|
156
|
+
{ id: 1, name: 'Mechanical Keyboard', price: 99 },
|
|
157
|
+
{ id: 2, name: 'Ergonomic Mouse', price: 49 },
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Post('/')
|
|
162
|
+
@ApiOperation({ summary: 'Create new product' })
|
|
163
|
+
@ApiResponse({ status: 201, description: 'Product created' })
|
|
164
|
+
async create(@Body() dto: { name: string; price: number }) {
|
|
165
|
+
return { id: 3, ...dto }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@Get('/:id')
|
|
169
|
+
@ApiOperation({ summary: 'Get product by ID' })
|
|
170
|
+
@ApiResponse({ status: 200, description: 'Product found' })
|
|
171
|
+
@ApiResponse({ status: 404, description: 'Product not found' })
|
|
172
|
+
async getById(@Param('id') id: string) {
|
|
173
|
+
return { id, name: 'Mechanical Keyboard', price: 99 }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Get('/internal/metrics')
|
|
177
|
+
@ApiExclude() // Excluded from Swagger documentation
|
|
178
|
+
async metrics() {
|
|
179
|
+
return { uptime: process.uptime() }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Cascading Boundary Metadata
|
|
187
|
+
|
|
188
|
+
Cascading boundaries (`boundary.ts`) can assign shared tags, descriptions, or security requirements to all nested routes in the directory:
|
|
189
|
+
|
|
190
|
+
```typescript filename="src/http/admin/boundary.ts"
|
|
191
|
+
import { defineBoundary } from 'exisjs/router'
|
|
192
|
+
|
|
193
|
+
export const config = defineBoundary({
|
|
194
|
+
tags: ['Admin Portal'],
|
|
195
|
+
security: [{ bearerAuth: [] }],
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Security Schemes & Authentication
|
|
202
|
+
|
|
203
|
+
Define reusable security schemes (such as Bearer JWT, API Keys, or OAuth2) in `exis.config.ts`:
|
|
204
|
+
|
|
205
|
+
```typescript filename="exis.config.ts"
|
|
206
|
+
import { defineConfig } from 'exisjs/config'
|
|
207
|
+
|
|
208
|
+
export default defineConfig({
|
|
209
|
+
docs: {
|
|
210
|
+
enabled: true,
|
|
211
|
+
securitySchemes: {
|
|
212
|
+
bearerAuth: {
|
|
213
|
+
type: 'http',
|
|
214
|
+
scheme: 'bearer',
|
|
215
|
+
bearerFormat: 'JWT',
|
|
216
|
+
description: 'Enter your JWT token: Bearer <token>',
|
|
217
|
+
},
|
|
218
|
+
apiKeyAuth: {
|
|
219
|
+
type: 'apiKey',
|
|
220
|
+
in: 'header',
|
|
221
|
+
name: 'X-API-KEY',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
security: [
|
|
225
|
+
{ bearerAuth: [] },
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Advanced Configuration (`exis.config.ts`)
|
|
234
|
+
|
|
235
|
+
For advanced customization, such as modifying the generated OpenAPI spec before serving or injecting custom styling:
|
|
236
|
+
|
|
237
|
+
```typescript filename="exis.config.ts"
|
|
238
|
+
import { defineConfig } from 'exisjs/config'
|
|
239
|
+
|
|
240
|
+
export default defineConfig({
|
|
241
|
+
docs: {
|
|
242
|
+
enabled: true,
|
|
243
|
+
title: 'Enterprise API Gateway',
|
|
244
|
+
version: '2.0.0',
|
|
245
|
+
description: 'Internal and external microservices API reference',
|
|
246
|
+
ui: 'scalar', // 'swagger-ui' | 'scalar'
|
|
247
|
+
servers: [
|
|
248
|
+
{ url: 'https://api.example.com', description: 'Production Server' },
|
|
249
|
+
{ url: 'http://localhost:3000', description: 'Local Development' },
|
|
250
|
+
],
|
|
251
|
+
transformSpec(spec) {
|
|
252
|
+
// Programmatically mutate or enrich OpenAPI specification
|
|
253
|
+
spec.info.contact = { name: 'API Support', email: 'support@example.com' }
|
|
254
|
+
return spec
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Server Lifecycle Hook (`server.ts`)
|
|
263
|
+
|
|
264
|
+
You can also interact with the Swagger subsystem or application instance during server boot in `src/http/server.ts`:
|
|
265
|
+
|
|
266
|
+
### Functional Paradigm
|
|
267
|
+
```typescript filename="src/http/server.ts"
|
|
268
|
+
import { exis } from 'exisjs'
|
|
269
|
+
|
|
270
|
+
export default exis({
|
|
271
|
+
onStart(app) {
|
|
272
|
+
console.log('[API] Swagger documentation ready at /docs')
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Class-Based (OOP) Paradigm
|
|
278
|
+
```typescript filename="src/http/server.ts"
|
|
279
|
+
import { Server } from 'exisjs/decorators'
|
|
280
|
+
import type { App } from 'exisjs'
|
|
281
|
+
|
|
282
|
+
@Server()
|
|
283
|
+
export default class RootServer {
|
|
284
|
+
async onStart(app: App) {
|
|
285
|
+
console.log('[API] Swagger documentation ready at /docs')
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Cron & Scheduling"
|
|
3
|
+
description: "API Reference for ExisJS Task Scheduling, Cron Expressions, and Job Management."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cron & Scheduling (`exisjs/cron`, `exisjs/decorators`)
|
|
7
|
+
|
|
8
|
+
The `exisjs/cron` module provides task scheduling, cron expression parsing, interval timers, and runtime job management.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Functional Definition Helpers
|
|
13
|
+
|
|
14
|
+
### `cron(scheduleOrOptions, handler?)`
|
|
15
|
+
|
|
16
|
+
Defines a functional cron job.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { cron, CronExpression } from 'exisjs/cron'
|
|
20
|
+
|
|
21
|
+
// Signature 1: Options object with inline run handler
|
|
22
|
+
export default cron({
|
|
23
|
+
name: 'sync-users',
|
|
24
|
+
schedule: CronExpression.EVERY_10_MINUTES,
|
|
25
|
+
timezone: 'UTC',
|
|
26
|
+
preventOverlap: true,
|
|
27
|
+
retries: 2,
|
|
28
|
+
retryDelayMs: 1000,
|
|
29
|
+
async run({ app, log }) {
|
|
30
|
+
// ...
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Signature 2: Schedule string with handler
|
|
35
|
+
export const purgeJob = cron('0 0 * * *', async ({ log }) => {
|
|
36
|
+
// ...
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `cron.every(duration, handler, options?)`
|
|
41
|
+
|
|
42
|
+
Shorthand helper accepting human-readable duration strings (e.g., `'10 seconds'`, `'5 minutes'`, `'2 hours'`).
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { cron } from 'exisjs/cron'
|
|
46
|
+
|
|
47
|
+
export const healthCheck = cron.every('30 seconds', async ({ log }) => {
|
|
48
|
+
log.info('Checking external dependencies...')
|
|
49
|
+
}, {
|
|
50
|
+
name: 'ext-health-check'
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `cron.interval(ms, handler, options?)`
|
|
55
|
+
|
|
56
|
+
Shorthand helper for millisecond recurring intervals.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { cron } from 'exisjs/cron'
|
|
60
|
+
|
|
61
|
+
export const heartbeat = cron.interval(60_000, async ({ log }) => {
|
|
62
|
+
log.info('Server heartbeat tick')
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `cron.timeout(ms, handler, options?)`
|
|
67
|
+
|
|
68
|
+
Shorthand helper for one-time delayed startup tasks (executes once after application boot).
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { cron } from 'exisjs/cron'
|
|
72
|
+
|
|
73
|
+
export const cacheWarmup = cron.timeout(5_000, async ({ app, log }) => {
|
|
74
|
+
log.info('Warming up Redis cache after boot')
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 2. OOP Decorators (`exisjs/decorators`)
|
|
81
|
+
|
|
82
|
+
### `@Cron(schedule, options?)`
|
|
83
|
+
|
|
84
|
+
Method decorator that marks a class method as a scheduled cron job. Can be used on any `@Injectable()` service.
|
|
85
|
+
|
|
86
|
+
| Parameter | Type | Description |
|
|
87
|
+
| :--- | :--- | :--- |
|
|
88
|
+
| `schedule` | `string` | Standard 5 or 6 field cron expression or `CronExpression` constant. |
|
|
89
|
+
| `options` | `CronJobOptions` | Optional job configuration (name, timezone, preventOverlap, retries, etc.). |
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Injectable, Cron, CronExpression } from 'exisjs/decorators'
|
|
93
|
+
|
|
94
|
+
@Injectable()
|
|
95
|
+
export default class ReportService {
|
|
96
|
+
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT, {
|
|
97
|
+
name: 'nightly-report',
|
|
98
|
+
timezone: 'UTC',
|
|
99
|
+
preventOverlap: true,
|
|
100
|
+
})
|
|
101
|
+
async generateDailyReport() {
|
|
102
|
+
// ...
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `@Interval(ms, options?)`
|
|
108
|
+
|
|
109
|
+
Method decorator for recurring millisecond tasks.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { Injectable, Interval } from 'exisjs/decorators'
|
|
113
|
+
|
|
114
|
+
@Injectable()
|
|
115
|
+
export default class QueueMonitor {
|
|
116
|
+
@Interval(15_000, { name: 'queue-poll' })
|
|
117
|
+
async pollQueue() {
|
|
118
|
+
// ...
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `@TimeoutTask(ms, options?)` (alias `@ScheduledTimeout`)
|
|
124
|
+
|
|
125
|
+
Method decorator for one-time startup tasks executed after the specified millisecond delay.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { Injectable, TimeoutTask } from 'exisjs/decorators'
|
|
129
|
+
|
|
130
|
+
@Injectable()
|
|
131
|
+
export default class CacheBootstrap {
|
|
132
|
+
@TimeoutTask(3_000, { name: 'seed-cache' })
|
|
133
|
+
async seedCache() {
|
|
134
|
+
// ...
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 3. Configuration & Options
|
|
142
|
+
|
|
143
|
+
### `CronJobOptions`
|
|
144
|
+
|
|
145
|
+
Configuration object passed to `cron()` or decorator options:
|
|
146
|
+
|
|
147
|
+
| Property | Type | Default | Description |
|
|
148
|
+
| :--- | :--- | :--- | :--- |
|
|
149
|
+
| `name` | `string` | `auto-generated` | Unique identifier for the job. Used in logs and runtime APIs. |
|
|
150
|
+
| `schedule` | `string` | `undefined` | 5 or 6 field cron expression (e.g. `'0 0 * * *'`). |
|
|
151
|
+
| `interval` | `number` | `undefined` | Millisecond interval for recurring execution. |
|
|
152
|
+
| `timeout` | `number` | `undefined` | Millisecond delay for one-time execution. |
|
|
153
|
+
| `timezone` | `string` | `'UTC'` | Valid IANA timezone string (e.g. `'America/New_York'`, `'Asia/Kolkata'`). |
|
|
154
|
+
| `preventOverlap` | `boolean` | `true` | Skips execution if the previous tick is still running. |
|
|
155
|
+
| `overlapPolicy` | `'skip' \| 'queue'` | `'skip'` | Action taken when execution overlap occurs. |
|
|
156
|
+
| `retries` | `number` | `0` | Number of retry attempts upon failure. |
|
|
157
|
+
| `retryDelayMs` | `number` | `1000` | Milliseconds to wait before executing a retry. |
|
|
158
|
+
| `autoStart` | `boolean` | `true` | Whether the job automatically starts on application bootstrap. |
|
|
159
|
+
| `run` / `handle` | `(ctx: CronContext) => Promise<any> \| any` | `undefined` | The execution callback function. |
|
|
160
|
+
| `onError` | `(err: Error, ctx: CronContext) => void` | `undefined` | Hook called when the job fails after all retries. |
|
|
161
|
+
|
|
162
|
+
### `CronContext`
|
|
163
|
+
|
|
164
|
+
The context object provided as the first argument to job execution handlers:
|
|
165
|
+
|
|
166
|
+
| Property | Type | Description |
|
|
167
|
+
| :--- | :--- | :--- |
|
|
168
|
+
| `name` | `string` | The unique name of the active job. |
|
|
169
|
+
| `scheduledTime` | `Date` | The theoretical timestamp the job was scheduled to run. |
|
|
170
|
+
| `executionTime` | `Date` | The actual timestamp execution began. |
|
|
171
|
+
| `iteration` | `number` | Total number of executions completed by this job instance. |
|
|
172
|
+
| `app` | `App` | Reference to the current ExisJS application instance. |
|
|
173
|
+
| `log` | `Logger` | Scoped logger instance with `job: name` context. |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 4. `CronExpression` Presets
|
|
178
|
+
|
|
179
|
+
ExisJS exports pre-validated standard cron expression constants:
|
|
180
|
+
|
|
181
|
+
| Constant | Value | Description |
|
|
182
|
+
| :--- | :--- | :--- |
|
|
183
|
+
| `CronExpression.EVERY_SECOND` | `* * * * * *` | Every second (6-field) |
|
|
184
|
+
| `CronExpression.EVERY_5_SECONDS` | `*/5 * * * * *` | Every 5 seconds (6-field) |
|
|
185
|
+
| `CronExpression.EVERY_10_SECONDS` | `*/10 * * * * *` | Every 10 seconds (6-field) |
|
|
186
|
+
| `CronExpression.EVERY_30_SECONDS` | `*/30 * * * * *` | Every 30 seconds (6-field) |
|
|
187
|
+
| `CronExpression.EVERY_MINUTE` | `* * * * *` | Every minute |
|
|
188
|
+
| `CronExpression.EVERY_5_MINUTES` | `*/5 * * * *` | Every 5 minutes |
|
|
189
|
+
| `CronExpression.EVERY_10_MINUTES` | `*/10 * * * *` | Every 10 minutes |
|
|
190
|
+
| `CronExpression.EVERY_15_MINUTES` | `*/15 * * * *` | Every 15 minutes |
|
|
191
|
+
| `CronExpression.EVERY_30_MINUTES` | `*/30 * * * *` | Every 30 minutes |
|
|
192
|
+
| `CronExpression.EVERY_HOUR` | `0 * * * *` | Top of every hour |
|
|
193
|
+
| `CronExpression.EVERY_2_HOURS` | `0 */2 * * *` | Every 2 hours |
|
|
194
|
+
| `CronExpression.EVERY_6_HOURS` | `0 */6 * * *` | Every 6 hours |
|
|
195
|
+
| `CronExpression.EVERY_12_HOURS` | `0 */12 * * *` | Every 12 hours |
|
|
196
|
+
| `CronExpression.EVERY_DAY_AT_MIDNIGHT` | `0 0 * * *` | Daily at 00:00 (Midnight) |
|
|
197
|
+
| `CronExpression.EVERY_DAY_AT_1AM` | `0 1 * * *` | Daily at 01:00 AM |
|
|
198
|
+
| `CronExpression.EVERY_DAY_AT_2AM` | `0 2 * * *` | Daily at 02:00 AM |
|
|
199
|
+
| `CronExpression.EVERY_DAY_AT_NOON` | `0 12 * * *` | Daily at 12:00 PM |
|
|
200
|
+
| `CronExpression.EVERY_WEEK` | `0 0 * * 0` | Every Sunday at midnight |
|
|
201
|
+
| `CronExpression.EVERY_MONTH` | `0 0 1 * *` | First day of every month at midnight |
|
|
202
|
+
| `CronExpression.EVERY_YEAR` | `0 0 1 1 *` | January 1st at midnight |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 5. Runtime Management API (`app.cron`)
|
|
207
|
+
|
|
208
|
+
Access the cron manager via `app.cron`:
|
|
209
|
+
|
|
210
|
+
### `app.cron.schedule(options)`
|
|
211
|
+
Registers and schedules a new cron job dynamically.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const job = app.cron.schedule({
|
|
215
|
+
name: 'dynamic-sync',
|
|
216
|
+
schedule: '0 */3 * * *',
|
|
217
|
+
run: async ({ log }) => {
|
|
218
|
+
log.info('Dynamic sync executed')
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### `app.cron.get(name)`
|
|
224
|
+
Returns the `CronJob` instance by name, or `undefined` if not found.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const job = app.cron.get('nightly-sync')
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### `app.cron.has(name)`
|
|
231
|
+
Returns `true` if a job with the specified name exists.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
if (app.cron.has('nightly-sync')) {
|
|
235
|
+
// ...
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `app.cron.list()`
|
|
240
|
+
Returns an array of all registered `CronJob` instances.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const allJobs = app.cron.list()
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### `app.cron.status()`
|
|
247
|
+
Returns an array of status and metric descriptors for all registered jobs.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const stats = app.cron.status()
|
|
251
|
+
// [
|
|
252
|
+
// {
|
|
253
|
+
// name: 'nightly-sync',
|
|
254
|
+
// status: 'running',
|
|
255
|
+
// running: false,
|
|
256
|
+
// totalExecutions: 14,
|
|
257
|
+
// successCount: 14,
|
|
258
|
+
// failureCount: 0,
|
|
259
|
+
// lastExecution: '2026-09-22T00:00:00.000Z',
|
|
260
|
+
// nextExecution: '2026-09-23T00:00:00.000Z',
|
|
261
|
+
// lastDurationMs: 142
|
|
262
|
+
// }
|
|
263
|
+
// ]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `app.cron.trigger(name)`
|
|
267
|
+
Manually triggers an immediate execution of the specified job, returning the handler result.
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
const result = await app.cron.trigger('nightly-sync')
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `app.cron.pause(name)` / `app.cron.resume(name)`
|
|
274
|
+
Pauses or resumes a job's automatic scheduling without unregistering it.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
app.cron.pause('nightly-sync')
|
|
278
|
+
app.cron.resume('nightly-sync')
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### `app.cron.remove(name)`
|
|
282
|
+
Stops and unregisters a job by name.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
app.cron.remove('nightly-sync')
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### `app.cron.stopAll()`
|
|
289
|
+
Stops all scheduled timers across all registered jobs.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
app.cron.stopAll()
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### `app.cron.drain(timeoutMs?)`
|
|
296
|
+
Stops new ticks and awaits any currently running job executions to complete. Called automatically during graceful server shutdown.
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
await app.cron.drain(5000)
|
|
300
|
+
```
|
|
@@ -123,3 +123,11 @@ Parameter decorators automatically extract data from the incoming request and in
|
|
|
123
123
|
- `@Boundary(config)`: Marks a class as a folder-scoped Boundary configuring CORS, headers, exclusions, and pipeline steps.
|
|
124
124
|
- `@Server(config)`: Marks a class as a root server definition.
|
|
125
125
|
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 6. Cron & Task Scheduling
|
|
129
|
+
|
|
130
|
+
- `@Cron(schedule: string, options?: CronJobOptions)`: Schedules a class method as a recurring cron job on any `@Injectable()` service.
|
|
131
|
+
- `@Interval(ms: number, options?: IntervalOptions)`: Schedules a class method to execute repeatedly every `ms` milliseconds.
|
|
132
|
+
- `@TimeoutTask(ms: number, options?: TimeoutOptions)` (alias `@ScheduledTimeout`): Schedules a class method to execute once after `ms` milliseconds following application bootstrap.
|
|
133
|
+
|
|
@@ -133,6 +133,50 @@ export class InsufficientFundsError extends HttpError {
|
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
136
|
+
## Framework Exception File Convention (`src/http/error.ts`)
|
|
137
|
+
|
|
138
|
+
ExisJS natively discovers and auto-mounts `src/http/error.ts` (or `src/http/*.error.ts`) during application bootstrap. It serves as the single source of truth for centralized exception filtering without requiring manual middleware registration in `server.ts`.
|
|
139
|
+
|
|
140
|
+
### Handler Signatures
|
|
141
|
+
|
|
142
|
+
**Functional (`export function onError`):**
|
|
143
|
+
```typescript filename="src/http/error.ts"
|
|
144
|
+
import type { Request, Response } from 'exisjs'
|
|
145
|
+
|
|
146
|
+
export function onError(err: any, req: Request, res: Response): void | Promise<void> {
|
|
147
|
+
const status = err.statusCode || err.status || 500
|
|
148
|
+
res.status(status).json({
|
|
149
|
+
success: false,
|
|
150
|
+
error: {
|
|
151
|
+
code: err.code || 'ERROR',
|
|
152
|
+
message: err.message || 'Internal Server Error',
|
|
153
|
+
path: req.path,
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Class-Based OOP (`export default class`):**
|
|
160
|
+
```typescript filename="src/http/error.ts"
|
|
161
|
+
import type { Request, Response } from 'exisjs'
|
|
162
|
+
|
|
163
|
+
export default class GlobalErrorHandler {
|
|
164
|
+
onError(err: any, req: Request, res: Response): void | Promise<void> {
|
|
165
|
+
const status = err.statusCode || err.status || 500
|
|
166
|
+
res.status(status).json({
|
|
167
|
+
success: false,
|
|
168
|
+
error: {
|
|
169
|
+
code: err.code || 'ERROR',
|
|
170
|
+
message: err.message || 'Internal Server Error',
|
|
171
|
+
path: req.path,
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
136
180
|
## Global Error Handler
|
|
137
181
|
|
|
138
182
|
ExisJS uses a highly sophisticated global error handler out of the box, generated via `createErrorHandler(isDev)`.
|