@framers/agentos-ext-triggers 0.0.0-stage → 0.1.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/LICENSE +98 -0
- package/README.md +198 -2
- package/dist/contract.d.ts +262 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/cron.d.ts +36 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +125 -0
- package/dist/cron.js.map +1 -0
- package/dist/housekeeping.d.ts +20 -0
- package/dist/housekeeping.d.ts.map +1 -0
- package/dist/housekeeping.js +33 -0
- package/dist/housekeeping.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/names.d.ts +9 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +9 -0
- package/dist/names.js.map +1 -0
- package/dist/report.d.ts +27 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +94 -0
- package/dist/report.js.map +1 -0
- package/dist/rows.d.ts +87 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +52 -0
- package/dist/rows.js.map +1 -0
- package/dist/scheduler.d.ts +44 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +743 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/store/ids.d.ts +3 -0
- package/dist/store/ids.d.ts.map +1 -0
- package/dist/store/ids.js +6 -0
- package/dist/store/ids.js.map +1 -0
- package/dist/store/migrations.d.ts +30 -0
- package/dist/store/migrations.d.ts.map +1 -0
- package/dist/store/migrations.js +26 -0
- package/dist/store/migrations.js.map +1 -0
- package/dist/store/prefix.d.ts +23 -0
- package/dist/store/prefix.d.ts.map +1 -0
- package/dist/store/prefix.js +37 -0
- package/dist/store/prefix.js.map +1 -0
- package/dist/store/sql/index.d.ts +21 -0
- package/dist/store/sql/index.d.ts.map +1 -0
- package/dist/store/sql/index.js +157 -0
- package/dist/store/sql/index.js.map +1 -0
- package/dist/watcher.d.ts +28 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +279 -0
- package/dist/watcher.js.map +1 -0
- package/dist/words.d.ts +12 -0
- package/dist/words.d.ts.map +1 -0
- package/dist/words.js +57 -0
- package/dist/words.js.map +1 -0
- package/manifest.json +39 -0
- package/package.json +68 -3
package/dist/cron.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The five-field cron parser and the next due time of the three schedule kinds,
|
|
3
|
+
* moved from wunderland's `CronScheduler` and hardened: every field's values, ranges and steps are
|
|
4
|
+
* checked at parse, so nothing loops on a step of 0 or admits `NaN`, and the next match steps a
|
|
5
|
+
* day at a time by epoch arithmetic over the UTC fields, so the host's zone never enters.
|
|
6
|
+
*/
|
|
7
|
+
import type { Schedule } from './contract.js';
|
|
8
|
+
/** A cron expression the parser refuses: five fields, each a list of numbers, ranges and steps inside its field's bounds. */
|
|
9
|
+
export declare class CronRefused extends RangeError {
|
|
10
|
+
constructor(expr: string, reason: string);
|
|
11
|
+
}
|
|
12
|
+
/** The values each of a cron expression's five fields admits, after the parser checked them. */
|
|
13
|
+
export interface CronFields {
|
|
14
|
+
readonly minutes: ReadonlySet<number>;
|
|
15
|
+
readonly hours: ReadonlySet<number>;
|
|
16
|
+
readonly daysOfMonth: ReadonlySet<number>;
|
|
17
|
+
readonly months: ReadonlySet<number>;
|
|
18
|
+
readonly daysOfWeek: ReadonlySet<number>;
|
|
19
|
+
}
|
|
20
|
+
/** Five fields (minute, hour, day of month, month, day of week with 0 as Sunday), each checked at parse: nothing here loops on a bad step or admits NaN. */
|
|
21
|
+
export declare function parseCronExpression(expr: string): CronFields;
|
|
22
|
+
/**
|
|
23
|
+
* The first whole minute after `afterMs` whose UTC fields match; undefined when none ever does (`0 0 31 2 *`) or `afterMs` is not finite.
|
|
24
|
+
* It steps a day at a time over one 400-year Gregorian cycle, after which the calendar repeats, so a match years away (29 February on a Monday,
|
|
25
|
+
* 40 years apart across 2100) is found and an expression with no match answers undefined. Epoch arithmetic only: the host's zone never enters.
|
|
26
|
+
* The day of the month and the day of the week must both match (a restricted pair is read as both, not as either).
|
|
27
|
+
*/
|
|
28
|
+
export declare function nextCronMatch(fields: CronFields, afterMs: number): number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The next due time strictly after `afterMs`: `at` once; `every` after plus the interval, or aligned to the anchor;
|
|
31
|
+
* `cron` the next matching minute. Undefined when `afterMs` is not finite. Throws `CronRefused` for a cron expression the parser refuses (`register` catches it).
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeNextRunAtMs(schedule: Schedule, afterMs: number): number | undefined;
|
|
34
|
+
/** The checks `register` runs: a finite `at`; an integer `every` of `tickMs` or more, with a finite anchor when it has one; a cron expression that parses. Throws a `RangeError` (`CronRefused` for cron). */
|
|
35
|
+
export declare function requireSchedule(schedule: Schedule, tickMs: number): void;
|
|
36
|
+
//# sourceMappingURL=cron.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../src/cron.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,6HAA6H;AAC7H,qBAAa,WAAY,SAAQ,UAAU;gBAC7B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAIzC;AAED,gGAAgG;AAChG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC1C;AA4BD,4JAA4J;AAC5J,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAU5D;AAUD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkBrF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB1F;AAED,8MAA8M;AAC9M,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CASxE"}
|
package/dist/cron.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** A cron expression the parser refuses: five fields, each a list of numbers, ranges and steps inside its field's bounds. */
|
|
2
|
+
export class CronRefused extends RangeError {
|
|
3
|
+
constructor(expr, reason) {
|
|
4
|
+
super(`The cron expression was refused (${reason}): ${expr}`);
|
|
5
|
+
this.name = 'CronRefused';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
const PART = /^(\*|(\d{1,2})(?:-(\d{1,2}))?)(?:\/(\d{1,2}))?$/;
|
|
9
|
+
function parseField(expr, field, min, max) {
|
|
10
|
+
const values = new Set();
|
|
11
|
+
if (field === '')
|
|
12
|
+
throw new CronRefused(expr, 'an empty field');
|
|
13
|
+
for (const part of field.split(',')) {
|
|
14
|
+
const match = PART.exec(part);
|
|
15
|
+
if (match === null)
|
|
16
|
+
throw new CronRefused(expr, `the part ${part} is not a number, a range, a step or *`);
|
|
17
|
+
const step = match[4] === undefined ? 1 : Number(match[4]);
|
|
18
|
+
if (!Number.isInteger(step) || step < 1)
|
|
19
|
+
throw new CronRefused(expr, 'a step below 1');
|
|
20
|
+
let start;
|
|
21
|
+
let end;
|
|
22
|
+
if (match[1] === '*') {
|
|
23
|
+
start = min;
|
|
24
|
+
end = max;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
start = Number(match[2]);
|
|
28
|
+
end = match[3] === undefined ? (match[4] === undefined ? start : max) : Number(match[3]);
|
|
29
|
+
}
|
|
30
|
+
if (start < min || end > max)
|
|
31
|
+
throw new CronRefused(expr, `a value outside ${min} to ${max}`);
|
|
32
|
+
if (start > end)
|
|
33
|
+
throw new CronRefused(expr, 'a reversed range');
|
|
34
|
+
for (let value = start; value <= end; value += step)
|
|
35
|
+
values.add(value);
|
|
36
|
+
}
|
|
37
|
+
return values;
|
|
38
|
+
}
|
|
39
|
+
/** Five fields (minute, hour, day of month, month, day of week with 0 as Sunday), each checked at parse: nothing here loops on a bad step or admits NaN. */
|
|
40
|
+
export function parseCronExpression(expr) {
|
|
41
|
+
const parts = expr.trim().split(/\s+/);
|
|
42
|
+
if (parts.length !== 5)
|
|
43
|
+
throw new CronRefused(expr, `expected 5 fields, got ${parts.length}`);
|
|
44
|
+
return {
|
|
45
|
+
minutes: parseField(expr, parts[0], 0, 59),
|
|
46
|
+
hours: parseField(expr, parts[1], 0, 23),
|
|
47
|
+
daysOfMonth: parseField(expr, parts[2], 1, 31),
|
|
48
|
+
months: parseField(expr, parts[3], 1, 12),
|
|
49
|
+
daysOfWeek: parseField(expr, parts[4], 0, 6),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const MINUTE_MS = 60_000;
|
|
53
|
+
const HOUR_MS = 3_600_000;
|
|
54
|
+
const DAY_MS = 86_400_000;
|
|
55
|
+
/** 400 Gregorian years: 146,097 days, a whole number of weeks, after which every day's month, date and weekday repeat. */
|
|
56
|
+
const SEARCH_DAYS = 146_097;
|
|
57
|
+
const ascending = (field) => [...field].sort((a, b) => a - b);
|
|
58
|
+
/**
|
|
59
|
+
* The first whole minute after `afterMs` whose UTC fields match; undefined when none ever does (`0 0 31 2 *`) or `afterMs` is not finite.
|
|
60
|
+
* It steps a day at a time over one 400-year Gregorian cycle, after which the calendar repeats, so a match years away (29 February on a Monday,
|
|
61
|
+
* 40 years apart across 2100) is found and an expression with no match answers undefined. Epoch arithmetic only: the host's zone never enters.
|
|
62
|
+
* The day of the month and the day of the week must both match (a restricted pair is read as both, not as either).
|
|
63
|
+
*/
|
|
64
|
+
export function nextCronMatch(fields, afterMs) {
|
|
65
|
+
if (!Number.isFinite(afterMs))
|
|
66
|
+
return undefined;
|
|
67
|
+
const start = Math.floor(afterMs / MINUTE_MS) * MINUTE_MS + MINUTE_MS;
|
|
68
|
+
const firstDay = Math.floor(start / DAY_MS) * DAY_MS;
|
|
69
|
+
const hours = ascending(fields.hours);
|
|
70
|
+
const minutes = ascending(fields.minutes);
|
|
71
|
+
for (let i = 0; i <= SEARCH_DAYS; i += 1) {
|
|
72
|
+
const day = firstDay + i * DAY_MS;
|
|
73
|
+
const d = new Date(day);
|
|
74
|
+
if (!fields.months.has(d.getUTCMonth() + 1) || !fields.daysOfMonth.has(d.getUTCDate()) || !fields.daysOfWeek.has(d.getUTCDay()))
|
|
75
|
+
continue;
|
|
76
|
+
for (const hour of hours) {
|
|
77
|
+
for (const minute of minutes) {
|
|
78
|
+
const at = day + hour * HOUR_MS + minute * MINUTE_MS;
|
|
79
|
+
if (at >= start)
|
|
80
|
+
return at;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The next due time strictly after `afterMs`: `at` once; `every` after plus the interval, or aligned to the anchor;
|
|
88
|
+
* `cron` the next matching minute. Undefined when `afterMs` is not finite. Throws `CronRefused` for a cron expression the parser refuses (`register` catches it).
|
|
89
|
+
*/
|
|
90
|
+
export function computeNextRunAtMs(schedule, afterMs) {
|
|
91
|
+
if (!Number.isFinite(afterMs))
|
|
92
|
+
return undefined;
|
|
93
|
+
switch (schedule.kind) {
|
|
94
|
+
case 'at':
|
|
95
|
+
return Number.isFinite(schedule.at) && schedule.at > afterMs ? schedule.at : undefined;
|
|
96
|
+
case 'every': {
|
|
97
|
+
const { everyMs, anchorMs } = schedule;
|
|
98
|
+
if (!Number.isInteger(everyMs) || everyMs <= 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (anchorMs === undefined)
|
|
101
|
+
return afterMs + everyMs;
|
|
102
|
+
if (!Number.isFinite(anchorMs))
|
|
103
|
+
return undefined;
|
|
104
|
+
if (afterMs < anchorMs)
|
|
105
|
+
return anchorMs;
|
|
106
|
+
return anchorMs + (Math.floor((afterMs - anchorMs) / everyMs) + 1) * everyMs;
|
|
107
|
+
}
|
|
108
|
+
case 'cron':
|
|
109
|
+
return nextCronMatch(parseCronExpression(schedule.expr), afterMs);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** The checks `register` runs: a finite `at`; an integer `every` of `tickMs` or more, with a finite anchor when it has one; a cron expression that parses. Throws a `RangeError` (`CronRefused` for cron). */
|
|
113
|
+
export function requireSchedule(schedule, tickMs) {
|
|
114
|
+
if (schedule.kind === 'at' && !Number.isFinite(schedule.at))
|
|
115
|
+
throw new RangeError('an at schedule needs a finite time');
|
|
116
|
+
if (schedule.kind === 'every' && (!Number.isInteger(schedule.everyMs) || schedule.everyMs < tickMs)) {
|
|
117
|
+
throw new RangeError(`an every schedule needs an integer of ${tickMs} ms or more`);
|
|
118
|
+
}
|
|
119
|
+
if (schedule.kind === 'every' && schedule.anchorMs !== undefined && !Number.isFinite(schedule.anchorMs)) {
|
|
120
|
+
throw new RangeError('an every schedule needs a finite anchor');
|
|
121
|
+
}
|
|
122
|
+
if (schedule.kind === 'cron')
|
|
123
|
+
parseCronExpression(schedule.expr);
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=cron.js.map
|
package/dist/cron.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.js","sourceRoot":"","sources":["../src/cron.ts"],"names":[],"mappings":"AAQA,6HAA6H;AAC7H,MAAM,OAAO,WAAY,SAAQ,UAAU;IACzC,YAAY,IAAY,EAAE,MAAc;QACtC,KAAK,CAAC,oCAAoC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAWD,MAAM,IAAI,GAAG,iDAAiD,CAAC;AAE/D,SAAS,UAAU,CAAC,IAAY,EAAE,KAAa,EAAE,GAAW,EAAE,GAAW;IACvE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,IAAI,KAAK,KAAK,EAAE;QAAE,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAChE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,KAAK,KAAK,IAAI;YAAE,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,YAAY,IAAI,wCAAwC,CAAC,CAAC;QAC1G,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QACvF,IAAI,KAAa,CAAC;QAClB,IAAI,GAAW,CAAC;QAChB,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACrB,KAAK,GAAG,GAAG,CAAC;YACZ,GAAG,GAAG,GAAG,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACzB,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3F,CAAC;QACD,IAAI,KAAK,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG;YAAE,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,GAAG,EAAE,CAAC,CAAC;QAC9F,IAAI,KAAK,GAAG,GAAG;YAAE,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QACjE,KAAK,IAAI,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,GAAG,EAAE,KAAK,IAAI,IAAI;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4JAA4J;AAC5J,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,0BAA0B,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9F,OAAO;QACL,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,KAAK,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,WAAW,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,MAAM,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,EAAE,CAAC,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAC;AACzB,MAAM,OAAO,GAAG,SAAS,CAAC;AAC1B,MAAM,MAAM,GAAG,UAAU,CAAC;AAC1B,0HAA0H;AAC1H,MAAM,WAAW,GAAG,OAAO,CAAC;AAE5B,MAAM,SAAS,GAAG,CAAC,KAA0B,EAAY,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAE7F;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAAkB,EAAE,OAAe;IAC/D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,SAAS,GAAG,SAAS,CAAC;IACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;IACrD,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC;QAClC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;YAAE,SAAS;QAC1I,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,EAAE,GAAG,GAAG,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;gBACrD,IAAI,EAAE,IAAI,KAAK;oBAAE,OAAO,EAAE,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAkB,EAAE,OAAe;IACpE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,IAAI;YACP,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,QAAQ,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC;gBAAE,OAAO,SAAS,CAAC;YACjE,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,OAAO,GAAG,OAAO,CAAC;YACrD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,OAAO,SAAS,CAAC;YACjD,IAAI,OAAO,GAAG,QAAQ;gBAAE,OAAO,QAAQ,CAAC;YACxC,OAAO,QAAQ,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;QAC/E,CAAC;QACD,KAAK,MAAM;YACT,OAAO,aAAa,CAAC,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED,8MAA8M;AAC9M,MAAM,UAAU,eAAe,CAAC,QAAkB,EAAE,MAAc;IAChE,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,oCAAoC,CAAC,CAAC;IACxH,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC;QACpG,MAAM,IAAI,UAAU,CAAC,yCAAyC,MAAM,aAAa,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxG,MAAM,IAAI,UAAU,CAAC,yCAAyC,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM;QAAE,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The retention of runs and missed runs, which the scheduler's tick runs once an hour
|
|
3
|
+
* at most: runs beyond the newest 100 a job or older than 14 days, missed rows beyond the newest 50
|
|
4
|
+
* a job or older than 7 days. A run still running and a missed due time still owed to a catch-up
|
|
5
|
+
* are kept whatever their age and number.
|
|
6
|
+
*/
|
|
7
|
+
import type { StorageAdapter } from '@framers/sql-storage-adapter';
|
|
8
|
+
import type { Keep } from './contract.js';
|
|
9
|
+
/** The defaults of spec 6.4. `Keep`'s fields are numbers, so a host's `keep` option is typed by them, not by these literals. */
|
|
10
|
+
export declare const KEEP: Keep;
|
|
11
|
+
/** The retention a host's `keep` gives: each field it leaves out (or gives as `undefined`) at its default; a field that is not a whole number above 0 throws a `RangeError`. */
|
|
12
|
+
export declare function requireKeep(keep?: Partial<Keep>): Keep;
|
|
13
|
+
/**
|
|
14
|
+
* The retention of spec 6.4. A run still `running` is never deleted (its settlement or the next claim closes it), and a
|
|
15
|
+
* missed due time still owed to a catch-up (`caught_up` with no run) is never deleted: a `required` catch-up that fails and
|
|
16
|
+
* retries reads its `covers` from those rows again, so none may go before a run served it. Each per-job statement deletes
|
|
17
|
+
* the rows a bounded SELECT leaves out, since SQLite has no DELETE ... LIMIT in every build.
|
|
18
|
+
*/
|
|
19
|
+
export declare function housekeep(db: StorageAdapter, table: (name: 'jobs' | 'runs' | 'missed') => string, nowMs: number, keep?: Keep): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=housekeeping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"housekeeping.d.ts","sourceRoot":"","sources":["../src/housekeeping.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,gIAAgI;AAChI,eAAO,MAAM,IAAI,EAAE,IAAwE,CAAC;AAI5F,gLAAgL;AAChL,wBAAgB,WAAW,CAAC,IAAI,GAAE,OAAO,CAAC,IAAI,CAAM,GAAG,IAAI,CAS1D;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,IAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAexJ"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The defaults of spec 6.4. `Keep`'s fields are numbers, so a host's `keep` option is typed by them, not by these literals. */
|
|
2
|
+
export const KEEP = { runsPerJob: 100, runDays: 14, missedPerJob: 50, missedDays: 7 };
|
|
3
|
+
const DAY_MS = 86_400_000;
|
|
4
|
+
/** The retention a host's `keep` gives: each field it leaves out (or gives as `undefined`) at its default; a field that is not a whole number above 0 throws a `RangeError`. */
|
|
5
|
+
export function requireKeep(keep = {}) {
|
|
6
|
+
const merged = { ...KEEP };
|
|
7
|
+
for (const field of Object.keys(KEEP)) {
|
|
8
|
+
const value = keep[field];
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
continue;
|
|
11
|
+
if (!(Number.isInteger(value) && value > 0))
|
|
12
|
+
throw new RangeError(`keep.${field} is a whole number above 0`);
|
|
13
|
+
merged[field] = value;
|
|
14
|
+
}
|
|
15
|
+
return merged;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The retention of spec 6.4. A run still `running` is never deleted (its settlement or the next claim closes it), and a
|
|
19
|
+
* missed due time still owed to a catch-up (`caught_up` with no run) is never deleted: a `required` catch-up that fails and
|
|
20
|
+
* retries reads its `covers` from those rows again, so none may go before a run served it. Each per-job statement deletes
|
|
21
|
+
* the rows a bounded SELECT leaves out, since SQLite has no DELETE ... LIMIT in every build.
|
|
22
|
+
*/
|
|
23
|
+
export async function housekeep(db, table, nowMs, keep = KEEP) {
|
|
24
|
+
const owed = `action = 'caught_up' AND run_id IS NULL`;
|
|
25
|
+
await db.run(`DELETE FROM ${table('runs')} WHERE outcome <> 'running' AND started_at < ?`, [nowMs - keep.runDays * DAY_MS]);
|
|
26
|
+
await db.run(`DELETE FROM ${table('missed')} WHERE due_at < ? AND NOT (${owed})`, [nowMs - keep.missedDays * DAY_MS]);
|
|
27
|
+
const jobs = await db.all(`SELECT name FROM ${table('jobs')}`);
|
|
28
|
+
for (const { name } of jobs) {
|
|
29
|
+
await db.run(`DELETE FROM ${table('runs')} WHERE job = ? AND outcome <> 'running' AND id NOT IN (SELECT id FROM ${table('runs')} WHERE job = ? ORDER BY started_at DESC LIMIT ?)`, [name, name, keep.runsPerJob]);
|
|
30
|
+
await db.run(`DELETE FROM ${table('missed')} WHERE job = ? AND NOT (${owed}) AND id NOT IN (SELECT id FROM ${table('missed')} WHERE job = ? ORDER BY due_at DESC LIMIT ?)`, [name, name, keep.missedPerJob]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=housekeeping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"housekeeping.js","sourceRoot":"","sources":["../src/housekeeping.ts"],"names":[],"mappings":"AASA,gIAAgI;AAChI,MAAM,CAAC,MAAM,IAAI,GAAS,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;AAE5F,MAAM,MAAM,GAAG,UAAU,CAAC;AAE1B,gLAAgL;AAChL,MAAM,UAAU,WAAW,CAAC,OAAsB,EAAE;IAClD,MAAM,MAAM,GAA+B,EAAE,GAAG,IAAI,EAAE,CAAC;IACvD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAsB,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,QAAQ,KAAK,4BAA4B,CAAC,CAAC;QAC7G,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;IACxB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAAkB,EAAE,KAAmD,EAAE,KAAa,EAAE,OAAa,IAAI;IACvI,MAAM,IAAI,GAAG,yCAAyC,CAAC;IACvD,MAAM,EAAE,CAAC,GAAG,CAAC,eAAe,KAAK,CAAC,MAAM,CAAC,gDAAgD,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC;IAC5H,MAAM,EAAE,CAAC,GAAG,CAAC,eAAe,KAAK,CAAC,QAAQ,CAAC,8BAA8B,IAAI,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC;IACtH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,GAAG,CAAmB,oBAAoB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjF,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,EAAE,CAAC,GAAG,CACV,eAAe,KAAK,CAAC,MAAM,CAAC,yEAAyE,KAAK,CAAC,MAAM,CAAC,kDAAkD,EACpK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAC9B,CAAC;QACF,MAAM,EAAE,CAAC,GAAG,CACV,eAAe,KAAK,CAAC,QAAQ,CAAC,2BAA2B,IAAI,mCAAmC,KAAK,CAAC,QAAQ,CAAC,8CAA8C,EAC7J,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAChC,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The package's root entry: the contract's types, `createScheduler`,
|
|
3
|
+
* `createSchedulerForTests`, `createWatcher`, `migrateTriggers`, `scheduleWords` and
|
|
4
|
+
* `computeNextRunAtMs`.
|
|
5
|
+
*/
|
|
6
|
+
import type { Scheduler, TriggersOptions, Watcher } from './contract.js';
|
|
7
|
+
import { type SchedulerInternals } from './scheduler.js';
|
|
8
|
+
import { type WatcherOptions } from './watcher.js';
|
|
9
|
+
export type * from './contract.js';
|
|
10
|
+
export type { SchedulerInternals } from './scheduler.js';
|
|
11
|
+
export type { WatcherOptions } from './watcher.js';
|
|
12
|
+
export { migrateTriggers } from './store/migrations.js';
|
|
13
|
+
export { scheduleWords } from './words.js';
|
|
14
|
+
export { computeNextRunAtMs } from './cron.js';
|
|
15
|
+
/**
|
|
16
|
+
* The scheduler of record over `options.db`: the process that runs jobs registers them and calls `start()`, which
|
|
17
|
+
* restores every job and arms the tick; a process that only reads and commands builds one and never starts it.
|
|
18
|
+
*/
|
|
19
|
+
export declare const createScheduler: (options: TriggersOptions) => Scheduler;
|
|
20
|
+
/**
|
|
21
|
+
* The same scheduler for a host's tests: no interval is armed, and the test drives `tick()` (which resolves once each
|
|
22
|
+
* run it claimed has called its body) and `settled()` (once every run in flight has settled, and every write a
|
|
23
|
+
* `report()` began) on its own clock.
|
|
24
|
+
*/
|
|
25
|
+
export declare const createSchedulerForTests: (options: TriggersOptions) => SchedulerInternals;
|
|
26
|
+
/**
|
|
27
|
+
* The stall watcher, for a process that runs no jobs: `start()` passes at once and then every `everyMs` (a minute by
|
|
28
|
+
* default) under the watch lease, telling the host's handlers of a stall, its recovery and a job that stays failed, each
|
|
29
|
+
* handler call given up as a throw after `handlerTimeoutMs` (30 seconds by default);
|
|
30
|
+
* `stop()` clears the interval and waits for a pass in progress.
|
|
31
|
+
*/
|
|
32
|
+
export declare const createWatcher: (options: WatcherOptions) => Watcher;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAA8C,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACrG,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3F,mBAAmB,eAAe,CAAC;AACnC,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,SAAyD,CAAC;AAEtH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,kBAAkF,CAAC;AAEvJ;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAqD,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createScheduler as createSchedulerInternal } from './scheduler.js';
|
|
2
|
+
import { createWatcher as createWatcherInternal } from './watcher.js';
|
|
3
|
+
export { migrateTriggers } from './store/migrations.js';
|
|
4
|
+
export { scheduleWords } from './words.js';
|
|
5
|
+
export { computeNextRunAtMs } from './cron.js';
|
|
6
|
+
/**
|
|
7
|
+
* The scheduler of record over `options.db`: the process that runs jobs registers them and calls `start()`, which
|
|
8
|
+
* restores every job and arms the tick; a process that only reads and commands builds one and never starts it.
|
|
9
|
+
*/
|
|
10
|
+
export const createScheduler = (options) => createSchedulerInternal(options);
|
|
11
|
+
/**
|
|
12
|
+
* The same scheduler for a host's tests: no interval is armed, and the test drives `tick()` (which resolves once each
|
|
13
|
+
* run it claimed has called its body) and `settled()` (once every run in flight has settled, and every write a
|
|
14
|
+
* `report()` began) on its own clock.
|
|
15
|
+
*/
|
|
16
|
+
export const createSchedulerForTests = (options) => createSchedulerInternal(options, { arm: false });
|
|
17
|
+
/**
|
|
18
|
+
* The stall watcher, for a process that runs no jobs: `start()` passes at once and then every `everyMs` (a minute by
|
|
19
|
+
* default) under the watch lease, telling the host's handlers of a stall, its recovery and a job that stays failed, each
|
|
20
|
+
* handler call given up as a throw after `handlerTimeoutMs` (30 seconds by default);
|
|
21
|
+
* `stop()` clears the interval and waits for a pass in progress.
|
|
22
|
+
*/
|
|
23
|
+
export const createWatcher = (options) => createWatcherInternal(options);
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,IAAI,uBAAuB,EAA2B,MAAM,gBAAgB,CAAC;AACrG,OAAO,EAAE,aAAa,IAAI,qBAAqB,EAAuB,MAAM,cAAc,CAAC;AAK3F,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAA4C,CAAC,OAAO,EAAE,EAAE,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAEtH;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAqD,CAAC,OAAO,EAAE,EAAE,CAAC,uBAAuB,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;AAEvJ;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAyC,CAAC,OAAO,EAAE,EAAE,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC"}
|
package/dist/names.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The job-name rule and the stall watcher's own row, which the scheduler, the
|
|
3
|
+
* reports-only writer and the watcher all read.
|
|
4
|
+
*/
|
|
5
|
+
/** A job's name: the row's key and the page's address. */
|
|
6
|
+
export declare const NAME: RegExp;
|
|
7
|
+
/** The stall watcher's own lease row: kind `scheduled`, never listed, never commanded. A name outside `NAME`, so no job can take it. */
|
|
8
|
+
export declare const WATCH_ROW = "__watch";
|
|
9
|
+
//# sourceMappingURL=names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0DAA0D;AAC1D,eAAO,MAAM,IAAI,QAA2B,CAAC;AAE7C,wIAAwI;AACxI,eAAO,MAAM,SAAS,YAAY,CAAC"}
|
package/dist/names.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The job-name rule and the stall watcher's own row, which the scheduler, the
|
|
3
|
+
* reports-only writer and the watcher all read.
|
|
4
|
+
*/
|
|
5
|
+
/** A job's name: the row's key and the page's address. */
|
|
6
|
+
export const NAME = /^[a-z][a-z0-9_]{0,63}$/;
|
|
7
|
+
/** The stall watcher's own lease row: kind `scheduled`, never listed, never commanded. A name outside `NAME`, so no job can take it. */
|
|
8
|
+
export const WATCH_ROW = '__watch';
|
|
9
|
+
//# sourceMappingURL=names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.js","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,IAAI,GAAG,wBAAwB,CAAC;AAE7C,wIAAwI;AACxI,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC"}
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reports-only jobs: a loop that runs on its own timer reports each run, and the
|
|
3
|
+
* scheduler keeps a `reports_only` row and a run row for it, written once a minute at most a job,
|
|
4
|
+
* so the jobs page shows it and the stall watcher judges it.
|
|
5
|
+
*/
|
|
6
|
+
import type { StorageAdapter } from '@framers/sql-storage-adapter';
|
|
7
|
+
import type { Scheduler, TriggersLogger } from './contract.js';
|
|
8
|
+
type Facts = Parameters<Scheduler['report']>[1];
|
|
9
|
+
type Outcome = Parameters<Scheduler['report']>[2];
|
|
10
|
+
/** How often a reports-only job's row and run row are written at most. */
|
|
11
|
+
export declare const REPORT_EVERY_MS = 60000;
|
|
12
|
+
/** What `createReporter` answers: `report`, and the writes it began that have not settled, which the scheduler's `settled()` waits for. */
|
|
13
|
+
export interface Reporter {
|
|
14
|
+
report(name: string, facts: Facts, outcome: Outcome): void;
|
|
15
|
+
pending(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reports-only jobs (spec 4.2 step 8): a row of kind `reports_only` upserted and one run row a minute at most, the writes
|
|
19
|
+
* coalesced in memory; never throws, never waits. A failure after a written ok or skip is written at once, so the row never
|
|
20
|
+
* reads ok over a failure its loop reported (two writes a minute at most). A name outside the job-name rule, facts no schedule
|
|
21
|
+
* can have, an outcome outside the three and a store that refuses the write each log one warning; a report refused before
|
|
22
|
+
* the store holds no minute. A row a scheduled job holds is never changed: the upsert changes a `reports_only` row only, and
|
|
23
|
+
* a report that changed no row writes no run and logs `report_refused`. A job's writes land in the order they began.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createReporter(db: StorageAdapter, table: (name: 'jobs' | 'runs') => string, now: () => number, logger: TriggersLogger): Reporter;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAK/D,KAAK,KAAK,GAAG,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,KAAK,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAElD,0EAA0E;AAC1E,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,2IAA2I;AAC3I,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AA+BD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAAE,GAAG,EAAE,MAAM,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,QAAQ,CAwDhJ"}
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { NAME } from './names.js';
|
|
2
|
+
import { randomHex } from './store/ids.js';
|
|
3
|
+
import { scheduleWords } from './words.js';
|
|
4
|
+
/** How often a reports-only job's row and run row are written at most. */
|
|
5
|
+
export const REPORT_EVERY_MS = 60_000;
|
|
6
|
+
/** The outcomes a report may carry: `running` is a scheduled run's own word, and a row reading it would be kept by the retention for good. */
|
|
7
|
+
const OUTCOMES = new Set(['ok', 'failed', 'skipped']);
|
|
8
|
+
/** A report's columns, checked; null for facts no schedule can have (`scheduleWords` and the gap's check throw) and for an outcome outside the three. */
|
|
9
|
+
function columnsOf(facts, outcome) {
|
|
10
|
+
try {
|
|
11
|
+
return check(facts, outcome);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** A report's columns. Throws a `RangeError` for facts no schedule can have and for an outcome outside the three. */
|
|
18
|
+
function check(facts, outcome) {
|
|
19
|
+
if (!OUTCOMES.has(outcome))
|
|
20
|
+
throw new RangeError('a report\'s outcome is ok, failed or skipped');
|
|
21
|
+
// `scheduleWords` refuses an interval that is not a whole number of milliseconds above 0.
|
|
22
|
+
const words = scheduleWords({ kind: 'every', everyMs: facts.everyMs });
|
|
23
|
+
if (facts.stallAfterMs !== undefined && !(Number.isInteger(facts.stallAfterMs) && facts.stallAfterMs > 0))
|
|
24
|
+
throw new RangeError('a stall gap is a whole number of milliseconds above 0');
|
|
25
|
+
// The row is written once a minute at most, so a healthy loop's `last_run_at` can read up to a minute old: the gap is never
|
|
26
|
+
// under three of those windows (a loop reporting each second would otherwise read stalled between two writes).
|
|
27
|
+
return { words, stallAfterMs: Math.max(facts.stallAfterMs ?? facts.everyMs * 2, 3 * REPORT_EVERY_MS) };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reports-only jobs (spec 4.2 step 8): a row of kind `reports_only` upserted and one run row a minute at most, the writes
|
|
31
|
+
* coalesced in memory; never throws, never waits. A failure after a written ok or skip is written at once, so the row never
|
|
32
|
+
* reads ok over a failure its loop reported (two writes a minute at most). A name outside the job-name rule, facts no schedule
|
|
33
|
+
* can have, an outcome outside the three and a store that refuses the write each log one warning; a report refused before
|
|
34
|
+
* the store holds no minute. A row a scheduled job holds is never changed: the upsert changes a `reports_only` row only, and
|
|
35
|
+
* a report that changed no row writes no run and logs `report_refused`. A job's writes land in the order they began.
|
|
36
|
+
*/
|
|
37
|
+
export function createReporter(db, table, now, logger) {
|
|
38
|
+
/** Each job's last write this instance began: its time and its outcome. */
|
|
39
|
+
const lastWrite = new Map();
|
|
40
|
+
/** Each job's newest write, which its next write waits for, so two writes of one job never commit out of order. */
|
|
41
|
+
const chains = new Map();
|
|
42
|
+
const writes = new Set();
|
|
43
|
+
async function write(name, facts, columns, outcome, at) {
|
|
44
|
+
try {
|
|
45
|
+
const changed = await db.transaction(async (trx) => {
|
|
46
|
+
const upsert = await trx.run(`INSERT INTO ${table('jobs')} (name, title, process, kind, schedule, schedule_words, rule, stall_after_ms, safe_to_repeat, retries, timeout_ms, legal_duty, enabled, last_run_at, last_outcome, registered_at, updated_at)
|
|
47
|
+
VALUES (?, ?, ?, 'reports_only', NULL, ?, 'skip', ?, 0, 0, 0, ?, 1, ?, ?, ?, ?)
|
|
48
|
+
ON CONFLICT (name) DO UPDATE SET title = excluded.title, process = excluded.process, schedule_words = excluded.schedule_words, stall_after_ms = excluded.stall_after_ms,
|
|
49
|
+
legal_duty = excluded.legal_duty, last_run_at = excluded.last_run_at, last_outcome = excluded.last_outcome, updated_at = excluded.updated_at
|
|
50
|
+
WHERE ${table('jobs')}.kind = 'reports_only'`, [name, facts.title, facts.process, columns.words, columns.stallAfterMs, facts.legalDuty === true ? 1 : 0, at, outcome, at, at]);
|
|
51
|
+
if (Number(upsert.changes) !== 1)
|
|
52
|
+
return false;
|
|
53
|
+
await trx.run(`INSERT INTO ${table('runs')} (id, job, started_at, ended_at, outcome, ms, tries) VALUES (?, ?, ?, ?, ?, 0, 1)`, [randomHex(16), name, at, at, outcome]);
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
if (!changed)
|
|
57
|
+
logger.warn('report_refused', { job: name });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
logger.warn('report_failed', { job: name });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
report(name, facts, outcome) {
|
|
65
|
+
if (typeof name !== 'string' || !NAME.test(name)) {
|
|
66
|
+
logger.warn('report_refused', { job: String(name).slice(0, 64) });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const columns = columnsOf(facts, outcome);
|
|
70
|
+
if (columns === null) {
|
|
71
|
+
logger.warn('report_failed', { job: name });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const at = now();
|
|
75
|
+
const last = lastWrite.get(name);
|
|
76
|
+
// One write a minute a job; a failure after a written ok or skip is written at once.
|
|
77
|
+
if (last !== undefined && at - last.at < REPORT_EVERY_MS && !(outcome === 'failed' && last.outcome !== 'failed'))
|
|
78
|
+
return;
|
|
79
|
+
lastWrite.set(name, { at, outcome });
|
|
80
|
+
const pending = (chains.get(name) ?? Promise.resolve()).then(() => write(name, facts, columns, outcome, at));
|
|
81
|
+
chains.set(name, pending);
|
|
82
|
+
writes.add(pending);
|
|
83
|
+
void pending.finally(() => {
|
|
84
|
+
writes.delete(pending);
|
|
85
|
+
if (chains.get(name) === pending)
|
|
86
|
+
chains.delete(name);
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
async pending() {
|
|
90
|
+
await Promise.all([...writes]);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
|
package/dist/rows.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The rows as the adapter answers them and the views the reads answer: the state
|
|
3
|
+
* word, the pause, the last run, the lease, the retry in progress, the failure and a hand run asked
|
|
4
|
+
* for. PostgreSQL answers BIGINT as a string, so every time goes through `num` or `must`.
|
|
5
|
+
*/
|
|
6
|
+
import type { JobState, JobView, MissedView, RunOutcome, RunView, Schedule } from './contract.js';
|
|
7
|
+
/** `<prefix>_jobs` as the adapter answers it; PostgreSQL answers BIGINT as a string, so every time goes through `num`. */
|
|
8
|
+
export interface JobRow {
|
|
9
|
+
name: string;
|
|
10
|
+
title: string;
|
|
11
|
+
process: string;
|
|
12
|
+
kind: 'scheduled' | 'reports_only';
|
|
13
|
+
schedule: string | null;
|
|
14
|
+
schedule_words: string;
|
|
15
|
+
rule: 'catch_up_once' | 'skip' | 'required';
|
|
16
|
+
stall_after_ms: number | string;
|
|
17
|
+
safe_to_repeat: number;
|
|
18
|
+
retries: number;
|
|
19
|
+
timeout_ms: number | string;
|
|
20
|
+
legal_duty: number;
|
|
21
|
+
enabled: number;
|
|
22
|
+
paused_by: string | null;
|
|
23
|
+
paused_at: number | string | null;
|
|
24
|
+
paused_note: string | null;
|
|
25
|
+
next_due_at: number | string | null;
|
|
26
|
+
lease_holder: string | null;
|
|
27
|
+
lease_token: string | null;
|
|
28
|
+
lease_until: number | string | null;
|
|
29
|
+
last_run_at: number | string | null;
|
|
30
|
+
last_outcome: string | null;
|
|
31
|
+
last_ms: number | string | null;
|
|
32
|
+
last_did: string | null;
|
|
33
|
+
tries: number;
|
|
34
|
+
retry_at: number | string | null;
|
|
35
|
+
failed_at: number | string | null;
|
|
36
|
+
failed_kind: string | null;
|
|
37
|
+
alerted_at: number | string | null;
|
|
38
|
+
hand_run_by: string | null;
|
|
39
|
+
hand_run_at: number | string | null;
|
|
40
|
+
stall_since: number | string | null;
|
|
41
|
+
stall_ref: string | null;
|
|
42
|
+
registered_at: number | string;
|
|
43
|
+
updated_at: number | string;
|
|
44
|
+
}
|
|
45
|
+
/** `<prefix>_runs` as the adapter answers it. */
|
|
46
|
+
export interface RunRow {
|
|
47
|
+
id: string;
|
|
48
|
+
job: string;
|
|
49
|
+
started_at: number | string;
|
|
50
|
+
ended_at: number | string | null;
|
|
51
|
+
outcome: RunOutcome;
|
|
52
|
+
ms: number | string | null;
|
|
53
|
+
tries: number;
|
|
54
|
+
kind: string | null;
|
|
55
|
+
did: string | null;
|
|
56
|
+
by_hand: string | null;
|
|
57
|
+
covers_from: number | string | null;
|
|
58
|
+
covers_to: number | string | null;
|
|
59
|
+
lease_token: string | null;
|
|
60
|
+
}
|
|
61
|
+
/** `<prefix>_missed` as the adapter answers it. */
|
|
62
|
+
export interface MissedRow {
|
|
63
|
+
id: string;
|
|
64
|
+
job: string;
|
|
65
|
+
due_at: number | string;
|
|
66
|
+
cause: 'worker_down' | 'paused';
|
|
67
|
+
action: 'caught_up' | 'skipped';
|
|
68
|
+
acted_at: number | string | null;
|
|
69
|
+
run_id: string | null;
|
|
70
|
+
}
|
|
71
|
+
/** A time column as a number, or null. */
|
|
72
|
+
export declare const num: (value: number | string | null | undefined) => number | null;
|
|
73
|
+
/** The stored schedule, or null on a reports-only row. */
|
|
74
|
+
export declare function scheduleOf(row: Pick<JobRow, 'schedule'>): Schedule | null;
|
|
75
|
+
/** The state word (`JobState`), in the order the page sorts by: failed, stalled, paused, running, missed, no run, ok. */
|
|
76
|
+
export declare function stateOf(row: JobRow, nowMs: number, missed24h: number): JobState;
|
|
77
|
+
/**
|
|
78
|
+
* A job's row as `get` and `list` answer it, with its missed count of the last 24 hours and the start of the run holding its
|
|
79
|
+
* lease. Between a claim and its run row there is no run to read: the lease's start is then the row's `updated_at`, which the
|
|
80
|
+
* claim writes as the run's start (a command in that gap writes a later time, never one ahead of now).
|
|
81
|
+
*/
|
|
82
|
+
export declare function viewOf(row: JobRow, nowMs: number, missed24h: number, runningSince: number | null): JobView;
|
|
83
|
+
/** A run's row as `runs` answers it. */
|
|
84
|
+
export declare function runViewOf(row: RunRow): RunView;
|
|
85
|
+
/** A missed due time's row as `missed` answers it. */
|
|
86
|
+
export declare function missedViewOf(row: MissedRow): MissedView;
|
|
87
|
+
//# sourceMappingURL=rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"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"}
|
package/dist/rows.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** A time column as a number, or null. */
|
|
2
|
+
export const num = (value) => (value === null || value === undefined ? null : Number(value));
|
|
3
|
+
const must = (value) => Number(value);
|
|
4
|
+
/** The stored schedule, or null on a reports-only row. */
|
|
5
|
+
export function scheduleOf(row) {
|
|
6
|
+
return row.schedule === null ? null : JSON.parse(row.schedule);
|
|
7
|
+
}
|
|
8
|
+
/** The state word (`JobState`), in the order the page sorts by: failed, stalled, paused, running, missed, no run, ok. */
|
|
9
|
+
export function stateOf(row, nowMs, missed24h) {
|
|
10
|
+
if (row.failed_at !== null)
|
|
11
|
+
return 'failed';
|
|
12
|
+
if (row.stall_since !== null)
|
|
13
|
+
return 'stalled';
|
|
14
|
+
if (row.paused_by !== null)
|
|
15
|
+
return 'paused';
|
|
16
|
+
if (row.lease_until !== null && must(row.lease_until) > nowMs)
|
|
17
|
+
return 'running';
|
|
18
|
+
if (missed24h > 0)
|
|
19
|
+
return 'missed';
|
|
20
|
+
if (row.last_run_at === null)
|
|
21
|
+
return 'no_run';
|
|
22
|
+
return 'ok';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A job's row as `get` and `list` answer it, with its missed count of the last 24 hours and the start of the run holding its
|
|
26
|
+
* lease. Between a claim and its run row there is no run to read: the lease's start is then the row's `updated_at`, which the
|
|
27
|
+
* claim writes as the run's start (a command in that gap writes a later time, never one ahead of now).
|
|
28
|
+
*/
|
|
29
|
+
export function viewOf(row, nowMs, missed24h, runningSince) {
|
|
30
|
+
const lease = row.lease_until !== null && must(row.lease_until) > nowMs && row.lease_holder !== null ? { holder: row.lease_holder, since: runningSince ?? Math.min(must(row.updated_at), nowMs) } : null;
|
|
31
|
+
return {
|
|
32
|
+
name: row.name, title: row.title, process: row.process, kind: row.kind, schedule: scheduleOf(row), scheduleWords: row.schedule_words, rule: row.rule,
|
|
33
|
+
stallAfterMs: must(row.stall_after_ms), safeToRepeat: row.safe_to_repeat === 1, retries: row.retries, legalDuty: row.legal_duty === 1,
|
|
34
|
+
state: stateOf(row, nowMs, missed24h),
|
|
35
|
+
paused: row.paused_by === null ? null : { by: row.paused_by, at: must(row.paused_at), note: row.paused_note },
|
|
36
|
+
lastRun: row.last_run_at === null ? null : { at: must(row.last_run_at), outcome: (row.last_outcome ?? 'ok'), ms: num(row.last_ms), did: row.last_did },
|
|
37
|
+
nextDueAt: num(row.next_due_at), lease,
|
|
38
|
+
failed: row.failed_at === null ? null : { tries: row.tries, lastAt: must(row.failed_at), kind: row.failed_kind ?? 'error', alertedAt: num(row.alerted_at) },
|
|
39
|
+
retrying: row.failed_at === null && row.retry_at !== null ? { tries: row.tries, kind: row.failed_kind ?? 'error', nextAt: must(row.retry_at) } : null,
|
|
40
|
+
handRun: row.hand_run_at === null ? null : { by: row.hand_run_by ?? 'system', at: must(row.hand_run_at) },
|
|
41
|
+
missed24h,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** A run's row as `runs` answers it. */
|
|
45
|
+
export function runViewOf(row) {
|
|
46
|
+
return { id: row.id, job: row.job, startedAt: must(row.started_at), endedAt: num(row.ended_at), outcome: row.outcome, ms: num(row.ms), tries: row.tries, kind: row.kind, did: row.did, byHand: row.by_hand };
|
|
47
|
+
}
|
|
48
|
+
/** A missed due time's row as `missed` answers it. */
|
|
49
|
+
export function missedViewOf(row) {
|
|
50
|
+
return { dueAt: must(row.due_at), cause: row.cause, action: row.action, actedAt: num(row.acted_at), runId: row.run_id };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=rows.js.map
|