exisjs 0.7.5 → 0.7.7
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 +171 -86
- 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/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +54 -16
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +26 -3
- 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 +264 -33
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/resolve-aliases.d.ts +1 -1
- package/dist/cli/resolve-aliases.d.ts.map +1 -1
- package/dist/cli/resolve-aliases.js +30 -13
- package/dist/cli/resolve-aliases.js.map +1 -1
- package/dist/config/config.d.ts +3 -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 +6 -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/middleware.d.ts +65 -2
- package/dist/decorators/middleware.d.ts.map +1 -1
- package/dist/decorators/middleware.js +148 -62
- package/dist/decorators/middleware.js.map +1 -1
- package/dist/decorators/params.d.ts +102 -2
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +105 -3
- package/dist/decorators/params.js.map +1 -1
- package/dist/decorators/response.d.ts.map +1 -1
- package/dist/decorators/response.js +32 -22
- package/dist/decorators/response.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/di/container.d.ts.map +1 -1
- package/dist/di/container.js +6 -1
- package/dist/di/container.js.map +1 -1
- package/dist/docs/content/cli/commands.mdx +1 -1
- package/dist/docs/content/controllers.mdx +127 -10
- package/dist/docs/content/cron.mdx +196 -0
- package/dist/docs/content/decorators.mdx +22 -4
- package/dist/docs/content/errors.mdx +58 -9
- package/dist/docs/content/introduction.mdx +7 -7
- package/dist/docs/content/middleware.mdx +269 -6
- package/dist/docs/content/requests.mdx +180 -6
- package/dist/docs/content/responses.mdx +283 -32
- package/dist/docs/content/routing.mdx +46 -0
- package/dist/docs/content/swagger.mdx +228 -0
- package/dist/docs/content/validation.mdx +190 -11
- package/dist/docs/reference/context.mdx +11 -0
- package/dist/docs/reference/cron.mdx +300 -0
- package/dist/docs/reference/decorators.mdx +22 -4
- package/dist/docs/reference/error.mdx +88 -0
- package/dist/docs/reference/logger.mdx +175 -43
- package/dist/docs/reference/middleware.mdx +31 -0
- package/dist/docs/reference/router.mdx +22 -0
- package/dist/docs/reference/swagger.mdx +143 -0
- package/dist/docs/reference/validator.mdx +83 -23
- package/dist/error/errors.d.ts.map +1 -1
- package/dist/error/errors.js +95 -43
- package/dist/error/errors.js.map +1 -1
- package/dist/error/index.d.ts +1 -0
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +11 -1
- package/dist/error/index.js.map +1 -1
- package/dist/error/overlay.d.ts +42 -0
- package/dist/error/overlay.d.ts.map +1 -0
- package/dist/error/overlay.js +306 -0
- package/dist/error/overlay.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/start-server.js +33 -7
- package/dist/lib/start-server.js.map +1 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +9 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/guard.d.ts +2 -2
- package/dist/middleware/guard.d.ts.map +1 -1
- package/dist/middleware/guard.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts +1 -0
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +2 -0
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/middleware.d.ts +2 -2
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +20 -2
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/security.d.ts +43 -1
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +101 -0
- package/dist/middleware/security.js.map +1 -1
- package/dist/response/index.d.ts +229 -6
- package/dist/response/index.d.ts.map +1 -1
- package/dist/response/index.js +272 -2
- package/dist/response/index.js.map +1 -1
- package/dist/router/boundary.d.ts +41 -11
- 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 +108 -4
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/index.d.ts +2 -2
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +2 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/route-builder.d.ts +262 -65
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js +181 -23
- 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 +71 -792
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +62 -17
- 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 +23 -0
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/app.d.ts +3 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +13 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +91 -30
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/cluster.d.ts.map +1 -1
- package/dist/server/cluster.js +13 -2
- package/dist/server/cluster.js.map +1 -1
- package/dist/server/context.d.ts +8 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +50 -6
- package/dist/server/context.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/lifecycle.d.ts +22 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/lifecycle.js +106 -0
- package/dist/server/lifecycle.js.map +1 -0
- package/dist/server/request-handler.d.ts +1 -0
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +27 -17
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts +1 -139
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +37 -433
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +112 -156
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +129 -279
- 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/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +6 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mocks.d.ts.map +1 -1
- package/dist/testing/mocks.js +15 -3
- package/dist/testing/mocks.js.map +1 -1
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +184 -1
- package/dist/utils/logger.js.map +1 -1
- 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-types.d.ts +2 -1
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/tex-types.js +2 -1
- package/dist/validator/tex-types.js.map +1 -1
- package/dist/validator/tex.d.ts +6 -142
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +14 -733
- 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 +8 -4
- package/dist/dev/error-overlay.d.ts +0 -3
- package/dist/dev/error-overlay.d.ts.map +0 -1
- package/dist/dev/error-overlay.js +0 -118
- package/dist/dev/error-overlay.js.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/queues"
|
|
3
|
+
nextTitle: "Queues & Background Jobs"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cron & Task Scheduling
|
|
7
|
+
|
|
8
|
+
ExisJS includes a built-in Cron and Task Scheduling system designed for reliability and developer ergonomic efficiency. It resolves common Node.js scheduling challenges such as timer drift, concurrent execution overlap, unhandled exceptions crashing the process, timezone/DST inaccuracies, and zombie timers during hot reload or process shutdown.
|
|
9
|
+
|
|
10
|
+
Both **Functional** and **Class-Based (OOP)** paradigms are fully supported with complete feature parity.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## File Conventions & Auto-Discovery
|
|
15
|
+
|
|
16
|
+
ExisJS auto-mounts cron jobs using two straightforward conventions:
|
|
17
|
+
|
|
18
|
+
1. **Single-File Management (`src/http/cron.ts` or `src/http/cron.js`)**:
|
|
19
|
+
Define and manage all your scheduled tasks in a single file located at `src/http/cron.ts` (or inside route slices such as `src/http/admin/cron.ts`). Multiple jobs can be exported using named exports, a default export, or an array of jobs.
|
|
20
|
+
|
|
21
|
+
2. **Dedicated Directory (`src/cron/*`)**:
|
|
22
|
+
Organize distinct tasks into separate files inside `src/cron/` (e.g. `src/cron/sync.ts`, `src/cron/reports.ts`). Every file inside `src/cron/` is automatically registered upon application boot.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. Functional Paradigm (`cron`)
|
|
27
|
+
|
|
28
|
+
Import `cron` from `exisjs/cron` to define scheduled tasks.
|
|
29
|
+
|
|
30
|
+
### Single-File Multi-Job Example
|
|
31
|
+
|
|
32
|
+
Manage multiple jobs within `src/http/cron.ts`:
|
|
33
|
+
|
|
34
|
+
```typescript filename="src/http/cron.ts"
|
|
35
|
+
import { cron, CronExpression } from 'exisjs/cron'
|
|
36
|
+
|
|
37
|
+
// Job 1: Nightly synchronization
|
|
38
|
+
export const syncJob = cron({
|
|
39
|
+
name: 'nightly-sync',
|
|
40
|
+
schedule: CronExpression.EVERY_DAY_AT_MIDNIGHT,
|
|
41
|
+
timezone: 'UTC',
|
|
42
|
+
preventOverlap: true,
|
|
43
|
+
retries: 2,
|
|
44
|
+
retryDelayMs: 3000,
|
|
45
|
+
async run({ app, log }) {
|
|
46
|
+
log.info('Executing nightly sync...')
|
|
47
|
+
},
|
|
48
|
+
onError(err, { log }) {
|
|
49
|
+
log.error({ err }, 'Nightly sync failed after retries')
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Job 2: Frequent polling using human-readable duration
|
|
54
|
+
export const pollJob = cron.every('5 minutes', async ({ log }) => {
|
|
55
|
+
log.info('Checking external queue...')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Job 3: Recurring millisecond interval
|
|
59
|
+
export const heartbeat = cron.interval(30_000, async ({ log }) => {
|
|
60
|
+
log.info('System heartbeat active')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Job 4: Startup delayed task (runs 5 seconds after server boots)
|
|
64
|
+
export const warmup = cron.timeout(5_000, async ({ app, log }) => {
|
|
65
|
+
log.info('Pre-warming cache...')
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Dedicated File Example
|
|
70
|
+
|
|
71
|
+
```typescript filename="src/cron/cleanup.ts"
|
|
72
|
+
import { cron, CronExpression } from 'exisjs/cron'
|
|
73
|
+
|
|
74
|
+
export default cron({
|
|
75
|
+
name: 'db-cleanup',
|
|
76
|
+
schedule: CronExpression.EVERY_DAY_AT_2AM,
|
|
77
|
+
timezone: 'America/New_York',
|
|
78
|
+
preventOverlap: true,
|
|
79
|
+
async run({ log }) {
|
|
80
|
+
log.info('Purging stale database records...')
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 2. Class-Based (OOP) Paradigm (`@Cron`)
|
|
88
|
+
|
|
89
|
+
In the OOP paradigm, methods on any `@Injectable()` service or class can be scheduled using decorators from `exisjs/decorators` with full **Dependency Injection** support.
|
|
90
|
+
|
|
91
|
+
```typescript filename="src/cron/cleanup.ts"
|
|
92
|
+
import { Injectable, Cron, Interval, TimeoutTask, CronExpression } from 'exisjs/decorators'
|
|
93
|
+
import { DatabaseService } from '../services/database.service'
|
|
94
|
+
import { LoggerService } from '../services/logger.service'
|
|
95
|
+
|
|
96
|
+
@Injectable()
|
|
97
|
+
export default class CleanupService {
|
|
98
|
+
constructor(
|
|
99
|
+
private db: DatabaseService,
|
|
100
|
+
private logger: LoggerService
|
|
101
|
+
) {}
|
|
102
|
+
|
|
103
|
+
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT, {
|
|
104
|
+
name: 'nightly-purge',
|
|
105
|
+
timezone: 'UTC',
|
|
106
|
+
preventOverlap: true,
|
|
107
|
+
})
|
|
108
|
+
async purgeOldSessions() {
|
|
109
|
+
this.logger.info('Purging expired user sessions...')
|
|
110
|
+
await this.db.deleteExpiredSessions()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@Interval(60_000, { name: 'health-ping' })
|
|
114
|
+
async checkDatabaseHealth() {
|
|
115
|
+
await this.db.ping()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@TimeoutTask(5_000, { name: 'cache-warmup' })
|
|
119
|
+
async warmupCache() {
|
|
120
|
+
this.logger.info('Pre-warming cache...')
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 3. Core Reliability Features
|
|
128
|
+
|
|
129
|
+
### Overlap Prevention (`preventOverlap: true`)
|
|
130
|
+
Standard timers trigger subsequent ticks even if the current execution is stalled, leading to connection exhaustion and database lock contention. With `preventOverlap: true` (default: `true`), ExisJS locks execution during active jobs and skips overlapping executions until the current run completes.
|
|
131
|
+
|
|
132
|
+
### Error Isolation Sandbox
|
|
133
|
+
Exceptions thrown inside cron handlers are caught within an isolated execution boundary. An error in a background task will never crash the HTTP server or stop subsequent cron triggers. Errors are logged and forwarded to an optional `onError(err, context)` hook for monitoring integration (e.g. Sentry, Datadog).
|
|
134
|
+
|
|
135
|
+
### IANA Timezones & Daylight Saving Time (DST)
|
|
136
|
+
Supports standard IANA timezones (e.g. `'America/New_York'`, `'Asia/Kolkata'`, `'Europe/London'`, `'UTC'`). Next execution dates are computed accurately across DST transitions using cached native `Intl.DateTimeFormat` evaluations.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 4. Programmatic Runtime Management (`app.cron`)
|
|
141
|
+
|
|
142
|
+
You can inspect, pause, resume, or trigger jobs dynamically at runtime via `app.cron`:
|
|
143
|
+
|
|
144
|
+
```typescript filename="src/http/admin/cron/route.ts"
|
|
145
|
+
import { controller, route } from 'exisjs/router'
|
|
146
|
+
|
|
147
|
+
export default controller({
|
|
148
|
+
// Query status and metrics of all registered jobs
|
|
149
|
+
listJobs: route.get('/', async ({ app }) => {
|
|
150
|
+
return {
|
|
151
|
+
jobs: app.cron.status()
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
|
|
155
|
+
// Manually trigger a scheduled job on demand
|
|
156
|
+
triggerJob: route.post('/:name/trigger', async ({ app, params }) => {
|
|
157
|
+
const result = await app.cron.trigger(params.name)
|
|
158
|
+
return { success: true, result }
|
|
159
|
+
}),
|
|
160
|
+
|
|
161
|
+
// Pause a job
|
|
162
|
+
pauseJob: route.post('/:name/pause', async ({ app, params }) => {
|
|
163
|
+
app.cron.pause(params.name)
|
|
164
|
+
return { success: true, message: `Job ${params.name} paused` }
|
|
165
|
+
}),
|
|
166
|
+
|
|
167
|
+
// Resume a paused job
|
|
168
|
+
resumeJob: route.post('/:name/resume', async ({ app, params }) => {
|
|
169
|
+
app.cron.resume(params.name)
|
|
170
|
+
return { success: true, message: `Job ${params.name} resumed` }
|
|
171
|
+
}),
|
|
172
|
+
})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 5. Graceful Shutdown & Draining
|
|
178
|
+
|
|
179
|
+
When the application receives a shutdown signal (`SIGINT`, `SIGTERM`, or `app.close()`):
|
|
180
|
+
1. The scheduler stops scheduling future ticks immediately.
|
|
181
|
+
2. Active timers are unreferenced (`unref()`).
|
|
182
|
+
3. Running tasks are allowed to finish within the configured drain window (default: 5000ms) before the process terminates.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 6. CLI Scaffolding
|
|
187
|
+
|
|
188
|
+
Generate cron job files using the ExisJS CLI:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Functional Job (generates src/cron/cleanup.ts)
|
|
192
|
+
npx exis generate cron cleanup
|
|
193
|
+
|
|
194
|
+
# Class-Based Job (generates src/cron/cleanup.ts with @Injectable & @Cron)
|
|
195
|
+
npx exis generate cron cleanup --oop
|
|
196
|
+
```
|
|
@@ -134,6 +134,12 @@ export default class UsersController {
|
|
|
134
134
|
- `@Param(nameOrPipe?)` — Extracts a route path param (e.g. `:id`).
|
|
135
135
|
- `@QueryParam(nameOrPipe?)` / `@Query(nameOrPipe?)` — Extracts a query parameter from the URL query string.
|
|
136
136
|
- `@Headers(nameOrPipe?)` — Extracts a request header by name.
|
|
137
|
+
- `@Cookies(nameOrPipe?)` / `@Cookie(nameOrPipe?)` — Injects incoming parsed cookies or a specific cookie value.
|
|
138
|
+
- `@State(keyOrPipe?)` — Injects request state from execution context or middleware.
|
|
139
|
+
- `@AppCtx()` — Injects the root ExisJS Application instance.
|
|
140
|
+
- `@Fields(nameOrPipe?)` — Injects non-file multipart form fields.
|
|
141
|
+
- `@UploadedFile(nameOrPipe?)` — Injects a single uploaded file.
|
|
142
|
+
- `@UploadedFiles(nameOrPipe?)` — Injects multiple uploaded files.
|
|
137
143
|
- `@HostParam(nameOrPipe?)` — Extracts a host subdomain parameter.
|
|
138
144
|
- `@Req()` — Injects the raw request object.
|
|
139
145
|
- `@Res(options?)` — Injects the response object (e.g. `{ passthrough: true }`).
|
|
@@ -150,16 +156,19 @@ export default class UsersController {
|
|
|
150
156
|
|
|
151
157
|
## 4. Middleware & Pipeline Decorators
|
|
152
158
|
|
|
153
|
-
You can bind middlewares, guards, interceptors, and exception filters to controllers or route methods using
|
|
159
|
+
You can bind middlewares, guards, interceptors, and exception filters to controllers or route methods using first-class decorators:
|
|
154
160
|
|
|
155
161
|
```typescript filename="src/http/checkout/route.ts"
|
|
156
|
-
import { Controller, Post, Use, Idempotent } from 'exisjs/decorators'
|
|
162
|
+
import { Controller, Post, Use, Idempotent, RateLimit, Cors, Timeout } from 'exisjs/decorators'
|
|
157
163
|
import { requireAuth } from '../../middleware/auth'
|
|
158
164
|
|
|
159
165
|
@Controller('/checkout')
|
|
160
166
|
@Use(requireAuth) // Applies to all routes in this controller
|
|
167
|
+
@Cors({ origin: 'https://app.yourdomain.com' })
|
|
168
|
+
@RateLimit({ max: 10, windowMs: 60000 })
|
|
161
169
|
export default class CheckoutController {
|
|
162
170
|
@Post('/')
|
|
171
|
+
@Timeout(30000)
|
|
163
172
|
@Idempotent({ ttlMs: 86400000 }) // In-memory off-heap idempotency protection
|
|
164
173
|
async processPayment() {
|
|
165
174
|
return { success: true }
|
|
@@ -169,7 +178,15 @@ export default class CheckoutController {
|
|
|
169
178
|
|
|
170
179
|
### Available Lifecycle & Middleware Decorators
|
|
171
180
|
- `@Use(...items)` — Unified decorator that attaches middlewares, guards (`canActivate`), interceptors (`intercept`), or exception filters (`catch`) to a class or method.
|
|
172
|
-
- `@
|
|
181
|
+
- `@Timeout(ms: number | TimeoutOptions)` — Overrides the request timeout for a class or method.
|
|
182
|
+
- `@Idempotent(options?)` — Caches responses based on the `Idempotency-Key` header using native in-memory off-heap storage.
|
|
183
|
+
- `@RateLimit(options?)` — Enforces request rate limits on a controller class or route method.
|
|
184
|
+
- `@Cors(options?)` — Configures Cross-Origin Resource Sharing rules on a controller class or route method.
|
|
185
|
+
- `@IpFilter(options)` — Enforces IP/CIDR allowlist and denylist.
|
|
186
|
+
- `@Compress()` — Enables automatic response compression (Gzip/Brotli).
|
|
187
|
+
- `@Dedupe(options)` — Deduplicates concurrent identical requests sharing the same key.
|
|
188
|
+
- `@UseUpload(options)` / `@Upload(options)` — Configures multipart file upload handling.
|
|
189
|
+
- `@BlockProbes(options?)` — Intercepts scanner exploit probes (`.env`, `/.git`, `/.DS_Store`) and blackholes them without log noise.
|
|
173
190
|
|
|
174
191
|
---
|
|
175
192
|
|
|
@@ -229,7 +246,8 @@ export class UserService {
|
|
|
229
246
|
You can inject dependencies using tokens or classes, and mark optional dependencies with `@Optional()`:
|
|
230
247
|
|
|
231
248
|
```typescript filename="src/http/users/route.ts"
|
|
232
|
-
import { Controller, Get, Param
|
|
249
|
+
import { Controller, Get, Param } from 'exisjs/decorators'
|
|
250
|
+
import { Inject, Optional } from 'exisjs/di'
|
|
233
251
|
import { UserService } from './service'
|
|
234
252
|
|
|
235
253
|
@Controller('/users')
|
|
@@ -163,40 +163,89 @@ ExisJS provides mechanisms for granular and centralized error handling.
|
|
|
163
163
|
|
|
164
164
|
### 1. Framework-Level Exception Handler (`src/http/error.ts`)
|
|
165
165
|
|
|
166
|
-
Instead of writing manual `try/catch` blocks in every route or
|
|
166
|
+
Instead of writing manual `try/catch` blocks in every route or wiring `app.onError` inside `server.ts`, ExisJS provides a dedicated file convention: **`src/http/error.ts`** (or `src/http/*.error.ts`).
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
When `src/http/error.ts` is present, ExisJS automatically discovers and mounts it during application boot as the global exception interceptor. Any uncaught exception, thrown `HttpError`, or validation failure anywhere in your application is routed to this file.
|
|
169
|
+
|
|
170
|
+
#### Functional Paradigm
|
|
169
171
|
|
|
170
172
|
```typescript filename="src/http/error.ts"
|
|
171
173
|
import type { Request, Response } from 'exisjs'
|
|
174
|
+
import { isDev } from 'exisjs/config'
|
|
172
175
|
|
|
173
176
|
/**
|
|
174
|
-
* Global Exception Handler
|
|
175
|
-
* Auto-mounted at startup to intercept all unhandled exceptions.
|
|
177
|
+
* Global Framework Exception Handler
|
|
178
|
+
* Auto-mounted at startup to intercept all unhandled exceptions and validation errors.
|
|
176
179
|
*/
|
|
177
180
|
export function onError(err: any, req: Request, res: Response) {
|
|
178
181
|
const status = err.statusCode || err.status || 500
|
|
182
|
+
const isOperational = Boolean(err.isOperational || err.statusCode < 500)
|
|
183
|
+
|
|
184
|
+
// 1. Log unexpected crashes in production to external APM (Sentry, Datadog, Axiom)
|
|
185
|
+
if (!isOperational && !isDev) {
|
|
186
|
+
// e.g. Sentry.captureException(err, { extra: { path: req.path, method: req.method } })
|
|
187
|
+
req.log.error({ err }, '[Fatal] Unhandled Server Exception')
|
|
188
|
+
}
|
|
179
189
|
|
|
180
|
-
//
|
|
190
|
+
// 2. Format a unified, standard error envelope for client applications
|
|
181
191
|
res.status(status).json({
|
|
182
192
|
success: false,
|
|
183
193
|
error: {
|
|
184
|
-
code: err.code || '
|
|
194
|
+
code: err.code || (status === 500 ? 'INTERNAL_SERVER_ERROR' : 'ERROR'),
|
|
185
195
|
message: err.message || 'Internal Server Error',
|
|
196
|
+
...(err.details ? { details: err.details } : {}),
|
|
197
|
+
...(err.errors ? { errors: err.errors } : {}),
|
|
186
198
|
path: req.path,
|
|
199
|
+
timestamp: new Date().toISOString(),
|
|
187
200
|
},
|
|
188
201
|
})
|
|
189
202
|
}
|
|
190
203
|
```
|
|
191
204
|
|
|
205
|
+
#### Class-Based (OOP) Paradigm
|
|
206
|
+
|
|
207
|
+
```typescript filename="src/http/error.ts"
|
|
208
|
+
import type { Request, Response } from 'exisjs'
|
|
209
|
+
import { isDev } from 'exisjs/config'
|
|
210
|
+
|
|
211
|
+
export default class GlobalErrorHandler {
|
|
212
|
+
onError(err: any, req: Request, res: Response) {
|
|
213
|
+
const status = err.statusCode || err.status || 500
|
|
214
|
+
const isOperational = Boolean(err.isOperational || err.statusCode < 500)
|
|
215
|
+
|
|
216
|
+
if (!isOperational && !isDev) {
|
|
217
|
+
req.log.error({ err }, '[Fatal] Unhandled Server Exception')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
res.status(status).json({
|
|
221
|
+
success: false,
|
|
222
|
+
error: {
|
|
223
|
+
code: err.code || (status === 500 ? 'INTERNAL_SERVER_ERROR' : 'ERROR'),
|
|
224
|
+
message: err.message || 'Internal Server Error',
|
|
225
|
+
...(err.details ? { details: err.details } : {}),
|
|
226
|
+
path: req.path,
|
|
227
|
+
timestamp: new Date().toISOString(),
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
192
234
|
> [!TIP]
|
|
193
|
-
> You can
|
|
235
|
+
> **Scaffold with CLI**: You can generate this file instantly using the ExisJS CLI:
|
|
194
236
|
> ```bash
|
|
195
|
-
> exis generate error
|
|
237
|
+
> npx exis generate error
|
|
196
238
|
> # or shorthand:
|
|
197
|
-
> exis g error
|
|
239
|
+
> npx exis g error
|
|
198
240
|
> ```
|
|
199
241
|
|
|
242
|
+
#### What `src/http/error.ts` Intercepts:
|
|
243
|
+
- **`HttpError` instances**: (e.g. `throw HttpError.notFound()`, `throw HttpError.unauthorized()`).
|
|
244
|
+
- **Validation failures**: Zod, Tex, or Yup validation rejections automatically pass structured `err.errors` and `err.details`.
|
|
245
|
+
- **Database / Network exceptions**: Postgres connection failures, timeout rejections, and third-party API errors.
|
|
246
|
+
- **Syntax errors**: Malformed JSON bodies or bad request payloads.
|
|
247
|
+
|
|
248
|
+
|
|
200
249
|
### 2. Exception Filters (`catchError`)
|
|
201
250
|
|
|
202
251
|
ExisJS allows you to create **Exception Filters** to globally intercept specific types of errors.
|
|
@@ -20,7 +20,7 @@ Exis aims to provide that out-of-the-box application architecture for backends:
|
|
|
20
20
|
The fastest way to start is the Exis CLI, which scaffolds a new project directory with the core files and a conventional base structure already in place. This is the recommended path for first-time users.
|
|
21
21
|
|
|
22
22
|
<div className="my-6 max-w-lg">
|
|
23
|
-
<CopyCommand command="
|
|
23
|
+
<CopyCommand command="npm create exisjs@latest my-backend" />
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
<Hint>
|
|
@@ -43,12 +43,12 @@ export default controller({
|
|
|
43
43
|
getUsers: route.get('/', {
|
|
44
44
|
handle(req) {
|
|
45
45
|
// Return a realistic response structure
|
|
46
|
-
return {
|
|
46
|
+
return {
|
|
47
47
|
data: [
|
|
48
48
|
{ id: 1, name: 'Alice' },
|
|
49
|
-
{ id: 2, name: 'Bob' }
|
|
49
|
+
{ id: 2, name: 'Bob' },
|
|
50
50
|
],
|
|
51
|
-
total: 2
|
|
51
|
+
total: 2,
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
}),
|
|
@@ -66,12 +66,12 @@ export default class UsersController {
|
|
|
66
66
|
@Get('/')
|
|
67
67
|
getUsers(@Req() req: Request) {
|
|
68
68
|
// Return a realistic response structure
|
|
69
|
-
return {
|
|
69
|
+
return {
|
|
70
70
|
data: [
|
|
71
71
|
{ id: 1, name: 'Alice' },
|
|
72
|
-
{ id: 2, name: 'Bob' }
|
|
72
|
+
{ id: 2, name: 'Bob' },
|
|
73
73
|
],
|
|
74
|
-
total: 2
|
|
74
|
+
total: 2,
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|