@connextar/house 0.10.0 → 0.11.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 +447 -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 +157 -0
- package/dist/jobs/health.d.ts.map +1 -0
- package/dist/jobs/health.js +146 -0
- package/dist/jobs/health.js.map +1 -0
- package/dist/jobs/index.d.ts +26 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +26 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/job-health-list.d.ts +66 -0
- package/dist/jobs/job-health-list.d.ts.map +1 -0
- package/dist/jobs/job-health-list.js +115 -0
- package/dist/jobs/job-health-list.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 +104 -0
- package/dist/jobs/registry.d.ts.map +1 -0
- package/dist/jobs/registry.js +72 -0
- package/dist/jobs/registry.js.map +1 -0
- package/dist/jobs/scheduler.d.ts +89 -0
- package/dist/jobs/scheduler.d.ts.map +1 -0
- package/dist/jobs/scheduler.js +282 -0
- package/dist/jobs/scheduler.js.map +1 -0
- package/dist/jobs/store.d.ts +94 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +56 -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,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CircleCheck, CirclePause, CircleX, Clock, LoaderCircle, Play, TriangleAlert, } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cx } from "../internal/cx.js";
|
|
6
|
+
import { useKeyedAction } from "../ux/use-action.js";
|
|
7
|
+
import { useOptimisticValue } from "../ux/use-optimistic-value.js";
|
|
8
|
+
import { countsSummary } from "./counts.js";
|
|
9
|
+
import { describeHealth, describeRun, describeSwitch, } from "./health.js";
|
|
10
|
+
/** The house look, on the shared theme tokens. */
|
|
11
|
+
export const JOB_HEALTH_CLASS_NAMES = {
|
|
12
|
+
list: "flex flex-col divide-y divide-border",
|
|
13
|
+
empty: "text-sm text-muted-foreground",
|
|
14
|
+
row: "flex flex-wrap items-start justify-between gap-3 py-3 first:pt-0 last:pb-0",
|
|
15
|
+
body: "flex min-w-0 flex-1 flex-col gap-1",
|
|
16
|
+
heading: "flex flex-wrap items-center gap-2",
|
|
17
|
+
label: "text-sm font-medium text-foreground",
|
|
18
|
+
badge: "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium [&_svg]:size-3",
|
|
19
|
+
badgeGood: "border-primary/30 bg-primary/10 text-primary",
|
|
20
|
+
badgeNeutral: "border-border text-muted-foreground",
|
|
21
|
+
badgeWarning: "border-destructive/40 text-destructive",
|
|
22
|
+
badgeBad: "border-destructive/40 bg-destructive/10 text-destructive",
|
|
23
|
+
meta: "text-xs text-muted-foreground",
|
|
24
|
+
detail: "text-xs text-muted-foreground",
|
|
25
|
+
error: "text-xs text-destructive",
|
|
26
|
+
counts: "text-xs text-muted-foreground tabular-nums",
|
|
27
|
+
switched: "text-xs text-muted-foreground",
|
|
28
|
+
actions: "flex shrink-0 items-center gap-3",
|
|
29
|
+
runNow: "inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:pointer-events-none disabled:opacity-60 [&_svg]:size-3.5",
|
|
30
|
+
switch: "relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
31
|
+
switchOn: "bg-primary",
|
|
32
|
+
switchOff: "bg-muted-foreground/30",
|
|
33
|
+
thumb: "pointer-events-none block size-4 rounded-full bg-card shadow transition-transform",
|
|
34
|
+
thumbOn: "translate-x-4",
|
|
35
|
+
thumbOff: "translate-x-0",
|
|
36
|
+
};
|
|
37
|
+
const ICONS = {
|
|
38
|
+
paused: CirclePause,
|
|
39
|
+
"never-run": Clock,
|
|
40
|
+
running: LoaderCircle,
|
|
41
|
+
failed: CircleX,
|
|
42
|
+
stale: TriangleAlert,
|
|
43
|
+
healthy: CircleCheck,
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The health screen's list: each job's schedule, last run, what it did in
|
|
47
|
+
* words, its health, and — for whoever may — its switch and Run now.
|
|
48
|
+
*
|
|
49
|
+
* The switch is an optimistic value: it moves on the click, the badge with it,
|
|
50
|
+
* and a refusal puts both back and says why. AltEd's switch did not move until
|
|
51
|
+
* the server answered, and greyed out every other switch on the screen while it
|
|
52
|
+
* waited.
|
|
53
|
+
*
|
|
54
|
+
* Draws plain elements on the theme tokens, like the wizard; there is still no
|
|
55
|
+
* `Button` in this package. The app wraps it in its own card and heading.
|
|
56
|
+
*/
|
|
57
|
+
export function JobHealthList({ jobs, onSwitch, onRunNow, onSettled, extra, emptyText = "No scheduled jobs.", className, classNames, locale, timeZone, }) {
|
|
58
|
+
const c = { ...JOB_HEALTH_CLASS_NAMES, ...classNames };
|
|
59
|
+
const actions = useKeyedAction();
|
|
60
|
+
if (jobs.length === 0)
|
|
61
|
+
return _jsx("p", { className: c.empty, children: emptyText });
|
|
62
|
+
return (_jsx("ul", { className: cx(c.list, className), children: jobs.map((job) => (_jsx(JobRow, { job: job, c: c, actions: actions, onSwitch: onSwitch, onRunNow: onRunNow, onSettled: onSettled, extra: extra, when: { locale, timeZone } }, job.id))) }));
|
|
63
|
+
}
|
|
64
|
+
function toneClass(c, words) {
|
|
65
|
+
switch (words.tone) {
|
|
66
|
+
case "good":
|
|
67
|
+
return c.badgeGood;
|
|
68
|
+
case "warning":
|
|
69
|
+
return c.badgeWarning;
|
|
70
|
+
case "bad":
|
|
71
|
+
return c.badgeBad;
|
|
72
|
+
default:
|
|
73
|
+
return c.badgeNeutral;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function JobRow({ job, c, actions, onSwitch, onRunNow, onSettled, extra, when, }) {
|
|
77
|
+
const on = useOptimisticValue(!job.paused, { onSettled });
|
|
78
|
+
// Health with the switch as the screen shows it, not as the server last said.
|
|
79
|
+
const health = on.value ? job.healthIfOn : { state: "paused" };
|
|
80
|
+
const words = describeHealth(health, when);
|
|
81
|
+
const Icon = ICONS[health.state];
|
|
82
|
+
const run = job.lastRun;
|
|
83
|
+
const success = job.lastSuccess;
|
|
84
|
+
const countsLine = run?.ok && run.counts.length
|
|
85
|
+
? countsSummary(run.counts)
|
|
86
|
+
: success && !run?.ok
|
|
87
|
+
? [describeRun(success, "Last success", when), countsSummary(success.counts)].filter(Boolean).join(": ")
|
|
88
|
+
: null;
|
|
89
|
+
const detail = health.state === "running" ? null : words.detail;
|
|
90
|
+
const switchLine = on.pending ? (on.value ? "Turning back on…" : "Pausing…") : describeSwitch(job, when);
|
|
91
|
+
const toggle = () => {
|
|
92
|
+
if (!onSwitch)
|
|
93
|
+
return;
|
|
94
|
+
const next = !on.value;
|
|
95
|
+
void on.set(next, async () => {
|
|
96
|
+
await onSwitch(job.id, next);
|
|
97
|
+
}, { error: next ? `Couldn't turn ${job.label} back on` : `Couldn't pause ${job.label}` });
|
|
98
|
+
};
|
|
99
|
+
const runNow = () => {
|
|
100
|
+
if (!onRunNow)
|
|
101
|
+
return;
|
|
102
|
+
void actions.run(job.id, () => onRunNow(job.id), {
|
|
103
|
+
success: (result) => (result && result.message) || `Ran ${job.label}.`,
|
|
104
|
+
error: `Couldn't run ${job.label}`,
|
|
105
|
+
// A failed run is on the ledger too, so the screen has something new to show either way.
|
|
106
|
+
onSuccess: () => onSettled?.(),
|
|
107
|
+
onError: () => onSettled?.(),
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
const running = actions.isPending(job.id);
|
|
111
|
+
const showSwitch = job.canSwitch && Boolean(onSwitch);
|
|
112
|
+
const showRunNow = job.runNow !== null && Boolean(onRunNow);
|
|
113
|
+
return (_jsxs("li", { className: c.row, "data-job": job.id, "data-health": health.state, children: [_jsxs("div", { className: c.body, children: [_jsxs("div", { className: c.heading, children: [_jsx("span", { className: c.label, children: job.label }), _jsxs("span", { className: cx(c.badge, toneClass(c, words)), children: [_jsx(Icon, { "aria-hidden": "true" }), words.label] })] }), _jsxs("p", { className: c.meta, children: [job.cadence, run ? ` · ${describeRun(run, "Last run", when)}` : ""] }), detail ? _jsx("p", { className: health.state === "failed" ? c.error : c.detail, children: detail }) : null, countsLine ? _jsx("p", { className: c.counts, children: countsLine }) : null, switchLine ? _jsx("p", { className: c.switched, children: switchLine }) : null, extra?.(job)] }), showSwitch || showRunNow ? (_jsxs("div", { className: c.actions, children: [showRunNow ? (_jsxs("button", { type: "button", className: c.runNow, onClick: runNow, disabled: running || !on.value, title: on.value ? undefined : "Switched off — turn it back on to run it", "data-busy": running ? "true" : undefined, "aria-label": `Run ${job.label} now`, children: [_jsx(Play, { "aria-hidden": "true" }), "Run now"] })) : null, showSwitch ? (_jsx("button", { type: "button", role: "switch", "aria-checked": on.value, "aria-label": job.label, "aria-busy": on.pending || undefined, className: cx(c.switch, on.value ? c.switchOn : c.switchOff), onClick: toggle, children: _jsx("span", { className: cx(c.thumb, on.value ? c.thumbOn : c.thumbOff) }) })) : null] })) : null] }));
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=job-health-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-health-list.js","sourceRoot":"","sources":["../../src/jobs/job-health-list.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/jobs/react` — the health screen's list, and the words it
|
|
3
|
+
* uses.
|
|
4
|
+
*
|
|
5
|
+
* Its own entry point because `@connextar/house/jobs` is server code: it
|
|
6
|
+
* checks the scheduler's secret with `node:crypto`, which no client bundle can
|
|
7
|
+
* load. Everything a client component needs — the list, the health and count
|
|
8
|
+
* wording, the view types — is here, and `entry-points.test.ts` follows the
|
|
9
|
+
* emitted imports to keep `node:` out of it.
|
|
10
|
+
*
|
|
11
|
+
* The switch and Run now go through `@connextar/house/ux` (`useOptimisticValue`,
|
|
12
|
+
* `useKeyedAction`), so a refusal is reported to the app's
|
|
13
|
+
* `ActionFeedbackProvider` and this module imports no toast library.
|
|
14
|
+
*/
|
|
15
|
+
export { countsSummary, describeSpan, formatDuration, formatMoney, type CountKind, type CountLine } from "./counts.js";
|
|
16
|
+
export { describeHealth, describeRun, describeSwitch, formatDay, formatWhen, type HealthWords, type JobHealth, type JobHealthState, type JobRunView, type JobView, type WhenOptions, } from "./health.js";
|
|
17
|
+
export { JOB_HEALTH_CLASS_NAMES, JobHealthList, type JobHealthClassNames, type JobHealthListProps, } from "./job-health-list.js";
|
|
18
|
+
export type { RunNowScope } from "./registry.js";
|
|
19
|
+
//# sourceMappingURL=react-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-index.d.ts","sourceRoot":"","sources":["../../src/jobs/react-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,EACL,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/jobs/react` — the health screen's list, and the words it
|
|
3
|
+
* uses.
|
|
4
|
+
*
|
|
5
|
+
* Its own entry point because `@connextar/house/jobs` is server code: it
|
|
6
|
+
* checks the scheduler's secret with `node:crypto`, which no client bundle can
|
|
7
|
+
* load. Everything a client component needs — the list, the health and count
|
|
8
|
+
* wording, the view types — is here, and `entry-points.test.ts` follows the
|
|
9
|
+
* emitted imports to keep `node:` out of it.
|
|
10
|
+
*
|
|
11
|
+
* The switch and Run now go through `@connextar/house/ux` (`useOptimisticValue`,
|
|
12
|
+
* `useKeyedAction`), so a refusal is reported to the app's
|
|
13
|
+
* `ActionFeedbackProvider` and this module imports no toast library.
|
|
14
|
+
*/
|
|
15
|
+
export { countsSummary, describeSpan, formatDuration, formatMoney } from "./counts.js";
|
|
16
|
+
export { describeHealth, describeRun, describeSwitch, formatDay, formatWhen, } from "./health.js";
|
|
17
|
+
export { JOB_HEALTH_CLASS_NAMES, JobHealthList, } from "./job-health-list.js";
|
|
18
|
+
//# sourceMappingURL=react-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-index.js","sourceRoot":"","sources":["../../src/jobs/react-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAkC,MAAM,aAAa,CAAC;AACvH,OAAO,EACL,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,UAAU,GAOX,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,aAAa,GAGd,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { CountDeclarations, CountValues } from "./counts.js";
|
|
2
|
+
import type { JobWindow } from "./windows.js";
|
|
3
|
+
/** Who did something: pressed Run now, switched a job off. Named, because the card says who. */
|
|
4
|
+
export interface JobActor {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Who a manual run may sweep.
|
|
10
|
+
*
|
|
11
|
+
* - `tenant` — only the caller's own tenant. Quickie's owners: every signup owns
|
|
12
|
+
* an organisation, so an unscoped Run now would let anyone fire every tenant's
|
|
13
|
+
* automations, emailing other businesses' customers.
|
|
14
|
+
* - `platform` — everything, which only someone with platform authority may ask
|
|
15
|
+
* for. AltEd's administrators: AltEd is single-tenant, so every job is a sweep
|
|
16
|
+
* over shared tables and there is no smaller slice to offer.
|
|
17
|
+
*/
|
|
18
|
+
export type RunNowScope = "tenant" | "platform";
|
|
19
|
+
export interface RunNowRule {
|
|
20
|
+
scope: RunNowScope;
|
|
21
|
+
/**
|
|
22
|
+
* How soon the same caller's scope may run it again. Default ten minutes. A
|
|
23
|
+
* manual run skips the job's hour gates, so without this a second press sends
|
|
24
|
+
* the whole batch again. `0` turns it off.
|
|
25
|
+
*/
|
|
26
|
+
cooldownMs?: number;
|
|
27
|
+
}
|
|
28
|
+
/** What a run is handed. */
|
|
29
|
+
export interface JobContext {
|
|
30
|
+
now: Date;
|
|
31
|
+
trigger: "schedule" | "manual";
|
|
32
|
+
/**
|
|
33
|
+
* `null`: sweep every tenant (the schedule, or a platform Run now). A string:
|
|
34
|
+
* sweep **only** this tenant — a tenant-scoped Run now always has one, and the
|
|
35
|
+
* job must pass it down to every query it makes.
|
|
36
|
+
*/
|
|
37
|
+
tenantId: string | null;
|
|
38
|
+
/** A manual run: skip the job's own hour gates. It is not a licence to skip a per-recipient throttle. */
|
|
39
|
+
force: boolean;
|
|
40
|
+
windowKey: string;
|
|
41
|
+
/** Who pressed Run now; `null` for the schedule. */
|
|
42
|
+
by: JobActor | null;
|
|
43
|
+
}
|
|
44
|
+
export interface JobDefinition<C extends CountDeclarations = CountDeclarations> {
|
|
45
|
+
/** Stable, and stored: the ledger's `job` column and the switch's key. Lower-case, `-` between words, ≤ 60. */
|
|
46
|
+
id: string;
|
|
47
|
+
/** What the card calls it: "Payment reminders". */
|
|
48
|
+
label: string;
|
|
49
|
+
/** When it runs, in words: "Daily, from 09:00". The window is the rule; this is how the card says it. */
|
|
50
|
+
cadence: string;
|
|
51
|
+
/**
|
|
52
|
+
* `tick` (the default): the dispatcher runs it from the app's one tick route.
|
|
53
|
+
* `own`: it has its own route and clock — AltEd's harvest every ten minutes,
|
|
54
|
+
* its import runner every minute — which calls `runOwn`. The tick never runs it.
|
|
55
|
+
*/
|
|
56
|
+
schedule?: "tick" | "own";
|
|
57
|
+
/** Which window a moment is in, or `null` when not due. Required for `tick`; optional for `own`. */
|
|
58
|
+
window?: JobWindow;
|
|
59
|
+
/**
|
|
60
|
+
* How long without a successful whole run before the card says **Stale**.
|
|
61
|
+
* `null` for a job that runs only when there is work — an import runner idle
|
|
62
|
+
* all week is not stale.
|
|
63
|
+
*/
|
|
64
|
+
staleAfterMs: number | null;
|
|
65
|
+
/** How long a run may go unfinished before the card calls it failed. Default 15 minutes. */
|
|
66
|
+
timeoutMs?: number;
|
|
67
|
+
/** Whether an administrator may switch it off. Default `true`. */
|
|
68
|
+
switchable?: boolean;
|
|
69
|
+
/** Whether Run now is offered, and over whom. Default: never. */
|
|
70
|
+
runNow?: false | RunNowRule;
|
|
71
|
+
/** Every number `run` reports, each with its words and kind. See `CountDeclaration`. */
|
|
72
|
+
counts: C;
|
|
73
|
+
run: (context: JobContext) => Promise<CountValues<C>>;
|
|
74
|
+
}
|
|
75
|
+
/** A job with its count types erased — how the registry holds a list of different ones. */
|
|
76
|
+
export type AnyJob = JobDefinition<CountDeclarations>;
|
|
77
|
+
/** `R`, with every key `C` does not declare forbidden. */
|
|
78
|
+
type OnlyDeclared<R, C> = R & {
|
|
79
|
+
[K in Exclude<keyof R, keyof C>]: never;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A job's definition, checked against its own counts at compile time: `run`
|
|
83
|
+
* must report every count `counts` declares, and nothing it does not — a
|
|
84
|
+
* number with no declaration has no words to be shown in. (At run time an
|
|
85
|
+
* undeclared key is dropped before it is stored, so it cannot reach a screen
|
|
86
|
+
* either way.)
|
|
87
|
+
*/
|
|
88
|
+
export declare function defineJob<const C extends CountDeclarations, R extends CountValues<C>>(job: Omit<JobDefinition<C>, "run"> & {
|
|
89
|
+
run: (context: JobContext) => Promise<OnlyDeclared<R, C>>;
|
|
90
|
+
}): AnyJob;
|
|
91
|
+
export declare const DEFAULT_TIMEOUT_MS: number;
|
|
92
|
+
export declare const DEFAULT_COOLDOWN_MS: number;
|
|
93
|
+
export declare const MAX_JOB_ID = 60;
|
|
94
|
+
export declare const MAX_WINDOW_KEY = 120;
|
|
95
|
+
/**
|
|
96
|
+
* Every way a list of jobs is wrong, as sentences — the same shape as
|
|
97
|
+
* `releaseHistoryProblems`, so a test can assert it is empty and read the
|
|
98
|
+
* failure when it is not.
|
|
99
|
+
*/
|
|
100
|
+
export declare function jobDefinitionProblems(jobs: readonly AnyJob[]): string[];
|
|
101
|
+
/** The app's job list, checked once, when the module that declares it loads. Throws every problem at once. */
|
|
102
|
+
export declare function defineJobs(jobs: readonly AnyJob[]): readonly AnyJob[];
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/jobs/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,gGAAgG;AAChG,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,4BAA4B;AAC5B,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yGAAyG;IACzG,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,EAAE,EAAE,QAAQ,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB;IAC5E,+GAA+G;IAC/G,EAAE,EAAE,MAAM,CAAC;IACX,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,yGAAyG;IACzG,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC1B,oGAAoG;IACpG,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iEAAiE;IACjE,MAAM,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC;IAC5B,wFAAwF;IACxF,MAAM,EAAE,CAAC,CAAC;IACV,GAAG,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CACvD;AAED,2FAA2F;AAC3F,MAAM,MAAM,MAAM,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC;AAEtD,0DAA0D;AAC1D,KAAK,YAAY,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;KAAG,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK;CAAE,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,CAAC,CAAC,SAAS,iBAAiB,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,EACnF,GAAG,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG;IAAE,GAAG,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;CAAE,GACjG,MAAM,CAER;AAED,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AACjD,eAAO,MAAM,mBAAmB,QAAiB,CAAC;AAClD,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,cAAc,MAAM,CAAC;AAMlC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CA2CvE;AAED,8GAA8G;AAC9G,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAIrE"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A job's definition, checked against its own counts at compile time: `run`
|
|
3
|
+
* must report every count `counts` declares, and nothing it does not — a
|
|
4
|
+
* number with no declaration has no words to be shown in. (At run time an
|
|
5
|
+
* undeclared key is dropped before it is stored, so it cannot reach a screen
|
|
6
|
+
* either way.)
|
|
7
|
+
*/
|
|
8
|
+
export function defineJob(job) {
|
|
9
|
+
return job;
|
|
10
|
+
}
|
|
11
|
+
export const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
12
|
+
export const DEFAULT_COOLDOWN_MS = 10 * 60 * 1000;
|
|
13
|
+
export const MAX_JOB_ID = 60;
|
|
14
|
+
export const MAX_WINDOW_KEY = 120;
|
|
15
|
+
const ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
16
|
+
const positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
17
|
+
/**
|
|
18
|
+
* Every way a list of jobs is wrong, as sentences — the same shape as
|
|
19
|
+
* `releaseHistoryProblems`, so a test can assert it is empty and read the
|
|
20
|
+
* failure when it is not.
|
|
21
|
+
*/
|
|
22
|
+
export function jobDefinitionProblems(jobs) {
|
|
23
|
+
const problems = [];
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const job of jobs) {
|
|
26
|
+
const name = job.id || "(a job with no id)";
|
|
27
|
+
if (!ID.test(job.id) || job.id.length > MAX_JOB_ID) {
|
|
28
|
+
problems.push(`"${name}" is not a job id: lower-case letters and digits, "-" between words, ${MAX_JOB_ID} at most.`);
|
|
29
|
+
}
|
|
30
|
+
if (seen.has(job.id))
|
|
31
|
+
problems.push(`"${name}" is declared twice.`);
|
|
32
|
+
seen.add(job.id);
|
|
33
|
+
if (!job.label?.trim())
|
|
34
|
+
problems.push(`"${name}" has no label.`);
|
|
35
|
+
if (!job.cadence?.trim())
|
|
36
|
+
problems.push(`"${name}" has no cadence — say when it runs, in words.`);
|
|
37
|
+
if ((job.schedule ?? "tick") === "tick" && typeof job.window !== "function") {
|
|
38
|
+
problems.push(`"${name}" runs from the tick but has no window, so it could never be due.`);
|
|
39
|
+
}
|
|
40
|
+
if (job.staleAfterMs !== null && !positive(job.staleAfterMs)) {
|
|
41
|
+
problems.push(`"${name}" needs a staleness threshold in milliseconds, or null for a job that runs only on demand.`);
|
|
42
|
+
}
|
|
43
|
+
if (job.timeoutMs !== undefined && !positive(job.timeoutMs))
|
|
44
|
+
problems.push(`"${name}" has a timeout that is not positive.`);
|
|
45
|
+
if (job.runNow) {
|
|
46
|
+
if (job.runNow.scope !== "tenant" && job.runNow.scope !== "platform") {
|
|
47
|
+
problems.push(`"${name}" offers Run now without saying over whom — "tenant" or "platform".`);
|
|
48
|
+
}
|
|
49
|
+
const cooldown = job.runNow.cooldownMs;
|
|
50
|
+
if (cooldown !== undefined && !(Number.isFinite(cooldown) && cooldown >= 0)) {
|
|
51
|
+
problems.push(`"${name}" has a Run now cool-down that is not zero or more.`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [key, count] of Object.entries(job.counts ?? {})) {
|
|
55
|
+
const words = typeof count.label === "string" ? count.label : `${count.label?.one ?? ""}${count.label?.other ?? ""}`;
|
|
56
|
+
if (!words.trim())
|
|
57
|
+
problems.push(`"${name}" reports "${key}" with no label, which would show as a bare key.`);
|
|
58
|
+
if (count.kind === "money" && !/^[A-Z]{3}$/.test(count.currency ?? "")) {
|
|
59
|
+
problems.push(`"${name}" reports "${key}" as money with no ISO currency code.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return problems;
|
|
64
|
+
}
|
|
65
|
+
/** The app's job list, checked once, when the module that declares it loads. Throws every problem at once. */
|
|
66
|
+
export function defineJobs(jobs) {
|
|
67
|
+
const problems = jobDefinitionProblems(jobs);
|
|
68
|
+
if (problems.length)
|
|
69
|
+
throw new Error(`The scheduled jobs are misdeclared:\n- ${problems.join("\n- ")}`);
|
|
70
|
+
return Object.freeze([...jobs]);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/jobs/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type CountFormatOptions, type CountLine } from "./counts.js";
|
|
2
|
+
import { type JobAudience, type JobView } from "./health.js";
|
|
3
|
+
import { type AnyJob, type JobActor } 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
|
+
export type RunNowOutcome = {
|
|
18
|
+
status: "ran";
|
|
19
|
+
job: string;
|
|
20
|
+
windowKey: string;
|
|
21
|
+
counts: CountLine[];
|
|
22
|
+
message: string;
|
|
23
|
+
} | {
|
|
24
|
+
status: "failed";
|
|
25
|
+
job: string;
|
|
26
|
+
windowKey: string;
|
|
27
|
+
error: string;
|
|
28
|
+
message: string;
|
|
29
|
+
} | {
|
|
30
|
+
status: "refused";
|
|
31
|
+
job: string;
|
|
32
|
+
reason: RunNowRefusal;
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
35
|
+
export type SwitchRefusal = "unknown" | "not-switchable" | "out-of-scope";
|
|
36
|
+
export type SwitchOutcome = {
|
|
37
|
+
status: "switched" | "unchanged";
|
|
38
|
+
job: string;
|
|
39
|
+
paused: boolean;
|
|
40
|
+
message: string;
|
|
41
|
+
} | {
|
|
42
|
+
status: "refused";
|
|
43
|
+
job: string;
|
|
44
|
+
reason: SwitchRefusal;
|
|
45
|
+
message: string;
|
|
46
|
+
};
|
|
47
|
+
/** Who pressed Run now, and what they may reach. The app has already checked they are signed in and may use the screen. */
|
|
48
|
+
export interface RunNowCaller extends JobAudience {
|
|
49
|
+
actor: JobActor;
|
|
50
|
+
}
|
|
51
|
+
export interface SwitchCaller {
|
|
52
|
+
actor: JobActor;
|
|
53
|
+
/** Switching a job off stops it for every tenant, so it takes platform authority. */
|
|
54
|
+
platform: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface SchedulerOptions extends CountFormatOptions {
|
|
57
|
+
jobs: readonly AnyJob[];
|
|
58
|
+
store: JobStore;
|
|
59
|
+
/** How long a run may stay unfinished before it reads as failed, for jobs that do not say. Default 15 minutes. */
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
/** The clock. Tests pass one; everything else leaves it. */
|
|
62
|
+
clock?: () => Date;
|
|
63
|
+
}
|
|
64
|
+
export interface Scheduler {
|
|
65
|
+
readonly jobs: readonly AnyJob[];
|
|
66
|
+
/** The tick route's whole body: every `tick` job whose window is open, once per window, one failure isolated. */
|
|
67
|
+
tick(now?: Date): Promise<TickResult[]>;
|
|
68
|
+
/** An own-schedule job's route: the switch, its window if it has one, the ledger. */
|
|
69
|
+
runOwn(jobId: string, now?: Date): Promise<TickResult>;
|
|
70
|
+
/** Run now — refused unless the job offers it, over a scope the caller holds. */
|
|
71
|
+
runNow(jobId: string, caller: RunNowCaller, now?: Date): Promise<RunNowOutcome>;
|
|
72
|
+
/** Switch a job off or on, recording who and when. */
|
|
73
|
+
setPaused(jobId: string, paused: boolean, caller: SwitchCaller, now?: Date): Promise<SwitchOutcome>;
|
|
74
|
+
/** Every job as the health screen draws it, for this viewer. */
|
|
75
|
+
views(audience: JobAudience, now?: Date): Promise<JobView[]>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The dispatcher, and everything else that touches the ledger.
|
|
79
|
+
*
|
|
80
|
+
* Declared once in the app beside its jobs and its store adapter; the tick
|
|
81
|
+
* route, the own-schedule routes, the Run now and switch routes and the health
|
|
82
|
+
* page all call this one object.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createScheduler(options: SchedulerOptions): Scheduler;
|
|
85
|
+
/** The tick in one line: "2 ran, 1 failed, 1 paused, 5 not due". */
|
|
86
|
+
export declare function summariseTick(results: readonly TickResult[]): string;
|
|
87
|
+
/** The HTTP status a refusal maps to, so every app answers the same refusal the same way. */
|
|
88
|
+
export declare function refusalStatus(reason: RunNowRefusal | SwitchRefusal): 403 | 404 | 409 | 429;
|
|
89
|
+
//# 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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"}
|