@procedures/schedule 0.0.0-stage → 0.3.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/LICENSE +21 -0
  3. package/README.md +98 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/cron.d.ts +47 -0
  6. package/dist/cron.d.ts.map +1 -0
  7. package/dist/cron.js +197 -0
  8. package/dist/cron.js.map +1 -0
  9. package/dist/errors.d.ts +22 -0
  10. package/dist/errors.d.ts.map +1 -0
  11. package/dist/errors.js +13 -0
  12. package/dist/errors.js.map +1 -0
  13. package/dist/index.d.ts +9 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +11 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/module.d.ts +42 -0
  18. package/dist/module.d.ts.map +1 -0
  19. package/dist/module.js +174 -0
  20. package/dist/module.js.map +1 -0
  21. package/dist/procedures/schedule/add.d.ts +17 -0
  22. package/dist/procedures/schedule/add.d.ts.map +1 -0
  23. package/dist/procedures/schedule/add.js +66 -0
  24. package/dist/procedures/schedule/add.js.map +1 -0
  25. package/dist/procedures/schedule/index.d.ts +5 -0
  26. package/dist/procedures/schedule/index.d.ts.map +1 -0
  27. package/dist/procedures/schedule/index.js +5 -0
  28. package/dist/procedures/schedule/index.js.map +1 -0
  29. package/dist/procedures/schedule/list.d.ts +12 -0
  30. package/dist/procedures/schedule/list.d.ts.map +1 -0
  31. package/dist/procedures/schedule/list.js +18 -0
  32. package/dist/procedures/schedule/list.js.map +1 -0
  33. package/dist/procedures/schedule/next.d.ts +13 -0
  34. package/dist/procedures/schedule/next.d.ts.map +1 -0
  35. package/dist/procedures/schedule/next.js +24 -0
  36. package/dist/procedures/schedule/next.js.map +1 -0
  37. package/dist/procedures/schedule/remove.d.ts +12 -0
  38. package/dist/procedures/schedule/remove.d.ts.map +1 -0
  39. package/dist/procedures/schedule/remove.js +13 -0
  40. package/dist/procedures/schedule/remove.js.map +1 -0
  41. package/dist/programs.d.ts +26 -0
  42. package/dist/programs.d.ts.map +1 -0
  43. package/dist/programs.js +34 -0
  44. package/dist/programs.js.map +1 -0
  45. package/dist/register.d.ts +6 -0
  46. package/dist/register.d.ts.map +1 -0
  47. package/dist/register.js +8 -0
  48. package/dist/register.js.map +1 -0
  49. package/dist/scheduler.d.ts +106 -0
  50. package/dist/scheduler.d.ts.map +1 -0
  51. package/dist/scheduler.js +274 -0
  52. package/dist/scheduler.js.map +1 -0
  53. package/dist/store.d.ts +62 -0
  54. package/dist/store.d.ts.map +1 -0
  55. package/dist/store.js +188 -0
  56. package/dist/store.js.map +1 -0
  57. package/dist/types.d.ts +100 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +8 -0
  60. package/dist/types.js.map +1 -0
  61. package/package.json +78 -3
  62. package/src/cron.ts +230 -0
  63. package/src/errors.ts +27 -0
  64. package/src/index.ts +10 -0
  65. package/src/module.ts +227 -0
  66. package/src/procedures/schedule/add.ts +66 -0
  67. package/src/procedures/schedule/index.ts +4 -0
  68. package/src/procedures/schedule/list.ts +20 -0
  69. package/src/procedures/schedule/next.ts +25 -0
  70. package/src/procedures/schedule/remove.ts +15 -0
  71. package/src/programs.ts +43 -0
  72. package/src/register.ts +10 -0
  73. package/src/scheduler.ts +331 -0
  74. package/src/store.ts +213 -0
  75. package/src/types.ts +122 -0
@@ -0,0 +1,5 @@
1
+ export { scheduleAdd, viewOf } from "./add.js";
2
+ export { scheduleList } from "./list.js";
3
+ export { scheduleRemove } from "./remove.js";
4
+ export { scheduleNext } from "./next.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/procedures/schedule/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * schedule.list procedure
3
+ *
4
+ * The schedules, sorted by name, with the next time, the last run and the counts of each.
5
+ */
6
+ import { type ScheduleSettings } from "../../programs.js";
7
+ import type { ScheduleListInput, ScheduleListOutput } from "../../types.js";
8
+ /**
9
+ * The schedules, with the next time and the last run of each
10
+ */
11
+ export declare function scheduleList(input: ScheduleListInput, settings?: ScheduleSettings): Promise<ScheduleListOutput>;
12
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/procedures/schedule/list.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG5E;;GAEG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,GAAE,gBAA4C,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAMhJ"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * schedule.list procedure
3
+ *
4
+ * The schedules, sorted by name, with the next time, the last run and the counts of each.
5
+ */
6
+ import { DEFAULT_SCHEDULE_SETTINGS } from "../../programs.js";
7
+ import { viewOf } from "./add.js";
8
+ /**
9
+ * The schedules, with the next time and the last run of each
10
+ */
11
+ export async function scheduleList(input, settings = DEFAULT_SCHEDULE_SETTINGS) {
12
+ const now = settings.now();
13
+ const schedules = await settings.store.list();
14
+ return {
15
+ schedules: schedules.filter((schedule) => input.program === undefined || schedule.program === input.program).map((schedule) => viewOf(schedule, now)),
16
+ };
17
+ }
18
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/procedures/schedule/list.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,yBAAyB,EAAyB,MAAM,mBAAmB,CAAC;AAErF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAwB,EAAE,QAAQ,GAAqB,yBAAyB;IACjH,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO;QACL,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;KACtJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * schedule.next procedure
3
+ *
4
+ * The next times of a cron expression. It reads no store: the output comes from the input and
5
+ * from the time.
6
+ */
7
+ import { type ScheduleSettings } from "../../programs.js";
8
+ import type { ScheduleNextInput, ScheduleNextOutput } from "../../types.js";
9
+ /**
10
+ * The next times of a cron expression
11
+ */
12
+ export declare function scheduleNext(input: ScheduleNextInput, settings?: Pick<ScheduleSettings, "now">): Promise<ScheduleNextOutput>;
13
+ //# sourceMappingURL=next.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../../src/procedures/schedule/next.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAE5E;;GAEG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,GAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAA6B,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAS7J"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * schedule.next procedure
3
+ *
4
+ * The next times of a cron expression. It reads no store: the output comes from the input and
5
+ * from the time.
6
+ */
7
+ import { formatTime, nextTimes, parseCron } from "../../cron.js";
8
+ import { ScheduleError } from "../../errors.js";
9
+ import { DEFAULT_SCHEDULE_SETTINGS } from "../../programs.js";
10
+ /**
11
+ * The next times of a cron expression
12
+ */
13
+ export async function scheduleNext(input, settings = DEFAULT_SCHEDULE_SETTINGS) {
14
+ const timeZone = input.timeZone ?? "local";
15
+ const cron = parseCron(input.cron);
16
+ let after = settings.now();
17
+ if (input.after !== undefined) {
18
+ after = Date.parse(input.after);
19
+ if (Number.isNaN(after))
20
+ throw new ScheduleError("BAD_CRON", `after must be an ISO time, not ${JSON.stringify(input.after)}`);
21
+ }
22
+ return { times: nextTimes(cron, after, input.count ?? 5, timeZone).map((time) => formatTime(time, timeZone)) };
23
+ }
24
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.js","sourceRoot":"","sources":["../../../src/procedures/schedule/next.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAyB,MAAM,mBAAmB,CAAC;AAGrF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAwB,EAAE,QAAQ,GAAkC,yBAAyB;IAC9H,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,OAAO,CAAC;IAC3C,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,aAAa,CAAC,UAAU,EAAE,kCAAkC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChI,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;AACjH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * schedule.remove procedure
3
+ *
4
+ * Remove a schedule. A run that started before stays until its end, and its record goes nowhere.
5
+ */
6
+ import { type ScheduleSettings } from "../../programs.js";
7
+ import type { ScheduleRemoveInput, ScheduleRemoveOutput } from "../../types.js";
8
+ /**
9
+ * Remove a schedule
10
+ */
11
+ export declare function scheduleRemove(input: ScheduleRemoveInput, settings?: ScheduleSettings): Promise<ScheduleRemoveOutput>;
12
+ //# sourceMappingURL=remove.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../../src/procedures/schedule/remove.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEhF;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,QAAQ,GAAE,gBAA4C,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAEtJ"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * schedule.remove procedure
3
+ *
4
+ * Remove a schedule. A run that started before stays until its end, and its record goes nowhere.
5
+ */
6
+ import { DEFAULT_SCHEDULE_SETTINGS } from "../../programs.js";
7
+ /**
8
+ * Remove a schedule
9
+ */
10
+ export async function scheduleRemove(input, settings = DEFAULT_SCHEDULE_SETTINGS) {
11
+ return { removed: await settings.store.delete(input.id) };
12
+ }
13
+ //# sourceMappingURL=remove.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove.js","sourceRoot":"","sources":["../../../src/procedures/schedule/remove.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,yBAAyB,EAAyB,MAAM,mBAAmB,CAAC;AAGrF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAA0B,EAAE,QAAQ,GAAqB,yBAAyB;IACrH,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The stored programs that a schedule starts, and the settings of the procedures.
3
+ *
4
+ * A schedule names a stored program: a procedure that `procedure.define` made, or a program of
5
+ * the program store of the registry (`@procedures/store`). A procedure of code is not a program:
6
+ * a schedule cannot start `shell.run` directly.
7
+ */
8
+ import { type ProcedureRegistryLike } from "@procedures/client";
9
+ import { type ScheduleStore } from "./store.js";
10
+ /** What the procedures of a module use: the store and the wall clock. */
11
+ export interface ScheduleSettings {
12
+ readonly store: ScheduleStore;
13
+ /** The wall time, in milliseconds since 1970. */
14
+ readonly now: () => number;
15
+ }
16
+ /** The settings of the default module: `defaultScheduleStore` and `Date.now`. */
17
+ export declare const DEFAULT_SCHEDULE_SETTINGS: ScheduleSettings;
18
+ /** The form of the key of a program: segments without spaces, joined with dots. */
19
+ export declare const PROGRAM_KEY: RegExp;
20
+ /**
21
+ * This function tells where the stored program of a key is: in the registry (a procedure that
22
+ * `procedure.define` made), or only in the program store of the registry. It throws
23
+ * `NOT_A_PROGRAM` for a procedure of code and `NOT_FOUND` for no program.
24
+ */
25
+ export declare function findProgram(registry: ProcedureRegistryLike, key: string): Promise<"registry" | "store">;
26
+ //# sourceMappingURL=programs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"programs.d.ts","sourceRoot":"","sources":["../src/programs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA+B,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG7F,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtE,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,iFAAiF;AACjF,eAAO,MAAM,yBAAyB,EAAE,gBAAwF,CAAC;AAEjI,mFAAmF;AACnF,eAAO,MAAM,WAAW,EAAE,MAAgC,CAAC;AAE3D;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,CAW7G"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The stored programs that a schedule starts, and the settings of the procedures.
3
+ *
4
+ * A schedule names a stored program: a procedure that `procedure.define` made, or a program of
5
+ * the program store of the registry (`@procedures/store`). A procedure of code is not a program:
6
+ * a schedule cannot start `shell.run` directly.
7
+ */
8
+ import { isRuntimeDefined, keyToPath } from "@procedures/client";
9
+ import { getProgramStore } from "@procedures/store";
10
+ import { ScheduleError } from "./errors.js";
11
+ import { defaultScheduleStore } from "./store.js";
12
+ /** The settings of the default module: `defaultScheduleStore` and `Date.now`. */
13
+ export const DEFAULT_SCHEDULE_SETTINGS = Object.freeze({ store: defaultScheduleStore, now: () => Date.now() });
14
+ /** The form of the key of a program: segments without spaces, joined with dots. */
15
+ export const PROGRAM_KEY = /^[^.\s]+(\.[^.\s]+)*$/;
16
+ /**
17
+ * This function tells where the stored program of a key is: in the registry (a procedure that
18
+ * `procedure.define` made), or only in the program store of the registry. It throws
19
+ * `NOT_A_PROGRAM` for a procedure of code and `NOT_FOUND` for no program.
20
+ */
21
+ export async function findProgram(registry, key) {
22
+ const procedure = registry.get(keyToPath(key));
23
+ if (procedure !== undefined) {
24
+ if (!isRuntimeDefined(procedure)) {
25
+ throw new ScheduleError("NOT_A_PROGRAM", `${key} is a procedure of code, not a stored program. A schedule starts only a program of procedure.define or of the program store.`);
26
+ }
27
+ return "registry";
28
+ }
29
+ const programs = getProgramStore(registry);
30
+ if (programs !== undefined && (await programs.get(key)) !== undefined)
31
+ return "store";
32
+ throw new ScheduleError("NOT_FOUND", `No stored program has the key ${key}: it is not in the registry and not in its program store`);
33
+ }
34
+ //# sourceMappingURL=programs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"programs.js","sourceRoot":"","sources":["../src/programs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAA8B,MAAM,oBAAoB,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAsB,MAAM,YAAY,CAAC;AAStE,iFAAiF;AACjF,MAAM,CAAC,MAAM,yBAAyB,GAAqB,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAEjI,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAW,uBAAuB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAA+B,EAAE,GAAW;IAC5E,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,aAAa,CAAC,eAAe,EAAE,GAAG,GAAG,8HAA8H,CAAC,CAAC;QACjL,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IACtF,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,iCAAiC,GAAG,0DAA0D,CAAC,CAAC;AACvI,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Importing this file registers the procedures of @procedures/schedule in the global registry:
3
+ * `import "@procedures/schedule/register"`. The package root registers nothing.
4
+ */
5
+ export * from "./module.js";
6
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,cAAc,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Importing this file registers the procedures of @procedures/schedule in the global registry:
3
+ * `import "@procedures/schedule/register"`. The package root registers nothing.
4
+ */
5
+ import { registerScheduleProcedures } from "./module.js";
6
+ export * from "./module.js";
7
+ registerScheduleProcedures();
8
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD,cAAc,aAAa,CAAC;AAE5B,0BAA0B,EAAE,CAAC"}
@@ -0,0 +1,106 @@
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 { type Capability, type Clock, type HostPolicy, type ProcedureRegistry } from "@procedures/client";
22
+ import { type ScheduleStore } from "./store.js";
23
+ /** The default time limit of a run: 10 minutes. */
24
+ export declare const DEFAULT_RUN_TIMEOUT_MS = 600000;
25
+ /** What a host gets after each run and each skipped time. */
26
+ export interface ScheduleRunEvent {
27
+ readonly id: string;
28
+ readonly program: string;
29
+ readonly outcome: "success" | "failure" | "skipped";
30
+ /** The wall time of the start, in milliseconds since 1970. */
31
+ readonly startedAt: number;
32
+ readonly durationMs: number;
33
+ readonly error?: {
34
+ readonly code?: string | undefined;
35
+ readonly message: string;
36
+ } | undefined;
37
+ }
38
+ /** The options of a `Scheduler`. */
39
+ export interface SchedulerOptions {
40
+ /** The registry of the programs. The default is the global registry. */
41
+ readonly registry?: ProcedureRegistry | undefined;
42
+ /** The store of the schedules: the store of the module of the procedures. The default is `defaultScheduleStore`. */
43
+ readonly store?: ScheduleStore | undefined;
44
+ /** The capability of the host for each run. The default is the capability of `policy`, else no limits. */
45
+ readonly capability?: Capability | undefined;
46
+ /** The policy of the host: its expose rule, call rule, interceptors, clearance order, resources and capability. */
47
+ readonly policy?: HostPolicy | undefined;
48
+ /** The clock of the timers and of the durations. The default is `realClock`. */
49
+ readonly clock?: Clock | undefined;
50
+ /** The wall time, in milliseconds since 1970. The default is `Date.now`. */
51
+ readonly now?: (() => number) | undefined;
52
+ /** The time limit of a run, in milliseconds. 0: no limit. The default is 10 minutes. */
53
+ readonly runTimeoutMs?: number | undefined;
54
+ /** This function gets each run and each skipped time. */
55
+ readonly onRun?: ((event: ScheduleRunEvent) => void) | undefined;
56
+ /** This function gets an error of the scheduler itself: a store that cannot be read, for example. */
57
+ readonly onError?: ((error: unknown) => void) | undefined;
58
+ }
59
+ /** A scheduler of the stored programs. See the comment of the file. */
60
+ export declare class Scheduler {
61
+ private readonly registry;
62
+ private readonly store;
63
+ private readonly clock;
64
+ private readonly now;
65
+ private readonly runTimeoutMs;
66
+ private readonly options;
67
+ private readonly due;
68
+ private readonly runs;
69
+ private startedAt;
70
+ private cancel;
71
+ private ticking;
72
+ constructor(options?: SchedulerOptions);
73
+ /** True after `start()` and before `stop()`. */
74
+ get started(): boolean;
75
+ /** The names of the schedules with a run that has not ended. */
76
+ get active(): string[];
77
+ /** This method starts the scheduler: the first times come after now. It gives the end of the first examination. */
78
+ start(): Promise<void>;
79
+ /** This method stops the timer, aborts the runs, and gives the end of the runs. */
80
+ stop(): Promise<void>;
81
+ /** This method gives the end of the examination in progress and of the runs in progress. */
82
+ settled(): Promise<void>;
83
+ /**
84
+ * This method examines the schedules now and starts each one whose time came. It does not wait
85
+ * for the runs. A second call during an examination gives the same examination.
86
+ */
87
+ tick(): Promise<void>;
88
+ /** This method starts one run of a schedule now, unless it has a run in progress. It gives the event of the run. */
89
+ runNow(id: string): Promise<ScheduleRunEvent>;
90
+ private examine;
91
+ /** This method arms the timer for the next minute, or for an earlier time that is due. */
92
+ private arm;
93
+ /** This method gives an event to `onRun`. An error of `onRun` goes to `onError`, not to the scheduler. */
94
+ private report;
95
+ /** This method records a time that came during a run of the same schedule. */
96
+ private skip;
97
+ /** This method starts a run, unless the schedule has one. It gives false for a schedule with a run. */
98
+ private launch;
99
+ /** One run: the program through the registry, then its record. */
100
+ private run;
101
+ /** The options of an invocation: the policy of the host, a capability and the signal of the run. */
102
+ private invokeOptions;
103
+ /** The program of a schedule through the registry, after a load from the program store when the registry does not have it. */
104
+ private invoke;
105
+ }
106
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
@@ -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