@j13b/scheduler 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -2
- package/dist/alterations.d.ts +6 -0
- package/dist/alterations.d.ts.map +1 -0
- package/dist/alterations.js +61 -0
- package/dist/alterations.js.map +1 -0
- package/dist/cjs/alterations.d.ts +5 -0
- package/dist/cjs/alterations.js +96 -0
- package/dist/cjs/dispatcher.d.ts +38 -0
- package/dist/cjs/dispatcher.js +165 -0
- package/dist/cjs/dispatcher_store.d.ts +9 -0
- package/dist/cjs/dispatcher_store.js +15 -0
- package/dist/cjs/generators/anchored.d.ts +3 -0
- package/dist/cjs/generators/anchored.js +57 -0
- package/dist/cjs/generators/days_of_month.d.ts +3 -0
- package/dist/cjs/generators/days_of_month.js +76 -0
- package/dist/cjs/generators/days_of_week.d.ts +5 -0
- package/dist/cjs/generators/days_of_week.js +65 -0
- package/dist/cjs/generators/days_of_year.d.ts +3 -0
- package/dist/cjs/generators/days_of_year.js +70 -0
- package/dist/cjs/generators/instant.d.ts +3 -0
- package/dist/cjs/generators/instant.js +15 -0
- package/dist/cjs/generators/local_date_time.d.ts +3 -0
- package/dist/cjs/generators/local_date_time.js +31 -0
- package/dist/cjs/generators/months_of_year.d.ts +3 -0
- package/dist/cjs/generators/months_of_year.js +109 -0
- package/dist/cjs/generators/types.d.ts +9 -0
- package/dist/cjs/generators/types.js +19 -0
- package/dist/cjs/generators/weeks_of_month.d.ts +3 -0
- package/dist/cjs/generators/weeks_of_month.js +99 -0
- package/dist/cjs/generators/yearly.d.ts +3 -0
- package/dist/cjs/generators/yearly.js +52 -0
- package/dist/cjs/in_memory_port.d.ts +18 -0
- package/dist/cjs/in_memory_port.js +59 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.js +14 -0
- package/dist/cjs/occurrence_key.d.ts +5 -0
- package/dist/cjs/occurrence_key.js +21 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/port.d.ts +15 -0
- package/dist/cjs/port.js +2 -0
- package/dist/cjs/scheduler.d.ts +24 -0
- package/dist/cjs/scheduler.js +460 -0
- package/dist/cjs/types.d.ts +149 -0
- package/dist/cjs/types.js +58 -0
- package/dist/cjs/tz.d.ts +22 -0
- package/dist/cjs/tz.js +197 -0
- package/dist/dispatcher.d.ts +39 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/dispatcher.js +111 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/dispatcher_store.d.ts +10 -0
- package/dist/dispatcher_store.d.ts.map +1 -0
- package/dist/dispatcher_store.js +13 -0
- package/dist/dispatcher_store.js.map +1 -0
- package/dist/generators/anchored.d.ts +4 -0
- package/dist/generators/anchored.d.ts.map +1 -0
- package/dist/generators/anchored.js +42 -0
- package/dist/generators/anchored.js.map +1 -0
- package/dist/generators/days_of_month.d.ts +4 -0
- package/dist/generators/days_of_month.d.ts.map +1 -0
- package/dist/generators/days_of_month.js +53 -0
- package/dist/generators/days_of_month.js.map +1 -0
- package/dist/generators/days_of_week.d.ts +6 -0
- package/dist/generators/days_of_week.d.ts.map +1 -0
- package/dist/generators/days_of_week.js +43 -0
- package/dist/generators/days_of_week.js.map +1 -0
- package/dist/generators/days_of_year.d.ts +4 -0
- package/dist/generators/days_of_year.d.ts.map +1 -0
- package/dist/generators/days_of_year.js +50 -0
- package/dist/generators/days_of_year.js.map +1 -0
- package/dist/generators/instant.d.ts +4 -0
- package/dist/generators/instant.d.ts.map +1 -0
- package/dist/generators/instant.js +15 -0
- package/dist/generators/instant.js.map +1 -0
- package/dist/generators/local_date_time.d.ts +4 -0
- package/dist/generators/local_date_time.d.ts.map +1 -0
- package/dist/generators/local_date_time.js +20 -0
- package/dist/generators/local_date_time.js.map +1 -0
- package/dist/generators/months_of_year.d.ts +4 -0
- package/dist/generators/months_of_year.d.ts.map +1 -0
- package/dist/generators/months_of_year.js +64 -0
- package/dist/generators/months_of_year.js.map +1 -0
- package/dist/generators/types.d.ts +10 -0
- package/dist/generators/types.d.ts.map +1 -0
- package/dist/generators/types.js +14 -0
- package/dist/generators/types.js.map +1 -0
- package/dist/generators/weeks_of_month.d.ts +4 -0
- package/dist/generators/weeks_of_month.d.ts.map +1 -0
- package/dist/generators/weeks_of_month.js +61 -0
- package/dist/generators/weeks_of_month.js.map +1 -0
- package/dist/generators/yearly.d.ts +4 -0
- package/dist/generators/yearly.d.ts.map +1 -0
- package/dist/generators/yearly.js +37 -0
- package/dist/generators/yearly.js.map +1 -0
- package/dist/in_memory_port.d.ts +19 -0
- package/dist/in_memory_port.d.ts.map +1 -0
- package/dist/in_memory_port.js +45 -0
- package/dist/in_memory_port.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/occurrence_key.d.ts +6 -0
- package/dist/occurrence_key.d.ts.map +1 -0
- package/dist/occurrence_key.js +25 -0
- package/dist/occurrence_key.js.map +1 -0
- package/dist/port.d.ts +16 -0
- package/dist/port.d.ts.map +1 -0
- package/dist/scheduler.d.ts +25 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +332 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/dist/tz.d.ts +23 -0
- package/dist/tz.d.ts.map +1 -0
- package/dist/tz.js +131 -0
- package/dist/tz.js.map +1 -0
- package/package.json +70 -4
- package/src/__tests__/alterations.test.ts +177 -0
- package/src/__tests__/anchored.test.ts +107 -0
- package/src/__tests__/days_of_month.test.ts +162 -0
- package/src/__tests__/days_of_week.test.ts +134 -0
- package/src/__tests__/days_of_year.test.ts +158 -0
- package/src/__tests__/dispatcher.test.ts +317 -0
- package/src/__tests__/instant.test.ts +50 -0
- package/src/__tests__/local_date_time.test.ts +139 -0
- package/src/__tests__/months_of_year.test.ts +229 -0
- package/src/__tests__/scheduler.test.ts +888 -0
- package/src/__tests__/tz.test.ts +201 -0
- package/src/__tests__/weeks_of_month.test.ts +195 -0
- package/src/__tests__/yearly.test.ts +108 -0
- package/src/alterations.ts +108 -0
- package/src/dispatcher.ts +184 -0
- package/src/dispatcher_store.ts +26 -0
- package/src/generators/anchored.ts +69 -0
- package/src/generators/days_of_month.ts +100 -0
- package/src/generators/days_of_week.ts +101 -0
- package/src/generators/days_of_year.ts +88 -0
- package/src/generators/instant.ts +18 -0
- package/src/generators/local_date_time.ts +38 -0
- package/src/generators/months_of_year.ts +143 -0
- package/src/generators/types.ts +26 -0
- package/src/generators/weeks_of_month.ts +133 -0
- package/src/generators/yearly.ts +73 -0
- package/src/in_memory_port.ts +70 -0
- package/src/index.ts +24 -0
- package/src/occurrence_key.ts +42 -0
- package/src/port.ts +31 -0
- package/src/scheduler.ts +499 -0
- package/src/types.ts +369 -0
- package/src/tz.ts +246 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// All types are plain data — safe to JSON-serialize and store in a JSON column.
|
|
3
|
+
// Generated occurrences carry a TPayload so consumers can attach whatever
|
|
4
|
+
// domain data they need (cron action, calendar event body, etc.).
|
|
5
|
+
//
|
|
6
|
+
// ─── DST gotchas for wall-clock rules ───────────────────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// daysOfWeek / weekOfMonth / yearly rules use wall-clock fields (timeOfDay,
|
|
9
|
+
// day-of-week, month/day) interpreted in the template's `timezone`. In a tz
|
|
10
|
+
// that observes DST, two edge cases exist:
|
|
11
|
+
//
|
|
12
|
+
// 1. Spring-forward gap. On the spring-forward day, an hour is SKIPPED — in
|
|
13
|
+
// America/Denver, 02:00 MST jumps to 03:00 MDT, so wall times 02:00–02:59
|
|
14
|
+
// don't exist that day. If `timeOfDay` lands in this gap, we resolve to
|
|
15
|
+
// the post-transition offset (the default in Luxon and date-fns-tz),
|
|
16
|
+
// which means the event fires at a *different* wall-clock time than you
|
|
17
|
+
// asked for — typically one hour earlier in real time. RECOMMENDATION:
|
|
18
|
+
// don't pick `timeOfDay` between 02:00 and 02:59 in DST-observing zones.
|
|
19
|
+
// 03:00 is always safe.
|
|
20
|
+
//
|
|
21
|
+
// 2. TimeSpan spanning the gap. The TimeSpan type has two flavors:
|
|
22
|
+
// `{type:"duration",ms}` — exact UTC milliseconds, no DST adjustment.
|
|
23
|
+
// An 8h duration shift starting 23:00 spring-forward eve runs for
|
|
24
|
+
// exactly 8h real time; the worker's wall display reads "9 hours
|
|
25
|
+
// elapsed" because clocks jumped. Payroll-correct.
|
|
26
|
+
// `{type:"hourSpan",hours}` — wall-clock hours, flexes with DST.
|
|
27
|
+
// An 8h hourSpan shift starting 23:00 spring-forward eve ends at
|
|
28
|
+
// 07:00 next wall day regardless; real elapsed time is 7 actual
|
|
29
|
+
// hours. Calendar-display correct.
|
|
30
|
+
// Both are valid; pick based on whether you care about real elapsed
|
|
31
|
+
// time or wall-clock display.
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.Month = exports.Day = void 0;
|
|
34
|
+
var Day;
|
|
35
|
+
(function (Day) {
|
|
36
|
+
Day[Day["Sunday"] = 0] = "Sunday";
|
|
37
|
+
Day[Day["Monday"] = 1] = "Monday";
|
|
38
|
+
Day[Day["Tuesday"] = 2] = "Tuesday";
|
|
39
|
+
Day[Day["Wednesday"] = 3] = "Wednesday";
|
|
40
|
+
Day[Day["Thursday"] = 4] = "Thursday";
|
|
41
|
+
Day[Day["Friday"] = 5] = "Friday";
|
|
42
|
+
Day[Day["Saturday"] = 6] = "Saturday";
|
|
43
|
+
})(Day || (exports.Day = Day = {}));
|
|
44
|
+
var Month;
|
|
45
|
+
(function (Month) {
|
|
46
|
+
Month[Month["January"] = 0] = "January";
|
|
47
|
+
Month[Month["February"] = 1] = "February";
|
|
48
|
+
Month[Month["March"] = 2] = "March";
|
|
49
|
+
Month[Month["April"] = 3] = "April";
|
|
50
|
+
Month[Month["May"] = 4] = "May";
|
|
51
|
+
Month[Month["June"] = 5] = "June";
|
|
52
|
+
Month[Month["July"] = 6] = "July";
|
|
53
|
+
Month[Month["August"] = 7] = "August";
|
|
54
|
+
Month[Month["September"] = 8] = "September";
|
|
55
|
+
Month[Month["October"] = 9] = "October";
|
|
56
|
+
Month[Month["November"] = 10] = "November";
|
|
57
|
+
Month[Month["December"] = 11] = "December";
|
|
58
|
+
})(Month || (exports.Month = Month = {}));
|
package/dist/cjs/tz.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Day, Disambiguation, TimeSpan } from "./types.js";
|
|
2
|
+
export interface WallParts {
|
|
3
|
+
year: number;
|
|
4
|
+
month: number;
|
|
5
|
+
day: number;
|
|
6
|
+
hour: number;
|
|
7
|
+
minute: number;
|
|
8
|
+
second: number;
|
|
9
|
+
ms: number;
|
|
10
|
+
dayOfWeek: Day;
|
|
11
|
+
}
|
|
12
|
+
export declare function isValidTimeZone(tz: string): boolean;
|
|
13
|
+
export declare function utcToWallParts(utcEpochMs: number, tz?: string): WallParts;
|
|
14
|
+
export declare function isNonexistentWallTime(year: number, month: number, day: number, msOfDay: number, tz: string | undefined): boolean;
|
|
15
|
+
export declare function wallTimeToUtc(year: number, month: number, // 0-indexed
|
|
16
|
+
day: number, msOfDay: number, // 0..86_399_999, ms since wall-clock midnight
|
|
17
|
+
tz?: string, disambiguation?: Disambiguation): number;
|
|
18
|
+
export declare function dayOfWeekInTz(utcEpochMs: number, tz?: string): Day;
|
|
19
|
+
export declare function addDaysInTz(utcEpochMs: number, days: number, tz?: string): number;
|
|
20
|
+
export declare function addMonthsInTz(utcEpochMs: number, months: number, tz?: string): number;
|
|
21
|
+
export declare function monthLength(year: number, month: number): number;
|
|
22
|
+
export declare function computeSpanEnd(startUtc: number, span: TimeSpan | undefined, tz: string | undefined): number;
|
package/dist/cjs/tz.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isValidTimeZone = isValidTimeZone;
|
|
4
|
+
exports.utcToWallParts = utcToWallParts;
|
|
5
|
+
exports.isNonexistentWallTime = isNonexistentWallTime;
|
|
6
|
+
exports.wallTimeToUtc = wallTimeToUtc;
|
|
7
|
+
exports.dayOfWeekInTz = dayOfWeekInTz;
|
|
8
|
+
exports.addDaysInTz = addDaysInTz;
|
|
9
|
+
exports.addMonthsInTz = addMonthsInTz;
|
|
10
|
+
exports.monthLength = monthLength;
|
|
11
|
+
exports.computeSpanEnd = computeSpanEnd;
|
|
12
|
+
const DAY_MS = 86_400_000;
|
|
13
|
+
const _formatterCache = new Map();
|
|
14
|
+
function _formatter(tz) {
|
|
15
|
+
let f = _formatterCache.get(tz);
|
|
16
|
+
if (f == null) {
|
|
17
|
+
f = new Intl.DateTimeFormat("en-US", {
|
|
18
|
+
timeZone: tz,
|
|
19
|
+
year: "numeric",
|
|
20
|
+
month: "2-digit",
|
|
21
|
+
day: "2-digit",
|
|
22
|
+
hour: "2-digit",
|
|
23
|
+
minute: "2-digit",
|
|
24
|
+
second: "2-digit",
|
|
25
|
+
hour12: false,
|
|
26
|
+
});
|
|
27
|
+
_formatterCache.set(tz, f);
|
|
28
|
+
}
|
|
29
|
+
return f;
|
|
30
|
+
}
|
|
31
|
+
// Is `tz` an IANA zone name (or "UTC") the runtime recognizes? Constructing an
|
|
32
|
+
// Intl.DateTimeFormat with an unknown zone throws RangeError; we probe for that.
|
|
33
|
+
function isValidTimeZone(tz) {
|
|
34
|
+
try {
|
|
35
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz }).format();
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function utcToWallParts(utcEpochMs, tz = "UTC") {
|
|
43
|
+
if (tz === "UTC") {
|
|
44
|
+
const d = new Date(utcEpochMs);
|
|
45
|
+
return {
|
|
46
|
+
year: d.getUTCFullYear(),
|
|
47
|
+
month: d.getUTCMonth(),
|
|
48
|
+
day: d.getUTCDate(),
|
|
49
|
+
hour: d.getUTCHours(),
|
|
50
|
+
minute: d.getUTCMinutes(),
|
|
51
|
+
second: d.getUTCSeconds(),
|
|
52
|
+
ms: d.getUTCMilliseconds(),
|
|
53
|
+
dayOfWeek: d.getUTCDay(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const parts = _formatter(tz).formatToParts(new Date(utcEpochMs));
|
|
57
|
+
const get = (type) => {
|
|
58
|
+
const p = parts.find((x) => x.type === type);
|
|
59
|
+
if (p == null)
|
|
60
|
+
throw new Error(`Missing date-time part '${type}' for tz '${tz}'`);
|
|
61
|
+
return Number(p.value);
|
|
62
|
+
};
|
|
63
|
+
let hour = get("hour");
|
|
64
|
+
if (hour === 24)
|
|
65
|
+
hour = 0; // some Node versions report midnight as "24"
|
|
66
|
+
const year = get("year");
|
|
67
|
+
const month = get("month") - 1;
|
|
68
|
+
const day = get("day");
|
|
69
|
+
// Day-of-week derived from the wall date via UTC math (the math itself
|
|
70
|
+
// is tz-independent — Date.UTC just gives us a zero-anchored instant).
|
|
71
|
+
const dayOfWeek = new Date(Date.UTC(year, month, day)).getUTCDay();
|
|
72
|
+
// Intl doesn't give us ms — preserve the original ms from utcEpochMs.
|
|
73
|
+
// Sub-second precision is the same in any tz, since offsets are minute-
|
|
74
|
+
// aligned (or, for a few historical zones, second-aligned).
|
|
75
|
+
const ms = ((utcEpochMs % 1000) + 1000) % 1000;
|
|
76
|
+
return {
|
|
77
|
+
year,
|
|
78
|
+
month,
|
|
79
|
+
day,
|
|
80
|
+
hour,
|
|
81
|
+
minute: get("minute"),
|
|
82
|
+
second: get("second"),
|
|
83
|
+
ms,
|
|
84
|
+
dayOfWeek,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Offset from UTC to local wall time at the given UTC instant, in ms.
|
|
88
|
+
// Positive for zones east of UTC. (wallAsUtc - utcInstant)
|
|
89
|
+
function _offsetMsAt(utcEpochMs, tz) {
|
|
90
|
+
if (tz === "UTC")
|
|
91
|
+
return 0;
|
|
92
|
+
const w = utcToWallParts(utcEpochMs, tz);
|
|
93
|
+
const wallAsUtc = Date.UTC(w.year, w.month, w.day, w.hour, w.minute, w.second) + w.ms;
|
|
94
|
+
return wallAsUtc - utcEpochMs;
|
|
95
|
+
}
|
|
96
|
+
// The instants that a wall time could map to in `tz`, plus the bracketing
|
|
97
|
+
// offsets. `valid` has length 1 normally, 0 in a spring-forward gap, or 2 in a
|
|
98
|
+
// fall-back overlap (sorted ascending).
|
|
99
|
+
function _possibleInstants(localUtc, tz) {
|
|
100
|
+
const offBefore = _offsetMsAt(localUtc - DAY_MS, tz);
|
|
101
|
+
const offAfter = _offsetMsAt(localUtc + DAY_MS, tz);
|
|
102
|
+
const valid = [];
|
|
103
|
+
const offsets = offBefore === offAfter ? [offBefore] : [offBefore, offAfter];
|
|
104
|
+
for (const off of offsets) {
|
|
105
|
+
const epoch = localUtc - off;
|
|
106
|
+
// Keep this candidate only if it reads back as the wall time we asked for
|
|
107
|
+
// (a gap's candidates won't; an overlap's both will).
|
|
108
|
+
const w = utcToWallParts(epoch, tz);
|
|
109
|
+
const back = Date.UTC(w.year, w.month, w.day, w.hour, w.minute, w.second) + w.ms;
|
|
110
|
+
if (back === localUtc && !valid.includes(epoch))
|
|
111
|
+
valid.push(epoch);
|
|
112
|
+
}
|
|
113
|
+
valid.sort((a, b) => a - b);
|
|
114
|
+
return { valid, offBefore, offAfter };
|
|
115
|
+
}
|
|
116
|
+
// Is this wall time nonexistent (in a spring-forward gap) in `tz`? Used by the
|
|
117
|
+
// "skip" disambiguation to drop occurrences whose wall time doesn't exist.
|
|
118
|
+
function isNonexistentWallTime(year, month, day, msOfDay, tz) {
|
|
119
|
+
if (tz == null || tz === "UTC")
|
|
120
|
+
return false;
|
|
121
|
+
const localUtc = Date.UTC(year, month, day) + msOfDay;
|
|
122
|
+
return _possibleInstants(localUtc, tz).valid.length === 0;
|
|
123
|
+
}
|
|
124
|
+
function wallTimeToUtc(year, month, // 0-indexed
|
|
125
|
+
day, msOfDay, // 0..86_399_999, ms since wall-clock midnight
|
|
126
|
+
tz = "UTC", disambiguation = "compatible") {
|
|
127
|
+
if (tz === "UTC") {
|
|
128
|
+
return Date.UTC(year, month, day) + msOfDay;
|
|
129
|
+
}
|
|
130
|
+
const localUtc = Date.UTC(year, month, day) + msOfDay;
|
|
131
|
+
const { valid, offBefore, offAfter } = _possibleInstants(localUtc, tz);
|
|
132
|
+
if (valid.length === 1)
|
|
133
|
+
return valid[0];
|
|
134
|
+
if (valid.length === 2) {
|
|
135
|
+
// Fall-back overlap. compatible/earlier/skip → earlier; later → later.
|
|
136
|
+
if (disambiguation === "reject") {
|
|
137
|
+
throw new Error(`Ambiguous wall time ${new Date(localUtc).toISOString().slice(0, 19)} in '${tz}' (fall-back overlap) with disambiguation "reject"`);
|
|
138
|
+
}
|
|
139
|
+
return disambiguation === "later" ? valid[1] : valid[0];
|
|
140
|
+
}
|
|
141
|
+
// Spring-forward gap (valid.length === 0). earlier → the earlier instant
|
|
142
|
+
// (post-transition offset); compatible/later/skip → the later instant.
|
|
143
|
+
// ("skip" only reaches here defensively — callers pre-check via
|
|
144
|
+
// isNonexistentWallTime; we resolve it like "compatible".)
|
|
145
|
+
if (disambiguation === "reject") {
|
|
146
|
+
throw new Error(`Nonexistent wall time ${new Date(localUtc).toISOString().slice(0, 19)} in '${tz}' (spring-forward gap) with disambiguation "reject"`);
|
|
147
|
+
}
|
|
148
|
+
return disambiguation === "earlier" ? localUtc - offAfter : localUtc - offBefore;
|
|
149
|
+
}
|
|
150
|
+
function dayOfWeekInTz(utcEpochMs, tz = "UTC") {
|
|
151
|
+
return utcToWallParts(utcEpochMs, tz).dayOfWeek;
|
|
152
|
+
}
|
|
153
|
+
// Add N calendar days in the wall-clock sense — important across DST
|
|
154
|
+
// boundaries, where one civil day is 23 or 25 hours, not 24.
|
|
155
|
+
function addDaysInTz(utcEpochMs, days, tz = "UTC") {
|
|
156
|
+
const w = utcToWallParts(utcEpochMs, tz);
|
|
157
|
+
return wallTimeToUtc(w.year, w.month, w.day + days, _hmsToMs(w), tz);
|
|
158
|
+
}
|
|
159
|
+
function addMonthsInTz(utcEpochMs, months, tz = "UTC") {
|
|
160
|
+
const w = utcToWallParts(utcEpochMs, tz);
|
|
161
|
+
return wallTimeToUtc(w.year, w.month + months, w.day, _hmsToMs(w), tz);
|
|
162
|
+
}
|
|
163
|
+
// Civil-calendar month length — tz-independent.
|
|
164
|
+
function monthLength(year, month) {
|
|
165
|
+
// Date.UTC(y, m+1, 0) gives the last day of month `m` (0-indexed).
|
|
166
|
+
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
167
|
+
}
|
|
168
|
+
// Returns endTimestamp for an occurrence given its start and a TimeSpan.
|
|
169
|
+
//
|
|
170
|
+
// For `duration`: pure UTC ms addition. The wall clock may show ±1h different
|
|
171
|
+
// elapsed time on DST nights, but real elapsed time is exactly `ms`.
|
|
172
|
+
//
|
|
173
|
+
// For `hourSpan`: add hours in wall-clock space (handles midnight crossing
|
|
174
|
+
// and DST flex). On a tz-less template, hourSpan collapses to hours*3_600_000
|
|
175
|
+
// since there's no wall clock to flex against.
|
|
176
|
+
//
|
|
177
|
+
// When span is undefined, defaults to 1ms (so the half-open intersection
|
|
178
|
+
// emits cron-fire occurrences correctly).
|
|
179
|
+
function computeSpanEnd(startUtc, span, tz) {
|
|
180
|
+
if (span == null)
|
|
181
|
+
return startUtc + 1;
|
|
182
|
+
if (span.type === "duration")
|
|
183
|
+
return startUtc + span.ms;
|
|
184
|
+
// hourSpan
|
|
185
|
+
if (tz == null) {
|
|
186
|
+
// No tz → treat hours as a fixed real-time interval.
|
|
187
|
+
return startUtc + span.hours * 3_600_000;
|
|
188
|
+
}
|
|
189
|
+
const w = utcToWallParts(startUtc, tz);
|
|
190
|
+
const totalMs = _hmsToMs(w) + span.hours * 3_600_000;
|
|
191
|
+
const daysAhead = Math.floor(totalMs / 86_400_000);
|
|
192
|
+
const endMsInDay = totalMs - daysAhead * 86_400_000;
|
|
193
|
+
return wallTimeToUtc(w.year, w.month, w.day + daysAhead, endMsInDay, tz);
|
|
194
|
+
}
|
|
195
|
+
function _hmsToMs(w) {
|
|
196
|
+
return w.hour * 3_600_000 + w.minute * 60_000 + w.second * 1000 + w.ms;
|
|
197
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DispatcherStore } from './dispatcher_store.js';
|
|
2
|
+
import { Scheduler } from './scheduler.js';
|
|
3
|
+
import { ScheduledEvent } from './types.js';
|
|
4
|
+
export interface DispatcherConfig<TPayload extends {
|
|
5
|
+
type: string;
|
|
6
|
+
}> {
|
|
7
|
+
scheduler: Scheduler<TPayload>;
|
|
8
|
+
fire: (event: ScheduledEvent<TPayload>) => Promise<void> | void;
|
|
9
|
+
store?: DispatcherStore;
|
|
10
|
+
onError?: (err: unknown, event?: ScheduledEvent<TPayload>) => void;
|
|
11
|
+
idleTimeoutMs?: number;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
setTimeout?: (fn: () => void, ms: number) => unknown;
|
|
14
|
+
clearTimeout?: (handle: unknown) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare class Dispatcher<TPayload extends {
|
|
17
|
+
type: string;
|
|
18
|
+
}> {
|
|
19
|
+
private readonly _scheduler;
|
|
20
|
+
private readonly _fire;
|
|
21
|
+
private readonly _store;
|
|
22
|
+
private readonly _onError;
|
|
23
|
+
private readonly _idleTimeoutMs;
|
|
24
|
+
private readonly _now;
|
|
25
|
+
private readonly _setTimeout;
|
|
26
|
+
private readonly _clearTimeout;
|
|
27
|
+
private _running;
|
|
28
|
+
private _wakeResolve;
|
|
29
|
+
private _changeSubscription;
|
|
30
|
+
constructor(config: DispatcherConfig<TPayload>);
|
|
31
|
+
start(): Promise<void>;
|
|
32
|
+
stop(): void;
|
|
33
|
+
private _loop;
|
|
34
|
+
private _findNextTarget;
|
|
35
|
+
private _tick;
|
|
36
|
+
private _sleepUntil;
|
|
37
|
+
private _wake;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAA2B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAOjD,MAAM,WAAW,gBAAgB,CAAC,QAAQ,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;IACjE,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAI/B,IAAI,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAEhE,KAAK,CAAC,EAAE,eAAe,CAAC;IAExB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAEnE,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C;AASD,qBAAa,UAAU,CAAC,QAAQ,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4D;IAClF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2D;IACpF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0C;IACtE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAE1D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,mBAAmB,CAAyC;gBAExD,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC;IAexC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,IAAI,IAAI,IAAI;YAYE,KAAK;YAYL,eAAe;YAiBf,KAAK;YAgCL,WAAW;IAuBzB,OAAO,CAAC,KAAK;CAGd"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { InMemoryDispatcherStore } from "./dispatcher_store.js";
|
|
2
|
+
// src/dispatcher.ts
|
|
3
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 36e5;
|
|
4
|
+
var Dispatcher = class {
|
|
5
|
+
_scheduler;
|
|
6
|
+
_fire;
|
|
7
|
+
_store;
|
|
8
|
+
_onError;
|
|
9
|
+
_idleTimeoutMs;
|
|
10
|
+
_now;
|
|
11
|
+
_setTimeout;
|
|
12
|
+
_clearTimeout;
|
|
13
|
+
_running = false;
|
|
14
|
+
_wakeResolve = null;
|
|
15
|
+
_changeSubscription = null;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this._scheduler = config.scheduler;
|
|
18
|
+
this._fire = config.fire;
|
|
19
|
+
this._store = config.store ?? new InMemoryDispatcherStore();
|
|
20
|
+
this._onError = config.onError ?? defaultOnError;
|
|
21
|
+
this._idleTimeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
22
|
+
this._now = config.now ?? Date.now;
|
|
23
|
+
this._setTimeout = config.setTimeout ?? ((fn, ms) => globalThis.setTimeout(fn, ms));
|
|
24
|
+
this._clearTimeout = config.clearTimeout ?? ((h) => globalThis.clearTimeout(h));
|
|
25
|
+
}
|
|
26
|
+
async start() {
|
|
27
|
+
if (this._running) return;
|
|
28
|
+
this._running = true;
|
|
29
|
+
this._changeSubscription = this._scheduler.changed.subscribe(() => this._wake());
|
|
30
|
+
await this._tick();
|
|
31
|
+
if (this._running) this._loop();
|
|
32
|
+
}
|
|
33
|
+
stop() {
|
|
34
|
+
if (!this._running) return;
|
|
35
|
+
this._running = false;
|
|
36
|
+
if (this._changeSubscription != null) {
|
|
37
|
+
this._changeSubscription.unsubscribe();
|
|
38
|
+
this._changeSubscription = null;
|
|
39
|
+
}
|
|
40
|
+
this._wake();
|
|
41
|
+
}
|
|
42
|
+
async _loop() {
|
|
43
|
+
while (this._running) {
|
|
44
|
+
const target = await this._findNextTarget();
|
|
45
|
+
if (!this._running) return;
|
|
46
|
+
await this._sleepUntil(target);
|
|
47
|
+
if (!this._running) return;
|
|
48
|
+
await this._tick();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async _findNextTarget() {
|
|
52
|
+
try {
|
|
53
|
+
const next = (await this._scheduler.nextDue(this._now(), 1))[0];
|
|
54
|
+
return next != null ? next.startTimestamp : this._now() + this._idleTimeoutMs;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
this._onError(err);
|
|
57
|
+
return this._now() + this._idleTimeoutMs;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async _tick() {
|
|
61
|
+
const cursor = await this._store.loadCursor() ?? this._now();
|
|
62
|
+
const windowEnd = this._now() + 1;
|
|
63
|
+
if (windowEnd <= cursor) return;
|
|
64
|
+
let events;
|
|
65
|
+
try {
|
|
66
|
+
events = await this._scheduler.list(cursor, windowEnd);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
this._onError(err);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
for (const event of events) {
|
|
72
|
+
if (!this._running) return;
|
|
73
|
+
try {
|
|
74
|
+
await this._fire(event);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
this._onError(err, event);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await this._store.saveCursor(windowEnd);
|
|
80
|
+
}
|
|
81
|
+
async _sleepUntil(targetMs) {
|
|
82
|
+
const remaining = targetMs - this._now();
|
|
83
|
+
if (remaining <= 0) return;
|
|
84
|
+
await new Promise((resolve) => {
|
|
85
|
+
let done = false;
|
|
86
|
+
const timer = this._setTimeout(() => {
|
|
87
|
+
if (done) return;
|
|
88
|
+
done = true;
|
|
89
|
+
this._wakeResolve = null;
|
|
90
|
+
resolve();
|
|
91
|
+
}, remaining);
|
|
92
|
+
this._wakeResolve = () => {
|
|
93
|
+
if (done) return;
|
|
94
|
+
done = true;
|
|
95
|
+
this._clearTimeout(timer);
|
|
96
|
+
this._wakeResolve = null;
|
|
97
|
+
resolve();
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
_wake() {
|
|
102
|
+
if (this._wakeResolve != null) this._wakeResolve();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
function defaultOnError(err, event) {
|
|
106
|
+
if (event != null) console.error(`[Dispatcher] fire failed for ${event.templateId}/${event.occurrenceKey}:`, err);
|
|
107
|
+
else console.error("[Dispatcher] error:", err);
|
|
108
|
+
}
|
|
109
|
+
export { Dispatcher };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.js","names":[],"sources":["../src/dispatcher.ts"],"sourcesContent":["import type { IEventSubscription } from \"@j13b/state\";\nimport { type DispatcherStore, InMemoryDispatcherStore } from \"./dispatcher_store.js\";\nimport type { Scheduler } from \"./scheduler.js\";\nimport type { ScheduledEvent } from \"./types.js\";\n\n// Default safety-net wait when nothing is scheduled. The Scheduler.changed\n// event normally wakes us as soon as something gets added, so this is just\n// a backstop in case a notification is missed.\nconst DEFAULT_IDLE_TIMEOUT_MS = 60 * 60 * 1000;\n\nexport interface DispatcherConfig<TPayload extends { type: string }> {\n scheduler: Scheduler<TPayload>;\n // Called for each due event in cursor → now order. Errors thrown from here\n // are passed to onError; the cursor advances regardless (cron-style: a\n // permanently-failing fire shouldn't block the queue forever).\n fire: (event: ScheduledEvent<TPayload>) => Promise<void> | void;\n // Persists the cursor across restarts. Defaults to in-memory (no recovery).\n store?: DispatcherStore;\n // Called when fire() throws or list() fails. Defaults to console.error.\n onError?: (err: unknown, event?: ScheduledEvent<TPayload>) => void;\n // Backstop wait when no event is scheduled. Default 1 hour. Lower for tests.\n idleTimeoutMs?: number;\n // Clock injection (Date.now) and timer injection (setTimeout/clearTimeout).\n // Defaults to wall clock. Inject for deterministic tests.\n now?: () => number;\n setTimeout?: (fn: () => void, ms: number) => unknown;\n clearTimeout?: (handle: unknown) => void;\n}\n\n// Drives a Scheduler in real time. The loop:\n// 1. Catch up: list(cursor, now) → fire each → advance cursor → save.\n// 2. Find next: nextDue(now, 1) → wait until its startTimestamp.\n// 3. Wait may end early via Scheduler.changed (a write happened) or stop().\n// Catch-up handles crashes AND in-process adds we missed. The change event\n// handles in-process adds while we're sleeping — the dispatcher wakes\n// immediately and re-queries instead of firing the stale \"next\" event late.\nexport class Dispatcher<TPayload extends { type: string }> {\n private readonly _scheduler: Scheduler<TPayload>;\n private readonly _fire: (event: ScheduledEvent<TPayload>) => Promise<void> | void;\n private readonly _store: DispatcherStore;\n private readonly _onError: (err: unknown, event?: ScheduledEvent<TPayload>) => void;\n private readonly _idleTimeoutMs: number;\n private readonly _now: () => number;\n private readonly _setTimeout: (fn: () => void, ms: number) => unknown;\n private readonly _clearTimeout: (handle: unknown) => void;\n\n private _running = false;\n private _wakeResolve: (() => void) | null = null;\n private _changeSubscription: IEventSubscription<void> | null = null;\n\n constructor(config: DispatcherConfig<TPayload>) {\n this._scheduler = config.scheduler;\n this._fire = config.fire;\n this._store = config.store ?? new InMemoryDispatcherStore();\n this._onError = config.onError ?? defaultOnError;\n this._idleTimeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;\n this._now = config.now ?? Date.now;\n this._setTimeout = config.setTimeout ?? ((fn, ms) => globalThis.setTimeout(fn, ms));\n this._clearTimeout =\n config.clearTimeout ?? ((h) => globalThis.clearTimeout(h as ReturnType<typeof setTimeout>));\n }\n\n // Starts the loop. Resolves after the first catch-up pass completes (so\n // tests can await an initial sync). The loop keeps running in the\n // background until stop() is called.\n async start(): Promise<void> {\n if (this._running) return;\n this._running = true;\n this._changeSubscription = this._scheduler.changed.subscribe(() => this._wake());\n // First iteration in foreground so callers can await initial catch-up.\n await this._tick();\n if (this._running) void this._loop();\n }\n\n // Halts the loop. Any in-flight fire() completes; subsequent fires are\n // skipped. The Scheduler subscription is removed.\n stop(): void {\n if (!this._running) return;\n this._running = false;\n if (this._changeSubscription != null) {\n this._changeSubscription.unsubscribe();\n this._changeSubscription = null;\n }\n this._wake();\n }\n\n // ─── Internals ──────────────────────────────────────────────\n\n private async _loop(): Promise<void> {\n while (this._running) {\n const target = await this._findNextTarget();\n if (!this._running) return;\n await this._sleepUntil(target);\n if (!this._running) return;\n await this._tick();\n }\n }\n\n // Returns the epoch-ms instant the dispatcher should next wake. Either\n // the next due event's startTimestamp, or now + idleTimeoutMs.\n private async _findNextTarget(): Promise<number> {\n try {\n const upcoming = await this._scheduler.nextDue(this._now(), 1);\n const next = upcoming[0];\n return next != null ? next.startTimestamp : this._now() + this._idleTimeoutMs;\n } catch (err) {\n this._onError(err);\n return this._now() + this._idleTimeoutMs;\n }\n }\n\n // Catch-up pass: fire everything with start in [cursor, now]. We use\n // `now + 1` as the half-open windowEnd so an event scheduled exactly at\n // the wake instant is included (the wake from setTimeout lands on the\n // event's startTimestamp, and we don't want to miss it). Advancing the\n // cursor to `now + 1` likewise avoids re-listing the same event on the\n // next tick.\n private async _tick(): Promise<void> {\n const cursor = (await this._store.loadCursor()) ?? this._now();\n const now = this._now();\n const windowEnd = now + 1;\n if (windowEnd <= cursor) {\n // Cursor is already at or past `now + 1` — happens on the very first\n // tick when we just-saved-and-rewoke. Nothing to do.\n return;\n }\n\n let events: ScheduledEvent<TPayload>[];\n try {\n events = await this._scheduler.list(cursor, windowEnd);\n } catch (err) {\n this._onError(err);\n // Don't advance cursor on a list failure — try again next tick.\n return;\n }\n\n for (const event of events) {\n if (!this._running) return;\n try {\n await this._fire(event);\n } catch (err) {\n this._onError(err, event);\n }\n }\n await this._store.saveCursor(windowEnd);\n }\n\n // Sleeps until either the target epoch-ms instant, or wake() is called\n // (via Scheduler.changed or stop()). No-op if target is already past.\n private async _sleepUntil(targetMs: number): Promise<void> {\n const remaining = targetMs - this._now();\n if (remaining <= 0) return;\n await new Promise<void>((resolve) => {\n let done = false;\n const timer = this._setTimeout(() => {\n if (done) return;\n done = true;\n this._wakeResolve = null;\n resolve();\n }, remaining);\n this._wakeResolve = () => {\n if (done) return;\n done = true;\n this._clearTimeout(timer);\n this._wakeResolve = null;\n resolve();\n };\n });\n }\n\n // Resolves any in-flight _sleepUntil. Called by stop() and by the\n // Scheduler.changed subscription.\n private _wake(): void {\n if (this._wakeResolve != null) this._wakeResolve();\n }\n}\n\nfunction defaultOnError(err: unknown, event?: ScheduledEvent<{ type: string }>): void {\n if (event != null) {\n console.error(`[Dispatcher] fire failed for ${event.templateId}/${event.occurrenceKey}:`, err);\n } else {\n console.error(\"[Dispatcher] error:\", err);\n }\n}\n"],"mappings":";;AAQA,IAAM,0BAA0B;AA4BhC,IAAa,aAAb,MAA2D;CACzD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,WAAmB;CACnB,eAA4C;CAC5C,sBAA+D;CAE/D,YAAY,QAAoC;EAC9C,KAAK,aAAa,OAAO;EACzB,KAAK,QAAQ,OAAO;EACpB,KAAK,SAAS,OAAO,SAAS,IAAI,wBAAwB;EAC1D,KAAK,WAAW,OAAO,WAAW;EAClC,KAAK,iBAAiB,OAAO,iBAAiB;EAC9C,KAAK,OAAO,OAAO,OAAO,KAAK;EAC/B,KAAK,cAAc,OAAO,gBAAgB,IAAI,OAAO,WAAW,WAAW,IAAI,EAAE;EACjF,KAAK,gBACH,OAAO,kBAAkB,MAAM,WAAW,aAAa,CAAkC;CAC7F;CAKA,MAAM,QAAuB;EAC3B,IAAI,KAAK,UAAU;EACnB,KAAK,WAAW;EAChB,KAAK,sBAAsB,KAAK,WAAW,QAAQ,gBAAgB,KAAK,MAAM,CAAC;EAE/E,MAAM,KAAK,MAAM;EACjB,IAAI,KAAK,UAAU,KAAU,MAAM;CACrC;CAIA,OAAa;EACX,IAAI,CAAC,KAAK,UAAU;EACpB,KAAK,WAAW;EAChB,IAAI,KAAK,uBAAuB,MAAM;GACpC,KAAK,oBAAoB,YAAY;GACrC,KAAK,sBAAsB;EAC7B;EACA,KAAK,MAAM;CACb;CAIA,MAAc,QAAuB;EACnC,OAAO,KAAK,UAAU;GACpB,MAAM,SAAS,MAAM,KAAK,gBAAgB;GAC1C,IAAI,CAAC,KAAK,UAAU;GACpB,MAAM,KAAK,YAAY,MAAM;GAC7B,IAAI,CAAC,KAAK,UAAU;GACpB,MAAM,KAAK,MAAM;EACnB;CACF;CAIA,MAAc,kBAAmC;EAC/C,IAAI;GAEF,MAAM,QAAO,MADU,KAAK,WAAW,QAAQ,KAAK,KAAK,GAAG,CAAC,EAAA,CACvC;GACtB,OAAO,QAAQ,OAAO,KAAK,iBAAiB,KAAK,KAAK,IAAI,KAAK;EACjE,SAAS,KAAK;GACZ,KAAK,SAAS,GAAG;GACjB,OAAO,KAAK,KAAK,IAAI,KAAK;EAC5B;CACF;CAQA,MAAc,QAAuB;EACnC,MAAM,SAAU,MAAM,KAAK,OAAO,WAAW,KAAM,KAAK,KAAK;EAE7D,MAAM,YADM,KAAK,KACC,IAAM;EACxB,IAAI,aAAa,QAGf;EAGF,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,KAAK,WAAW,KAAK,QAAQ,SAAS;EACvD,SAAS,KAAK;GACZ,KAAK,SAAS,GAAG;GAEjB;EACF;EAEA,KAAK,MAAM,SAAS,QAAQ;GAC1B,IAAI,CAAC,KAAK,UAAU;GACpB,IAAI;IACF,MAAM,KAAK,MAAM,KAAK;GACxB,SAAS,KAAK;IACZ,KAAK,SAAS,KAAK,KAAK;GAC1B;EACF;EACA,MAAM,KAAK,OAAO,WAAW,SAAS;CACxC;CAIA,MAAc,YAAY,UAAiC;EACzD,MAAM,YAAY,WAAW,KAAK,KAAK;EACvC,IAAI,aAAa,GAAG;EACpB,MAAM,IAAI,SAAe,YAAY;GACnC,IAAI,OAAO;GACX,MAAM,QAAQ,KAAK,kBAAkB;IACnC,IAAI,MAAM;IACV,OAAO;IACP,KAAK,eAAe;IACpB,QAAQ;GACV,GAAG,SAAS;GACZ,KAAK,qBAAqB;IACxB,IAAI,MAAM;IACV,OAAO;IACP,KAAK,cAAc,KAAK;IACxB,KAAK,eAAe;IACpB,QAAQ;GACV;EACF,CAAC;CACH;CAIA,QAAsB;EACpB,IAAI,KAAK,gBAAgB,MAAM,KAAK,aAAa;CACnD;AACF;AAEA,SAAS,eAAe,KAAc,OAAgD;CACpF,IAAI,SAAS,MACX,QAAQ,MAAM,gCAAgC,MAAM,WAAW,GAAG,MAAM,cAAc,IAAI,GAAG;MAE7F,QAAQ,MAAM,uBAAuB,GAAG;AAE5C"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface DispatcherStore {
|
|
2
|
+
loadCursor(): Promise<number | null>;
|
|
3
|
+
saveCursor(cursor: number): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare class InMemoryDispatcherStore implements DispatcherStore {
|
|
6
|
+
private _cursor;
|
|
7
|
+
loadCursor(): Promise<number | null>;
|
|
8
|
+
saveCursor(cursor: number): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=dispatcher_store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher_store.d.ts","sourceRoot":"","sources":["../src/dispatcher_store.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,eAAe;IAC9B,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAID,qBAAa,uBAAwB,YAAW,eAAe;IAC7D,OAAO,CAAC,OAAO,CAAuB;IAEhC,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIpC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/dispatcher_store.ts
|
|
2
|
+
var InMemoryDispatcherStore = class {
|
|
3
|
+
_cursor = null;
|
|
4
|
+
async loadCursor() {
|
|
5
|
+
return this._cursor;
|
|
6
|
+
}
|
|
7
|
+
async saveCursor(cursor) {
|
|
8
|
+
this._cursor = cursor;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
export { InMemoryDispatcherStore };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=dispatcher_store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher_store.js","names":[],"sources":["../src/dispatcher_store.ts"],"sourcesContent":["// Persistence for the Dispatcher's tick cursor. The cursor is a single\n// epoch-ms value: the last instant for which the Dispatcher has fired all\n// due events. On restart, the Dispatcher catches up by listing events in\n// [cursor, now) and firing them — so a process crash followed by restart\n// won't lose events that were due during downtime.\n//\n// Same shape as ChatStore / MemoryStore in the agent module — implement\n// against whatever backend you want.\nexport interface DispatcherStore {\n loadCursor(): Promise<number | null>;\n saveCursor(cursor: number): Promise<void>;\n}\n\n// Reference in-memory implementation. Useful for tests and short-lived\n// processes that don't need crash recovery.\nexport class InMemoryDispatcherStore implements DispatcherStore {\n private _cursor: number | null = null;\n\n async loadCursor(): Promise<number | null> {\n return this._cursor;\n }\n\n async saveCursor(cursor: number): Promise<void> {\n this._cursor = cursor;\n }\n}\n"],"mappings":";AAeA,IAAa,0BAAb,MAAgE;CAC9D,UAAiC;CAEjC,MAAM,aAAqC;EACzC,OAAO,KAAK;CACd;CAEA,MAAM,WAAW,QAA+B;EAC9C,KAAK,UAAU;CACjB;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchored.d.ts","sourceRoot":"","sources":["../../src/generators/anchored.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAEL,KAAK,UAAU,EAGhB,MAAM,YAAY,CAAC;AAKpB,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,UAAU,EAAE,CAmDd"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { computeSpanEnd } from "../tz.js";
|
|
2
|
+
import { TooManyOccurrencesError, intersects } from "./types.js";
|
|
3
|
+
// src/generators/anchored.ts
|
|
4
|
+
function generateAnchored(rule, tz, windowStart, windowEnd) {
|
|
5
|
+
if (rule.intervalMs <= 0) throw new Error(`AnchoredRule.intervalMs must be > 0 (got ${rule.intervalMs})`);
|
|
6
|
+
if (rule.count != null && rule.count < 0) throw new Error(`AnchoredRule.count must be >= 0 (got ${rule.count})`);
|
|
7
|
+
const seriesEnd = rule.seriesEnd ?? Number.POSITIVE_INFINITY;
|
|
8
|
+
const effectiveEnd = Math.min(windowEnd, seriesEnd);
|
|
9
|
+
const effectiveStart = Math.max(windowStart, rule.seriesStart);
|
|
10
|
+
if (effectiveStart >= effectiveEnd) return [];
|
|
11
|
+
let index;
|
|
12
|
+
let occurrenceStart;
|
|
13
|
+
if (rule.count != null) {
|
|
14
|
+
index = 0;
|
|
15
|
+
occurrenceStart = rule.anchorDate;
|
|
16
|
+
} else if (effectiveStart <= rule.anchorDate) {
|
|
17
|
+
index = 0;
|
|
18
|
+
occurrenceStart = rule.anchorDate;
|
|
19
|
+
} else {
|
|
20
|
+
const stepsAhead = Math.ceil((effectiveStart - rule.anchorDate) / rule.intervalMs);
|
|
21
|
+
index = stepsAhead;
|
|
22
|
+
occurrenceStart = rule.anchorDate + stepsAhead * rule.intervalMs;
|
|
23
|
+
}
|
|
24
|
+
const out = [];
|
|
25
|
+
while (occurrenceStart < effectiveEnd) {
|
|
26
|
+
if (rule.count != null && index >= rule.count) break;
|
|
27
|
+
const occEnd = computeSpanEnd(occurrenceStart, rule.timeSpan, tz);
|
|
28
|
+
if (intersects(occurrenceStart, occEnd, windowStart, windowEnd) && occurrenceStart >= rule.seriesStart) {
|
|
29
|
+
out.push({
|
|
30
|
+
startTimestamp: occurrenceStart,
|
|
31
|
+
endTimestamp: occEnd
|
|
32
|
+
});
|
|
33
|
+
if (out.length > 1e4) throw new TooManyOccurrencesError("anchored");
|
|
34
|
+
}
|
|
35
|
+
index++;
|
|
36
|
+
occurrenceStart += rule.intervalMs;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
export { generateAnchored };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=anchored.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchored.js","names":[],"sources":["../../src/generators/anchored.ts"],"sourcesContent":["import type { AnchoredRule } from \"../types.js\";\nimport { computeSpanEnd } from \"../tz.js\";\nimport {\n MAX_OCCURRENCES_PER_GENERATE,\n type Occurrence,\n TooManyOccurrencesError,\n intersects,\n} from \"./types.js\";\n\n// Anchored is tz-independent for STEPPING (every occurrence is exactly\n// intervalMs ms from the anchor), but tz can still affect the end-of-\n// occurrence when hourSpan is used. seriesStart/seriesEnd bound in UTC.\nexport function generateAnchored(\n rule: AnchoredRule,\n tz: string | undefined,\n windowStart: number,\n windowEnd: number\n): Occurrence[] {\n if (rule.intervalMs <= 0) {\n throw new Error(`AnchoredRule.intervalMs must be > 0 (got ${rule.intervalMs})`);\n }\n if (rule.count != null && rule.count < 0) {\n throw new Error(`AnchoredRule.count must be >= 0 (got ${rule.count})`);\n }\n\n const seriesEnd = rule.seriesEnd ?? Number.POSITIVE_INFINITY;\n const effectiveEnd = Math.min(windowEnd, seriesEnd);\n const effectiveStart = Math.max(windowStart, rule.seriesStart);\n\n if (effectiveStart >= effectiveEnd) return [];\n\n // If `count` is set we must start counting from the anchor; otherwise we\n // can fast-forward to the window start.\n let index: number;\n let occurrenceStart: number;\n if (rule.count != null) {\n index = 0;\n occurrenceStart = rule.anchorDate;\n } else {\n if (effectiveStart <= rule.anchorDate) {\n index = 0;\n occurrenceStart = rule.anchorDate;\n } else {\n // First occurrence at or after effectiveStart.\n const stepsAhead = Math.ceil((effectiveStart - rule.anchorDate) / rule.intervalMs);\n index = stepsAhead;\n occurrenceStart = rule.anchorDate + stepsAhead * rule.intervalMs;\n }\n }\n\n const out: Occurrence[] = [];\n while (occurrenceStart < effectiveEnd) {\n if (rule.count != null && index >= rule.count) break;\n\n const occEnd = computeSpanEnd(occurrenceStart, rule.timeSpan, tz);\n if (\n intersects(occurrenceStart, occEnd, windowStart, windowEnd) &&\n occurrenceStart >= rule.seriesStart\n ) {\n out.push({ startTimestamp: occurrenceStart, endTimestamp: occEnd });\n if (out.length > MAX_OCCURRENCES_PER_GENERATE) {\n throw new TooManyOccurrencesError(\"anchored\");\n }\n }\n index++;\n occurrenceStart += rule.intervalMs;\n }\n return out;\n}\n"],"mappings":";;;AAYA,SAAgB,iBACd,MACA,IACA,aACA,WACc;CACd,IAAI,KAAK,cAAc,GACrB,MAAM,IAAI,MAAM,4CAA4C,KAAK,WAAW,EAAE;CAEhF,IAAI,KAAK,SAAS,QAAQ,KAAK,QAAQ,GACrC,MAAM,IAAI,MAAM,wCAAwC,KAAK,MAAM,EAAE;CAGvE,MAAM,YAAY,KAAK,aAAa,OAAO;CAC3C,MAAM,eAAe,KAAK,IAAI,WAAW,SAAS;CAClD,MAAM,iBAAiB,KAAK,IAAI,aAAa,KAAK,WAAW;CAE7D,IAAI,kBAAkB,cAAc,OAAO,CAAC;CAI5C,IAAI;CACJ,IAAI;CACJ,IAAI,KAAK,SAAS,MAAM;EACtB,QAAQ;EACR,kBAAkB,KAAK;CACzB,OACE,IAAI,kBAAkB,KAAK,YAAY;EACrC,QAAQ;EACR,kBAAkB,KAAK;CACzB,OAAO;EAEL,MAAM,aAAa,KAAK,MAAM,iBAAiB,KAAK,cAAc,KAAK,UAAU;EACjF,QAAQ;EACR,kBAAkB,KAAK,aAAa,aAAa,KAAK;CACxD;CAGF,MAAM,MAAoB,CAAC;CAC3B,OAAO,kBAAkB,cAAc;EACrC,IAAI,KAAK,SAAS,QAAQ,SAAS,KAAK,OAAO;EAE/C,MAAM,SAAS,eAAe,iBAAiB,KAAK,UAAU,EAAE;EAChE,IACE,WAAW,iBAAiB,QAAQ,aAAa,SAAS,KAC1D,mBAAmB,KAAK,aACxB;GACA,IAAI,KAAK;IAAE,gBAAgB;IAAiB,cAAc;GAAO,CAAC;GAClE,IAAI,IAAI,SAAA,KACN,MAAM,IAAI,wBAAwB,UAAU;EAEhD;EACA;EACA,mBAAmB,KAAK;CAC1B;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { DaysOfMonthRule, Disambiguation } from '../types.js';
|
|
2
|
+
import { Occurrence } from './types.js';
|
|
3
|
+
export declare function generateDaysOfMonth(rule: DaysOfMonthRule, tz: string | undefined, windowStart: number, windowEnd: number, disambiguation?: Disambiguation): Occurrence[];
|
|
4
|
+
//# sourceMappingURL=days_of_month.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"days_of_month.d.ts","sourceRoot":"","sources":["../../src/generators/days_of_month.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQnE,OAAO,EAEL,KAAK,UAAU,EAGhB,MAAM,YAAY,CAAC;AAIpB,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,cAA6B,GAC5C,UAAU,EAAE,CA4Ed"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { computeSpanEnd, isNonexistentWallTime, monthLength, utcToWallParts, wallTimeToUtc } from "../tz.js";
|
|
2
|
+
import { TooManyOccurrencesError, intersects } from "./types.js";
|
|
3
|
+
// src/generators/days_of_month.ts
|
|
4
|
+
function generateDaysOfMonth(rule, tz, windowStart, windowEnd, disambiguation = "compatible") {
|
|
5
|
+
if (rule.yearInterval <= 0) throw new Error(`DaysOfMonthRule.yearInterval must be > 0 (got ${rule.yearInterval})`);
|
|
6
|
+
if (rule.months.length === 0) throw new Error("DaysOfMonthRule.months must be non-empty");
|
|
7
|
+
for (const m of rule.months) if (m < 0 || m > 11) throw new Error(`DaysOfMonthRule.months entries must be in 0..11 (got ${m})`);
|
|
8
|
+
if (rule.daysOfMonth.length === 0) throw new Error("DaysOfMonthRule.daysOfMonth must be non-empty");
|
|
9
|
+
for (const d of rule.daysOfMonth) if (d < 1 || d > 31) throw new Error(`DaysOfMonthRule.daysOfMonth entries must be in 1..31 (got ${d})`);
|
|
10
|
+
if (rule.count != null && rule.count < 0) throw new Error(`DaysOfMonthRule.count must be >= 0 (got ${rule.count})`);
|
|
11
|
+
const seriesEnd = rule.seriesEnd ?? Number.POSITIVE_INFINITY;
|
|
12
|
+
const effectiveEnd = Math.min(windowEnd, seriesEnd);
|
|
13
|
+
if (rule.seriesStart >= effectiveEnd) return [];
|
|
14
|
+
const months = [...new Set(rule.months)].sort((a, b) => a - b);
|
|
15
|
+
const daysOfMonth = [...new Set(rule.daysOfMonth)].sort((a, b) => a - b);
|
|
16
|
+
const startYear = utcToWallParts(rule.seriesStart, tz).year;
|
|
17
|
+
const endYear = utcToWallParts(Math.min(effectiveEnd, Number.MAX_SAFE_INTEGER), tz).year;
|
|
18
|
+
const out = [];
|
|
19
|
+
let count = 0;
|
|
20
|
+
for (let year = startYear; year <= endYear; year += rule.yearInterval) {
|
|
21
|
+
const yearEmits = [];
|
|
22
|
+
for (const month of months) {
|
|
23
|
+
const len = monthLength(year, month);
|
|
24
|
+
const clampedDays = [...new Set(daysOfMonth.map((d) => Math.min(d, len)))];
|
|
25
|
+
for (const day of clampedDays) {
|
|
26
|
+
if (disambiguation === "skip" && isNonexistentWallTime(year, month, day, rule.timeOfDay, tz)) continue;
|
|
27
|
+
const occStart = wallTimeToUtc(year, month, day, rule.timeOfDay, tz, disambiguation);
|
|
28
|
+
const occEnd = computeSpanEnd(occStart, rule.timeSpan, tz);
|
|
29
|
+
if (occStart < rule.seriesStart) continue;
|
|
30
|
+
if (occStart >= seriesEnd) continue;
|
|
31
|
+
if (rule.count != null && count >= rule.count) {
|
|
32
|
+
yearEmits.sort((a, b) => a.startTimestamp - b.startTimestamp);
|
|
33
|
+
out.push(...yearEmits);
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
count++;
|
|
37
|
+
if (intersects(occStart, occEnd, windowStart, windowEnd)) {
|
|
38
|
+
yearEmits.push({
|
|
39
|
+
startTimestamp: occStart,
|
|
40
|
+
endTimestamp: occEnd
|
|
41
|
+
});
|
|
42
|
+
if (out.length + yearEmits.length > 1e4) throw new TooManyOccurrencesError("daysOfMonth");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
yearEmits.sort((a, b) => a.startTimestamp - b.startTimestamp);
|
|
47
|
+
out.push(...yearEmits);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
export { generateDaysOfMonth };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=days_of_month.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"days_of_month.js","names":[],"sources":["../../src/generators/days_of_month.ts"],"sourcesContent":["import type { DaysOfMonthRule, Disambiguation } from \"../types.js\";\nimport {\n computeSpanEnd,\n isNonexistentWallTime,\n monthLength,\n utcToWallParts,\n wallTimeToUtc,\n} from \"../tz.js\";\nimport {\n MAX_OCCURRENCES_PER_GENERATE,\n type Occurrence,\n TooManyOccurrencesError,\n intersects,\n} from \"./types.js\";\n\n// Numerical date-based recurrence: months × daysOfMonth. Days clamp per\n// month (day 31 in April → Apr 30, day 29 in Feb non-leap → Feb 28).\nexport function generateDaysOfMonth(\n rule: DaysOfMonthRule,\n tz: string | undefined,\n windowStart: number,\n windowEnd: number,\n disambiguation: Disambiguation = \"compatible\"\n): Occurrence[] {\n if (rule.yearInterval <= 0) {\n throw new Error(`DaysOfMonthRule.yearInterval must be > 0 (got ${rule.yearInterval})`);\n }\n if (rule.months.length === 0) {\n throw new Error(\"DaysOfMonthRule.months must be non-empty\");\n }\n for (const m of rule.months) {\n if (m < 0 || m > 11) {\n throw new Error(`DaysOfMonthRule.months entries must be in 0..11 (got ${m})`);\n }\n }\n if (rule.daysOfMonth.length === 0) {\n throw new Error(\"DaysOfMonthRule.daysOfMonth must be non-empty\");\n }\n for (const d of rule.daysOfMonth) {\n if (d < 1 || d > 31) {\n throw new Error(`DaysOfMonthRule.daysOfMonth entries must be in 1..31 (got ${d})`);\n }\n }\n if (rule.count != null && rule.count < 0) {\n throw new Error(`DaysOfMonthRule.count must be >= 0 (got ${rule.count})`);\n }\n\n const seriesEnd = rule.seriesEnd ?? Number.POSITIVE_INFINITY;\n const effectiveEnd = Math.min(windowEnd, seriesEnd);\n if (rule.seriesStart >= effectiveEnd) return [];\n\n const months = [...new Set(rule.months)].sort((a, b) => a - b);\n const daysOfMonth = [...new Set(rule.daysOfMonth)].sort((a, b) => a - b);\n\n const startYear = utcToWallParts(rule.seriesStart, tz).year;\n const endYear = utcToWallParts(Math.min(effectiveEnd, Number.MAX_SAFE_INTEGER), tz).year;\n\n const out: Occurrence[] = [];\n let count = 0;\n\n for (let year = startYear; year <= endYear; year += rule.yearInterval) {\n const yearEmits: Occurrence[] = [];\n\n for (const month of months) {\n const len = monthLength(year, month);\n // Dedup clamped days so day-31 + day-30 in April don't both emit Apr 30.\n const clampedDays = [...new Set(daysOfMonth.map((d) => Math.min(d, len)))];\n for (const day of clampedDays) {\n if (\n disambiguation === \"skip\" &&\n isNonexistentWallTime(year, month, day, rule.timeOfDay, tz)\n ) {\n continue;\n }\n const occStart = wallTimeToUtc(year, month, day, rule.timeOfDay, tz, disambiguation);\n const occEnd = computeSpanEnd(occStart, rule.timeSpan, tz);\n\n if (occStart < rule.seriesStart) continue;\n if (occStart >= seriesEnd) continue;\n\n if (rule.count != null && count >= rule.count) {\n yearEmits.sort((a, b) => a.startTimestamp - b.startTimestamp);\n out.push(...yearEmits);\n return out;\n }\n count++;\n\n if (intersects(occStart, occEnd, windowStart, windowEnd)) {\n yearEmits.push({ startTimestamp: occStart, endTimestamp: occEnd });\n if (out.length + yearEmits.length > MAX_OCCURRENCES_PER_GENERATE) {\n throw new TooManyOccurrencesError(\"daysOfMonth\");\n }\n }\n }\n }\n yearEmits.sort((a, b) => a.startTimestamp - b.startTimestamp);\n out.push(...yearEmits);\n }\n return out;\n}\n"],"mappings":";;;AAiBA,SAAgB,oBACd,MACA,IACA,aACA,WACA,iBAAiC,cACnB;CACd,IAAI,KAAK,gBAAgB,GACvB,MAAM,IAAI,MAAM,iDAAiD,KAAK,aAAa,EAAE;CAEvF,IAAI,KAAK,OAAO,WAAW,GACzB,MAAM,IAAI,MAAM,0CAA0C;CAE5D,KAAK,MAAM,KAAK,KAAK,QACnB,IAAI,IAAI,KAAK,IAAI,IACf,MAAM,IAAI,MAAM,wDAAwD,EAAE,EAAE;CAGhF,IAAI,KAAK,YAAY,WAAW,GAC9B,MAAM,IAAI,MAAM,+CAA+C;CAEjE,KAAK,MAAM,KAAK,KAAK,aACnB,IAAI,IAAI,KAAK,IAAI,IACf,MAAM,IAAI,MAAM,6DAA6D,EAAE,EAAE;CAGrF,IAAI,KAAK,SAAS,QAAQ,KAAK,QAAQ,GACrC,MAAM,IAAI,MAAM,2CAA2C,KAAK,MAAM,EAAE;CAG1E,MAAM,YAAY,KAAK,aAAa,OAAO;CAC3C,MAAM,eAAe,KAAK,IAAI,WAAW,SAAS;CAClD,IAAI,KAAK,eAAe,cAAc,OAAO,CAAC;CAE9C,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAC7D,MAAM,cAAc,CAAC,GAAG,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAEvE,MAAM,YAAY,eAAe,KAAK,aAAa,EAAE,CAAC,CAAC;CACvD,MAAM,UAAU,eAAe,KAAK,IAAI,cAAc,OAAO,gBAAgB,GAAG,EAAE,CAAC,CAAC;CAEpF,MAAM,MAAoB,CAAC;CAC3B,IAAI,QAAQ;CAEZ,KAAK,IAAI,OAAO,WAAW,QAAQ,SAAS,QAAQ,KAAK,cAAc;EACrE,MAAM,YAA0B,CAAC;EAEjC,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,MAAM,YAAY,MAAM,KAAK;GAEnC,MAAM,cAAc,CAAC,GAAG,IAAI,IAAI,YAAY,KAAK,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC;GACzE,KAAK,MAAM,OAAO,aAAa;IAC7B,IACE,mBAAmB,UACnB,sBAAsB,MAAM,OAAO,KAAK,KAAK,WAAW,EAAE,GAE1D;IAEF,MAAM,WAAW,cAAc,MAAM,OAAO,KAAK,KAAK,WAAW,IAAI,cAAc;IACnF,MAAM,SAAS,eAAe,UAAU,KAAK,UAAU,EAAE;IAEzD,IAAI,WAAW,KAAK,aAAa;IACjC,IAAI,YAAY,WAAW;IAE3B,IAAI,KAAK,SAAS,QAAQ,SAAS,KAAK,OAAO;KAC7C,UAAU,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;KAC5D,IAAI,KAAK,GAAG,SAAS;KACrB,OAAO;IACT;IACA;IAEA,IAAI,WAAW,UAAU,QAAQ,aAAa,SAAS,GAAG;KACxD,UAAU,KAAK;MAAE,gBAAgB;MAAU,cAAc;KAAO,CAAC;KACjE,IAAI,IAAI,SAAS,UAAU,SAAA,KACzB,MAAM,IAAI,wBAAwB,aAAa;IAEnD;GACF;EACF;EACA,UAAU,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;EAC5D,IAAI,KAAK,GAAG,SAAS;CACvB;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DaysOfWeekRule, Disambiguation } from '../types.js';
|
|
2
|
+
import { dayOfWeekInTz } from '../tz.js';
|
|
3
|
+
import { Occurrence } from './types.js';
|
|
4
|
+
export declare function generateDaysOfWeek(rule: DaysOfWeekRule, tz: string | undefined, windowStart: number, windowEnd: number, disambiguation?: Disambiguation): Occurrence[];
|
|
5
|
+
export { dayOfWeekInTz };
|
|
6
|
+
//# sourceMappingURL=days_of_week.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"days_of_week.d.ts","sourceRoot":"","sources":["../../src/generators/days_of_week.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAGL,aAAa,EAId,MAAM,UAAU,CAAC;AAClB,OAAO,EAEL,KAAK,UAAU,EAGhB,MAAM,YAAY,CAAC;AAKpB,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,cAA6B,GAC5C,UAAU,EAAE,CAwEd;AAGD,OAAO,EAAE,aAAa,EAAE,CAAC"}
|