@craftguild/jscalendar 0.5.2 → 0.5.4

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 (124) hide show
  1. package/README.md +130 -94
  2. package/dist/index.cjs +3923 -0
  3. package/dist/index.d.cts +802 -0
  4. package/dist/index.d.mts +802 -0
  5. package/dist/index.mjs +3927 -0
  6. package/package.json +46 -31
  7. package/dist/__tests__/builders.test.d.ts +0 -1
  8. package/dist/__tests__/builders.test.js +0 -101
  9. package/dist/__tests__/calendar-extra.test.d.ts +0 -1
  10. package/dist/__tests__/calendar-extra.test.js +0 -221
  11. package/dist/__tests__/calendar.test.d.ts +0 -1
  12. package/dist/__tests__/calendar.test.js +0 -97
  13. package/dist/__tests__/ical-extra.test.d.ts +0 -1
  14. package/dist/__tests__/ical-extra.test.js +0 -87
  15. package/dist/__tests__/ical.test.d.ts +0 -1
  16. package/dist/__tests__/ical.test.js +0 -72
  17. package/dist/__tests__/index.test.d.ts +0 -1
  18. package/dist/__tests__/index.test.js +0 -9
  19. package/dist/__tests__/patch.test.d.ts +0 -1
  20. package/dist/__tests__/patch.test.js +0 -47
  21. package/dist/__tests__/recurrence.test.d.ts +0 -1
  22. package/dist/__tests__/recurrence.test.js +0 -640
  23. package/dist/__tests__/search.test.d.ts +0 -1
  24. package/dist/__tests__/search.test.js +0 -264
  25. package/dist/__tests__/timezones.test.d.ts +0 -1
  26. package/dist/__tests__/timezones.test.js +0 -12
  27. package/dist/__tests__/utils.test.d.ts +0 -1
  28. package/dist/__tests__/utils.test.js +0 -127
  29. package/dist/__tests__/validation.test.d.ts +0 -1
  30. package/dist/__tests__/validation.test.js +0 -224
  31. package/dist/ical.d.ts +0 -13
  32. package/dist/ical.js +0 -270
  33. package/dist/index.d.ts +0 -3
  34. package/dist/index.js +0 -2
  35. package/dist/jscal/base.d.ts +0 -89
  36. package/dist/jscal/base.js +0 -173
  37. package/dist/jscal/builders.d.ts +0 -183
  38. package/dist/jscal/builders.js +0 -287
  39. package/dist/jscal/constants.d.ts +0 -11
  40. package/dist/jscal/constants.js +0 -11
  41. package/dist/jscal/datetime.d.ts +0 -14
  42. package/dist/jscal/datetime.js +0 -42
  43. package/dist/jscal/defaults.d.ts +0 -31
  44. package/dist/jscal/defaults.js +0 -102
  45. package/dist/jscal/duration.d.ts +0 -43
  46. package/dist/jscal/duration.js +0 -75
  47. package/dist/jscal/event.d.ts +0 -23
  48. package/dist/jscal/event.js +0 -78
  49. package/dist/jscal/group.d.ts +0 -31
  50. package/dist/jscal/group.js +0 -69
  51. package/dist/jscal/guards.d.ts +0 -19
  52. package/dist/jscal/guards.js +0 -25
  53. package/dist/jscal/ids.d.ts +0 -11
  54. package/dist/jscal/ids.js +0 -77
  55. package/dist/jscal/normalize.d.ts +0 -32
  56. package/dist/jscal/normalize.js +0 -45
  57. package/dist/jscal/task.d.ts +0 -23
  58. package/dist/jscal/task.js +0 -67
  59. package/dist/jscal/types.d.ts +0 -38
  60. package/dist/jscal/types.js +0 -1
  61. package/dist/jscal.d.ts +0 -145
  62. package/dist/jscal.js +0 -126
  63. package/dist/patch.d.ts +0 -18
  64. package/dist/patch.js +0 -216
  65. package/dist/recurrence/constants.d.ts +0 -13
  66. package/dist/recurrence/constants.js +0 -13
  67. package/dist/recurrence/date-utils.d.ts +0 -125
  68. package/dist/recurrence/date-utils.js +0 -259
  69. package/dist/recurrence/expand.d.ts +0 -23
  70. package/dist/recurrence/expand.js +0 -315
  71. package/dist/recurrence/rule-candidates.d.ts +0 -21
  72. package/dist/recurrence/rule-candidates.js +0 -120
  73. package/dist/recurrence/rule-generate.d.ts +0 -11
  74. package/dist/recurrence/rule-generate.js +0 -36
  75. package/dist/recurrence/rule-matchers.d.ts +0 -34
  76. package/dist/recurrence/rule-matchers.js +0 -120
  77. package/dist/recurrence/rule-normalize.d.ts +0 -9
  78. package/dist/recurrence/rule-normalize.js +0 -57
  79. package/dist/recurrence/rule-selectors.d.ts +0 -7
  80. package/dist/recurrence/rule-selectors.js +0 -21
  81. package/dist/recurrence/rules.d.ts +0 -14
  82. package/dist/recurrence/rules.js +0 -57
  83. package/dist/recurrence/types.d.ts +0 -27
  84. package/dist/recurrence/types.js +0 -1
  85. package/dist/recurrence.d.ts +0 -2
  86. package/dist/recurrence.js +0 -1
  87. package/dist/search.d.ts +0 -44
  88. package/dist/search.js +0 -292
  89. package/dist/timezones/chunk_1.d.ts +0 -2
  90. package/dist/timezones/chunk_1.js +0 -72
  91. package/dist/timezones/chunk_2.d.ts +0 -2
  92. package/dist/timezones/chunk_2.js +0 -72
  93. package/dist/timezones/chunk_3.d.ts +0 -2
  94. package/dist/timezones/chunk_3.js +0 -72
  95. package/dist/timezones/chunk_4.d.ts +0 -2
  96. package/dist/timezones/chunk_4.js +0 -72
  97. package/dist/timezones/chunk_5.d.ts +0 -2
  98. package/dist/timezones/chunk_5.js +0 -72
  99. package/dist/timezones/chunk_6.d.ts +0 -2
  100. package/dist/timezones/chunk_6.js +0 -72
  101. package/dist/timezones/chunk_7.d.ts +0 -2
  102. package/dist/timezones/chunk_7.js +0 -6
  103. package/dist/timezones.d.ts +0 -9
  104. package/dist/timezones.js +0 -452
  105. package/dist/types.d.ts +0 -246
  106. package/dist/types.js +0 -1
  107. package/dist/utils.d.ts +0 -82
  108. package/dist/utils.js +0 -164
  109. package/dist/validate/asserts.d.ts +0 -155
  110. package/dist/validate/asserts.js +0 -381
  111. package/dist/validate/constants.d.ts +0 -25
  112. package/dist/validate/constants.js +0 -33
  113. package/dist/validate/error.d.ts +0 -19
  114. package/dist/validate/error.js +0 -25
  115. package/dist/validate/validators-common.d.ts +0 -64
  116. package/dist/validate/validators-common.js +0 -390
  117. package/dist/validate/validators-objects.d.ts +0 -8
  118. package/dist/validate/validators-objects.js +0 -70
  119. package/dist/validate/validators-recurrence.d.ts +0 -15
  120. package/dist/validate/validators-recurrence.js +0 -115
  121. package/dist/validate/validators.d.ts +0 -1
  122. package/dist/validate/validators.js +0 -1
  123. package/dist/validate.d.ts +0 -2
  124. package/dist/validate.js +0 -2
package/dist/index.cjs ADDED
@@ -0,0 +1,3923 @@
1
+ let date_fns = require("date-fns");
2
+ let date_fns_tz = require("date-fns-tz");
3
+
4
+ //#region src/utils.ts
5
+ const TYPEOF_FUNCTION$1 = "function";
6
+ const TYPEOF_STRING = "string";
7
+ const TYPEOF_NUMBER = "number";
8
+ const TYPEOF_BOOLEAN = "boolean";
9
+ const TYPEOF_OBJECT = "object";
10
+ /**
11
+ * Check whether a value is a string.
12
+ * @param value Value to check.
13
+ * @return True if the value is a string.
14
+ */
15
+ function isStringValue(value) {
16
+ return typeof value === TYPEOF_STRING;
17
+ }
18
+ /**
19
+ * Check whether a value is a number.
20
+ * @param value Value to check.
21
+ * @return True if the value is a number.
22
+ */
23
+ function isNumberValue(value) {
24
+ return typeof value === TYPEOF_NUMBER;
25
+ }
26
+ /**
27
+ * Check whether a value is a boolean.
28
+ * @param value Value to check.
29
+ * @return True if the value is a boolean.
30
+ */
31
+ function isBooleanValue(value) {
32
+ return typeof value === TYPEOF_BOOLEAN;
33
+ }
34
+ /**
35
+ * Check whether a value is a non-null object.
36
+ * @param value Value to check.
37
+ * @return True if the value is a non-null object.
38
+ */
39
+ function isObjectValue(value) {
40
+ return value !== null && typeof value === TYPEOF_OBJECT;
41
+ }
42
+ /**
43
+ * Get the current UTC date-time string.
44
+ * @return Current time as UTCDateTime.
45
+ */
46
+ function nowUtc() {
47
+ return normalizeUtcDateTime((/* @__PURE__ */ new Date()).toISOString());
48
+ }
49
+ /**
50
+ * Deep-clone a value using structuredClone.
51
+ * @param value Value to clone.
52
+ * @return Deep clone of the input value.
53
+ */
54
+ function deepClone(value) {
55
+ if (typeof structuredClone === TYPEOF_FUNCTION$1) return structuredClone(value);
56
+ throw new Error("structuredClone is not available in this environment");
57
+ }
58
+ /**
59
+ * Check if a string is a UTCDateTime with Z suffix.
60
+ * @param value Date-time string.
61
+ * @return True if the value ends with Z.
62
+ */
63
+ function isUtcDateTime(value) {
64
+ return /Z$/.test(value);
65
+ }
66
+ /**
67
+ * Compare two date-time strings, returning null when incomparable.
68
+ * @param a Date-time string A.
69
+ * @param b Date-time string B.
70
+ * @return -1, 0, 1, or null when formats are incompatible.
71
+ */
72
+ function compareDateTime(a, b) {
73
+ const aUtc = isUtcDateTime(a);
74
+ const bUtc = isUtcDateTime(b);
75
+ if (aUtc && bUtc) {
76
+ const aMs = Date.parse(a);
77
+ const bMs = Date.parse(b);
78
+ if (Number.isNaN(aMs) || Number.isNaN(bMs)) return null;
79
+ return aMs === bMs ? 0 : aMs < bMs ? -1 : 1;
80
+ }
81
+ if (!aUtc && !bUtc) {
82
+ if (a === b) return 0;
83
+ return a < b ? -1 : 1;
84
+ }
85
+ return null;
86
+ }
87
+ /**
88
+ * Convert a duration string to milliseconds.
89
+ * @param duration Duration string.
90
+ * @return Milliseconds or null when invalid.
91
+ */
92
+ function durationToMilliseconds(duration) {
93
+ const match = /^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(duration);
94
+ if (!match) return null;
95
+ const weeks = Number(match[1] || 0);
96
+ const days = Number(match[2] || 0);
97
+ const hours = Number(match[3] || 0);
98
+ const minutes = Number(match[4] || 0);
99
+ const seconds = Number(match[5] || 0);
100
+ return (weeks * 7 * 24 * 60 * 60 + days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds) * 1e3;
101
+ }
102
+ /**
103
+ * Normalize a UTCDateTime by trimming trailing fractional zeros.
104
+ * @param value UTCDateTime string.
105
+ * @return Normalized UTCDateTime string.
106
+ */
107
+ function normalizeUtcDateTime(value) {
108
+ var _match$, _match$2;
109
+ const match = value.match(/^(.*)\.(\d+)Z$/);
110
+ if (!match) return value;
111
+ const prefix = (_match$ = match[1]) !== null && _match$ !== void 0 ? _match$ : value;
112
+ const trimmed = ((_match$2 = match[2]) !== null && _match$2 !== void 0 ? _match$2 : "").replace(/0+$/, "");
113
+ if (trimmed.length === 0) return `${prefix}Z`;
114
+ return `${prefix}.${trimmed}Z`;
115
+ }
116
+ /**
117
+ * Format a Date as LocalDateTime string.
118
+ * @param value Date to format.
119
+ * @return LocalDateTime string.
120
+ */
121
+ function localDateTimeFromDate(value) {
122
+ return (0, date_fns.format)(value, "yyyy-MM-dd'T'HH:mm:ss");
123
+ }
124
+ /**
125
+ * Format a Date as a LocalDateTime in a target time zone.
126
+ * @param value Date to format.
127
+ * @param timeZone IANA time zone.
128
+ * @return LocalDateTime string in the time zone.
129
+ */
130
+ function dateTimeInTimeZone(value, timeZone) {
131
+ return (0, date_fns_tz.formatInTimeZone)(value, timeZone, "yyyy-MM-dd'T'HH:mm:ss");
132
+ }
133
+ /**
134
+ * Convert LocalDateTime string to a UTC Date using a time zone.
135
+ * @param value LocalDateTime string.
136
+ * @param timeZone IANA time zone.
137
+ * @return Date in UTC.
138
+ */
139
+ function localDateTimeToUtcDate(value, timeZone) {
140
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/.exec(value);
141
+ if (!match) throw new Error(`Invalid LocalDateTime: ${value}`);
142
+ const year = Number(match[1]);
143
+ const month = Number(match[2]);
144
+ const day = Number(match[3]);
145
+ const hour = Number(match[4]);
146
+ const minute = Number(match[5]);
147
+ const second = Number(match[6]);
148
+ return (0, date_fns_tz.fromZonedTime)(new Date(year, month - 1, day, hour, minute, second), timeZone);
149
+ }
150
+
151
+ //#endregion
152
+ //#region src/ical.ts
153
+ const TYPE_EVENT$4 = "Event";
154
+ const TYPE_GROUP$3 = "Group";
155
+ const TYPE_TASK$4 = "Task";
156
+ const DEFAULT_PRODID = "-//craftguild//EN";
157
+ /**
158
+ * Convert JSCalendar objects into an iCalendar string.
159
+ * @param objects JSCalendar objects to export.
160
+ * @param options Export options.
161
+ * @return iCalendar text.
162
+ */
163
+ function toICal(objects, options = {}) {
164
+ var _options$prodId, _options$method;
165
+ const lines = [];
166
+ const includeX = options.includeXJSCalendar !== false;
167
+ lines.push("BEGIN:VCALENDAR");
168
+ lines.push("VERSION:2.0");
169
+ lines.push(`PRODID:${(_options$prodId = options.prodId) !== null && _options$prodId !== void 0 ? _options$prodId : DEFAULT_PRODID}`);
170
+ const method = (_options$method = options.method) !== null && _options$method !== void 0 ? _options$method : findMethod(objects);
171
+ if (method) lines.push(`METHOD:${method.toUpperCase()}`);
172
+ for (const object of objects) if (object["@type"] === TYPE_GROUP$3) {
173
+ const group = object;
174
+ if (includeX) lines.push(`X-JSCALENDAR-GROUP:${escapeText(JSON.stringify(stripEntries(group)))}`);
175
+ for (const entry of group.entries) lines.push(...buildComponent(entry, includeX));
176
+ } else lines.push(...buildComponent(object, includeX));
177
+ lines.push("END:VCALENDAR");
178
+ return foldLines(lines).join("\r\n");
179
+ }
180
+ /**
181
+ * Find the first METHOD value from objects.
182
+ * @param objects JSCalendar objects.
183
+ * @return METHOD value or undefined.
184
+ */
185
+ function findMethod(objects) {
186
+ for (const object of objects) if (object.method) return object.method;
187
+ }
188
+ /**
189
+ * Build an iCalendar component from a JSCalendar object.
190
+ * @param object JSCalendar object.
191
+ * @param includeX Whether to include X-JSCALENDAR.
192
+ * @return iCalendar lines for the component.
193
+ */
194
+ function buildComponent(object, includeX) {
195
+ if (object["@type"] === TYPE_EVENT$4) return buildEvent(object, includeX);
196
+ if (object["@type"] === TYPE_TASK$4) return buildTask(object, includeX);
197
+ return [];
198
+ }
199
+ /**
200
+ * Build a VEVENT component.
201
+ * @param event Event object.
202
+ * @param includeX Whether to include X-JSCALENDAR.
203
+ * @return VEVENT lines.
204
+ */
205
+ function buildEvent(event, includeX) {
206
+ const lines = [];
207
+ lines.push("BEGIN:VEVENT");
208
+ lines.push(`UID:${escapeText(event.uid)}`);
209
+ lines.push(`DTSTAMP:${formatUtcDateTime(event.updated)}`);
210
+ if (event.sequence !== void 0) lines.push(`SEQUENCE:${event.sequence}`);
211
+ if (event.title) lines.push(`SUMMARY:${escapeText(event.title)}`);
212
+ if (event.description) lines.push(`DESCRIPTION:${escapeText(event.description)}`);
213
+ const dtStart = formatLocalDateTime$1(event.start);
214
+ if (event.timeZone) lines.push(`DTSTART;TZID=${event.timeZone}:${dtStart}`);
215
+ else lines.push(`DTSTART:${dtStart}`);
216
+ if (event.duration) lines.push(`DURATION:${event.duration}`);
217
+ if (event.status) lines.push(`STATUS:${event.status.toUpperCase()}`);
218
+ appendRecurrence(lines, event.recurrenceRules);
219
+ if (includeX) lines.push(`X-JSCALENDAR:${escapeText(JSON.stringify(event))}`);
220
+ lines.push("END:VEVENT");
221
+ return lines;
222
+ }
223
+ /**
224
+ * Build a VTODO component.
225
+ * @param task Task object.
226
+ * @param includeX Whether to include X-JSCALENDAR.
227
+ * @return VTODO lines.
228
+ */
229
+ function buildTask(task, includeX) {
230
+ const lines = [];
231
+ lines.push("BEGIN:VTODO");
232
+ lines.push(`UID:${escapeText(task.uid)}`);
233
+ lines.push(`DTSTAMP:${formatUtcDateTime(task.updated)}`);
234
+ if (task.sequence !== void 0) lines.push(`SEQUENCE:${task.sequence}`);
235
+ if (task.title) lines.push(`SUMMARY:${escapeText(task.title)}`);
236
+ if (task.description) lines.push(`DESCRIPTION:${escapeText(task.description)}`);
237
+ if (task.start) {
238
+ const dtStart = formatLocalDateTime$1(task.start);
239
+ if (task.timeZone) lines.push(`DTSTART;TZID=${task.timeZone}:${dtStart}`);
240
+ else lines.push(`DTSTART:${dtStart}`);
241
+ }
242
+ if (task.due) {
243
+ const due = formatLocalDateTime$1(task.due);
244
+ if (task.timeZone) lines.push(`DUE;TZID=${task.timeZone}:${due}`);
245
+ else lines.push(`DUE:${due}`);
246
+ }
247
+ if (task.percentComplete !== void 0) lines.push(`PERCENT-COMPLETE:${task.percentComplete}`);
248
+ if (task.progress) lines.push(`STATUS:${task.progress.toUpperCase()}`);
249
+ appendRecurrence(lines, task.recurrenceRules);
250
+ if (includeX) lines.push(`X-JSCALENDAR:${escapeText(JSON.stringify(task))}`);
251
+ lines.push("END:VTODO");
252
+ return lines;
253
+ }
254
+ /**
255
+ * Append RRULE lines for recurrence rules.
256
+ * @param lines Lines to append to.
257
+ * @param rules Recurrence rules.
258
+ * @return Nothing.
259
+ */
260
+ function appendRecurrence(lines, rules) {
261
+ if (!rules) return;
262
+ for (const rule of rules) {
263
+ const rrule = recurrenceRuleToRRule(rule);
264
+ if (rrule) lines.push(`RRULE:${rrule}`);
265
+ }
266
+ }
267
+ /**
268
+ * Convert a RecurrenceRule to an RRULE string.
269
+ * @param rule Recurrence rule.
270
+ * @return RRULE value or null.
271
+ */
272
+ function recurrenceRuleToRRule(rule) {
273
+ var _rule$byDay, _rule$byMonthDay, _rule$byMonth, _rule$byYearDay, _rule$byWeekNo, _rule$byHour, _rule$byMinute, _rule$bySecond, _rule$bySetPosition;
274
+ const parts = [];
275
+ parts.push(`FREQ=${rule.frequency.toUpperCase()}`);
276
+ if (rule.interval) parts.push(`INTERVAL=${rule.interval}`);
277
+ if (rule.count) parts.push(`COUNT=${rule.count}`);
278
+ if (rule.until) parts.push(`UNTIL=${formatLocalDateTime$1(rule.until)}`);
279
+ if ((_rule$byDay = rule.byDay) === null || _rule$byDay === void 0 ? void 0 : _rule$byDay.length) {
280
+ const days = rule.byDay.map((day) => {
281
+ var _day$nthOfPeriod;
282
+ return `${(_day$nthOfPeriod = day.nthOfPeriod) !== null && _day$nthOfPeriod !== void 0 ? _day$nthOfPeriod : ""}${day.day.toUpperCase()}`;
283
+ }).join(",");
284
+ parts.push(`BYDAY=${days}`);
285
+ }
286
+ if ((_rule$byMonthDay = rule.byMonthDay) === null || _rule$byMonthDay === void 0 ? void 0 : _rule$byMonthDay.length) parts.push(`BYMONTHDAY=${rule.byMonthDay.join(",")}`);
287
+ if ((_rule$byMonth = rule.byMonth) === null || _rule$byMonth === void 0 ? void 0 : _rule$byMonth.length) parts.push(`BYMONTH=${rule.byMonth.join(",")}`);
288
+ if ((_rule$byYearDay = rule.byYearDay) === null || _rule$byYearDay === void 0 ? void 0 : _rule$byYearDay.length) parts.push(`BYYEARDAY=${rule.byYearDay.join(",")}`);
289
+ if ((_rule$byWeekNo = rule.byWeekNo) === null || _rule$byWeekNo === void 0 ? void 0 : _rule$byWeekNo.length) parts.push(`BYWEEKNO=${rule.byWeekNo.join(",")}`);
290
+ if ((_rule$byHour = rule.byHour) === null || _rule$byHour === void 0 ? void 0 : _rule$byHour.length) parts.push(`BYHOUR=${rule.byHour.join(",")}`);
291
+ if ((_rule$byMinute = rule.byMinute) === null || _rule$byMinute === void 0 ? void 0 : _rule$byMinute.length) parts.push(`BYMINUTE=${rule.byMinute.join(",")}`);
292
+ if ((_rule$bySecond = rule.bySecond) === null || _rule$bySecond === void 0 ? void 0 : _rule$bySecond.length) parts.push(`BYSECOND=${rule.bySecond.join(",")}`);
293
+ if ((_rule$bySetPosition = rule.bySetPosition) === null || _rule$bySetPosition === void 0 ? void 0 : _rule$bySetPosition.length) parts.push(`BYSETPOS=${rule.bySetPosition.join(",")}`);
294
+ if (rule.firstDayOfWeek) parts.push(`WKST=${rule.firstDayOfWeek.toUpperCase()}`);
295
+ if (rule.rscale) parts.push(`RSCALE=${rule.rscale.toUpperCase()}`);
296
+ if (rule.skip) parts.push(`SKIP=${rule.skip.toUpperCase()}`);
297
+ return parts.join(";");
298
+ }
299
+ /**
300
+ * Format a UTCDateTime for iCalendar.
301
+ * @param value UTCDateTime string.
302
+ * @return iCalendar-formatted UTCDateTime.
303
+ */
304
+ function formatUtcDateTime(value) {
305
+ return normalizeUtcDateTime(value).replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").replace(/Z$/, "Z");
306
+ }
307
+ /**
308
+ * Format a LocalDateTime for iCalendar.
309
+ * @param value LocalDateTime string.
310
+ * @return iCalendar-formatted LocalDateTime.
311
+ */
312
+ function formatLocalDateTime$1(value) {
313
+ return value.replace(/[-:]/g, "").replace(/\.\d+$/, "").replace(/Z$/, "");
314
+ }
315
+ /**
316
+ * Escape text for iCalendar content lines.
317
+ * @param value Raw text.
318
+ * @return Escaped text.
319
+ */
320
+ function escapeText(value) {
321
+ return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/;/g, "\\;").replace(/,/g, "\\,");
322
+ }
323
+ /**
324
+ * Fold iCalendar lines to 75 octets per RFC.
325
+ * @param lines Lines to fold.
326
+ * @return Folded lines.
327
+ */
328
+ function foldLines(lines) {
329
+ const result = [];
330
+ for (const line of lines) {
331
+ if (line.length <= 75) {
332
+ result.push(line);
333
+ continue;
334
+ }
335
+ let remaining = line;
336
+ result.push(remaining.slice(0, 75));
337
+ remaining = remaining.slice(75);
338
+ while (remaining.length > 0) {
339
+ result.push(` ${remaining.slice(0, 74)}`);
340
+ remaining = remaining.slice(74);
341
+ }
342
+ }
343
+ return result;
344
+ }
345
+ /**
346
+ * Strip group entries for X-JSCALENDAR-GROUP payload.
347
+ * @param group Group object.
348
+ * @return Group without entries.
349
+ */
350
+ function stripEntries(group) {
351
+ const { entries: _entries, ...rest } = group;
352
+ return rest;
353
+ }
354
+
355
+ //#endregion
356
+ //#region src/patch.ts
357
+ const POINTER_SEPARATOR = "/";
358
+ const TYPE_OBJECT = "object";
359
+ const PATCH_ERROR_NAME = "PatchError";
360
+ /**
361
+ * Error thrown when a patch operation is invalid.
362
+ */
363
+ var PatchError = class extends Error {
364
+ /**
365
+ * Create a new PatchError.
366
+ * @param message Error message.
367
+ */
368
+ constructor(message) {
369
+ super(message);
370
+ this.name = PATCH_ERROR_NAME;
371
+ }
372
+ };
373
+ /**
374
+ * Unescape a JSON Pointer segment.
375
+ * @param segment Escaped pointer segment.
376
+ * @return Unescaped pointer segment.
377
+ */
378
+ function unescapePointer(segment) {
379
+ return segment.replace(/~1/g, "/").replace(/~0/g, "~");
380
+ }
381
+ /**
382
+ * Normalize a JSON Pointer string to ensure it is absolute.
383
+ * @param pointer Pointer to normalize.
384
+ * @return Normalized pointer.
385
+ */
386
+ function normalizePointer(pointer) {
387
+ return pointer.startsWith(POINTER_SEPARATOR) ? pointer : `${POINTER_SEPARATOR}${pointer}`;
388
+ }
389
+ /**
390
+ * Split a JSON Pointer into unescaped segments.
391
+ * @param pointer Pointer to split.
392
+ * @return Unescaped pointer segments.
393
+ */
394
+ function splitPointer(pointer) {
395
+ const normalized = normalizePointer(pointer);
396
+ if (normalized === POINTER_SEPARATOR) return [];
397
+ return normalized.split(POINTER_SEPARATOR).slice(1).map(unescapePointer);
398
+ }
399
+ /**
400
+ * Validate that no pointer is a prefix of another pointer.
401
+ * @param pointers Patch pointers to validate.
402
+ * @return Nothing.
403
+ */
404
+ function validatePrefixConflicts(pointers) {
405
+ const normalized = pointers.map(normalizePointer).sort();
406
+ for (let i = 0; i < normalized.length; i += 1) {
407
+ const current = normalized[i];
408
+ for (let j = i + 1; j < normalized.length; j += 1) {
409
+ const other = normalized[j];
410
+ const prefix = `${current}${POINTER_SEPARATOR}`;
411
+ if (other && other.startsWith(prefix)) throw new PatchError(`Patch pointer conflict: ${current} is prefix of ${other}`);
412
+ }
413
+ }
414
+ }
415
+ /**
416
+ * Ensure the value is not an array when traversing pointers.
417
+ * @param value Value to check.
418
+ * @param pointer Pointer for error context.
419
+ * @return Nothing.
420
+ */
421
+ function assertNotArray(value, pointer) {
422
+ if (Array.isArray(value)) throw new PatchError(`Patch pointer references into array: ${pointer}`);
423
+ }
424
+ /**
425
+ * Ensure a value is an object record for traversal.
426
+ * @param value Value to check.
427
+ * @param pointer Pointer for error context.
428
+ * @return Nothing.
429
+ */
430
+ function assertObject(value, pointer) {
431
+ if (value === null || typeof value !== TYPE_OBJECT || Array.isArray(value)) throw new PatchError(`Patch pointer references missing or non-object path: ${pointer}`);
432
+ }
433
+ /**
434
+ * Ensure a value is a record at the current traversal position.
435
+ * @param value Value to check.
436
+ * @param pointer Pointer for error context.
437
+ * @return Nothing.
438
+ */
439
+ function assertRecord(value, pointer) {
440
+ if (value === null || Array.isArray(value)) throw new PatchError(`Patch pointer references missing or non-object path: ${pointer}`);
441
+ }
442
+ /**
443
+ * Apply a PatchObject to a target record.
444
+ * @param target Target record to mutate.
445
+ * @param patch Patch entries to apply.
446
+ * @return Nothing.
447
+ */
448
+ function applyPatchEntries(target, patch) {
449
+ for (const [rawPointer, value] of Object.entries(patch)) {
450
+ const pointer = normalizePointer(rawPointer);
451
+ applyPointerSegments(target, pointer, splitPointer(pointer), value);
452
+ }
453
+ }
454
+ /**
455
+ * Apply patch segments to the target by walking each segment.
456
+ * @param target Target record to mutate.
457
+ * @param pointer Normalized pointer.
458
+ * @param segments Pointer segments.
459
+ * @param value Patch value for the pointer.
460
+ * @return Nothing.
461
+ */
462
+ function applyPointerSegments(target, pointer, segments, value) {
463
+ let current = target;
464
+ for (let i = 0; i < segments.length; i += 1) {
465
+ const segment = ensureSegment(segments[i], pointer);
466
+ const isLast = isLastSegment(i, segments.length);
467
+ current = applySegment(current, segment, isLast, value, pointer);
468
+ }
469
+ }
470
+ /**
471
+ * Ensure the current segment is present.
472
+ * @param segment Segment to validate.
473
+ * @param pointer Pointer for error context.
474
+ * @return Validated segment.
475
+ */
476
+ function ensureSegment(segment, pointer) {
477
+ if (segment === void 0) throw new PatchError(`Patch pointer missing segment: ${pointer}`);
478
+ return segment;
479
+ }
480
+ /**
481
+ * Determine if the index is the last segment.
482
+ * @param index Current index.
483
+ * @param length Total number of segments.
484
+ * @return True if this is the last segment.
485
+ */
486
+ function isLastSegment(index, length) {
487
+ return index === length - 1;
488
+ }
489
+ /**
490
+ * Apply a single patch segment and return the next traversal record.
491
+ * @param current Current record.
492
+ * @param segment Current segment.
493
+ * @param isLast Whether this is the last segment.
494
+ * @param value Patch value.
495
+ * @param pointer Pointer for error context.
496
+ * @return Next record to traverse.
497
+ */
498
+ function applySegment(current, segment, isLast, value, pointer) {
499
+ assertNotArray(current, pointer);
500
+ assertRecord(current, pointer);
501
+ if (isLast) {
502
+ applyValueAtSegment(current, segment, value);
503
+ return current;
504
+ }
505
+ return getNextRecord(current, segment, pointer);
506
+ }
507
+ /**
508
+ * Apply a value update or delete at the given segment.
509
+ * @param current Current record.
510
+ * @param segment Segment to update.
511
+ * @param value Patch value.
512
+ * @return Nothing.
513
+ */
514
+ function applyValueAtSegment(current, segment, value) {
515
+ if (value === null) {
516
+ if (Object.prototype.hasOwnProperty.call(current, segment)) delete current[segment];
517
+ return;
518
+ }
519
+ current[segment] = value;
520
+ }
521
+ /**
522
+ * Resolve the next record in the traversal, validating it exists and is an object.
523
+ * @param current Current record.
524
+ * @param segment Segment to traverse.
525
+ * @param pointer Pointer for error context.
526
+ * @return Next record to traverse.
527
+ */
528
+ function getNextRecord(current, segment, pointer) {
529
+ if (!Object.prototype.hasOwnProperty.call(current, segment)) throw new PatchError(`Patch pointer missing path: ${pointer}`);
530
+ const next = current[segment];
531
+ if (next === void 0) throw new PatchError(`Patch pointer missing path: ${pointer}`);
532
+ assertObject(next, pointer);
533
+ return next;
534
+ }
535
+ /**
536
+ * Apply a JSCalendar PatchObject to an input object.
537
+ * @param input Input object.
538
+ * @param patch Patch object to apply.
539
+ * @return Patched clone of the input object.
540
+ */
541
+ function applyPatch(input, patch) {
542
+ validatePrefixConflicts(Object.keys(patch));
543
+ const target = deepClone(input);
544
+ assertRecord(target, POINTER_SEPARATOR);
545
+ applyPatchEntries(target, patch);
546
+ return target;
547
+ }
548
+
549
+ //#endregion
550
+ //#region src/recurrence/constants.ts
551
+ const TYPE_EVENT$3 = "Event";
552
+ const TYPE_TASK$3 = "Task";
553
+ const RSCALE_GREGORIAN$1 = "gregorian";
554
+ const SKIP_OMIT = "omit";
555
+ const SKIP_FORWARD = "forward";
556
+ const SKIP_BACKWARD = "backward";
557
+ const FREQ_YEARLY = "yearly";
558
+ const FREQ_MONTHLY = "monthly";
559
+ const FREQ_WEEKLY = "weekly";
560
+ const FREQ_DAILY = "daily";
561
+ const FREQ_HOURLY = "hourly";
562
+ const FREQ_MINUTELY = "minutely";
563
+ const FREQ_SECONDLY = "secondly";
564
+
565
+ //#endregion
566
+ //#region src/recurrence/date-utils.ts
567
+ /**
568
+ * Advance a date-time by a recurrence interval.
569
+ * @param start Anchor date-time.
570
+ * @param frequency Recurrence frequency.
571
+ * @param amount Number of intervals to add.
572
+ * @param firstDay First day of week for weekly intervals.
573
+ * @return Date-time moved to the next interval boundary.
574
+ */
575
+ function addInterval(start, frequency, amount, firstDay) {
576
+ if (frequency === FREQ_YEARLY) return {
577
+ year: start.year + amount,
578
+ month: 1,
579
+ day: 1,
580
+ hour: 0,
581
+ minute: 0,
582
+ second: 0
583
+ };
584
+ if (frequency === FREQ_MONTHLY) {
585
+ const next = addMonths(start, amount);
586
+ return {
587
+ year: next.year,
588
+ month: next.month,
589
+ day: 1,
590
+ hour: 0,
591
+ minute: 0,
592
+ second: 0
593
+ };
594
+ }
595
+ if (frequency === FREQ_WEEKLY) return addDays(startOfWeek(start, firstDay), amount * 7);
596
+ if (frequency === FREQ_DAILY) return addDays(start, amount);
597
+ if (frequency === FREQ_HOURLY) return addHours(start, amount);
598
+ if (frequency === FREQ_MINUTELY) return addMinutes(start, amount);
599
+ return addSeconds(start, amount);
600
+ }
601
+ /**
602
+ * Parse local date time into structured data.
603
+ * @param value LocalDateTime string.
604
+ * @return Parsed date-time parts.
605
+ */
606
+ function parseLocalDateTime(value) {
607
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/.exec(value);
608
+ if (!match) throw new Error(`Invalid LocalDateTime: ${value}`);
609
+ return {
610
+ year: Number(match[1]),
611
+ month: Number(match[2]),
612
+ day: Number(match[3]),
613
+ hour: Number(match[4]),
614
+ minute: Number(match[5]),
615
+ second: Number(match[6])
616
+ };
617
+ }
618
+ /**
619
+ * Format local date time as string.
620
+ * @param dt Date-time parts.
621
+ * @return LocalDateTime string.
622
+ */
623
+ function formatLocalDateTime(dt) {
624
+ return `${pad(dt.year, 4)}-${pad(dt.month, 2)}-${pad(dt.day, 2)}T${pad(dt.hour, 2)}:${pad(dt.minute, 2)}:${pad(dt.second, 2)}`;
625
+ }
626
+ /**
627
+ * Pad a numeric value with leading zeros.
628
+ * @param value Number to pad.
629
+ * @param length Total length.
630
+ * @return Zero-padded string.
631
+ */
632
+ function pad(value, length) {
633
+ return value.toString().padStart(length, "0");
634
+ }
635
+ /**
636
+ * Add days to the target.
637
+ * @param dt Date-time parts.
638
+ * @param days Days to add.
639
+ * @return New date-time with days added.
640
+ */
641
+ function addDays(dt, days) {
642
+ const ms = Date.UTC(dt.year, dt.month - 1, dt.day, dt.hour, dt.minute, dt.second) + days * 86400 * 1e3;
643
+ const d = new Date(ms);
644
+ return {
645
+ year: d.getUTCFullYear(),
646
+ month: d.getUTCMonth() + 1,
647
+ day: d.getUTCDate(),
648
+ hour: dt.hour,
649
+ minute: dt.minute,
650
+ second: dt.second
651
+ };
652
+ }
653
+ /**
654
+ * Add hours to the target.
655
+ * @param dt Date-time parts.
656
+ * @param hours Hours to add.
657
+ * @return New date-time with hours added.
658
+ */
659
+ function addHours(dt, hours) {
660
+ const ms = Date.UTC(dt.year, dt.month - 1, dt.day, dt.hour, dt.minute, dt.second) + hours * 3600 * 1e3;
661
+ const d = new Date(ms);
662
+ return {
663
+ year: d.getUTCFullYear(),
664
+ month: d.getUTCMonth() + 1,
665
+ day: d.getUTCDate(),
666
+ hour: d.getUTCHours(),
667
+ minute: d.getUTCMinutes(),
668
+ second: d.getUTCSeconds()
669
+ };
670
+ }
671
+ /**
672
+ * Add minutes to the target.
673
+ * @param dt Date-time parts.
674
+ * @param minutes Minutes to add.
675
+ * @return New date-time with minutes added.
676
+ */
677
+ function addMinutes(dt, minutes) {
678
+ return addSeconds(dt, minutes * 60);
679
+ }
680
+ /**
681
+ * Add seconds to the target.
682
+ * @param dt Date-time parts.
683
+ * @param seconds Seconds to add.
684
+ * @return New date-time with seconds added.
685
+ */
686
+ function addSeconds(dt, seconds) {
687
+ const ms = Date.UTC(dt.year, dt.month - 1, dt.day, dt.hour, dt.minute, dt.second) + seconds * 1e3;
688
+ const d = new Date(ms);
689
+ return {
690
+ year: d.getUTCFullYear(),
691
+ month: d.getUTCMonth() + 1,
692
+ day: d.getUTCDate(),
693
+ hour: d.getUTCHours(),
694
+ minute: d.getUTCMinutes(),
695
+ second: d.getUTCSeconds()
696
+ };
697
+ }
698
+ /**
699
+ * Add months to the target.
700
+ * @param dt Date-time parts.
701
+ * @param months Months to add.
702
+ * @return New date-time with months added.
703
+ */
704
+ function addMonths(dt, months) {
705
+ const total = dt.year * 12 + (dt.month - 1) + months;
706
+ const year = Math.floor(total / 12);
707
+ const month = total % 12 + 1;
708
+ return {
709
+ year,
710
+ month,
711
+ day: Math.min(dt.day, daysInMonth(year, month)),
712
+ hour: dt.hour,
713
+ minute: dt.minute,
714
+ second: dt.second
715
+ };
716
+ }
717
+ /**
718
+ * Get day of week for a date-time.
719
+ * @param dt Date-time parts.
720
+ * @return Two-letter weekday code.
721
+ */
722
+ function dayOfWeek(dt) {
723
+ const idx = new Date(Date.UTC(dt.year, dt.month - 1, dt.day)).getUTCDay();
724
+ if (idx === 0) return "su";
725
+ if (idx === 1) return "mo";
726
+ if (idx === 2) return "tu";
727
+ if (idx === 3) return "we";
728
+ if (idx === 4) return "th";
729
+ if (idx === 5) return "fr";
730
+ return "sa";
731
+ }
732
+ /**
733
+ * Get day of year for a date-time.
734
+ * @param dt Date-time parts.
735
+ * @return Day of year (1-366).
736
+ */
737
+ function dayOfYear(dt) {
738
+ const start = Date.UTC(dt.year, 0, 1);
739
+ const current = Date.UTC(dt.year, dt.month - 1, dt.day);
740
+ return Math.floor((current - start) / (24 * 3600 * 1e3)) + 1;
741
+ }
742
+ /**
743
+ * Get number of days in a month.
744
+ * @param year Year number.
745
+ * @param month Month number (1-12).
746
+ * @return Days in the month.
747
+ */
748
+ function daysInMonth(year, month) {
749
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
750
+ }
751
+ /**
752
+ * Get number of days in a year.
753
+ * @param year Year number.
754
+ * @return Days in the year.
755
+ */
756
+ function daysInYear(year) {
757
+ return new Date(Date.UTC(year + 1, 0, 0)).getUTCDate();
758
+ }
759
+ /**
760
+ * Get the start of week for a date-time.
761
+ * @param dt Date-time parts.
762
+ * @param firstDay Weekday that starts the week.
763
+ * @return Date-time at the start of the week.
764
+ */
765
+ function startOfWeek(dt, firstDay) {
766
+ const order = [
767
+ "mo",
768
+ "tu",
769
+ "we",
770
+ "th",
771
+ "fr",
772
+ "sa",
773
+ "su"
774
+ ];
775
+ const dow = dayOfWeek(dt);
776
+ return addDays(dt, -((order.indexOf(dow) - order.indexOf(firstDay) + 7) % 7));
777
+ }
778
+ /**
779
+ * Get ISO-like week number for a date-time.
780
+ * @param dt Date-time parts.
781
+ * @param firstDay Weekday that starts the week.
782
+ * @return Week number for the given date.
783
+ */
784
+ function weekNumber(dt, firstDay) {
785
+ const yearStart = {
786
+ year: dt.year,
787
+ month: 1,
788
+ day: 1,
789
+ hour: 0,
790
+ minute: 0,
791
+ second: 0
792
+ };
793
+ const weekStart = startOfWeek(yearStart, firstDay);
794
+ const week1Start = 7 - daysBetween(weekStart, yearStart) >= 4 ? weekStart : addDays(weekStart, 7);
795
+ if (compareDate(dt, week1Start) < 0) return totalWeeksInYear(dt.year - 1, firstDay);
796
+ const diff = daysBetween(week1Start, dt);
797
+ return Math.floor(diff / 7) + 1;
798
+ }
799
+ /**
800
+ * Get total weeks in a year.
801
+ * @param year Year number.
802
+ * @param firstDay Weekday that starts the week.
803
+ * @return Total number of weeks in the year.
804
+ */
805
+ function totalWeeksInYear(year, firstDay) {
806
+ return weekNumber({
807
+ year,
808
+ month: 12,
809
+ day: 31,
810
+ hour: 0,
811
+ minute: 0,
812
+ second: 0
813
+ }, firstDay);
814
+ }
815
+ /**
816
+ * Get whole-day difference between two dates.
817
+ * @param a Start date.
818
+ * @param b End date.
819
+ * @return Whole-day difference (b - a).
820
+ */
821
+ function daysBetween(a, b) {
822
+ const msA = Date.UTC(a.year, a.month - 1, a.day);
823
+ const msB = Date.UTC(b.year, b.month - 1, b.day);
824
+ return Math.floor((msB - msA) / (24 * 3600 * 1e3));
825
+ }
826
+ /**
827
+ * Compare two dates without time.
828
+ * @param a First date.
829
+ * @param b Second date.
830
+ * @return Negative/zero/positive comparison result.
831
+ */
832
+ function compareDate(a, b) {
833
+ if (a.year !== b.year) return a.year - b.year;
834
+ if (a.month !== b.month) return a.month - b.month;
835
+ if (a.day !== b.day) return a.day - b.day;
836
+ return 0;
837
+ }
838
+
839
+ //#endregion
840
+ //#region src/recurrence/rule-matchers.ts
841
+ /**
842
+ * Check whether a candidate date matches BYMONTHDAY values.
843
+ * @param date Candidate date.
844
+ * @param byMonthDay BYMONTHDAY values (positive or negative).
845
+ * @return True when the candidate date matches any BYMONTHDAY value.
846
+ */
847
+ function matchesByMonthDay(date, byMonthDay) {
848
+ const dim = daysInMonth(date.year, date.month);
849
+ for (const v of byMonthDay) {
850
+ if (v > 0 && date.day === v) return true;
851
+ if (v < 0 && date.day === dim + v + 1) return true;
852
+ }
853
+ return false;
854
+ }
855
+ /**
856
+ * Check whether a candidate date matches BYYEARDAY values.
857
+ * @param date Candidate date.
858
+ * @param byYearDay BYYEARDAY values (positive or negative).
859
+ * @return True when the candidate date matches any BYYEARDAY value.
860
+ */
861
+ function matchesByYearDay(date, byYearDay) {
862
+ const diy = daysInYear(date.year);
863
+ const doy = dayOfYear({
864
+ year: date.year,
865
+ month: date.month,
866
+ day: date.day,
867
+ hour: 0,
868
+ minute: 0,
869
+ second: 0
870
+ });
871
+ for (const v of byYearDay) {
872
+ if (v > 0 && doy === v) return true;
873
+ if (v < 0 && doy === diy + v + 1) return true;
874
+ }
875
+ return false;
876
+ }
877
+ /**
878
+ * Check whether a candidate date matches BYWEEKNO values.
879
+ * @param date Candidate date.
880
+ * @param byWeekNo BYWEEKNO values (positive or negative).
881
+ * @param firstDay First day of the week for week number calculations.
882
+ * @return True when the candidate date matches any BYWEEKNO value.
883
+ */
884
+ function matchesByWeekNo(date, byWeekNo, firstDay) {
885
+ const week = weekNumber({
886
+ year: date.year,
887
+ month: date.month,
888
+ day: date.day,
889
+ hour: 0,
890
+ minute: 0,
891
+ second: 0
892
+ }, firstDay);
893
+ const total = totalWeeksInYear(date.year, firstDay);
894
+ for (const v of byWeekNo) {
895
+ if (v > 0 && week === v) return true;
896
+ if (v < 0 && week === total + v + 1) return true;
897
+ }
898
+ return false;
899
+ }
900
+ /**
901
+ * Check whether a candidate date matches BYDAY rules.
902
+ * @param date Candidate date.
903
+ * @param byDay BYDAY rules (with optional nth-of-period entries).
904
+ * @param frequency Rule frequency.
905
+ * @param periodStart Start of the current recurrence period.
906
+ * @param firstDay First day of the week for weekly calculations.
907
+ * @return True when the candidate date matches any BYDAY rule.
908
+ */
909
+ function matchesByDay(date, byDay, frequency, periodStart, firstDay) {
910
+ const weekday = dayOfWeek({
911
+ year: date.year,
912
+ month: date.month,
913
+ day: date.day,
914
+ hour: 0,
915
+ minute: 0,
916
+ second: 0
917
+ });
918
+ for (const entry of byDay) {
919
+ if (entry.nthOfPeriod === void 0) {
920
+ if (entry.day === weekday) return true;
921
+ continue;
922
+ }
923
+ if (frequency !== FREQ_MONTHLY && frequency !== FREQ_YEARLY) continue;
924
+ const matches = listNthPeriodDates(date, frequency, periodStart, firstDay).filter((d) => dayOfWeek(d) === entry.day);
925
+ const index = entry.nthOfPeriod > 0 ? entry.nthOfPeriod - 1 : matches.length + entry.nthOfPeriod;
926
+ if (index >= 0 && index < matches.length) {
927
+ const target = matches[index];
928
+ if (target && target.year === date.year && target.month === date.month && target.day === date.day) return true;
929
+ }
930
+ }
931
+ return false;
932
+ }
933
+ /**
934
+ * List dates in the current period for nth-of-period matching.
935
+ * @param date Candidate date (year/month used for period selection).
936
+ * @param frequency Rule frequency.
937
+ * @param periodStart Start of the current recurrence period.
938
+ * @param firstDay First day of the week for weekly calculations.
939
+ * @return Date list used to resolve nth-of-period BYDAY entries.
940
+ */
941
+ function listNthPeriodDates(date, frequency, periodStart, firstDay) {
942
+ if (frequency === FREQ_YEARLY) {
943
+ const result = [];
944
+ for (let month = 1; month <= 12; month += 1) {
945
+ const days = daysInMonth(date.year, month);
946
+ for (let day = 1; day <= days; day += 1) result.push({
947
+ year: date.year,
948
+ month,
949
+ day,
950
+ hour: 0,
951
+ minute: 0,
952
+ second: 0
953
+ });
954
+ }
955
+ return result;
956
+ }
957
+ if (frequency === FREQ_MONTHLY) {
958
+ const result = [];
959
+ const days = daysInMonth(date.year, date.month);
960
+ for (let day = 1; day <= days; day += 1) result.push({
961
+ year: date.year,
962
+ month: date.month,
963
+ day,
964
+ hour: 0,
965
+ minute: 0,
966
+ second: 0
967
+ });
968
+ return result;
969
+ }
970
+ const result = [];
971
+ let cursor = periodStart;
972
+ for (let i = 0; i < 7; i += 1) {
973
+ result.push({
974
+ year: cursor.year,
975
+ month: cursor.month,
976
+ day: cursor.day,
977
+ hour: 0,
978
+ minute: 0,
979
+ second: 0
980
+ });
981
+ cursor = addDays(cursor, 1);
982
+ }
983
+ return result;
984
+ }
985
+
986
+ //#endregion
987
+ //#region src/recurrence/rule-candidates.ts
988
+ /**
989
+ * Build candidate dates for the current recurrence period.
990
+ * @param periodStart Start of the current recurrence period.
991
+ * @param rule Normalized recurrence rule.
992
+ * @param firstDay First day of the week for weekly calculations.
993
+ * @param skip Skip policy for invalid month days.
994
+ * @return Candidate dates for further filtering.
995
+ */
996
+ function generateDateCandidates(periodStart, rule, firstDay, skip) {
997
+ const result = [];
998
+ const wantsInvalid = skip !== SKIP_OMIT && rule.byMonthDay && rule.byMonthDay.length > 0;
999
+ if (rule.frequency === FREQ_YEARLY) {
1000
+ for (let month = 1; month <= 12; month += 1) {
1001
+ const maxDays = wantsInvalid ? 31 : daysInMonth(periodStart.year, month);
1002
+ for (let day = 1; day <= maxDays; day += 1) {
1003
+ const valid = day <= daysInMonth(periodStart.year, month);
1004
+ result.push({
1005
+ year: periodStart.year,
1006
+ month,
1007
+ day,
1008
+ valid
1009
+ });
1010
+ }
1011
+ }
1012
+ return result;
1013
+ }
1014
+ if (rule.frequency === FREQ_MONTHLY) {
1015
+ const maxDays = wantsInvalid ? 31 : daysInMonth(periodStart.year, periodStart.month);
1016
+ for (let day = 1; day <= maxDays; day += 1) {
1017
+ const valid = day <= daysInMonth(periodStart.year, periodStart.month);
1018
+ result.push({
1019
+ year: periodStart.year,
1020
+ month: periodStart.month,
1021
+ day,
1022
+ valid
1023
+ });
1024
+ }
1025
+ return result;
1026
+ }
1027
+ if (rule.frequency === FREQ_WEEKLY) {
1028
+ let cursor = periodStart;
1029
+ for (let i = 0; i < 7; i += 1) {
1030
+ result.push({
1031
+ year: cursor.year,
1032
+ month: cursor.month,
1033
+ day: cursor.day,
1034
+ valid: true
1035
+ });
1036
+ cursor = addDays(cursor, 1);
1037
+ }
1038
+ return result;
1039
+ }
1040
+ if (rule.frequency === FREQ_DAILY) {
1041
+ result.push({
1042
+ year: periodStart.year,
1043
+ month: periodStart.month,
1044
+ day: periodStart.day,
1045
+ valid: true
1046
+ });
1047
+ return result;
1048
+ }
1049
+ if (rule.frequency === FREQ_HOURLY || rule.frequency === FREQ_MINUTELY || rule.frequency === FREQ_SECONDLY) {
1050
+ result.push({
1051
+ year: periodStart.year,
1052
+ month: periodStart.month,
1053
+ day: periodStart.day,
1054
+ valid: true
1055
+ });
1056
+ return result;
1057
+ }
1058
+ return result;
1059
+ }
1060
+ /**
1061
+ * Apply BY* filters and skip behavior to candidate dates.
1062
+ * @param candidates Candidate dates for the period.
1063
+ * @param rule Normalized recurrence rule.
1064
+ * @param periodStart Start of the current recurrence period.
1065
+ * @param firstDay First day of the week for weekly calculations.
1066
+ * @param skip Skip policy for invalid month days.
1067
+ * @return Filtered candidate dates.
1068
+ */
1069
+ function filterDateCandidates(candidates, rule, periodStart, firstDay, skip) {
1070
+ let result = candidates;
1071
+ if (rule.byMonth && rule.byMonth.length > 0) {
1072
+ const months = rule.byMonth.map((m) => parseInt(m, 10)).filter((m) => !Number.isNaN(m));
1073
+ result = result.filter((d) => months.includes(d.month));
1074
+ }
1075
+ if (rule.byWeekNo && rule.byWeekNo.length > 0) {
1076
+ const byWeekNo = rule.byWeekNo.filter((d) => d !== 0);
1077
+ result = result.filter((d) => d.valid && matchesByWeekNo(d, byWeekNo, firstDay));
1078
+ }
1079
+ if (rule.byYearDay && rule.byYearDay.length > 0) {
1080
+ const byYearDay = rule.byYearDay.filter((d) => d !== 0);
1081
+ result = result.filter((d) => d.valid && matchesByYearDay(d, byYearDay));
1082
+ }
1083
+ if (rule.byMonthDay && rule.byMonthDay.length > 0) {
1084
+ const byMonthDay = rule.byMonthDay.filter((d) => d !== 0);
1085
+ result = result.filter((d) => matchesByMonthDay(d, byMonthDay));
1086
+ }
1087
+ const byDay = rule.byDay;
1088
+ if (byDay && byDay.length > 0) result = result.filter((d) => matchesByDay(d, byDay, rule.frequency, periodStart, firstDay));
1089
+ if (skip !== SKIP_OMIT && rule.byMonthDay && rule.byMonthDay.length > 0) result = adjustInvalidMonthDays(result, skip);
1090
+ else result = result.filter((d) => d.valid);
1091
+ return result;
1092
+ }
1093
+ /**
1094
+ * Shift invalid month-day candidates using the skip policy.
1095
+ * @param candidates Candidate dates that may include invalid month days.
1096
+ * @param skip Skip policy for invalid month days.
1097
+ * @return Candidates with invalid days adjusted or removed.
1098
+ */
1099
+ function adjustInvalidMonthDays(candidates, skip) {
1100
+ const adjusted = [];
1101
+ for (const candidate of candidates) {
1102
+ if (candidate.valid) {
1103
+ adjusted.push(candidate);
1104
+ continue;
1105
+ }
1106
+ if (skip === SKIP_FORWARD) {
1107
+ const next = addMonths({
1108
+ year: candidate.year,
1109
+ month: candidate.month,
1110
+ day: 1,
1111
+ hour: 0,
1112
+ minute: 0,
1113
+ second: 0
1114
+ }, 1);
1115
+ adjusted.push({
1116
+ year: next.year,
1117
+ month: next.month,
1118
+ day: 1,
1119
+ valid: true
1120
+ });
1121
+ } else if (skip === SKIP_BACKWARD) {
1122
+ const day = daysInMonth(candidate.year, candidate.month);
1123
+ adjusted.push({
1124
+ year: candidate.year,
1125
+ month: candidate.month,
1126
+ day,
1127
+ valid: true
1128
+ });
1129
+ }
1130
+ }
1131
+ const deduped = /* @__PURE__ */ new Map();
1132
+ for (const candidate of adjusted) {
1133
+ const key = `${pad(candidate.year, 4)}-${pad(candidate.month, 2)}-${pad(candidate.day, 2)}`;
1134
+ if (!deduped.has(key)) deduped.set(key, candidate);
1135
+ }
1136
+ return Array.from(deduped.values());
1137
+ }
1138
+
1139
+ //#endregion
1140
+ //#region src/recurrence/rule-generate.ts
1141
+ /**
1142
+ * Generate date-time strings for the current period using rule filters.
1143
+ * @param periodStart Start of the current recurrence period.
1144
+ * @param rule Normalized recurrence rule.
1145
+ * @param firstDay First day of the week for weekly calculations.
1146
+ * @param skip Skip policy for invalid month days.
1147
+ * @return Date-time strings for this period.
1148
+ */
1149
+ function generateDateTimes(periodStart, rule, firstDay, skip) {
1150
+ const filteredDates = filterDateCandidates(generateDateCandidates(periodStart, rule, firstDay, skip), rule, periodStart, firstDay, skip);
1151
+ const hours = rule.byHour && rule.byHour.length > 0 ? rule.byHour : [periodStart.hour];
1152
+ const minutes = rule.byMinute && rule.byMinute.length > 0 ? rule.byMinute : [periodStart.minute];
1153
+ const seconds = rule.bySecond && rule.bySecond.length > 0 ? rule.bySecond : [periodStart.second];
1154
+ const result = [];
1155
+ for (const date of filteredDates) for (const hour of hours) for (const minute of minutes) for (const second of seconds) {
1156
+ const dt = formatLocalDateTime({
1157
+ year: date.year,
1158
+ month: date.month,
1159
+ day: date.day,
1160
+ hour,
1161
+ minute,
1162
+ second
1163
+ });
1164
+ result.push(dt);
1165
+ }
1166
+ return result;
1167
+ }
1168
+
1169
+ //#endregion
1170
+ //#region src/recurrence/rule-normalize.ts
1171
+ /**
1172
+ * Normalize rule fields by copying arrays and filling defaults from the start date-time.
1173
+ * @param rule Recurrence rule to normalize.
1174
+ * @param start Anchor date-time that supplies default by* values.
1175
+ * @return Normalized recurrence rule with default by* fields filled.
1176
+ */
1177
+ function normalizeRule(rule, start) {
1178
+ const normalized = {
1179
+ ...rule,
1180
+ bySecond: rule.bySecond ? [...rule.bySecond] : void 0,
1181
+ byMinute: rule.byMinute ? [...rule.byMinute] : void 0,
1182
+ byHour: rule.byHour ? [...rule.byHour] : void 0,
1183
+ byDay: rule.byDay ? [...rule.byDay] : void 0,
1184
+ byMonthDay: rule.byMonthDay ? [...rule.byMonthDay] : void 0,
1185
+ byMonth: rule.byMonth ? [...rule.byMonth] : void 0,
1186
+ byYearDay: rule.byYearDay ? [...rule.byYearDay] : void 0,
1187
+ byWeekNo: rule.byWeekNo ? [...rule.byWeekNo] : void 0,
1188
+ bySetPosition: rule.bySetPosition ? [...rule.bySetPosition] : void 0
1189
+ };
1190
+ if (normalized.frequency !== FREQ_SECONDLY && (!normalized.bySecond || normalized.bySecond.length === 0)) normalized.bySecond = [start.second];
1191
+ if (normalized.frequency !== FREQ_SECONDLY && normalized.frequency !== FREQ_MINUTELY && (!normalized.byMinute || normalized.byMinute.length === 0)) normalized.byMinute = [start.minute];
1192
+ if (normalized.frequency !== FREQ_SECONDLY && normalized.frequency !== FREQ_MINUTELY && normalized.frequency !== FREQ_HOURLY && (!normalized.byHour || normalized.byHour.length === 0)) normalized.byHour = [start.hour];
1193
+ if (normalized.frequency === FREQ_WEEKLY && (!normalized.byDay || normalized.byDay.length === 0)) normalized.byDay = [{
1194
+ "@type": "NDay",
1195
+ day: dayOfWeek(start)
1196
+ }];
1197
+ if (normalized.frequency === FREQ_MONTHLY && (!normalized.byDay || normalized.byDay.length === 0) && (!normalized.byMonthDay || normalized.byMonthDay.length === 0)) normalized.byMonthDay = [start.day];
1198
+ if (normalized.frequency === FREQ_YEARLY && (!normalized.byYearDay || normalized.byYearDay.length === 0)) {
1199
+ const hasByMonth = normalized.byMonth && normalized.byMonth.length > 0;
1200
+ const hasByWeekNo = normalized.byWeekNo && normalized.byWeekNo.length > 0;
1201
+ const hasByMonthDay = normalized.byMonthDay && normalized.byMonthDay.length > 0;
1202
+ const hasByDay = normalized.byDay && normalized.byDay.length > 0;
1203
+ if (!hasByMonth && !hasByWeekNo && (hasByMonthDay || !hasByDay)) normalized.byMonth = [start.month.toString()];
1204
+ if (!hasByMonthDay && !hasByWeekNo && !hasByDay) normalized.byMonthDay = [start.day];
1205
+ if (hasByWeekNo && !hasByMonthDay && !hasByDay) normalized.byDay = [{
1206
+ "@type": "NDay",
1207
+ day: dayOfWeek(start)
1208
+ }];
1209
+ }
1210
+ return normalized;
1211
+ }
1212
+
1213
+ //#endregion
1214
+ //#region src/recurrence/rule-selectors.ts
1215
+ /**
1216
+ * Select candidate positions based on BYSETPOS rules.
1217
+ * @param candidates Candidate date-time strings.
1218
+ * @param setPos BYSETPOS indices (1-based, negative from end).
1219
+ * @return Selected date-time strings in the order of set positions.
1220
+ */
1221
+ function applyBySetPos(candidates, setPos) {
1222
+ const sorted = [...candidates].sort();
1223
+ const result = [];
1224
+ const total = sorted.length;
1225
+ for (const pos of setPos) {
1226
+ const index = pos > 0 ? pos - 1 : total + pos;
1227
+ if (index >= 0 && index < total) {
1228
+ const value = sorted[index];
1229
+ if (value !== void 0) result.push(value);
1230
+ }
1231
+ }
1232
+ return result;
1233
+ }
1234
+
1235
+ //#endregion
1236
+ //#region src/recurrence/rules.ts
1237
+ /**
1238
+ * Expand a recurrence rule into local date-time strings within a range.
1239
+ * @param anchor Anchor LocalDateTime for the rule.
1240
+ * @param rule Recurrence rule to expand.
1241
+ * @param fromLocal Local date-time lower bound (inclusive).
1242
+ * @param toLocal Local date-time upper bound (inclusive).
1243
+ * @param includeAnchor Whether to include the anchor occurrence.
1244
+ * @param timeZone Optional time zone for range comparisons.
1245
+ * @param fromDate Optional Date lower bound (time zone-aware).
1246
+ * @param toDate Optional Date upper bound (time zone-aware).
1247
+ * @return Local date-time strings that match the rule in range order.
1248
+ */
1249
+ function expandRule(anchor, rule, fromLocal, toLocal, includeAnchor, timeZone, fromDate, toDate) {
1250
+ var _normalized$interval, _normalized$count, _normalized$skip, _normalized$firstDayO;
1251
+ if (rule.rscale && rule.rscale !== RSCALE_GREGORIAN$1) throw new Error(`Unsupported rscale: ${rule.rscale}`);
1252
+ const start = parseLocalDateTime(anchor);
1253
+ const normalized = normalizeRule(rule, start);
1254
+ const interval = (_normalized$interval = normalized.interval) !== null && _normalized$interval !== void 0 ? _normalized$interval : 1;
1255
+ const until = normalized.until;
1256
+ const count = (_normalized$count = normalized.count) !== null && _normalized$count !== void 0 ? _normalized$count : Infinity;
1257
+ const skip = (_normalized$skip = normalized.skip) !== null && _normalized$skip !== void 0 ? _normalized$skip : SKIP_OMIT;
1258
+ const firstDay = (_normalized$firstDayO = normalized.firstDayOfWeek) !== null && _normalized$firstDayO !== void 0 ? _normalized$firstDayO : "mo";
1259
+ const results = [];
1260
+ let generated = 0;
1261
+ let cursor = start;
1262
+ while (generated < count) {
1263
+ let filtered = generateDateTimes(cursor, normalized, firstDay, skip).sort();
1264
+ if (normalized.bySetPosition && normalized.bySetPosition.length > 0) filtered = applyBySetPos(filtered, normalized.bySetPosition);
1265
+ for (const dt of filtered) {
1266
+ generated += 1;
1267
+ if (generated > count) break;
1268
+ if (until && dt > until) return results;
1269
+ if (dt >= fromLocal && dt <= toLocal) results.push(dt);
1270
+ }
1271
+ if (until && cursor && formatLocalDateTime(cursor) > until) break;
1272
+ if (generated >= count) break;
1273
+ if (cursor && compareDate(cursor, parseLocalDateTime(toLocal)) > 0) break;
1274
+ cursor = addInterval(cursor, normalized.frequency, interval, firstDay);
1275
+ }
1276
+ return results;
1277
+ }
1278
+
1279
+ //#endregion
1280
+ //#region src/recurrence/expand.ts
1281
+ /**
1282
+ * Expand recurrence into occurrences sorted by recurrenceId/start.
1283
+ * @param items JSCalendar objects to expand.
1284
+ * @param range Date range bounds.
1285
+ * @return Generator of expanded occurrences.
1286
+ */
1287
+ function* expandRecurrence(items, range) {
1288
+ const occurrences = [];
1289
+ let index = 0;
1290
+ for (const item of items) {
1291
+ if (item["@type"] === TYPE_EVENT$3) {
1292
+ for (const occurrence of expandEvent(item, range)) {
1293
+ occurrences.push({
1294
+ value: occurrence,
1295
+ key: occurrenceKey(occurrence),
1296
+ index
1297
+ });
1298
+ index += 1;
1299
+ }
1300
+ continue;
1301
+ }
1302
+ if (item["@type"] === TYPE_TASK$3) {
1303
+ for (const occurrence of expandTask(item, range)) {
1304
+ occurrences.push({
1305
+ value: occurrence,
1306
+ key: occurrenceKey(occurrence),
1307
+ index
1308
+ });
1309
+ index += 1;
1310
+ }
1311
+ continue;
1312
+ }
1313
+ occurrences.push({
1314
+ value: item,
1315
+ key: occurrenceKey(item),
1316
+ index
1317
+ });
1318
+ index += 1;
1319
+ }
1320
+ occurrences.sort((a, b) => {
1321
+ if (a.key && b.key) return a.key.localeCompare(b.key);
1322
+ if (a.key) return -1;
1323
+ if (b.key) return 1;
1324
+ return a.index - b.index;
1325
+ });
1326
+ for (const occurrence of occurrences) yield occurrence.value;
1327
+ }
1328
+ /**
1329
+ * Expand recurrence paged into occurrences.
1330
+ * @param items JSCalendar objects to expand.
1331
+ * @param range Date range bounds.
1332
+ * @param options Pagination options.
1333
+ * @return Page of expanded items plus an optional next cursor.
1334
+ */
1335
+ function expandRecurrencePaged(items, range, options) {
1336
+ const result = [];
1337
+ let nextCursor;
1338
+ for (const occurrence of expandRecurrence(items, range)) {
1339
+ const key = occurrenceKey(occurrence);
1340
+ if (options.cursor && key) {
1341
+ if (key <= options.cursor) continue;
1342
+ } else if (options.cursor && !key) continue;
1343
+ result.push(occurrence);
1344
+ if (key) nextCursor = key;
1345
+ if (result.length >= options.limit) break;
1346
+ }
1347
+ return {
1348
+ items: result,
1349
+ nextCursor
1350
+ };
1351
+ }
1352
+ /**
1353
+ * Expand event into occurrences.
1354
+ * @param event Event to expand.
1355
+ * @param range Date range bounds.
1356
+ * @return Generator of expanded occurrences.
1357
+ */
1358
+ function expandEvent(event, range) {
1359
+ var _event$timeZone;
1360
+ return expandObject(event, range, event.start, event.recurrenceRules, event.excludedRecurrenceRules, event.recurrenceOverrides, (_event$timeZone = event.timeZone) !== null && _event$timeZone !== void 0 ? _event$timeZone : null);
1361
+ }
1362
+ /**
1363
+ * Expand task into occurrences.
1364
+ * @param task Task to expand.
1365
+ * @param range Date range bounds.
1366
+ * @return Generator of expanded occurrences.
1367
+ */
1368
+ function expandTask(task, range) {
1369
+ var _task$start, _task$timeZone;
1370
+ const anchor = (_task$start = task.start) !== null && _task$start !== void 0 ? _task$start : task.due;
1371
+ if (!anchor) return (function* empty() {})();
1372
+ return expandObject(task, range, anchor, task.recurrenceRules, task.excludedRecurrenceRules, task.recurrenceOverrides, (_task$timeZone = task.timeZone) !== null && _task$timeZone !== void 0 ? _task$timeZone : null);
1373
+ }
1374
+ /**
1375
+ * Build a stable ordering key for an occurrence.
1376
+ * @param value Occurrence object.
1377
+ * @return Sort key or undefined when not available.
1378
+ */
1379
+ function occurrenceKey(value) {
1380
+ var _value$start;
1381
+ if (value.recurrenceId) return value.recurrenceId;
1382
+ if (value["@type"] === TYPE_EVENT$3) return value.start;
1383
+ if (value["@type"] === TYPE_TASK$3) return (_value$start = value.start) !== null && _value$start !== void 0 ? _value$start : value.due;
1384
+ }
1385
+ /**
1386
+ * Expand object into occurrences.
1387
+ * @param base Base JSCalendar object.
1388
+ * @param range Date range bounds.
1389
+ * @param anchor Anchor LocalDateTime for the series.
1390
+ * @param rules Inclusion recurrence rules.
1391
+ * @param excludedRules Exclusion recurrence rules.
1392
+ * @param overrides Recurrence overrides keyed by LocalDateTime.
1393
+ * @param recurrenceIdTimeZone Optional time zone for recurrence IDs.
1394
+ * @return Generator of expanded occurrences.
1395
+ */
1396
+ function* expandObject(base, range, anchor, rules, excludedRules, overrides, recurrenceIdTimeZone) {
1397
+ var _rules$;
1398
+ const hasZone = Boolean(recurrenceIdTimeZone);
1399
+ const fromLocal = hasZone && recurrenceIdTimeZone ? dateTimeInTimeZone(range.from, recurrenceIdTimeZone) : localDateTimeFromDate(range.from);
1400
+ const toLocal = hasZone && recurrenceIdTimeZone ? dateTimeInTimeZone(range.to, recurrenceIdTimeZone) : localDateTimeFromDate(range.to);
1401
+ const fromDate = range.from;
1402
+ const toDate = range.to;
1403
+ const overrideKeys = overrides ? Object.keys(overrides) : [];
1404
+ if (!rules || rules.length === 0) {
1405
+ if (hasZone && recurrenceIdTimeZone) {
1406
+ if (isInRangeWithZone(anchor, fromDate, toDate, recurrenceIdTimeZone)) yield base;
1407
+ } else if (isInRange(anchor, fromLocal, toLocal)) yield base;
1408
+ for (const key of overrideKeys) {
1409
+ const instance = buildInstance(base, key, recurrenceIdTimeZone, overrides ? overrides[key] : void 0);
1410
+ if (!instance) continue;
1411
+ if (hasZone && recurrenceIdTimeZone) {
1412
+ if (isInRangeWithZone(key, fromDate, toDate, recurrenceIdTimeZone)) yield instance;
1413
+ } else if (isInRange(key, fromLocal, toLocal)) yield instance;
1414
+ }
1415
+ return;
1416
+ }
1417
+ const occurrences = [];
1418
+ for (const rule of rules) {
1419
+ const expanded = expandRule(anchor, rule, fromLocal, toLocal, true, recurrenceIdTimeZone !== null && recurrenceIdTimeZone !== void 0 ? recurrenceIdTimeZone : void 0, fromDate, toDate);
1420
+ occurrences.push(...expanded);
1421
+ }
1422
+ const excluded = /* @__PURE__ */ new Set();
1423
+ if (excludedRules && excludedRules.length > 0) for (const rule of excludedRules) {
1424
+ const expanded = expandRule(anchor, rule, fromLocal, toLocal, true, recurrenceIdTimeZone !== null && recurrenceIdTimeZone !== void 0 ? recurrenceIdTimeZone : void 0, fromDate, toDate);
1425
+ for (const value of expanded) excluded.add(value);
1426
+ }
1427
+ if (!occurrences.includes(anchor)) occurrences.push(anchor);
1428
+ for (const key of overrideKeys) if (!occurrences.includes(key)) occurrences.push(key);
1429
+ let sorted = Array.from(new Set(occurrences)).sort((a, b) => compareLocal(a, b, recurrenceIdTimeZone !== null && recurrenceIdTimeZone !== void 0 ? recurrenceIdTimeZone : void 0));
1430
+ if (((_rules$ = rules[0]) === null || _rules$ === void 0 ? void 0 : _rules$.count) && sorted.length > rules[0].count) sorted = sorted.slice(0, rules[0].count);
1431
+ for (const dt of sorted) {
1432
+ if (excluded.has(dt)) continue;
1433
+ const instance = buildInstance(base, dt, recurrenceIdTimeZone, overrides ? overrides[dt] : void 0);
1434
+ if (!instance) continue;
1435
+ if (hasZone && recurrenceIdTimeZone) {
1436
+ if (isInRangeWithZone(dt, fromDate, toDate, recurrenceIdTimeZone)) yield instance;
1437
+ } else if (isInRange(dt, fromLocal, toLocal)) yield instance;
1438
+ }
1439
+ }
1440
+ /**
1441
+ * Build an occurrence instance from a base object plus override patch.
1442
+ * @param base Base JSCalendar object.
1443
+ * @param recurrenceId LocalDateTime recurrence id.
1444
+ * @param recurrenceIdTimeZone Optional time zone for recurrence id.
1445
+ * @param patch Override patch for the occurrence.
1446
+ * @return Occurrence instance or null if excluded.
1447
+ */
1448
+ function buildInstance(base, recurrenceId, recurrenceIdTimeZone, patch) {
1449
+ const patched = patch ? applyPatch(base, patch) : base;
1450
+ if (isExcludedInstance(patched)) return null;
1451
+ const overridesStart = patchHasKey(patch, "start");
1452
+ const overridesDue = patchHasKey(patch, "due");
1453
+ let shifted;
1454
+ if (patched["@type"] === TYPE_EVENT$3) shifted = overridesStart ? patched : {
1455
+ ...patched,
1456
+ start: recurrenceId
1457
+ };
1458
+ else if (patched["@type"] === TYPE_TASK$3) if (patched.start) shifted = overridesStart ? patched : {
1459
+ ...patched,
1460
+ start: recurrenceId
1461
+ };
1462
+ else if (patched.due) shifted = overridesDue ? patched : {
1463
+ ...patched,
1464
+ due: recurrenceId
1465
+ };
1466
+ else shifted = patched;
1467
+ else shifted = patched;
1468
+ const instance = {
1469
+ ...stripRecurrenceProperties(shifted),
1470
+ recurrenceId
1471
+ };
1472
+ if (recurrenceIdTimeZone) instance.recurrenceIdTimeZone = recurrenceIdTimeZone;
1473
+ return instance;
1474
+ }
1475
+ /**
1476
+ * Check if a patch contains a key or pointer.
1477
+ * @param patch Patch object to inspect.
1478
+ * @param key Field name to look up.
1479
+ * @return True when the patch modifies the field.
1480
+ */
1481
+ function patchHasKey(patch, key) {
1482
+ if (!patch) return false;
1483
+ if (Object.prototype.hasOwnProperty.call(patch, key)) return true;
1484
+ if (Object.prototype.hasOwnProperty.call(patch, `/${key}`)) return true;
1485
+ return false;
1486
+ }
1487
+ /**
1488
+ * Strip recurrence properties from value.
1489
+ * @param object JSCalendar object to clean.
1490
+ * @return Object without recurrence rule fields.
1491
+ */
1492
+ function stripRecurrenceProperties(object) {
1493
+ const { recurrenceRules: _recurrenceRules, excludedRecurrenceRules: _excludedRecurrenceRules, recurrenceOverrides: _recurrenceOverrides, ...rest } = object;
1494
+ return rest;
1495
+ }
1496
+ /**
1497
+ * Check whether value is excluded instance.
1498
+ * @param object JSCalendar object.
1499
+ * @return True when the occurrence is excluded.
1500
+ */
1501
+ function isExcludedInstance(object) {
1502
+ return object.excluded === true;
1503
+ }
1504
+ /**
1505
+ * Check whether value is in range.
1506
+ * @param value LocalDateTime string.
1507
+ * @param from LocalDateTime lower bound.
1508
+ * @param to LocalDateTime upper bound.
1509
+ * @return True when value is within the range.
1510
+ */
1511
+ function isInRange(value, from, to) {
1512
+ return value >= from && value <= to;
1513
+ }
1514
+ /**
1515
+ * Check whether value is in range with zone.
1516
+ * @param value LocalDateTime string.
1517
+ * @param from Date lower bound.
1518
+ * @param to Date upper bound.
1519
+ * @param timeZone Time zone for LocalDateTime conversion.
1520
+ * @return True when value is within the range.
1521
+ */
1522
+ function isInRangeWithZone(value, from, to, timeZone) {
1523
+ const utc = localDateTimeToUtcDate(value, timeZone);
1524
+ return utc >= from && utc <= to;
1525
+ }
1526
+ /**
1527
+ * Compare local date-time strings, optionally using a time zone.
1528
+ * @param a LocalDateTime string A.
1529
+ * @param b LocalDateTime string B.
1530
+ * @param timeZone Optional time zone for comparison.
1531
+ * @return Negative/zero/positive comparison result.
1532
+ */
1533
+ function compareLocal(a, b, timeZone) {
1534
+ if (!timeZone) {
1535
+ if (a === b) return 0;
1536
+ return a < b ? -1 : 1;
1537
+ }
1538
+ const aUtc = localDateTimeToUtcDate(a, timeZone).getTime();
1539
+ const bUtc = localDateTimeToUtcDate(b, timeZone).getTime();
1540
+ if (aUtc === bUtc) return 0;
1541
+ return aUtc < bUtc ? -1 : 1;
1542
+ }
1543
+
1544
+ //#endregion
1545
+ //#region src/search.ts
1546
+ const TYPE_EVENT$2 = "Event";
1547
+ const TYPE_GROUP$2 = "Group";
1548
+ const TYPE_TASK$2 = "Task";
1549
+ /**
1550
+ * Find a JSCalendar object by UID.
1551
+ * @param items Items to search.
1552
+ * @param uid UID to match.
1553
+ * @return The matching item or undefined.
1554
+ */
1555
+ function findByUid(items, uid) {
1556
+ return items.find((item) => item.uid === uid);
1557
+ }
1558
+ /**
1559
+ * Filter items by JSCalendar @type.
1560
+ * @param items Items to filter.
1561
+ * @param type Type to match.
1562
+ * @return Filtered items.
1563
+ */
1564
+ function filterByType(items, type) {
1565
+ return items.filter((item) => item["@type"] === type);
1566
+ }
1567
+ /**
1568
+ * Group JSCalendar objects by @type.
1569
+ * @param items Items to group.
1570
+ * @return Record keyed by type.
1571
+ */
1572
+ function groupByType(items) {
1573
+ return items.reduce((acc, item) => {
1574
+ const type = item["@type"];
1575
+ if (!acc[type]) acc[type] = [];
1576
+ acc[type].push(item);
1577
+ return acc;
1578
+ }, {});
1579
+ }
1580
+ /**
1581
+ * Filter items by free-text match.
1582
+ * @param items Items to search.
1583
+ * @param query Text query.
1584
+ * @return Filtered items.
1585
+ */
1586
+ function filterByText(items, query) {
1587
+ const needle = query.trim().toLowerCase();
1588
+ if (!needle) return items;
1589
+ return items.filter((item) => collectText(item).includes(needle));
1590
+ }
1591
+ /**
1592
+ * Filter items that intersect a date range.
1593
+ * @param items Items to filter.
1594
+ * @param range Range with optional start and end.
1595
+ * @param options Filtering options.
1596
+ * @return Filtered items.
1597
+ */
1598
+ function filterByDateRange(items, range, options = {}) {
1599
+ var _options$includeIncom;
1600
+ const includeIncomparable = (_options$includeIncom = options.includeIncomparable) !== null && _options$includeIncom !== void 0 ? _options$includeIncom : false;
1601
+ return items.filter((item) => {
1602
+ var _item$timeZone;
1603
+ const objectRange = getObjectRange(item);
1604
+ if (!objectRange) return includeIncomparable;
1605
+ if (range.start instanceof Date || range.end instanceof Date) {
1606
+ var _objectRangeDate$end;
1607
+ const objectRangeDate = getObjectRangeAsDates(item);
1608
+ if (!objectRangeDate) return includeIncomparable;
1609
+ const startDate = objectRangeDate.start;
1610
+ const endDate = (_objectRangeDate$end = objectRangeDate.end) !== null && _objectRangeDate$end !== void 0 ? _objectRangeDate$end : objectRangeDate.start;
1611
+ if (range.start instanceof Date) {
1612
+ if (endDate < range.start) return false;
1613
+ }
1614
+ if (range.end instanceof Date) {
1615
+ if (startDate > range.end) return false;
1616
+ }
1617
+ return true;
1618
+ }
1619
+ const timeZone = "timeZone" in item ? (_item$timeZone = item.timeZone) !== null && _item$timeZone !== void 0 ? _item$timeZone : void 0 : void 0;
1620
+ const rangeStart = normalizeRangeValue(range.start, timeZone);
1621
+ const rangeEnd = normalizeRangeValue(range.end, timeZone);
1622
+ if (range.start) {
1623
+ var _objectRange$end;
1624
+ const cmpStart = compareDateTime((_objectRange$end = objectRange.end) !== null && _objectRange$end !== void 0 ? _objectRange$end : objectRange.start, rangeStart);
1625
+ if (cmpStart === null) return includeIncomparable;
1626
+ if (cmpStart < 0) return false;
1627
+ }
1628
+ if (range.end) {
1629
+ const cmpEnd = compareDateTime(objectRange.start, rangeEnd);
1630
+ if (cmpEnd === null) return includeIncomparable;
1631
+ if (cmpEnd > 0) return false;
1632
+ }
1633
+ return true;
1634
+ });
1635
+ }
1636
+ /**
1637
+ * Normalize a range value into a LocalDateTime or UTCDateTime string.
1638
+ * @param value Range value.
1639
+ * @param timeZone Optional time zone.
1640
+ * @return Normalized date-time string.
1641
+ */
1642
+ function normalizeRangeValue(value, timeZone) {
1643
+ if (!value) return "";
1644
+ if (isStringValue(value)) return value;
1645
+ if (timeZone) return dateTimeInTimeZone(value, timeZone);
1646
+ return localDateTimeFromDate(value);
1647
+ }
1648
+ /**
1649
+ * Collect searchable text from a JSCalendar object.
1650
+ * @param item Item to inspect.
1651
+ * @return Lowercased text blob.
1652
+ */
1653
+ function collectText(item) {
1654
+ const parts = [];
1655
+ if (item.title) parts.push(item.title);
1656
+ if (item.description) parts.push(item.description);
1657
+ if (item.locations) for (const location of Object.values(item.locations)) {
1658
+ if (location.name) parts.push(location.name);
1659
+ if (location.description) parts.push(location.description);
1660
+ }
1661
+ if (item.virtualLocations) for (const vloc of Object.values(item.virtualLocations)) {
1662
+ if (vloc.name) parts.push(vloc.name);
1663
+ if (vloc.description) parts.push(vloc.description);
1664
+ if (vloc.uri) parts.push(vloc.uri);
1665
+ }
1666
+ if (item.participants) for (const participant of Object.values(item.participants)) {
1667
+ if (participant.name) parts.push(participant.name);
1668
+ if (participant.email) parts.push(participant.email);
1669
+ if (participant.description) parts.push(participant.description);
1670
+ }
1671
+ return parts.join(" ").toLowerCase();
1672
+ }
1673
+ /**
1674
+ * Get the string-based time range for an object.
1675
+ * @param item JSCalendar object.
1676
+ * @return Range or null if unavailable.
1677
+ */
1678
+ function getObjectRange(item) {
1679
+ if (item["@type"] === TYPE_EVENT$2) return getEventRange(item);
1680
+ if (item["@type"] === TYPE_TASK$2) return getTaskRange(item);
1681
+ if (item["@type"] === TYPE_GROUP$2) return getGroupRange(item);
1682
+ return null;
1683
+ }
1684
+ /**
1685
+ * Get the Date-based time range for an object.
1686
+ * @param item JSCalendar object.
1687
+ * @return Range or null if unavailable.
1688
+ */
1689
+ function getObjectRangeAsDates(item) {
1690
+ if (item["@type"] === TYPE_EVENT$2) return getEventRangeAsDates(item);
1691
+ if (item["@type"] === TYPE_TASK$2) return getTaskRangeAsDates(item);
1692
+ if (item["@type"] === TYPE_GROUP$2) return getGroupRangeAsDates(item);
1693
+ return null;
1694
+ }
1695
+ /**
1696
+ * Get the string-based range for an event.
1697
+ * @param event Event instance.
1698
+ * @return Event range.
1699
+ */
1700
+ function getEventRange(event) {
1701
+ if (!event.duration) return { start: event.start };
1702
+ if (isUtcDateTime(event.start)) {
1703
+ const ms = durationToMilliseconds(event.duration);
1704
+ if (ms !== null) {
1705
+ const end = new Date(Date.parse(event.start) + ms).toISOString().replace(/\.000Z$/, "Z");
1706
+ return {
1707
+ start: event.start,
1708
+ end
1709
+ };
1710
+ }
1711
+ }
1712
+ return { start: event.start };
1713
+ }
1714
+ /**
1715
+ * Get the Date-based range for an event.
1716
+ * @param event Event instance.
1717
+ * @return Event range or null if incomparable.
1718
+ */
1719
+ function getEventRangeAsDates(event) {
1720
+ const start = event.timeZone ? localDateTimeToUtcDate(event.start, event.timeZone) : isUtcDateTime(event.start) ? new Date(event.start) : null;
1721
+ if (!start) return null;
1722
+ if (!event.duration) return { start };
1723
+ const ms = durationToMilliseconds(event.duration);
1724
+ if (ms === null) return { start };
1725
+ return {
1726
+ start,
1727
+ end: new Date(start.getTime() + ms)
1728
+ };
1729
+ }
1730
+ /**
1731
+ * Get the string-based range for a task.
1732
+ * @param task Task instance.
1733
+ * @return Task range or null if missing.
1734
+ */
1735
+ function getTaskRange(task) {
1736
+ var _task$start;
1737
+ const start = (_task$start = task.start) !== null && _task$start !== void 0 ? _task$start : task.due;
1738
+ if (!start) return null;
1739
+ return { start };
1740
+ }
1741
+ /**
1742
+ * Get the Date-based range for a task.
1743
+ * @param task Task instance.
1744
+ * @return Task range or null if incomparable.
1745
+ */
1746
+ function getTaskRangeAsDates(task) {
1747
+ var _task$start2;
1748
+ const start = (_task$start2 = task.start) !== null && _task$start2 !== void 0 ? _task$start2 : task.due;
1749
+ if (!start) return null;
1750
+ if (task.timeZone) return { start: localDateTimeToUtcDate(start, task.timeZone) };
1751
+ if (isUtcDateTime(start)) return { start: new Date(start) };
1752
+ return null;
1753
+ }
1754
+ /**
1755
+ * Get the string-based range for a group.
1756
+ * @param group Group instance.
1757
+ * @return Group range or null if empty.
1758
+ */
1759
+ function getGroupRange(group) {
1760
+ const ranges = group.entries.map((entry) => getObjectRange(entry)).filter((range) => Boolean(range));
1761
+ if (ranges.length === 0) return null;
1762
+ const starts = ranges.map((range) => range.start).sort();
1763
+ const ends = ranges.map((range) => {
1764
+ var _range$end;
1765
+ return (_range$end = range.end) !== null && _range$end !== void 0 ? _range$end : range.start;
1766
+ }).sort();
1767
+ const start = starts[0];
1768
+ const end = ends[ends.length - 1];
1769
+ if (!start || !end) return null;
1770
+ return {
1771
+ start,
1772
+ end
1773
+ };
1774
+ }
1775
+ /**
1776
+ * Get the Date-based range for a group.
1777
+ * @param group Group instance.
1778
+ * @return Group range or null if empty.
1779
+ */
1780
+ function getGroupRangeAsDates(group) {
1781
+ const ranges = group.entries.map((entry) => getObjectRangeAsDates(entry)).filter((range) => Boolean(range));
1782
+ if (ranges.length === 0) return null;
1783
+ const starts = ranges.map((range) => range.start.getTime()).sort((a, b) => a - b);
1784
+ const ends = ranges.map((range) => {
1785
+ var _range$end2;
1786
+ return ((_range$end2 = range.end) !== null && _range$end2 !== void 0 ? _range$end2 : range.start).getTime();
1787
+ }).sort((a, b) => a - b);
1788
+ const start = starts[0];
1789
+ const end = ends[ends.length - 1];
1790
+ if (start === void 0 || end === void 0) return null;
1791
+ return {
1792
+ start: new Date(start),
1793
+ end: new Date(end)
1794
+ };
1795
+ }
1796
+
1797
+ //#endregion
1798
+ //#region src/timezones.ts
1799
+ const TimeZones = [
1800
+ "Africa/Abidjan",
1801
+ "Africa/Accra",
1802
+ "Africa/Addis_Ababa",
1803
+ "Africa/Algiers",
1804
+ "Africa/Asmara",
1805
+ "Africa/Bamako",
1806
+ "Africa/Bangui",
1807
+ "Africa/Banjul",
1808
+ "Africa/Bissau",
1809
+ "Africa/Blantyre",
1810
+ "Africa/Brazzaville",
1811
+ "Africa/Bujumbura",
1812
+ "Africa/Cairo",
1813
+ "Africa/Casablanca",
1814
+ "Africa/Ceuta",
1815
+ "Africa/Conakry",
1816
+ "Africa/Dakar",
1817
+ "Africa/Dar_es_Salaam",
1818
+ "Africa/Djibouti",
1819
+ "Africa/Douala",
1820
+ "Africa/El_Aaiun",
1821
+ "Africa/Freetown",
1822
+ "Africa/Gaborone",
1823
+ "Africa/Harare",
1824
+ "Africa/Johannesburg",
1825
+ "Africa/Juba",
1826
+ "Africa/Kampala",
1827
+ "Africa/Khartoum",
1828
+ "Africa/Kigali",
1829
+ "Africa/Kinshasa",
1830
+ "Africa/Lagos",
1831
+ "Africa/Libreville",
1832
+ "Africa/Lome",
1833
+ "Africa/Luanda",
1834
+ "Africa/Lubumbashi",
1835
+ "Africa/Lusaka",
1836
+ "Africa/Malabo",
1837
+ "Africa/Maputo",
1838
+ "Africa/Maseru",
1839
+ "Africa/Mbabane",
1840
+ "Africa/Mogadishu",
1841
+ "Africa/Monrovia",
1842
+ "Africa/Nairobi",
1843
+ "Africa/Ndjamena",
1844
+ "Africa/Niamey",
1845
+ "Africa/Nouakchott",
1846
+ "Africa/Ouagadougou",
1847
+ "Africa/Porto-Novo",
1848
+ "Africa/Sao_Tome",
1849
+ "Africa/Tripoli",
1850
+ "Africa/Tunis",
1851
+ "Africa/Windhoek",
1852
+ "America/Adak",
1853
+ "America/Anchorage",
1854
+ "America/Anguilla",
1855
+ "America/Antigua",
1856
+ "America/Araguaina",
1857
+ "America/Argentina/Buenos_Aires",
1858
+ "America/Argentina/Catamarca",
1859
+ "America/Argentina/Cordoba",
1860
+ "America/Argentina/Jujuy",
1861
+ "America/Argentina/La_Rioja",
1862
+ "America/Argentina/Mendoza",
1863
+ "America/Argentina/Rio_Gallegos",
1864
+ "America/Argentina/Salta",
1865
+ "America/Argentina/San_Juan",
1866
+ "America/Argentina/San_Luis",
1867
+ "America/Argentina/Tucuman",
1868
+ "America/Argentina/Ushuaia",
1869
+ "America/Aruba",
1870
+ "America/Asuncion",
1871
+ "America/Atikokan",
1872
+ "America/Bahia",
1873
+ "America/Bahia_Banderas",
1874
+ "America/Barbados",
1875
+ "America/Belem",
1876
+ "America/Belize",
1877
+ "America/Blanc-Sablon",
1878
+ "America/Boa_Vista",
1879
+ "America/Bogota",
1880
+ "America/Boise",
1881
+ "America/Cambridge_Bay",
1882
+ "America/Campo_Grande",
1883
+ "America/Cancun",
1884
+ "America/Caracas",
1885
+ "America/Cayenne",
1886
+ "America/Cayman",
1887
+ "America/Chicago",
1888
+ "America/Chihuahua",
1889
+ "America/Costa_Rica",
1890
+ "America/Creston",
1891
+ "America/Cuiaba",
1892
+ "America/Curacao",
1893
+ "America/Danmarkshavn",
1894
+ "America/Dawson",
1895
+ "America/Dawson_Creek",
1896
+ "America/Denver",
1897
+ "America/Detroit",
1898
+ "America/Dominica",
1899
+ "America/Edmonton",
1900
+ "America/Eirunepe",
1901
+ "America/El_Salvador",
1902
+ "America/Fort_Nelson",
1903
+ "America/Fortaleza",
1904
+ "America/Glace_Bay",
1905
+ "America/Godthab",
1906
+ "America/Goose_Bay",
1907
+ "America/Grand_Turk",
1908
+ "America/Grenada",
1909
+ "America/Guadeloupe",
1910
+ "America/Guatemala",
1911
+ "America/Guayaquil",
1912
+ "America/Guyana",
1913
+ "America/Halifax",
1914
+ "America/Havana",
1915
+ "America/Hermosillo",
1916
+ "America/Indiana/Indianapolis",
1917
+ "America/Indiana/Knox",
1918
+ "America/Indiana/Marengo",
1919
+ "America/Indiana/Petersburg",
1920
+ "America/Indiana/Tell_City",
1921
+ "America/Indiana/Vevay",
1922
+ "America/Indiana/Vincennes",
1923
+ "America/Indiana/Winamac",
1924
+ "America/Inuvik",
1925
+ "America/Iqaluit",
1926
+ "America/Jamaica",
1927
+ "America/Juneau",
1928
+ "America/Kentucky/Louisville",
1929
+ "America/Kentucky/Monticello",
1930
+ "America/Kralendijk",
1931
+ "America/La_Paz",
1932
+ "America/Lima",
1933
+ "America/Los_Angeles",
1934
+ "America/Lower_Princes",
1935
+ "America/Maceio",
1936
+ "America/Managua",
1937
+ "America/Manaus",
1938
+ "America/Marigot",
1939
+ "America/Martinique",
1940
+ "America/Matamoros",
1941
+ "America/Mazatlan",
1942
+ "America/Menominee",
1943
+ "America/Merida",
1944
+ "America/Metlakatla",
1945
+ "America/Mexico_City",
1946
+ "America/Miquelon",
1947
+ "America/Moncton",
1948
+ "America/Monterrey",
1949
+ "America/Montevideo",
1950
+ "America/Montserrat",
1951
+ "America/Nassau",
1952
+ "America/New_York",
1953
+ "America/Nipigon",
1954
+ "America/Nome",
1955
+ "America/Noronha",
1956
+ "America/North_Dakota/Beulah",
1957
+ "America/North_Dakota/Center",
1958
+ "America/North_Dakota/New_Salem",
1959
+ "America/Ojinaga",
1960
+ "America/Panama",
1961
+ "America/Pangnirtung",
1962
+ "America/Paramaribo",
1963
+ "America/Phoenix",
1964
+ "America/Port-au-Prince",
1965
+ "America/Port_of_Spain",
1966
+ "America/Porto_Velho",
1967
+ "America/Puerto_Rico",
1968
+ "America/Punta_Arenas",
1969
+ "America/Rainy_River",
1970
+ "America/Rankin_Inlet",
1971
+ "America/Recife",
1972
+ "America/Regina",
1973
+ "America/Resolute",
1974
+ "America/Rio_Branco",
1975
+ "America/Santarem",
1976
+ "America/Santiago",
1977
+ "America/Santo_Domingo",
1978
+ "America/Sao_Paulo",
1979
+ "America/Scoresbysund",
1980
+ "America/Sitka",
1981
+ "America/St_Barthelemy",
1982
+ "America/St_Johns",
1983
+ "America/St_Kitts",
1984
+ "America/St_Lucia",
1985
+ "America/St_Thomas",
1986
+ "America/St_Vincent",
1987
+ "America/Swift_Current",
1988
+ "America/Tegucigalpa",
1989
+ "America/Thule",
1990
+ "America/Thunder_Bay",
1991
+ "America/Tijuana",
1992
+ "America/Toronto",
1993
+ "America/Tortola",
1994
+ "America/Vancouver",
1995
+ "America/Whitehorse",
1996
+ "America/Winnipeg",
1997
+ "America/Yakutat",
1998
+ "America/Yellowknife",
1999
+ "Antarctica/Casey",
2000
+ "Antarctica/Davis",
2001
+ "Antarctica/DumontDUrville",
2002
+ "Antarctica/Macquarie",
2003
+ "Antarctica/Mawson",
2004
+ "Antarctica/McMurdo",
2005
+ "Antarctica/Palmer",
2006
+ "Antarctica/Rothera",
2007
+ "Antarctica/Syowa",
2008
+ "Antarctica/Troll",
2009
+ "Antarctica/Vostok",
2010
+ "Arctic/Longyearbyen",
2011
+ "Asia/Aden",
2012
+ "Asia/Almaty",
2013
+ "Asia/Amman",
2014
+ "Asia/Anadyr",
2015
+ "Asia/Aqtau",
2016
+ "Asia/Aqtobe",
2017
+ "Asia/Ashgabat",
2018
+ "Asia/Atyrau",
2019
+ "Asia/Baghdad",
2020
+ "Asia/Bahrain",
2021
+ "Asia/Baku",
2022
+ "Asia/Bangkok",
2023
+ "Asia/Barnaul",
2024
+ "Asia/Beirut",
2025
+ "Asia/Bishkek",
2026
+ "Asia/Brunei",
2027
+ "Asia/Chita",
2028
+ "Asia/Choibalsan",
2029
+ "Asia/Colombo",
2030
+ "Asia/Damascus",
2031
+ "Asia/Dhaka",
2032
+ "Asia/Dili",
2033
+ "Asia/Dubai",
2034
+ "Asia/Dushanbe",
2035
+ "Asia/Famagusta",
2036
+ "Asia/Gaza",
2037
+ "Asia/Hebron",
2038
+ "Asia/Ho_Chi_Minh",
2039
+ "Asia/Hong_Kong",
2040
+ "Asia/Hovd",
2041
+ "Asia/Irkutsk",
2042
+ "Asia/Jakarta",
2043
+ "Asia/Jayapura",
2044
+ "Asia/Jerusalem",
2045
+ "Asia/Kabul",
2046
+ "Asia/Kamchatka",
2047
+ "Asia/Karachi",
2048
+ "Asia/Kathmandu",
2049
+ "Asia/Khandyga",
2050
+ "Asia/Kolkata",
2051
+ "Asia/Krasnoyarsk",
2052
+ "Asia/Kuala_Lumpur",
2053
+ "Asia/Kuching",
2054
+ "Asia/Kuwait",
2055
+ "Asia/Macau",
2056
+ "Asia/Magadan",
2057
+ "Asia/Makassar",
2058
+ "Asia/Manila",
2059
+ "Asia/Muscat",
2060
+ "Asia/Nicosia",
2061
+ "Asia/Novokuznetsk",
2062
+ "Asia/Novosibirsk",
2063
+ "Asia/Omsk",
2064
+ "Asia/Oral",
2065
+ "Asia/Phnom_Penh",
2066
+ "Asia/Pontianak",
2067
+ "Asia/Pyongyang",
2068
+ "Asia/Qatar",
2069
+ "Asia/Qyzylorda",
2070
+ "Asia/Riyadh",
2071
+ "Asia/Sakhalin",
2072
+ "Asia/Samarkand",
2073
+ "Asia/Seoul",
2074
+ "Asia/Shanghai",
2075
+ "Asia/Singapore",
2076
+ "Asia/Srednekolymsk",
2077
+ "Asia/Taipei",
2078
+ "Asia/Tashkent",
2079
+ "Asia/Tbilisi",
2080
+ "Asia/Tehran",
2081
+ "Asia/Thimphu",
2082
+ "Asia/Tokyo",
2083
+ "Asia/Tomsk",
2084
+ "Asia/Ulaanbaatar",
2085
+ "Asia/Urumqi",
2086
+ "Asia/Ust-Nera",
2087
+ "Asia/Vientiane",
2088
+ "Asia/Vladivostok",
2089
+ "Asia/Yakutsk",
2090
+ "Asia/Yangon",
2091
+ "Asia/Yekaterinburg",
2092
+ "Asia/Yerevan",
2093
+ "Atlantic/Azores",
2094
+ "Atlantic/Bermuda",
2095
+ "Atlantic/Canary",
2096
+ "Atlantic/Cape_Verde",
2097
+ "Atlantic/Faroe",
2098
+ "Atlantic/Madeira",
2099
+ "Atlantic/Reykjavik",
2100
+ "Atlantic/South_Georgia",
2101
+ "Atlantic/St_Helena",
2102
+ "Atlantic/Stanley",
2103
+ "Australia/Adelaide",
2104
+ "Australia/Brisbane",
2105
+ "Australia/Broken_Hill",
2106
+ "Australia/Currie",
2107
+ "Australia/Darwin",
2108
+ "Australia/Eucla",
2109
+ "Australia/Hobart",
2110
+ "Australia/Lindeman",
2111
+ "Australia/Lord_Howe",
2112
+ "Australia/Melbourne",
2113
+ "Australia/Perth",
2114
+ "Australia/Sydney",
2115
+ "Europe/Amsterdam",
2116
+ "Europe/Andorra",
2117
+ "Europe/Astrakhan",
2118
+ "Europe/Athens",
2119
+ "Europe/Belgrade",
2120
+ "Europe/Berlin",
2121
+ "Europe/Bratislava",
2122
+ "Europe/Brussels",
2123
+ "Europe/Bucharest",
2124
+ "Europe/Budapest",
2125
+ "Europe/Busingen",
2126
+ "Europe/Chisinau",
2127
+ "Europe/Copenhagen",
2128
+ "Europe/Dublin",
2129
+ "Europe/Gibraltar",
2130
+ "Europe/Guernsey",
2131
+ "Europe/Helsinki",
2132
+ "Europe/Isle_of_Man",
2133
+ "Europe/Istanbul",
2134
+ "Europe/Jersey",
2135
+ "Europe/Kaliningrad",
2136
+ "Europe/Kiev",
2137
+ "Europe/Kirov",
2138
+ "Europe/Lisbon",
2139
+ "Europe/Ljubljana",
2140
+ "Europe/London",
2141
+ "Europe/Luxembourg",
2142
+ "Europe/Madrid",
2143
+ "Europe/Malta",
2144
+ "Europe/Mariehamn",
2145
+ "Europe/Minsk",
2146
+ "Europe/Monaco",
2147
+ "Europe/Moscow",
2148
+ "Europe/Oslo",
2149
+ "Europe/Paris",
2150
+ "Europe/Podgorica",
2151
+ "Europe/Prague",
2152
+ "Europe/Riga",
2153
+ "Europe/Rome",
2154
+ "Europe/Samara",
2155
+ "Europe/San_Marino",
2156
+ "Europe/Sarajevo",
2157
+ "Europe/Saratov",
2158
+ "Europe/Simferopol",
2159
+ "Europe/Skopje",
2160
+ "Europe/Sofia",
2161
+ "Europe/Stockholm",
2162
+ "Europe/Tallinn",
2163
+ "Europe/Tirane",
2164
+ "Europe/Ulyanovsk",
2165
+ "Europe/Uzhgorod",
2166
+ "Europe/Vaduz",
2167
+ "Europe/Vatican",
2168
+ "Europe/Vienna",
2169
+ "Europe/Vilnius",
2170
+ "Europe/Volgograd",
2171
+ "Europe/Warsaw",
2172
+ "Europe/Zagreb",
2173
+ "Europe/Zaporozhye",
2174
+ "Europe/Zurich",
2175
+ "Indian/Antananarivo",
2176
+ "Indian/Chagos",
2177
+ "Indian/Christmas",
2178
+ "Indian/Cocos",
2179
+ "Indian/Comoro",
2180
+ "Indian/Kerguelen",
2181
+ "Indian/Mahe",
2182
+ "Indian/Maldives",
2183
+ "Indian/Mauritius",
2184
+ "Indian/Mayotte",
2185
+ "Indian/Reunion",
2186
+ "Pacific/Apia",
2187
+ "Pacific/Auckland",
2188
+ "Pacific/Bougainville",
2189
+ "Pacific/Chatham",
2190
+ "Pacific/Chuuk",
2191
+ "Pacific/Easter",
2192
+ "Pacific/Efate",
2193
+ "Pacific/Enderbury",
2194
+ "Pacific/Fakaofo",
2195
+ "Pacific/Fiji",
2196
+ "Pacific/Funafuti",
2197
+ "Pacific/Galapagos",
2198
+ "Pacific/Gambier",
2199
+ "Pacific/Guadalcanal",
2200
+ "Pacific/Guam",
2201
+ "Pacific/Honolulu",
2202
+ "Pacific/Kiritimati",
2203
+ "Pacific/Kosrae",
2204
+ "Pacific/Kwajalein",
2205
+ "Pacific/Majuro",
2206
+ "Pacific/Marquesas",
2207
+ "Pacific/Midway",
2208
+ "Pacific/Nauru",
2209
+ "Pacific/Niue",
2210
+ "Pacific/Norfolk",
2211
+ "Pacific/Noumea",
2212
+ "Pacific/Pago_Pago",
2213
+ "Pacific/Palau",
2214
+ "Pacific/Pitcairn",
2215
+ "Pacific/Pohnpei",
2216
+ "Pacific/Port_Moresby",
2217
+ "Pacific/Rarotonga",
2218
+ "Pacific/Saipan",
2219
+ "Pacific/Tahiti",
2220
+ "Pacific/Tarawa",
2221
+ "Pacific/Tongatapu",
2222
+ "Pacific/Wake",
2223
+ "Pacific/Wallis"
2224
+ ];
2225
+ /**
2226
+ * Build a lowercase lookup map for time zones.
2227
+ * @param zones List of time zone IDs.
2228
+ * @return Map keyed by lowercase ID.
2229
+ */
2230
+ function createTimeZoneMap(zones) {
2231
+ const map = {};
2232
+ for (const tz of zones) map[tz.toLowerCase()] = tz;
2233
+ return map;
2234
+ }
2235
+ const TimeZoneMap = createTimeZoneMap(TimeZones);
2236
+ /**
2237
+ * Resolve a time zone input to its canonical ID.
2238
+ * @param input Time zone input.
2239
+ * @return Canonical TimeZoneId.
2240
+ */
2241
+ function resolveTimeZone(input) {
2242
+ const value = TimeZoneMap[input.toLowerCase()];
2243
+ if (!value) throw new Error(`Unknown time zone: ${input}`);
2244
+ return value;
2245
+ }
2246
+
2247
+ //#endregion
2248
+ //#region src/validate/error.ts
2249
+ /**
2250
+ * Error thrown when validation fails.
2251
+ */
2252
+ var ValidationError = class extends Error {
2253
+ path;
2254
+ /**
2255
+ * Create a new ValidationError.
2256
+ * @param path Validation path.
2257
+ * @param message Error message.
2258
+ */
2259
+ constructor(path, message) {
2260
+ super(`${path}: ${message}`);
2261
+ this.name = "ValidationError";
2262
+ this.path = path;
2263
+ }
2264
+ };
2265
+ /**
2266
+ * Throw a ValidationError.
2267
+ * @param path Validation path.
2268
+ * @param message Error message.
2269
+ * @return Never returns.
2270
+ */
2271
+ function fail(path, message) {
2272
+ throw new ValidationError(path, message);
2273
+ }
2274
+
2275
+ //#endregion
2276
+ //#region src/validate/constants.ts
2277
+ const Z_SUFFIX = "Z";
2278
+ const CHARSET_KEY = "charset";
2279
+ const UTF8 = "utf-8";
2280
+ const RSCALE_GREGORIAN = "gregorian";
2281
+ const TYPE_NDAY$1 = "NDay";
2282
+ const TYPE_RECURRENCE_RULE$1 = "RecurrenceRule";
2283
+ const TYPE_ALERT$1 = "Alert";
2284
+ const TYPE_OFFSET_TRIGGER$1 = "OffsetTrigger";
2285
+ const TYPE_ABSOLUTE_TRIGGER$1 = "AbsoluteTrigger";
2286
+ const TYPE_RELATION$1 = "Relation";
2287
+ const TYPE_LINK$1 = "Link";
2288
+ const TYPE_LOCATION$1 = "Location";
2289
+ const TYPE_VIRTUAL_LOCATION$1 = "VirtualLocation";
2290
+ const TYPE_TIME_ZONE_RULE$1 = "TimeZoneRule";
2291
+ const TYPE_TIME_ZONE$1 = "TimeZone";
2292
+ const TYPE_PARTICIPANT$1 = "Participant";
2293
+ const TYPE_EVENT$1 = "Event";
2294
+ const TYPE_TASK$1 = "Task";
2295
+ const TYPE_GROUP$1 = "Group";
2296
+ const DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z)?$/;
2297
+ const DURATION = /^-?P(?:(\d+)W(?:(\d+)D)?|(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?$/;
2298
+ const DAY_OF_WEEK = new Set([
2299
+ "mo",
2300
+ "tu",
2301
+ "we",
2302
+ "th",
2303
+ "fr",
2304
+ "sa",
2305
+ "su"
2306
+ ]);
2307
+ const RECURRENCE_FREQUENCY = new Set([
2308
+ "yearly",
2309
+ "monthly",
2310
+ "weekly",
2311
+ "daily",
2312
+ "hourly",
2313
+ "minutely",
2314
+ "secondly"
2315
+ ]);
2316
+ const SKIP = new Set([
2317
+ "omit",
2318
+ "backward",
2319
+ "forward"
2320
+ ]);
2321
+ const ID_PATTERN = /^[A-Za-z0-9_-]+$/;
2322
+
2323
+ //#endregion
2324
+ //#region src/validate/asserts.ts
2325
+ const TYPEOF_UNDEFINED = "undefined";
2326
+ /**
2327
+ * Get the UTF-8 byte length of a string.
2328
+ * @param value Input string.
2329
+ * @return Byte length.
2330
+ */
2331
+ function utf8Length(value) {
2332
+ if (typeof TextEncoder !== TYPEOF_UNDEFINED) return new TextEncoder().encode(value).length;
2333
+ return value.length;
2334
+ }
2335
+ /**
2336
+ * Check whether a value is a plain record.
2337
+ * @param value Input value.
2338
+ * @return True if the value is a record.
2339
+ */
2340
+ function isRecord(value) {
2341
+ return !!value && isObjectValue(value) && !Array.isArray(value);
2342
+ }
2343
+ /**
2344
+ * Assert a value is a string when defined.
2345
+ * @param value Value to check.
2346
+ * @param path Validation path.
2347
+ * @return Nothing.
2348
+ */
2349
+ function assertString(value, path) {
2350
+ if (value === void 0 || value === null) return;
2351
+ if (!isStringValue(value)) fail(path, "must be a string");
2352
+ }
2353
+ /**
2354
+ * Assert a value is a non-empty string when defined.
2355
+ * @param value Value to check.
2356
+ * @param path Validation path.
2357
+ * @return Nothing.
2358
+ */
2359
+ function assertNonEmptyString(value, path) {
2360
+ if (value === void 0) return;
2361
+ if (!isStringValue(value)) fail(path, "must be a string");
2362
+ if (value.length === 0) fail(path, "must not be empty");
2363
+ }
2364
+ /**
2365
+ * Assert a value is a valid Id when defined.
2366
+ * @param value Value to check.
2367
+ * @param path Validation path.
2368
+ * @return Nothing.
2369
+ */
2370
+ function assertId(value, path) {
2371
+ if (value === void 0) return;
2372
+ if (!isStringValue(value)) fail(path, "must be an Id");
2373
+ const length = utf8Length(value);
2374
+ if (length < 1 || length > 255) fail(path, "must be between 1 and 255 octets");
2375
+ if (!ID_PATTERN.test(value)) fail(path, "must use base64url characters");
2376
+ }
2377
+ /**
2378
+ * Assert a value is boolean when defined.
2379
+ * @param value Value to check.
2380
+ * @param path Validation path.
2381
+ * @return Nothing.
2382
+ */
2383
+ function assertBoolean(value, path) {
2384
+ if (value === void 0 || value === null) return;
2385
+ if (!isBooleanValue(value)) fail(path, "must be a boolean");
2386
+ }
2387
+ /**
2388
+ * Assert a value is an integer when defined.
2389
+ * @param value Value to check.
2390
+ * @param path Validation path.
2391
+ * @return Nothing.
2392
+ */
2393
+ function assertInteger(value, path) {
2394
+ if (value === void 0 || value === null) return;
2395
+ if (!isNumberValue(value) || !Number.isInteger(value)) fail(path, "must be an integer");
2396
+ }
2397
+ /**
2398
+ * Assert a value is a non-negative integer when defined.
2399
+ * @param value Value to check.
2400
+ * @param path Validation path.
2401
+ * @return Nothing.
2402
+ */
2403
+ function assertUnsignedInt(value, path) {
2404
+ if (value === void 0 || value === null) return;
2405
+ if (!isNumberValue(value) || !Number.isInteger(value) || value < 0) fail(path, "must be a non-negative integer");
2406
+ }
2407
+ /**
2408
+ * Assert a date-time string matches the expected format.
2409
+ * @param value Date-time string.
2410
+ * @param path Validation path.
2411
+ * @param requireZ Whether a Z suffix is required.
2412
+ * @return Nothing.
2413
+ */
2414
+ function assertDateTime(value, path, requireZ) {
2415
+ if (value === void 0) return;
2416
+ if (!isStringValue(value)) fail(path, "must be a date-time string");
2417
+ const match = value.match(DATE_TIME);
2418
+ if (!match) fail(path, requireZ ? "must be a UTCDateTime (YYYY-MM-DDTHH:mm:ssZ)" : "must be a LocalDateTime (YYYY-MM-DDTHH:mm:ss)");
2419
+ const [, year, month, day, hour, minute, second, fraction, zFlag] = match;
2420
+ if (requireZ && zFlag !== Z_SUFFIX) fail(path, "must use Z suffix");
2421
+ if (!requireZ && zFlag) fail(path, "must not include time zone offset");
2422
+ const monthNum = Number.parseInt(month !== null && month !== void 0 ? month : "0", 10);
2423
+ const dayNum = Number.parseInt(day !== null && day !== void 0 ? day : "0", 10);
2424
+ const hourNum = Number.parseInt(hour !== null && hour !== void 0 ? hour : "0", 10);
2425
+ const minuteNum = Number.parseInt(minute !== null && minute !== void 0 ? minute : "0", 10);
2426
+ const secondNum = Number.parseInt(second !== null && second !== void 0 ? second : "0", 10);
2427
+ if (monthNum < 1 || monthNum > 12) fail(path, "month must be 01-12");
2428
+ if (dayNum < 1 || dayNum > 31) fail(path, "day must be 01-31");
2429
+ if (hourNum < 0 || hourNum > 23) fail(path, "hour must be 00-23");
2430
+ if (minuteNum < 0 || minuteNum > 59) fail(path, "minute must be 00-59");
2431
+ if (secondNum < 0 || secondNum > 59) fail(path, "second must be 00-59");
2432
+ if (fraction !== void 0) {
2433
+ if (fraction.length > 9) fail(path, "fractional seconds must be 1-9 digits");
2434
+ if (/^0+$/.test(fraction)) fail(path, "fractional seconds must be non-zero");
2435
+ if (fraction.endsWith("0")) fail(path, "fractional seconds must not have trailing zeros");
2436
+ }
2437
+ }
2438
+ /**
2439
+ * Assert local date time when provided.
2440
+ * @param value LocalDateTime string.
2441
+ * @param path Validation path.
2442
+ * @return Nothing.
2443
+ */
2444
+ function assertLocalDateTime(value, path) {
2445
+ return assertDateTime(value, path, false);
2446
+ }
2447
+ /**
2448
+ * Assert utc date time when provided.
2449
+ * @param value UTCDateTime string.
2450
+ * @param path Validation path.
2451
+ * @return Nothing.
2452
+ */
2453
+ function assertUtcDateTime(value, path) {
2454
+ return assertDateTime(value, path, true);
2455
+ }
2456
+ /**
2457
+ * Assert duration like when provided.
2458
+ * @param value Duration string.
2459
+ * @param path Validation path.
2460
+ * @param signed Whether negative values are allowed.
2461
+ * @return Nothing.
2462
+ */
2463
+ function assertDurationLike(value, path, signed) {
2464
+ if (value === void 0) return;
2465
+ if (!isStringValue(value)) fail(path, "must be a duration string");
2466
+ if (!signed && value.startsWith("-")) fail(path, "must not be negative");
2467
+ const match = value.match(DURATION);
2468
+ if (!match) fail(path, "must be an ISO 8601 duration");
2469
+ const week = match[1];
2470
+ const dayFromWeek = match[2];
2471
+ const day = match[3];
2472
+ const hour = match[4];
2473
+ const minute = match[5];
2474
+ const second = match[6];
2475
+ const fraction = match[7];
2476
+ if (!(!!week || !!dayFromWeek || !!day) && !(!!hour || !!minute || !!second)) fail(path, "must include at least one duration component");
2477
+ if (fraction !== void 0) {
2478
+ if (fraction.length > 9) fail(path, "fractional seconds must be 1-9 digits");
2479
+ if (/^0+$/.test(fraction)) fail(path, "fractional seconds must be non-zero");
2480
+ if (fraction.endsWith("0")) fail(path, "fractional seconds must not have trailing zeros");
2481
+ }
2482
+ }
2483
+ /**
2484
+ * Assert duration when provided.
2485
+ * @param value Duration string.
2486
+ * @param path Validation path.
2487
+ * @return Nothing.
2488
+ */
2489
+ function assertDuration(value, path) {
2490
+ return assertDurationLike(value, path, false);
2491
+ }
2492
+ /**
2493
+ * Assert signed duration when provided.
2494
+ * @param value Duration string.
2495
+ * @param path Validation path.
2496
+ * @return Nothing.
2497
+ */
2498
+ function assertSignedDuration(value, path) {
2499
+ return assertDurationLike(value, path, true);
2500
+ }
2501
+ /**
2502
+ * Assert boolean map when provided.
2503
+ * @param value Boolean map object.
2504
+ * @param path Validation path.
2505
+ * @return Nothing.
2506
+ */
2507
+ function assertBooleanMap(value, path) {
2508
+ if (value === void 0) return;
2509
+ if (!isRecord(value)) fail(path, "must be a boolean map object");
2510
+ for (const [key, entry] of Object.entries(value)) if (entry !== true) fail(`${path}.${key}`, "must be true");
2511
+ }
2512
+ /**
2513
+ * Assert ID boolean map when provided.
2514
+ * @param value Boolean map object keyed by Ids.
2515
+ * @param path Validation path.
2516
+ * @return Nothing.
2517
+ */
2518
+ function assertIdBooleanMap(value, path) {
2519
+ if (value === void 0) return;
2520
+ if (!isRecord(value)) fail(path, "must be a boolean map object");
2521
+ for (const [key, entry] of Object.entries(value)) {
2522
+ assertId(key, `${path}.${key}`);
2523
+ if (entry !== true) fail(`${path}.${key}`, "must be true");
2524
+ }
2525
+ }
2526
+ /**
2527
+ * Assert media type when provided.
2528
+ * @param value Media type string.
2529
+ * @param path Validation path.
2530
+ * @return Nothing.
2531
+ */
2532
+ function assertMediaType(value, path) {
2533
+ if (value === void 0) return;
2534
+ if (!isStringValue(value)) fail(path, "must be a media type string");
2535
+ const [typePart, ...params] = value.split(";");
2536
+ const mediaType = (typePart !== null && typePart !== void 0 ? typePart : "").trim();
2537
+ if (!/^[a-zA-Z0-9!#$&^_.+-]+\/[a-zA-Z0-9!#$&^_.+-]+$/.test(mediaType)) fail(path, "must be a valid media type");
2538
+ for (const param of params) {
2539
+ const [rawKey, rawValue] = param.split("=");
2540
+ if (!rawKey || !rawValue) continue;
2541
+ const key = rawKey.trim().toLowerCase();
2542
+ const valuePart = rawValue.trim().toLowerCase();
2543
+ if (key === CHARSET_KEY && valuePart !== UTF8) fail(path, "charset parameter must be utf-8");
2544
+ }
2545
+ }
2546
+ /**
2547
+ * Assert text content type when provided.
2548
+ * @param value Media type string.
2549
+ * @param path Validation path.
2550
+ * @return Nothing.
2551
+ */
2552
+ function assertTextContentType(value, path) {
2553
+ if (value === void 0) return;
2554
+ if (!isStringValue(value)) fail(path, "must be a media type string");
2555
+ const [typePart] = value.split(";");
2556
+ if (!(typePart !== null && typePart !== void 0 ? typePart : "").trim().startsWith("text/")) fail(path, "must be a text/* media type");
2557
+ assertMediaType(value, path);
2558
+ }
2559
+ /**
2560
+ * Assert content ID when provided.
2561
+ * @param value Content-ID string.
2562
+ * @param path Validation path.
2563
+ * @return Nothing.
2564
+ */
2565
+ function assertContentId(value, path) {
2566
+ if (value === void 0) return;
2567
+ if (!isStringValue(value)) fail(path, "must be a content-id");
2568
+ const trimmed = value.trim();
2569
+ const hasBrackets = trimmed.startsWith("<") || trimmed.endsWith(">");
2570
+ if (hasBrackets && !(trimmed.startsWith("<") && trimmed.endsWith(">"))) fail(path, "must use matching angle brackets");
2571
+ const raw = hasBrackets ? trimmed.slice(1, -1) : trimmed;
2572
+ if (!/^[^\s<>@]+@[^\s<>@]+$/.test(raw)) fail(path, "must be a content-id");
2573
+ }
2574
+ /**
2575
+ * Assert time zone when provided.
2576
+ * @param value Time zone ID.
2577
+ * @param path Validation path.
2578
+ * @return Nothing.
2579
+ */
2580
+ function assertTimeZone(value, path) {
2581
+ if (value === void 0 || value === null) return;
2582
+ if (!isStringValue(value)) fail(path, "must be a time zone ID");
2583
+ for (const tz of TimeZones) if (tz === value) return;
2584
+ fail(path, "must be a supported time zone ID");
2585
+ }
2586
+ /**
2587
+ * Assert json value when provided.
2588
+ * @param value JSON value.
2589
+ * @param path Validation path.
2590
+ * @return Nothing.
2591
+ */
2592
+ function assertJsonValue(value, path) {
2593
+ if (value === void 0) fail(path, "must be a JSON value");
2594
+ if (value === null) return;
2595
+ if (isStringValue(value) || isNumberValue(value) || isBooleanValue(value)) return;
2596
+ if (Array.isArray(value)) {
2597
+ value.forEach((entry, index) => assertJsonValue(entry, `${path}[${index}]`));
2598
+ return;
2599
+ }
2600
+ if (isObjectValue(value)) {
2601
+ if (!isRecord(value)) fail(path, "must be a JSON object");
2602
+ for (const [key, entry] of Object.entries(value)) assertJsonValue(entry, `${path}.${key}`);
2603
+ return;
2604
+ }
2605
+ fail(path, "must be a JSON value");
2606
+ }
2607
+ /**
2608
+ * Assert patch object when provided.
2609
+ * @param value PatchObject.
2610
+ * @param path Validation path.
2611
+ * @return Nothing.
2612
+ */
2613
+ function assertPatchObject(value, path) {
2614
+ if (value === void 0) return;
2615
+ if (!isRecord(value)) fail(path, "must be a PatchObject");
2616
+ for (const [key, entry] of Object.entries(value)) {
2617
+ if (entry === null) continue;
2618
+ assertJsonValue(entry, `${path}.${key}`);
2619
+ }
2620
+ }
2621
+
2622
+ //#endregion
2623
+ //#region src/validate/validators-recurrence.ts
2624
+ /**
2625
+ * Validate n day structure.
2626
+ * @param value NDay object to validate.
2627
+ * @param path JSON pointer path for error messages.
2628
+ * @return Nothing.
2629
+ */
2630
+ function validateNDay(value, path) {
2631
+ if (value["@type"] !== TYPE_NDAY$1) fail(path, "must have @type NDay");
2632
+ if (!DAY_OF_WEEK.has(value.day)) fail(`${path}.day`, "must be a valid day of week");
2633
+ assertInteger(value.nthOfPeriod, `${path}.nthOfPeriod`);
2634
+ }
2635
+ /**
2636
+ * Validate recurrence rule structure.
2637
+ * @param value RecurrenceRule object to validate.
2638
+ * @param path JSON pointer path for error messages.
2639
+ * @return Nothing.
2640
+ */
2641
+ function validateRecurrenceRule(value, path) {
2642
+ if (value["@type"] !== TYPE_RECURRENCE_RULE$1) fail(path, "must have @type RecurrenceRule");
2643
+ if (!RECURRENCE_FREQUENCY.has(value.frequency)) fail(`${path}.frequency`, "must be a valid frequency");
2644
+ assertUnsignedInt(value.interval, `${path}.interval`);
2645
+ assertUnsignedInt(value.count, `${path}.count`);
2646
+ if (value.rscale !== void 0 && value.rscale !== RSCALE_GREGORIAN) fail(`${path}.rscale`, "only gregorian is supported");
2647
+ if (value.skip !== void 0 && !SKIP.has(value.skip)) fail(`${path}.skip`, "must be omit, backward, or forward");
2648
+ if (value.firstDayOfWeek !== void 0 && !DAY_OF_WEEK.has(value.firstDayOfWeek)) fail(`${path}.firstDayOfWeek`, "must be a valid day of week");
2649
+ if (value.byDay) for (let i = 0; i < value.byDay.length; i += 1) {
2650
+ const entry = value.byDay[i];
2651
+ if (!entry) continue;
2652
+ validateNDay(entry, `${path}.byDay[${i}]`);
2653
+ }
2654
+ if (value.byMonthDay) for (let i = 0; i < value.byMonthDay.length; i += 1) {
2655
+ const entry = value.byMonthDay[i];
2656
+ if (!isNumberValue(entry) || !Number.isInteger(entry) || entry === 0 || entry < -31 || entry > 31) fail(`${path}.byMonthDay[${i}]`, "must be an integer between -31 and 31, excluding 0");
2657
+ }
2658
+ if (value.byMonth) for (let i = 0; i < value.byMonth.length; i += 1) {
2659
+ const entry = value.byMonth[i];
2660
+ if (!isStringValue(entry)) fail(`${path}.byMonth[${i}]`, "must be a string month");
2661
+ const numeric = Number.parseInt(entry, 10);
2662
+ if (!Number.isInteger(numeric) || numeric < 1 || numeric > 12) fail(`${path}.byMonth[${i}]`, "must be a month number between 1 and 12");
2663
+ }
2664
+ if (value.byYearDay) for (let i = 0; i < value.byYearDay.length; i += 1) {
2665
+ const entry = value.byYearDay[i];
2666
+ if (!isNumberValue(entry) || !Number.isInteger(entry) || entry === 0 || entry < -366 || entry > 366) fail(`${path}.byYearDay[${i}]`, "must be an integer between -366 and 366, excluding 0");
2667
+ }
2668
+ if (value.byWeekNo) for (let i = 0; i < value.byWeekNo.length; i += 1) {
2669
+ const entry = value.byWeekNo[i];
2670
+ if (!isNumberValue(entry) || !Number.isInteger(entry) || entry === 0 || entry < -53 || entry > 53) fail(`${path}.byWeekNo[${i}]`, "must be an integer between -53 and 53, excluding 0");
2671
+ }
2672
+ if (value.byHour) for (let i = 0; i < value.byHour.length; i += 1) {
2673
+ const entry = value.byHour[i];
2674
+ if (!isNumberValue(entry) || !Number.isInteger(entry) || entry < 0 || entry > 23) fail(`${path}.byHour[${i}]`, "must be an integer between 0 and 23");
2675
+ }
2676
+ if (value.byMinute) for (let i = 0; i < value.byMinute.length; i += 1) {
2677
+ const entry = value.byMinute[i];
2678
+ if (!isNumberValue(entry) || !Number.isInteger(entry) || entry < 0 || entry > 59) fail(`${path}.byMinute[${i}]`, "must be an integer between 0 and 59");
2679
+ }
2680
+ if (value.bySecond) for (let i = 0; i < value.bySecond.length; i += 1) {
2681
+ const entry = value.bySecond[i];
2682
+ if (!isNumberValue(entry) || !Number.isInteger(entry) || entry < 0 || entry > 59) fail(`${path}.bySecond[${i}]`, "must be an integer between 0 and 59");
2683
+ }
2684
+ if (value.bySetPosition) for (let i = 0; i < value.bySetPosition.length; i += 1) {
2685
+ const entry = value.bySetPosition[i];
2686
+ if (!isNumberValue(entry) || !Number.isInteger(entry) || entry === 0) fail(`${path}.bySetPosition[${i}]`, "must be a non-zero integer");
2687
+ }
2688
+ assertLocalDateTime(value.until, `${path}.until`);
2689
+ }
2690
+
2691
+ //#endregion
2692
+ //#region src/validate/validators-common.ts
2693
+ /**
2694
+ * Validate alert structure.
2695
+ * @param value Alert object to validate.
2696
+ * @param path JSON pointer path for error messages.
2697
+ * @return Nothing.
2698
+ */
2699
+ function validateAlert(value, path) {
2700
+ if (value["@type"] !== TYPE_ALERT$1) fail(path, "must have @type Alert");
2701
+ if (!value.trigger) fail(`${path}.trigger`, "is required");
2702
+ if (value.trigger["@type"] === TYPE_OFFSET_TRIGGER$1) {
2703
+ const offset = value.trigger.offset;
2704
+ if (!isStringValue(offset)) fail(`${path}.trigger.offset`, "must be a duration string");
2705
+ assertSignedDuration(offset, `${path}.trigger.offset`);
2706
+ } else if (value.trigger["@type"] === TYPE_ABSOLUTE_TRIGGER$1) {
2707
+ const when = value.trigger.when;
2708
+ if (!isStringValue(when)) fail(`${path}.trigger.when`, "must be a UTCDateTime string");
2709
+ assertUtcDateTime(when, `${path}.trigger.when`);
2710
+ }
2711
+ assertUtcDateTime(value.acknowledged, `${path}.acknowledged`);
2712
+ assertString(value.action, `${path}.action`);
2713
+ if (value.relatedTo) {
2714
+ if (!isObjectValue(value.relatedTo) || value.relatedTo === null || Array.isArray(value.relatedTo)) fail(`${path}.relatedTo`, "must be an object");
2715
+ for (const [key, entry] of Object.entries(value.relatedTo)) {
2716
+ if (!entry || !isObjectValue(entry)) fail(`${path}.relatedTo.${key}`, "must be a relation object");
2717
+ validateRelation(entry, `${path}.relatedTo.${key}`);
2718
+ }
2719
+ }
2720
+ }
2721
+ /**
2722
+ * Validate relation structure.
2723
+ * @param value Relation object to validate.
2724
+ * @param path JSON pointer path for error messages.
2725
+ * @return Nothing.
2726
+ */
2727
+ function validateRelation(value, path) {
2728
+ if (value["@type"] !== TYPE_RELATION$1) fail(path, "must have @type Relation");
2729
+ if (value.relation) assertBooleanMap(value.relation, `${path}.relation`);
2730
+ }
2731
+ /**
2732
+ * Validate link structure.
2733
+ * @param value Link object to validate.
2734
+ * @param path JSON pointer path for error messages.
2735
+ * @return Nothing.
2736
+ */
2737
+ function validateLink(value, path) {
2738
+ if (value["@type"] !== TYPE_LINK$1) fail(path, "must have @type Link");
2739
+ assertString(value.href, `${path}.href`);
2740
+ if (!value.href) fail(`${path}.href`, "is required");
2741
+ assertContentId(value.cid, `${path}.cid`);
2742
+ assertMediaType(value.contentType, `${path}.contentType`);
2743
+ assertUnsignedInt(value.size, `${path}.size`);
2744
+ assertString(value.rel, `${path}.rel`);
2745
+ assertString(value.display, `${path}.display`);
2746
+ assertString(value.title, `${path}.title`);
2747
+ }
2748
+ /**
2749
+ * Validate location structure.
2750
+ * @param value Location object to validate.
2751
+ * @param path JSON pointer path for error messages.
2752
+ * @return Nothing.
2753
+ */
2754
+ function validateLocation(value, path) {
2755
+ if (value["@type"] !== TYPE_LOCATION$1) fail(path, "must have @type Location");
2756
+ assertId(value.relativeTo, `${path}.relativeTo`);
2757
+ assertString(value.name, `${path}.name`);
2758
+ assertString(value.description, `${path}.description`);
2759
+ if (value.locationTypes) assertBooleanMap(value.locationTypes, `${path}.locationTypes`);
2760
+ assertString(value.relativeTo, `${path}.relativeTo`);
2761
+ assertTimeZone(value.timeZone, `${path}.timeZone`);
2762
+ assertString(value.coordinates, `${path}.coordinates`);
2763
+ if (value.links) {
2764
+ if (!isObjectValue(value.links) || value.links === null || Array.isArray(value.links)) fail(`${path}.links`, "must be an object");
2765
+ for (const [key, entry] of Object.entries(value.links)) {
2766
+ assertId(key, `${path}.links.${key}`);
2767
+ if (!entry || !isObjectValue(entry)) fail(`${path}.links.${key}`, "must be a link object");
2768
+ validateLink(entry, `${path}.links.${key}`);
2769
+ }
2770
+ }
2771
+ }
2772
+ /**
2773
+ * Validate virtual location structure.
2774
+ * @param value VirtualLocation object to validate.
2775
+ * @param path JSON pointer path for error messages.
2776
+ * @return Nothing.
2777
+ */
2778
+ function validateVirtualLocation(value, path) {
2779
+ if (value["@type"] !== TYPE_VIRTUAL_LOCATION$1) fail(path, "must have @type VirtualLocation");
2780
+ assertString(value.name, `${path}.name`);
2781
+ assertString(value.description, `${path}.description`);
2782
+ assertString(value.uri, `${path}.uri`);
2783
+ if (!value.uri) fail(`${path}.uri`, "is required");
2784
+ if (value.features) assertBooleanMap(value.features, `${path}.features`);
2785
+ }
2786
+ /**
2787
+ * Validate time zone rule structure.
2788
+ * @param value TimeZoneRule object to validate.
2789
+ * @param path JSON pointer path for error messages.
2790
+ * @return Nothing.
2791
+ */
2792
+ function validateTimeZoneRule(value, path) {
2793
+ if (value["@type"] !== TYPE_TIME_ZONE_RULE$1) fail(path, "must have @type TimeZoneRule");
2794
+ assertLocalDateTime(value.start, `${path}.start`);
2795
+ if (!value.start) fail(`${path}.start`, "is required");
2796
+ assertString(value.offsetFrom, `${path}.offsetFrom`);
2797
+ if (!value.offsetFrom) fail(`${path}.offsetFrom`, "is required");
2798
+ assertString(value.offsetTo, `${path}.offsetTo`);
2799
+ if (!value.offsetTo) fail(`${path}.offsetTo`, "is required");
2800
+ if (value.recurrenceRules) value.recurrenceRules.forEach((rule, index) => validateRecurrenceRule(rule, `${path}.recurrenceRules[${index}]`));
2801
+ if (value.comments) value.comments.forEach((entry, index) => assertString(entry, `${path}.comments[${index}]`));
2802
+ }
2803
+ /**
2804
+ * Validate time zone object structure.
2805
+ * @param value TimeZone object to validate.
2806
+ * @param path JSON pointer path for error messages.
2807
+ * @return Nothing.
2808
+ */
2809
+ function validateTimeZoneObject(value, path) {
2810
+ if (value["@type"] !== TYPE_TIME_ZONE$1) fail(path, "must have @type TimeZone");
2811
+ assertTimeZone(value.tzId, `${path}.tzId`);
2812
+ if (!value.tzId) fail(`${path}.tzId`, "is required");
2813
+ assertUtcDateTime(value.updated, `${path}.updated`);
2814
+ assertString(value.url, `${path}.url`);
2815
+ assertUtcDateTime(value.validUntil, `${path}.validUntil`);
2816
+ if (value.aliases) assertBooleanMap(value.aliases, `${path}.aliases`);
2817
+ if (value.standard) value.standard.forEach((rule, index) => validateTimeZoneRule(rule, `${path}.standard[${index}]`));
2818
+ if (value.daylight) value.daylight.forEach((rule, index) => validateTimeZoneRule(rule, `${path}.daylight[${index}]`));
2819
+ }
2820
+ /**
2821
+ * Validate participant structure.
2822
+ * @param value Participant object to validate.
2823
+ * @param path JSON pointer path for error messages.
2824
+ * @return Nothing.
2825
+ */
2826
+ function validateParticipant(value, path) {
2827
+ if (value["@type"] !== TYPE_PARTICIPANT$1) fail(path, "must have @type Participant");
2828
+ assertString(value.name, `${path}.name`);
2829
+ assertString(value.email, `${path}.email`);
2830
+ assertString(value.description, `${path}.description`);
2831
+ if (value.sendTo) {
2832
+ if (!isObjectValue(value.sendTo) || value.sendTo === null || Array.isArray(value.sendTo)) fail(`${path}.sendTo`, "must be an object");
2833
+ for (const [key, entry] of Object.entries(value.sendTo)) {
2834
+ assertString(key, `${path}.sendTo.${key}`);
2835
+ assertString(entry, `${path}.sendTo.${key}`);
2836
+ }
2837
+ }
2838
+ assertString(value.kind, `${path}.kind`);
2839
+ if (!value.roles) fail(`${path}.roles`, "is required");
2840
+ assertBooleanMap(value.roles, `${path}.roles`);
2841
+ if (Object.keys(value.roles).length === 0) fail(`${path}.roles`, "must include at least one role");
2842
+ assertId(value.locationId, `${path}.locationId`);
2843
+ assertString(value.language, `${path}.language`);
2844
+ assertString(value.participationStatus, `${path}.participationStatus`);
2845
+ assertString(value.participationComment, `${path}.participationComment`);
2846
+ assertBoolean(value.expectReply, `${path}.expectReply`);
2847
+ assertString(value.scheduleAgent, `${path}.scheduleAgent`);
2848
+ assertBoolean(value.scheduleForceSend, `${path}.scheduleForceSend`);
2849
+ assertUnsignedInt(value.scheduleSequence, `${path}.scheduleSequence`);
2850
+ if (value.scheduleStatus) value.scheduleStatus.forEach((entry, index) => assertString(entry, `${path}.scheduleStatus[${index}]`));
2851
+ assertUtcDateTime(value.scheduleUpdated, `${path}.scheduleUpdated`);
2852
+ assertString(value.sentBy, `${path}.sentBy`);
2853
+ assertId(value.invitedBy, `${path}.invitedBy`);
2854
+ if (value.delegatedTo) assertIdBooleanMap(value.delegatedTo, `${path}.delegatedTo`);
2855
+ if (value.delegatedFrom) assertIdBooleanMap(value.delegatedFrom, `${path}.delegatedFrom`);
2856
+ if (value.memberOf) assertIdBooleanMap(value.memberOf, `${path}.memberOf`);
2857
+ if (value.links) {
2858
+ if (!isObjectValue(value.links) || value.links === null || Array.isArray(value.links)) fail(`${path}.links`, "must be an object");
2859
+ for (const [key, entry] of Object.entries(value.links)) {
2860
+ assertId(key, `${path}.links.${key}`);
2861
+ if (!entry || !isObjectValue(entry)) fail(`${path}.links.${key}`, "must be a link object");
2862
+ validateLink(entry, `${path}.links.${key}`);
2863
+ }
2864
+ }
2865
+ assertString(value.progress, `${path}.progress`);
2866
+ assertUtcDateTime(value.progressUpdated, `${path}.progressUpdated`);
2867
+ assertUnsignedInt(value.percentComplete, `${path}.percentComplete`);
2868
+ }
2869
+ /**
2870
+ * Validate common structure.
2871
+ * @param value JSCalendar object to validate.
2872
+ * @param path JSON pointer path for error messages.
2873
+ * @return Nothing.
2874
+ */
2875
+ function validateCommon(value, path) {
2876
+ assertNonEmptyString(value.uid, `${path}.uid`);
2877
+ if (!value.uid) fail(`${path}.uid`, "is required");
2878
+ assertUtcDateTime(value.updated, `${path}.updated`);
2879
+ assertUtcDateTime(value.created, `${path}.created`);
2880
+ assertUnsignedInt(value.sequence, `${path}.sequence`);
2881
+ assertString(value.method, `${path}.method`);
2882
+ if (value.method && value.method !== value.method.toLowerCase()) fail(`${path}.method`, "must be lowercase");
2883
+ assertString(value.title, `${path}.title`);
2884
+ assertString(value.description, `${path}.description`);
2885
+ assertTextContentType(value.descriptionContentType, `${path}.descriptionContentType`);
2886
+ assertBoolean(value.showWithoutTime, `${path}.showWithoutTime`);
2887
+ if (value.relatedTo) {
2888
+ if (!isObjectValue(value.relatedTo) || value.relatedTo === null || Array.isArray(value.relatedTo)) fail(`${path}.relatedTo`, "must be an object");
2889
+ for (const [key, entry] of Object.entries(value.relatedTo)) {
2890
+ assertNonEmptyString(key, `${path}.relatedTo.${key}`);
2891
+ if (!entry || !isObjectValue(entry)) fail(`${path}.relatedTo.${key}`, "must be a relation object");
2892
+ validateRelation(entry, `${path}.relatedTo.${key}`);
2893
+ }
2894
+ }
2895
+ if (value.keywords) assertBooleanMap(value.keywords, `${path}.keywords`);
2896
+ if (value.categories) assertBooleanMap(value.categories, `${path}.categories`);
2897
+ assertString(value.color, `${path}.color`);
2898
+ assertLocalDateTime(value.recurrenceId, `${path}.recurrenceId`);
2899
+ assertTimeZone(value.recurrenceIdTimeZone, `${path}.recurrenceIdTimeZone`);
2900
+ if (value.recurrenceRules) value.recurrenceRules.forEach((rule, index) => validateRecurrenceRule(rule, `${path}.recurrenceRules[${index}]`));
2901
+ if (value.excludedRecurrenceRules) value.excludedRecurrenceRules.forEach((rule, index) => validateRecurrenceRule(rule, `${path}.excludedRecurrenceRules[${index}]`));
2902
+ if (value.recurrenceOverrides) {
2903
+ if (!isObjectValue(value.recurrenceOverrides) || value.recurrenceOverrides === null || Array.isArray(value.recurrenceOverrides)) fail(`${path}.recurrenceOverrides`, "must be an object");
2904
+ for (const [key, entry] of Object.entries(value.recurrenceOverrides)) {
2905
+ assertLocalDateTime(key, `${path}.recurrenceOverrides.${key}`);
2906
+ assertPatchObject(entry, `${path}.recurrenceOverrides.${key}`);
2907
+ }
2908
+ }
2909
+ assertBoolean(value.excluded, `${path}.excluded`);
2910
+ assertInteger(value.priority, `${path}.priority`);
2911
+ assertString(value.freeBusyStatus, `${path}.freeBusyStatus`);
2912
+ assertString(value.privacy, `${path}.privacy`);
2913
+ if (value.replyTo) {
2914
+ if (!isObjectValue(value.replyTo) || value.replyTo === null || Array.isArray(value.replyTo)) fail(`${path}.replyTo`, "must be an object");
2915
+ for (const [key, entry] of Object.entries(value.replyTo)) assertString(entry, `${path}.replyTo.${key}`);
2916
+ }
2917
+ assertString(value.sentBy, `${path}.sentBy`);
2918
+ if (value.locations) {
2919
+ if (!isObjectValue(value.locations) || value.locations === null || Array.isArray(value.locations)) fail(`${path}.locations`, "must be an object");
2920
+ for (const [key, entry] of Object.entries(value.locations)) {
2921
+ assertId(key, `${path}.locations.${key}`);
2922
+ if (!entry || !isObjectValue(entry)) fail(`${path}.locations.${key}`, "must be a location object");
2923
+ validateLocation(entry, `${path}.locations.${key}`);
2924
+ }
2925
+ }
2926
+ if (value.virtualLocations) {
2927
+ if (!isObjectValue(value.virtualLocations) || value.virtualLocations === null || Array.isArray(value.virtualLocations)) fail(`${path}.virtualLocations`, "must be an object");
2928
+ for (const [key, entry] of Object.entries(value.virtualLocations)) {
2929
+ assertId(key, `${path}.virtualLocations.${key}`);
2930
+ if (!entry || !isObjectValue(entry)) fail(`${path}.virtualLocations.${key}`, "must be a virtual location object");
2931
+ validateVirtualLocation(entry, `${path}.virtualLocations.${key}`);
2932
+ }
2933
+ }
2934
+ if (value.links) {
2935
+ if (!isObjectValue(value.links) || value.links === null || Array.isArray(value.links)) fail(`${path}.links`, "must be an object");
2936
+ for (const [key, entry] of Object.entries(value.links)) {
2937
+ assertId(key, `${path}.links.${key}`);
2938
+ if (!entry || !isObjectValue(entry)) fail(`${path}.links.${key}`, "must be a link object");
2939
+ validateLink(entry, `${path}.links.${key}`);
2940
+ }
2941
+ }
2942
+ if (value.participants) {
2943
+ if (!isObjectValue(value.participants) || value.participants === null || Array.isArray(value.participants)) fail(`${path}.participants`, "must be an object");
2944
+ for (const [key, entry] of Object.entries(value.participants)) {
2945
+ assertId(key, `${path}.participants.${key}`);
2946
+ if (!entry || !isObjectValue(entry)) fail(`${path}.participants.${key}`, "must be a participant object");
2947
+ validateParticipant(entry, `${path}.participants.${key}`);
2948
+ }
2949
+ }
2950
+ assertString(value.requestStatus, `${path}.requestStatus`);
2951
+ assertBoolean(value.useDefaultAlerts, `${path}.useDefaultAlerts`);
2952
+ if (value.alerts) {
2953
+ if (!isObjectValue(value.alerts) || value.alerts === null || Array.isArray(value.alerts)) fail(`${path}.alerts`, "must be an object");
2954
+ for (const [key, entry] of Object.entries(value.alerts)) {
2955
+ assertId(key, `${path}.alerts.${key}`);
2956
+ if (!entry || !isObjectValue(entry)) fail(`${path}.alerts.${key}`, "must be an alert object");
2957
+ validateAlert(entry, `${path}.alerts.${key}`);
2958
+ }
2959
+ }
2960
+ if (value.localizations) {
2961
+ if (!isRecord(value.localizations)) fail(`${path}.localizations`, "must be an object");
2962
+ for (const [key, entry] of Object.entries(value.localizations)) assertPatchObject(entry, `${path}.localizations.${key}`);
2963
+ }
2964
+ assertTimeZone(value.timeZone, `${path}.timeZone`);
2965
+ if (value.timeZones) {
2966
+ const timeZones = value.timeZones;
2967
+ if (!isObjectValue(timeZones) || timeZones === null || Array.isArray(timeZones)) fail(`${path}.timeZones`, "must be an object");
2968
+ for (const [key] of Object.entries(timeZones)) assertTimeZone(key, `${path}.timeZones.${key}`);
2969
+ for (const [key, entry] of Object.entries(timeZones)) {
2970
+ if (!entry || !isObjectValue(entry)) fail(`${path}.timeZones.${key}`, "must be a time zone object");
2971
+ validateTimeZoneObject(entry, `${path}.timeZones.${key}`);
2972
+ }
2973
+ }
2974
+ }
2975
+
2976
+ //#endregion
2977
+ //#region src/validate/validators-objects.ts
2978
+ /**
2979
+ * Validate event structure.
2980
+ * @param value Event object to validate.
2981
+ * @param path JSON pointer path for error messages.
2982
+ * @return Nothing.
2983
+ */
2984
+ function validateEvent(value, path) {
2985
+ if (value["@type"] !== TYPE_EVENT$1) fail(path, "must have @type Event");
2986
+ validateCommon(value, path);
2987
+ assertLocalDateTime(value.start, `${path}.start`);
2988
+ if (!value.start) fail(`${path}.start`, "is required");
2989
+ assertDuration(value.duration, `${path}.duration`);
2990
+ assertString(value.status, `${path}.status`);
2991
+ }
2992
+ /**
2993
+ * Validate task structure.
2994
+ * @param value Task object to validate.
2995
+ * @param path JSON pointer path for error messages.
2996
+ * @return Nothing.
2997
+ */
2998
+ function validateTask(value, path) {
2999
+ if (value["@type"] !== TYPE_TASK$1) fail(path, "must have @type Task");
3000
+ validateCommon(value, path);
3001
+ assertLocalDateTime(value.start, `${path}.start`);
3002
+ assertLocalDateTime(value.due, `${path}.due`);
3003
+ assertDuration(value.estimatedDuration, `${path}.estimatedDuration`);
3004
+ assertUnsignedInt(value.percentComplete, `${path}.percentComplete`);
3005
+ assertString(value.progress, `${path}.progress`);
3006
+ assertUtcDateTime(value.progressUpdated, `${path}.progressUpdated`);
3007
+ }
3008
+ /**
3009
+ * Validate group structure.
3010
+ * @param value Group object to validate.
3011
+ * @param path JSON pointer path for error messages.
3012
+ * @return Nothing.
3013
+ */
3014
+ function validateGroup(value, path) {
3015
+ if (value["@type"] !== TYPE_GROUP$1) fail(path, "must have @type Group");
3016
+ validateCommon(value, path);
3017
+ if (!Array.isArray(value.entries)) fail(`${path}.entries`, "must be an array");
3018
+ value.entries.forEach((entry, index) => validateJsCalendarObject(entry, `${path}.entries[${index}]`));
3019
+ assertString(value.source, `${path}.source`);
3020
+ }
3021
+ /**
3022
+ * Validate js calendar object structure.
3023
+ * @param value JSCalendar object to validate.
3024
+ * @param path JSON pointer path for error messages.
3025
+ * @return Nothing.
3026
+ */
3027
+ function validateJsCalendarObject(value, path = "object") {
3028
+ if (!value || !isObjectValue(value)) fail(path, "must be an object");
3029
+ if (value["@type"] === TYPE_EVENT$1) return validateEvent(value, path);
3030
+ if (value["@type"] === TYPE_TASK$1) return validateTask(value, path);
3031
+ if (value["@type"] === TYPE_GROUP$1) return validateGroup(value, path);
3032
+ fail(`${path}["@type"]`, "must be Event, Task, or Group");
3033
+ }
3034
+
3035
+ //#endregion
3036
+ //#region src/jscal/constants.ts
3037
+ const TYPEOF_FUNCTION = "function";
3038
+ const EMPTY_STRING = "";
3039
+ const TYPE_EVENT = "Event";
3040
+ const TYPE_TASK = "Task";
3041
+ const TYPE_GROUP = "Group";
3042
+ const STATUS_COMPLETED = "completed";
3043
+ const STATUS_FAILED = "failed";
3044
+ const STATUS_IN_PROCESS = "in-process";
3045
+ const STATUS_NEEDS_ACTION = "needs-action";
3046
+
3047
+ //#endregion
3048
+ //#region src/jscal/defaults.ts
3049
+ /**
3050
+ * Apply common defaults to the target.
3051
+ * @param data JSCalendar object to mutate.
3052
+ * @return The same object with defaults applied.
3053
+ */
3054
+ function applyCommonDefaults(data) {
3055
+ if (data.sequence === void 0) data.sequence = 0;
3056
+ if (data.title === void 0) data.title = "";
3057
+ if (data.description === void 0) data.description = "";
3058
+ if (data.descriptionContentType === void 0) data.descriptionContentType = "text/plain";
3059
+ if (data.showWithoutTime === void 0) data.showWithoutTime = false;
3060
+ if (data.recurrenceIdTimeZone === void 0) data.recurrenceIdTimeZone = null;
3061
+ if (data.excluded === void 0) data.excluded = false;
3062
+ if (data.priority === void 0) data.priority = 0;
3063
+ if (data.freeBusyStatus === void 0) data.freeBusyStatus = "busy";
3064
+ if (data.privacy === void 0) data.privacy = "public";
3065
+ if (data.useDefaultAlerts === void 0) data.useDefaultAlerts = false;
3066
+ return data;
3067
+ }
3068
+ /**
3069
+ * Apply event defaults to the target.
3070
+ * @param data Event to mutate.
3071
+ * @return The same event with defaults applied.
3072
+ */
3073
+ function applyEventDefaults(data) {
3074
+ if (data.duration === void 0) data.duration = "PT0S";
3075
+ if (data.status === void 0) data.status = "confirmed";
3076
+ return data;
3077
+ }
3078
+ /**
3079
+ * Apply task defaults to the target.
3080
+ * @param data Task to mutate.
3081
+ * @return The same task with defaults applied.
3082
+ */
3083
+ function applyTaskDefaults(data) {
3084
+ if (data.progress === void 0) {
3085
+ const participants = data.participants ? Object.values(data.participants) : [];
3086
+ if (participants.length === 0) data.progress = STATUS_NEEDS_ACTION;
3087
+ else if (participants.every((p) => p.progress === STATUS_COMPLETED)) data.progress = STATUS_COMPLETED;
3088
+ else if (participants.some((p) => p.progress === STATUS_FAILED)) data.progress = STATUS_FAILED;
3089
+ else if (participants.some((p) => p.progress === STATUS_IN_PROCESS)) data.progress = STATUS_IN_PROCESS;
3090
+ else data.progress = STATUS_NEEDS_ACTION;
3091
+ }
3092
+ return data;
3093
+ }
3094
+
3095
+ //#endregion
3096
+ //#region src/jscal/duration.ts
3097
+ /**
3098
+ * Convert seconds to an ISO 8601 duration string.
3099
+ * @param totalSeconds Total seconds to encode.
3100
+ * @return ISO 8601 duration string.
3101
+ */
3102
+ function durationFromSeconds(totalSeconds) {
3103
+ const negative = totalSeconds < 0;
3104
+ const absSeconds = Math.abs(totalSeconds);
3105
+ const clamped = Math.floor(absSeconds);
3106
+ const days = Math.floor(clamped / 86400);
3107
+ let remaining = clamped % 86400;
3108
+ const hours = Math.floor(remaining / 3600);
3109
+ remaining %= 3600;
3110
+ const minutes = Math.floor(remaining / 60);
3111
+ const seconds = remaining % 60;
3112
+ const datePart = days > 0 ? `${days}D` : "";
3113
+ const timeParts = [];
3114
+ if (hours > 0) timeParts.push(`${hours}H`);
3115
+ if (minutes > 0) timeParts.push(`${minutes}M`);
3116
+ if (seconds > 0 || datePart === EMPTY_STRING && timeParts.length === 0) timeParts.push(`${seconds}S`);
3117
+ const timePart = timeParts.length > 0 ? `T${timeParts.join("")}` : "";
3118
+ return `${negative && clamped > 0 ? "-" : ""}P${datePart}${timePart}`;
3119
+ }
3120
+ const Duration = {
3121
+ seconds(value) {
3122
+ return durationFromSeconds(value);
3123
+ },
3124
+ minutes(value) {
3125
+ return durationFromSeconds(value * 60);
3126
+ },
3127
+ hours(value) {
3128
+ return durationFromSeconds(value * 3600);
3129
+ },
3130
+ days(value) {
3131
+ return durationFromSeconds(value * 86400);
3132
+ },
3133
+ from(parts) {
3134
+ var _parts$days, _parts$hours, _parts$minutes, _parts$seconds;
3135
+ return durationFromSeconds(((_parts$days = parts.days) !== null && _parts$days !== void 0 ? _parts$days : 0) * 86400 + ((_parts$hours = parts.hours) !== null && _parts$hours !== void 0 ? _parts$hours : 0) * 3600 + ((_parts$minutes = parts.minutes) !== null && _parts$minutes !== void 0 ? _parts$minutes : 0) * 60 + ((_parts$seconds = parts.seconds) !== null && _parts$seconds !== void 0 ? _parts$seconds : 0));
3136
+ }
3137
+ };
3138
+
3139
+ //#endregion
3140
+ //#region src/jscal/ids.ts
3141
+ /**
3142
+ * Get a Crypto instance if available.
3143
+ * @return Crypto instance when available, otherwise undefined.
3144
+ */
3145
+ function getCrypto() {
3146
+ const cryptoObj = globalThis.crypto;
3147
+ if (cryptoObj && typeof cryptoObj.getRandomValues === TYPEOF_FUNCTION) return cryptoObj;
3148
+ }
3149
+ /**
3150
+ * Generate random bytes using Crypto or Math.random.
3151
+ * @param length Number of bytes to generate.
3152
+ * @return Random byte array.
3153
+ */
3154
+ function getRandomBytes(length) {
3155
+ const bytes = new Uint8Array(length);
3156
+ const cryptoObj = getCrypto();
3157
+ if (cryptoObj) {
3158
+ cryptoObj.getRandomValues(bytes);
3159
+ return bytes;
3160
+ }
3161
+ for (let i = 0; i < length; i += 1) bytes[i] = Math.floor(Math.random() * 256);
3162
+ return bytes;
3163
+ }
3164
+ /**
3165
+ * Encode bytes as base64url.
3166
+ * @param bytes Byte array to encode.
3167
+ * @return Base64url string without padding.
3168
+ */
3169
+ function base64UrlEncode(bytes) {
3170
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3171
+ let output = "";
3172
+ for (let i = 0; i < bytes.length; i += 3) {
3173
+ var _bytes$i, _bytes, _bytes2, _alphabet, _alphabet2, _alphabet3, _alphabet4;
3174
+ const b0 = (_bytes$i = bytes[i]) !== null && _bytes$i !== void 0 ? _bytes$i : 0;
3175
+ const b1 = (_bytes = bytes[i + 1]) !== null && _bytes !== void 0 ? _bytes : 0;
3176
+ const b2 = (_bytes2 = bytes[i + 2]) !== null && _bytes2 !== void 0 ? _bytes2 : 0;
3177
+ const triplet = (b0 !== null && b0 !== void 0 ? b0 : 0) << 16 | (b1 !== null && b1 !== void 0 ? b1 : 0) << 8 | (b2 !== null && b2 !== void 0 ? b2 : 0);
3178
+ output += (_alphabet = alphabet[triplet >> 18 & 63]) !== null && _alphabet !== void 0 ? _alphabet : "";
3179
+ output += (_alphabet2 = alphabet[triplet >> 12 & 63]) !== null && _alphabet2 !== void 0 ? _alphabet2 : "";
3180
+ output += i + 1 < bytes.length ? (_alphabet3 = alphabet[triplet >> 6 & 63]) !== null && _alphabet3 !== void 0 ? _alphabet3 : "" : "=";
3181
+ output += i + 2 < bytes.length ? (_alphabet4 = alphabet[triplet & 63]) !== null && _alphabet4 !== void 0 ? _alphabet4 : "" : "=";
3182
+ }
3183
+ return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3184
+ }
3185
+ /**
3186
+ * Create a UUID-like identifier.
3187
+ * @return UUID string.
3188
+ */
3189
+ function createUid() {
3190
+ const cryptoObj = getCrypto();
3191
+ if (cryptoObj === null || cryptoObj === void 0 ? void 0 : cryptoObj.randomUUID) return cryptoObj.randomUUID();
3192
+ const bytes = getRandomBytes(16);
3193
+ if (bytes.length > 6) {
3194
+ var _bytes$;
3195
+ bytes[6] = ((_bytes$ = bytes[6]) !== null && _bytes$ !== void 0 ? _bytes$ : 0) & 15 | 64;
3196
+ }
3197
+ if (bytes.length > 8) {
3198
+ var _bytes$2;
3199
+ bytes[8] = ((_bytes$2 = bytes[8]) !== null && _bytes$2 !== void 0 ? _bytes$2 : 0) & 63 | 128;
3200
+ }
3201
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3202
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3203
+ }
3204
+ /**
3205
+ * Create a compact base64url identifier.
3206
+ * @return Base64url ID string.
3207
+ */
3208
+ function createId() {
3209
+ return base64UrlEncode(getRandomBytes(16));
3210
+ }
3211
+
3212
+ //#endregion
3213
+ //#region src/jscal/datetime.ts
3214
+ /**
3215
+ * Pad a number to two digits.
3216
+ * @param value Number to pad.
3217
+ * @return Zero-padded string.
3218
+ */
3219
+ function pad2(value) {
3220
+ return value.toString().padStart(2, "0");
3221
+ }
3222
+ /**
3223
+ * Convert a Date input to a LocalDateTime string.
3224
+ * @param value Date or LocalDateTime string.
3225
+ * @return LocalDateTime string.
3226
+ */
3227
+ function toLocalDateTime(value) {
3228
+ if (isStringValue(value)) return value;
3229
+ return [
3230
+ value.getFullYear().toString().padStart(4, "0"),
3231
+ "-",
3232
+ pad2(value.getMonth() + 1),
3233
+ "-",
3234
+ pad2(value.getDate()),
3235
+ "T",
3236
+ pad2(value.getHours()),
3237
+ ":",
3238
+ pad2(value.getMinutes()),
3239
+ ":",
3240
+ pad2(value.getSeconds())
3241
+ ].join("");
3242
+ }
3243
+ /**
3244
+ * Convert a Date input to a UTCDateTime string.
3245
+ * @param value Date or UTCDateTime string.
3246
+ * @return UTCDateTime string.
3247
+ */
3248
+ function toUtcDateTime(value) {
3249
+ if (isStringValue(value)) return value;
3250
+ return normalizeUtcDateTime(value.toISOString());
3251
+ }
3252
+
3253
+ //#endregion
3254
+ //#region src/jscal/base.ts
3255
+ var Base = class {
3256
+ data;
3257
+ /**
3258
+ * Create a new base instance that wraps a JSCalendar object.
3259
+ * @param data Underlying JSCalendar data.
3260
+ * @return Result.
3261
+ */
3262
+ constructor(data) {
3263
+ this.data = data;
3264
+ }
3265
+ /**
3266
+ * Return a deep-cloned plain object for safe serialization.
3267
+ * @return Cloned JSCalendar data.
3268
+ */
3269
+ eject() {
3270
+ return deepClone(this.data);
3271
+ }
3272
+ /**
3273
+ * Clone the current instance with a deep-cloned payload.
3274
+ * @return New instance with cloned data.
3275
+ */
3276
+ clone() {
3277
+ return this.wrap(deepClone(this.data));
3278
+ }
3279
+ /**
3280
+ * Read a field value from the underlying data.
3281
+ * @param key Field key.
3282
+ * @return Field value.
3283
+ */
3284
+ get(key) {
3285
+ return this.data[key];
3286
+ }
3287
+ /**
3288
+ * Set a field value and update metadata as needed.
3289
+ * @param key Field key.
3290
+ * @param value Field value.
3291
+ * @return New instance with the updated field.
3292
+ */
3293
+ set(key, value) {
3294
+ const next = deepClone(this.data);
3295
+ next[key] = value;
3296
+ const touched = this.touchKeys(next, [String(key)]);
3297
+ return this.wrap(touched);
3298
+ }
3299
+ /**
3300
+ * Apply a PatchObject and touch updated/sequence metadata.
3301
+ * @param patch Patch to apply.
3302
+ * @param options Update options.
3303
+ * @return New instance with applied patch.
3304
+ */
3305
+ patch(patch, options = {}) {
3306
+ const next = applyPatch(this.data, patch);
3307
+ if (options.validate !== false) validateJsCalendarObject(next);
3308
+ const touched = this.touchFromPatch(next, patch, options);
3309
+ return this.wrap(touched);
3310
+ }
3311
+ /**
3312
+ * Update updated/sequence metadata for modified keys.
3313
+ * @param keys Modified keys.
3314
+ * @param options Update options.
3315
+ * @return Updated instance.
3316
+ */
3317
+ touchKeys(data, keys, options = {}) {
3318
+ var _options$now;
3319
+ if (options.touch === false) return data;
3320
+ data.updated = ((_options$now = options.now) !== null && _options$now !== void 0 ? _options$now : nowUtc)();
3321
+ if (options.sequence === false) return data;
3322
+ data.sequence = (isNumberValue(data.sequence) ? data.sequence : 0) + 1;
3323
+ return data;
3324
+ }
3325
+ /**
3326
+ * Update updated/sequence metadata for PatchObject changes.
3327
+ * @param patch Patch applied to the object.
3328
+ * @param options Update options.
3329
+ * @return Updated instance.
3330
+ */
3331
+ touchFromPatch(data, patch, options = {}) {
3332
+ var _options$now2;
3333
+ if (options.touch === false) return data;
3334
+ data.updated = ((_options$now2 = options.now) !== null && _options$now2 !== void 0 ? _options$now2 : nowUtc)();
3335
+ if (options.sequence === false) return data;
3336
+ data.sequence = (isNumberValue(data.sequence) ? data.sequence : 0) + 1;
3337
+ return data;
3338
+ }
3339
+ };
3340
+
3341
+ //#endregion
3342
+ //#region src/jscal/event.ts
3343
+ var EventObject = class EventObject extends Base {
3344
+ /**
3345
+ * Wrap updated data in a new EventObject.
3346
+ * @param data Updated event data.
3347
+ * @return New EventObject instance.
3348
+ */
3349
+ wrap(data) {
3350
+ const { "@type": _type, ...rest } = data;
3351
+ return new EventObject(rest, { validate: false });
3352
+ }
3353
+ /**
3354
+ * Create an event with normalized dates, duration, and RFC defaults.
3355
+ * @param input Event input values to normalize.
3356
+ * @param options Creation options (validation, clock).
3357
+ * @return EventObject instance.
3358
+ */
3359
+ constructor(input, options = {}) {
3360
+ var _options$now, _input$uid;
3361
+ if (!isStringValue(input.start) && !(input.start instanceof Date)) throw new Error("Event.start is required");
3362
+ if (isStringValue(input.start) && input.start.length === 0) throw new Error("Event.start is required");
3363
+ const now = (_options$now = options.now) !== null && _options$now !== void 0 ? _options$now : nowUtc;
3364
+ const { start: rawStart, duration: rawDuration, updated: rawUpdated, created: rawCreated, timeZone: rawTimeZone, ...rest } = input;
3365
+ const start = toLocalDateTime(rawStart);
3366
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
3367
+ let timeZone;
3368
+ if (rawTimeZone === null) timeZone = null;
3369
+ else if (rawTimeZone) timeZone = resolveTimeZone(rawTimeZone);
3370
+ const data = {
3371
+ ...rest,
3372
+ "@type": "Event",
3373
+ start,
3374
+ uid: (_input$uid = input.uid) !== null && _input$uid !== void 0 ? _input$uid : createUid(),
3375
+ updated
3376
+ };
3377
+ if (timeZone !== void 0) data.timeZone = timeZone;
3378
+ if (rawDuration !== void 0) data.duration = isNumberValue(rawDuration) ? durationFromSeconds(Math.max(0, rawDuration)) : rawDuration;
3379
+ if (rawCreated) data.created = toUtcDateTime(rawCreated);
3380
+ else data.created = updated;
3381
+ applyCommonDefaults(data);
3382
+ applyEventDefaults(data);
3383
+ if (options.validate !== false) validateJsCalendarObject(data);
3384
+ super(data);
3385
+ }
3386
+ /**
3387
+ * Clone the event as a new EventObject instance.
3388
+ * @return Cloned EventObject.
3389
+ */
3390
+ clone() {
3391
+ return this.wrap(deepClone(this.data));
3392
+ }
3393
+ };
3394
+
3395
+ //#endregion
3396
+ //#region src/jscal/task.ts
3397
+ var TaskObject = class TaskObject extends Base {
3398
+ /**
3399
+ * Wrap updated data in a new TaskObject.
3400
+ * @param data Updated task data.
3401
+ * @return New TaskObject instance.
3402
+ */
3403
+ wrap(data) {
3404
+ const { "@type": _type, ...rest } = data;
3405
+ return new TaskObject(rest, { validate: false });
3406
+ }
3407
+ /**
3408
+ * Create a task with normalized date fields and RFC defaults applied.
3409
+ * @param input Task input values to normalize.
3410
+ * @param options Creation options (validation, clock).
3411
+ * @return TaskObject instance.
3412
+ */
3413
+ constructor(input = {}, options = {}) {
3414
+ var _options$now, _input$uid;
3415
+ const now = (_options$now = options.now) !== null && _options$now !== void 0 ? _options$now : nowUtc;
3416
+ const { start: rawStart, due: rawDue, updated: rawUpdated, created: rawCreated, timeZone: rawTimeZone, ...rest } = input;
3417
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
3418
+ let timeZone;
3419
+ if (rawTimeZone === null) timeZone = null;
3420
+ else if (rawTimeZone) timeZone = resolveTimeZone(rawTimeZone);
3421
+ const data = {
3422
+ ...rest,
3423
+ "@type": "Task",
3424
+ uid: (_input$uid = input.uid) !== null && _input$uid !== void 0 ? _input$uid : createUid(),
3425
+ updated
3426
+ };
3427
+ if (rawStart) data.start = toLocalDateTime(rawStart);
3428
+ if (rawDue) data.due = toLocalDateTime(rawDue);
3429
+ if (timeZone !== void 0) data.timeZone = timeZone;
3430
+ if (rawCreated) data.created = toUtcDateTime(rawCreated);
3431
+ else data.created = updated;
3432
+ applyCommonDefaults(data);
3433
+ applyTaskDefaults(data);
3434
+ if (options.validate !== false) validateJsCalendarObject(data);
3435
+ super(data);
3436
+ }
3437
+ /**
3438
+ * Clone the task as a new TaskObject instance.
3439
+ * @return Cloned TaskObject.
3440
+ */
3441
+ clone() {
3442
+ return this.wrap(deepClone(this.data));
3443
+ }
3444
+ };
3445
+
3446
+ //#endregion
3447
+ //#region src/jscal/normalize.ts
3448
+ /**
3449
+ * Type guard for wrapper instances that expose a data field.
3450
+ * @param value Value to check.
3451
+ * @return True when value looks like a JsCal instance.
3452
+ */
3453
+ function isJsCalInstance(value) {
3454
+ return "data" in value;
3455
+ }
3456
+ /**
3457
+ * Normalize a list of mixed JSCalendar objects and JsCal instances.
3458
+ * @param items Items to normalize.
3459
+ * @return Plain JSCalendar objects.
3460
+ */
3461
+ function normalizeItems(items) {
3462
+ const mapped = [];
3463
+ for (const entry of items) if (isObjectValue(entry) && isJsCalInstance(entry)) mapped.push(entry.data);
3464
+ else mapped.push(entry);
3465
+ return mapped;
3466
+ }
3467
+ /**
3468
+ * Normalize a group entry into a plain Event or Task.
3469
+ * @param entry Group entry input.
3470
+ * @return Plain Event or Task object.
3471
+ */
3472
+ function normalizeEntry(entry) {
3473
+ if (isObjectValue(entry) && isJsCalInstance(entry)) return entry.data;
3474
+ return entry;
3475
+ }
3476
+ /**
3477
+ * Normalize a list to plain JSCalendar objects.
3478
+ * @param value List of JSCalendar objects or JsCal instances.
3479
+ * @return Plain JSCalendar objects.
3480
+ */
3481
+ function normalizeToObjects(value) {
3482
+ return normalizeItems(value);
3483
+ }
3484
+
3485
+ //#endregion
3486
+ //#region src/jscal/group.ts
3487
+ var GroupObject = class GroupObject extends Base {
3488
+ /**
3489
+ * Wrap updated data in a new GroupObject.
3490
+ * @param data Updated group data.
3491
+ * @return New GroupObject instance.
3492
+ */
3493
+ wrap(data) {
3494
+ const { "@type": _type, ...rest } = data;
3495
+ return new GroupObject(rest, { validate: false });
3496
+ }
3497
+ /**
3498
+ * Create a group with normalized entries and RFC defaults.
3499
+ * @param input Group input values to normalize.
3500
+ * @param options Creation options (validation, clock).
3501
+ * @return GroupObject instance.
3502
+ */
3503
+ constructor(input, options = {}) {
3504
+ var _options$now, _input$uid;
3505
+ if (!Array.isArray(input.entries)) throw new Error("Group.entries is required");
3506
+ const now = (_options$now = options.now) !== null && _options$now !== void 0 ? _options$now : nowUtc;
3507
+ const { updated: rawUpdated, created: rawCreated, ...rest } = input;
3508
+ const entries = input.entries.map((entry) => normalizeEntry(entry));
3509
+ const updated = rawUpdated ? toUtcDateTime(rawUpdated) : now();
3510
+ const data = {
3511
+ ...rest,
3512
+ "@type": "Group",
3513
+ entries,
3514
+ uid: (_input$uid = input.uid) !== null && _input$uid !== void 0 ? _input$uid : createUid(),
3515
+ updated
3516
+ };
3517
+ if (rawCreated) data.created = toUtcDateTime(rawCreated);
3518
+ else data.created = updated;
3519
+ applyCommonDefaults(data);
3520
+ if (options.validate !== false) validateJsCalendarObject(data);
3521
+ super(data);
3522
+ }
3523
+ /**
3524
+ * Clone the group as a new GroupObject instance.
3525
+ * @return Cloned GroupObject.
3526
+ */
3527
+ clone() {
3528
+ return this.wrap(deepClone(this.data));
3529
+ }
3530
+ };
3531
+
3532
+ //#endregion
3533
+ //#region src/jscal/guards.ts
3534
+ /**
3535
+ * Type guard for JSCalendar Event objects.
3536
+ * @param obj JSCalendar object to check.
3537
+ * @return True when the object is an Event.
3538
+ */
3539
+ function isEvent(obj) {
3540
+ return obj["@type"] === TYPE_EVENT;
3541
+ }
3542
+ /**
3543
+ * Type guard for JSCalendar Task objects.
3544
+ * @param obj JSCalendar object to check.
3545
+ * @return True when the object is a Task.
3546
+ */
3547
+ function isTask(obj) {
3548
+ return obj["@type"] === TYPE_TASK;
3549
+ }
3550
+ /**
3551
+ * Type guard for JSCalendar Group objects.
3552
+ * @param obj JSCalendar object to check.
3553
+ * @return True when the object is a Group.
3554
+ */
3555
+ function isGroup(obj) {
3556
+ return obj["@type"] === TYPE_GROUP;
3557
+ }
3558
+
3559
+ //#endregion
3560
+ //#region src/jscal/builders.ts
3561
+ const TYPE_PARTICIPANT = "Participant";
3562
+ const TYPE_LOCATION = "Location";
3563
+ const TYPE_VIRTUAL_LOCATION = "VirtualLocation";
3564
+ const TYPE_ALERT = "Alert";
3565
+ const TYPE_OFFSET_TRIGGER = "OffsetTrigger";
3566
+ const TYPE_ABSOLUTE_TRIGGER = "AbsoluteTrigger";
3567
+ const TYPE_RELATION = "Relation";
3568
+ const TYPE_LINK = "Link";
3569
+ const TYPE_TIME_ZONE = "TimeZone";
3570
+ const TYPE_TIME_ZONE_RULE = "TimeZoneRule";
3571
+ const TYPE_RECURRENCE_RULE = "RecurrenceRule";
3572
+ const TYPE_NDAY = "NDay";
3573
+ /**
3574
+ * Build a Participant object with @type set and validated.
3575
+ * @param input Participant fields without @type.
3576
+ * @return Validated Participant object.
3577
+ */
3578
+ function buildParticipant(input) {
3579
+ if (input["@type"] && input["@type"] !== TYPE_PARTICIPANT) fail("participant", `must have @type ${TYPE_PARTICIPANT}`);
3580
+ const participant = {
3581
+ ...input,
3582
+ "@type": TYPE_PARTICIPANT
3583
+ };
3584
+ validateParticipant(participant, "participant");
3585
+ return participant;
3586
+ }
3587
+ /**
3588
+ * Build a Location object with @type set and validated.
3589
+ * @param input Location fields without @type.
3590
+ * @return Validated Location object.
3591
+ */
3592
+ function buildLocation(input) {
3593
+ if (input["@type"] && input["@type"] !== TYPE_LOCATION) fail("location", `must have @type ${TYPE_LOCATION}`);
3594
+ const location = {
3595
+ ...input,
3596
+ "@type": TYPE_LOCATION
3597
+ };
3598
+ validateLocation(location, "location");
3599
+ return location;
3600
+ }
3601
+ /**
3602
+ * Build a VirtualLocation object with @type set and validated.
3603
+ * @param input VirtualLocation fields without @type.
3604
+ * @return Validated VirtualLocation object.
3605
+ */
3606
+ function buildVirtualLocation(input) {
3607
+ if (input["@type"] && input["@type"] !== TYPE_VIRTUAL_LOCATION) fail("virtualLocation", `must have @type ${TYPE_VIRTUAL_LOCATION}`);
3608
+ const virtualLocation = {
3609
+ ...input,
3610
+ "@type": TYPE_VIRTUAL_LOCATION
3611
+ };
3612
+ validateVirtualLocation(virtualLocation, "virtualLocation");
3613
+ return virtualLocation;
3614
+ }
3615
+ /**
3616
+ * Build an Alert object with @type set and validated.
3617
+ * @param input Alert fields without @type.
3618
+ * @return Validated Alert object.
3619
+ */
3620
+ function buildAlert(input) {
3621
+ if (input["@type"] && input["@type"] !== TYPE_ALERT) fail("alert", `must have @type ${TYPE_ALERT}`);
3622
+ const alert = {
3623
+ ...input,
3624
+ "@type": TYPE_ALERT
3625
+ };
3626
+ validateAlert(alert, "alert");
3627
+ return alert;
3628
+ }
3629
+ /**
3630
+ * Build an OffsetTrigger object with @type set and validated.
3631
+ * @param input OffsetTrigger fields without @type.
3632
+ * @return Validated OffsetTrigger object.
3633
+ */
3634
+ function buildOffsetTrigger(input) {
3635
+ if (input["@type"] && input["@type"] !== TYPE_OFFSET_TRIGGER) fail("offsetTrigger", `must have @type ${TYPE_OFFSET_TRIGGER}`);
3636
+ const trigger = {
3637
+ ...input,
3638
+ "@type": TYPE_OFFSET_TRIGGER
3639
+ };
3640
+ assertSignedDuration(trigger.offset, "offsetTrigger.offset");
3641
+ assertString(trigger.relativeTo, "offsetTrigger.relativeTo");
3642
+ return trigger;
3643
+ }
3644
+ /**
3645
+ * Build an AbsoluteTrigger object with @type set and validated.
3646
+ * @param input AbsoluteTrigger fields without @type.
3647
+ * @return Validated AbsoluteTrigger object.
3648
+ */
3649
+ function buildAbsoluteTrigger(input) {
3650
+ if (input["@type"] && input["@type"] !== TYPE_ABSOLUTE_TRIGGER) fail("absoluteTrigger", `must have @type ${TYPE_ABSOLUTE_TRIGGER}`);
3651
+ const trigger = {
3652
+ ...input,
3653
+ "@type": TYPE_ABSOLUTE_TRIGGER
3654
+ };
3655
+ assertUtcDateTime(trigger.when, "absoluteTrigger.when");
3656
+ return trigger;
3657
+ }
3658
+ /**
3659
+ * Build a Relation object with @type set and validated.
3660
+ * @param input Relation fields without @type.
3661
+ * @return Validated Relation object.
3662
+ */
3663
+ function buildRelation(input) {
3664
+ if (input["@type"] && input["@type"] !== TYPE_RELATION) fail("relation", `must have @type ${TYPE_RELATION}`);
3665
+ const relation = {
3666
+ ...input,
3667
+ "@type": TYPE_RELATION
3668
+ };
3669
+ validateRelation(relation, "relation");
3670
+ return relation;
3671
+ }
3672
+ /**
3673
+ * Build a Link object with @type set and validated.
3674
+ * @param input Link fields without @type.
3675
+ * @return Validated Link object.
3676
+ */
3677
+ function buildLink(input) {
3678
+ if (input["@type"] && input["@type"] !== TYPE_LINK) fail("link", `must have @type ${TYPE_LINK}`);
3679
+ const link = {
3680
+ ...input,
3681
+ "@type": TYPE_LINK
3682
+ };
3683
+ validateLink(link, "link");
3684
+ return link;
3685
+ }
3686
+ /**
3687
+ * Build a TimeZone object with @type set and validated.
3688
+ * @param input TimeZone fields without @type.
3689
+ * @return Validated TimeZone object.
3690
+ */
3691
+ function buildTimeZone(input) {
3692
+ if (input["@type"] && input["@type"] !== TYPE_TIME_ZONE) fail("timeZone", `must have @type ${TYPE_TIME_ZONE}`);
3693
+ const timeZone = {
3694
+ ...input,
3695
+ "@type": TYPE_TIME_ZONE
3696
+ };
3697
+ validateTimeZoneObject(timeZone, "timeZone");
3698
+ return timeZone;
3699
+ }
3700
+ /**
3701
+ * Build a TimeZoneRule object with @type set and validated.
3702
+ * @param input TimeZoneRule fields without @type.
3703
+ * @return Validated TimeZoneRule object.
3704
+ */
3705
+ function buildTimeZoneRule(input) {
3706
+ if (input["@type"] && input["@type"] !== TYPE_TIME_ZONE_RULE) fail("timeZoneRule", `must have @type ${TYPE_TIME_ZONE_RULE}`);
3707
+ const rule = {
3708
+ ...input,
3709
+ "@type": TYPE_TIME_ZONE_RULE
3710
+ };
3711
+ validateTimeZoneRule(rule, "timeZoneRule");
3712
+ return rule;
3713
+ }
3714
+ /**
3715
+ * Build a RecurrenceRule object with @type set and validated.
3716
+ * @param input RecurrenceRule fields without @type.
3717
+ * @return Validated RecurrenceRule object.
3718
+ */
3719
+ function buildRecurrenceRule(input) {
3720
+ if (input["@type"] && input["@type"] !== TYPE_RECURRENCE_RULE) fail("recurrenceRule", `must have @type ${TYPE_RECURRENCE_RULE}`);
3721
+ const rule = {
3722
+ ...input,
3723
+ "@type": TYPE_RECURRENCE_RULE
3724
+ };
3725
+ validateRecurrenceRule(rule, "recurrenceRule");
3726
+ return rule;
3727
+ }
3728
+ /**
3729
+ * Build an NDay object with @type set and validated.
3730
+ * @param input NDay fields without @type.
3731
+ * @return Validated NDay object.
3732
+ */
3733
+ function buildNDay(input) {
3734
+ if (input["@type"] && input["@type"] !== TYPE_NDAY) fail("nday", `must have @type ${TYPE_NDAY}`);
3735
+ const nday = {
3736
+ ...input,
3737
+ "@type": TYPE_NDAY
3738
+ };
3739
+ validateNDay(nday, "nday");
3740
+ return nday;
3741
+ }
3742
+ /**
3743
+ * Build a patch object for Event-like updates with JSON value validation.
3744
+ * @param input Patch fields for an event.
3745
+ * @return Validated PatchObject.
3746
+ */
3747
+ function buildEventPatch(input) {
3748
+ const patch = { ...input };
3749
+ assertPatchObject(patch, "eventPatch");
3750
+ return patch;
3751
+ }
3752
+ /**
3753
+ * Build a patch object for Task-like updates with JSON value validation.
3754
+ * @param input Patch fields for a task.
3755
+ * @return Validated PatchObject.
3756
+ */
3757
+ function buildTaskPatch(input) {
3758
+ const patch = { ...input };
3759
+ assertPatchObject(patch, "taskPatch");
3760
+ return patch;
3761
+ }
3762
+ /**
3763
+ * Build a patch object for Group-like updates with JSON value validation.
3764
+ * @param input Patch fields for a group.
3765
+ * @return Validated PatchObject.
3766
+ */
3767
+ function buildGroupPatch(input) {
3768
+ const patch = { ...input };
3769
+ assertPatchObject(patch, "groupPatch");
3770
+ return patch;
3771
+ }
3772
+ /**
3773
+ * Build a record keyed by Ids, optionally merging into an existing record.
3774
+ * @param items Items to store with optional explicit ids.
3775
+ * @param builder Builder function to validate each item.
3776
+ * @param idFn Optional ID generator for items without explicit ids.
3777
+ * @param existing Existing record to merge into.
3778
+ * @return Record keyed by ids with merged values.
3779
+ */
3780
+ function buildIdMap(items, builder, idFn = () => createId(), existing) {
3781
+ const result = existing ? { ...existing } : {};
3782
+ items.forEach((item, index) => {
3783
+ var _item$id;
3784
+ const id = (_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : idFn(item.value, index);
3785
+ result[id] = builder(item.value);
3786
+ });
3787
+ return result;
3788
+ }
3789
+ /**
3790
+ * Build a record of Participant objects keyed by Ids.
3791
+ * @param items Participant inputs with optional explicit ids.
3792
+ * @param existing Existing participant record to merge into.
3793
+ * @return Participant record keyed by Id.
3794
+ */
3795
+ function buildParticipants(items, existing) {
3796
+ return buildIdMap(items, buildParticipant, () => createId(), existing);
3797
+ }
3798
+ /**
3799
+ * Build a record of Location objects keyed by Ids.
3800
+ * @param items Location inputs with optional explicit ids.
3801
+ * @param existing Existing location record to merge into.
3802
+ * @return Location record keyed by Id.
3803
+ */
3804
+ function buildLocations(items, existing) {
3805
+ return buildIdMap(items, buildLocation, () => createId(), existing);
3806
+ }
3807
+ /**
3808
+ * Build a record of VirtualLocation objects keyed by Ids.
3809
+ * @param items VirtualLocation inputs with optional explicit ids.
3810
+ * @param existing Existing virtual location record to merge into.
3811
+ * @return VirtualLocation record keyed by Id.
3812
+ */
3813
+ function buildVirtualLocations(items, existing) {
3814
+ return buildIdMap(items, buildVirtualLocation, () => createId(), existing);
3815
+ }
3816
+ /**
3817
+ * Build a record of Alert objects keyed by Ids.
3818
+ * @param items Alert inputs with optional explicit ids.
3819
+ * @param existing Existing alert record to merge into.
3820
+ * @return Alert record keyed by Id.
3821
+ */
3822
+ function buildAlerts(items, existing) {
3823
+ return buildIdMap(items, buildAlert, () => createId(), existing);
3824
+ }
3825
+ /**
3826
+ * Build a record of Link objects keyed by Ids.
3827
+ * @param items Link inputs with optional explicit ids.
3828
+ * @param existing Existing link record to merge into.
3829
+ * @return Link record keyed by Id.
3830
+ */
3831
+ function buildLinks(items, existing) {
3832
+ return buildIdMap(items, buildLink, () => createId(), existing);
3833
+ }
3834
+ /**
3835
+ * Build a record of Relation objects keyed by Ids.
3836
+ * @param items Relation inputs with optional explicit ids.
3837
+ * @param existing Existing relation record to merge into.
3838
+ * @return Relation record keyed by Id.
3839
+ */
3840
+ function buildRelatedTo(items, existing) {
3841
+ return buildIdMap(items, buildRelation, () => createId(), existing);
3842
+ }
3843
+ /**
3844
+ * Build a record of TimeZone objects keyed by tzId.
3845
+ * @param items TimeZone inputs.
3846
+ * @return TimeZone record keyed by tzId.
3847
+ */
3848
+ function buildTimeZoneMap(items) {
3849
+ const result = {};
3850
+ items.forEach((item) => {
3851
+ const timeZone = buildTimeZone(item);
3852
+ result[timeZone.tzId] = timeZone;
3853
+ });
3854
+ return result;
3855
+ }
3856
+
3857
+ //#endregion
3858
+ //#region src/jscal.ts
3859
+ const JsCal = {
3860
+ Event: EventObject,
3861
+ Task: TaskObject,
3862
+ Group: GroupObject,
3863
+ createUid,
3864
+ createId,
3865
+ isEvent,
3866
+ isGroup,
3867
+ isTask,
3868
+ duration: Duration,
3869
+ timeZone: resolveTimeZone,
3870
+ timeZones: TimeZones,
3871
+ applyPatch,
3872
+ Participant: buildParticipant,
3873
+ Location: buildLocation,
3874
+ VirtualLocation: buildVirtualLocation,
3875
+ Alert: buildAlert,
3876
+ OffsetTrigger: buildOffsetTrigger,
3877
+ AbsoluteTrigger: buildAbsoluteTrigger,
3878
+ Relation: buildRelation,
3879
+ Link: buildLink,
3880
+ TimeZone: buildTimeZone,
3881
+ TimeZoneRule: buildTimeZoneRule,
3882
+ RecurrenceRule: buildRecurrenceRule,
3883
+ NDay: buildNDay,
3884
+ ByDay: buildNDay,
3885
+ EventPatch: buildEventPatch,
3886
+ TaskPatch: buildTaskPatch,
3887
+ GroupPatch: buildGroupPatch,
3888
+ participants: buildParticipants,
3889
+ locations: buildLocations,
3890
+ virtualLocations: buildVirtualLocations,
3891
+ alerts: buildAlerts,
3892
+ links: buildLinks,
3893
+ relatedTo: buildRelatedTo,
3894
+ timeZonesMap: buildTimeZoneMap,
3895
+ findByUid(items, uid) {
3896
+ return findByUid(normalizeItems(items), uid);
3897
+ },
3898
+ filterByType(items, type) {
3899
+ return filterByType(normalizeItems(items), type);
3900
+ },
3901
+ groupByType(items) {
3902
+ return groupByType(normalizeItems(items));
3903
+ },
3904
+ filterByText(items, query) {
3905
+ return filterByText(normalizeItems(items), query);
3906
+ },
3907
+ filterByDateRange(items, range, options) {
3908
+ return filterByDateRange(normalizeItems(items), range, options);
3909
+ },
3910
+ expandRecurrence(items, range) {
3911
+ return expandRecurrence(normalizeItems(items), range);
3912
+ },
3913
+ expandRecurrencePaged(items, range, options) {
3914
+ return expandRecurrencePaged(normalizeItems(items), range, options);
3915
+ },
3916
+ toICal(value, options) {
3917
+ return toICal(normalizeToObjects(value), options);
3918
+ }
3919
+ };
3920
+
3921
+ //#endregion
3922
+ exports.JsCal = JsCal;
3923
+ exports.ValidationError = ValidationError;