create-forgeon 0.3.11 → 0.3.14
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/package.json +1 -1
- package/src/modules/dependencies.test.mjs +41 -0
- package/src/modules/executor.mjs +3 -0
- package/src/modules/executor.test.mjs +91 -0
- package/src/modules/queue.mjs +412 -410
- package/src/modules/registry.mjs +15 -0
- package/src/modules/scheduler.mjs +368 -0
- package/src/run-add-module.mjs +4 -3
- package/src/utils/fs.mjs +31 -26
- package/templates/module-fragments/scheduler/00_title.md +1 -0
- package/templates/module-fragments/scheduler/10_overview.md +6 -0
- package/templates/module-fragments/scheduler/20_scope.md +8 -0
- package/templates/module-fragments/scheduler/90_status_implemented.md +3 -0
- package/templates/module-presets/scheduler/packages/scheduler/package.json +24 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/forgeon-scheduler.module.ts +12 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/index.ts +6 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.loader.ts +23 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.module.ts +11 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.service.ts +29 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-env.schema.ts +15 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler.service.ts +123 -0
- package/templates/module-presets/scheduler/packages/scheduler/tsconfig.json +9 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { SchedulerRegistry } from '@nestjs/schedule';
|
|
3
|
+
import { QueueService } from '@forgeon/queue';
|
|
4
|
+
import { CronJob } from 'cron';
|
|
5
|
+
import { SchedulerConfigService } from './scheduler-config.service';
|
|
6
|
+
|
|
7
|
+
const FORGEON_SCHEDULER_CRON_ID = 'forgeon.scheduler.heartbeat';
|
|
8
|
+
const FORGEON_SCHEDULER_HEARTBEAT_JOB = 'scheduler.heartbeat';
|
|
9
|
+
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class ForgeonSchedulerService implements OnModuleInit, OnModuleDestroy {
|
|
12
|
+
private readonly logger = new Logger(ForgeonSchedulerService.name);
|
|
13
|
+
|
|
14
|
+
private initializedAt: string | null = null;
|
|
15
|
+
private lastTickAt: string | null = null;
|
|
16
|
+
private lastEnqueue: { queued: boolean; id: string | null } | null = null;
|
|
17
|
+
private lastError: string | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly schedulerConfig: SchedulerConfigService,
|
|
21
|
+
private readonly schedulerRegistry: SchedulerRegistry,
|
|
22
|
+
private readonly queueService: QueueService,
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
async onModuleInit(): Promise<void> {
|
|
26
|
+
this.initializedAt = new Date().toISOString();
|
|
27
|
+
|
|
28
|
+
if (!this.schedulerConfig.enabled) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.registerHeartbeatJob();
|
|
33
|
+
await this.runHeartbeat('bootstrap');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onModuleDestroy(): void {
|
|
37
|
+
try {
|
|
38
|
+
const job = this.schedulerRegistry.getCronJob(FORGEON_SCHEDULER_CRON_ID);
|
|
39
|
+
job.stop();
|
|
40
|
+
this.schedulerRegistry.deleteCronJob(FORGEON_SCHEDULER_CRON_ID);
|
|
41
|
+
} catch {
|
|
42
|
+
// Job may not exist when the module is disabled or partially initialized.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getProbeStatus(): Promise<Record<string, unknown>> {
|
|
47
|
+
const enabled = this.schedulerConfig.enabled;
|
|
48
|
+
const heartbeatRegistered = this.isHeartbeatRegistered();
|
|
49
|
+
const status = !enabled || (heartbeatRegistered && this.lastError == null) ? 'ok' : 'error';
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
status,
|
|
53
|
+
feature: 'scheduler',
|
|
54
|
+
enabled,
|
|
55
|
+
timezone: this.schedulerConfig.timezone,
|
|
56
|
+
heartbeatCron: this.schedulerConfig.heartbeatCron,
|
|
57
|
+
heartbeatRegistered,
|
|
58
|
+
initializedAt: this.initializedAt,
|
|
59
|
+
lastTickAt: this.lastTickAt,
|
|
60
|
+
lastEnqueue: this.lastEnqueue,
|
|
61
|
+
lastError: this.lastError,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private registerHeartbeatJob(): void {
|
|
66
|
+
if (this.isHeartbeatRegistered()) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const job = new CronJob(
|
|
71
|
+
this.schedulerConfig.heartbeatCron,
|
|
72
|
+
() => {
|
|
73
|
+
void this.runHeartbeat('cron');
|
|
74
|
+
},
|
|
75
|
+
null,
|
|
76
|
+
false,
|
|
77
|
+
this.schedulerConfig.timezone,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
this.schedulerRegistry.addCronJob(FORGEON_SCHEDULER_CRON_ID, job);
|
|
81
|
+
job.start();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private isHeartbeatRegistered(): boolean {
|
|
85
|
+
try {
|
|
86
|
+
this.schedulerRegistry.getCronJob(FORGEON_SCHEDULER_CRON_ID);
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private async runHeartbeat(source: 'bootstrap' | 'cron'): Promise<void> {
|
|
94
|
+
this.lastTickAt = new Date().toISOString();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
this.lastEnqueue = await this.queueService.enqueue(
|
|
98
|
+
FORGEON_SCHEDULER_HEARTBEAT_JOB,
|
|
99
|
+
{
|
|
100
|
+
source,
|
|
101
|
+
scheduledAt: this.lastTickAt,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
jobId: FORGEON_SCHEDULER_HEARTBEAT_JOB,
|
|
105
|
+
removeOnComplete: 1,
|
|
106
|
+
removeOnFail: 20,
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
this.lastError = null;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
this.lastEnqueue = { queued: false, id: null };
|
|
112
|
+
this.lastError =
|
|
113
|
+
error instanceof Error ? error.message : 'Scheduler heartbeat enqueue failed';
|
|
114
|
+
this.logger.error(
|
|
115
|
+
JSON.stringify({
|
|
116
|
+
event: 'scheduler.heartbeat.enqueue_failed',
|
|
117
|
+
message: this.lastError,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|