firstly 0.7.2 → 0.7.3
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/CHANGELOG.md +6 -0
- package/esm/cron/Cron.d.ts +1 -1
- package/esm/cron/Cron.js +1 -1
- package/esm/cron/server/index.d.ts +9 -1
- package/esm/cron/server/index.js +42 -29
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# firstly
|
|
2
2
|
|
|
3
|
+
## 0.7.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#315](https://github.com/jycouet/firstly/pull/315) [`3403d6c`](https://github.com/jycouet/firstly/commit/3403d6cffa657ad04acdac26024bdf6e989df7b4) Thanks [@jycouet](https://github.com/jycouet)! - cron: quieter logs by default - a tick only logs `done in Xms` when it took at least `logs.ended` ms (default 100; `true` = always, `false` = never). `starting` and `result` lines are opt-in, `logs.setup` can silence the registration line. If `onTick` throws, the run is now stored as `failed` (error in `result`), always logged, and no longer leaks the concurrency slot.
|
|
8
|
+
|
|
3
9
|
## 0.7.2
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/esm/cron/Cron.d.ts
CHANGED
package/esm/cron/Cron.js
CHANGED
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { Entity, Fields } from 'remult';
|
|
8
8
|
import { Roles_Cron } from './Roles_Cron';
|
|
9
|
-
const statuses = ['starting', 'ended', 'skipped'];
|
|
9
|
+
const statuses = ['starting', 'ended', 'skipped', 'failed'];
|
|
10
10
|
let Cron = class Cron {
|
|
11
11
|
id;
|
|
12
12
|
topic;
|
|
@@ -41,9 +41,17 @@ export type CronJobParams = {
|
|
|
41
41
|
onTick: CronOnTick;
|
|
42
42
|
topic: string;
|
|
43
43
|
concurrent?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Defaults: a tick logs `done in Xms` only when it took at least `ended` ms
|
|
46
|
+
* (default 100, `true` = always, `false` = never) + a `setup done` line at registration.
|
|
47
|
+
* `starting` & `result` are opt-in (full history incl. results is stored in `_ff_crons`).
|
|
48
|
+
* Failures and concurrency skips are always logged.
|
|
49
|
+
*/
|
|
44
50
|
logs?: {
|
|
51
|
+
setup?: boolean;
|
|
45
52
|
starting?: boolean;
|
|
46
|
-
|
|
53
|
+
result?: boolean;
|
|
54
|
+
ended?: boolean | number;
|
|
47
55
|
};
|
|
48
56
|
start?: boolean;
|
|
49
57
|
runOnInit?: boolean;
|
package/esm/cron/server/index.js
CHANGED
|
@@ -30,6 +30,8 @@ export const cronTime = {
|
|
|
30
30
|
*/
|
|
31
31
|
every_friday_morning: '11 5 * * 5',
|
|
32
32
|
};
|
|
33
|
+
const DEFAULT_ENDED_MIN_MS = 100;
|
|
34
|
+
const fmtDuration = (ms) => (ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`);
|
|
33
35
|
/**
|
|
34
36
|
* usage:
|
|
35
37
|
*
|
|
@@ -59,47 +61,55 @@ export const cron = (jobsInfos) => {
|
|
|
59
61
|
key: 'cron',
|
|
60
62
|
entities: [Cron],
|
|
61
63
|
});
|
|
62
|
-
const logJobs = (topic, job, message, with_metadata = true, isSuccess = true) => {
|
|
63
|
-
const l = [];
|
|
64
|
-
l.push(magenta(`[${topic}]`));
|
|
65
|
-
l.push(message);
|
|
66
|
-
if (with_metadata) {
|
|
67
|
-
// If the job is "stopped", there will still be a next date, but it will not fire it. The job has to start.
|
|
68
|
-
l.push(`(${job.isActive ? green('running') : red('stopped')}, next at ${yellow(job.nextDate().toISO())})`);
|
|
69
|
-
}
|
|
70
|
-
if (isSuccess) {
|
|
71
|
-
log.success(l.join(' '));
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
log.info(l.join(' '));
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
64
|
m.initApi = async () => {
|
|
78
65
|
jobsInfos.forEach((infos) => {
|
|
79
66
|
const { topic, runOnInit, logs, concurrent, onTick: originalOnTick, ...params } = infos;
|
|
80
67
|
const concurrentToUse = concurrent ?? 1;
|
|
68
|
+
const prefix = magenta(`[${topic}]`);
|
|
69
|
+
const endedMinMs = logs?.ended === false
|
|
70
|
+
? Infinity
|
|
71
|
+
: logs?.ended === true
|
|
72
|
+
? 0
|
|
73
|
+
: (logs?.ended ?? DEFAULT_ENDED_MIN_MS);
|
|
81
74
|
// Create a wrapper that converts the return type to void for CronJob
|
|
82
75
|
const wrappedOnTick = async () => {
|
|
83
|
-
if (jobs[topic].concurrentInProgress
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
if (jobs[topic].concurrentInProgress >= concurrentToUse) {
|
|
77
|
+
await repo(Cron).insert({ topic, status: 'skipped' });
|
|
78
|
+
log.info(`${prefix} skipped because of concurrent limit (${yellow(concurrentToUse.toString())})`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
jobs[topic].concurrentInProgress++;
|
|
82
|
+
const startedAt = Date.now();
|
|
83
|
+
const rCron = await repo(Cron).insert({ topic });
|
|
84
|
+
if (logs?.starting) {
|
|
85
|
+
log.info(`${prefix} starting...`);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
89
88
|
const res = await originalOnTick();
|
|
90
|
-
log.info(`[${topic}] result:`, res);
|
|
91
89
|
rCron.result = res;
|
|
92
90
|
rCron.endedAt = new Date();
|
|
93
91
|
rCron.status = 'ended';
|
|
94
92
|
await repo(Cron).save(rCron);
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const ms = Date.now() - startedAt;
|
|
94
|
+
if (ms >= endedMinMs) {
|
|
95
|
+
const msg = `${prefix} done in ${fmtDuration(ms)}`;
|
|
96
|
+
if (logs?.result) {
|
|
97
|
+
log.success(msg, res);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
log.success(msg);
|
|
101
|
+
}
|
|
97
102
|
}
|
|
98
|
-
jobs[topic].concurrentInProgress = jobs[topic].concurrentInProgress - 1;
|
|
99
103
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
catch (error) {
|
|
105
|
+
rCron.result = { error: error instanceof Error ? error.message : String(error) };
|
|
106
|
+
rCron.endedAt = new Date();
|
|
107
|
+
rCron.status = 'failed';
|
|
108
|
+
await repo(Cron).save(rCron);
|
|
109
|
+
log.error(`${prefix} failed after ${fmtDuration(Date.now() - startedAt)}`, error);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
jobs[topic].concurrentInProgress--;
|
|
103
113
|
}
|
|
104
114
|
};
|
|
105
115
|
// Use type assertion to bypass complex generic type issues
|
|
@@ -108,7 +118,10 @@ export const cron = (jobsInfos) => {
|
|
|
108
118
|
onTick: wrappedOnTick,
|
|
109
119
|
});
|
|
110
120
|
jobs[topic] = { job, concurrentInProgress: 0 };
|
|
111
|
-
|
|
121
|
+
if (logs?.setup !== false) {
|
|
122
|
+
// A stopped job still reports a next date, it just won't fire it.
|
|
123
|
+
log.success(`${prefix} setup done (${job.isActive ? green('running') : red('stopped')}, next at ${yellow(job.nextDate().toISO())})`);
|
|
124
|
+
}
|
|
112
125
|
// If not it will be done too early
|
|
113
126
|
if (runOnInit) {
|
|
114
127
|
job.fireOnTick();
|