@j13b/scheduler 0.0.0-stage → 0.1.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -2
  3. package/dist/alterations.d.ts +6 -0
  4. package/dist/alterations.d.ts.map +1 -0
  5. package/dist/alterations.js +61 -0
  6. package/dist/alterations.js.map +1 -0
  7. package/dist/cjs/alterations.d.ts +5 -0
  8. package/dist/cjs/alterations.js +96 -0
  9. package/dist/cjs/dispatcher.d.ts +38 -0
  10. package/dist/cjs/dispatcher.js +165 -0
  11. package/dist/cjs/dispatcher_store.d.ts +9 -0
  12. package/dist/cjs/dispatcher_store.js +15 -0
  13. package/dist/cjs/generators/anchored.d.ts +3 -0
  14. package/dist/cjs/generators/anchored.js +57 -0
  15. package/dist/cjs/generators/days_of_month.d.ts +3 -0
  16. package/dist/cjs/generators/days_of_month.js +76 -0
  17. package/dist/cjs/generators/days_of_week.d.ts +5 -0
  18. package/dist/cjs/generators/days_of_week.js +65 -0
  19. package/dist/cjs/generators/days_of_year.d.ts +3 -0
  20. package/dist/cjs/generators/days_of_year.js +70 -0
  21. package/dist/cjs/generators/instant.d.ts +3 -0
  22. package/dist/cjs/generators/instant.js +15 -0
  23. package/dist/cjs/generators/local_date_time.d.ts +3 -0
  24. package/dist/cjs/generators/local_date_time.js +31 -0
  25. package/dist/cjs/generators/months_of_year.d.ts +3 -0
  26. package/dist/cjs/generators/months_of_year.js +109 -0
  27. package/dist/cjs/generators/types.d.ts +9 -0
  28. package/dist/cjs/generators/types.js +19 -0
  29. package/dist/cjs/generators/weeks_of_month.d.ts +3 -0
  30. package/dist/cjs/generators/weeks_of_month.js +99 -0
  31. package/dist/cjs/generators/yearly.d.ts +3 -0
  32. package/dist/cjs/generators/yearly.js +52 -0
  33. package/dist/cjs/in_memory_port.d.ts +18 -0
  34. package/dist/cjs/in_memory_port.js +59 -0
  35. package/dist/cjs/index.d.ts +7 -0
  36. package/dist/cjs/index.js +18 -0
  37. package/dist/cjs/occurrence_key.d.ts +5 -0
  38. package/dist/cjs/occurrence_key.js +21 -0
  39. package/dist/cjs/package.json +3 -0
  40. package/dist/cjs/port.d.ts +15 -0
  41. package/dist/cjs/port.js +2 -0
  42. package/dist/cjs/scheduler.d.ts +23 -0
  43. package/dist/cjs/scheduler.js +302 -0
  44. package/dist/cjs/types.d.ts +149 -0
  45. package/dist/cjs/types.js +58 -0
  46. package/dist/cjs/tz.d.ts +22 -0
  47. package/dist/cjs/tz.js +197 -0
  48. package/dist/cjs/validate_rule.d.ts +4 -0
  49. package/dist/cjs/validate_rule.js +267 -0
  50. package/dist/dispatcher.d.ts +39 -0
  51. package/dist/dispatcher.d.ts.map +1 -0
  52. package/dist/dispatcher.js +111 -0
  53. package/dist/dispatcher.js.map +1 -0
  54. package/dist/dispatcher_store.d.ts +10 -0
  55. package/dist/dispatcher_store.d.ts.map +1 -0
  56. package/dist/dispatcher_store.js +13 -0
  57. package/dist/dispatcher_store.js.map +1 -0
  58. package/dist/generators/anchored.d.ts +4 -0
  59. package/dist/generators/anchored.d.ts.map +1 -0
  60. package/dist/generators/anchored.js +42 -0
  61. package/dist/generators/anchored.js.map +1 -0
  62. package/dist/generators/days_of_month.d.ts +4 -0
  63. package/dist/generators/days_of_month.d.ts.map +1 -0
  64. package/dist/generators/days_of_month.js +53 -0
  65. package/dist/generators/days_of_month.js.map +1 -0
  66. package/dist/generators/days_of_week.d.ts +6 -0
  67. package/dist/generators/days_of_week.d.ts.map +1 -0
  68. package/dist/generators/days_of_week.js +43 -0
  69. package/dist/generators/days_of_week.js.map +1 -0
  70. package/dist/generators/days_of_year.d.ts +4 -0
  71. package/dist/generators/days_of_year.d.ts.map +1 -0
  72. package/dist/generators/days_of_year.js +50 -0
  73. package/dist/generators/days_of_year.js.map +1 -0
  74. package/dist/generators/instant.d.ts +4 -0
  75. package/dist/generators/instant.d.ts.map +1 -0
  76. package/dist/generators/instant.js +15 -0
  77. package/dist/generators/instant.js.map +1 -0
  78. package/dist/generators/local_date_time.d.ts +4 -0
  79. package/dist/generators/local_date_time.d.ts.map +1 -0
  80. package/dist/generators/local_date_time.js +20 -0
  81. package/dist/generators/local_date_time.js.map +1 -0
  82. package/dist/generators/months_of_year.d.ts +4 -0
  83. package/dist/generators/months_of_year.d.ts.map +1 -0
  84. package/dist/generators/months_of_year.js +64 -0
  85. package/dist/generators/months_of_year.js.map +1 -0
  86. package/dist/generators/types.d.ts +10 -0
  87. package/dist/generators/types.d.ts.map +1 -0
  88. package/dist/generators/types.js +14 -0
  89. package/dist/generators/types.js.map +1 -0
  90. package/dist/generators/weeks_of_month.d.ts +4 -0
  91. package/dist/generators/weeks_of_month.d.ts.map +1 -0
  92. package/dist/generators/weeks_of_month.js +61 -0
  93. package/dist/generators/weeks_of_month.js.map +1 -0
  94. package/dist/generators/yearly.d.ts +4 -0
  95. package/dist/generators/yearly.d.ts.map +1 -0
  96. package/dist/generators/yearly.js +37 -0
  97. package/dist/generators/yearly.js.map +1 -0
  98. package/dist/in_memory_port.d.ts +19 -0
  99. package/dist/in_memory_port.d.ts.map +1 -0
  100. package/dist/in_memory_port.js +45 -0
  101. package/dist/in_memory_port.js.map +1 -0
  102. package/dist/index.d.ts +8 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +7 -0
  105. package/dist/occurrence_key.d.ts +6 -0
  106. package/dist/occurrence_key.d.ts.map +1 -0
  107. package/dist/occurrence_key.js +25 -0
  108. package/dist/occurrence_key.js.map +1 -0
  109. package/dist/port.d.ts +16 -0
  110. package/dist/port.d.ts.map +1 -0
  111. package/dist/scheduler.d.ts +24 -0
  112. package/dist/scheduler.d.ts.map +1 -0
  113. package/dist/scheduler.js +241 -0
  114. package/dist/scheduler.js.map +1 -0
  115. package/dist/types.d.ts +150 -0
  116. package/dist/types.d.ts.map +1 -0
  117. package/dist/types.js +29 -0
  118. package/dist/types.js.map +1 -0
  119. package/dist/tz.d.ts +23 -0
  120. package/dist/tz.d.ts.map +1 -0
  121. package/dist/tz.js +131 -0
  122. package/dist/tz.js.map +1 -0
  123. package/dist/validate_rule.d.ts +5 -0
  124. package/dist/validate_rule.d.ts.map +1 -0
  125. package/dist/validate_rule.js +280 -0
  126. package/dist/validate_rule.js.map +1 -0
  127. package/package.json +76 -4
  128. package/src/__tests__/alterations.test.ts +177 -0
  129. package/src/__tests__/anchored.test.ts +107 -0
  130. package/src/__tests__/days_of_month.test.ts +162 -0
  131. package/src/__tests__/days_of_week.test.ts +134 -0
  132. package/src/__tests__/days_of_year.test.ts +158 -0
  133. package/src/__tests__/dispatcher.test.ts +317 -0
  134. package/src/__tests__/instant.test.ts +50 -0
  135. package/src/__tests__/local_date_time.test.ts +139 -0
  136. package/src/__tests__/months_of_year.test.ts +229 -0
  137. package/src/__tests__/scheduler.test.ts +888 -0
  138. package/src/__tests__/tz.test.ts +201 -0
  139. package/src/__tests__/validate_rule.test.ts +190 -0
  140. package/src/__tests__/weeks_of_month.test.ts +195 -0
  141. package/src/__tests__/yearly.test.ts +108 -0
  142. package/src/alterations.ts +108 -0
  143. package/src/dispatcher.ts +184 -0
  144. package/src/dispatcher_store.ts +26 -0
  145. package/src/generators/anchored.ts +69 -0
  146. package/src/generators/days_of_month.ts +100 -0
  147. package/src/generators/days_of_week.ts +101 -0
  148. package/src/generators/days_of_year.ts +88 -0
  149. package/src/generators/instant.ts +18 -0
  150. package/src/generators/local_date_time.ts +38 -0
  151. package/src/generators/months_of_year.ts +143 -0
  152. package/src/generators/types.ts +26 -0
  153. package/src/generators/weeks_of_month.ts +133 -0
  154. package/src/generators/yearly.ts +73 -0
  155. package/src/in_memory_port.ts +70 -0
  156. package/src/index.ts +25 -0
  157. package/src/occurrence_key.ts +42 -0
  158. package/src/port.ts +31 -0
  159. package/src/scheduler.ts +353 -0
  160. package/src/types.ts +369 -0
  161. package/src/tz.ts +246 -0
  162. package/src/validate_rule.ts +311 -0
  163. package/tsconfig.json +25 -0
@@ -0,0 +1,229 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { generateMonthsOfYear } from "../generators/months_of_year.js";
3
+ import { Day, Month, type MonthsOfYearRule } from "../types.js";
4
+
5
+ const HOUR = 3_600_000;
6
+
7
+ describe("generateMonthsOfYear — positional", () => {
8
+ test("Thanksgiving: 4th Thursday of November every year", () => {
9
+ const rule: MonthsOfYearRule = {
10
+ kind: "monthsOfYear",
11
+ months: [Month.November],
12
+ weeks: [4],
13
+ days: [Day.Thursday],
14
+ yearInterval: 1,
15
+ timeOfDay: 12 * HOUR,
16
+ timeSpan: { type: "duration", ms: HOUR },
17
+ seriesStart: Date.UTC(2024, 0, 1),
18
+ seriesEnd: null,
19
+ };
20
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2024, 0, 1), Date.UTC(2027, 0, 1));
21
+ expect(out.map((o) => new Date(o.startTimestamp).toISOString())).toEqual([
22
+ "2024-11-28T12:00:00.000Z",
23
+ "2025-11-27T12:00:00.000Z",
24
+ "2026-11-26T12:00:00.000Z",
25
+ ]);
26
+ });
27
+
28
+ test("Mother's Day: 2nd Sunday of May every year", () => {
29
+ const rule: MonthsOfYearRule = {
30
+ kind: "monthsOfYear",
31
+ months: [Month.May],
32
+ weeks: [2],
33
+ days: [Day.Sunday],
34
+ yearInterval: 1,
35
+ timeOfDay: 0,
36
+ timeSpan: { type: "duration", ms: HOUR },
37
+ seriesStart: Date.UTC(2024, 0, 1),
38
+ seriesEnd: null,
39
+ };
40
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2024, 0, 1), Date.UTC(2027, 0, 1));
41
+ expect(out.map((o) => new Date(o.startTimestamp).toISOString())).toEqual([
42
+ "2024-05-12T00:00:00.000Z",
43
+ "2025-05-11T00:00:00.000Z",
44
+ "2026-05-10T00:00:00.000Z",
45
+ ]);
46
+ });
47
+
48
+ test("1st Monday of Jan AND Jul, every year", () => {
49
+ const rule: MonthsOfYearRule = {
50
+ kind: "monthsOfYear",
51
+ months: [Month.January, Month.July],
52
+ weeks: [1],
53
+ days: [Day.Monday],
54
+ yearInterval: 1,
55
+ timeOfDay: 9 * HOUR,
56
+ timeSpan: { type: "duration", ms: HOUR },
57
+ seriesStart: Date.UTC(2026, 0, 1),
58
+ seriesEnd: null,
59
+ };
60
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2026, 0, 1), Date.UTC(2027, 0, 1));
61
+ // Jan 2026: 1st Mon = Jan 5. Jul 2026: 1st Mon = Jul 6.
62
+ expect(out.map((o) => new Date(o.startTimestamp).toISOString())).toEqual([
63
+ "2026-01-05T09:00:00.000Z",
64
+ "2026-07-06T09:00:00.000Z",
65
+ ]);
66
+ });
67
+
68
+ test("last Friday of every quarter (Mar/Jun/Sep/Dec)", () => {
69
+ const rule: MonthsOfYearRule = {
70
+ kind: "monthsOfYear",
71
+ months: [Month.March, Month.June, Month.September, Month.December],
72
+ weeks: [-1],
73
+ days: [Day.Friday],
74
+ yearInterval: 1,
75
+ timeOfDay: 17 * HOUR,
76
+ timeSpan: { type: "duration", ms: HOUR },
77
+ seriesStart: Date.UTC(2026, 0, 1),
78
+ seriesEnd: null,
79
+ };
80
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2026, 0, 1), Date.UTC(2027, 0, 1));
81
+ expect(out.map((o) => new Date(o.startTimestamp).toISOString())).toEqual([
82
+ "2026-03-27T17:00:00.000Z",
83
+ "2026-06-26T17:00:00.000Z",
84
+ "2026-09-25T17:00:00.000Z",
85
+ "2026-12-25T17:00:00.000Z",
86
+ ]);
87
+ });
88
+
89
+ test("5th Sunday of every month: emits only when it exists", () => {
90
+ // Only fires in months that have a 5th occurrence of the weekday.
91
+ const rule: MonthsOfYearRule = {
92
+ kind: "monthsOfYear",
93
+ months: [
94
+ Month.January,
95
+ Month.February,
96
+ Month.March,
97
+ Month.April,
98
+ Month.May,
99
+ Month.June,
100
+ Month.July,
101
+ Month.August,
102
+ Month.September,
103
+ Month.October,
104
+ Month.November,
105
+ Month.December,
106
+ ],
107
+ weeks: [5],
108
+ days: [Day.Sunday],
109
+ yearInterval: 1,
110
+ timeOfDay: 0,
111
+ timeSpan: { type: "duration", ms: HOUR },
112
+ seriesStart: Date.UTC(2026, 0, 1),
113
+ seriesEnd: null,
114
+ };
115
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2026, 0, 1), Date.UTC(2027, 0, 1));
116
+ // 2026 5th Sundays: Mar 29, May 31, Aug 30, Nov 29
117
+ expect(out.map((o) => new Date(o.startTimestamp).toISOString())).toEqual([
118
+ "2026-03-29T00:00:00.000Z",
119
+ "2026-05-31T00:00:00.000Z",
120
+ "2026-08-30T00:00:00.000Z",
121
+ "2026-11-29T00:00:00.000Z",
122
+ ]);
123
+ });
124
+
125
+ test("first AND last week, multiple weekdays", () => {
126
+ const rule: MonthsOfYearRule = {
127
+ kind: "monthsOfYear",
128
+ months: [Month.June],
129
+ weeks: [1, -1],
130
+ days: [Day.Monday, Day.Friday],
131
+ yearInterval: 1,
132
+ timeOfDay: 8 * HOUR,
133
+ timeSpan: { type: "duration", ms: HOUR },
134
+ seriesStart: Date.UTC(2026, 0, 1),
135
+ seriesEnd: null,
136
+ };
137
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2026, 5, 1), Date.UTC(2026, 6, 1));
138
+ // June 2026: Mondays = 1, 8, 15, 22, 29; Fridays = 5, 12, 19, 26
139
+ // 1st Mon = Jun 1, 1st Fri = Jun 5, last Mon = Jun 29, last Fri = Jun 26
140
+ expect(out.map((o) => new Date(o.startTimestamp).toISOString())).toEqual([
141
+ "2026-06-01T08:00:00.000Z",
142
+ "2026-06-05T08:00:00.000Z",
143
+ "2026-06-26T08:00:00.000Z",
144
+ "2026-06-29T08:00:00.000Z",
145
+ ]);
146
+ });
147
+
148
+ test("yearInterval = 2", () => {
149
+ const rule: MonthsOfYearRule = {
150
+ kind: "monthsOfYear",
151
+ months: [Month.January],
152
+ weeks: [1],
153
+ days: [Day.Monday],
154
+ yearInterval: 2,
155
+ timeOfDay: 0,
156
+ timeSpan: { type: "duration", ms: HOUR },
157
+ seriesStart: Date.UTC(2024, 0, 1),
158
+ seriesEnd: null,
159
+ };
160
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2024, 0, 1), Date.UTC(2030, 0, 1));
161
+ expect(out.map((o) => new Date(o.startTimestamp).toISOString())).toEqual([
162
+ "2024-01-01T00:00:00.000Z", // Mon Jan 1 2024
163
+ "2026-01-05T00:00:00.000Z",
164
+ "2028-01-03T00:00:00.000Z",
165
+ ]);
166
+ });
167
+
168
+ test("count caps emissions", () => {
169
+ const rule: MonthsOfYearRule = {
170
+ kind: "monthsOfYear",
171
+ months: [Month.January, Month.July],
172
+ weeks: [1],
173
+ days: [Day.Monday],
174
+ yearInterval: 1,
175
+ timeOfDay: 0,
176
+ timeSpan: { type: "duration", ms: HOUR },
177
+ seriesStart: Date.UTC(2024, 0, 1),
178
+ seriesEnd: null,
179
+ count: 3,
180
+ };
181
+ const out = generateMonthsOfYear(rule, "UTC", Date.UTC(2024, 0, 1), Date.UTC(2030, 0, 1));
182
+ expect(out).toHaveLength(3);
183
+ });
184
+
185
+ test("throws on empty months / weeks / days", () => {
186
+ const base = {
187
+ kind: "monthsOfYear" as const,
188
+ yearInterval: 1,
189
+ timeOfDay: 0,
190
+ timeSpan: { type: "duration", ms: HOUR } as const,
191
+ seriesStart: 0,
192
+ seriesEnd: null,
193
+ };
194
+ expect(() =>
195
+ generateMonthsOfYear({ ...base, months: [], weeks: [1], days: [Day.Monday] }, "UTC", 0, 1000)
196
+ ).toThrow(/months/);
197
+ expect(() =>
198
+ generateMonthsOfYear(
199
+ { ...base, months: [Month.January], weeks: [], days: [Day.Monday] },
200
+ "UTC",
201
+ 0,
202
+ 1000
203
+ )
204
+ ).toThrow(/weeks/);
205
+ expect(() =>
206
+ generateMonthsOfYear(
207
+ { ...base, months: [Month.January], weeks: [1], days: [] },
208
+ "UTC",
209
+ 0,
210
+ 1000
211
+ )
212
+ ).toThrow(/days/);
213
+ });
214
+
215
+ test("throws on weeks entry of 0", () => {
216
+ const rule: MonthsOfYearRule = {
217
+ kind: "monthsOfYear",
218
+ months: [Month.January],
219
+ weeks: [0],
220
+ days: [Day.Monday],
221
+ yearInterval: 1,
222
+ timeOfDay: 0,
223
+ timeSpan: { type: "duration", ms: HOUR },
224
+ seriesStart: 0,
225
+ seriesEnd: null,
226
+ };
227
+ expect(() => generateMonthsOfYear(rule, "UTC", 0, 1000)).toThrow(/weeks/);
228
+ });
229
+ });