@hazeljs/cron 0.2.0-alpha.1
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/LICENSE +192 -0
- package/README.md +442 -0
- package/dist/cron.decorator.d.ts +20 -0
- package/dist/cron.decorator.d.ts.map +1 -0
- package/dist/cron.decorator.js +38 -0
- package/dist/cron.module.d.ts +38 -0
- package/dist/cron.module.d.ts.map +1 -0
- package/dist/cron.module.js +78 -0
- package/dist/cron.service.d.ts +56 -0
- package/dist/cron.service.d.ts.map +1 -0
- package/dist/cron.service.js +273 -0
- package/dist/cron.test.d.ts +2 -0
- package/dist/cron.test.d.ts.map +1 -0
- package/dist/cron.test.js +401 -0
- package/dist/cron.types.d.ts +174 -0
- package/dist/cron.types.d.ts.map +1 -0
- package/dist/cron.types.js +79 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/package.json +54 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/// <reference types="jest" />
|
|
3
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
4
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
6
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
7
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
8
|
+
};
|
|
9
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
10
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
jest.mock('@hazeljs/core', () => ({
|
|
14
|
+
__esModule: true,
|
|
15
|
+
Service: () => () => undefined,
|
|
16
|
+
HazelModule: () => () => undefined,
|
|
17
|
+
Container: { getInstance: jest.fn() },
|
|
18
|
+
logger: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
19
|
+
default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
20
|
+
}));
|
|
21
|
+
const core_1 = require("@hazeljs/core");
|
|
22
|
+
const cron_service_1 = require("./cron.service");
|
|
23
|
+
const cron_module_1 = require("./cron.module");
|
|
24
|
+
const cron_decorator_1 = require("./cron.decorator");
|
|
25
|
+
const cron_types_1 = require("./cron.types");
|
|
26
|
+
// Use a minute-level cron expression that won't fire during tests
|
|
27
|
+
const EVERY_MINUTE = '* * * * *';
|
|
28
|
+
describe('CronService', () => {
|
|
29
|
+
let service;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
service = new cron_service_1.CronService();
|
|
32
|
+
});
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
service.clearAll();
|
|
35
|
+
});
|
|
36
|
+
describe('registerJob()', () => {
|
|
37
|
+
it('registers a job and increments count', () => {
|
|
38
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
39
|
+
expect(service.getJobCount()).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
it('replaces an existing job with the same name', () => {
|
|
42
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
43
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
44
|
+
expect(service.getJobCount()).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
it('does not auto-start when enabled is false', () => {
|
|
47
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn(), {
|
|
48
|
+
cronTime: EVERY_MINUTE,
|
|
49
|
+
enabled: false,
|
|
50
|
+
});
|
|
51
|
+
const status = service.getJobStatus('job1');
|
|
52
|
+
expect(status).toBeDefined();
|
|
53
|
+
expect(status?.enabled).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
it('throws for invalid cron expression', () => {
|
|
56
|
+
expect(() => service.registerJob('bad', 'not-a-cron', jest.fn())).toThrow('Invalid cron expression');
|
|
57
|
+
});
|
|
58
|
+
it('registers multiple independent jobs', () => {
|
|
59
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
60
|
+
service.registerJob('job2', EVERY_MINUTE, jest.fn());
|
|
61
|
+
service.registerJob('job3', EVERY_MINUTE, jest.fn());
|
|
62
|
+
expect(service.getJobCount()).toBe(3);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('deleteJob()', () => {
|
|
66
|
+
it('returns true and removes the job', () => {
|
|
67
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
68
|
+
expect(service.deleteJob('job1')).toBe(true);
|
|
69
|
+
expect(service.getJobCount()).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
it('returns false for unknown job', () => {
|
|
72
|
+
expect(service.deleteJob('unknown')).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('startJob() / stopJob()', () => {
|
|
76
|
+
it('startJob returns true for existing job', () => {
|
|
77
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn(), {
|
|
78
|
+
cronTime: EVERY_MINUTE,
|
|
79
|
+
enabled: false,
|
|
80
|
+
});
|
|
81
|
+
expect(service.startJob('job1')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('startJob returns false for unknown job', () => {
|
|
84
|
+
expect(service.startJob('unknown')).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
it('stopJob returns true for existing job', () => {
|
|
87
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
88
|
+
expect(service.stopJob('job1')).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
it('stopJob returns false for unknown job', () => {
|
|
91
|
+
expect(service.stopJob('unknown')).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('enableJob() / disableJob()', () => {
|
|
95
|
+
it('enableJob returns true for existing job', () => {
|
|
96
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn(), {
|
|
97
|
+
cronTime: EVERY_MINUTE,
|
|
98
|
+
enabled: false,
|
|
99
|
+
});
|
|
100
|
+
expect(service.enableJob('job1')).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
it('enableJob returns false for unknown job', () => {
|
|
103
|
+
expect(service.enableJob('unknown')).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
it('disableJob returns true for existing job', () => {
|
|
106
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
107
|
+
expect(service.disableJob('job1')).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
it('disableJob returns false for unknown job', () => {
|
|
110
|
+
expect(service.disableJob('unknown')).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('getJobStatus()', () => {
|
|
114
|
+
it('returns status for registered job', () => {
|
|
115
|
+
service.registerJob('job1', EVERY_MINUTE, jest.fn());
|
|
116
|
+
const status = service.getJobStatus('job1');
|
|
117
|
+
expect(status).toBeDefined();
|
|
118
|
+
expect(status?.name).toBe('job1');
|
|
119
|
+
expect(status?.runCount).toBe(0);
|
|
120
|
+
expect(status?.enabled).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
it('returns undefined for unknown job', () => {
|
|
123
|
+
expect(service.getJobStatus('unknown')).toBeUndefined();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('getAllJobStatuses()', () => {
|
|
127
|
+
it('returns empty array when no jobs', () => {
|
|
128
|
+
expect(service.getAllJobStatuses()).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
it('returns all job statuses', () => {
|
|
131
|
+
service.registerJob('j1', EVERY_MINUTE, jest.fn());
|
|
132
|
+
service.registerJob('j2', EVERY_MINUTE, jest.fn());
|
|
133
|
+
const statuses = service.getAllJobStatuses();
|
|
134
|
+
expect(statuses).toHaveLength(2);
|
|
135
|
+
expect(statuses.map((s) => s.name)).toContain('j1');
|
|
136
|
+
expect(statuses.map((s) => s.name)).toContain('j2');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('stopAll() / startAll() / clearAll()', () => {
|
|
140
|
+
it('stopAll stops all registered jobs', () => {
|
|
141
|
+
service.registerJob('j1', EVERY_MINUTE, jest.fn());
|
|
142
|
+
service.registerJob('j2', EVERY_MINUTE, jest.fn());
|
|
143
|
+
expect(() => service.stopAll()).not.toThrow();
|
|
144
|
+
});
|
|
145
|
+
it('startAll starts all registered jobs', () => {
|
|
146
|
+
service.registerJob('j1', EVERY_MINUTE, jest.fn(), {
|
|
147
|
+
cronTime: EVERY_MINUTE,
|
|
148
|
+
enabled: false,
|
|
149
|
+
});
|
|
150
|
+
expect(() => service.startAll()).not.toThrow();
|
|
151
|
+
});
|
|
152
|
+
it('clearAll removes all jobs', () => {
|
|
153
|
+
service.registerJob('j1', EVERY_MINUTE, jest.fn());
|
|
154
|
+
service.registerJob('j2', EVERY_MINUTE, jest.fn());
|
|
155
|
+
service.clearAll();
|
|
156
|
+
expect(service.getJobCount()).toBe(0);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('getJobCount()', () => {
|
|
160
|
+
it('returns 0 when no jobs registered', () => {
|
|
161
|
+
expect(service.getJobCount()).toBe(0);
|
|
162
|
+
});
|
|
163
|
+
it('returns correct count after adding/removing jobs', () => {
|
|
164
|
+
service.registerJob('j1', EVERY_MINUTE, jest.fn());
|
|
165
|
+
service.registerJob('j2', EVERY_MINUTE, jest.fn());
|
|
166
|
+
expect(service.getJobCount()).toBe(2);
|
|
167
|
+
service.deleteJob('j1');
|
|
168
|
+
expect(service.getJobCount()).toBe(1);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('job options: runOnInit', () => {
|
|
172
|
+
it('executes callback immediately when runOnInit is true', async () => {
|
|
173
|
+
const callback = jest.fn().mockResolvedValue(undefined);
|
|
174
|
+
service.registerJob('runNow', EVERY_MINUTE, callback, {
|
|
175
|
+
cronTime: EVERY_MINUTE,
|
|
176
|
+
runOnInit: true,
|
|
177
|
+
});
|
|
178
|
+
// Allow the async execute() to run
|
|
179
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
180
|
+
expect(callback).toHaveBeenCalled();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe('job options: onComplete / onError', () => {
|
|
184
|
+
it('calls onComplete after successful execution (via runOnInit)', async () => {
|
|
185
|
+
const onComplete = jest.fn();
|
|
186
|
+
service.registerJob('complete-job', EVERY_MINUTE, jest.fn().mockResolvedValue(undefined), {
|
|
187
|
+
cronTime: EVERY_MINUTE,
|
|
188
|
+
runOnInit: true,
|
|
189
|
+
onComplete,
|
|
190
|
+
});
|
|
191
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
192
|
+
expect(onComplete).toHaveBeenCalled();
|
|
193
|
+
});
|
|
194
|
+
it('calls onError when callback throws (via runOnInit)', async () => {
|
|
195
|
+
const onError = jest.fn();
|
|
196
|
+
service.registerJob('error-job', EVERY_MINUTE, jest.fn().mockRejectedValue(new Error('fail')), {
|
|
197
|
+
cronTime: EVERY_MINUTE,
|
|
198
|
+
runOnInit: true,
|
|
199
|
+
onError,
|
|
200
|
+
});
|
|
201
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
202
|
+
expect(onError).toHaveBeenCalledWith(expect.any(Error));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
describe('startJob on disabled job', () => {
|
|
206
|
+
it('warns but does not crash when starting a disabled job', () => {
|
|
207
|
+
service.registerJob('disabled-job', EVERY_MINUTE, jest.fn(), {
|
|
208
|
+
cronTime: EVERY_MINUTE,
|
|
209
|
+
enabled: false,
|
|
210
|
+
});
|
|
211
|
+
// startJob calls job.start() which logs a warn since job is disabled
|
|
212
|
+
expect(() => service.startJob('disabled-job')).not.toThrow();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('starting an already-started job', () => {
|
|
216
|
+
it('does not throw when starting a job that is already running', () => {
|
|
217
|
+
service.registerJob('already-running', EVERY_MINUTE, jest.fn());
|
|
218
|
+
// job is already started by registerJob
|
|
219
|
+
expect(() => service.startJob('already-running')).not.toThrow();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('Cron decorator', () => {
|
|
224
|
+
it('attaches metadata to the class', () => {
|
|
225
|
+
class MyService {
|
|
226
|
+
doWork() {
|
|
227
|
+
// noop
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
__decorate([
|
|
231
|
+
(0, cron_decorator_1.Cron)({ cronTime: EVERY_MINUTE, name: 'myJob' }),
|
|
232
|
+
__metadata("design:type", Function),
|
|
233
|
+
__metadata("design:paramtypes", []),
|
|
234
|
+
__metadata("design:returntype", void 0)
|
|
235
|
+
], MyService.prototype, "doWork", null);
|
|
236
|
+
const metadata = Reflect.getMetadata(cron_decorator_1.CRON_METADATA_KEY, MyService);
|
|
237
|
+
expect(metadata).toHaveLength(1);
|
|
238
|
+
expect(metadata[0].methodName).toBe('doWork');
|
|
239
|
+
expect(metadata[0].options.name).toBe('myJob');
|
|
240
|
+
expect(metadata[0].options.cronTime).toBe(EVERY_MINUTE);
|
|
241
|
+
});
|
|
242
|
+
it('uses default name based on class and method when name is not provided', () => {
|
|
243
|
+
class AnotherService {
|
|
244
|
+
handleTask() {
|
|
245
|
+
// noop
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
__decorate([
|
|
249
|
+
(0, cron_decorator_1.Cron)({ cronTime: EVERY_MINUTE }),
|
|
250
|
+
__metadata("design:type", Function),
|
|
251
|
+
__metadata("design:paramtypes", []),
|
|
252
|
+
__metadata("design:returntype", void 0)
|
|
253
|
+
], AnotherService.prototype, "handleTask", null);
|
|
254
|
+
const metadata = Reflect.getMetadata(cron_decorator_1.CRON_METADATA_KEY, AnotherService);
|
|
255
|
+
expect(metadata[0].options.name).toBe('AnotherService.handleTask');
|
|
256
|
+
});
|
|
257
|
+
it('accumulates metadata for multiple methods', () => {
|
|
258
|
+
class MultiService {
|
|
259
|
+
firstJob() {
|
|
260
|
+
// noop
|
|
261
|
+
}
|
|
262
|
+
secondJob() {
|
|
263
|
+
// noop
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
__decorate([
|
|
267
|
+
(0, cron_decorator_1.Cron)({ cronTime: EVERY_MINUTE, name: 'first' }),
|
|
268
|
+
__metadata("design:type", Function),
|
|
269
|
+
__metadata("design:paramtypes", []),
|
|
270
|
+
__metadata("design:returntype", void 0)
|
|
271
|
+
], MultiService.prototype, "firstJob", null);
|
|
272
|
+
__decorate([
|
|
273
|
+
(0, cron_decorator_1.Cron)({ cronTime: EVERY_MINUTE, name: 'second' }),
|
|
274
|
+
__metadata("design:type", Function),
|
|
275
|
+
__metadata("design:paramtypes", []),
|
|
276
|
+
__metadata("design:returntype", void 0)
|
|
277
|
+
], MultiService.prototype, "secondJob", null);
|
|
278
|
+
const metadata = Reflect.getMetadata(cron_decorator_1.CRON_METADATA_KEY, MultiService);
|
|
279
|
+
expect(metadata).toHaveLength(2);
|
|
280
|
+
const names = metadata.map((m) => m.options.name);
|
|
281
|
+
expect(names).toContain('first');
|
|
282
|
+
expect(names).toContain('second');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
describe('getCronMetadata()', () => {
|
|
286
|
+
it('returns empty array for class with no @Cron decorators', () => {
|
|
287
|
+
class Plain {
|
|
288
|
+
doThing() {
|
|
289
|
+
// noop
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const instance = new Plain();
|
|
293
|
+
expect((0, cron_decorator_1.getCronMetadata)(instance)).toEqual([]);
|
|
294
|
+
});
|
|
295
|
+
it('returns metadata for class with @Cron decorators', () => {
|
|
296
|
+
class Scheduled {
|
|
297
|
+
job() {
|
|
298
|
+
// noop
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
__decorate([
|
|
302
|
+
(0, cron_decorator_1.Cron)({ cronTime: EVERY_MINUTE }),
|
|
303
|
+
__metadata("design:type", Function),
|
|
304
|
+
__metadata("design:paramtypes", []),
|
|
305
|
+
__metadata("design:returntype", void 0)
|
|
306
|
+
], Scheduled.prototype, "job", null);
|
|
307
|
+
const instance = new Scheduled();
|
|
308
|
+
const meta = (0, cron_decorator_1.getCronMetadata)(instance);
|
|
309
|
+
expect(meta).toHaveLength(1);
|
|
310
|
+
expect(meta[0].methodName).toBe('job');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
describe('CronModule', () => {
|
|
314
|
+
const EVERY_MINUTE = '* * * * *';
|
|
315
|
+
describe('forRoot()', () => {
|
|
316
|
+
it('returns module configuration with defaults', () => {
|
|
317
|
+
const result = cron_module_1.CronModule.forRoot();
|
|
318
|
+
expect(result.module).toBe(cron_module_1.CronModule);
|
|
319
|
+
expect(result.providers).toContain(cron_service_1.CronService);
|
|
320
|
+
expect(result.exports).toContain(cron_service_1.CronService);
|
|
321
|
+
expect(result.global).toBe(true);
|
|
322
|
+
});
|
|
323
|
+
it('respects isGlobal: false', () => {
|
|
324
|
+
const result = cron_module_1.CronModule.forRoot({ isGlobal: false });
|
|
325
|
+
expect(result.global).toBe(false);
|
|
326
|
+
});
|
|
327
|
+
it('respects isGlobal: true explicitly', () => {
|
|
328
|
+
const result = cron_module_1.CronModule.forRoot({ isGlobal: true });
|
|
329
|
+
expect(result.global).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
describe('registerJobsFromProvider()', () => {
|
|
333
|
+
it('registers cron jobs from a provider with @Cron decorated methods', () => {
|
|
334
|
+
const cronService = new cron_service_1.CronService();
|
|
335
|
+
core_1.Container.getInstance.mockReturnValue({
|
|
336
|
+
resolve: jest.fn().mockReturnValue(cronService),
|
|
337
|
+
});
|
|
338
|
+
class TaskService {
|
|
339
|
+
doTask() {
|
|
340
|
+
// noop
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
__decorate([
|
|
344
|
+
(0, cron_decorator_1.Cron)({ cronTime: EVERY_MINUTE, name: 'myTask' }),
|
|
345
|
+
__metadata("design:type", Function),
|
|
346
|
+
__metadata("design:paramtypes", []),
|
|
347
|
+
__metadata("design:returntype", void 0)
|
|
348
|
+
], TaskService.prototype, "doTask", null);
|
|
349
|
+
const instance = new TaskService();
|
|
350
|
+
cron_module_1.CronModule.registerJobsFromProvider(instance);
|
|
351
|
+
expect(cronService.getJobCount()).toBeGreaterThan(0);
|
|
352
|
+
cronService.clearAll();
|
|
353
|
+
});
|
|
354
|
+
it('does nothing when CronService is not in container', () => {
|
|
355
|
+
core_1.Container.getInstance.mockReturnValue({
|
|
356
|
+
resolve: jest.fn().mockReturnValue(null),
|
|
357
|
+
});
|
|
358
|
+
class NoopService {
|
|
359
|
+
}
|
|
360
|
+
expect(() => cron_module_1.CronModule.registerJobsFromProvider(new NoopService())).not.toThrow();
|
|
361
|
+
});
|
|
362
|
+
it('does nothing when provider has no @Cron metadata', () => {
|
|
363
|
+
const cronService = new cron_service_1.CronService();
|
|
364
|
+
core_1.Container.getInstance.mockReturnValue({
|
|
365
|
+
resolve: jest.fn().mockReturnValue(cronService),
|
|
366
|
+
});
|
|
367
|
+
class EmptyService {
|
|
368
|
+
plainMethod() {
|
|
369
|
+
// noop
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
cron_module_1.CronModule.registerJobsFromProvider(new EmptyService());
|
|
373
|
+
expect(cronService.getJobCount()).toBe(0);
|
|
374
|
+
});
|
|
375
|
+
it('handles errors gracefully', () => {
|
|
376
|
+
core_1.Container.getInstance.mockImplementation(() => {
|
|
377
|
+
throw new Error('container not ready');
|
|
378
|
+
});
|
|
379
|
+
class TaskService {
|
|
380
|
+
}
|
|
381
|
+
expect(() => cron_module_1.CronModule.registerJobsFromProvider(new TaskService())).not.toThrow();
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
describe('CronExpression constants', () => {
|
|
386
|
+
it('EVERY_SECOND is a valid 6-field expression', () => {
|
|
387
|
+
expect(cron_types_1.CronExpression.EVERY_SECOND).toBe('* * * * * *');
|
|
388
|
+
});
|
|
389
|
+
it('EVERY_MINUTE is a valid expression', () => {
|
|
390
|
+
expect(cron_types_1.CronExpression.EVERY_MINUTE).toBe('0 * * * * *');
|
|
391
|
+
});
|
|
392
|
+
it('EVERY_HOUR is a valid expression', () => {
|
|
393
|
+
expect(cron_types_1.CronExpression.EVERY_HOUR).toBe('0 0 * * * *');
|
|
394
|
+
});
|
|
395
|
+
it('EVERY_DAY_AT_MIDNIGHT is a valid expression', () => {
|
|
396
|
+
expect(cron_types_1.CronExpression.EVERY_DAY_AT_MIDNIGHT).toBe('0 0 0 * * *');
|
|
397
|
+
});
|
|
398
|
+
it('EVERY_YEAR is a valid expression', () => {
|
|
399
|
+
expect(cron_types_1.CronExpression.EVERY_YEAR).toBe('0 0 0 1 1 *');
|
|
400
|
+
});
|
|
401
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron job types and interfaces
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Cron expression type
|
|
6
|
+
* Format: second minute hour day-of-month month day-of-week
|
|
7
|
+
* Examples:
|
|
8
|
+
* - '* * * * * *' - Every second
|
|
9
|
+
* - '0 * * * * *' - Every minute
|
|
10
|
+
* - '0 0 * * * *' - Every hour
|
|
11
|
+
* - '0 0 0 * * *' - Every day at midnight
|
|
12
|
+
* - '0 0 9 * * 1-5' - Every weekday at 9 AM
|
|
13
|
+
*/
|
|
14
|
+
export type CronExpression = string;
|
|
15
|
+
/**
|
|
16
|
+
* Cron job options
|
|
17
|
+
*/
|
|
18
|
+
export interface CronOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Name of the cron job (for logging and management)
|
|
21
|
+
*/
|
|
22
|
+
name?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Cron expression defining when the job should run
|
|
25
|
+
*/
|
|
26
|
+
cronTime: CronExpression;
|
|
27
|
+
/**
|
|
28
|
+
* Timezone for the cron job
|
|
29
|
+
* @default 'UTC'
|
|
30
|
+
*/
|
|
31
|
+
timeZone?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to start the job immediately
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
runOnInit?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the job is enabled
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Maximum number of times the job can run
|
|
44
|
+
*/
|
|
45
|
+
maxRuns?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Error handler for the job
|
|
48
|
+
*/
|
|
49
|
+
onError?: (error: Error) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Callback when job completes
|
|
52
|
+
*/
|
|
53
|
+
onComplete?: () => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Cron job metadata
|
|
57
|
+
*/
|
|
58
|
+
export interface CronJobMetadata {
|
|
59
|
+
/**
|
|
60
|
+
* Target class
|
|
61
|
+
*/
|
|
62
|
+
target: object;
|
|
63
|
+
/**
|
|
64
|
+
* Method name
|
|
65
|
+
*/
|
|
66
|
+
methodName: string;
|
|
67
|
+
/**
|
|
68
|
+
* Cron options
|
|
69
|
+
*/
|
|
70
|
+
options: CronOptions;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Cron job status
|
|
74
|
+
*/
|
|
75
|
+
export interface CronJobStatus {
|
|
76
|
+
/**
|
|
77
|
+
* Job name
|
|
78
|
+
*/
|
|
79
|
+
name: string;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the job is running
|
|
82
|
+
*/
|
|
83
|
+
isRunning: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Last execution time
|
|
86
|
+
*/
|
|
87
|
+
lastExecution?: Date;
|
|
88
|
+
/**
|
|
89
|
+
* Next execution time
|
|
90
|
+
*/
|
|
91
|
+
nextExecution?: Date;
|
|
92
|
+
/**
|
|
93
|
+
* Number of times the job has run
|
|
94
|
+
*/
|
|
95
|
+
runCount: number;
|
|
96
|
+
/**
|
|
97
|
+
* Whether the job is enabled
|
|
98
|
+
*/
|
|
99
|
+
enabled: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Predefined cron expressions
|
|
103
|
+
*/
|
|
104
|
+
export declare const CronExpression: {
|
|
105
|
+
/**
|
|
106
|
+
* Every second
|
|
107
|
+
*/
|
|
108
|
+
readonly EVERY_SECOND: "* * * * * *";
|
|
109
|
+
/**
|
|
110
|
+
* Every 5 seconds
|
|
111
|
+
*/
|
|
112
|
+
readonly EVERY_5_SECONDS: "*/5 * * * * *";
|
|
113
|
+
/**
|
|
114
|
+
* Every 10 seconds
|
|
115
|
+
*/
|
|
116
|
+
readonly EVERY_10_SECONDS: "*/10 * * * * *";
|
|
117
|
+
/**
|
|
118
|
+
* Every 30 seconds
|
|
119
|
+
*/
|
|
120
|
+
readonly EVERY_30_SECONDS: "*/30 * * * * *";
|
|
121
|
+
/**
|
|
122
|
+
* Every minute
|
|
123
|
+
*/
|
|
124
|
+
readonly EVERY_MINUTE: "0 * * * * *";
|
|
125
|
+
/**
|
|
126
|
+
* Every 5 minutes
|
|
127
|
+
*/
|
|
128
|
+
readonly EVERY_5_MINUTES: "0 */5 * * * *";
|
|
129
|
+
/**
|
|
130
|
+
* Every 10 minutes
|
|
131
|
+
*/
|
|
132
|
+
readonly EVERY_10_MINUTES: "0 */10 * * * *";
|
|
133
|
+
/**
|
|
134
|
+
* Every 30 minutes
|
|
135
|
+
*/
|
|
136
|
+
readonly EVERY_30_MINUTES: "0 */30 * * * *";
|
|
137
|
+
/**
|
|
138
|
+
* Every hour
|
|
139
|
+
*/
|
|
140
|
+
readonly EVERY_HOUR: "0 0 * * * *";
|
|
141
|
+
/**
|
|
142
|
+
* Every day at midnight
|
|
143
|
+
*/
|
|
144
|
+
readonly EVERY_DAY_AT_MIDNIGHT: "0 0 0 * * *";
|
|
145
|
+
/**
|
|
146
|
+
* Every day at noon
|
|
147
|
+
*/
|
|
148
|
+
readonly EVERY_DAY_AT_NOON: "0 0 12 * * *";
|
|
149
|
+
/**
|
|
150
|
+
* Every week (Sunday at midnight)
|
|
151
|
+
*/
|
|
152
|
+
readonly EVERY_WEEK: "0 0 0 * * 0";
|
|
153
|
+
/**
|
|
154
|
+
* Every weekday (Monday-Friday at midnight)
|
|
155
|
+
*/
|
|
156
|
+
readonly EVERY_WEEKDAY: "0 0 0 * * 1-5";
|
|
157
|
+
/**
|
|
158
|
+
* Every weekend (Saturday and Sunday at midnight)
|
|
159
|
+
*/
|
|
160
|
+
readonly EVERY_WEEKEND: "0 0 0 * * 0,6";
|
|
161
|
+
/**
|
|
162
|
+
* Every month (1st day at midnight)
|
|
163
|
+
*/
|
|
164
|
+
readonly EVERY_MONTH: "0 0 0 1 * *";
|
|
165
|
+
/**
|
|
166
|
+
* Every quarter (1st day of Jan, Apr, Jul, Oct at midnight)
|
|
167
|
+
*/
|
|
168
|
+
readonly EVERY_QUARTER: "0 0 0 1 */3 *";
|
|
169
|
+
/**
|
|
170
|
+
* Every year (Jan 1st at midnight)
|
|
171
|
+
*/
|
|
172
|
+
readonly EVERY_YEAR: "0 0 0 1 1 *";
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=cron.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.types.d.ts","sourceRoot":"","sources":["../src/cron.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,EAAE,cAAc,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE,IAAI,CAAC;IAErB;;OAEG;IACH,aAAa,CAAC,EAAE,IAAI,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc;IACzB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;CAEK,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cron job types and interfaces
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CronExpression = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Predefined cron expressions
|
|
9
|
+
*/
|
|
10
|
+
exports.CronExpression = {
|
|
11
|
+
/**
|
|
12
|
+
* Every second
|
|
13
|
+
*/
|
|
14
|
+
EVERY_SECOND: '* * * * * *',
|
|
15
|
+
/**
|
|
16
|
+
* Every 5 seconds
|
|
17
|
+
*/
|
|
18
|
+
EVERY_5_SECONDS: '*/5 * * * * *',
|
|
19
|
+
/**
|
|
20
|
+
* Every 10 seconds
|
|
21
|
+
*/
|
|
22
|
+
EVERY_10_SECONDS: '*/10 * * * * *',
|
|
23
|
+
/**
|
|
24
|
+
* Every 30 seconds
|
|
25
|
+
*/
|
|
26
|
+
EVERY_30_SECONDS: '*/30 * * * * *',
|
|
27
|
+
/**
|
|
28
|
+
* Every minute
|
|
29
|
+
*/
|
|
30
|
+
EVERY_MINUTE: '0 * * * * *',
|
|
31
|
+
/**
|
|
32
|
+
* Every 5 minutes
|
|
33
|
+
*/
|
|
34
|
+
EVERY_5_MINUTES: '0 */5 * * * *',
|
|
35
|
+
/**
|
|
36
|
+
* Every 10 minutes
|
|
37
|
+
*/
|
|
38
|
+
EVERY_10_MINUTES: '0 */10 * * * *',
|
|
39
|
+
/**
|
|
40
|
+
* Every 30 minutes
|
|
41
|
+
*/
|
|
42
|
+
EVERY_30_MINUTES: '0 */30 * * * *',
|
|
43
|
+
/**
|
|
44
|
+
* Every hour
|
|
45
|
+
*/
|
|
46
|
+
EVERY_HOUR: '0 0 * * * *',
|
|
47
|
+
/**
|
|
48
|
+
* Every day at midnight
|
|
49
|
+
*/
|
|
50
|
+
EVERY_DAY_AT_MIDNIGHT: '0 0 0 * * *',
|
|
51
|
+
/**
|
|
52
|
+
* Every day at noon
|
|
53
|
+
*/
|
|
54
|
+
EVERY_DAY_AT_NOON: '0 0 12 * * *',
|
|
55
|
+
/**
|
|
56
|
+
* Every week (Sunday at midnight)
|
|
57
|
+
*/
|
|
58
|
+
EVERY_WEEK: '0 0 0 * * 0',
|
|
59
|
+
/**
|
|
60
|
+
* Every weekday (Monday-Friday at midnight)
|
|
61
|
+
*/
|
|
62
|
+
EVERY_WEEKDAY: '0 0 0 * * 1-5',
|
|
63
|
+
/**
|
|
64
|
+
* Every weekend (Saturday and Sunday at midnight)
|
|
65
|
+
*/
|
|
66
|
+
EVERY_WEEKEND: '0 0 0 * * 0,6',
|
|
67
|
+
/**
|
|
68
|
+
* Every month (1st day at midnight)
|
|
69
|
+
*/
|
|
70
|
+
EVERY_MONTH: '0 0 0 1 * *',
|
|
71
|
+
/**
|
|
72
|
+
* Every quarter (1st day of Jan, Apr, Jul, Oct at midnight)
|
|
73
|
+
*/
|
|
74
|
+
EVERY_QUARTER: '0 0 0 1 */3 *',
|
|
75
|
+
/**
|
|
76
|
+
* Every year (Jan 1st at midnight)
|
|
77
|
+
*/
|
|
78
|
+
EVERY_YEAR: '0 0 0 1 1 *',
|
|
79
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/cron - Cron job scheduling module for HazelJS
|
|
3
|
+
*/
|
|
4
|
+
export { CronModule, type CronModuleOptions } from './cron.module';
|
|
5
|
+
export { CronService } from './cron.service';
|
|
6
|
+
export { Cron, getCronMetadata } from './cron.decorator';
|
|
7
|
+
export type { CronOptions, CronJobMetadata, CronJobStatus } from './cron.types';
|
|
8
|
+
export { CronExpression } from './cron.types';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @hazeljs/cron - Cron job scheduling module for HazelJS
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CronExpression = exports.getCronMetadata = exports.Cron = exports.CronService = exports.CronModule = void 0;
|
|
7
|
+
var cron_module_1 = require("./cron.module");
|
|
8
|
+
Object.defineProperty(exports, "CronModule", { enumerable: true, get: function () { return cron_module_1.CronModule; } });
|
|
9
|
+
var cron_service_1 = require("./cron.service");
|
|
10
|
+
Object.defineProperty(exports, "CronService", { enumerable: true, get: function () { return cron_service_1.CronService; } });
|
|
11
|
+
var cron_decorator_1 = require("./cron.decorator");
|
|
12
|
+
Object.defineProperty(exports, "Cron", { enumerable: true, get: function () { return cron_decorator_1.Cron; } });
|
|
13
|
+
Object.defineProperty(exports, "getCronMetadata", { enumerable: true, get: function () { return cron_decorator_1.getCronMetadata; } });
|
|
14
|
+
var cron_types_1 = require("./cron.types");
|
|
15
|
+
Object.defineProperty(exports, "CronExpression", { enumerable: true, get: function () { return cron_types_1.CronExpression; } });
|