@procedures/schedule 0.0.0-stage → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -2
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/cron.d.ts +47 -0
  5. package/dist/cron.d.ts.map +1 -0
  6. package/dist/cron.js +197 -0
  7. package/dist/cron.js.map +1 -0
  8. package/dist/errors.d.ts +22 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +13 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/index.d.ts +9 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +11 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/module.d.ts +42 -0
  17. package/dist/module.d.ts.map +1 -0
  18. package/dist/module.js +174 -0
  19. package/dist/module.js.map +1 -0
  20. package/dist/procedures/schedule/add.d.ts +17 -0
  21. package/dist/procedures/schedule/add.d.ts.map +1 -0
  22. package/dist/procedures/schedule/add.js +66 -0
  23. package/dist/procedures/schedule/add.js.map +1 -0
  24. package/dist/procedures/schedule/index.d.ts +5 -0
  25. package/dist/procedures/schedule/index.d.ts.map +1 -0
  26. package/dist/procedures/schedule/index.js +5 -0
  27. package/dist/procedures/schedule/index.js.map +1 -0
  28. package/dist/procedures/schedule/list.d.ts +12 -0
  29. package/dist/procedures/schedule/list.d.ts.map +1 -0
  30. package/dist/procedures/schedule/list.js +18 -0
  31. package/dist/procedures/schedule/list.js.map +1 -0
  32. package/dist/procedures/schedule/next.d.ts +13 -0
  33. package/dist/procedures/schedule/next.d.ts.map +1 -0
  34. package/dist/procedures/schedule/next.js +24 -0
  35. package/dist/procedures/schedule/next.js.map +1 -0
  36. package/dist/procedures/schedule/remove.d.ts +12 -0
  37. package/dist/procedures/schedule/remove.d.ts.map +1 -0
  38. package/dist/procedures/schedule/remove.js +13 -0
  39. package/dist/procedures/schedule/remove.js.map +1 -0
  40. package/dist/programs.d.ts +26 -0
  41. package/dist/programs.d.ts.map +1 -0
  42. package/dist/programs.js +34 -0
  43. package/dist/programs.js.map +1 -0
  44. package/dist/register.d.ts +6 -0
  45. package/dist/register.d.ts.map +1 -0
  46. package/dist/register.js +8 -0
  47. package/dist/register.js.map +1 -0
  48. package/dist/scheduler.d.ts +106 -0
  49. package/dist/scheduler.d.ts.map +1 -0
  50. package/dist/scheduler.js +274 -0
  51. package/dist/scheduler.js.map +1 -0
  52. package/dist/store.d.ts +62 -0
  53. package/dist/store.d.ts.map +1 -0
  54. package/dist/store.js +188 -0
  55. package/dist/store.js.map +1 -0
  56. package/dist/types.d.ts +100 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +8 -0
  59. package/dist/types.js.map +1 -0
  60. package/package.json +78 -3
  61. package/src/cron.ts +230 -0
  62. package/src/errors.ts +27 -0
  63. package/src/index.ts +10 -0
  64. package/src/module.ts +227 -0
  65. package/src/procedures/schedule/add.ts +66 -0
  66. package/src/procedures/schedule/index.ts +4 -0
  67. package/src/procedures/schedule/list.ts +20 -0
  68. package/src/procedures/schedule/next.ts +25 -0
  69. package/src/procedures/schedule/remove.ts +15 -0
  70. package/src/programs.ts +43 -0
  71. package/src/register.ts +10 -0
  72. package/src/scheduler.ts +331 -0
  73. package/src/store.ts +213 -0
  74. package/src/types.ts +122 -0
@@ -0,0 +1,274 @@
1
+ /**
2
+ * The scheduler: a host starts it, and it starts the stored programs of the schedules at their
3
+ * times (roadmap R46).
4
+ *
5
+ * - It wakes at each minute (the unit of cron), reads the schedules of its store, and starts each
6
+ * schedule whose time came. A schedule that a procedure adds or removes counts at the next minute.
7
+ * - A run goes through the registry, with the capability of the host narrowed by the capability
8
+ * of the caller that added the schedule. The expose rule, the call rule, the interceptors and
9
+ * the resources of the host policy apply too.
10
+ * - A schedule has at most one run at a time. A time that comes during a run does not start a
11
+ * second run: the store counts it as skipped.
12
+ * - The store records the last run of each schedule: its start, its time and its outcome.
13
+ * - A run that passes `runTimeoutMs` gets the abort of its signal and the outcome `TIMEOUT`. The
14
+ * schedule stays busy until the handler ends: a handler that does not stop blocks only its own
15
+ * schedule.
16
+ * - The scheduler does not run the times that it missed while it was stopped. Times that it
17
+ * missed during a run or a slow minute give one run, not one for each time.
18
+ * - The timers use an injectable `Clock` (`after` and `now`), and the calendar uses an injectable
19
+ * wall clock (`now`, default `Date.now`). A test gives `fakeClock` of `@mark1russell7/service/testing`.
20
+ */
21
+ import { InvocationError, PROCEDURE_REGISTRY, invokePath, invokeProcedure, isRuntimeDefined, keyToPath, narrowCapability, outputValue, realClock, } from "@procedures/client";
22
+ import { nextTime, parseCron } from "./cron.js";
23
+ import { ScheduleError } from "./errors.js";
24
+ import { findProgram } from "./programs.js";
25
+ import { defaultScheduleStore } from "./store.js";
26
+ /** The default time limit of a run: 10 minutes. */
27
+ export const DEFAULT_RUN_TIMEOUT_MS = 600_000;
28
+ const versionOf = (schedule) => `${schedule.createdAt}|${schedule.cron}|${schedule.timeZone}`;
29
+ /** The code and the message of an error. */
30
+ function errorInfo(error) {
31
+ const message = error instanceof Error ? error.message : String(error);
32
+ const code = error?.code;
33
+ return typeof code === "string" ? { code, message } : { message };
34
+ }
35
+ /** The next time of a schedule after a time, or undefined for an expression that is not correct. */
36
+ function nextOf(schedule, after) {
37
+ try {
38
+ return nextTime(parseCron(schedule.cron), after, schedule.timeZone);
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ /** A scheduler of the stored programs. See the comment of the file. */
45
+ export class Scheduler {
46
+ registry;
47
+ store;
48
+ clock;
49
+ now;
50
+ runTimeoutMs;
51
+ options;
52
+ due = new Map();
53
+ runs = new Map();
54
+ startedAt;
55
+ cancel;
56
+ ticking;
57
+ constructor(options = {}) {
58
+ this.options = options;
59
+ this.registry = options.registry ?? PROCEDURE_REGISTRY;
60
+ this.store = options.store ?? defaultScheduleStore;
61
+ this.clock = options.clock ?? realClock;
62
+ this.now = options.now ?? (() => Date.now());
63
+ this.runTimeoutMs = options.runTimeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
64
+ if (!(this.runTimeoutMs >= 0))
65
+ throw new TypeError("Scheduler: runTimeoutMs must be 0 or more");
66
+ }
67
+ /** True after `start()` and before `stop()`. */
68
+ get started() {
69
+ return this.startedAt !== undefined;
70
+ }
71
+ /** The names of the schedules with a run that has not ended. */
72
+ get active() {
73
+ return [...this.runs.keys()].sort();
74
+ }
75
+ /** This method starts the scheduler: the first times come after now. It gives the end of the first examination. */
76
+ async start() {
77
+ if (this.startedAt !== undefined)
78
+ return;
79
+ this.startedAt = this.now();
80
+ await this.tick();
81
+ }
82
+ /** This method stops the timer, aborts the runs, and gives the end of the runs. */
83
+ async stop() {
84
+ this.startedAt = undefined;
85
+ this.cancel?.();
86
+ this.cancel = undefined;
87
+ this.due.clear();
88
+ for (const run of this.runs.values())
89
+ run.controller.abort(new Error("The scheduler stopped"));
90
+ await this.settled();
91
+ }
92
+ /** This method gives the end of the examination in progress and of the runs in progress. */
93
+ async settled() {
94
+ await this.ticking;
95
+ await Promise.all([...this.runs.values()].map((run) => run.promise));
96
+ }
97
+ /**
98
+ * This method examines the schedules now and starts each one whose time came. It does not wait
99
+ * for the runs. A second call during an examination gives the same examination.
100
+ */
101
+ tick() {
102
+ this.ticking ??= this.examine().finally(() => {
103
+ this.ticking = undefined;
104
+ this.arm();
105
+ });
106
+ return this.ticking;
107
+ }
108
+ /** This method starts one run of a schedule now, unless it has a run in progress. It gives the event of the run. */
109
+ async runNow(id) {
110
+ const schedule = await this.store.get(id);
111
+ if (schedule === undefined)
112
+ throw new ScheduleError("NOT_FOUND", `No schedule is named ${id}`);
113
+ return new Promise((resolve) => {
114
+ if (!this.launch(schedule, this.now(), resolve))
115
+ resolve(this.skip(schedule, this.now()));
116
+ });
117
+ }
118
+ async examine() {
119
+ if (this.startedAt === undefined)
120
+ return;
121
+ const now = this.now();
122
+ let schedules;
123
+ try {
124
+ schedules = await this.store.list();
125
+ }
126
+ catch (error) {
127
+ this.options.onError?.(error);
128
+ return;
129
+ }
130
+ const ids = new Set(schedules.map((schedule) => schedule.id));
131
+ for (const id of this.due.keys())
132
+ if (!ids.has(id))
133
+ this.due.delete(id);
134
+ for (const schedule of schedules) {
135
+ // A stop during the read of the store starts nothing
136
+ if (this.startedAt === undefined)
137
+ return;
138
+ const version = versionOf(schedule);
139
+ let due = this.due.get(schedule.id);
140
+ if (due === undefined || due.version !== version) {
141
+ // A new or a changed schedule: its first time is after it was added, and after the start
142
+ due = { version, at: nextOf(schedule, Math.max(schedule.createdAt, this.startedAt) - 1) };
143
+ this.due.set(schedule.id, due);
144
+ }
145
+ if (due.at === undefined || due.at > now)
146
+ continue;
147
+ // The times that passed give one run: the next time comes after now
148
+ due.at = nextOf(schedule, now);
149
+ if (!this.launch(schedule, now))
150
+ void this.skip(schedule, now);
151
+ }
152
+ }
153
+ /** This method arms the timer for the next minute, or for an earlier time that is due. */
154
+ arm() {
155
+ if (this.startedAt === undefined)
156
+ return;
157
+ this.cancel?.();
158
+ const now = this.now();
159
+ let wake = Math.floor(now / 60_000) * 60_000 + 60_000;
160
+ for (const due of this.due.values())
161
+ if (due.at !== undefined && due.at < wake)
162
+ wake = due.at;
163
+ this.cancel = this.clock.after(Math.max(0, wake - now), () => {
164
+ this.cancel = undefined;
165
+ void this.tick();
166
+ });
167
+ }
168
+ /** This method gives an event to `onRun`. An error of `onRun` goes to `onError`, not to the scheduler. */
169
+ report(event) {
170
+ try {
171
+ this.options.onRun?.(event);
172
+ }
173
+ catch (error) {
174
+ this.options.onError?.(error);
175
+ }
176
+ }
177
+ /** This method records a time that came during a run of the same schedule. */
178
+ skip(schedule, now) {
179
+ const event = { id: schedule.id, program: schedule.program, outcome: "skipped", startedAt: now, durationMs: 0 };
180
+ void this.store
181
+ .update(schedule.id, (current) => (current.createdAt === schedule.createdAt ? { ...current, skipped: current.skipped + 1 } : undefined))
182
+ .catch((error) => this.options.onError?.(error));
183
+ this.report(event);
184
+ return event;
185
+ }
186
+ /** This method starts a run, unless the schedule has one. It gives false for a schedule with a run. */
187
+ launch(schedule, startedAt, done) {
188
+ if (this.runs.has(schedule.id))
189
+ return false;
190
+ const controller = new AbortController();
191
+ const promise = this.run(schedule, startedAt, controller).then((event) => {
192
+ this.runs.delete(schedule.id);
193
+ this.report(event);
194
+ done?.(event);
195
+ });
196
+ this.runs.set(schedule.id, { promise, controller });
197
+ return true;
198
+ }
199
+ /** One run: the program through the registry, then its record. */
200
+ async run(schedule, startedAt, controller) {
201
+ const start = this.clock.now();
202
+ let timedOut = false;
203
+ const stopTimer = this.runTimeoutMs > 0
204
+ ? this.clock.after(this.runTimeoutMs, () => {
205
+ timedOut = true;
206
+ controller.abort(new Error(`The run of ${schedule.id} passed its time limit of ${this.runTimeoutMs} ms`));
207
+ })
208
+ : undefined;
209
+ let error;
210
+ try {
211
+ await this.invoke(schedule, controller.signal);
212
+ }
213
+ catch (caught) {
214
+ error = errorInfo(caught);
215
+ }
216
+ finally {
217
+ stopTimer?.();
218
+ }
219
+ if (timedOut)
220
+ error = { code: "TIMEOUT", message: `The run passed its time limit of ${this.runTimeoutMs} ms${error ? `: ${error.message}` : ""}` };
221
+ const run = {
222
+ startedAt,
223
+ durationMs: Math.max(0, this.clock.now() - start),
224
+ outcome: error === undefined ? "success" : "failure",
225
+ ...(error !== undefined ? { error } : {}),
226
+ };
227
+ try {
228
+ await this.store.update(schedule.id, (current) => current.createdAt === schedule.createdAt
229
+ ? { ...current, lastRun: run, runs: current.runs + 1, failures: current.failures + (error === undefined ? 0 : 1) }
230
+ : undefined);
231
+ }
232
+ catch (caught) {
233
+ this.options.onError?.(caught);
234
+ }
235
+ return { id: schedule.id, program: schedule.program, outcome: run.outcome, startedAt, durationMs: run.durationMs, ...(error !== undefined ? { error } : {}) };
236
+ }
237
+ /** The options of an invocation: the policy of the host, a capability and the signal of the run. */
238
+ invokeOptions(capability, signal) {
239
+ const policy = this.options.policy;
240
+ return {
241
+ registry: this.registry,
242
+ signal,
243
+ ...(capability !== undefined ? { capability } : {}),
244
+ ...(policy?.expose ? { expose: policy.expose } : {}),
245
+ ...(policy?.strictCalls ? { strictCalls: true } : {}),
246
+ ...(policy && policy.interceptors.length > 0 ? { interceptors: policy.interceptors } : {}),
247
+ ...(policy?.clearanceOrder ? { clearanceOrder: policy.clearanceOrder } : {}),
248
+ ...(policy?.resources ? { resources: policy.resources } : {}),
249
+ };
250
+ }
251
+ /** The program of a schedule through the registry, after a load from the program store when the registry does not have it. */
252
+ async invoke(schedule, signal) {
253
+ const path = keyToPath(schedule.program);
254
+ const host = this.options.capability ?? this.options.policy?.capabilityOf?.();
255
+ if ((await findProgram(this.registry, schedule.program)) === "store") {
256
+ // The load defines the program: a step of the host, with the capability of the host
257
+ await outputValue(await invokePath(["procedure", "load"], { path }, this.invokeOptions(host, signal)), ["procedure", "load"]);
258
+ }
259
+ const procedure = this.registry.get(path);
260
+ if (procedure === undefined || !isRuntimeDefined(procedure)) {
261
+ throw new ScheduleError("NOT_FOUND", `The program ${schedule.program} is not in the registry after its load`);
262
+ }
263
+ const capability = schedule.capability === undefined ? host : narrowCapability(host, schedule.capability, this.options.policy?.clearanceOrder);
264
+ if (signal.aborted)
265
+ throw new InvocationError("ABORTED", `The run of ${schedule.id} was aborted before its start`, path);
266
+ const output = await invokeProcedure(procedure, schedule.input ?? {}, this.invokeOptions(capability, signal));
267
+ if (output.kind === "stream") {
268
+ for await (const _item of output.items) {
269
+ // A program that gives a stream runs to its end
270
+ }
271
+ }
272
+ }
273
+ }
274
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,SAAS,GAOV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAsB,MAAM,YAAY,CAAC;AAGtE,mDAAmD;AACnD,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAyC9C,MAAM,SAAS,GAAG,CAAC,QAAkB,EAAU,EAAE,CAAC,GAAG,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;AAEhH,4CAA4C;AAC5C,SAAS,SAAS,CAAC,KAAc;IAC/B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,MAAM,IAAI,GAAI,KAAmC,EAAE,IAAI,CAAC;IACxD,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;AACpE,CAAC;AAED,oGAAoG;AACpG,SAAS,MAAM,CAAC,QAAkB,EAAE,KAAa;IAC/C,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,OAAO,SAAS;IACH,QAAQ,CAAoB;IAC5B,KAAK,CAAgB;IACrB,KAAK,CAAQ;IACb,GAAG,CAAe;IAClB,YAAY,CAAS;IACrB,OAAO,CAAmB;IAC1B,GAAG,GAAG,IAAI,GAAG,EAAe,CAAC;IAC7B,IAAI,GAAG,IAAI,GAAG,EAAmE,CAAC;IAC3F,SAAS,CAAqB;IAC9B,MAAM,CAAqB;IAC3B,OAAO,CAA4B;IAE3C,YAAY,OAAO,GAAqB,EAAE;QACxC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,kBAAkB,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,oBAAoB,CAAC;QACnD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;QACxC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,sBAAsB,CAAC;QACnE,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAC;IAClG,CAAC;IAED,gDAAgD;IAChD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC;IACtC,CAAC;IAED,gEAAgE;IAChE,IAAI,MAAM;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,mHAAmH;IACnH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO;QACzC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACjB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAC/F,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;IAED,4FAA4F;IAC5F,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,OAAO,CAAC;QACnB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC3C,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;YACzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,oHAAoH;IACpH,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1C,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,wBAAwB,EAAE,EAAE,CAAC,CAAC;QAC/F,OAAO,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,EAAE;YAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC5F,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO;QACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,SAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxE,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,qDAAqD;YACrD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;gBAAE,OAAO;YACzC,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACpC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;gBACjD,yFAAyF;gBACzF,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC1F,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACjC,CAAC;YACD,IAAI,GAAG,CAAC,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,EAAE,GAAG,GAAG;gBAAE,SAAS;YACnD,oEAAoE;YACpE,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC;gBAAE,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,0FAA0F;IAClF,GAAG;QACT,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO;QACzC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAChB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC;QACtD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE;YAAE,IAAI,GAAG,CAAC,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI;gBAAE,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QAC9F,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE;YAC3D,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YACxB,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,0GAA0G;IAClG,MAAM,CAAC,KAAuB;QACpC,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,8EAA8E;IACtE,IAAI,CAAC,QAAkB,EAAE,GAAW;QAC1C,MAAM,KAAK,GAAqB,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QAClI,KAAK,IAAI,CAAC,KAAK;aACZ,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;aACvI,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uGAAuG;IAC/F,MAAM,CAAC,QAAkB,EAAE,SAAiB,EAAE,IAAwC;QAC5F,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YACvE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kEAAkE;IAC1D,KAAK,CAAC,GAAG,CAAC,QAAkB,EAAE,SAAiB,EAAE,UAA2B;QAClF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAC/B,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,SAAS,GACb,IAAI,CAAC,YAAY,GAAG,CAAC;YACnB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE;gBACvC,QAAQ,GAAG,IAAI,CAAC;gBAChB,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,cAAc,QAAQ,CAAC,EAAE,6BAA6B,IAAI,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC;YAC5G,CAAC,CAAC;YACJ,CAAC,CAAC,SAAS,CAAC;QAChB,IAAI,KAAqD,CAAC;QAC1D,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;gBAAS,CAAC;YACT,SAAS,EAAE,EAAE,CAAC;QAChB,CAAC;QACD,IAAI,QAAQ;YAAE,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,oCAAoC,IAAI,CAAC,YAAY,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;QACnJ,MAAM,GAAG,GAAgB;YACvB,SAAS;YACT,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;YACjD,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;YACpD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,CAAC;QACF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,CAC/C,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,SAAS;gBACtC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;gBAClH,CAAC,CAAC,SAAS,CACd,CAAC;QACJ,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAChK,CAAC;IAED,oGAAoG;IAC5F,aAAa,CAAC,UAAkC,EAAE,MAAmB;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QACnC,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM;YACN,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1F,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC;IACJ,CAAC;IAED,8HAA8H;IACtH,KAAK,CAAC,MAAM,CAAC,QAAkB,EAAE,MAAmB;QAC1D,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAAC;QAC9E,IAAI,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;YACrE,oFAAoF;YACpF,MAAM,WAAW,CAAC,MAAM,UAAU,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;QAChI,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,eAAe,QAAQ,CAAC,OAAO,wCAAwC,CAAC,CAAC;QAChH,CAAC;QACD,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QAC/I,IAAI,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,cAAc,QAAQ,CAAC,EAAE,+BAA+B,EAAE,IAAI,CAAC,CAAC;QACzH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9G,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACvC,gDAAgD;YAClD,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The stores of the schedules: in memory, or in one JSON file.
3
+ *
4
+ * A host gives one store to the module of the procedures (`createScheduleModule({ store })`) and
5
+ * the same store to its `Scheduler`. `update()` changes one schedule in one step, so the record of
6
+ * a run does not lose a change of `schedule.add` that came at the same time.
7
+ */
8
+ import type { Schedule } from "./types.js";
9
+ /** A store of schedules. Each method gives a Promise: a caller cannot tell a local store from a remote one. */
10
+ export interface ScheduleStore {
11
+ /** The kind of the backend: "memory", "file" or another name. */
12
+ readonly kind: string;
13
+ get(id: string): Promise<Schedule | undefined>;
14
+ /** The schedules, sorted by name. */
15
+ list(): Promise<Schedule[]>;
16
+ /** Write a schedule. A schedule of the same name is replaced. */
17
+ put(schedule: Schedule): Promise<void>;
18
+ /** Delete a schedule. True when it existed. */
19
+ delete(id: string): Promise<boolean>;
20
+ /**
21
+ * Change one schedule in one step. `change` gets a copy and gives the new schedule, or
22
+ * undefined for no change. The result is the stored schedule, or undefined when none exists.
23
+ */
24
+ update(id: string, change: (schedule: Schedule) => Schedule | undefined): Promise<Schedule | undefined>;
25
+ }
26
+ /** The schedule of a record that came from a file: its shape is not trusted. */
27
+ export declare function checkSchedule(value: unknown): Schedule;
28
+ /** A store in memory: for tests, and for a host that keeps its schedules only while it runs. */
29
+ export declare class MemoryScheduleStore implements ScheduleStore {
30
+ readonly kind = "memory";
31
+ private readonly schedules;
32
+ get(id: string): Promise<Schedule | undefined>;
33
+ list(): Promise<Schedule[]>;
34
+ put(schedule: Schedule): Promise<void>;
35
+ delete(id: string): Promise<boolean>;
36
+ update(id: string, change: (schedule: Schedule) => Schedule | undefined): Promise<Schedule | undefined>;
37
+ }
38
+ /**
39
+ * A store in one JSON file: `{ "schemaVersion": 1, "schedules": { "<id>": <Schedule> } }`. Each
40
+ * operation reads the file again. A write goes through a temporary file and a rename, so a reader
41
+ * never sees a part of a write. The writes of this process go one at a time. The store is for
42
+ * one process: two processes on one file can lose a write.
43
+ */
44
+ export declare class FileScheduleStore implements ScheduleStore {
45
+ readonly path: string;
46
+ readonly kind = "file";
47
+ private queue;
48
+ /** @param path - The JSON file. The store makes it, and its folder, at the first write. */
49
+ constructor(path: string);
50
+ get(id: string): Promise<Schedule | undefined>;
51
+ list(): Promise<Schedule[]>;
52
+ put(schedule: Schedule): Promise<void>;
53
+ delete(id: string): Promise<boolean>;
54
+ update(id: string, change: (schedule: Schedule) => Schedule | undefined): Promise<Schedule | undefined>;
55
+ /** The content of the file. A missing file is an empty store. */
56
+ private read;
57
+ /** One change of the file, after the changes before it. `edit` gives false for no change. */
58
+ private change;
59
+ }
60
+ /** The store of the default module and of a `Scheduler` without a store: in memory, one for the process. */
61
+ export declare const defaultScheduleStore: ScheduleStore;
62
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,+GAA+G;AAC/G,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IAC/C,qCAAqC;IACrC,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5B,iEAAiE;IACjE,GAAG,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,+CAA+C;IAC/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;CACzG;AAMD,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAWtD;AAED,gGAAgG;AAChG,qBAAa,mBAAoB,YAAW,aAAa;IACvD,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEnD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAGnD;IAEK,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAEhC;IAEK,GAAG,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3C;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEzC;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAS5G;CACF;AAeD;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IAKzC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAJjC,QAAQ,CAAC,IAAI,UAAU;IACvB,OAAO,CAAC,KAAK,CAAuC;IAEpD,2FAA2F;IAC3F,YAAqB,IAAI,EAAE,MAAM,EAAI;IAE/B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAEnD;IAEK,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAEhC;IAEK,GAAG,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3C;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQzC;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAiB5G;IAED,iEAAiE;YACnD,IAAI;IA2BlB,6FAA6F;IAC7F,OAAO,CAAC,MAAM;CAwBf;AAED,4GAA4G;AAC5G,eAAO,MAAM,oBAAoB,EAAE,aAAyC,CAAC"}
package/dist/store.js ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * The stores of the schedules: in memory, or in one JSON file.
3
+ *
4
+ * A host gives one store to the module of the procedures (`createScheduleModule({ store })`) and
5
+ * the same store to its `Scheduler`. `update()` changes one schedule in one step, so the record of
6
+ * a run does not lose a change of `schedule.add` that came at the same time.
7
+ */
8
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
9
+ import { dirname } from "node:path";
10
+ import { randomBytes } from "node:crypto";
11
+ import { ScheduleError } from "./errors.js";
12
+ const byId = (a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
13
+ const isCount = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
14
+ /** The schedule of a record that came from a file: its shape is not trusted. */
15
+ export function checkSchedule(value) {
16
+ const record = value;
17
+ if (record === null || typeof record !== "object")
18
+ throw new ScheduleError("BAD_STORE", "A stored schedule must be an object");
19
+ if (typeof record.id !== "string" || record.id === "")
20
+ throw new ScheduleError("BAD_STORE", "A stored schedule needs an id");
21
+ if (typeof record.cron !== "string" || typeof record.program !== "string" || record.program === "") {
22
+ throw new ScheduleError("BAD_STORE", `The schedule ${record.id} needs a cron expression and a program`);
23
+ }
24
+ if (record.timeZone !== "utc" && record.timeZone !== "local")
25
+ throw new ScheduleError("BAD_STORE", `The schedule ${record.id} needs the time zone utc or local`);
26
+ if (typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt))
27
+ throw new ScheduleError("BAD_STORE", `The schedule ${record.id} needs createdAt`);
28
+ if (!isCount(record.runs) || !isCount(record.failures) || !isCount(record.skipped))
29
+ throw new ScheduleError("BAD_STORE", `The schedule ${record.id} needs the counts of its runs`);
30
+ return record;
31
+ }
32
+ /** A store in memory: for tests, and for a host that keeps its schedules only while it runs. */
33
+ export class MemoryScheduleStore {
34
+ kind = "memory";
35
+ schedules = new Map();
36
+ async get(id) {
37
+ const schedule = this.schedules.get(id);
38
+ return schedule ? structuredClone(schedule) : undefined;
39
+ }
40
+ async list() {
41
+ return [...this.schedules.values()].map((schedule) => structuredClone(schedule)).sort(byId);
42
+ }
43
+ async put(schedule) {
44
+ const checked = checkSchedule(structuredClone(schedule));
45
+ this.schedules.set(checked.id, checked);
46
+ }
47
+ async delete(id) {
48
+ return this.schedules.delete(id);
49
+ }
50
+ async update(id, change) {
51
+ const current = this.schedules.get(id);
52
+ if (current === undefined)
53
+ return undefined;
54
+ const next = change(structuredClone(current));
55
+ if (next === undefined)
56
+ return structuredClone(current);
57
+ const checked = checkSchedule(structuredClone(next));
58
+ if (checked.id !== id)
59
+ throw new ScheduleError("BAD_STORE", "An update cannot change the id of a schedule");
60
+ this.schedules.set(id, checked);
61
+ return structuredClone(checked);
62
+ }
63
+ }
64
+ /** True when Windows holds a file for a moment. */
65
+ function isContention(error) {
66
+ const code = error.code;
67
+ return process.platform === "win32" && (code === "EPERM" || code === "EACCES" || code === "EBUSY");
68
+ }
69
+ const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
70
+ /**
71
+ * A store in one JSON file: `{ "schemaVersion": 1, "schedules": { "<id>": <Schedule> } }`. Each
72
+ * operation reads the file again. A write goes through a temporary file and a rename, so a reader
73
+ * never sees a part of a write. The writes of this process go one at a time. The store is for
74
+ * one process: two processes on one file can lose a write.
75
+ */
76
+ export class FileScheduleStore {
77
+ path;
78
+ kind = "file";
79
+ queue = Promise.resolve();
80
+ /** @param path - The JSON file. The store makes it, and its folder, at the first write. */
81
+ constructor(path) {
82
+ this.path = path;
83
+ }
84
+ async get(id) {
85
+ return (await this.read()).schedules[id];
86
+ }
87
+ async list() {
88
+ return Object.values((await this.read()).schedules).sort(byId);
89
+ }
90
+ async put(schedule) {
91
+ const checked = checkSchedule(structuredClone(schedule));
92
+ await this.change((file) => {
93
+ file.schedules[checked.id] = checked;
94
+ return true;
95
+ });
96
+ }
97
+ async delete(id) {
98
+ let deleted = false;
99
+ await this.change((file) => {
100
+ deleted = Object.hasOwn(file.schedules, id);
101
+ if (deleted)
102
+ delete file.schedules[id];
103
+ return deleted;
104
+ });
105
+ return deleted;
106
+ }
107
+ async update(id, change) {
108
+ let result;
109
+ await this.change((file) => {
110
+ const current = file.schedules[id];
111
+ if (current === undefined)
112
+ return false;
113
+ const next = change(structuredClone(current));
114
+ if (next === undefined) {
115
+ result = current;
116
+ return false;
117
+ }
118
+ const checked = checkSchedule(structuredClone(next));
119
+ if (checked.id !== id)
120
+ throw new ScheduleError("BAD_STORE", "An update cannot change the id of a schedule");
121
+ file.schedules[id] = checked;
122
+ result = checked;
123
+ return true;
124
+ });
125
+ return result;
126
+ }
127
+ /** The content of the file. A missing file is an empty store. */
128
+ async read() {
129
+ let text;
130
+ try {
131
+ text = await readFile(this.path, "utf8");
132
+ }
133
+ catch (error) {
134
+ if (error.code === "ENOENT")
135
+ return { schemaVersion: 1, schedules: {} };
136
+ throw error;
137
+ }
138
+ let parsed;
139
+ try {
140
+ parsed = JSON.parse(text);
141
+ }
142
+ catch {
143
+ throw new ScheduleError("BAD_STORE", `The schedule file ${this.path} is not JSON. The store does not write over it.`);
144
+ }
145
+ const file = parsed;
146
+ if (file === null || typeof file !== "object" || file.schemaVersion !== 1 || file.schedules === null || typeof file.schedules !== "object") {
147
+ throw new ScheduleError("BAD_STORE", `The schedule file ${this.path} does not have schemaVersion 1 and schedules`);
148
+ }
149
+ const schedules = {};
150
+ for (const [id, value] of Object.entries(file.schedules)) {
151
+ const schedule = checkSchedule(value);
152
+ if (schedule.id !== id)
153
+ throw new ScheduleError("BAD_STORE", `The schedule file ${this.path} has the schedule ${schedule.id} under the key ${id}`);
154
+ schedules[id] = schedule;
155
+ }
156
+ return { schemaVersion: 1, schedules };
157
+ }
158
+ /** One change of the file, after the changes before it. `edit` gives false for no change. */
159
+ change(edit) {
160
+ const run = this.queue.then(async () => {
161
+ const file = await this.read();
162
+ if (!edit(file))
163
+ return;
164
+ await mkdir(dirname(this.path), { recursive: true });
165
+ const temporary = `${this.path}.${randomBytes(6).toString("hex")}.tmp`;
166
+ await writeFile(temporary, JSON.stringify(file, null, 2) + "\n", "utf8");
167
+ for (let attempt = 1;; attempt++) {
168
+ try {
169
+ await rename(temporary, this.path);
170
+ return;
171
+ }
172
+ catch (error) {
173
+ if (attempt >= 20 || !isContention(error)) {
174
+ await rm(temporary, { force: true });
175
+ throw error;
176
+ }
177
+ await pause(10 * attempt);
178
+ }
179
+ }
180
+ });
181
+ // A failed change does not stop the next one
182
+ this.queue = run.catch(() => undefined);
183
+ return run;
184
+ }
185
+ }
186
+ /** The store of the default module and of a `Scheduler` without a store: in memory, one for the process. */
187
+ export const defaultScheduleStore = new MemoryScheduleStore();
188
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}