@forcecalendar/core 2.4.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,9 +64,12 @@ export declare class EventStore {
64
64
  addEvent(event: Event | import('../types.js').EventData): Event;
65
65
  /**
66
66
  * Update an existing event
67
- * @param {string} eventId - The event ID
67
+ *
68
+ * An occurrence id (see {@link Event.occurrenceId}) updates the recurring
69
+ * master the occurrence belongs to, i.e. the whole series.
70
+ * @param {string} eventId - Event id or occurrence id
68
71
  * @param {Partial<import('../types.js').EventData>} updates - Properties to update
69
- * @returns {Event} The updated event
72
+ * @returns {Event} The updated event (the master for an occurrence id)
70
73
  * @throws {Error} If event not found
71
74
  */
72
75
  updateEvent(eventId: string, updates: Partial<import('../types.js').EventData>): Event;
@@ -80,7 +83,10 @@ export declare class EventStore {
80
83
  private _replaceEvent;
81
84
  /**
82
85
  * Remove an event from the store
83
- * @param {string} eventId - The event ID to remove
86
+ *
87
+ * An occurrence id (see {@link Event.occurrenceId}) removes the recurring
88
+ * master the occurrence belongs to, i.e. the whole series.
89
+ * @param {string} eventId - Event id or occurrence id
84
90
  * @returns {boolean} True if removed, false if not found
85
91
  */
86
92
  removeEvent(eventId: string): boolean;
@@ -90,12 +96,67 @@ export declare class EventStore {
90
96
  * @private
91
97
  */
92
98
  private _detachEvent;
99
+ /**
100
+ * Drop the recurrence engine's cached expansions of one series, or of
101
+ * every series when no id is given. The engine is pluggable, so both
102
+ * hooks are optional.
103
+ * @param {string} [eventId] - Series to invalidate; omit to clear everything
104
+ * @private
105
+ */
106
+ private _invalidateOccurrenceCache;
93
107
  /**
94
108
  * Get an event by ID
95
- * @param {string} eventId - The event ID
96
- * @returns {Event|null} The event or null if not found
109
+ *
110
+ * Occurrence ids produced by {@link EventStore#expandRecurringEvent}
111
+ * (`<masterId>_<startMs>`, see {@link Event.occurrenceId}) resolve to the
112
+ * stored recurring master they were derived from, so an id taken from view
113
+ * data can always be looked up. Occurrences themselves are never stored.
114
+ * @param {string} eventId - Event id or occurrence id
115
+ * @returns {Event|null} The stored event (the master for an occurrence id) or null
97
116
  */
98
117
  getEvent(eventId: string): Event | null;
118
+ /**
119
+ * Resolve an occurrence id to the stored recurring master it belongs to.
120
+ * Not cached: the master's own cache entry is the one kept in sync.
121
+ * @param {string} eventId - Candidate occurrence id
122
+ * @returns {Event|null} The master event or null
123
+ * @private
124
+ */
125
+ private _resolveOccurrenceMaster;
126
+ /**
127
+ * Resolve an id taken from anywhere (store, view data, a drag or click
128
+ * handler) to the id of the stored event it refers to.
129
+ *
130
+ * Returns the id itself for a stored event, the master's id for an
131
+ * occurrence id (see {@link Event.occurrenceId}) whose master is a stored
132
+ * recurring event, and `null` when nothing stored matches. This is the
133
+ * same resolution {@link EventStore#getEvent}, updateEvent and removeEvent
134
+ * apply, exposed for consumers that only need the id.
135
+ *
136
+ * @example
137
+ * const masterId = store.resolveEventId(chip.dataset.eventId); // 'standup' for 'standup_1750028400000'
138
+ *
139
+ * @param {string} id - Event id or occurrence id
140
+ * @returns {string|null} Id of the stored event, or null
141
+ */
142
+ resolveEventId(id: string): string | null;
143
+ /**
144
+ * Get the occurrence an occurrence id stands for, as an {@link Event}
145
+ * exactly like the ones {@link EventStore#expandRecurringEvent} returns.
146
+ *
147
+ * Returns `null` when the id is not an occurrence id, its master is not a
148
+ * stored recurring event, or the series has no occurrence starting at the
149
+ * encoded instant. A master id is never an occurrence, so it yields null
150
+ * too; use {@link EventStore#getEvent} for the master.
151
+ *
152
+ * @example
153
+ * const occurrence = store.getOccurrence('standup_1750028400000');
154
+ *
155
+ * @param {string} occurrenceId - Occurrence id (`<masterId>_<startMs>`)
156
+ * @param {string} [timezone] - Timezone for the expansion (defaults to the store timezone)
157
+ * @returns {Event|null} The occurrence, or null
158
+ */
159
+ getOccurrence(occurrenceId: string, timezone?: string): Event | null;
99
160
  /**
100
161
  * Get all events
101
162
  * @returns {Event[]} Array of all events
@@ -109,11 +170,59 @@ export declare class EventStore {
109
170
  queryEvents(filters?: import('../types.js').QueryFilters): Event[];
110
171
  /**
111
172
  * Get events for a specific date
173
+ *
174
+ * Recurring series are expanded for the day, so the result holds their
175
+ * occurrences (see {@link EventStore#expandRecurringEvent}) rather than the
176
+ * master events. When building a grid of days use
177
+ * {@link EventStore#getEventsByDate}, which expands once for the whole range.
112
178
  * @param {Date} date - The date to query
113
179
  * @param {string} [timezone] - Timezone for the query (defaults to store timezone)
114
180
  * @returns {Event[]} Events occurring on the date, sorted by start time
115
181
  */
116
182
  getEventsForDate(date: Date, timezone?: string): Event[];
183
+ /**
184
+ * Get the events for every day in a range, keyed by local date (YYYY-MM-DD)
185
+ *
186
+ * Recurring series are expanded once for the whole range rather than once
187
+ * per day, which is what a month or week grid needs. Every day in the range
188
+ * has an entry (an empty array when nothing occurs) and multi-day events
189
+ * appear under each day they span. Each array is sorted like
190
+ * {@link EventStore#getEventsForDate}.
191
+ *
192
+ * @example
193
+ * const byDate = store.getEventsByDate(gridStart, gridEnd);
194
+ * const events = byDate.get(DateUtils.getLocalDateString(cellDate)) || [];
195
+ *
196
+ * @param {Date} start - First day of the range
197
+ * @param {Date} end - Last day of the range
198
+ * @param {string} [timezone] - Timezone deciding which day an event falls on (defaults to store timezone)
199
+ * @returns {Map<string, Event[]>} Local date string -> events on that day
200
+ */
201
+ getEventsByDate(start: Date, end: Date, timezone?: string): Map<string, Event[]>;
202
+ /**
203
+ * Collect the ids of stored events that may occur on a date.
204
+ * @param {Date} date - The date to query
205
+ * @returns {Set<string>} Candidate event ids
206
+ * @private
207
+ */
208
+ private _collectDateCandidateIds;
209
+ /**
210
+ * Keep the events that overlap a day in the given timezone, sorted by start.
211
+ * @param {Event[]} events - Candidate events
212
+ * @param {Date} dayStart - Start of the day
213
+ * @param {Date} dayEnd - End of the day
214
+ * @param {string} timezone - Timezone deciding whether an event falls on the day
215
+ * @returns {Event[]} Events on the day, sorted
216
+ * @private
217
+ */
218
+ private _selectEventsForDay;
219
+ /**
220
+ * Comparator ordering events by start time in a timezone, longer events first.
221
+ * @param {string} timezone - Timezone used for the start comparison
222
+ * @returns {(a: Event, b: Event) => number} Comparator
223
+ * @private
224
+ */
225
+ private _compareByStart;
117
226
  /**
118
227
  * Get events that overlap with a given time range
119
228
  * @param {Date} start - Start time
@@ -138,6 +247,15 @@ export declare class EventStore {
138
247
  * @returns {Array<Event[]>} Array of event groups that overlap
139
248
  */
140
249
  getOverlapGroups(date: Date, timedOnly?: boolean): Array<Event[]>;
250
+ /**
251
+ * Group a list of events into clusters of overlapping time slots
252
+ * Same result as {@link EventStore#getOverlapGroups} for events already fetched
253
+ * (for example one day of {@link EventStore#getEventsByDate}).
254
+ * @param {Event[]} events - Events to group; the array is not modified
255
+ * @param {boolean} [timedOnly=true] - Only include timed events (not all-day)
256
+ * @returns {Array<Event[]>} Array of event groups that overlap
257
+ */
258
+ groupOverlappingEvents(events: Event[], timedOnly?: boolean): Array<Event[]>;
141
259
  /**
142
260
  * Calculate positions for overlapping events (for rendering)
143
261
  * @param {Event[]} events - Array of overlapping events
@@ -159,6 +277,16 @@ export declare class EventStore {
159
277
  getEventsInRange(start: Date, end: Date, expandRecurringOrOptions?: boolean | Object, timezone?: string): Event[];
160
278
  /**
161
279
  * Expand a recurring event into individual occurrences
280
+ *
281
+ * Returns every occurrence that overlaps the range, including ones that
282
+ * start before it but run into it (multi-day series). Each occurrence is an
283
+ * {@link Event} cloned from the master with:
284
+ * - `id` from {@link Event.occurrenceId} (`<masterId>_<startMs>`), stable
285
+ * across ranges and resolvable with {@link EventStore#getEvent},
286
+ * - `isOccurrence: true`, `recurringEventId` and `occurrenceStart`,
287
+ * - `metadata.recurringEventId`, `metadata.occurrenceId` (same as `id`) and
288
+ * `metadata.occurrenceIndex` (position within this expansion).
289
+ * Non-recurring events are returned as-is in a one-element array.
162
290
  * @param {Event} event - The recurring event
163
291
  * @param {Date} rangeStart - Start of the expansion range
164
292
  * @param {Date} rangeEnd - End of the expansion range
@@ -166,6 +294,78 @@ export declare class EventStore {
166
294
  * @returns {Event[]} Array of event occurrences
167
295
  */
168
296
  expandRecurringEvent(event: Event, rangeStart: Date, rangeEnd: Date, timezone?: string): Event[];
297
+ /**
298
+ * Build the Event instance for one occurrence of a recurring master.
299
+ * @param {Event} event - The recurring master
300
+ * @param {{start: Date, end: Date, timezone?: string}} occurrence - Engine occurrence
301
+ * @param {string} eventTimezone - Timezone the series was expanded in
302
+ * @param {number} index - Position within the current expansion
303
+ * @returns {Event} Occurrence event
304
+ * @private
305
+ */
306
+ private _createOccurrence;
307
+ /**
308
+ * Lazily iterate the occurrences of a stored event in chronological order.
309
+ *
310
+ * Occurrences come one at a time from the store's recurrence engine
311
+ * (RecurrenceEngineV2 by default), so taking the next few occurrences of
312
+ * an open-ended series does not expand the series. `after` and `before`
313
+ * are exclusive unless `inclusive` is set; see
314
+ * RecurrenceEngineV2.iterateOccurrences for the full semantics. The
315
+ * expansion timezone defaults to the event's, then the store's.
316
+ *
317
+ * @example
318
+ * for (const occurrence of store.iterateOccurrences('standup', { after: new Date() })) {
319
+ * if (occurrence.start > deadline) break;
320
+ * remind(occurrence);
321
+ * }
322
+ *
323
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
324
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
325
+ * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>} Occurrences in chronological order
326
+ * @throws {Error} If no event with the ID exists
327
+ */
328
+ iterateOccurrences(eventId: string, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): Generator<import('../types.js').ExpandedOccurrence, void, undefined>;
329
+ /**
330
+ * First occurrence of a stored event after an instant, or null when the
331
+ * series has no occurrence after it. `after` is exclusive unless
332
+ * `options.inclusive` is set.
333
+ *
334
+ * @example
335
+ * const upcoming = store.getNextOccurrence('standup', new Date());
336
+ *
337
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
338
+ * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
339
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Further options
340
+ * @returns {import('../types.js').ExpandedOccurrence|null} The next occurrence, or null
341
+ * @throws {Error} If no event with the ID exists
342
+ */
343
+ getNextOccurrence(eventId: string, after?: Date | number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence | null;
344
+ /**
345
+ * The first `count` occurrences of a stored event inside a window,
346
+ * generated lazily. `count` is capped at the engine's
347
+ * MAX_OCCURRENCES_HARD_LIMIT.
348
+ *
349
+ * @example
350
+ * const nextFive = store.takeOccurrences('standup', 5, { after: new Date() });
351
+ *
352
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
353
+ * @param {number} count - Maximum number of occurrences to return (fractions are floored)
354
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
355
+ * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
356
+ * @throws {Error} If no event with the ID exists
357
+ */
358
+ takeOccurrences(eventId: string, count: number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence[];
359
+ /**
360
+ * Resolve an occurrence query to the stored event and its options, with
361
+ * the timezone defaulted as expandRecurringEvent does
362
+ * @param {string} eventId - The event ID
363
+ * @param {Object} options - Caller options
364
+ * @returns {{ event: Event, options: Object }}
365
+ * @throws {Error} If no event with the ID exists
366
+ * @private
367
+ */
368
+ private _occurrenceQuery;
169
369
  /**
170
370
  * Clear all events
171
371
  */
@@ -185,13 +385,23 @@ export declare class EventStore {
185
385
  * - adds events whose id is not in the store (`add` change),
186
386
  * - removes stored events missing from the snapshot (`remove` change),
187
387
  * unless `removeMissing` is `false`,
388
+ * - treats occurrences of a recurring series (entries with
389
+ * `isOccurrence: true`, the plain object of such an occurrence, or an id
390
+ * that {@link EventStore#getEvent} resolves to a stored recurring master)
391
+ * as a reference to their master: the master is kept as unchanged and is
392
+ * never replaced by an occurrence. An occurrence whose master is neither
393
+ * stored nor in the snapshot is an error,
188
394
  * - emits a single `batch` notification listing those changes, or nothing at
189
395
  * all when the snapshot matches the store. When called while a batch is
190
396
  * already open the changes are queued on that batch instead.
191
397
  *
192
- * Input is validated up front: invalid event data or duplicate ids throw
193
- * before the store is modified. Any error raised while applying the diff
194
- * rolls the store back to its previous state.
398
+ * Input is validated up front: invalid event data, duplicate ids and
399
+ * occurrences without a master throw before the store is modified. When
400
+ * reconcile opens the batch itself, any error raised while applying the
401
+ * diff rolls the store back to its previous state; inside a batch opened
402
+ * by the caller the changes applied so far stay queued on that batch, and
403
+ * it is the caller's rollbackBatch() that undoes them (a custom
404
+ * `isEquivalent` that throws is the usual way to get there).
195
405
  *
196
406
  * @example
197
407
  * // periodic server snapshot
@@ -204,6 +414,16 @@ export declare class EventStore {
204
414
  * @throws {Error} If an entry fails validation or two entries share an id
205
415
  */
206
416
  reconcile(events: Array<Event | import('../types.js').EventData>, options?: import('../types.js').ReconcileOptions): import('../types.js').ReconcileResult;
417
+ /**
418
+ * Id of the recurring master a reconcile entry is an occurrence of, or
419
+ * null when the entry is an event in its own right. Recognises Event
420
+ * occurrences (isOccurrence), their toObject() form (occurrence markers
421
+ * in metadata) and ids that resolve to a stored recurring master.
422
+ * @param {Event|import('../types.js').EventData} eventData - Reconcile entry
423
+ * @returns {string|null} Master id or null
424
+ * @private
425
+ */
426
+ private _occurrenceMasterId;
207
427
  /**
208
428
  * Subscribe to store changes
209
429
  * @param {Function} callback - Callback function
@@ -241,16 +461,18 @@ export declare class EventStore {
241
461
  */
242
462
  private _removeFromReferencedIndex;
243
463
  /**
244
- * Notify listeners of changes
464
+ * Deliver a change now, or queue it when a batch is open
465
+ * @param {import('../types.js').EventStoreChange} change - Change to deliver
245
466
  * @private
246
467
  */
468
+ private _queueChange;
247
469
  /**
248
- * Deliver a change now, or queue it when a batch is open
470
+ * Deliver a change to every subscriber now, regardless of batch mode.
471
+ * A listener that throws is reported and does not stop the others.
249
472
  * @param {import('../types.js').EventStoreChange} change - Change to deliver
250
473
  * @private
251
474
  */
252
- private _queueChange;
253
- _notifyChange(change: any): void;
475
+ private _notifyChange;
254
476
  /**
255
477
  * Get store statistics
256
478
  * @returns {Object}
@@ -308,7 +530,7 @@ export declare class EventStore {
308
530
  */
309
531
  getPerformanceMetrics(): Object;
310
532
  /**
311
- * Clear all caches
533
+ * Clear all caches, including the recurrence engine's cached expansions
312
534
  */
313
535
  clearCaches(): void;
314
536
  /**
@@ -36,7 +36,16 @@ export declare class RRuleParser {
36
36
  */
37
37
  private static parseExceptionDates;
38
38
  /**
39
- * Validate and normalize rule
39
+ * Validate and normalize a rule.
40
+ *
41
+ * Works on a copy: rule objects handed in are typically the stored
42
+ * recurrenceRule of an Event, and normalising them in place would make
43
+ * the stored event differ from the data it was created from (a spurious
44
+ * update on the next reconcile) and let the engines' per-rule caches leak
45
+ * into it. The copy is shallow except for the array fields, which are
46
+ * copied as well.
47
+ * @param {Object} rule - Rule object (not modified)
48
+ * @returns {Object} Normalised copy
40
49
  * @private
41
50
  */
42
51
  private static validateRule;
@@ -26,6 +26,145 @@ export declare class RecurrenceEngine {
26
26
  * @returns {import('../types.js').EventOccurrence[]} Array of occurrence objects with start/end dates
27
27
  */
28
28
  static expandEvent(event: import('./Event.js').Event, rangeStart: Date, rangeEnd: Date, maxOccurrences?: number, timezone?: string): import('../types.js').EventOccurrence[];
29
+ /**
30
+ * Lazily iterate the occurrences of an event in chronological order.
31
+ *
32
+ * Yields the same occurrence objects, in the same order, that expandEvent
33
+ * returns for the window — but one at a time, so a caller can stop after
34
+ * any number of them without the rest of the series being generated.
35
+ * Daily, weekly and sub-daily rules are seeked to `after` arithmetically,
36
+ * so finding the first occurrence after a far-away instant does not step
37
+ * through the series from its start.
38
+ *
39
+ * Both bounds are exclusive unless `inclusive` is set: an occurrence that
40
+ * starts exactly at `after` or `before` is skipped by default, which lets
41
+ * `iterateOccurrences(event, { after: previous.start })` continue a series
42
+ * without repeating `previous`. With `inclusive: true` the window is
43
+ * closed on both ends, exactly like expandEvent's range. An omitted bound
44
+ * leaves that end of the window open.
45
+ *
46
+ * COUNT, UNTIL, INTERVAL, BYDAY/BYMONTHDAY/BYSETPOS and exception dates
47
+ * are honoured as in expandEvent; BYSETPOS rules are yielded one period at
48
+ * a time, since the set positions of a period are only known once it is
49
+ * complete. A non-recurring event yields its single occurrence when it
50
+ * falls inside the window. Iteration ends at COUNT or UNTIL, at `before`,
51
+ * or — as a guard for rules that produce no occurrences — after
52
+ * MAX_ITERATIONS_HARD_LIMIT consecutive steps without one.
53
+ *
54
+ * The generator is single-use; call this method again for a fresh one.
55
+ *
56
+ * @example
57
+ * for (const occurrence of RecurrenceEngine.iterateOccurrences(event, { after: new Date() })) {
58
+ * if (occurrence.start > deadline) break;
59
+ * schedule(occurrence);
60
+ * }
61
+ *
62
+ * @param {import('./Event.js').Event} event - The event to iterate
63
+ * @param {import('../types.js').OccurrenceIteratorOptions} [options={}] - Window and timezone
64
+ * @returns {Generator<import('../types.js').EventOccurrence, void, undefined>} Occurrences in chronological order
65
+ * @throws {TypeError} If `after` or `before` is not a valid Date or timestamp
66
+ */
67
+ static iterateOccurrences(event: import('./Event.js').Event, options?: import('../types.js').OccurrenceIteratorOptions): Generator<import('../types.js').EventOccurrence, void, undefined>;
68
+ /**
69
+ * First occurrence of an event after an instant, or null when the series
70
+ * has no occurrence after it (past COUNT or UNTIL, or a non-recurring
71
+ * event that already started).
72
+ *
73
+ * `after` is exclusive unless `options.inclusive` is set, so passing the
74
+ * start of a known occurrence returns the one that follows it. Not to be
75
+ * confused with getNextOccurrence, which steps a parsed rule once
76
+ * without regard to COUNT, UNTIL or exceptions.
77
+ *
78
+ * @example
79
+ * const upcoming = RecurrenceEngine.nextOccurrence(event, new Date());
80
+ *
81
+ * @param {import('./Event.js').Event} event - The event to query
82
+ * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
83
+ * @param {import('../types.js').OccurrenceIteratorOptions} [options={}] - Further window options
84
+ * @returns {import('../types.js').EventOccurrence|null} The next occurrence, or null
85
+ */
86
+ static nextOccurrence(event: import('./Event.js').Event, after?: Date | number, options?: import('../types.js').OccurrenceIteratorOptions): import('../types.js').EventOccurrence | null;
87
+ /**
88
+ * The first `count` occurrences of an event inside a window, generated
89
+ * lazily so an open-ended series costs only the occurrences taken.
90
+ * `count` is capped at MAX_OCCURRENCES_HARD_LIMIT; fewer are returned
91
+ * when the series or the window ends first.
92
+ *
93
+ * @example
94
+ * const nextFive = RecurrenceEngine.takeOccurrences(event, 5, { after: new Date() });
95
+ *
96
+ * @param {import('./Event.js').Event} event - The event to query
97
+ * @param {number} count - Maximum number of occurrences to return (fractions are floored)
98
+ * @param {import('../types.js').OccurrenceIteratorOptions} [options={}] - Window and timezone
99
+ * @returns {import('../types.js').EventOccurrence[]} Up to `count` occurrences in chronological order
100
+ */
101
+ static takeOccurrences(event: import('./Event.js').Event, count: number, options?: import('../types.js').OccurrenceIteratorOptions): import('../types.js').EventOccurrence[];
102
+ /**
103
+ * Resolve iterator options into a closed window on numeric timestamps.
104
+ * Exclusive bounds are shifted by one millisecond, the resolution of
105
+ * Date, so the expansion loops only ever compare inclusively.
106
+ * @param {import('../types.js').OccurrenceIteratorOptions} options - Iterator options
107
+ * @returns {{ startMs: number, endMs: number }} Inclusive bounds (infinite when open)
108
+ * @throws {TypeError} If a bound is not a valid Date or timestamp
109
+ * @private
110
+ */
111
+ private static _occurrenceWindow;
112
+ /**
113
+ * Timestamp of a window bound given as a Date or a number
114
+ * @param {Date|number} value - Bound to convert
115
+ * @param {string} name - Option name for the error message
116
+ * @returns {number} Timestamp in milliseconds
117
+ * @throws {TypeError} If the value is not a valid Date or timestamp
118
+ * @private
119
+ */
120
+ private static _boundMs;
121
+ /**
122
+ * Yield a single occurrence if it starts inside the window
123
+ * @param {import('../types.js').EventOccurrence} occurrence - The occurrence
124
+ * @param {{ startMs: number, endMs: number }} window - Inclusive bounds
125
+ * @returns {Generator<import('../types.js').EventOccurrence, void, undefined>}
126
+ * @private
127
+ */
128
+ private static _iterateSingle;
129
+ /**
130
+ * Lazy counterpart of the expansion loops: seeks to the window, then
131
+ * advances a Date cursor per step and yields each in-window occurrence,
132
+ * applying the same DST adjustment and exception filtering.
133
+ * @private
134
+ */
135
+ private static _iterateRule;
136
+ /**
137
+ * Seek the iteration cursor to the last occurrence before rangeStartMs
138
+ * using the same arithmetic as expandEvent. The system-transition scan
139
+ * is bounded by the target itself, so an open-ended window costs no
140
+ * more than a closed one.
141
+ * @param {number} fromMs - Cursor position (DTSTART)
142
+ * @param {number} weekday - Weekday of the cursor
143
+ * @param {Object} rule - Parsed recurrence rule
144
+ * @param {number} rangeStartMs - Seek target
145
+ * @returns {{ ms: number, steps: number }|null} New cursor, or null for rules that cannot seek
146
+ * @private
147
+ */
148
+ private static _seekToWindow;
149
+ /**
150
+ * Milliseconds per _advanceInPlace step for every rule whose step is a
151
+ * fixed duration between system-timezone transitions: the sub-daily
152
+ * frequencies, DAILY and plain WEEKLY
153
+ * @param {Object} rule - Parsed recurrence rule
154
+ * @returns {number} Step length in milliseconds, or 0 when not fixed
155
+ * @private
156
+ */
157
+ private static _seekStepMs;
158
+ /**
159
+ * Streaming BYSETPOS filter: buffers the occurrences of one period and
160
+ * yields its selected positions once the next period begins. Periods
161
+ * arrive contiguously and in order, so the result matches _applyBySetPos.
162
+ * @param {Iterable<import('../types.js').EventOccurrence>} source - Occurrences in order
163
+ * @param {Object} rule - Rule with bySetPos
164
+ * @returns {Generator<import('../types.js').EventOccurrence, void, undefined>}
165
+ * @private
166
+ */
167
+ private static _iterateBySetPos;
29
168
  /**
30
169
  * General expansion loop: advances a Date cursor per step. Handles every
31
170
  * frequency and degenerate rules (non-advancing dates, invalid intervals).
@@ -100,7 +239,9 @@ export declare class RecurrenceEngine {
100
239
  private static _seekWeekCycle;
101
240
  /**
102
241
  * Find the next system-timezone offset transition after fromMs.
103
- * Cached module-wide: the system timezone is fixed for the process.
242
+ * Cached module-wide: the system timezone is fixed for the process. The
243
+ * cache grows incrementally, so extending coverage (a view navigating
244
+ * forward, a series with a far-past DTSTART) scans only the new span.
104
245
  * @param {number} fromMs - Search from this timestamp (exclusive)
105
246
  * @param {number} toMs - Extend cache coverage at least this far
106
247
  * @returns {number} Transition timestamp, or Infinity if none within coverage
@@ -122,6 +263,23 @@ export declare class RecurrenceEngine {
122
263
  * @private
123
264
  */
124
265
  private static _applyBySetPos;
266
+ /**
267
+ * Key of the BYSETPOS period an occurrence belongs to
268
+ * @param {import('../types.js').EventOccurrence} occurrence - The occurrence
269
+ * @param {Object} rule - Recurrence rule
270
+ * @returns {string|number} Period key
271
+ * @private
272
+ */
273
+ private static _bySetPosKey;
274
+ /**
275
+ * Occurrences of one period selected by the rule's BYSETPOS positions,
276
+ * in BYSETPOS order
277
+ * @param {Array} group - Occurrences of a single period, in order
278
+ * @param {Object} rule - Rule with bySetPos
279
+ * @returns {Array} Selected occurrences
280
+ * @private
281
+ */
282
+ private static _selectBySetPos;
125
283
  /**
126
284
  * Parse an RRULE string into a rule object
127
285
  * @param {string|import('../types.js').RecurrenceRule} ruleString - RRULE string (e.g., "FREQ=DAILY;INTERVAL=1;COUNT=10") or rule object