@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,37 @@
1
+ import { computeSpanEnd, isNonexistentWallTime, monthLength, utcToWallParts, wallTimeToUtc } from "../tz.js";
2
+ import { TooManyOccurrencesError, intersects } from "./types.js";
3
+ // src/generators/yearly.ts
4
+ function generateYearly(rule, tz, windowStart, windowEnd, disambiguation = "compatible") {
5
+ if (rule.yearInterval <= 0) throw new Error(`YearlyRule.yearInterval must be > 0 (got ${rule.yearInterval})`);
6
+ if (rule.month < 0 || rule.month > 11) throw new Error(`YearlyRule.month must be in 0..11 (got ${rule.month})`);
7
+ if (rule.day < 1 || rule.day > 31) throw new Error(`YearlyRule.day must be in 1..31 (got ${rule.day})`);
8
+ if (rule.count != null && rule.count < 0) throw new Error(`YearlyRule.count must be >= 0 (got ${rule.count})`);
9
+ const seriesEnd = rule.seriesEnd ?? Number.POSITIVE_INFINITY;
10
+ const effectiveEnd = Math.min(windowEnd, seriesEnd);
11
+ if (rule.seriesStart >= effectiveEnd) return [];
12
+ const startYear = utcToWallParts(rule.seriesStart, tz).year;
13
+ const endYear = utcToWallParts(Math.min(effectiveEnd, Number.MAX_SAFE_INTEGER), tz).year;
14
+ const out = [];
15
+ let count = 0;
16
+ for (let year = startYear; year <= endYear; year += rule.yearInterval) {
17
+ const day = Math.min(rule.day, monthLength(year, rule.month));
18
+ if (disambiguation === "skip" && isNonexistentWallTime(year, rule.month, day, rule.timeOfDay, tz)) continue;
19
+ const occStart = wallTimeToUtc(year, rule.month, day, rule.timeOfDay, tz, disambiguation);
20
+ const occEnd = computeSpanEnd(occStart, rule.timeSpan, tz);
21
+ if (occStart < rule.seriesStart) continue;
22
+ if (occStart >= seriesEnd) continue;
23
+ if (rule.count != null && count >= rule.count) break;
24
+ count++;
25
+ if (intersects(occStart, occEnd, windowStart, windowEnd)) {
26
+ out.push({
27
+ startTimestamp: occStart,
28
+ endTimestamp: occEnd
29
+ });
30
+ if (out.length > 1e4) throw new TooManyOccurrencesError("yearly");
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+ export { generateYearly };
36
+
37
+ //# sourceMappingURL=yearly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yearly.js","names":[],"sources":["../../src/generators/yearly.ts"],"sourcesContent":["import type { Disambiguation, YearlyRule } 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// Simple yearly recurrence: a single month + day. Day clamps to month\n// length (Feb 29 → Feb 28 in non-leap years, Apr 31 → Apr 30, etc.).\nexport function generateYearly(\n rule: YearlyRule,\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(`YearlyRule.yearInterval must be > 0 (got ${rule.yearInterval})`);\n }\n if (rule.month < 0 || rule.month > 11) {\n throw new Error(`YearlyRule.month must be in 0..11 (got ${rule.month})`);\n }\n if (rule.day < 1 || rule.day > 31) {\n throw new Error(`YearlyRule.day must be in 1..31 (got ${rule.day})`);\n }\n if (rule.count != null && rule.count < 0) {\n throw new Error(`YearlyRule.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 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 day = Math.min(rule.day, monthLength(year, rule.month));\n if (\n disambiguation === \"skip\" &&\n isNonexistentWallTime(year, rule.month, day, rule.timeOfDay, tz)\n ) {\n continue;\n }\n const occStart = wallTimeToUtc(year, rule.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) break;\n count++;\n\n if (intersects(occStart, occEnd, windowStart, windowEnd)) {\n out.push({ startTimestamp: occStart, endTimestamp: occEnd });\n if (out.length > MAX_OCCURRENCES_PER_GENERATE) {\n throw new TooManyOccurrencesError(\"yearly\");\n }\n }\n }\n return out;\n}\n"],"mappings":";;;AAiBA,SAAgB,eACd,MACA,IACA,aACA,WACA,iBAAiC,cACnB;CACd,IAAI,KAAK,gBAAgB,GACvB,MAAM,IAAI,MAAM,4CAA4C,KAAK,aAAa,EAAE;CAElF,IAAI,KAAK,QAAQ,KAAK,KAAK,QAAQ,IACjC,MAAM,IAAI,MAAM,0CAA0C,KAAK,MAAM,EAAE;CAEzE,IAAI,KAAK,MAAM,KAAK,KAAK,MAAM,IAC7B,MAAM,IAAI,MAAM,wCAAwC,KAAK,IAAI,EAAE;CAErE,IAAI,KAAK,SAAS,QAAQ,KAAK,QAAQ,GACrC,MAAM,IAAI,MAAM,sCAAsC,KAAK,MAAM,EAAE;CAGrE,MAAM,YAAY,KAAK,aAAa,OAAO;CAC3C,MAAM,eAAe,KAAK,IAAI,WAAW,SAAS;CAClD,IAAI,KAAK,eAAe,cAAc,OAAO,CAAC;CAE9C,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,MAAM,KAAK,IAAI,KAAK,KAAK,YAAY,MAAM,KAAK,KAAK,CAAC;EAC5D,IACE,mBAAmB,UACnB,sBAAsB,MAAM,KAAK,OAAO,KAAK,KAAK,WAAW,EAAE,GAE/D;EAEF,MAAM,WAAW,cAAc,MAAM,KAAK,OAAO,KAAK,KAAK,WAAW,IAAI,cAAc;EACxF,MAAM,SAAS,eAAe,UAAU,KAAK,UAAU,EAAE;EAEzD,IAAI,WAAW,KAAK,aAAa;EACjC,IAAI,YAAY,WAAW;EAE3B,IAAI,KAAK,SAAS,QAAQ,SAAS,KAAK,OAAO;EAC/C;EAEA,IAAI,WAAW,UAAU,QAAQ,aAAa,SAAS,GAAG;GACxD,IAAI,KAAK;IAAE,gBAAgB;IAAU,cAAc;GAAO,CAAC;GAC3D,IAAI,IAAI,SAAA,KACN,MAAM,IAAI,wBAAwB,QAAQ;EAE9C;CACF;CACA,OAAO;AACT"}
@@ -0,0 +1,19 @@
1
+ import { SchedulerPort } from './port.js';
2
+ import { Alteration, ScheduleTemplate } from './types.js';
3
+ export declare class InMemorySchedulerPort<TPayload extends {
4
+ type: string;
5
+ }> implements SchedulerPort<TPayload> {
6
+ private _templates;
7
+ private _alterations;
8
+ listTemplates(_start: number, _end: number): Promise<ScheduleTemplate<TPayload>[]>;
9
+ getTemplate(id: string): Promise<ScheduleTemplate<TPayload> | null>;
10
+ addTemplate(template: ScheduleTemplate<TPayload>): Promise<void>;
11
+ updateTemplate(template: ScheduleTemplate<TPayload>): Promise<void>;
12
+ removeTemplate(id: string): Promise<void>;
13
+ listAlterations(_start: number, _end: number): Promise<Alteration<TPayload>[]>;
14
+ listAlterationsByTemplate(templateId: string): Promise<Alteration<TPayload>[]>;
15
+ addAlteration(alteration: Alteration<TPayload>): Promise<void>;
16
+ removeAlteration(id: string): Promise<void>;
17
+ removeAlterationsForTemplate(templateId: string): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=in_memory_port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in_memory_port.d.ts","sourceRoot":"","sources":["../src/in_memory_port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAO/D,qBAAa,qBAAqB,CAAC,QAAQ,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAClE,YAAW,aAAa,CAAC,QAAQ,CAAC;IAElC,OAAO,CAAC,UAAU,CAAiD;IACnE,OAAO,CAAC,YAAY,CAA2C;IAEzD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAIlF,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAKnE,WAAW,CAAC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhE,cAAc,CAAC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnE,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;IAI9E,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;IAI9E,aAAa,CAAC,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAKtE"}
@@ -0,0 +1,45 @@
1
+ // src/in_memory_port.ts
2
+ var InMemorySchedulerPort = class {
3
+ _templates = /* @__PURE__ */ new Map();
4
+ _alterations = /* @__PURE__ */ new Map();
5
+ async listTemplates(_start, _end) {
6
+ return [...this._templates.values()].map(clone);
7
+ }
8
+ async getTemplate(id) {
9
+ const t = this._templates.get(id);
10
+ return t == null ? null : clone(t);
11
+ }
12
+ async addTemplate(template) {
13
+ if (this._templates.has(template.id)) throw new Error(`Template already exists: ${template.id}`);
14
+ this._templates.set(template.id, clone(template));
15
+ }
16
+ async updateTemplate(template) {
17
+ if (!this._templates.has(template.id)) throw new Error(`No template with id ${template.id}`);
18
+ this._templates.set(template.id, clone(template));
19
+ }
20
+ async removeTemplate(id) {
21
+ this._templates.delete(id);
22
+ }
23
+ async listAlterations(_start, _end) {
24
+ return [...this._alterations.values()].map(clone);
25
+ }
26
+ async listAlterationsByTemplate(templateId) {
27
+ return [...this._alterations.values()].filter((a) => a.templateId === templateId).map(clone);
28
+ }
29
+ async addAlteration(alteration) {
30
+ if (this._alterations.has(alteration.id)) throw new Error(`Alteration already exists: ${alteration.id}`);
31
+ this._alterations.set(alteration.id, clone(alteration));
32
+ }
33
+ async removeAlteration(id) {
34
+ this._alterations.delete(id);
35
+ }
36
+ async removeAlterationsForTemplate(templateId) {
37
+ for (const [id, a] of this._alterations) if (a.templateId === templateId) this._alterations.delete(id);
38
+ }
39
+ };
40
+ function clone(value) {
41
+ return JSON.parse(JSON.stringify(value));
42
+ }
43
+ export { InMemorySchedulerPort };
44
+
45
+ //# sourceMappingURL=in_memory_port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in_memory_port.js","names":[],"sources":["../src/in_memory_port.ts"],"sourcesContent":["import type { SchedulerPort } from \"./port.js\";\nimport type { Alteration, ScheduleTemplate } from \"./types.js\";\n\n// Reference port. Stores templates and alterations in plain Maps. Returns\n// deep-cloned copies so callers can't mutate internal state.\n//\n// Range queries return everything and let the Scheduler filter — correct,\n// but O(n) per query. A real backend would index on seriesStart/seriesEnd.\nexport class InMemorySchedulerPort<TPayload extends { type: string }>\n implements SchedulerPort<TPayload>\n{\n private _templates = new Map<string, ScheduleTemplate<TPayload>>();\n private _alterations = new Map<string, Alteration<TPayload>>();\n\n async listTemplates(_start: number, _end: number): Promise<ScheduleTemplate<TPayload>[]> {\n return [...this._templates.values()].map(clone);\n }\n\n async getTemplate(id: string): Promise<ScheduleTemplate<TPayload> | null> {\n const t = this._templates.get(id);\n return t == null ? null : clone(t);\n }\n\n async addTemplate(template: ScheduleTemplate<TPayload>): Promise<void> {\n if (this._templates.has(template.id)) {\n throw new Error(`Template already exists: ${template.id}`);\n }\n this._templates.set(template.id, clone(template));\n }\n\n async updateTemplate(template: ScheduleTemplate<TPayload>): Promise<void> {\n if (!this._templates.has(template.id)) {\n throw new Error(`No template with id ${template.id}`);\n }\n this._templates.set(template.id, clone(template));\n }\n\n async removeTemplate(id: string): Promise<void> {\n this._templates.delete(id);\n }\n\n async listAlterations(_start: number, _end: number): Promise<Alteration<TPayload>[]> {\n return [...this._alterations.values()].map(clone);\n }\n\n async listAlterationsByTemplate(templateId: string): Promise<Alteration<TPayload>[]> {\n return [...this._alterations.values()].filter((a) => a.templateId === templateId).map(clone);\n }\n\n async addAlteration(alteration: Alteration<TPayload>): Promise<void> {\n if (this._alterations.has(alteration.id)) {\n throw new Error(`Alteration already exists: ${alteration.id}`);\n }\n this._alterations.set(alteration.id, clone(alteration));\n }\n\n async removeAlteration(id: string): Promise<void> {\n this._alterations.delete(id);\n }\n\n async removeAlterationsForTemplate(templateId: string): Promise<void> {\n for (const [id, a] of this._alterations) {\n if (a.templateId === templateId) this._alterations.delete(id);\n }\n }\n}\n\nfunction clone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n"],"mappings":";AAQA,IAAa,wBAAb,MAEA;CACE,6BAAqB,IAAI,IAAwC;CACjE,+BAAuB,IAAI,IAAkC;CAE7D,MAAM,cAAc,QAAgB,MAAqD;EACvF,OAAO,CAAC,GAAG,KAAK,WAAW,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK;CAChD;CAEA,MAAM,YAAY,IAAwD;EACxE,MAAM,IAAI,KAAK,WAAW,IAAI,EAAE;EAChC,OAAO,KAAK,OAAO,OAAO,MAAM,CAAC;CACnC;CAEA,MAAM,YAAY,UAAqD;EACrE,IAAI,KAAK,WAAW,IAAI,SAAS,EAAE,GACjC,MAAM,IAAI,MAAM,4BAA4B,SAAS,IAAI;EAE3D,KAAK,WAAW,IAAI,SAAS,IAAI,MAAM,QAAQ,CAAC;CAClD;CAEA,MAAM,eAAe,UAAqD;EACxE,IAAI,CAAC,KAAK,WAAW,IAAI,SAAS,EAAE,GAClC,MAAM,IAAI,MAAM,uBAAuB,SAAS,IAAI;EAEtD,KAAK,WAAW,IAAI,SAAS,IAAI,MAAM,QAAQ,CAAC;CAClD;CAEA,MAAM,eAAe,IAA2B;EAC9C,KAAK,WAAW,OAAO,EAAE;CAC3B;CAEA,MAAM,gBAAgB,QAAgB,MAA+C;EACnF,OAAO,CAAC,GAAG,KAAK,aAAa,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK;CAClD;CAEA,MAAM,0BAA0B,YAAqD;EACnF,OAAO,CAAC,GAAG,KAAK,aAAa,OAAO,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,eAAe,UAAU,CAAC,CAAC,IAAI,KAAK;CAC7F;CAEA,MAAM,cAAc,YAAiD;EACnE,IAAI,KAAK,aAAa,IAAI,WAAW,EAAE,GACrC,MAAM,IAAI,MAAM,8BAA8B,WAAW,IAAI;EAE/D,KAAK,aAAa,IAAI,WAAW,IAAI,MAAM,UAAU,CAAC;CACxD;CAEA,MAAM,iBAAiB,IAA2B;EAChD,KAAK,aAAa,OAAO,EAAE;CAC7B;CAEA,MAAM,6BAA6B,YAAmC;EACpE,KAAK,MAAM,CAAC,IAAI,MAAM,KAAK,cACzB,IAAI,EAAE,eAAe,YAAY,KAAK,aAAa,OAAO,EAAE;CAEhE;AACF;AAEA,SAAS,MAAS,OAAa;CAC7B,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC"}
@@ -0,0 +1,8 @@
1
+ export { Scheduler } from './scheduler.js';
2
+ export { InMemorySchedulerPort } from './in_memory_port.js';
3
+ export type { SchedulerPort } from './port.js';
4
+ export { Dispatcher, type DispatcherConfig } from './dispatcher.js';
5
+ export { type DispatcherStore, InMemoryDispatcherStore } from './dispatcher_store.js';
6
+ export { Day, Month, type RecurrenceRule, type InstantRule, type LocalDateTimeRule, type AnchoredRule, type DaysOfWeekRule, type WeeksOfMonthRule, type MonthsOfYearRule, type YearlyRule, type DaysOfMonthRule, type DaysOfYearRule, type ScheduleTemplate, type Alteration, type ScheduledEvent, type TimeSpan, type Disambiguation, } from './types.js';
7
+ export { RULE_KINDS, validateRule, validateTimeSpan } from './validate_rule.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,KAAK,eAAe,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACL,GAAG,EACH,KAAK,EACL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { Scheduler } from "./scheduler.js";
2
+ import { InMemorySchedulerPort } from "./in_memory_port.js";
3
+ import { Dispatcher } from "./dispatcher.js";
4
+ import { InMemoryDispatcherStore } from "./dispatcher_store.js";
5
+ import { Day, Month } from "./types.js";
6
+ import { RULE_KINDS, validateRule, validateTimeSpan } from "./validate_rule.js";
7
+ export { Day, Dispatcher, InMemoryDispatcherStore, InMemorySchedulerPort, Month, RULE_KINDS, Scheduler, validateRule, validateTimeSpan };
@@ -0,0 +1,6 @@
1
+ import { Occurrence } from './generators/types.js';
2
+ export interface KeyedOccurrence extends Occurrence {
3
+ occurrenceKey: string;
4
+ }
5
+ export declare function assignOccurrenceKeys(occurrences: readonly Occurrence[], tz: string | undefined): KeyedOccurrence[];
6
+ //# sourceMappingURL=occurrence_key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"occurrence_key.d.ts","sourceRoot":"","sources":["../src/occurrence_key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAgBxD,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,EAAE,EAAE,MAAM,GAAG,SAAS,GACrB,eAAe,EAAE,CAUnB"}
@@ -0,0 +1,25 @@
1
+ import { utcToWallParts } from "./tz.js";
2
+ // src/occurrence_key.ts
3
+ function assignOccurrenceKeys(occurrences, tz) {
4
+ const counts = /* @__PURE__ */ new Map();
5
+ return occurrences.map((occ) => {
6
+ const w = utcToWallParts(occ.startTimestamp, tz);
7
+ const date = `${_pad4(w.year)}-${_pad2(w.month + 1)}-${_pad2(w.day)}`;
8
+ const idx = counts.get(date) ?? 0;
9
+ counts.set(date, idx + 1);
10
+ const occurrenceKey = idx === 0 ? date : `${date}#${idx}`;
11
+ return {
12
+ ...occ,
13
+ occurrenceKey
14
+ };
15
+ });
16
+ }
17
+ function _pad2(n) {
18
+ return n < 10 ? `0${n}` : String(n);
19
+ }
20
+ function _pad4(n) {
21
+ return n < 10 ? `000${n}` : n < 100 ? `00${n}` : n < 1e3 ? `0${n}` : String(n);
22
+ }
23
+ export { assignOccurrenceKeys };
24
+
25
+ //# sourceMappingURL=occurrence_key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"occurrence_key.js","names":[],"sources":["../src/occurrence_key.ts"],"sourcesContent":["import type { Occurrence } from \"./generators/types.js\";\nimport { utcToWallParts } from \"./tz.js\";\n\n// A key identifies a specific occurrence within a template's series. It must\n// be:\n// - Stable across small rule edits (e.g. tweaking timeOfDay)\n// - Unique within a single generate() call's output for one template\n//\n// Format: `YYYY-MM-DD` (in the template's tz), with `#N` suffix when multiple\n// occurrences land on the same calendar day. Index is 0-based by emission\n// order within that day.\n//\n// Trade-off: changing timeOfDay across the date boundary moves an event to\n// a different day, which orphans alterations on it. We accept that as the\n// cost of human-readable, edit-tolerant keys. Same goes for timezone changes.\n\nexport interface KeyedOccurrence extends Occurrence {\n occurrenceKey: string;\n}\n\nexport function assignOccurrenceKeys(\n occurrences: readonly Occurrence[],\n tz: string | undefined\n): KeyedOccurrence[] {\n const counts = new Map<string, number>();\n return occurrences.map((occ) => {\n const w = utcToWallParts(occ.startTimestamp, tz);\n const date = `${_pad4(w.year)}-${_pad2(w.month + 1)}-${_pad2(w.day)}`;\n const idx = counts.get(date) ?? 0;\n counts.set(date, idx + 1);\n const occurrenceKey = idx === 0 ? date : `${date}#${idx}`;\n return { ...occ, occurrenceKey };\n });\n}\n\nfunction _pad2(n: number): string {\n return n < 10 ? `0${n}` : String(n);\n}\n\nfunction _pad4(n: number): string {\n return n < 10 ? `000${n}` : n < 100 ? `00${n}` : n < 1000 ? `0${n}` : String(n);\n}\n"],"mappings":";;AAoBA,SAAgB,qBACd,aACA,IACmB;CACnB,MAAM,yBAAS,IAAI,IAAoB;CACvC,OAAO,YAAY,KAAK,QAAQ;EAC9B,MAAM,IAAI,eAAe,IAAI,gBAAgB,EAAE;EAC/C,MAAM,OAAO,GAAG,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG;EAClE,MAAM,MAAM,OAAO,IAAI,IAAI,KAAK;EAChC,OAAO,IAAI,MAAM,MAAM,CAAC;EACxB,MAAM,gBAAgB,QAAQ,IAAI,OAAO,GAAG,KAAK,GAAG;EACpD,OAAO;GAAE,GAAG;GAAK;EAAc;CACjC,CAAC;AACH;AAEA,SAAS,MAAM,GAAmB;CAChC,OAAO,IAAI,KAAK,IAAI,MAAM,OAAO,CAAC;AACpC;AAEA,SAAS,MAAM,GAAmB;CAChC,OAAO,IAAI,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,MAAO,IAAI,MAAM,OAAO,CAAC;AAChF"}
package/dist/port.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { Alteration, ScheduleTemplate } from './types.js';
2
+ export interface SchedulerPort<TPayload extends {
3
+ type: string;
4
+ }> {
5
+ listTemplates(start: number, end: number): Promise<ScheduleTemplate<TPayload>[]>;
6
+ getTemplate(id: string): Promise<ScheduleTemplate<TPayload> | null>;
7
+ addTemplate(template: ScheduleTemplate<TPayload>): Promise<void>;
8
+ updateTemplate(template: ScheduleTemplate<TPayload>): Promise<void>;
9
+ removeTemplate(id: string): Promise<void>;
10
+ listAlterations(start: number, end: number): Promise<Alteration<TPayload>[]>;
11
+ listAlterationsByTemplate(templateId: string): Promise<Alteration<TPayload>[]>;
12
+ addAlteration(alteration: Alteration<TPayload>): Promise<void>;
13
+ removeAlteration(id: string): Promise<void>;
14
+ removeAlterationsForTemplate(templateId: string): Promise<void>;
15
+ }
16
+ //# sourceMappingURL=port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAW/D,MAAM,WAAW,aAAa,CAAC,QAAQ,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;IAE9D,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACjF,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;IACpE,WAAW,CAAC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,cAAc,CAAC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG1C,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE7E,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC/E,aAAa,CAAC,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAI5C,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE"}
@@ -0,0 +1,24 @@
1
+ import { IEventBroadcast } from '@j13b/state';
2
+ import { SchedulerPort } from './port.js';
3
+ import { ScheduleTemplate, ScheduledEvent, TimeSpan } from './types.js';
4
+ export declare class Scheduler<TPayload extends {
5
+ type: string;
6
+ }> {
7
+ private readonly _port;
8
+ private readonly _changed;
9
+ constructor(port: SchedulerPort<TPayload>);
10
+ get changed(): IEventBroadcast<void>;
11
+ list(windowStart: number, windowEnd: number): Promise<ScheduledEvent<TPayload>[]>;
12
+ next(templateId: string, after: number): Promise<ScheduledEvent<TPayload> | null>;
13
+ nextDue(after: number, limit?: number): Promise<ScheduledEvent<TPayload>[]>;
14
+ add(template: ScheduleTemplate<TPayload>): Promise<void>;
15
+ update(delta: {
16
+ id: string;
17
+ } & Partial<Omit<ScheduleTemplate<TPayload>, "id">>): Promise<void>;
18
+ remove(id: string): Promise<void>;
19
+ reschedule(templateId: string, occurrenceKey: string, newStart: number, newTimeSpan?: TimeSpan, payloadOverride?: Partial<TPayload>): Promise<string>;
20
+ cancel(templateId: string, occurrenceKey: string): Promise<string>;
21
+ private _generateKeyed;
22
+ private _removeExistingAlteration;
23
+ }
24
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAa1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAIV,gBAAgB,EAChB,cAAc,EACd,QAAQ,EACT,MAAM,YAAY,CAAC;AAUpB,qBAAa,SAAS,CAAC,QAAQ,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;IAIhD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;gBAElC,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC;IAIzC,IAAI,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,CAEnC;IAMK,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;IA0BjF,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IA+BjF,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;IAuBvE,GAAG,CAAC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAexD,MAAM,CACV,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,GACtE,OAAO,CAAC,IAAI,CAAC;IAuBV,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjC,UAAU,CACd,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,QAAQ,EACtB,eAAe,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,GAClC,OAAO,CAAC,MAAM,CAAC;IAkBZ,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBxE,OAAO,CAAC,cAAc;YA4CR,yBAAyB;CAWxC"}
@@ -0,0 +1,241 @@
1
+ import { applyAlterations } from "./alterations.js";
2
+ import { generateAnchored } from "./generators/anchored.js";
3
+ import { generateDaysOfMonth } from "./generators/days_of_month.js";
4
+ import { generateDaysOfWeek } from "./generators/days_of_week.js";
5
+ import { generateDaysOfYear } from "./generators/days_of_year.js";
6
+ import { generateInstant } from "./generators/instant.js";
7
+ import { generateLocalDateTime } from "./generators/local_date_time.js";
8
+ import { generateMonthsOfYear } from "./generators/months_of_year.js";
9
+ import { generateWeeksOfMonth } from "./generators/weeks_of_month.js";
10
+ import { generateYearly } from "./generators/yearly.js";
11
+ import { assignOccurrenceKeys } from "./occurrence_key.js";
12
+ import { isValidTimeZone } from "./tz.js";
13
+ import { validateRule, validateTimeSpan } from "./validate_rule.js";
14
+ import { Event } from "@j13b/state";
15
+ // src/scheduler.ts
16
+ var DAY_MS = 864e5;
17
+ var NEXT_SEARCH_HORIZON_MS = 1825 * DAY_MS;
18
+ var Scheduler = class {
19
+ _port;
20
+ _changed = new Event();
21
+ constructor(port) {
22
+ this._port = port;
23
+ }
24
+ get changed() {
25
+ return this._changed.broadcast;
26
+ }
27
+ async list(windowStart, windowEnd) {
28
+ if (windowEnd <= windowStart) return [];
29
+ const [templates, alterations] = await Promise.all([this._port.listTemplates(windowStart, windowEnd), this._port.listAlterations(windowStart, windowEnd)]);
30
+ const out = [];
31
+ for (const tpl of templates) {
32
+ const keyed = this._generateKeyed(tpl, windowStart, windowEnd);
33
+ const events = applyAlterations(tpl.id, tpl.payload, keyed, alterations, tpl.timezone, windowStart, windowEnd);
34
+ out.push(...events);
35
+ }
36
+ out.sort((a, b) => a.startTimestamp - b.startTimestamp);
37
+ return out;
38
+ }
39
+ async next(templateId, after) {
40
+ const tpl = await this._port.getTemplate(templateId);
41
+ if (tpl == null) return null;
42
+ const horizonEnd = after + NEXT_SEARCH_HORIZON_MS;
43
+ let windowStart = after;
44
+ let windowSize = DAY_MS;
45
+ while (windowStart < horizonEnd) {
46
+ const windowEnd = Math.min(windowStart + windowSize, horizonEnd);
47
+ const alterations = await this._port.listAlterationsByTemplate(templateId);
48
+ const keyed = this._generateKeyed(tpl, windowStart, windowEnd);
49
+ const firstAfter = applyAlterations(tpl.id, tpl.payload, keyed, alterations, tpl.timezone, windowStart, windowEnd).find((e) => e.startTimestamp > after);
50
+ if (firstAfter != null) return firstAfter;
51
+ windowStart = windowEnd;
52
+ windowSize = Math.min(windowSize * 2, 365 * DAY_MS);
53
+ }
54
+ return null;
55
+ }
56
+ async nextDue(after, limit = 10) {
57
+ if (limit <= 0) return [];
58
+ const horizonEnd = after + NEXT_SEARCH_HORIZON_MS;
59
+ const out = [];
60
+ let windowStart = after;
61
+ let windowSize = DAY_MS;
62
+ while (windowStart < horizonEnd && out.length < limit) {
63
+ const windowEnd = Math.min(windowStart + windowSize, horizonEnd);
64
+ const events = await this.list(windowStart, windowEnd);
65
+ for (const e of events) if (e.startTimestamp > after) {
66
+ out.push(e);
67
+ if (out.length >= limit) break;
68
+ }
69
+ windowStart = windowEnd;
70
+ windowSize = Math.min(windowSize * 2, 365 * DAY_MS);
71
+ }
72
+ return out;
73
+ }
74
+ async add(template) {
75
+ validateRule(template.rule);
76
+ _validateTimezone(template.timezone);
77
+ _validateDisambiguation(template.disambiguation);
78
+ const normalized = {
79
+ ...template,
80
+ rule: _normalizeRule(template.rule)
81
+ };
82
+ await this._port.addTemplate(normalized);
83
+ this._changed.notify();
84
+ }
85
+ async update(delta) {
86
+ const existing = await this._port.getTemplate(delta.id);
87
+ if (existing == null) throw new Error(`No template with id ${delta.id}`);
88
+ const merged = {
89
+ ...existing,
90
+ ...delta,
91
+ id: delta.id
92
+ };
93
+ validateRule(merged.rule);
94
+ _validateTimezone(merged.timezone);
95
+ _validateDisambiguation(merged.disambiguation);
96
+ const updated = {
97
+ ...merged,
98
+ rule: _normalizeRule(merged.rule)
99
+ };
100
+ const ruleChanged = !_rulesEqual(existing.rule, updated.rule);
101
+ await this._port.updateTemplate(updated);
102
+ if (ruleChanged) await this._port.removeAlterationsForTemplate(updated.id);
103
+ this._changed.notify();
104
+ }
105
+ async remove(id) {
106
+ await this._port.removeAlterationsForTemplate(id);
107
+ await this._port.removeTemplate(id);
108
+ this._changed.notify();
109
+ }
110
+ async reschedule(templateId, occurrenceKey, newStart, newTimeSpan, payloadOverride) {
111
+ if (newTimeSpan != null) validateTimeSpan(newTimeSpan);
112
+ const id = `alt-${templateId}-${occurrenceKey}-${Date.now()}`;
113
+ await this._removeExistingAlteration(templateId, occurrenceKey);
114
+ const alt = {
115
+ id,
116
+ templateId,
117
+ occurrenceKey,
118
+ kind: "reschedule",
119
+ startTimestamp: newStart,
120
+ timeSpan: newTimeSpan,
121
+ payloadOverride
122
+ };
123
+ await this._port.addAlteration(alt);
124
+ this._changed.notify();
125
+ return id;
126
+ }
127
+ async cancel(templateId, occurrenceKey) {
128
+ const id = `alt-${templateId}-${occurrenceKey}-${Date.now()}`;
129
+ await this._removeExistingAlteration(templateId, occurrenceKey);
130
+ const alt = {
131
+ id,
132
+ templateId,
133
+ occurrenceKey,
134
+ kind: "cancel"
135
+ };
136
+ await this._port.addAlteration(alt);
137
+ this._changed.notify();
138
+ return id;
139
+ }
140
+ _generateKeyed(tpl, windowStart, windowEnd) {
141
+ const tz = tpl.timezone;
142
+ const dis = tpl.disambiguation ?? "compatible";
143
+ let occurrences;
144
+ switch (tpl.rule.kind) {
145
+ case "instant":
146
+ occurrences = generateInstant(tpl.rule, tz, windowStart, windowEnd);
147
+ break;
148
+ case "localDateTime":
149
+ occurrences = generateLocalDateTime(tpl.rule, tz, windowStart, windowEnd, dis);
150
+ break;
151
+ case "anchored":
152
+ occurrences = generateAnchored(tpl.rule, tz, windowStart, windowEnd);
153
+ break;
154
+ case "daysOfWeek":
155
+ occurrences = generateDaysOfWeek(tpl.rule, tz, windowStart, windowEnd, dis);
156
+ break;
157
+ case "weeksOfMonth":
158
+ occurrences = generateWeeksOfMonth(tpl.rule, tz, windowStart, windowEnd, dis);
159
+ break;
160
+ case "monthsOfYear":
161
+ occurrences = generateMonthsOfYear(tpl.rule, tz, windowStart, windowEnd, dis);
162
+ break;
163
+ case "yearly":
164
+ occurrences = generateYearly(tpl.rule, tz, windowStart, windowEnd, dis);
165
+ break;
166
+ case "daysOfMonth":
167
+ occurrences = generateDaysOfMonth(tpl.rule, tz, windowStart, windowEnd, dis);
168
+ break;
169
+ case "daysOfYear":
170
+ occurrences = generateDaysOfYear(tpl.rule, tz, windowStart, windowEnd, dis);
171
+ break;
172
+ default: {
173
+ const _exhaustive = tpl.rule;
174
+ throw new Error(`Unknown rule kind: ${JSON.stringify(_exhaustive)}`);
175
+ }
176
+ }
177
+ return assignOccurrenceKeys(occurrences, tz);
178
+ }
179
+ async _removeExistingAlteration(templateId, occurrenceKey) {
180
+ const existing = await this._port.listAlterationsByTemplate(templateId);
181
+ for (const alt of existing) if (alt.occurrenceKey === occurrenceKey) await this._port.removeAlteration(alt.id);
182
+ }
183
+ };
184
+ function _normalizeRule(rule) {
185
+ if (rule.timeSpan != null) return rule;
186
+ return {
187
+ ...rule,
188
+ timeSpan: {
189
+ type: "duration",
190
+ ms: 1
191
+ }
192
+ };
193
+ }
194
+ function _validateTimezone(tz) {
195
+ if (typeof tz !== "string" || tz.length === 0) throw new Error("ScheduleTemplate.timezone is required (use 'UTC' for zone-free scheduling)");
196
+ if (!isValidTimeZone(tz)) throw new Error(`Unknown timezone '${tz}' (expected an IANA zone name or 'UTC')`);
197
+ }
198
+ var _DISAMBIGUATIONS = [
199
+ "compatible",
200
+ "earlier",
201
+ "later",
202
+ "reject",
203
+ "skip"
204
+ ];
205
+ function _validateDisambiguation(d) {
206
+ if (d == null) return;
207
+ if (!_DISAMBIGUATIONS.includes(d)) throw new Error(`Unknown disambiguation '${d}' (expected one of ${_DISAMBIGUATIONS.join(", ")})`);
208
+ }
209
+ function _rulesEqual(a, b) {
210
+ return JSON.stringify(_canonicalize(a)) === JSON.stringify(_canonicalize(b));
211
+ }
212
+ function _canonicalize(rule) {
213
+ if (rule.kind === "daysOfWeek") return {
214
+ ...rule,
215
+ days: [...new Set(rule.days)].sort((a, b) => a - b)
216
+ };
217
+ if (rule.kind === "weeksOfMonth") return {
218
+ ...rule,
219
+ days: [...new Set(rule.days)].sort((a, b) => a - b),
220
+ weeks: [...new Set(rule.weeks)].sort((a, b) => a - b)
221
+ };
222
+ if (rule.kind === "monthsOfYear") return {
223
+ ...rule,
224
+ months: [...new Set(rule.months)].sort((a, b) => a - b),
225
+ weeks: [...new Set(rule.weeks)].sort((a, b) => a - b),
226
+ days: [...new Set(rule.days)].sort((a, b) => a - b)
227
+ };
228
+ if (rule.kind === "daysOfMonth") return {
229
+ ...rule,
230
+ months: [...new Set(rule.months)].sort((a, b) => a - b),
231
+ daysOfMonth: [...new Set(rule.daysOfMonth)].sort((a, b) => a - b)
232
+ };
233
+ if (rule.kind === "daysOfYear") return {
234
+ ...rule,
235
+ daysOfYear: [...new Set(rule.daysOfYear)].sort((a, b) => a - b)
236
+ };
237
+ return rule;
238
+ }
239
+ export { Scheduler };
240
+
241
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","names":[],"sources":["../src/scheduler.ts"],"sourcesContent":["import { Event, type IEventBroadcast } from \"@j13b/state\";\nimport { applyAlterations } from \"./alterations.js\";\nimport { generateAnchored } from \"./generators/anchored.js\";\nimport { generateDaysOfMonth } from \"./generators/days_of_month.js\";\nimport { generateDaysOfWeek } from \"./generators/days_of_week.js\";\nimport { generateDaysOfYear } from \"./generators/days_of_year.js\";\nimport { generateInstant } from \"./generators/instant.js\";\nimport { generateLocalDateTime } from \"./generators/local_date_time.js\";\nimport { generateMonthsOfYear } from \"./generators/months_of_year.js\";\nimport type { Occurrence } from \"./generators/types.js\";\nimport { generateWeeksOfMonth } from \"./generators/weeks_of_month.js\";\nimport { generateYearly } from \"./generators/yearly.js\";\nimport { assignOccurrenceKeys } from \"./occurrence_key.js\";\nimport type { SchedulerPort } from \"./port.js\";\nimport type {\n Alteration,\n Disambiguation,\n RecurrenceRule,\n ScheduleTemplate,\n ScheduledEvent,\n TimeSpan,\n} from \"./types.js\";\nimport { isValidTimeZone } from \"./tz.js\";\nimport { validateRule, validateTimeSpan } from \"./validate_rule.js\";\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n// Search horizon for next() / nextDue() before giving up. Five years covers\n// every realistic use; if you need longer, call list() with an explicit\n// window.\nconst NEXT_SEARCH_HORIZON_MS = 5 * 365 * DAY_MS;\n\nexport class Scheduler<TPayload extends { type: string }> {\n private readonly _port: SchedulerPort<TPayload>;\n // Fires after every successful write (add/update/remove/reschedule/cancel).\n // The Dispatcher subscribes via .changed so a new event added during a\n // sleep can interrupt the sleep and re-query nextDue().\n private readonly _changed = new Event<void>();\n\n constructor(port: SchedulerPort<TPayload>) {\n this._port = port;\n }\n\n get changed(): IEventBroadcast<void> {\n return this._changed.broadcast;\n }\n\n // ─── Reads ──────────────────────────────────────────────────\n\n // All occurrences whose [start, end) intersects [windowStart, windowEnd),\n // sorted ascending by startTimestamp. Honors alterations.\n async list(windowStart: number, windowEnd: number): Promise<ScheduledEvent<TPayload>[]> {\n if (windowEnd <= windowStart) return [];\n const [templates, alterations] = await Promise.all([\n this._port.listTemplates(windowStart, windowEnd),\n this._port.listAlterations(windowStart, windowEnd),\n ]);\n const out: ScheduledEvent<TPayload>[] = [];\n for (const tpl of templates) {\n const keyed = this._generateKeyed(tpl, windowStart, windowEnd);\n const events = applyAlterations(\n tpl.id,\n tpl.payload,\n keyed,\n alterations,\n tpl.timezone,\n windowStart,\n windowEnd\n );\n out.push(...events);\n }\n out.sort((a, b) => a.startTimestamp - b.startTimestamp);\n return out;\n }\n\n // Earliest occurrence of one template strictly after `after`. Returns null\n // if no occurrence exists within NEXT_SEARCH_HORIZON_MS.\n async next(templateId: string, after: number): Promise<ScheduledEvent<TPayload> | null> {\n const tpl = await this._port.getTemplate(templateId);\n if (tpl == null) return null;\n\n // Expanding-window search. Doubles each iteration until horizon.\n const horizonEnd = after + NEXT_SEARCH_HORIZON_MS;\n let windowStart = after;\n let windowSize = DAY_MS;\n while (windowStart < horizonEnd) {\n const windowEnd = Math.min(windowStart + windowSize, horizonEnd);\n const alterations = await this._port.listAlterationsByTemplate(templateId);\n const keyed = this._generateKeyed(tpl, windowStart, windowEnd);\n const events = applyAlterations(\n tpl.id,\n tpl.payload,\n keyed,\n alterations,\n tpl.timezone,\n windowStart,\n windowEnd\n );\n const firstAfter = events.find((e) => e.startTimestamp > after);\n if (firstAfter != null) return firstAfter;\n windowStart = windowEnd;\n windowSize = Math.min(windowSize * 2, 365 * DAY_MS);\n }\n return null;\n }\n\n // The next `limit` occurrences globally after `after`, sorted ascending.\n // Defaults to 10. Searches in expanding windows like next().\n async nextDue(after: number, limit = 10): Promise<ScheduledEvent<TPayload>[]> {\n if (limit <= 0) return [];\n const horizonEnd = after + NEXT_SEARCH_HORIZON_MS;\n const out: ScheduledEvent<TPayload>[] = [];\n let windowStart = after;\n let windowSize = DAY_MS;\n while (windowStart < horizonEnd && out.length < limit) {\n const windowEnd = Math.min(windowStart + windowSize, horizonEnd);\n const events = await this.list(windowStart, windowEnd);\n for (const e of events) {\n if (e.startTimestamp > after) {\n out.push(e);\n if (out.length >= limit) break;\n }\n }\n windowStart = windowEnd;\n windowSize = Math.min(windowSize * 2, 365 * DAY_MS);\n }\n return out;\n }\n\n // ─── Writes ─────────────────────────────────────────────────\n\n async add(template: ScheduleTemplate<TPayload>): Promise<void> {\n validateRule(template.rule);\n _validateTimezone(template.timezone);\n _validateDisambiguation(template.disambiguation);\n const normalized: ScheduleTemplate<TPayload> = {\n ...template,\n rule: _normalizeRule(template.rule),\n };\n await this._port.addTemplate(normalized);\n this._changed.notify();\n }\n\n // Partial update. Caller supplies any subset of the template fields plus\n // the id. If the rule changes (kind or any field), all alterations for\n // this template are wiped — same policy as activity-board, made explicit.\n async update(\n delta: { id: string } & Partial<Omit<ScheduleTemplate<TPayload>, \"id\">>\n ): Promise<void> {\n const existing = await this._port.getTemplate(delta.id);\n if (existing == null) throw new Error(`No template with id ${delta.id}`);\n const merged: ScheduleTemplate<TPayload> = {\n ...existing,\n ...delta,\n id: delta.id,\n };\n validateRule(merged.rule);\n _validateTimezone(merged.timezone);\n _validateDisambiguation(merged.disambiguation);\n const updated: ScheduleTemplate<TPayload> = {\n ...merged,\n rule: _normalizeRule(merged.rule),\n };\n const ruleChanged = !_rulesEqual(existing.rule, updated.rule);\n await this._port.updateTemplate(updated);\n if (ruleChanged) {\n await this._port.removeAlterationsForTemplate(updated.id);\n }\n this._changed.notify();\n }\n\n async remove(id: string): Promise<void> {\n await this._port.removeAlterationsForTemplate(id);\n await this._port.removeTemplate(id);\n this._changed.notify();\n }\n\n // Adds or replaces a reschedule alteration for the given occurrence.\n // Caller is responsible for providing a valid occurrenceKey (typically\n // taken from a prior list() result). If `newTimeSpan` is omitted, the\n // rescheduled occurrence inherits the original's UTC duration (i.e. moves\n // in time but keeps the same length).\n async reschedule(\n templateId: string,\n occurrenceKey: string,\n newStart: number,\n newTimeSpan?: TimeSpan,\n payloadOverride?: Partial<TPayload>\n ): Promise<string> {\n if (newTimeSpan != null) validateTimeSpan(newTimeSpan);\n const id = `alt-${templateId}-${occurrenceKey}-${Date.now()}`;\n await this._removeExistingAlteration(templateId, occurrenceKey);\n const alt: Alteration<TPayload> = {\n id,\n templateId,\n occurrenceKey,\n kind: \"reschedule\",\n startTimestamp: newStart,\n timeSpan: newTimeSpan,\n payloadOverride,\n };\n await this._port.addAlteration(alt);\n this._changed.notify();\n return id;\n }\n\n async cancel(templateId: string, occurrenceKey: string): Promise<string> {\n const id = `alt-${templateId}-${occurrenceKey}-${Date.now()}`;\n await this._removeExistingAlteration(templateId, occurrenceKey);\n const alt: Alteration<TPayload> = {\n id,\n templateId,\n occurrenceKey,\n kind: \"cancel\",\n };\n await this._port.addAlteration(alt);\n this._changed.notify();\n return id;\n }\n\n // ─── Internals ──────────────────────────────────────────────\n\n private _generateKeyed(tpl: ScheduleTemplate<TPayload>, windowStart: number, windowEnd: number) {\n const tz = tpl.timezone;\n const dis = tpl.disambiguation ?? \"compatible\";\n let occurrences: Occurrence[];\n switch (tpl.rule.kind) {\n case \"instant\":\n // Absolute start — no wall-clock ambiguity, disambiguation N/A.\n occurrences = generateInstant(tpl.rule, tz, windowStart, windowEnd);\n break;\n case \"localDateTime\":\n occurrences = generateLocalDateTime(tpl.rule, tz, windowStart, windowEnd, dis);\n break;\n case \"anchored\":\n // Absolute stepping — no wall-clock ambiguity, disambiguation N/A.\n occurrences = generateAnchored(tpl.rule, tz, windowStart, windowEnd);\n break;\n case \"daysOfWeek\":\n occurrences = generateDaysOfWeek(tpl.rule, tz, windowStart, windowEnd, dis);\n break;\n case \"weeksOfMonth\":\n occurrences = generateWeeksOfMonth(tpl.rule, tz, windowStart, windowEnd, dis);\n break;\n case \"monthsOfYear\":\n occurrences = generateMonthsOfYear(tpl.rule, tz, windowStart, windowEnd, dis);\n break;\n case \"yearly\":\n occurrences = generateYearly(tpl.rule, tz, windowStart, windowEnd, dis);\n break;\n case \"daysOfMonth\":\n occurrences = generateDaysOfMonth(tpl.rule, tz, windowStart, windowEnd, dis);\n break;\n case \"daysOfYear\":\n occurrences = generateDaysOfYear(tpl.rule, tz, windowStart, windowEnd, dis);\n break;\n default: {\n // Exhaustiveness guard — adding a new kind without handling it here\n // is a compile error.\n const _exhaustive: never = tpl.rule;\n throw new Error(`Unknown rule kind: ${JSON.stringify(_exhaustive)}`);\n }\n }\n return assignOccurrenceKeys(occurrences, tz);\n }\n\n private async _removeExistingAlteration(\n templateId: string,\n occurrenceKey: string\n ): Promise<void> {\n const existing = await this._port.listAlterationsByTemplate(templateId);\n for (const alt of existing) {\n if (alt.occurrenceKey === occurrenceKey) {\n await this._port.removeAlteration(alt.id);\n }\n }\n }\n}\n\n// ─── Validation ───────────────────────────────────────────────\n\n// Returns a copy of `rule` with timeSpan defaulted when omitted, so\n// generators don't have to handle undefined repeatedly.\nfunction _normalizeRule(rule: RecurrenceRule): RecurrenceRule {\n if (rule.timeSpan != null) return rule;\n return { ...rule, timeSpan: { type: \"duration\", ms: 1 } };\n}\n\nfunction _validateTimezone(tz: string): void {\n if (typeof tz !== \"string\" || tz.length === 0) {\n throw new Error(\"ScheduleTemplate.timezone is required (use 'UTC' for zone-free scheduling)\");\n }\n if (!isValidTimeZone(tz)) {\n throw new Error(`Unknown timezone '${tz}' (expected an IANA zone name or 'UTC')`);\n }\n}\n\nconst _DISAMBIGUATIONS: readonly Disambiguation[] = [\n \"compatible\",\n \"earlier\",\n \"later\",\n \"reject\",\n \"skip\",\n];\n\nfunction _validateDisambiguation(d: Disambiguation | undefined): void {\n if (d == null) return;\n if (!_DISAMBIGUATIONS.includes(d)) {\n throw new Error(\n `Unknown disambiguation '${d}' (expected one of ${_DISAMBIGUATIONS.join(\", \")})`\n );\n }\n}\n\nfunction _rulesEqual(a: RecurrenceRule, b: RecurrenceRule): boolean {\n return JSON.stringify(_canonicalize(a)) === JSON.stringify(_canonicalize(b));\n}\n\nfunction _canonicalize(rule: RecurrenceRule): unknown {\n // Sort + dedup any array selectors so reorderings or duplicates don't\n // false-flag a rule change (which would wipe alterations).\n if (rule.kind === \"daysOfWeek\") {\n return { ...rule, days: [...new Set(rule.days)].sort((a, b) => a - b) };\n }\n if (rule.kind === \"weeksOfMonth\") {\n return {\n ...rule,\n days: [...new Set(rule.days)].sort((a, b) => a - b),\n weeks: [...new Set(rule.weeks)].sort((a, b) => a - b),\n };\n }\n if (rule.kind === \"monthsOfYear\") {\n return {\n ...rule,\n months: [...new Set(rule.months)].sort((a, b) => a - b),\n weeks: [...new Set(rule.weeks)].sort((a, b) => a - b),\n days: [...new Set(rule.days)].sort((a, b) => a - b),\n };\n }\n if (rule.kind === \"daysOfMonth\") {\n return {\n ...rule,\n months: [...new Set(rule.months)].sort((a, b) => a - b),\n daysOfMonth: [...new Set(rule.daysOfMonth)].sort((a, b) => a - b),\n };\n }\n if (rule.kind === \"daysOfYear\") {\n return {\n ...rule,\n daysOfYear: [...new Set(rule.daysOfYear)].sort((a, b) => a - b),\n };\n }\n return rule;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyBA,IAAM,SAAS;AAIf,IAAM,yBAAyB,OAAU;AAEzC,IAAa,YAAb,MAA0D;CACxD;CAIA,WAA4B,IAAI,MAAY;CAE5C,YAAY,MAA+B;EACzC,KAAK,QAAQ;CACf;CAEA,IAAI,UAAiC;EACnC,OAAO,KAAK,SAAS;CACvB;CAMA,MAAM,KAAK,aAAqB,WAAwD;EACtF,IAAI,aAAa,aAAa,OAAO,CAAC;EACtC,MAAM,CAAC,WAAW,eAAe,MAAM,QAAQ,IAAI,CACjD,KAAK,MAAM,cAAc,aAAa,SAAS,GAC/C,KAAK,MAAM,gBAAgB,aAAa,SAAS,CACnD,CAAC;EACD,MAAM,MAAkC,CAAC;EACzC,KAAK,MAAM,OAAO,WAAW;GAC3B,MAAM,QAAQ,KAAK,eAAe,KAAK,aAAa,SAAS;GAC7D,MAAM,SAAS,iBACb,IAAI,IACJ,IAAI,SACJ,OACA,aACA,IAAI,UACJ,aACA,SACF;GACA,IAAI,KAAK,GAAG,MAAM;EACpB;EACA,IAAI,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;EACtD,OAAO;CACT;CAIA,MAAM,KAAK,YAAoB,OAAyD;EACtF,MAAM,MAAM,MAAM,KAAK,MAAM,YAAY,UAAU;EACnD,IAAI,OAAO,MAAM,OAAO;EAGxB,MAAM,aAAa,QAAQ;EAC3B,IAAI,cAAc;EAClB,IAAI,aAAa;EACjB,OAAO,cAAc,YAAY;GAC/B,MAAM,YAAY,KAAK,IAAI,cAAc,YAAY,UAAU;GAC/D,MAAM,cAAc,MAAM,KAAK,MAAM,0BAA0B,UAAU;GACzE,MAAM,QAAQ,KAAK,eAAe,KAAK,aAAa,SAAS;GAU7D,MAAM,aATS,iBACb,IAAI,IACJ,IAAI,SACJ,OACA,aACA,IAAI,UACJ,aACA,SAEiB,CAAA,CAAO,MAAM,MAAM,EAAE,iBAAiB,KAAK;GAC9D,IAAI,cAAc,MAAM,OAAO;GAC/B,cAAc;GACd,aAAa,KAAK,IAAI,aAAa,GAAG,MAAM,MAAM;EACpD;EACA,OAAO;CACT;CAIA,MAAM,QAAQ,OAAe,QAAQ,IAAyC;EAC5E,IAAI,SAAS,GAAG,OAAO,CAAC;EACxB,MAAM,aAAa,QAAQ;EAC3B,MAAM,MAAkC,CAAC;EACzC,IAAI,cAAc;EAClB,IAAI,aAAa;EACjB,OAAO,cAAc,cAAc,IAAI,SAAS,OAAO;GACrD,MAAM,YAAY,KAAK,IAAI,cAAc,YAAY,UAAU;GAC/D,MAAM,SAAS,MAAM,KAAK,KAAK,aAAa,SAAS;GACrD,KAAK,MAAM,KAAK,QACd,IAAI,EAAE,iBAAiB,OAAO;IAC5B,IAAI,KAAK,CAAC;IACV,IAAI,IAAI,UAAU,OAAO;GAC3B;GAEF,cAAc;GACd,aAAa,KAAK,IAAI,aAAa,GAAG,MAAM,MAAM;EACpD;EACA,OAAO;CACT;CAIA,MAAM,IAAI,UAAqD;EAC7D,aAAa,SAAS,IAAI;EAC1B,kBAAkB,SAAS,QAAQ;EACnC,wBAAwB,SAAS,cAAc;EAC/C,MAAM,aAAyC;GAC7C,GAAG;GACH,MAAM,eAAe,SAAS,IAAI;EACpC;EACA,MAAM,KAAK,MAAM,YAAY,UAAU;EACvC,KAAK,SAAS,OAAO;CACvB;CAKA,MAAM,OACJ,OACe;EACf,MAAM,WAAW,MAAM,KAAK,MAAM,YAAY,MAAM,EAAE;EACtD,IAAI,YAAY,MAAM,MAAM,IAAI,MAAM,uBAAuB,MAAM,IAAI;EACvE,MAAM,SAAqC;GACzC,GAAG;GACH,GAAG;GACH,IAAI,MAAM;EACZ;EACA,aAAa,OAAO,IAAI;EACxB,kBAAkB,OAAO,QAAQ;EACjC,wBAAwB,OAAO,cAAc;EAC7C,MAAM,UAAsC;GAC1C,GAAG;GACH,MAAM,eAAe,OAAO,IAAI;EAClC;EACA,MAAM,cAAc,CAAC,YAAY,SAAS,MAAM,QAAQ,IAAI;EAC5D,MAAM,KAAK,MAAM,eAAe,OAAO;EACvC,IAAI,aACF,MAAM,KAAK,MAAM,6BAA6B,QAAQ,EAAE;EAE1D,KAAK,SAAS,OAAO;CACvB;CAEA,MAAM,OAAO,IAA2B;EACtC,MAAM,KAAK,MAAM,6BAA6B,EAAE;EAChD,MAAM,KAAK,MAAM,eAAe,EAAE;EAClC,KAAK,SAAS,OAAO;CACvB;CAOA,MAAM,WACJ,YACA,eACA,UACA,aACA,iBACiB;EACjB,IAAI,eAAe,MAAM,iBAAiB,WAAW;EACrD,MAAM,KAAK,OAAO,WAAW,GAAG,cAAc,GAAG,KAAK,IAAI;EAC1D,MAAM,KAAK,0BAA0B,YAAY,aAAa;EAC9D,MAAM,MAA4B;GAChC;GACA;GACA;GACA,MAAM;GACN,gBAAgB;GAChB,UAAU;GACV;EACF;EACA,MAAM,KAAK,MAAM,cAAc,GAAG;EAClC,KAAK,SAAS,OAAO;EACrB,OAAO;CACT;CAEA,MAAM,OAAO,YAAoB,eAAwC;EACvE,MAAM,KAAK,OAAO,WAAW,GAAG,cAAc,GAAG,KAAK,IAAI;EAC1D,MAAM,KAAK,0BAA0B,YAAY,aAAa;EAC9D,MAAM,MAA4B;GAChC;GACA;GACA;GACA,MAAM;EACR;EACA,MAAM,KAAK,MAAM,cAAc,GAAG;EAClC,KAAK,SAAS,OAAO;EACrB,OAAO;CACT;CAIA,eAAuB,KAAiC,aAAqB,WAAmB;EAC9F,MAAM,KAAK,IAAI;EACf,MAAM,MAAM,IAAI,kBAAkB;EAClC,IAAI;EACJ,QAAQ,IAAI,KAAK,MAAjB;GACE,KAAK;IAEH,cAAc,gBAAgB,IAAI,MAAM,IAAI,aAAa,SAAS;IAClE;GACF,KAAK;IACH,cAAc,sBAAsB,IAAI,MAAM,IAAI,aAAa,WAAW,GAAG;IAC7E;GACF,KAAK;IAEH,cAAc,iBAAiB,IAAI,MAAM,IAAI,aAAa,SAAS;IACnE;GACF,KAAK;IACH,cAAc,mBAAmB,IAAI,MAAM,IAAI,aAAa,WAAW,GAAG;IAC1E;GACF,KAAK;IACH,cAAc,qBAAqB,IAAI,MAAM,IAAI,aAAa,WAAW,GAAG;IAC5E;GACF,KAAK;IACH,cAAc,qBAAqB,IAAI,MAAM,IAAI,aAAa,WAAW,GAAG;IAC5E;GACF,KAAK;IACH,cAAc,eAAe,IAAI,MAAM,IAAI,aAAa,WAAW,GAAG;IACtE;GACF,KAAK;IACH,cAAc,oBAAoB,IAAI,MAAM,IAAI,aAAa,WAAW,GAAG;IAC3E;GACF,KAAK;IACH,cAAc,mBAAmB,IAAI,MAAM,IAAI,aAAa,WAAW,GAAG;IAC1E;GACF,SAAS;IAGP,MAAM,cAAqB,IAAI;IAC/B,MAAM,IAAI,MAAM,sBAAsB,KAAK,UAAU,WAAW,GAAG;GACrE;EACF;EACA,OAAO,qBAAqB,aAAa,EAAE;CAC7C;CAEA,MAAc,0BACZ,YACA,eACe;EACf,MAAM,WAAW,MAAM,KAAK,MAAM,0BAA0B,UAAU;EACtE,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,kBAAkB,eACxB,MAAM,KAAK,MAAM,iBAAiB,IAAI,EAAE;CAG9C;AACF;AAMA,SAAS,eAAe,MAAsC;CAC5D,IAAI,KAAK,YAAY,MAAM,OAAO;CAClC,OAAO;EAAE,GAAG;EAAM,UAAU;GAAE,MAAM;GAAY,IAAI;EAAE;CAAE;AAC1D;AAEA,SAAS,kBAAkB,IAAkB;CAC3C,IAAI,OAAO,OAAO,YAAY,GAAG,WAAW,GAC1C,MAAM,IAAI,MAAM,4EAA4E;CAE9F,IAAI,CAAC,gBAAgB,EAAE,GACrB,MAAM,IAAI,MAAM,qBAAqB,GAAG,wCAAwC;AAEpF;AAEA,IAAM,mBAA8C;CAClD;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,wBAAwB,GAAqC;CACpE,IAAI,KAAK,MAAM;CACf,IAAI,CAAC,iBAAiB,SAAS,CAAC,GAC9B,MAAM,IAAI,MACR,2BAA2B,EAAE,qBAAqB,iBAAiB,KAAK,IAAI,EAAE,EAChF;AAEJ;AAEA,SAAS,YAAY,GAAmB,GAA4B;CAClE,OAAO,KAAK,UAAU,cAAc,CAAC,CAAC,MAAM,KAAK,UAAU,cAAc,CAAC,CAAC;AAC7E;AAEA,SAAS,cAAc,MAA+B;CAGpD,IAAI,KAAK,SAAS,cAChB,OAAO;EAAE,GAAG;EAAM,MAAM,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAAE;CAExE,IAAI,KAAK,SAAS,gBAChB,OAAO;EACL,GAAG;EACH,MAAM,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;EAClD,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CACtD;CAEF,IAAI,KAAK,SAAS,gBAChB,OAAO;EACL,GAAG;EACH,QAAQ,CAAC,GAAG,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;EACtD,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;EACpD,MAAM,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CACpD;CAEF,IAAI,KAAK,SAAS,eAChB,OAAO;EACL,GAAG;EACH,QAAQ,CAAC,GAAG,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;EACtD,aAAa,CAAC,GAAG,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAClE;CAEF,IAAI,KAAK,SAAS,cAChB,OAAO;EACL,GAAG;EACH,YAAY,CAAC,GAAG,IAAI,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAChE;CAEF,OAAO;AACT"}