@forcecalendar/core 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,14 +80,20 @@ export declare class Calendar {
80
80
  addEvent(eventData: import('../events/Event.js').Event | import('../types.js').EventData): import('../events/Event.js').Event;
81
81
  /**
82
82
  * Update an event
83
- * @param {string} eventId - The event ID
83
+ *
84
+ * An occurrence id taken from view data (see {@link Event.occurrenceId})
85
+ * updates the recurring master, i.e. the whole series.
86
+ * @param {string} eventId - Event id or occurrence id
84
87
  * @param {Object} updates - Properties to update
85
- * @returns {Event} The updated event
88
+ * @returns {Event} The updated event (the master for an occurrence id)
86
89
  */
87
90
  updateEvent(eventId: string, updates: Object): Event;
88
91
  /**
89
92
  * Remove an event
90
- * @param {string} eventId - The event ID
93
+ *
94
+ * An occurrence id taken from view data (see {@link Event.occurrenceId})
95
+ * removes the recurring master, i.e. the whole series.
96
+ * @param {string} eventId - Event id or occurrence id
91
97
  * @returns {boolean} True if removed
92
98
  */
93
99
  removeEvent(eventId: string): boolean;
@@ -99,12 +105,16 @@ export declare class Calendar {
99
105
  deleteEvent(eventId: string): boolean;
100
106
  /**
101
107
  * Get an event by ID
102
- * @param {string} eventId - The event ID
103
- * @returns {Event|null}
108
+ *
109
+ * Occurrence ids taken from view data (`<masterId>_<startMs>`, see
110
+ * {@link Event.occurrenceId}) resolve to the stored recurring master, so
111
+ * every id a renderer hands back can be looked up here.
112
+ * @param {string} eventId - Event id or occurrence id
113
+ * @returns {Event|null} The stored event (the master for an occurrence id) or null
104
114
  */
105
115
  getEvent(eventId: string): Event | null;
106
116
  /**
107
- * Get all events
117
+ * Get all stored events (recurring masters, never their occurrences)
108
118
  * @returns {Event[]}
109
119
  */
110
120
  getEvents(): Event[];
@@ -177,12 +187,23 @@ export declare class Calendar {
177
187
  */
178
188
  queryEvents(filters: Object): Event[];
179
189
  /**
180
- * Get events for a specific date
190
+ * Get events for a specific date, with recurring series expanded into occurrences
181
191
  * @param {Date} date - The date
182
192
  * @param {string} [timezone] - Timezone for the query (defaults to calendar timezone)
183
193
  * @returns {Event[]}
184
194
  */
185
195
  getEventsForDate(date: Date, timezone?: string): Event[];
196
+ /**
197
+ * Get the events for every day in a range, keyed by local date (YYYY-MM-DD)
198
+ *
199
+ * Recurring series are expanded once for the whole range; this is what the
200
+ * month and week views use. See `EventStore.getEventsByDate`.
201
+ * @param {Date} start - First day of the range
202
+ * @param {Date} end - Last day of the range
203
+ * @param {string} [timezone] - Timezone for the query (defaults to calendar timezone)
204
+ * @returns {Map<string, Event[]>} Local date string -> events on that day
205
+ */
206
+ getEventsByDate(start: Date, end: Date, timezone?: string): Map<string, Event[]>;
186
207
  /**
187
208
  * Get events in a date range
188
209
  * @param {Date} start - Start date
@@ -191,6 +212,55 @@ export declare class Calendar {
191
212
  * @returns {Event[]}
192
213
  */
193
214
  getEventsInRange(start: Date, end: Date, timezone?: string): Event[];
215
+ /**
216
+ * Lazily iterate the occurrences of an event in chronological order.
217
+ *
218
+ * Occurrences are produced one at a time, so taking the next few of an
219
+ * open-ended series does not expand the series. `after` and `before`
220
+ * are exclusive unless `inclusive` is set; see
221
+ * RecurrenceEngineV2.iterateOccurrences for the full semantics.
222
+ *
223
+ * @example
224
+ * for (const occurrence of calendar.iterateOccurrences('standup', { after: new Date() })) {
225
+ * if (occurrence.start > deadline) break;
226
+ * remind(occurrence);
227
+ * }
228
+ *
229
+ * @param {string} eventId - The event ID
230
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
231
+ * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>} Occurrences in chronological order
232
+ * @throws {Error} If no event with the ID exists
233
+ */
234
+ iterateOccurrences(eventId: string, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): Generator<import('../types.js').ExpandedOccurrence, void, undefined>;
235
+ /**
236
+ * First occurrence of an event after an instant, or null when the
237
+ * series has no occurrence after it. `after` is exclusive unless
238
+ * `options.inclusive` is set.
239
+ *
240
+ * @example
241
+ * const upcoming = calendar.getNextOccurrence('standup', new Date());
242
+ *
243
+ * @param {string} eventId - The event ID
244
+ * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
245
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Further options
246
+ * @returns {import('../types.js').ExpandedOccurrence|null} The next occurrence, or null
247
+ * @throws {Error} If no event with the ID exists
248
+ */
249
+ getNextOccurrence(eventId: string, after?: Date | number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence | null;
250
+ /**
251
+ * The first `count` occurrences of an event inside a window, generated
252
+ * lazily. `count` is capped at the engine's MAX_OCCURRENCES_HARD_LIMIT.
253
+ *
254
+ * @example
255
+ * const nextFive = calendar.takeOccurrences('standup', 5, { after: new Date() });
256
+ *
257
+ * @param {string} eventId - The event ID
258
+ * @param {number} count - Maximum number of occurrences to return
259
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
260
+ * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
261
+ * @throws {Error} If no event with the ID exists
262
+ */
263
+ takeOccurrences(eventId: string, count: number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence[];
194
264
  /**
195
265
  * Set the calendar's timezone
196
266
  * @param {string} timezone - IANA timezone identifier
@@ -23,6 +23,12 @@ export declare class Event {
23
23
  textColor: string | undefined;
24
24
  recurring: boolean | undefined;
25
25
  recurrenceRule: string | import("../types.js").RecurrenceRule | undefined;
26
+ /** @type {boolean} True when this instance is one occurrence of a recurring series */
27
+ isOccurrence: boolean;
28
+ /** @type {string|null} Id of the recurring master this occurrence belongs to */
29
+ recurringEventId: string | null;
30
+ /** @type {Date|null} Start of this occurrence as generated by the recurrence rule */
31
+ occurrenceStart: Date | null;
26
32
  _originalTimeZone: string | null;
27
33
  status: import("../types.js").EventStatus | undefined;
28
34
  visibility: import("../types.js").EventVisibility | undefined;
@@ -192,6 +198,39 @@ export declare class Event {
192
198
  * @throws {Error} If raw event data fails {@link Event.validate}
193
199
  */
194
200
  static isEquivalent(a: Event | import('../types.js').EventData, b: Event | import('../types.js').EventData): boolean;
201
+ /**
202
+ * Build the id of one occurrence of a recurring series.
203
+ *
204
+ * The id is `<recurringEventId>_<startMs>` where `startMs` is the
205
+ * occurrence start as returned by `Date.prototype.getTime()`. It is
206
+ * deterministic, so the same occurrence gets the same id no matter which
207
+ * range it was expanded for, and it matches the ids generated by
208
+ * `RecurrenceEngineV2`. Use {@link Event.parseOccurrenceId} to get the
209
+ * master id back.
210
+ *
211
+ * @example
212
+ * Event.occurrenceId('standup', new Date(2025, 5, 16, 9)); // 'standup_1750028400000'
213
+ *
214
+ * @param {string} recurringEventId - Id of the recurring master event
215
+ * @param {Date|number|string} occurrenceStart - Start of the occurrence
216
+ * @returns {string} Occurrence id
217
+ */
218
+ static occurrenceId(recurringEventId: string, occurrenceStart: Date | number | string): string;
219
+ /**
220
+ * Split an occurrence id built by {@link Event.occurrenceId} into the master
221
+ * id and the occurrence start.
222
+ *
223
+ * Returns `null` for ids that do not have the `<id>_<startMs>` shape. A
224
+ * positive result only means the id is well-formed; whether the master
225
+ * exists is for the caller (see `EventStore.getEvent`) to check.
226
+ *
227
+ * @param {string} id - Candidate occurrence id
228
+ * @returns {{recurringEventId: string, occurrenceStart: Date}|null} Parsed parts or null
229
+ */
230
+ static parseOccurrenceId(id: string): {
231
+ recurringEventId: string;
232
+ occurrenceStart: Date;
233
+ } | null;
195
234
  /**
196
235
  * Add an attendee to the event
197
236
  * @param {import('../types.js').Attendee} attendee - Attendee to add
@@ -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;
@@ -92,10 +98,23 @@ export declare class EventStore {
92
98
  private _detachEvent;
93
99
  /**
94
100
  * Get an event by ID
95
- * @param {string} eventId - The event ID
96
- * @returns {Event|null} The event or null if not found
101
+ *
102
+ * Occurrence ids produced by {@link EventStore#expandRecurringEvent}
103
+ * (`<masterId>_<startMs>`, see {@link Event.occurrenceId}) resolve to the
104
+ * stored recurring master they were derived from, so an id taken from view
105
+ * data can always be looked up. Occurrences themselves are never stored.
106
+ * @param {string} eventId - Event id or occurrence id
107
+ * @returns {Event|null} The stored event (the master for an occurrence id) or null
97
108
  */
98
109
  getEvent(eventId: string): Event | null;
110
+ /**
111
+ * Resolve an occurrence id to the stored recurring master it belongs to.
112
+ * Not cached: the master's own cache entry is the one kept in sync.
113
+ * @param {string} eventId - Candidate occurrence id
114
+ * @returns {Event|null} The master event or null
115
+ * @private
116
+ */
117
+ private _resolveOccurrenceMaster;
99
118
  /**
100
119
  * Get all events
101
120
  * @returns {Event[]} Array of all events
@@ -109,11 +128,59 @@ export declare class EventStore {
109
128
  queryEvents(filters?: import('../types.js').QueryFilters): Event[];
110
129
  /**
111
130
  * Get events for a specific date
131
+ *
132
+ * Recurring series are expanded for the day, so the result holds their
133
+ * occurrences (see {@link EventStore#expandRecurringEvent}) rather than the
134
+ * master events. When building a grid of days use
135
+ * {@link EventStore#getEventsByDate}, which expands once for the whole range.
112
136
  * @param {Date} date - The date to query
113
137
  * @param {string} [timezone] - Timezone for the query (defaults to store timezone)
114
138
  * @returns {Event[]} Events occurring on the date, sorted by start time
115
139
  */
116
140
  getEventsForDate(date: Date, timezone?: string): Event[];
141
+ /**
142
+ * Get the events for every day in a range, keyed by local date (YYYY-MM-DD)
143
+ *
144
+ * Recurring series are expanded once for the whole range rather than once
145
+ * per day, which is what a month or week grid needs. Every day in the range
146
+ * has an entry (an empty array when nothing occurs) and multi-day events
147
+ * appear under each day they span. Each array is sorted like
148
+ * {@link EventStore#getEventsForDate}.
149
+ *
150
+ * @example
151
+ * const byDate = store.getEventsByDate(gridStart, gridEnd);
152
+ * const events = byDate.get(DateUtils.getLocalDateString(cellDate)) || [];
153
+ *
154
+ * @param {Date} start - First day of the range
155
+ * @param {Date} end - Last day of the range
156
+ * @param {string} [timezone] - Timezone deciding which day an event falls on (defaults to store timezone)
157
+ * @returns {Map<string, Event[]>} Local date string -> events on that day
158
+ */
159
+ getEventsByDate(start: Date, end: Date, timezone?: string): Map<string, Event[]>;
160
+ /**
161
+ * Collect the ids of stored events that may occur on a date.
162
+ * @param {Date} date - The date to query
163
+ * @returns {Set<string>} Candidate event ids
164
+ * @private
165
+ */
166
+ private _collectDateCandidateIds;
167
+ /**
168
+ * Keep the events that overlap a day in the given timezone, sorted by start.
169
+ * @param {Event[]} events - Candidate events
170
+ * @param {Date} dayStart - Start of the day
171
+ * @param {Date} dayEnd - End of the day
172
+ * @param {string} timezone - Timezone deciding whether an event falls on the day
173
+ * @returns {Event[]} Events on the day, sorted
174
+ * @private
175
+ */
176
+ private _selectEventsForDay;
177
+ /**
178
+ * Comparator ordering events by start time in a timezone, longer events first.
179
+ * @param {string} timezone - Timezone used for the start comparison
180
+ * @returns {(a: Event, b: Event) => number} Comparator
181
+ * @private
182
+ */
183
+ private _compareByStart;
117
184
  /**
118
185
  * Get events that overlap with a given time range
119
186
  * @param {Date} start - Start time
@@ -138,6 +205,15 @@ export declare class EventStore {
138
205
  * @returns {Array<Event[]>} Array of event groups that overlap
139
206
  */
140
207
  getOverlapGroups(date: Date, timedOnly?: boolean): Array<Event[]>;
208
+ /**
209
+ * Group a list of events into clusters of overlapping time slots
210
+ * Same result as {@link EventStore#getOverlapGroups} for events already fetched
211
+ * (for example one day of {@link EventStore#getEventsByDate}).
212
+ * @param {Event[]} events - Events to group; the array is not modified
213
+ * @param {boolean} [timedOnly=true] - Only include timed events (not all-day)
214
+ * @returns {Array<Event[]>} Array of event groups that overlap
215
+ */
216
+ groupOverlappingEvents(events: Event[], timedOnly?: boolean): Array<Event[]>;
141
217
  /**
142
218
  * Calculate positions for overlapping events (for rendering)
143
219
  * @param {Event[]} events - Array of overlapping events
@@ -159,6 +235,16 @@ export declare class EventStore {
159
235
  getEventsInRange(start: Date, end: Date, expandRecurringOrOptions?: boolean | Object, timezone?: string): Event[];
160
236
  /**
161
237
  * Expand a recurring event into individual occurrences
238
+ *
239
+ * Returns every occurrence that overlaps the range, including ones that
240
+ * start before it but run into it (multi-day series). Each occurrence is an
241
+ * {@link Event} cloned from the master with:
242
+ * - `id` from {@link Event.occurrenceId} (`<masterId>_<startMs>`), stable
243
+ * across ranges and resolvable with {@link EventStore#getEvent},
244
+ * - `isOccurrence: true`, `recurringEventId` and `occurrenceStart`,
245
+ * - `metadata.recurringEventId`, `metadata.occurrenceId` (same as `id`) and
246
+ * `metadata.occurrenceIndex` (position within this expansion).
247
+ * Non-recurring events are returned as-is in a one-element array.
162
248
  * @param {Event} event - The recurring event
163
249
  * @param {Date} rangeStart - Start of the expansion range
164
250
  * @param {Date} rangeEnd - End of the expansion range
@@ -166,6 +252,78 @@ export declare class EventStore {
166
252
  * @returns {Event[]} Array of event occurrences
167
253
  */
168
254
  expandRecurringEvent(event: Event, rangeStart: Date, rangeEnd: Date, timezone?: string): Event[];
255
+ /**
256
+ * Build the Event instance for one occurrence of a recurring master.
257
+ * @param {Event} event - The recurring master
258
+ * @param {{start: Date, end: Date, timezone?: string}} occurrence - Engine occurrence
259
+ * @param {string} eventTimezone - Timezone the series was expanded in
260
+ * @param {number} index - Position within the current expansion
261
+ * @returns {Event} Occurrence event
262
+ * @private
263
+ */
264
+ private _createOccurrence;
265
+ /**
266
+ * Lazily iterate the occurrences of a stored event in chronological order.
267
+ *
268
+ * Occurrences come one at a time from the store's recurrence engine
269
+ * (RecurrenceEngineV2 by default), so taking the next few occurrences of
270
+ * an open-ended series does not expand the series. `after` and `before`
271
+ * are exclusive unless `inclusive` is set; see
272
+ * RecurrenceEngineV2.iterateOccurrences for the full semantics. The
273
+ * expansion timezone defaults to the event's, then the store's.
274
+ *
275
+ * @example
276
+ * for (const occurrence of store.iterateOccurrences('standup', { after: new Date() })) {
277
+ * if (occurrence.start > deadline) break;
278
+ * remind(occurrence);
279
+ * }
280
+ *
281
+ * @param {string} eventId - The event ID
282
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
283
+ * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>} Occurrences in chronological order
284
+ * @throws {Error} If no event with the ID exists
285
+ */
286
+ iterateOccurrences(eventId: string, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): Generator<import('../types.js').ExpandedOccurrence, void, undefined>;
287
+ /**
288
+ * First occurrence of a stored event after an instant, or null when the
289
+ * series has no occurrence after it. `after` is exclusive unless
290
+ * `options.inclusive` is set.
291
+ *
292
+ * @example
293
+ * const upcoming = store.getNextOccurrence('standup', new Date());
294
+ *
295
+ * @param {string} eventId - The event ID
296
+ * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
297
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Further options
298
+ * @returns {import('../types.js').ExpandedOccurrence|null} The next occurrence, or null
299
+ * @throws {Error} If no event with the ID exists
300
+ */
301
+ getNextOccurrence(eventId: string, after?: Date | number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence | null;
302
+ /**
303
+ * The first `count` occurrences of a stored event inside a window,
304
+ * generated lazily. `count` is capped at the engine's
305
+ * MAX_OCCURRENCES_HARD_LIMIT.
306
+ *
307
+ * @example
308
+ * const nextFive = store.takeOccurrences('standup', 5, { after: new Date() });
309
+ *
310
+ * @param {string} eventId - The event ID
311
+ * @param {number} count - Maximum number of occurrences to return
312
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
313
+ * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
314
+ * @throws {Error} If no event with the ID exists
315
+ */
316
+ takeOccurrences(eventId: string, count: number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence[];
317
+ /**
318
+ * Resolve an occurrence query to the stored event and its options, with
319
+ * the timezone defaulted as expandRecurringEvent does
320
+ * @param {string} eventId - The event ID
321
+ * @param {Object} options - Caller options
322
+ * @returns {{ event: Event, options: Object }}
323
+ * @throws {Error} If no event with the ID exists
324
+ * @private
325
+ */
326
+ private _occurrenceQuery;
169
327
  /**
170
328
  * Clear all events
171
329
  */
@@ -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
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).
@@ -122,6 +261,23 @@ export declare class RecurrenceEngine {
122
261
  * @private
123
262
  */
124
263
  private static _applyBySetPos;
264
+ /**
265
+ * Key of the BYSETPOS period an occurrence belongs to
266
+ * @param {import('../types.js').EventOccurrence} occurrence - The occurrence
267
+ * @param {Object} rule - Recurrence rule
268
+ * @returns {string|number} Period key
269
+ * @private
270
+ */
271
+ private static _bySetPosKey;
272
+ /**
273
+ * Occurrences of one period selected by the rule's BYSETPOS positions,
274
+ * in BYSETPOS order
275
+ * @param {Array} group - Occurrences of a single period, in order
276
+ * @param {Object} rule - Rule with bySetPos
277
+ * @returns {Array} Selected occurrences
278
+ * @private
279
+ */
280
+ private static _selectBySetPos;
125
281
  /**
126
282
  * Parse an RRULE string into a rule object
127
283
  * @param {string|import('../types.js').RecurrenceRule} ruleString - RRULE string (e.g., "FREQ=DAILY;INTERVAL=1;COUNT=10") or rule object