@navios/schedule 0.3.0
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 +370 -0
- package/dist/src/__tests__/schedule.spec.d.mts +2 -0
- package/dist/src/__tests__/schedule.spec.d.mts.map +1 -0
- package/dist/src/cron.constants.d.mts +17 -0
- package/dist/src/cron.constants.d.mts.map +1 -0
- package/dist/src/decorators/cron.decorator.d.mts +6 -0
- package/dist/src/decorators/cron.decorator.d.mts.map +1 -0
- package/dist/src/decorators/index.d.mts +3 -0
- package/dist/src/decorators/index.d.mts.map +1 -0
- package/dist/src/decorators/schedulable.decorator.d.mts +3 -0
- package/dist/src/decorators/schedulable.decorator.d.mts.map +1 -0
- package/dist/src/index.d.mts +5 -0
- package/dist/src/index.d.mts.map +1 -0
- package/dist/src/metadata/cron.metadata.d.mts +10 -0
- package/dist/src/metadata/cron.metadata.d.mts.map +1 -0
- package/dist/src/metadata/index.d.mts +3 -0
- package/dist/src/metadata/index.d.mts.map +1 -0
- package/dist/src/metadata/schedule.metadata.d.mts +11 -0
- package/dist/src/metadata/schedule.metadata.d.mts.map +1 -0
- package/dist/src/scheduler.service.d.mts +12 -0
- package/dist/src/scheduler.service.d.mts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsdown.config.d.mts +3 -0
- package/dist/tsdown.config.d.mts.map +1 -0
- package/dist/tsup.config.d.mts +3 -0
- package/dist/tsup.config.d.mts.map +1 -0
- package/dist/vitest.config.d.mts +3 -0
- package/dist/vitest.config.d.mts.map +1 -0
- package/lib/_tsup-dts-rollup.d.mts +105 -0
- package/lib/_tsup-dts-rollup.d.ts +105 -0
- package/lib/index.d.mts +14 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +240 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +228 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +41 -0
- package/project.json +53 -0
- package/src/__tests__/schedule.spec.mts +167 -0
- package/src/cron.constants.mts +16 -0
- package/src/decorators/cron.decorator.mts +30 -0
- package/src/decorators/index.mts +2 -0
- package/src/decorators/schedulable.decorator.mts +19 -0
- package/src/index.mts +4 -0
- package/src/metadata/cron.metadata.mts +52 -0
- package/src/metadata/index.mts +2 -0
- package/src/metadata/schedule.metadata.mts +55 -0
- package/src/scheduler.service.mts +93 -0
- package/tsconfig.json +13 -0
- package/tsup.config.mts +12 -0
- package/vitest.config.mts +12 -0
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@navios/schedule",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Oleksandr Hanzha",
|
|
6
|
+
"email": "alex@granted.name"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"directory": "packages/schedule",
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/Arilas/navios.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@navios/core": "^0.3.0",
|
|
16
|
+
"zod": "^3.23.8"
|
|
17
|
+
},
|
|
18
|
+
"typings": "./lib/index.d.mts",
|
|
19
|
+
"main": "./lib/index.js",
|
|
20
|
+
"module": "./lib/index.mjs",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./lib/index.d.mts",
|
|
25
|
+
"default": "./lib/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./lib/index.d.ts",
|
|
29
|
+
"default": "./lib/index.js"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@navios/core": "^0.3.0",
|
|
35
|
+
"typescript": "^5.8.3",
|
|
36
|
+
"zod": "^3.24.4"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"cron": "^4.3.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@navios/schedule",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/schedule/src",
|
|
5
|
+
"prefix": "schedule",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"projectType": "library",
|
|
8
|
+
"targets": {
|
|
9
|
+
"check": {
|
|
10
|
+
"executor": "nx:run-commands",
|
|
11
|
+
"outputs": ["{projectRoot}/dist"],
|
|
12
|
+
"inputs": ["^projectSources", "projectSources"],
|
|
13
|
+
"options": {
|
|
14
|
+
"command": ["tsc -b"],
|
|
15
|
+
"cwd": "packages/schedule"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"test:ci": {
|
|
19
|
+
"executor": "nx:run-commands",
|
|
20
|
+
"inputs": ["^projectSources", "project"],
|
|
21
|
+
"options": {
|
|
22
|
+
"command": "vitest run",
|
|
23
|
+
"cwd": "packages/schedule"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"build": {
|
|
27
|
+
"executor": "nx:run-commands",
|
|
28
|
+
"inputs": ["projectSources"],
|
|
29
|
+
"outputs": ["{projectRoot}/lib"],
|
|
30
|
+
"dependsOn": ["check", "test:ci"],
|
|
31
|
+
"options": {
|
|
32
|
+
"command": "tsup",
|
|
33
|
+
"cwd": "packages/schedule"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"publish": {
|
|
37
|
+
"executor": "nx:run-commands",
|
|
38
|
+
"dependsOn": ["build"],
|
|
39
|
+
"options": {
|
|
40
|
+
"command": "yarn npm publish --access public",
|
|
41
|
+
"cwd": "packages/schedule"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"publish:next": {
|
|
45
|
+
"executor": "nx:run-commands",
|
|
46
|
+
"dependsOn": ["build"],
|
|
47
|
+
"options": {
|
|
48
|
+
"command": "yarn npm publish --access public --tag next",
|
|
49
|
+
"cwd": "packages/schedule"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { inject, Injectable } from '@navios/core'
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { Cron, Schedulable } from '../decorators/index.mjs'
|
|
6
|
+
import { SchedulerService } from '../scheduler.service.mjs'
|
|
7
|
+
|
|
8
|
+
describe('Schedule Module', () => {
|
|
9
|
+
let schedulerService: SchedulerService
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
vi.useFakeTimers()
|
|
13
|
+
schedulerService = await inject(SchedulerService)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
schedulerService.stopAll()
|
|
18
|
+
vi.useRealTimers()
|
|
19
|
+
vi.clearAllMocks()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should register a schedulable service', () => {
|
|
23
|
+
@Schedulable()
|
|
24
|
+
class TestService {
|
|
25
|
+
@Cron('*/1 * * * * *')
|
|
26
|
+
async testJob() {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
expect(() => schedulerService.register(TestService)).not.toThrow()
|
|
30
|
+
const job = schedulerService.getJob(TestService, 'testJob')
|
|
31
|
+
expect(job).toBeDefined()
|
|
32
|
+
expect(job?.isActive).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should throw an error when registering a non-schedulable service', () => {
|
|
36
|
+
@Injectable()
|
|
37
|
+
class NonSchedulableService {
|
|
38
|
+
async testJob() {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
expect(() => schedulerService.register(NonSchedulableService)).toThrow()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should execute a job at the scheduled time', async () => {
|
|
45
|
+
const mockJob = vi.fn()
|
|
46
|
+
|
|
47
|
+
@Schedulable()
|
|
48
|
+
class TestService {
|
|
49
|
+
@Cron('*/1 * * * * *')
|
|
50
|
+
async testJob() {
|
|
51
|
+
mockJob()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
schedulerService.register(TestService)
|
|
56
|
+
expect(mockJob).not.toHaveBeenCalled()
|
|
57
|
+
|
|
58
|
+
// Advance time by 1 second to trigger the job
|
|
59
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
60
|
+
expect(mockJob).toHaveBeenCalledTimes(1)
|
|
61
|
+
|
|
62
|
+
// Advance time by another second to trigger again
|
|
63
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
64
|
+
expect(mockJob).toHaveBeenCalledTimes(2)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should not execute jobs when they are stopped', async () => {
|
|
68
|
+
const mockJob = vi.fn()
|
|
69
|
+
|
|
70
|
+
@Schedulable()
|
|
71
|
+
class TestService {
|
|
72
|
+
@Cron('*/1 * * * * *')
|
|
73
|
+
async testJob() {
|
|
74
|
+
mockJob()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
schedulerService.register(TestService)
|
|
79
|
+
schedulerService.stopAll()
|
|
80
|
+
|
|
81
|
+
// Advance time but expect no execution
|
|
82
|
+
await vi.advanceTimersByTimeAsync(3000)
|
|
83
|
+
expect(mockJob).not.toHaveBeenCalled()
|
|
84
|
+
|
|
85
|
+
// Start jobs and verify they execute
|
|
86
|
+
schedulerService.startAll()
|
|
87
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
88
|
+
expect(mockJob).toHaveBeenCalledTimes(1)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should handle multiple jobs in a service', async () => {
|
|
92
|
+
const mockJob1 = vi.fn()
|
|
93
|
+
const mockJob2 = vi.fn()
|
|
94
|
+
vi.setSystemTime('2021-01-01T00:00:00.000Z')
|
|
95
|
+
|
|
96
|
+
@Schedulable()
|
|
97
|
+
class TestService {
|
|
98
|
+
@Cron('*/1 * * * * *')
|
|
99
|
+
async job1() {
|
|
100
|
+
mockJob1()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Cron('*/2 * * * * *')
|
|
104
|
+
async job2() {
|
|
105
|
+
mockJob2()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
schedulerService.register(TestService)
|
|
110
|
+
|
|
111
|
+
// After 1 second, only job1 should execute
|
|
112
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
113
|
+
expect(mockJob1).toHaveBeenCalledTimes(1)
|
|
114
|
+
expect(mockJob2).not.toHaveBeenCalled()
|
|
115
|
+
|
|
116
|
+
// After 2 seconds, both jobs should have executed
|
|
117
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
118
|
+
expect(mockJob1).toHaveBeenCalledTimes(2)
|
|
119
|
+
expect(mockJob2).toHaveBeenCalledTimes(1)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should handle disabled jobs', async () => {
|
|
123
|
+
const mockJob = vi.fn()
|
|
124
|
+
|
|
125
|
+
@Schedulable()
|
|
126
|
+
class TestService {
|
|
127
|
+
@Cron('*/1 * * * * *', { disabled: true })
|
|
128
|
+
async testJob() {
|
|
129
|
+
mockJob()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
schedulerService.register(TestService)
|
|
134
|
+
const job = schedulerService.getJob(TestService, 'testJob')
|
|
135
|
+
expect(job?.isActive).toBe(false)
|
|
136
|
+
|
|
137
|
+
// Advance time but expect no execution
|
|
138
|
+
await vi.advanceTimersByTimeAsync(3000)
|
|
139
|
+
expect(mockJob).not.toHaveBeenCalled()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should handle errors in job execution without crashing', async () => {
|
|
143
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
144
|
+
|
|
145
|
+
@Schedulable()
|
|
146
|
+
class TestService {
|
|
147
|
+
@Cron('*/1 * * * * *')
|
|
148
|
+
async testJob() {
|
|
149
|
+
throw new Error('Test error')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
schedulerService.register(TestService)
|
|
154
|
+
|
|
155
|
+
// Job should not throw outside, but log the error
|
|
156
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
157
|
+
|
|
158
|
+
// Should continue executing despite previous error
|
|
159
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
160
|
+
|
|
161
|
+
// Job should still be active
|
|
162
|
+
const job = schedulerService.getJob(TestService, 'testJob')
|
|
163
|
+
expect(job?.isActive).toBe(true)
|
|
164
|
+
|
|
165
|
+
errorSpy.mockRestore()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export enum Schedule {
|
|
2
|
+
EveryMinute = '*/1 * * * *',
|
|
3
|
+
EveryFiveMinutes = '*/5 * * * *',
|
|
4
|
+
EveryTenMinutes = '*/10 * * * *',
|
|
5
|
+
EveryFifteenMinutes = '*/15 * * * *',
|
|
6
|
+
EveryThirtyMinutes = '*/30 * * * *',
|
|
7
|
+
EveryHour = '0 * * * *',
|
|
8
|
+
EveryTwoHours = '0 */2 * * *',
|
|
9
|
+
EveryThreeHours = '0 */3 * * *',
|
|
10
|
+
EveryFourHours = '0 */4 * * *',
|
|
11
|
+
EverySixHours = '0 */6 * * *',
|
|
12
|
+
EveryTwelveHours = '0 */12 * * *',
|
|
13
|
+
EveryDay = '0 0 * * *',
|
|
14
|
+
EveryWeek = '0 0 * * 0',
|
|
15
|
+
EveryMonth = '0 0 1 * *',
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/core'
|
|
2
|
+
import type { CronJobParams } from 'cron'
|
|
3
|
+
|
|
4
|
+
import { getCronMetadata } from '../metadata/index.mjs'
|
|
5
|
+
|
|
6
|
+
export interface CronOptions {
|
|
7
|
+
disabled?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Cron(
|
|
11
|
+
cronTime: CronJobParams['cronTime'],
|
|
12
|
+
options?: CronOptions,
|
|
13
|
+
) {
|
|
14
|
+
return (
|
|
15
|
+
target: () => Promise<void>,
|
|
16
|
+
context: ClassMethodDecoratorContext,
|
|
17
|
+
) => {
|
|
18
|
+
if (context.kind !== 'method') {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Cron can only be applied to methods, not ${context.kind}`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
if (context.metadata) {
|
|
24
|
+
const metadata = getCronMetadata(target, context)
|
|
25
|
+
metadata.cronTime = cronTime
|
|
26
|
+
metadata.disabled = options?.disabled ?? false
|
|
27
|
+
}
|
|
28
|
+
return target
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/core'
|
|
2
|
+
|
|
3
|
+
import { Injectable } from '@navios/core'
|
|
4
|
+
|
|
5
|
+
import { getScheduleMetadata } from '../metadata/index.mjs'
|
|
6
|
+
|
|
7
|
+
export function Schedulable() {
|
|
8
|
+
return (target: ClassType, context: ClassDecoratorContext) => {
|
|
9
|
+
if (context.kind !== 'class') {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`SchedulableDecorator can only be applied to classes, not ${context.kind}`,
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
if (context.metadata) {
|
|
15
|
+
getScheduleMetadata(target, context)
|
|
16
|
+
}
|
|
17
|
+
return Injectable()(target, context)
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.mts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CronJobParams } from 'cron'
|
|
2
|
+
|
|
3
|
+
export const CronMetadataKey = Symbol('CronMetadataKey')
|
|
4
|
+
|
|
5
|
+
export interface CronMetadata {
|
|
6
|
+
classMethod: string
|
|
7
|
+
cronTime: CronJobParams['cronTime'] | null
|
|
8
|
+
disabled: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getAllCronMetadata(
|
|
12
|
+
context: ClassMethodDecoratorContext | ClassDecoratorContext,
|
|
13
|
+
): Set<CronMetadata> {
|
|
14
|
+
if (context.metadata) {
|
|
15
|
+
const metadata = context.metadata[CronMetadataKey] as
|
|
16
|
+
| Set<CronMetadata>
|
|
17
|
+
| undefined
|
|
18
|
+
if (metadata) {
|
|
19
|
+
return metadata
|
|
20
|
+
} else {
|
|
21
|
+
context.metadata[CronMetadataKey] = new Set<CronMetadata>()
|
|
22
|
+
return context.metadata[CronMetadataKey] as Set<CronMetadata>
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
throw new Error('[Navios-Schedule] Wrong environment.')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getCronMetadata(
|
|
29
|
+
target: Function,
|
|
30
|
+
context: ClassMethodDecoratorContext,
|
|
31
|
+
): CronMetadata {
|
|
32
|
+
if (context.metadata) {
|
|
33
|
+
const metadata = getAllCronMetadata(context)
|
|
34
|
+
if (metadata) {
|
|
35
|
+
const endpointMetadata = Array.from(metadata).find(
|
|
36
|
+
(item) => item.classMethod === target.name,
|
|
37
|
+
)
|
|
38
|
+
if (endpointMetadata) {
|
|
39
|
+
return endpointMetadata
|
|
40
|
+
} else {
|
|
41
|
+
const newMetadata: CronMetadata = {
|
|
42
|
+
classMethod: target.name,
|
|
43
|
+
cronTime: null,
|
|
44
|
+
disabled: false,
|
|
45
|
+
}
|
|
46
|
+
metadata.add(newMetadata)
|
|
47
|
+
return newMetadata
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw new Error('[Navios-Schedule] Wrong environment.')
|
|
52
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/core'
|
|
2
|
+
|
|
3
|
+
import type { CronMetadata } from './cron.metadata.mjs'
|
|
4
|
+
|
|
5
|
+
import { getAllCronMetadata } from './cron.metadata.mjs'
|
|
6
|
+
|
|
7
|
+
export const ScheduleMetadataKey = Symbol('ControllerMetadataKey')
|
|
8
|
+
|
|
9
|
+
export interface ScheduleMetadata {
|
|
10
|
+
name: string
|
|
11
|
+
jobs: Set<CronMetadata>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getScheduleMetadata(
|
|
15
|
+
target: ClassType,
|
|
16
|
+
context: ClassDecoratorContext,
|
|
17
|
+
): ScheduleMetadata {
|
|
18
|
+
if (context.metadata) {
|
|
19
|
+
const metadata = context.metadata[ScheduleMetadataKey] as
|
|
20
|
+
| ScheduleMetadata
|
|
21
|
+
| undefined
|
|
22
|
+
if (metadata) {
|
|
23
|
+
return metadata
|
|
24
|
+
} else {
|
|
25
|
+
const jobsMetadata = getAllCronMetadata(context)
|
|
26
|
+
const newMetadata: ScheduleMetadata = {
|
|
27
|
+
name: target.name,
|
|
28
|
+
jobs: jobsMetadata,
|
|
29
|
+
}
|
|
30
|
+
context.metadata[ScheduleMetadataKey] = newMetadata
|
|
31
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
32
|
+
target[ScheduleMetadataKey] = newMetadata
|
|
33
|
+
return newMetadata
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw new Error('[Navios-Schedule] Wrong environment.')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function extractScheduleMetadata(target: ClassType): ScheduleMetadata {
|
|
40
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
41
|
+
const metadata = target[ScheduleMetadataKey] as ScheduleMetadata | undefined
|
|
42
|
+
if (!metadata) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
'[Navios-Schedule] Controller metadata not found. Make sure to use @Controller decorator.',
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
return metadata
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function hasScheduleMetadata(target: ClassType): boolean {
|
|
51
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
52
|
+
const metadata = target[ScheduleMetadataKey] as ScheduleMetadata | undefined
|
|
53
|
+
|
|
54
|
+
return !!metadata
|
|
55
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/core'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
EnvConfigProvider,
|
|
5
|
+
inject,
|
|
6
|
+
Injectable,
|
|
7
|
+
Logger,
|
|
8
|
+
syncInject,
|
|
9
|
+
} from '@navios/core'
|
|
10
|
+
|
|
11
|
+
import { CronJob } from 'cron'
|
|
12
|
+
|
|
13
|
+
import type { ScheduleMetadata } from './metadata/index.mjs'
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
extractScheduleMetadata,
|
|
17
|
+
hasScheduleMetadata,
|
|
18
|
+
} from './metadata/index.mjs'
|
|
19
|
+
|
|
20
|
+
@Injectable()
|
|
21
|
+
export class SchedulerService {
|
|
22
|
+
// private readonly configService = syncInject(EnvConfigProvider)
|
|
23
|
+
private readonly logger = syncInject(Logger, {
|
|
24
|
+
context: SchedulerService.name,
|
|
25
|
+
})
|
|
26
|
+
private readonly jobs: Map<string, CronJob> = new Map()
|
|
27
|
+
|
|
28
|
+
register(service: ClassType) {
|
|
29
|
+
if (!hasScheduleMetadata(service)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`[Navios-Schedule] Service ${service.name} is not schedulable. Make sure to use @Schedulable decorator.`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
const metadata = extractScheduleMetadata(service)
|
|
35
|
+
this.logger.debug('Scheduling service', metadata.name)
|
|
36
|
+
this.registerJobs(service, metadata)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getJob<T extends ClassType>(
|
|
40
|
+
service: T,
|
|
41
|
+
method: keyof InstanceType<T>,
|
|
42
|
+
): CronJob | undefined {
|
|
43
|
+
const metadata = extractScheduleMetadata(service)
|
|
44
|
+
const jobName = `${metadata.name}.${method as string}()`
|
|
45
|
+
return this.jobs.get(jobName)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private registerJobs(service: ClassType, metadata: ScheduleMetadata) {
|
|
49
|
+
const jobs = metadata.jobs
|
|
50
|
+
for (const job of jobs) {
|
|
51
|
+
if (!job.cronTime) {
|
|
52
|
+
this.logger.debug('Skipping job', job.classMethod)
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
const name = `${metadata.name}.${job.classMethod}()`
|
|
56
|
+
const self = this
|
|
57
|
+
const defaultDisabled = false
|
|
58
|
+
const cronJob = CronJob.from({
|
|
59
|
+
cronTime: job.cronTime,
|
|
60
|
+
name,
|
|
61
|
+
async onTick() {
|
|
62
|
+
try {
|
|
63
|
+
self.logger.debug('Executing job', name)
|
|
64
|
+
const instance = await inject(service)
|
|
65
|
+
await instance[job.classMethod]()
|
|
66
|
+
} catch (error) {
|
|
67
|
+
self.logger.error('Error executing job', name, error)
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
start: !(defaultDisabled || job.disabled),
|
|
71
|
+
})
|
|
72
|
+
this.jobs.set(name, cronJob)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
startAll() {
|
|
77
|
+
for (const job of this.jobs.values()) {
|
|
78
|
+
if (job.isActive) {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
job.start()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
stopAll() {
|
|
86
|
+
for (const job of this.jobs.values()) {
|
|
87
|
+
if (!job.isActive) {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
job.stop()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.mts
ADDED