@connextar/house 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +590 -0
- package/dist/jobs/counts.d.ts +82 -0
- package/dist/jobs/counts.d.ts.map +1 -0
- package/dist/jobs/counts.js +112 -0
- package/dist/jobs/counts.js.map +1 -0
- package/dist/jobs/cron-auth.d.ts +44 -0
- package/dist/jobs/cron-auth.d.ts.map +1 -0
- package/dist/jobs/cron-auth.js +20 -0
- package/dist/jobs/cron-auth.js.map +1 -0
- package/dist/jobs/health.d.ts +170 -0
- package/dist/jobs/health.d.ts.map +1 -0
- package/dist/jobs/health.js +149 -0
- package/dist/jobs/health.js.map +1 -0
- package/dist/jobs/index.d.ts +27 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +27 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/job-health-list.d.ts +78 -0
- package/dist/jobs/job-health-list.d.ts.map +1 -0
- package/dist/jobs/job-health-list.js +119 -0
- package/dist/jobs/job-health-list.js.map +1 -0
- package/dist/jobs/prune.d.ts +45 -0
- package/dist/jobs/prune.d.ts.map +1 -0
- package/dist/jobs/prune.js +58 -0
- package/dist/jobs/prune.js.map +1 -0
- package/dist/jobs/react-index.d.ts +19 -0
- package/dist/jobs/react-index.d.ts.map +1 -0
- package/dist/jobs/react-index.js +18 -0
- package/dist/jobs/react-index.js.map +1 -0
- package/dist/jobs/registry.d.ts +119 -0
- package/dist/jobs/registry.d.ts.map +1 -0
- package/dist/jobs/registry.js +87 -0
- package/dist/jobs/registry.js.map +1 -0
- package/dist/jobs/scheduler.d.ts +100 -0
- package/dist/jobs/scheduler.d.ts.map +1 -0
- package/dist/jobs/scheduler.js +297 -0
- package/dist/jobs/scheduler.js.map +1 -0
- package/dist/jobs/store.d.ts +119 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +87 -0
- package/dist/jobs/store.js.map +1 -0
- package/dist/jobs/windows.d.ts +66 -0
- package/dist/jobs/windows.d.ts.map +1 -0
- package/dist/jobs/windows.js +130 -0
- package/dist/jobs/windows.js.map +1 -0
- package/package.json +9 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const SCOPE_ORDER = ["tenant", "platform"];
|
|
2
|
+
/**
|
|
3
|
+
* The scopes a rule offers, narrowest first, each once: `["tenant"]`,
|
|
4
|
+
* `["platform"]` or `["tenant", "platform"]`. Nothing when Run now is off.
|
|
5
|
+
*/
|
|
6
|
+
export function runNowScopes(rule) {
|
|
7
|
+
if (!rule)
|
|
8
|
+
return [];
|
|
9
|
+
const declared = Array.isArray(rule.scope) ? rule.scope : [rule.scope];
|
|
10
|
+
return SCOPE_ORDER.filter((scope) => declared.includes(scope));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A job's definition, checked against its own counts at compile time: `run`
|
|
14
|
+
* must report every count `counts` declares, and nothing it does not — a
|
|
15
|
+
* number with no declaration has no words to be shown in. (At run time an
|
|
16
|
+
* undeclared key is dropped before it is stored, so it cannot reach a screen
|
|
17
|
+
* either way.)
|
|
18
|
+
*/
|
|
19
|
+
export function defineJob(job) {
|
|
20
|
+
return job;
|
|
21
|
+
}
|
|
22
|
+
export const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
23
|
+
export const DEFAULT_COOLDOWN_MS = 10 * 60 * 1000;
|
|
24
|
+
export const MAX_JOB_ID = 60;
|
|
25
|
+
export const MAX_WINDOW_KEY = 120;
|
|
26
|
+
const ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
27
|
+
const positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
28
|
+
/**
|
|
29
|
+
* Every way a list of jobs is wrong, as sentences — the same shape as
|
|
30
|
+
* `releaseHistoryProblems`, so a test can assert it is empty and read the
|
|
31
|
+
* failure when it is not.
|
|
32
|
+
*/
|
|
33
|
+
export function jobDefinitionProblems(jobs) {
|
|
34
|
+
const problems = [];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const job of jobs) {
|
|
37
|
+
const name = job.id || "(a job with no id)";
|
|
38
|
+
if (!ID.test(job.id) || job.id.length > MAX_JOB_ID) {
|
|
39
|
+
problems.push(`"${name}" is not a job id: lower-case letters and digits, "-" between words, ${MAX_JOB_ID} at most.`);
|
|
40
|
+
}
|
|
41
|
+
if (seen.has(job.id))
|
|
42
|
+
problems.push(`"${name}" is declared twice.`);
|
|
43
|
+
seen.add(job.id);
|
|
44
|
+
if (!job.label?.trim())
|
|
45
|
+
problems.push(`"${name}" has no label.`);
|
|
46
|
+
if (!job.cadence?.trim())
|
|
47
|
+
problems.push(`"${name}" has no cadence — say when it runs, in words.`);
|
|
48
|
+
if ((job.schedule ?? "tick") === "tick" && typeof job.window !== "function") {
|
|
49
|
+
problems.push(`"${name}" runs from the tick but has no window, so it could never be due.`);
|
|
50
|
+
}
|
|
51
|
+
if (job.staleAfterMs !== null && !positive(job.staleAfterMs)) {
|
|
52
|
+
problems.push(`"${name}" needs a staleness threshold in milliseconds, or null for a job that runs only on demand.`);
|
|
53
|
+
}
|
|
54
|
+
if (job.timeoutMs !== undefined && !positive(job.timeoutMs))
|
|
55
|
+
problems.push(`"${name}" has a timeout that is not positive.`);
|
|
56
|
+
if (job.runNow) {
|
|
57
|
+
const declared = Array.isArray(job.runNow.scope) ? job.runNow.scope : [job.runNow.scope];
|
|
58
|
+
if (declared.length === 0 || declared.some((scope) => scope !== "tenant" && scope !== "platform")) {
|
|
59
|
+
problems.push(`"${name}" offers Run now without saying over whom — "tenant" or "platform".`);
|
|
60
|
+
}
|
|
61
|
+
else if (new Set(declared).size !== declared.length) {
|
|
62
|
+
problems.push(`"${name}" offers Run now over the same scope twice.`);
|
|
63
|
+
}
|
|
64
|
+
const cooldown = job.runNow.cooldownMs;
|
|
65
|
+
if (cooldown !== undefined && !(Number.isFinite(cooldown) && cooldown >= 0)) {
|
|
66
|
+
problems.push(`"${name}" has a Run now cool-down that is not zero or more.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const [key, count] of Object.entries(job.counts ?? {})) {
|
|
70
|
+
const words = typeof count.label === "string" ? count.label : `${count.label?.one ?? ""}${count.label?.other ?? ""}`;
|
|
71
|
+
if (!words.trim())
|
|
72
|
+
problems.push(`"${name}" reports "${key}" with no label, which would show as a bare key.`);
|
|
73
|
+
if (count.kind === "money" && !/^[A-Z]{3}$/.test(count.currency ?? "")) {
|
|
74
|
+
problems.push(`"${name}" reports "${key}" as money with no ISO currency code.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return problems;
|
|
79
|
+
}
|
|
80
|
+
/** The app's job list, checked once, when the module that declares it loads. Throws every problem at once. */
|
|
81
|
+
export function defineJobs(jobs) {
|
|
82
|
+
const problems = jobDefinitionProblems(jobs);
|
|
83
|
+
if (problems.length)
|
|
84
|
+
throw new Error(`The scheduled jobs are misdeclared:\n- ${problems.join("\n- ")}`);
|
|
85
|
+
return Object.freeze([...jobs]);
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/jobs/registry.ts"],"names":[],"mappings":"AAyCA,MAAM,WAAW,GAA2B,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AAEnE;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAoC;IAC/D,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,QAAQ,GAAuB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3F,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACjE,CAAC;AAyDD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,GAAkG;IAElG,OAAO,GAAwB,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAClD,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAElC,MAAM,EAAE,GAAG,4BAA4B,CAAC;AAExC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAEtG;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAuB;IAC3D,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,IAAI,oBAAoB,CAAC;QAC5C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;YACnD,QAAQ,CAAC,IAAI,CACX,IAAI,IAAI,wEAAwE,UAAU,WAAW,CACtG,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,sBAAsB,CAAC,CAAC;QACpE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,iBAAiB,CAAC,CAAC;QACjE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,gDAAgD,CAAC,CAAC;QAClG,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC5E,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,mEAAmE,CAAC,CAAC;QAC7F,CAAC;QACD,IAAI,GAAG,CAAC,YAAY,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7D,QAAQ,CAAC,IAAI,CACX,IAAI,IAAI,4FAA4F,CACrG,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;YACzD,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,uCAAuC,CAAC,CAAC;QACjE,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,MAAM,QAAQ,GAAuB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC7G,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,UAAU,CAAC,EAAE,CAAC;gBAClG,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,qEAAqE,CAAC,CAAC;YAC/F,CAAC;iBAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACtD,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,6CAA6C,CAAC,CAAC;YACvE,CAAC;YACD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;YACvC,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC5E,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,qDAAqD,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;YAC5D,MAAM,KAAK,GACT,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC;YACzG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;gBAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,cAAc,GAAG,kDAAkD,CAAC,CAAC;YAC9G,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;gBACvE,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,cAAc,GAAG,uCAAuC,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,UAAU,CAAC,IAAuB;IAChD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,QAAQ,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACxG,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type CountFormatOptions, type CountLine } from "./counts.js";
|
|
2
|
+
import { type JobAudience, type JobView } from "./health.js";
|
|
3
|
+
import { type AnyJob, type JobActor, type RunNowScope } from "./registry.js";
|
|
4
|
+
import type { JobStore } from "./store.js";
|
|
5
|
+
/** What the tick did with one job. */
|
|
6
|
+
export type TickStatus = "ran" | "failed" | "already-ran" | "not-due" | "paused";
|
|
7
|
+
export interface TickResult {
|
|
8
|
+
job: string;
|
|
9
|
+
status: TickStatus;
|
|
10
|
+
windowKey?: string;
|
|
11
|
+
counts?: Record<string, number>;
|
|
12
|
+
error?: string;
|
|
13
|
+
/** One sentence, for the route's answer and the log. */
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
export type RunNowRefusal = "unknown" | "not-runnable" | "out-of-scope" | "paused" | "running" | "too-soon";
|
|
17
|
+
/** `scope` on a run says what it swept — the app's audit entry says "for every organisation" or not by it. */
|
|
18
|
+
export type RunNowOutcome = {
|
|
19
|
+
status: "ran";
|
|
20
|
+
job: string;
|
|
21
|
+
scope: RunNowScope;
|
|
22
|
+
windowKey: string;
|
|
23
|
+
counts: CountLine[];
|
|
24
|
+
message: string;
|
|
25
|
+
} | {
|
|
26
|
+
status: "failed";
|
|
27
|
+
job: string;
|
|
28
|
+
scope: RunNowScope;
|
|
29
|
+
windowKey: string;
|
|
30
|
+
error: string;
|
|
31
|
+
message: string;
|
|
32
|
+
} | {
|
|
33
|
+
status: "refused";
|
|
34
|
+
job: string;
|
|
35
|
+
reason: RunNowRefusal;
|
|
36
|
+
message: string;
|
|
37
|
+
};
|
|
38
|
+
export type SwitchRefusal = "unknown" | "not-switchable" | "out-of-scope";
|
|
39
|
+
export type SwitchOutcome = {
|
|
40
|
+
status: "switched" | "unchanged";
|
|
41
|
+
job: string;
|
|
42
|
+
paused: boolean;
|
|
43
|
+
message: string;
|
|
44
|
+
} | {
|
|
45
|
+
status: "refused";
|
|
46
|
+
job: string;
|
|
47
|
+
reason: SwitchRefusal;
|
|
48
|
+
message: string;
|
|
49
|
+
};
|
|
50
|
+
/** Who pressed Run now, and what they may reach. The app has already checked they are signed in and may use the screen. */
|
|
51
|
+
export interface RunNowCaller extends JobAudience {
|
|
52
|
+
actor: JobActor;
|
|
53
|
+
/**
|
|
54
|
+
* Which Run now was pressed, on a job that offers both — the list hands it
|
|
55
|
+
* to `onRunNow`. Left out, a job with one scope runs over that one (as in
|
|
56
|
+
* 0.11.0), and a job with both runs over the caller's own tenant when they
|
|
57
|
+
* have one, and over every tenant when they do not. It is a request, never
|
|
58
|
+
* an authority: `platform` and `tenantId` still decide whether it is allowed.
|
|
59
|
+
*/
|
|
60
|
+
scope?: RunNowScope;
|
|
61
|
+
}
|
|
62
|
+
export interface SwitchCaller {
|
|
63
|
+
actor: JobActor;
|
|
64
|
+
/** Switching a job off stops it for every tenant, so it takes platform authority. */
|
|
65
|
+
platform: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface SchedulerOptions extends CountFormatOptions {
|
|
68
|
+
jobs: readonly AnyJob[];
|
|
69
|
+
store: JobStore;
|
|
70
|
+
/** How long a run may stay unfinished before it reads as failed, for jobs that do not say. Default 15 minutes. */
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
/** The clock. Tests pass one; everything else leaves it. */
|
|
73
|
+
clock?: () => Date;
|
|
74
|
+
}
|
|
75
|
+
export interface Scheduler {
|
|
76
|
+
readonly jobs: readonly AnyJob[];
|
|
77
|
+
/** The tick route's whole body: every `tick` job whose window is open, once per window, one failure isolated. */
|
|
78
|
+
tick(now?: Date): Promise<TickResult[]>;
|
|
79
|
+
/** An own-schedule job's route: the switch, its window if it has one, the ledger. */
|
|
80
|
+
runOwn(jobId: string, now?: Date): Promise<TickResult>;
|
|
81
|
+
/** Run now — refused unless the job offers it, over a scope the caller holds. */
|
|
82
|
+
runNow(jobId: string, caller: RunNowCaller, now?: Date): Promise<RunNowOutcome>;
|
|
83
|
+
/** Switch a job off or on, recording who and when. */
|
|
84
|
+
setPaused(jobId: string, paused: boolean, caller: SwitchCaller, now?: Date): Promise<SwitchOutcome>;
|
|
85
|
+
/** Every job as the health screen draws it, for this viewer. */
|
|
86
|
+
views(audience: JobAudience, now?: Date): Promise<JobView[]>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The dispatcher, and everything else that touches the ledger.
|
|
90
|
+
*
|
|
91
|
+
* Declared once in the app beside its jobs and its store adapter; the tick
|
|
92
|
+
* route, the own-schedule routes, the Run now and switch routes and the health
|
|
93
|
+
* page all call this one object.
|
|
94
|
+
*/
|
|
95
|
+
export declare function createScheduler(options: SchedulerOptions): Scheduler;
|
|
96
|
+
/** The tick in one line: "2 ran, 1 failed, 1 paused, 5 not due". */
|
|
97
|
+
export declare function summariseTick(results: readonly TickResult[]): string;
|
|
98
|
+
/** The HTTP status a refusal maps to, so every app answers the same refusal the same way. */
|
|
99
|
+
export declare function refusalStatus(reason: RunNowRefusal | SwitchRefusal): 403 | 404 | 409 | 429;
|
|
100
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/jobs/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { errorMessage } from "../ux/errors.js";
|
|
2
|
+
import { countLines, countsSummary, declaredCounts } from "./counts.js";
|
|
3
|
+
import { buildJobView, effectiveTimeout, formatWhen } from "./health.js";
|
|
4
|
+
import { DEFAULT_COOLDOWN_MS, DEFAULT_TIMEOUT_MS, MAX_WINDOW_KEY, runNowScopes, } from "./registry.js";
|
|
5
|
+
import { MANUAL_KEY_PREFIX } from "./windows.js";
|
|
6
|
+
const MAX_ERROR = 1000;
|
|
7
|
+
function messageOf(error) {
|
|
8
|
+
const message = errorMessage(error, "It failed without saying why.");
|
|
9
|
+
return message.length > MAX_ERROR ? `${message.slice(0, MAX_ERROR - 1)}…` : message;
|
|
10
|
+
}
|
|
11
|
+
const minutes = (ms) => {
|
|
12
|
+
const n = Math.max(1, Math.round(ms / 60_000));
|
|
13
|
+
return `${n} minute${n === 1 ? "" : "s"}`;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The dispatcher, and everything else that touches the ledger.
|
|
17
|
+
*
|
|
18
|
+
* Declared once in the app beside its jobs and its store adapter; the tick
|
|
19
|
+
* route, the own-schedule routes, the Run now and switch routes and the health
|
|
20
|
+
* page all call this one object.
|
|
21
|
+
*/
|
|
22
|
+
export function createScheduler(options) {
|
|
23
|
+
const { jobs, store } = options;
|
|
24
|
+
const dual = jobs.filter((job) => runNowScopes(job.runNow).length > 1).map((job) => `"${job.id}"`);
|
|
25
|
+
if (dual.length && typeof store.latestUnfinishedRun !== "function") {
|
|
26
|
+
throw new Error(`${dual.join(", ")} ${dual.length === 1 ? "offers" : "offer"} Run now over both scopes, which needs ` +
|
|
27
|
+
"JobStore.latestUnfinishedRun: a run over every tenant must not start while one tenant's is in flight.");
|
|
28
|
+
}
|
|
29
|
+
const clock = options.clock ?? (() => new Date());
|
|
30
|
+
const fallbackTimeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31
|
+
const format = { locale: options.locale };
|
|
32
|
+
const find = (id) => jobs.find((job) => job.id === id);
|
|
33
|
+
/** Run a claimed job and record the outcome. Never throws. */
|
|
34
|
+
async function execute(job, runId, context) {
|
|
35
|
+
let counts = null;
|
|
36
|
+
let error = null;
|
|
37
|
+
try {
|
|
38
|
+
counts = declaredCounts(job.counts, await job.run(context));
|
|
39
|
+
}
|
|
40
|
+
catch (thrown) {
|
|
41
|
+
error = messageOf(thrown);
|
|
42
|
+
}
|
|
43
|
+
let unrecorded = null;
|
|
44
|
+
try {
|
|
45
|
+
await store.finishRun(runId, { finishedAt: clock(), ok: error === null, counts, error });
|
|
46
|
+
}
|
|
47
|
+
catch (thrown) {
|
|
48
|
+
// The work is done (or failed) either way; the ledger just doesn't know.
|
|
49
|
+
// It will read as "never finished" once the timeout passes, which is true.
|
|
50
|
+
unrecorded = messageOf(thrown);
|
|
51
|
+
}
|
|
52
|
+
return { counts, error, unrecorded };
|
|
53
|
+
}
|
|
54
|
+
/** The switch, the window, the claim and the run — for one job, with nothing allowed to escape. */
|
|
55
|
+
async function dispatch(job, now, switches, windowKeyFor) {
|
|
56
|
+
const base = { job: job.id };
|
|
57
|
+
try {
|
|
58
|
+
if (job.switchable ?? true) {
|
|
59
|
+
if (switches instanceof Error) {
|
|
60
|
+
return {
|
|
61
|
+
...base,
|
|
62
|
+
status: "failed",
|
|
63
|
+
error: switches.message,
|
|
64
|
+
message: `${job.label} didn't start: whether it is switched on couldn't be read (${switches.message}).`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const state = switches.get(job.id);
|
|
68
|
+
if (state?.paused) {
|
|
69
|
+
// Its window is not claimed, so switched back on inside the same window it still runs.
|
|
70
|
+
const who = state.by ? ` by ${state.by}` : "";
|
|
71
|
+
const when = state.at ? ` on ${formatWhen(state.at.toISOString())}` : "";
|
|
72
|
+
return { ...base, status: "paused", message: `${job.label} is switched off${who}${when}; not started.` };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const windowKey = windowKeyFor(now);
|
|
76
|
+
if (windowKey === null)
|
|
77
|
+
return { ...base, status: "not-due", message: `${job.label} is not due.` };
|
|
78
|
+
if (windowKey.length > MAX_WINDOW_KEY) {
|
|
79
|
+
throw new Error(`Its window key is longer than ${MAX_WINDOW_KEY} characters.`);
|
|
80
|
+
}
|
|
81
|
+
const runId = await store.claimRun({ job: job.id, windowKey, tenantId: null, by: null, startedAt: now });
|
|
82
|
+
if (runId === null) {
|
|
83
|
+
return { ...base, windowKey, status: "already-ran", message: `${job.label} has already run in ${windowKey}.` };
|
|
84
|
+
}
|
|
85
|
+
const context = { now, trigger: "schedule", tenantId: null, force: false, windowKey, by: null };
|
|
86
|
+
const { counts, error, unrecorded } = await execute(job, runId, context);
|
|
87
|
+
if (error !== null) {
|
|
88
|
+
return { ...base, windowKey, status: "failed", error, message: `${job.label} failed: ${error}` };
|
|
89
|
+
}
|
|
90
|
+
const summary = countsSummary(countLines(job.counts, counts, format));
|
|
91
|
+
return {
|
|
92
|
+
...base,
|
|
93
|
+
windowKey,
|
|
94
|
+
status: "ran",
|
|
95
|
+
counts: counts ?? {},
|
|
96
|
+
...(unrecorded ? { error: unrecorded } : {}),
|
|
97
|
+
message: unrecorded
|
|
98
|
+
? `${job.label} ran, but its result couldn't be recorded: ${unrecorded}`
|
|
99
|
+
: `${job.label} ran${summary ? `: ${summary}` : ""}.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (thrown) {
|
|
103
|
+
// A store that failed to claim, a window function that threw: this job's
|
|
104
|
+
// problem, reported as this job's result. The next job still runs.
|
|
105
|
+
const error = messageOf(thrown);
|
|
106
|
+
return { ...base, status: "failed", error, message: `${job.label} didn't run: ${error}` };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function readSwitches() {
|
|
110
|
+
try {
|
|
111
|
+
return await store.readSwitches();
|
|
112
|
+
}
|
|
113
|
+
catch (thrown) {
|
|
114
|
+
return new Error(messageOf(thrown));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
jobs,
|
|
119
|
+
async tick(now = clock()) {
|
|
120
|
+
const switches = await readSwitches();
|
|
121
|
+
const results = [];
|
|
122
|
+
// One at a time, in declaration order: predictable load, and the order the card lists them in.
|
|
123
|
+
for (const job of jobs) {
|
|
124
|
+
if ((job.schedule ?? "tick") !== "tick")
|
|
125
|
+
continue;
|
|
126
|
+
results.push(await dispatch(job, now, switches, (at) => job.window(at)));
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
},
|
|
130
|
+
async runOwn(jobId, now = clock()) {
|
|
131
|
+
const job = find(jobId);
|
|
132
|
+
if (!job || job.schedule !== "own") {
|
|
133
|
+
throw new Error(`"${jobId}" is not a job with its own schedule.`);
|
|
134
|
+
}
|
|
135
|
+
return dispatch(job, now, await readSwitches(), (at) => job.window ? job.window(at) : `run:${at.toISOString()}`);
|
|
136
|
+
},
|
|
137
|
+
async runNow(jobId, caller, now = clock()) {
|
|
138
|
+
const job = find(jobId);
|
|
139
|
+
if (!job)
|
|
140
|
+
return { status: "refused", job: jobId, reason: "unknown", message: "There is no scheduled job by that name." };
|
|
141
|
+
const refuse = (reason, message) => ({
|
|
142
|
+
status: "refused",
|
|
143
|
+
job: job.id,
|
|
144
|
+
reason,
|
|
145
|
+
message,
|
|
146
|
+
});
|
|
147
|
+
const rule = job.runNow;
|
|
148
|
+
const offered = runNowScopes(rule);
|
|
149
|
+
if (!rule || offered.length === 0)
|
|
150
|
+
return refuse("not-runnable", `${job.label} runs on its schedule only.`);
|
|
151
|
+
const scope = caller.scope ?? (offered.length === 1 ? offered[0] : caller.tenantId ? "tenant" : "platform");
|
|
152
|
+
if (!offered.includes(scope)) {
|
|
153
|
+
return refuse("out-of-scope", scope === "platform"
|
|
154
|
+
? `${job.label} can only be run for one organisation at a time, not across every account.`
|
|
155
|
+
: `${job.label} runs across every account only; it can't be run for one organisation.`);
|
|
156
|
+
}
|
|
157
|
+
if (scope === "tenant" && !caller.tenantId) {
|
|
158
|
+
return refuse("out-of-scope", `${job.label} can only be run for your own organisation, and you aren't in one.`);
|
|
159
|
+
}
|
|
160
|
+
if (scope === "platform" && !caller.platform) {
|
|
161
|
+
return refuse("out-of-scope", `${job.label} runs across every account, so only a platform administrator can run it.`);
|
|
162
|
+
}
|
|
163
|
+
const tenantId = scope === "tenant" ? caller.tenantId : null;
|
|
164
|
+
if ((job.switchable ?? true) && (await store.readSwitches()).get(job.id)?.paused) {
|
|
165
|
+
return refuse("paused", `${job.label} is switched off. Turn it back on to run it.`);
|
|
166
|
+
}
|
|
167
|
+
// Nothing in flight that this run could overlap: a sweep over every tenant (the schedule's, or a platform
|
|
168
|
+
// Run now) covers this tenant too; and on a job with both scopes, a platform run covers every tenant's.
|
|
169
|
+
const latest = tenantId === null && offered.length > 1
|
|
170
|
+
? await store.latestUnfinishedRun(job.id)
|
|
171
|
+
: await store.latestRun({ job: job.id, tenants: tenantId === null ? [null] : [null, tenantId] });
|
|
172
|
+
const timeout = effectiveTimeout(job, fallbackTimeout);
|
|
173
|
+
if (latest && !latest.finishedAt && now.getTime() - latest.startedAt.getTime() <= timeout) {
|
|
174
|
+
return refuse("running", `${job.label} is running now — it started ${formatWhen(latest.startedAt.toISOString())}.`);
|
|
175
|
+
}
|
|
176
|
+
const cooldown = rule.cooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
177
|
+
if (cooldown > 0) {
|
|
178
|
+
const last = await store.latestRun({ job: job.id, tenants: [tenantId], manualOnly: true });
|
|
179
|
+
const since = last ? now.getTime() - last.startedAt.getTime() : Infinity;
|
|
180
|
+
if (since < cooldown) {
|
|
181
|
+
return refuse("too-soon", `${job.label} was run ${minutes(since)} ago. It can be run again in ${minutes(cooldown - since)}.`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Two presses in the same breath share a key, so the index lets one through.
|
|
185
|
+
const at = cooldown > 0 ? new Date(Math.floor(now.getTime() / cooldown) * cooldown) : now;
|
|
186
|
+
const windowKey = `${MANUAL_KEY_PREFIX}${tenantId === null ? "platform" : `tenant:${tenantId}`}:${at.toISOString()}`;
|
|
187
|
+
if (windowKey.length > MAX_WINDOW_KEY)
|
|
188
|
+
throw new Error(`The Run now key for ${job.id} is too long to store.`);
|
|
189
|
+
const runId = await store.claimRun({ job: job.id, windowKey, tenantId, by: caller.actor.name, startedAt: now });
|
|
190
|
+
if (runId === null)
|
|
191
|
+
return refuse("too-soon", `${job.label} has just been run.`);
|
|
192
|
+
const context = { now, trigger: "manual", tenantId, force: true, windowKey, by: caller.actor };
|
|
193
|
+
const { counts, error } = await execute(job, runId, context);
|
|
194
|
+
if (error !== null) {
|
|
195
|
+
return { status: "failed", job: job.id, scope, windowKey, error, message: `${job.label} failed: ${error}` };
|
|
196
|
+
}
|
|
197
|
+
const lines = countLines(job.counts, counts, format);
|
|
198
|
+
const summary = countsSummary(lines);
|
|
199
|
+
return {
|
|
200
|
+
status: "ran",
|
|
201
|
+
job: job.id,
|
|
202
|
+
scope,
|
|
203
|
+
windowKey,
|
|
204
|
+
counts: lines,
|
|
205
|
+
message: `Ran ${job.label}${summary ? ` — ${summary}` : ""}.`,
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
async setPaused(jobId, paused, caller, now = clock()) {
|
|
209
|
+
const job = find(jobId);
|
|
210
|
+
if (!job)
|
|
211
|
+
return { status: "refused", job: jobId, reason: "unknown", message: "There is no scheduled job by that name." };
|
|
212
|
+
if (!(job.switchable ?? true)) {
|
|
213
|
+
return {
|
|
214
|
+
status: "refused",
|
|
215
|
+
job: job.id,
|
|
216
|
+
reason: "not-switchable",
|
|
217
|
+
message: `${job.label} can't be switched off.`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (!caller.platform) {
|
|
221
|
+
return {
|
|
222
|
+
status: "refused",
|
|
223
|
+
job: job.id,
|
|
224
|
+
reason: "out-of-scope",
|
|
225
|
+
message: `Switching ${job.label} off stops it for every account, so only a platform administrator can.`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const current = (await store.readSwitches()).get(job.id)?.paused ?? false;
|
|
229
|
+
if (current === paused) {
|
|
230
|
+
return {
|
|
231
|
+
status: "unchanged",
|
|
232
|
+
job: job.id,
|
|
233
|
+
paused,
|
|
234
|
+
message: paused ? `${job.label} is already paused.` : `${job.label} is already on.`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
await store.writeSwitch({ job: job.id, label: job.label, paused, at: now, by: caller.actor });
|
|
238
|
+
return {
|
|
239
|
+
status: "switched",
|
|
240
|
+
job: job.id,
|
|
241
|
+
paused,
|
|
242
|
+
message: paused
|
|
243
|
+
? `${job.label} is paused. A run already under way finishes; no more start until you turn it back on.`
|
|
244
|
+
: `${job.label} is back on. It runs at its next scheduled time.`,
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
async views(audience, now = clock()) {
|
|
248
|
+
const switches = await store.readSwitches();
|
|
249
|
+
const tenants = audience.tenantId ? [null, audience.tenantId] : [null];
|
|
250
|
+
return Promise.all(jobs.map(async (job) => {
|
|
251
|
+
const [latest, lastWholeSuccess, lastVisibleSuccess] = await Promise.all([
|
|
252
|
+
store.latestRun({ job: job.id, tenants }),
|
|
253
|
+
store.latestRun({ job: job.id, tenants: [null], ok: true }),
|
|
254
|
+
tenants.length > 1 ? store.latestRun({ job: job.id, tenants, ok: true }) : null,
|
|
255
|
+
]);
|
|
256
|
+
return buildJobView(job, {
|
|
257
|
+
switch: switches.get(job.id) ?? null,
|
|
258
|
+
latest,
|
|
259
|
+
lastWholeSuccess,
|
|
260
|
+
lastVisibleSuccess: tenants.length > 1 ? lastVisibleSuccess : lastWholeSuccess,
|
|
261
|
+
}, audience, { now, timeoutMs: fallbackTimeout, locale: options.locale });
|
|
262
|
+
}));
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/** The tick in one line: "2 ran, 1 failed, 1 paused, 5 not due". */
|
|
267
|
+
export function summariseTick(results) {
|
|
268
|
+
const order = [
|
|
269
|
+
["ran", "ran"],
|
|
270
|
+
["failed", "failed"],
|
|
271
|
+
["paused", "paused"],
|
|
272
|
+
["already-ran", "already ran"],
|
|
273
|
+
["not-due", "not due"],
|
|
274
|
+
];
|
|
275
|
+
const parts = order.flatMap(([status, words]) => {
|
|
276
|
+
const n = results.filter((result) => result.status === status).length;
|
|
277
|
+
return n ? [`${n} ${words}`] : [];
|
|
278
|
+
});
|
|
279
|
+
return parts.length ? parts.join(", ") : "No scheduled jobs";
|
|
280
|
+
}
|
|
281
|
+
/** The HTTP status a refusal maps to, so every app answers the same refusal the same way. */
|
|
282
|
+
export function refusalStatus(reason) {
|
|
283
|
+
switch (reason) {
|
|
284
|
+
case "unknown":
|
|
285
|
+
return 404;
|
|
286
|
+
case "not-runnable":
|
|
287
|
+
case "not-switchable":
|
|
288
|
+
case "out-of-scope":
|
|
289
|
+
return 403;
|
|
290
|
+
case "paused":
|
|
291
|
+
case "running":
|
|
292
|
+
return 409;
|
|
293
|
+
case "too-soon":
|
|
294
|
+
return 429;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/jobs/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { JobActor } from "./registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* Where runs and switches are kept — the app's, behind this interface.
|
|
4
|
+
*
|
|
5
|
+
* The package owns no schema. Both apps already have a `cron_run` table with
|
|
6
|
+
* the same eight columns, and AltEd keeps its switches in a settings table it
|
|
7
|
+
* shares with the harvest's own screen; a package migration would fight both.
|
|
8
|
+
* The README carries a reference Prisma model and adapter that both existing
|
|
9
|
+
* tables map onto, adding two nullable columns and losing nothing.
|
|
10
|
+
*/
|
|
11
|
+
/** A ledger row as the app stores it. `counts` is whatever JSON is there; it is read through the job's declarations. */
|
|
12
|
+
export interface JobRunRecord {
|
|
13
|
+
id: string;
|
|
14
|
+
job: string;
|
|
15
|
+
windowKey: string;
|
|
16
|
+
/** `null`: a run over every tenant. Otherwise the one tenant a Run now swept. */
|
|
17
|
+
tenantId: string | null;
|
|
18
|
+
/** Who pressed Run now, by name; `null` for the schedule. */
|
|
19
|
+
by: string | null;
|
|
20
|
+
startedAt: Date;
|
|
21
|
+
finishedAt: Date | null;
|
|
22
|
+
ok: boolean;
|
|
23
|
+
counts: unknown;
|
|
24
|
+
error: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface JobRunClaim {
|
|
27
|
+
job: string;
|
|
28
|
+
windowKey: string;
|
|
29
|
+
tenantId: string | null;
|
|
30
|
+
by: string | null;
|
|
31
|
+
startedAt: Date;
|
|
32
|
+
}
|
|
33
|
+
export interface JobRunOutcome {
|
|
34
|
+
finishedAt: Date;
|
|
35
|
+
ok: boolean;
|
|
36
|
+
counts: Record<string, number> | null;
|
|
37
|
+
error: string | null;
|
|
38
|
+
}
|
|
39
|
+
/** "The most recent run of this job, among these tenants" — `null` in the list meaning runs over every tenant. */
|
|
40
|
+
export interface JobRunQuery {
|
|
41
|
+
job: string;
|
|
42
|
+
tenants: readonly (string | null)[];
|
|
43
|
+
/** Successful runs only. */
|
|
44
|
+
ok?: true;
|
|
45
|
+
/** Run now runs only (their key starts `manual:`). */
|
|
46
|
+
manualOnly?: true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Old runs to delete: finished ones that started before `startedBefore`,
|
|
50
|
+
* oldest first, at most `limit` of them — **never** one that is the latest
|
|
51
|
+
* run, or the latest successful run, of its job for its tenant (`null`, the
|
|
52
|
+
* runs over every tenant, counting as one tenant of its own). Health is read
|
|
53
|
+
* from exactly those rows, so a monthly job last run 120 days ago keeps its
|
|
54
|
+
* last run and its last success and does not turn into "never run".
|
|
55
|
+
*/
|
|
56
|
+
export interface JobRunPrune {
|
|
57
|
+
startedBefore: Date;
|
|
58
|
+
limit: number;
|
|
59
|
+
}
|
|
60
|
+
/** Whether a job is switched off, and the latest change. No entry means on. */
|
|
61
|
+
export interface JobSwitchState {
|
|
62
|
+
paused: boolean;
|
|
63
|
+
at: Date | null;
|
|
64
|
+
by: string | null;
|
|
65
|
+
}
|
|
66
|
+
export interface JobSwitchChange {
|
|
67
|
+
job: string;
|
|
68
|
+
label: string;
|
|
69
|
+
paused: boolean;
|
|
70
|
+
at: Date;
|
|
71
|
+
by: JobActor;
|
|
72
|
+
}
|
|
73
|
+
export interface JobStore {
|
|
74
|
+
/**
|
|
75
|
+
* Insert the run row, claiming `(job, windowKey)`. Resolve with its id, or
|
|
76
|
+
* with `null` when the pair already exists — the unique index refusing it is
|
|
77
|
+
* the idempotency, so that refusal is an answer, not an error. Anything else
|
|
78
|
+
* throws.
|
|
79
|
+
*/
|
|
80
|
+
claimRun(claim: JobRunClaim): Promise<string | null>;
|
|
81
|
+
finishRun(id: string, outcome: JobRunOutcome): Promise<void>;
|
|
82
|
+
latestRun(query: JobRunQuery): Promise<JobRunRecord | null>;
|
|
83
|
+
/**
|
|
84
|
+
* The most recently started run of this job **over any tenant** that has not
|
|
85
|
+
* finished, or `null`. Only a job whose Run now offers both scopes needs it —
|
|
86
|
+
* a platform run must not start while one tenant's run is in flight — and
|
|
87
|
+
* `createScheduler` refuses such a job on a store without it.
|
|
88
|
+
*/
|
|
89
|
+
latestUnfinishedRun?(job: string): Promise<JobRunRecord | null>;
|
|
90
|
+
/**
|
|
91
|
+
* Delete old runs by the rules of `JobRunPrune`, in one bounded statement, and
|
|
92
|
+
* resolve with how many were deleted. Only `pruneJobRuns` calls it — from the
|
|
93
|
+
* app's own housekeeping job — and it throws on a store without it.
|
|
94
|
+
*/
|
|
95
|
+
pruneRuns?(prune: JobRunPrune): Promise<number>;
|
|
96
|
+
readSwitches(): Promise<ReadonlyMap<string, JobSwitchState>>;
|
|
97
|
+
/**
|
|
98
|
+
* Record a switch. **Write the audit entry here too, in the same
|
|
99
|
+
* transaction:** the switch keeps only the latest change, and a job paused and
|
|
100
|
+
* forgotten — billing reminders, say — is found by asking who, and when.
|
|
101
|
+
*/
|
|
102
|
+
writeSwitch(change: JobSwitchChange): Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A stored switch value, read the way AltEd learnt to: a row that exists but
|
|
106
|
+
* does not read as `{ paused: boolean }` is **paused**. Somebody stored it, and a
|
|
107
|
+
* switch nobody can read should not send a job out spending.
|
|
108
|
+
*/
|
|
109
|
+
export declare function pausedFromStored(value: unknown): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The whole contract in memory — for tests, the app's included. It keeps the
|
|
112
|
+
* unique `(job, windowKey)` rule exactly as the database would.
|
|
113
|
+
*/
|
|
114
|
+
export declare function createMemoryJobStore(): Required<JobStore> & {
|
|
115
|
+
runs: JobRunRecord[];
|
|
116
|
+
switches: Map<string, JobSwitchState>;
|
|
117
|
+
switchLog: JobSwitchChange[];
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/jobs/store.ts"],"names":[],"mappings":"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"}
|