@nestjs/workflows 0.0.1

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 (265) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +50 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +8 -0
  5. package/dist/core/codecs/aes-gcm-payload.codec.d.ts +42 -0
  6. package/dist/core/codecs/aes-gcm-payload.codec.js +119 -0
  7. package/dist/core/codecs/index.d.ts +2 -0
  8. package/dist/core/codecs/index.js +2 -0
  9. package/dist/core/codecs/payload-codecs.d.ts +59 -0
  10. package/dist/core/codecs/payload-codecs.js +130 -0
  11. package/dist/core/index.d.ts +16 -0
  12. package/dist/core/index.js +21 -0
  13. package/dist/core/interfaces/clock.interface.d.ts +18 -0
  14. package/dist/core/interfaces/clock.interface.js +1 -0
  15. package/dist/core/interfaces/index.d.ts +9 -0
  16. package/dist/core/interfaces/index.js +9 -0
  17. package/dist/core/interfaces/leased-worker-options.interface.d.ts +94 -0
  18. package/dist/core/interfaces/leased-worker-options.interface.js +1 -0
  19. package/dist/core/interfaces/limits.interface.d.ts +92 -0
  20. package/dist/core/interfaces/limits.interface.js +1 -0
  21. package/dist/core/interfaces/payload-codec.interface.d.ts +64 -0
  22. package/dist/core/interfaces/payload-codec.interface.js +1 -0
  23. package/dist/core/interfaces/result-waiter-options.interface.d.ts +52 -0
  24. package/dist/core/interfaces/result-waiter-options.interface.js +1 -0
  25. package/dist/core/interfaces/retry-settings.interface.d.ts +71 -0
  26. package/dist/core/interfaces/retry-settings.interface.js +1 -0
  27. package/dist/core/interfaces/schedule-store.interface.d.ts +143 -0
  28. package/dist/core/interfaces/schedule-store.interface.js +1 -0
  29. package/dist/core/interfaces/schedule.interface.d.ts +106 -0
  30. package/dist/core/interfaces/schedule.interface.js +1 -0
  31. package/dist/core/interfaces/scheduler-options.interface.d.ts +152 -0
  32. package/dist/core/interfaces/scheduler-options.interface.js +1 -0
  33. package/dist/core/limits/index.d.ts +1 -0
  34. package/dist/core/limits/index.js +1 -0
  35. package/dist/core/limits/limits.d.ts +61 -0
  36. package/dist/core/limits/limits.js +130 -0
  37. package/dist/core/results/index.d.ts +1 -0
  38. package/dist/core/results/index.js +1 -0
  39. package/dist/core/results/result-waiter.d.ts +54 -0
  40. package/dist/core/results/result-waiter.js +140 -0
  41. package/dist/core/retries/index.d.ts +1 -0
  42. package/dist/core/retries/index.js +1 -0
  43. package/dist/core/retries/retry.d.ts +60 -0
  44. package/dist/core/retries/retry.js +107 -0
  45. package/dist/core/scheduling/cron.util.d.ts +52 -0
  46. package/dist/core/scheduling/cron.util.js +262 -0
  47. package/dist/core/scheduling/encoded-schedule.store.d.ts +26 -0
  48. package/dist/core/scheduling/encoded-schedule.store.js +54 -0
  49. package/dist/core/scheduling/in-memory-schedule.store.d.ts +20 -0
  50. package/dist/core/scheduling/in-memory-schedule.store.js +90 -0
  51. package/dist/core/scheduling/index.d.ts +3 -0
  52. package/dist/core/scheduling/index.js +3 -0
  53. package/dist/core/scheduling/rrule.util.d.ts +55 -0
  54. package/dist/core/scheduling/rrule.util.js +371 -0
  55. package/dist/core/scheduling/schedule-spec.d.ts +57 -0
  56. package/dist/core/scheduling/schedule-spec.js +233 -0
  57. package/dist/core/scheduling/scheduler.d.ts +134 -0
  58. package/dist/core/scheduling/scheduler.js +450 -0
  59. package/dist/core/scheduling/time-zone.util.d.ts +18 -0
  60. package/dist/core/scheduling/time-zone.util.js +88 -0
  61. package/dist/core/time/clock.d.ts +28 -0
  62. package/dist/core/time/clock.js +35 -0
  63. package/dist/core/time/duration.d.ts +21 -0
  64. package/dist/core/time/duration.js +30 -0
  65. package/dist/core/time/index.d.ts +2 -0
  66. package/dist/core/time/index.js +2 -0
  67. package/dist/core/utils/canonical.util.d.ts +2 -0
  68. package/dist/core/utils/canonical.util.js +4 -0
  69. package/dist/core/utils/maybe.util.d.ts +8 -0
  70. package/dist/core/utils/maybe.util.js +14 -0
  71. package/dist/core/utils/payload-place.util.d.ts +10 -0
  72. package/dist/core/utils/payload-place.util.js +32 -0
  73. package/dist/core/workers/index.d.ts +1 -0
  74. package/dist/core/workers/index.js +1 -0
  75. package/dist/core/workers/leased-worker.d.ts +86 -0
  76. package/dist/core/workers/leased-worker.js +280 -0
  77. package/dist/cqrs/decorators/index.d.ts +2 -0
  78. package/dist/cqrs/decorators/index.js +2 -0
  79. package/dist/cqrs/decorators/signal-on.decorator.d.ts +18 -0
  80. package/dist/cqrs/decorators/signal-on.decorator.js +34 -0
  81. package/dist/cqrs/decorators/start-on.decorator.d.ts +18 -0
  82. package/dist/cqrs/decorators/start-on.decorator.js +49 -0
  83. package/dist/cqrs/index.d.ts +3 -0
  84. package/dist/cqrs/index.js +5 -0
  85. package/dist/cqrs/interfaces/index.d.ts +4 -0
  86. package/dist/cqrs/interfaces/index.js +4 -0
  87. package/dist/cqrs/interfaces/signal-on-options.interface.d.ts +22 -0
  88. package/dist/cqrs/interfaces/signal-on-options.interface.js +1 -0
  89. package/dist/cqrs/interfaces/start-on-options.interface.d.ts +23 -0
  90. package/dist/cqrs/interfaces/start-on-options.interface.js +1 -0
  91. package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.d.ts +10 -0
  92. package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.js +1 -0
  93. package/dist/cqrs/interfaces/workflow-event-route.interface.d.ts +20 -0
  94. package/dist/cqrs/interfaces/workflow-event-route.interface.js +1 -0
  95. package/dist/cqrs/utils/event-routes.util.d.ts +7 -0
  96. package/dist/cqrs/utils/event-routes.util.js +22 -0
  97. package/dist/cqrs/workflow-event.publisher.d.ts +52 -0
  98. package/dist/cqrs/workflow-event.publisher.js +257 -0
  99. package/dist/cqrs/workflow-events.explorer.d.ts +27 -0
  100. package/dist/cqrs/workflow-events.explorer.js +92 -0
  101. package/dist/cqrs/workflows-cqrs.module.d.ts +10 -0
  102. package/dist/cqrs/workflows-cqrs.module.js +27 -0
  103. package/dist/decorators/index.d.ts +1 -0
  104. package/dist/decorators/index.js +1 -0
  105. package/dist/decorators/workflow.decorator.d.ts +7 -0
  106. package/dist/decorators/workflow.decorator.js +52 -0
  107. package/dist/errors/child-workflow-failed.error.d.ts +13 -0
  108. package/dist/errors/child-workflow-failed.error.js +14 -0
  109. package/dist/errors/index.d.ts +13 -0
  110. package/dist/errors/index.js +13 -0
  111. package/dist/errors/non-retryable-step.error.d.ts +4 -0
  112. package/dist/errors/non-retryable-step.error.js +7 -0
  113. package/dist/errors/step-failed.error.d.ts +15 -0
  114. package/dist/errors/step-failed.error.js +16 -0
  115. package/dist/errors/step-timeout.error.d.ts +5 -0
  116. package/dist/errors/step-timeout.error.js +8 -0
  117. package/dist/errors/workflow-definition.error.d.ts +10 -0
  118. package/dist/errors/workflow-definition.error.js +13 -0
  119. package/dist/errors/workflow-failed.error.d.ts +20 -0
  120. package/dist/errors/workflow-failed.error.js +17 -0
  121. package/dist/errors/workflow-id-conflict.error.d.ts +6 -0
  122. package/dist/errors/workflow-id-conflict.error.js +9 -0
  123. package/dist/errors/workflow-interrupt.error.d.ts +32 -0
  124. package/dist/errors/workflow-interrupt.error.js +19 -0
  125. package/dist/errors/workflow-non-determinism.error.d.ts +4 -0
  126. package/dist/errors/workflow-non-determinism.error.js +7 -0
  127. package/dist/errors/workflow-not-found.error.d.ts +10 -0
  128. package/dist/errors/workflow-not-found.error.js +13 -0
  129. package/dist/errors/workflow-result-timeout.error.d.ts +11 -0
  130. package/dist/errors/workflow-result-timeout.error.js +13 -0
  131. package/dist/errors/workflow-state.error.d.ts +11 -0
  132. package/dist/errors/workflow-state.error.js +14 -0
  133. package/dist/errors/workflow.error.d.ts +7 -0
  134. package/dist/errors/workflow.error.js +7 -0
  135. package/dist/events/index.d.ts +3 -0
  136. package/dist/events/index.js +3 -0
  137. package/dist/events/workflow-events.interface.d.ts +145 -0
  138. package/dist/events/workflow-events.interface.js +1 -0
  139. package/dist/events/workflow-events.service.d.ts +17 -0
  140. package/dist/events/workflow-events.service.js +38 -0
  141. package/dist/events/workflows.channels.d.ts +3 -0
  142. package/dist/events/workflows.channels.js +9 -0
  143. package/dist/index.d.ts +20 -0
  144. package/dist/index.js +23 -0
  145. package/dist/interfaces/index.d.ts +13 -0
  146. package/dist/interfaces/index.js +13 -0
  147. package/dist/interfaces/serialized-workflow-error.interface.d.ts +6 -0
  148. package/dist/interfaces/serialized-workflow-error.interface.js +1 -0
  149. package/dist/interfaces/workflow-client.interface.d.ts +156 -0
  150. package/dist/interfaces/workflow-client.interface.js +1 -0
  151. package/dist/interfaces/workflow-clock.interface.d.ts +7 -0
  152. package/dist/interfaces/workflow-clock.interface.js +1 -0
  153. package/dist/interfaces/workflow-context.interface.d.ts +241 -0
  154. package/dist/interfaces/workflow-context.interface.js +1 -0
  155. package/dist/interfaces/workflow-decorator-options.interface.d.ts +101 -0
  156. package/dist/interfaces/workflow-decorator-options.interface.js +1 -0
  157. package/dist/interfaces/workflow-instance.interface.d.ts +83 -0
  158. package/dist/interfaces/workflow-instance.interface.js +1 -0
  159. package/dist/interfaces/workflow-payload-codec.interface.d.ts +32 -0
  160. package/dist/interfaces/workflow-payload-codec.interface.js +1 -0
  161. package/dist/interfaces/workflow-retry-options.interface.d.ts +9 -0
  162. package/dist/interfaces/workflow-retry-options.interface.js +1 -0
  163. package/dist/interfaces/workflow-runner.interface.d.ts +11 -0
  164. package/dist/interfaces/workflow-runner.interface.js +1 -0
  165. package/dist/interfaces/workflow-schedule.interface.d.ts +104 -0
  166. package/dist/interfaces/workflow-schedule.interface.js +1 -0
  167. package/dist/interfaces/workflow-storage.interface.d.ts +4 -0
  168. package/dist/interfaces/workflow-storage.interface.js +1 -0
  169. package/dist/interfaces/workflow-store.interface.d.ts +488 -0
  170. package/dist/interfaces/workflow-store.interface.js +1 -0
  171. package/dist/interfaces/workflows-module-options.interface.d.ts +80 -0
  172. package/dist/interfaces/workflows-module-options.interface.js +1 -0
  173. package/dist/mysql/index.d.ts +5 -0
  174. package/dist/mysql/index.js +8 -0
  175. package/dist/mysql/interfaces/index.d.ts +1 -0
  176. package/dist/mysql/interfaces/index.js +1 -0
  177. package/dist/mysql/interfaces/mysql-workflow-store-options.interface.d.ts +25 -0
  178. package/dist/mysql/interfaces/mysql-workflow-store-options.interface.js +1 -0
  179. package/dist/mysql/key-limits.d.ts +30 -0
  180. package/dist/mysql/key-limits.js +41 -0
  181. package/dist/mysql/migrations/index.d.ts +7 -0
  182. package/dist/mysql/migrations/index.js +16 -0
  183. package/dist/mysql/migrations/initial.migration.d.ts +9 -0
  184. package/dist/mysql/migrations/initial.migration.js +104 -0
  185. package/dist/mysql/mysql-workflow.store.d.ts +197 -0
  186. package/dist/mysql/mysql-workflow.store.js +828 -0
  187. package/dist/postgres/errors/index.d.ts +1 -0
  188. package/dist/postgres/errors/index.js +2 -0
  189. package/dist/postgres/index.d.ts +5 -0
  190. package/dist/postgres/index.js +8 -0
  191. package/dist/postgres/interfaces/index.d.ts +1 -0
  192. package/dist/postgres/interfaces/index.js +1 -0
  193. package/dist/postgres/interfaces/postgres-workflow-store-options.interface.d.ts +22 -0
  194. package/dist/postgres/interfaces/postgres-workflow-store-options.interface.js +1 -0
  195. package/dist/postgres/migrations/index.d.ts +6 -0
  196. package/dist/postgres/migrations/index.js +15 -0
  197. package/dist/postgres/migrations/initial.migration.d.ts +6 -0
  198. package/dist/postgres/migrations/initial.migration.js +106 -0
  199. package/dist/postgres/postgres-workflow.store.d.ts +132 -0
  200. package/dist/postgres/postgres-workflow.store.js +586 -0
  201. package/dist/services/index.d.ts +6 -0
  202. package/dist/services/index.js +6 -0
  203. package/dist/services/workflow-client.service.d.ts +146 -0
  204. package/dist/services/workflow-client.service.js +436 -0
  205. package/dist/services/workflow-execution.service.d.ts +267 -0
  206. package/dist/services/workflow-execution.service.js +1076 -0
  207. package/dist/services/workflow-registry.service.d.ts +56 -0
  208. package/dist/services/workflow-registry.service.js +172 -0
  209. package/dist/services/workflow-scheduler.service.d.ts +54 -0
  210. package/dist/services/workflow-scheduler.service.js +173 -0
  211. package/dist/services/workflow-schedules.service.d.ts +60 -0
  212. package/dist/services/workflow-schedules.service.js +193 -0
  213. package/dist/services/workflow-worker.service.d.ts +92 -0
  214. package/dist/services/workflow-worker.service.js +468 -0
  215. package/dist/signals/index.d.ts +1 -0
  216. package/dist/signals/index.js +1 -0
  217. package/dist/signals/workflow.signal.d.ts +17 -0
  218. package/dist/signals/workflow.signal.js +28 -0
  219. package/dist/sql/rate-windows.d.ts +41 -0
  220. package/dist/sql/rate-windows.js +53 -0
  221. package/dist/sql/workflow-rows.d.ts +16 -0
  222. package/dist/sql/workflow-rows.js +102 -0
  223. package/dist/sql/workflow-schema.error.d.ts +21 -0
  224. package/dist/sql/workflow-schema.error.js +15 -0
  225. package/dist/storage/encoded-workflow.store.d.ts +71 -0
  226. package/dist/storage/encoded-workflow.store.js +201 -0
  227. package/dist/storage/index.d.ts +1 -0
  228. package/dist/storage/index.js +1 -0
  229. package/dist/storage/workflow.storage.d.ts +55 -0
  230. package/dist/storage/workflow.storage.js +159 -0
  231. package/dist/stores/in-memory-workflow.store.d.ts +75 -0
  232. package/dist/stores/in-memory-workflow.store.js +380 -0
  233. package/dist/stores/index.d.ts +1 -0
  234. package/dist/stores/index.js +1 -0
  235. package/dist/testing/assertions.d.ts +10 -0
  236. package/dist/testing/assertions.js +72 -0
  237. package/dist/testing/index.d.ts +38 -0
  238. package/dist/testing/index.js +1089 -0
  239. package/dist/testing/schedule-store.contract.d.ts +33 -0
  240. package/dist/testing/schedule-store.contract.js +161 -0
  241. package/dist/utils/index.d.ts +7 -0
  242. package/dist/utils/index.js +7 -0
  243. package/dist/utils/journal-limits.util.d.ts +6 -0
  244. package/dist/utils/journal-limits.util.js +25 -0
  245. package/dist/utils/new-instance.util.d.ts +26 -0
  246. package/dist/utils/new-instance.util.js +87 -0
  247. package/dist/utils/normalize.util.d.ts +5 -0
  248. package/dist/utils/normalize.util.js +11 -0
  249. package/dist/utils/run-timeout.util.d.ts +3 -0
  250. package/dist/utils/run-timeout.util.js +15 -0
  251. package/dist/utils/schedule-spec.util.d.ts +16 -0
  252. package/dist/utils/schedule-spec.util.js +9 -0
  253. package/dist/utils/schedule-store.util.d.ts +15 -0
  254. package/dist/utils/schedule-store.util.js +44 -0
  255. package/dist/utils/serialize-error.util.d.ts +7 -0
  256. package/dist/utils/serialize-error.util.js +22 -0
  257. package/dist/utils/step-scope.util.d.ts +16 -0
  258. package/dist/utils/step-scope.util.js +38 -0
  259. package/dist/workflows.constants.d.ts +10 -0
  260. package/dist/workflows.constants.js +10 -0
  261. package/dist/workflows.module-definition.d.ts +4 -0
  262. package/dist/workflows.module-definition.js +6 -0
  263. package/dist/workflows.module.d.ts +24 -0
  264. package/dist/workflows.module.js +68 -0
  265. package/package.json +123 -0
@@ -0,0 +1,55 @@
1
+ declare const FREQUENCIES: readonly ['SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'];
2
+ /** FREQ: the length of the periods a rule repeats in. */
3
+ export type RRuleFrequency = (typeof FREQUENCIES)[number];
4
+ /** A BYDAY entry: `weekday` 0-6 (Sunday is 0); `nth` the n-th such day, negative from the end (`-1FR`), 0 for all. */
5
+ export interface RRuleWeekday {
6
+ readonly weekday: number;
7
+ readonly nth: number;
8
+ }
9
+ /** UNTIL: `YYYYMMDD` (`date`, through the end of that day), `YYYYMMDDTHHMMSS` (`local`) or `YYYYMMDDTHHMMSSZ` (`utc`). */
10
+ export interface RRuleUntil {
11
+ /** A wall time in the schedule's zone (midnight of the day for `date`), or an instant for `utc`. */
12
+ readonly time: number;
13
+ readonly form: 'date' | 'local' | 'utc';
14
+ }
15
+ /** A parsed RRULE; a missing BYxxx part is null. */
16
+ export interface RRule {
17
+ /** The rule as given. */
18
+ readonly source: string;
19
+ readonly freq: RRuleFrequency;
20
+ readonly interval: number;
21
+ readonly count: number | null;
22
+ readonly until: RRuleUntil | null;
23
+ /** WKST, 0-6 (Sunday is 0; Monday by default). */
24
+ readonly weekStart: number;
25
+ readonly byMonth: readonly number[] | null;
26
+ readonly byMonthDay: readonly number[] | null;
27
+ readonly byDay: readonly RRuleWeekday[] | null;
28
+ readonly byHour: readonly number[] | null;
29
+ readonly byMinute: readonly number[] | null;
30
+ readonly bySecond: readonly number[] | null;
31
+ }
32
+ /**
33
+ * Parses an RFC 5545 (§3.3.10) recurrence rule such as "FREQ=WEEKLY;BYDAY=MO;BYHOUR=8", with an optional "RRULE:"
34
+ * prefix, in any case. Parts: FREQ (required), INTERVAL, COUNT or UNTIL, WKST, BYMONTH, BYMONTHDAY (not with
35
+ * WEEKLY), BYDAY (numbered, like `1MO` or `-1FR`, only with MONTHLY or YEARLY), BYHOUR, BYMINUTE and BYSECOND.
36
+ * Throws a TypeError for anything else, for a repeated part, and for a rule that never matches (BYMONTH=2;BYMONTHDAY=30).
37
+ */
38
+ export declare function parseRRule(rule: string): RRule;
39
+ /**
40
+ * Occurrences strictly after `after`, ascending, strictly increasing. `start` is DTSTART as an instant (the
41
+ * schedule's startAt) or null: then DTSTART is midnight, 1 January 1970, wall time in `tz` (a Thursday). DTSTART
42
+ * gives the defaults (a missing BYMONTH/BYMONTHDAY/BYDAY/BYHOUR/BYMINUTE/BYSECOND takes DTSTART's value, as RFC 5545
43
+ * says, so "FREQ=WEEKLY;BYDAY=MO;BYHOUR=8" is Mondays 08:00:00 without a start) and the INTERVAL phase; with a
44
+ * `start`, nothing before it occurs. COUNT counts occurrences from DTSTART (and requires `start`); UNTIL is inclusive.
45
+ *
46
+ * Unlike RFC 5545, which counts DTSTART as the first occurrence whether or not it fits the rule, a DTSTART that
47
+ * doesn't fit isn't returned (nor counted), as in the common libraries (python-dateutil, rrule.js). Occurrences are
48
+ * wall times in `tz` mapped as `fromWallTime` does: one skipped by clocks going forward runs that much later (unless
49
+ * that instant already occurs), one repeated by clocks going back runs once, the first time. HOURLY, MINUTELY and
50
+ * SECONDLY periods count wall time, so FREQ=HOURLY runs once in the hour clocks repeat.
51
+ */
52
+ export declare function rruleOccurrences(rule: RRule, start: number | null, tz: string, after: number): Generator<number>;
53
+ /** Whether the rule uses COUNT (the caller requires a start for it). */
54
+ export declare function rruleCounts(rule: RRule): boolean;
55
+ export {};
@@ -0,0 +1,371 @@
1
+ import { fromWallTime, recentClockChange, toWallTime } from './time-zone.util.js';
2
+ const SECOND = 1_000;
3
+ const MINUTE = 60_000;
4
+ const HOUR = 3_600_000;
5
+ const DAY = 86_400_000;
6
+ const IDLE_PERIODS = 10_000;
7
+ const FREQUENCIES = ['SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'];
8
+ const PARTS = ['FREQ', 'INTERVAL', 'COUNT', 'UNTIL', 'WKST', 'BYMONTH', 'BYMONTHDAY', 'BYDAY', 'BYHOUR', 'BYMINUTE', 'BYSECOND'];
9
+ const WEEKDAYS = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
10
+ const ALL_MONTHS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
11
+ const UNITS = { DAILY: DAY, HOURLY: HOUR, MINUTELY: MINUTE, SECONDLY: SECOND };
12
+ /**
13
+ * Parses an RFC 5545 (§3.3.10) recurrence rule such as "FREQ=WEEKLY;BYDAY=MO;BYHOUR=8", with an optional "RRULE:"
14
+ * prefix, in any case. Parts: FREQ (required), INTERVAL, COUNT or UNTIL, WKST, BYMONTH, BYMONTHDAY (not with
15
+ * WEEKLY), BYDAY (numbered, like `1MO` or `-1FR`, only with MONTHLY or YEARLY), BYHOUR, BYMINUTE and BYSECOND.
16
+ * Throws a TypeError for anything else, for a repeated part, and for a rule that never matches (BYMONTH=2;BYMONTHDAY=30).
17
+ */
18
+ export function parseRRule(rule) {
19
+ if (typeof rule !== 'string') {
20
+ throw new TypeError(`Invalid RRULE ${JSON.stringify(rule)}. Use a string, such as "FREQ=WEEKLY;BYDAY=MO;BYHOUR=8".`);
21
+ }
22
+ const parts = new Map();
23
+ for (const part of rule.trim().replace(/^RRULE:/i, '').split(';')) {
24
+ if (part.trim() === '') {
25
+ continue;
26
+ }
27
+ const match = /^([A-Za-z][A-Za-z0-9-]*)=(.*)$/.exec(part.trim());
28
+ if (!match) {
29
+ throw new TypeError(`Invalid RRULE part "${part}" in "${rule}": use NAME=VALUE parts separated by ";", such as FREQ=DAILY;BYHOUR=8.`);
30
+ }
31
+ const name = match[1].toUpperCase();
32
+ if (!PARTS.includes(name)) {
33
+ throw new TypeError(`Unsupported RRULE part "${name}" in "${rule}": use FREQ, INTERVAL, COUNT, UNTIL, WKST, BYMONTH, BYMONTHDAY, BYDAY, BYHOUR, BYMINUTE or BYSECOND.`);
34
+ }
35
+ if (parts.has(name)) {
36
+ throw new TypeError(`Invalid RRULE "${rule}": ${name} is given twice; give each part once.`);
37
+ }
38
+ parts.set(name, match[2].trim().toUpperCase());
39
+ }
40
+ const invalid = (reason) => new TypeError(`Invalid RRULE "${rule}": ${reason}.`);
41
+ const failIn = (name) => (reason) => {
42
+ throw new TypeError(`Invalid RRULE part "${name}=${parts.get(name)}" in "${rule}": ${reason}.`);
43
+ };
44
+ const freq = FREQUENCIES.find((frequency) => frequency === parts.get('FREQ'));
45
+ if (!parts.has('FREQ')) {
46
+ throw invalid('FREQ is required, such as FREQ=DAILY');
47
+ }
48
+ if (freq === undefined) {
49
+ throw new TypeError(`Invalid RRULE part "FREQ=${parts.get('FREQ')}" in "${rule}": use SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY or YEARLY.`);
50
+ }
51
+ if (parts.has('COUNT') && parts.has('UNTIL')) {
52
+ throw invalid('use COUNT or UNTIL, not both');
53
+ }
54
+ const parsed = {
55
+ source: rule,
56
+ freq,
57
+ interval: parts.has('INTERVAL') ? positive(parts.get('INTERVAL'), failIn('INTERVAL')) : 1,
58
+ count: parts.has('COUNT') ? positive(parts.get('COUNT'), failIn('COUNT')) : null,
59
+ until: parts.has('UNTIL') ? parseUntil(parts.get('UNTIL'), failIn('UNTIL')) : null,
60
+ weekStart: parts.has('WKST') ? weekday(parts.get('WKST'), failIn('WKST')) : 1,
61
+ byMonth: numbers(parts.get('BYMONTH'), 1, 12, false, failIn('BYMONTH')),
62
+ byMonthDay: numbers(parts.get('BYMONTHDAY'), 1, 31, true, failIn('BYMONTHDAY')),
63
+ byDay: weekdays(parts.get('BYDAY'), failIn('BYDAY')),
64
+ byHour: numbers(parts.get('BYHOUR'), 0, 23, false, failIn('BYHOUR')),
65
+ byMinute: numbers(parts.get('BYMINUTE'), 0, 59, false, failIn('BYMINUTE')),
66
+ bySecond: numbers(parts.get('BYSECOND'), 0, 59, false, failIn('BYSECOND')),
67
+ };
68
+ if (parsed.byMonthDay !== null && parsed.freq === 'WEEKLY') {
69
+ throw invalid("BYMONTHDAY doesn't go with FREQ=WEEKLY (RFC 5545); use BYDAY");
70
+ }
71
+ if (parsed.byDay?.some(({ nth }) => nth !== 0) && parsed.freq !== 'MONTHLY' && parsed.freq !== 'YEARLY') {
72
+ throw invalid('a numbered BYDAY (such as 1MO or -1FR) needs FREQ=MONTHLY or FREQ=YEARLY');
73
+ }
74
+ // Probed without INTERVAL, whose phase depends on the start: this catches BYxxx parts that can never meet.
75
+ const probe = { ...parsed, interval: 1, count: null, until: null };
76
+ if (rruleOccurrences(probe, null, 'UTC', Number.NEGATIVE_INFINITY).next().done) {
77
+ throw invalid('it never matches, as no date fits all of its BYxxx parts');
78
+ }
79
+ return parsed;
80
+ }
81
+ /**
82
+ * Occurrences strictly after `after`, ascending, strictly increasing. `start` is DTSTART as an instant (the
83
+ * schedule's startAt) or null: then DTSTART is midnight, 1 January 1970, wall time in `tz` (a Thursday). DTSTART
84
+ * gives the defaults (a missing BYMONTH/BYMONTHDAY/BYDAY/BYHOUR/BYMINUTE/BYSECOND takes DTSTART's value, as RFC 5545
85
+ * says, so "FREQ=WEEKLY;BYDAY=MO;BYHOUR=8" is Mondays 08:00:00 without a start) and the INTERVAL phase; with a
86
+ * `start`, nothing before it occurs. COUNT counts occurrences from DTSTART (and requires `start`); UNTIL is inclusive.
87
+ *
88
+ * Unlike RFC 5545, which counts DTSTART as the first occurrence whether or not it fits the rule, a DTSTART that
89
+ * doesn't fit isn't returned (nor counted), as in the common libraries (python-dateutil, rrule.js). Occurrences are
90
+ * wall times in `tz` mapped as `fromWallTime` does: one skipped by clocks going forward runs that much later (unless
91
+ * that instant already occurs), one repeated by clocks going back runs once, the first time. HOURLY, MINUTELY and
92
+ * SECONDLY periods count wall time, so FREQ=HOURLY runs once in the hour clocks repeat.
93
+ */
94
+ export function rruleOccurrences(rule, start, tz, after) {
95
+ if (rule.count !== null && start === null) {
96
+ throw new TypeError(`The RRULE "${rule.source}" has a COUNT, which counts from the schedule's start: give it one.`);
97
+ }
98
+ return occurrences(rule, start, tz, after);
99
+ }
100
+ /** Whether the rule uses COUNT (the caller requires a start for it). */
101
+ export function rruleCounts(rule) {
102
+ return rule.count !== null;
103
+ }
104
+ function* occurrences(rule, start, tz, after) {
105
+ const dtstart = start === null ? Date.UTC(1970, 0, 1) : Math.floor(toWallTime(start, tz) / SECOND) * SECOND;
106
+ const first = start ?? fromWallTime(dtstart, tz);
107
+ const until = rule.until === null ? null : untilInstant(rule.until, tz);
108
+ // Right after clocks went forward a skipped wall time can still land after `after`; right after they went back, a
109
+ // wall time past UNTIL's own can still be before it. Both widen the wall times scanned by the clocks' jump.
110
+ const lastWall = until === null ? Infinity : toWallTime(until, tz) + Math.max(0, -recentClockChange(until, tz));
111
+ const from = rule.count !== null || after < first ? dtstart : toWallTime(after, tz) - Math.max(0, recentClockChange(after, tz));
112
+ let counted = 0;
113
+ for (const instant of ascending(wallTimes(plan(rule, dtstart), dtstart, from), tz, lastWall)) {
114
+ if (instant < first) {
115
+ continue;
116
+ }
117
+ if (until !== null && instant > until) {
118
+ return;
119
+ }
120
+ counted += 1;
121
+ if (instant > after) {
122
+ yield instant;
123
+ }
124
+ if (counted === rule.count) {
125
+ return;
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * The wall times the rule generates, ascending, from the period that holds `from` (on the INTERVAL grid from
131
+ * DTSTART's period). Expansion follows RFC 5545's table: a BYxxx part coarser than FREQ limits, a finer one expands.
132
+ * Stops after 10,000 periods in a row without one.
133
+ */
134
+ function* wallTimes(plan, dtstart, from) {
135
+ const origin = periodOf(plan, dtstart);
136
+ const onGrid = (period) => origin + Math.ceil((period - origin) / plan.interval) * plan.interval;
137
+ let period = origin + Math.floor((periodOf(plan, from) - origin) / plan.interval) * plan.interval;
138
+ for (let idle = 0; idle < IDLE_PERIODS;) {
139
+ const { walls, next } = expand(plan, period);
140
+ idle = walls.length === 0 ? idle + 1 : 0;
141
+ yield* walls;
142
+ period = onGrid(Math.max(next, period + 1));
143
+ }
144
+ }
145
+ /**
146
+ * The instants of `walls`, ascending and each once. A wall time in a gap lands that much later, possibly after the
147
+ * instants of the wall times that follow it, so each instant waits until the walls pass where it landed.
148
+ */
149
+ function* ascending(walls, tz, lastWall) {
150
+ const pending = [];
151
+ let last = Number.NEGATIVE_INFINITY;
152
+ for (const wall of walls) {
153
+ while (pending.length > 0 && pending[0].landed < wall) {
154
+ last = pending.shift().instant;
155
+ yield last;
156
+ }
157
+ if (wall > lastWall) {
158
+ break;
159
+ }
160
+ const instant = fromWallTime(wall, tz);
161
+ let at = pending.length;
162
+ while (at > 0 && pending[at - 1].instant > instant) {
163
+ at -= 1;
164
+ }
165
+ if (instant > last && pending[at - 1]?.instant !== instant) {
166
+ pending.splice(at, 0, { instant, landed: toWallTime(instant, tz) });
167
+ }
168
+ }
169
+ for (const { instant } of pending) {
170
+ yield instant;
171
+ }
172
+ }
173
+ /** The wall times of `period`, and the period to look at next (a later one when a limit rules out a whole day or month). */
174
+ function expand(plan, period) {
175
+ switch (plan.freq) {
176
+ case 'YEARLY':
177
+ return { walls: (plan.months ?? ALL_MONTHS).flatMap((month) => monthWalls(plan, period, month)), next: period + 1 };
178
+ case 'MONTHLY':
179
+ return { walls: monthWalls(plan, Math.floor(period / 12), (period % 12) + 1), next: period + 1 };
180
+ case 'WEEKLY':
181
+ return { walls: weekWalls(plan, period), next: period + 1 };
182
+ default:
183
+ return unitWalls(plan, period, UNITS[plan.freq]);
184
+ }
185
+ }
186
+ function monthWalls(plan, year, month) {
187
+ const walls = [];
188
+ const length = daysIn(year, month);
189
+ for (let day = 1; day <= length; day++) {
190
+ walls.push(...dayWalls(plan, year, month, day));
191
+ }
192
+ return walls;
193
+ }
194
+ function weekWalls(plan, week) {
195
+ const walls = [];
196
+ const firstDay = 7 * week - 4 + plan.weekStart;
197
+ for (let day = firstDay; day < firstDay + 7; day++) {
198
+ const date = new Date(day * DAY);
199
+ walls.push(...dayWalls(plan, date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()));
200
+ }
201
+ return walls;
202
+ }
203
+ function dayWalls(plan, year, month, day) {
204
+ if (!dayMatches(plan, year, month, day)) {
205
+ return [];
206
+ }
207
+ return times(plan.hours, plan.minutes, plan.seconds).map((time) => Date.UTC(year, month - 1, day) + time);
208
+ }
209
+ /** DAILY, HOURLY, MINUTELY or SECONDLY: `period` counts `unit`s of wall time since 1970, and fixes the fields down to it. */
210
+ function unitWalls(plan, period, unit) {
211
+ const date = new Date(period * unit);
212
+ const year = date.getUTCFullYear();
213
+ const month = date.getUTCMonth() + 1;
214
+ const day = date.getUTCDate();
215
+ const hour = date.getUTCHours();
216
+ const minute = date.getUTCMinutes();
217
+ const second = date.getUTCSeconds();
218
+ const skipTo = (wall) => ({ walls: [], next: wall / unit });
219
+ if (plan.months !== null && !plan.months.includes(month)) {
220
+ const nextMonth = following(plan.months, month + 1);
221
+ return skipTo(nextMonth === undefined ? Date.UTC(year + 1, plan.months[0] - 1) : Date.UTC(year, nextMonth - 1));
222
+ }
223
+ if (!dayMatches(plan, year, month, day)) {
224
+ return skipTo(Date.UTC(year, month - 1, day + 1));
225
+ }
226
+ if (unit < DAY && plan.hours !== null && !plan.hours.includes(hour)) {
227
+ const nextHour = following(plan.hours, hour + 1);
228
+ return skipTo(Date.UTC(year, month - 1, nextHour === undefined ? day + 1 : day, nextHour ?? 0));
229
+ }
230
+ if (unit < HOUR && plan.minutes !== null && !plan.minutes.includes(minute)) {
231
+ const nextMinute = following(plan.minutes, minute + 1);
232
+ return skipTo(Date.UTC(year, month - 1, day, nextMinute === undefined ? hour + 1 : hour, nextMinute ?? 0));
233
+ }
234
+ if (unit < MINUTE && plan.seconds !== null && !plan.seconds.includes(second)) {
235
+ const nextSecond = following(plan.seconds, second + 1);
236
+ return skipTo(Date.UTC(year, month - 1, day, hour, nextSecond === undefined ? minute + 1 : minute, nextSecond ?? 0));
237
+ }
238
+ const walls = times(unit < DAY ? [hour] : plan.hours, unit < HOUR ? [minute] : plan.minutes, unit < MINUTE ? [second] : plan.seconds);
239
+ return { walls: walls.map((time) => Date.UTC(year, month - 1, day) + time), next: period + 1 };
240
+ }
241
+ function dayMatches(plan, year, month, day) {
242
+ if (plan.months !== null && !plan.months.includes(month)) {
243
+ return false;
244
+ }
245
+ // A negative day counts from the month's end; a day the month doesn't have (February 30) never matches.
246
+ const length = daysIn(year, month);
247
+ if (plan.monthDays !== null && !plan.monthDays.some((monthDay) => (monthDay > 0 ? monthDay : length + 1 + monthDay) === day)) {
248
+ return false;
249
+ }
250
+ if (plan.weekdays === null) {
251
+ return true;
252
+ }
253
+ const date = Date.UTC(year, month - 1, day);
254
+ const weekday = weekdayOf(date);
255
+ const position = plan.withinYear ? (date - Date.UTC(year, 0, 1)) / DAY + 1 : day;
256
+ const span = plan.withinYear ? (Date.UTC(year + 1, 0, 1) - Date.UTC(year, 0, 1)) / DAY : length;
257
+ return plan.weekdays.some(({ weekday: wanted, nth }) => wanted === weekday && (nth === 0 || (nth > 0 ? Math.ceil(position / 7) === nth : Math.ceil((span - position + 1) / 7) === -nth)));
258
+ }
259
+ /**
260
+ * RFC 5545's defaults: a part the rule leaves out, but needs to pin an occurrence down, takes DTSTART's value: the
261
+ * month and day for YEARLY, the day for MONTHLY and the weekday for WEEKLY (unless BYMONTHDAY or BYDAY is given),
262
+ * and the hour, minute and second whenever FREQ is coarser than them.
263
+ */
264
+ function plan(rule, dtstart) {
265
+ const date = new Date(dtstart);
266
+ const noDays = rule.byMonthDay === null && rule.byDay === null;
267
+ const coarser = (unit) => FREQUENCIES.indexOf(rule.freq) > FREQUENCIES.indexOf(unit);
268
+ return {
269
+ freq: rule.freq,
270
+ interval: rule.interval,
271
+ weekStart: rule.weekStart,
272
+ months: rule.byMonth ?? (noDays && rule.freq === 'YEARLY' ? [date.getUTCMonth() + 1] : null),
273
+ monthDays: rule.byMonthDay ?? (noDays && (rule.freq === 'YEARLY' || rule.freq === 'MONTHLY') ? [date.getUTCDate()] : null),
274
+ weekdays: rule.byDay ?? (noDays && rule.freq === 'WEEKLY' ? [{ weekday: date.getUTCDay(), nth: 0 }] : null),
275
+ withinYear: rule.freq === 'YEARLY' && rule.byMonth === null,
276
+ hours: rule.byHour ?? (coarser('HOURLY') ? [date.getUTCHours()] : null),
277
+ minutes: rule.byMinute ?? (coarser('MINUTELY') ? [date.getUTCMinutes()] : null),
278
+ seconds: rule.bySecond ?? (coarser('SECONDLY') ? [date.getUTCSeconds()] : null),
279
+ };
280
+ }
281
+ function periodOf(plan, wall) {
282
+ const date = new Date(wall);
283
+ switch (plan.freq) {
284
+ case 'YEARLY':
285
+ return date.getUTCFullYear();
286
+ case 'MONTHLY':
287
+ return date.getUTCFullYear() * 12 + date.getUTCMonth();
288
+ case 'WEEKLY':
289
+ // 1 January 1970, day 0, is a Thursday (4): weeks start on the WKST day.
290
+ return Math.floor((Math.floor(wall / DAY) + 4 - plan.weekStart) / 7);
291
+ default:
292
+ return Math.floor(wall / UNITS[plan.freq]);
293
+ }
294
+ }
295
+ /** Times of day (ms), ascending. */
296
+ function times(hours, minutes, seconds) {
297
+ return hours.flatMap((hour) => minutes.flatMap((minute) => seconds.map((second) => hour * HOUR + minute * MINUTE + second * SECOND)));
298
+ }
299
+ function untilInstant(until, tz) {
300
+ switch (until.form) {
301
+ case 'utc':
302
+ return until.time;
303
+ case 'local':
304
+ return fromWallTime(until.time, tz);
305
+ default:
306
+ return fromWallTime(until.time + DAY, tz) - 1;
307
+ }
308
+ }
309
+ function parseUntil(value, fail) {
310
+ const match = /^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})(Z?))?$/.exec(value);
311
+ const [year, month, day, hour, minute, second] = (match?.slice(1, 7) ?? []).map((field) => Number(field ?? 0));
312
+ if (!match || month < 1 || month > 12 || day < 1 || day > daysIn(year, month) || hour > 23 || minute > 59 || second > 59) {
313
+ fail('UNTIL is a date, YYYYMMDD, or a date and time, YYYYMMDDTHHMMSS in the schedule\'s time zone or YYYYMMDDTHHMMSSZ in UTC');
314
+ }
315
+ return {
316
+ time: Date.UTC(year, month - 1, day, hour, minute, second),
317
+ form: match[4] === undefined ? 'date' : match[7] === 'Z' ? 'utc' : 'local',
318
+ };
319
+ }
320
+ function positive(value, fail) {
321
+ if (!/^\d+$/.test(value) || !(Number(value) >= 1) || !Number.isSafeInteger(Number(value))) {
322
+ fail('use a positive whole number');
323
+ }
324
+ return Number(value);
325
+ }
326
+ function weekday(value, fail) {
327
+ if (!WEEKDAYS.includes(value)) {
328
+ fail('use SU, MO, TU, WE, TH, FR or SA');
329
+ }
330
+ return WEEKDAYS.indexOf(value);
331
+ }
332
+ /** A list of whole numbers from `min` to `max`, ascending; `signed`: also -`max` to -`min`. */
333
+ function numbers(value, min, max, signed, fail) {
334
+ if (value === undefined) {
335
+ return null;
336
+ }
337
+ const items = value.split(',');
338
+ const inRange = (item) => (signed ? /^[+-]?\d+$/ : /^\d+$/).test(item) && Math.abs(Number(item)) >= min && Math.abs(Number(item)) <= max;
339
+ if (!items.every(inRange)) {
340
+ fail(signed ? `use ${min} to ${max}, or -${max} to -${min} to count from the end` : `use ${min} to ${max}`);
341
+ }
342
+ return [...new Set(items.map(Number))].sort((a, b) => a - b);
343
+ }
344
+ function weekdays(value, fail) {
345
+ if (value === undefined) {
346
+ return null;
347
+ }
348
+ return value.split(',').map((item) => {
349
+ const match = /^([+-]?\d{1,2})?(SU|MO|TU|WE|TH|FR|SA)$/.exec(item);
350
+ const nth = Number(match?.[1] ?? 0);
351
+ if (!match || (match[1] !== undefined && (nth === 0 || Math.abs(nth) > 53))) {
352
+ fail('use SU, MO, TU, WE, TH, FR or SA, numbered 1 to 53 or -1 to -53 for the n-th of the month or year (1MO, -1FR)');
353
+ }
354
+ return { weekday: WEEKDAYS.indexOf(match[2]), nth };
355
+ });
356
+ }
357
+ function following(values, from) {
358
+ for (const value of values) {
359
+ if (value >= from) {
360
+ return value;
361
+ }
362
+ }
363
+ return undefined;
364
+ }
365
+ function daysIn(year, month) {
366
+ return (Date.UTC(year, month, 1) - Date.UTC(year, month - 1, 1)) / DAY;
367
+ }
368
+ /** 0-6, Sunday is 0, of the day starting at wall time `date` (1 January 1970 was a Thursday). */
369
+ function weekdayOf(date) {
370
+ return (((Math.floor(date / DAY) + 4) % 7) + 7) % 7;
371
+ }
@@ -0,0 +1,57 @@
1
+ import type { ScheduleOptions, ScheduleSpec } from '../interfaces/schedule.interface.js';
2
+ /**
3
+ * Checks a schedule's options and returns its spec: validated, times in milliseconds, defaults applied (`tz: 'UTC'`,
4
+ * `missed: 'skip'`, `overlap: 'skip'`). Throws a `TypeError` that starts with `owner` for anything invalid, with the
5
+ * reason (`0 0 30 2 *` never matches: February has no 30th). Fields it doesn't know are left out: a package adds its
6
+ * own to the spec.
7
+ *
8
+ * ```ts
9
+ * const spec = parseSchedule({ cron: '0 0 8 * * MON', tz: 'Europe/Warsaw', missed: 'once' }, 'Schedule "weekly-digest"');
10
+ * ```
11
+ */
12
+ export declare function parseSchedule(options: ScheduleOptions, owner?: string): ScheduleSpec;
13
+ /**
14
+ * The times (epoch milliseconds, ascending) of a schedule's next occurrences strictly after `from` (default: now),
15
+ * at most `count` (default 10, at most 1,000) and at most what its `limit` leaves after `runs` occurrences: a
16
+ * schedule's preview. No I/O. Throws a `TypeError` that starts with `owner` for an invalid `count` or `from`.
17
+ *
18
+ * ```ts
19
+ * nextOccurrences(parseSchedule({ cron: '0 9 * * MON-FRI', tz: 'America/New_York' }), { from: Date.UTC(2026, 0, 1), count: 2 });
20
+ * // [Date.UTC(2026, 0, 1, 14), Date.UTC(2026, 0, 2, 14)]: 09:00 EST on Thursday and Friday
21
+ * ```
22
+ */
23
+ export declare function nextOccurrences(spec: ScheduleSpec, options?: {
24
+ from?: Date | number;
25
+ count?: number;
26
+ runs?: number;
27
+ }, owner?: string): number[];
28
+ /**
29
+ * The id of the run an occurrence starts: `<schedule id>@<ISO time of the occurrence>`, such as
30
+ * `weekly-digest@2026-01-05T07:00:00.000Z`, the same wherever and however often it is started, so a second start of
31
+ * an occurrence (by a worker that took over) finds the first.
32
+ *
33
+ * ```ts
34
+ * occurrenceId('weekly-digest', Date.UTC(2026, 0, 5, 7)); // 'weekly-digest@2026-01-05T07:00:00.000Z'
35
+ * ```
36
+ */
37
+ export declare function occurrenceId(schedule: string, at: number): string;
38
+ /**
39
+ * Throws a `TypeError` unless `id` can be a schedule's id: letters, digits, `.`, `:`, `_` and `-` (never the `@` of
40
+ * an occurrence's run id). `owner`, when given, is named in the message.
41
+ *
42
+ * ```ts
43
+ * assertScheduleId(id, 'queue "emails"'); // Invalid schedule id "weekly digest" of queue "emails". ...
44
+ * ```
45
+ */
46
+ export declare function assertScheduleId(id: unknown, owner?: string): asserts id is string;
47
+ /** Whether two specs have the same occurrences (their policies aside). */
48
+ export declare function sameTiming(a: ScheduleSpec, b: ScheduleSpec): boolean;
49
+ /** The spec's occurrences strictly after `after`, ascending, within `startAt` and `endAt` (not counting `limit`). */
50
+ export declare function occurrences(spec: ScheduleSpec, after: number): Generator<number>;
51
+ /** The first occurrence strictly after `after`, or `null`. */
52
+ export declare function nextOccurrence(spec: ScheduleSpec, after: number): number | null;
53
+ /**
54
+ * The latest `count` occurrences in `[from, to]`, ascending. Looks back from `to` in growing windows, so a
55
+ * schedule that fell far behind costs as much as the occurrences it returns, not all it missed.
56
+ */
57
+ export declare function latestOccurrences(spec: ScheduleSpec, from: number, to: number, count: number): number[];