@forcecalendar/core 2.5.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.
package/core/types.js CHANGED
@@ -324,6 +324,15 @@
324
324
  * @property {import('./events/Event.js').Event[]} unchanged - Events left untouched
325
325
  */
326
326
 
327
+ /**
328
+ * Payload of the Calendar `eventSelect` event
329
+ * @typedef {Object} EventSelectPayload
330
+ * @property {import('./events/Event.js').Event} event - The stored event (the master for an occurrence id)
331
+ * @property {string} eventId - Id of the stored event, as kept in the state's selectedEventId
332
+ * @property {string|null} occurrenceId - The occurrence id that was selected, or null for a stored event's id
333
+ * @property {import('./events/Event.js').Event|null} occurrence - The selected occurrence as in view data, or null
334
+ */
335
+
327
336
  /**
328
337
  * @typedef {Object} QueryFilters
329
338
  * @property {Date} [start] - Start date for range query
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcecalendar/core",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "A modern, lightweight, framework-agnostic calendar engine optimized for Salesforce",
@@ -113,6 +113,29 @@ export declare class Calendar {
113
113
  * @returns {Event|null} The stored event (the master for an occurrence id) or null
114
114
  */
115
115
  getEvent(eventId: string): Event | null;
116
+ /**
117
+ * Resolve an event id or occurrence id to the id of the stored event it
118
+ * refers to: the id itself for a stored event, the master's id for an
119
+ * occurrence id taken from view data, `null` when nothing stored matches.
120
+ * See `EventStore.resolveEventId`.
121
+ *
122
+ * @example
123
+ * calendar.resolveEventId('standup_1750028400000'); // 'standup'
124
+ * calendar.resolveEventId('unknown'); // null
125
+ *
126
+ * @param {string} id - Event id or occurrence id
127
+ * @returns {string|null} Id of the stored event, or null
128
+ */
129
+ resolveEventId(id: string): string | null;
130
+ /**
131
+ * Get the occurrence an occurrence id from view data stands for, as an
132
+ * {@link Event} like the ones the views hold, or `null` when the id is
133
+ * not an occurrence of a stored recurring series. See
134
+ * `EventStore.getOccurrence`.
135
+ * @param {string} occurrenceId - Occurrence id (`<masterId>_<startMs>`)
136
+ * @returns {Event|null} The occurrence, or null
137
+ */
138
+ getOccurrence(occurrenceId: string): Event | null;
116
139
  /**
117
140
  * Get all stored events (recurring masters, never their occurrences)
118
141
  * @returns {Event[]}
@@ -166,9 +189,16 @@ export declare class Calendar {
166
189
  * @param {Array<import('../events/Event.js').Event|import('../types.js').EventData>} events - Complete snapshot of events
167
190
  * @param {import('../types.js').ReconcileOptions} [options={}] - Reconcile options
168
191
  * @returns {import('../types.js').EventsSetPayload} Resulting events and the applied change set
169
- * @throws {Error} If an entry fails validation or two entries share an id
192
+ * @throws {Error} If `events` is not iterable, an entry fails validation or two entries share an id
170
193
  */
171
194
  reconcileEvents(events: Array<import('../events/Event.js').Event | import('../types.js').EventData>, options?: import('../types.js').ReconcileOptions): import('../types.js').EventsSetPayload;
195
+ /**
196
+ * Throw a clear error when a snapshot is not iterable
197
+ * @param {*} events - Candidate snapshot
198
+ * @param {string} method - Calling method, for the message
199
+ * @private
200
+ */
201
+ private _assertIterable;
172
202
  /**
173
203
  * Get the event store's change counter
174
204
  *
@@ -226,7 +256,7 @@ export declare class Calendar {
226
256
  * remind(occurrence);
227
257
  * }
228
258
  *
229
- * @param {string} eventId - The event ID
259
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
230
260
  * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
231
261
  * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>} Occurrences in chronological order
232
262
  * @throws {Error} If no event with the ID exists
@@ -240,7 +270,7 @@ export declare class Calendar {
240
270
  * @example
241
271
  * const upcoming = calendar.getNextOccurrence('standup', new Date());
242
272
  *
243
- * @param {string} eventId - The event ID
273
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
244
274
  * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
245
275
  * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Further options
246
276
  * @returns {import('../types.js').ExpandedOccurrence|null} The next occurrence, or null
@@ -254,8 +284,8 @@ export declare class Calendar {
254
284
  * @example
255
285
  * const nextFive = calendar.takeOccurrences('standup', 5, { after: new Date() });
256
286
  *
257
- * @param {string} eventId - The event ID
258
- * @param {number} count - Maximum number of occurrences to return
287
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
288
+ * @param {number} count - Maximum number of occurrences to return (fractions are floored)
259
289
  * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
260
290
  * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
261
291
  * @throws {Error} If no event with the ID exists
@@ -363,7 +393,21 @@ export declare class Calendar {
363
393
  private _getListViewData;
364
394
  /**
365
395
  * Select an event
366
- * @param {string} eventId - Event ID to select
396
+ *
397
+ * Accepts a stored event's id or an occurrence id taken from view data
398
+ * (see {@link Event.occurrenceId}). Either way the stored event is what
399
+ * gets selected: `selectedEventId` in the state holds its id, so it can
400
+ * always be looked up with {@link Calendar#getEvent}. The `eventSelect`
401
+ * payload carries the stored `event`, its `eventId`, and for an
402
+ * occurrence id also `occurrenceId` and the `occurrence` itself (an
403
+ * {@link Event} as in view data, or null when the series has no
404
+ * occurrence at that instant). Nothing happens for an unknown id.
405
+ *
406
+ * @example
407
+ * calendar.on('eventSelect', ({ event, occurrence }) => open(occurrence || event));
408
+ * calendar.selectEvent(chip.dataset.eventId);
409
+ *
410
+ * @param {string} eventId - Event id or occurrence id to select
367
411
  */
368
412
  selectEvent(eventId: string): void;
369
413
  /**
@@ -167,6 +167,8 @@ export declare class Event {
167
167
  * Scalars are compared with strict equality, dates by timestamp and
168
168
  * structured fields (recurrence rule, organizer, attendees, reminders,
169
169
  * categories, attachments, conference data, metadata) structurally.
170
+ * The `color` shorthand is not listed: normalization copies it into
171
+ * `backgroundColor` and `borderColor`, which are compared instead.
170
172
  * @type {ReadonlyArray<string>}
171
173
  */
172
174
  static EQUIVALENCE_FIELDS: ReadonlyArray<string>;
@@ -185,6 +187,14 @@ export declare class Event {
185
187
  * describe the same event. Two events with different ids are never
186
188
  * equivalent.
187
189
  *
190
+ * Two things to know when building snapshots for `reconcile()`:
191
+ * - `attendees`, `reminders`, `categories` and `attachments` are compared
192
+ * in order, so the same attendees listed in a different order count as
193
+ * a change.
194
+ * - Only the top-level dates are normalized. Values inside `metadata` are
195
+ * compared as given, so a `Date` and its ISO string are not equivalent
196
+ * there; keep metadata in one representation.
197
+ *
188
198
  * This is the default comparator used by `EventStore.reconcile()` to decide
189
199
  * whether an incoming snapshot entry replaces the stored event.
190
200
  *
@@ -214,6 +224,7 @@ export declare class Event {
214
224
  * @param {string} recurringEventId - Id of the recurring master event
215
225
  * @param {Date|number|string} occurrenceStart - Start of the occurrence
216
226
  * @returns {string} Occurrence id
227
+ * @throws {TypeError} If occurrenceStart is not a valid date
217
228
  */
218
229
  static occurrenceId(recurringEventId: string, occurrenceStart: Date | number | string): string;
219
230
  /**
@@ -96,6 +96,14 @@ export declare class EventStore {
96
96
  * @private
97
97
  */
98
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;
99
107
  /**
100
108
  * Get an event by ID
101
109
  *
@@ -115,6 +123,40 @@ export declare class EventStore {
115
123
  * @private
116
124
  */
117
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;
118
160
  /**
119
161
  * Get all events
120
162
  * @returns {Event[]} Array of all events
@@ -278,7 +320,7 @@ export declare class EventStore {
278
320
  * remind(occurrence);
279
321
  * }
280
322
  *
281
- * @param {string} eventId - The event ID
323
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
282
324
  * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
283
325
  * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>} Occurrences in chronological order
284
326
  * @throws {Error} If no event with the ID exists
@@ -292,7 +334,7 @@ export declare class EventStore {
292
334
  * @example
293
335
  * const upcoming = store.getNextOccurrence('standup', new Date());
294
336
  *
295
- * @param {string} eventId - The event ID
337
+ * @param {string} eventId - Event id or occurrence id (resolved to its master)
296
338
  * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
297
339
  * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Further options
298
340
  * @returns {import('../types.js').ExpandedOccurrence|null} The next occurrence, or null
@@ -307,8 +349,8 @@ export declare class EventStore {
307
349
  * @example
308
350
  * const nextFive = store.takeOccurrences('standup', 5, { after: new Date() });
309
351
  *
310
- * @param {string} eventId - The event ID
311
- * @param {number} count - Maximum number of occurrences to return
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)
312
354
  * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
313
355
  * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
314
356
  * @throws {Error} If no event with the ID exists
@@ -343,13 +385,23 @@ export declare class EventStore {
343
385
  * - adds events whose id is not in the store (`add` change),
344
386
  * - removes stored events missing from the snapshot (`remove` change),
345
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,
346
394
  * - emits a single `batch` notification listing those changes, or nothing at
347
395
  * all when the snapshot matches the store. When called while a batch is
348
396
  * already open the changes are queued on that batch instead.
349
397
  *
350
- * Input is validated up front: invalid event data or duplicate ids throw
351
- * before the store is modified. Any error raised while applying the diff
352
- * 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).
353
405
  *
354
406
  * @example
355
407
  * // periodic server snapshot
@@ -362,6 +414,16 @@ export declare class EventStore {
362
414
  * @throws {Error} If an entry fails validation or two entries share an id
363
415
  */
364
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;
365
427
  /**
366
428
  * Subscribe to store changes
367
429
  * @param {Function} callback - Callback function
@@ -399,16 +461,18 @@ export declare class EventStore {
399
461
  */
400
462
  private _removeFromReferencedIndex;
401
463
  /**
402
- * 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
403
466
  * @private
404
467
  */
468
+ private _queueChange;
405
469
  /**
406
- * 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.
407
472
  * @param {import('../types.js').EventStoreChange} change - Change to deliver
408
473
  * @private
409
474
  */
410
- private _queueChange;
411
- _notifyChange(change: any): void;
475
+ private _notifyChange;
412
476
  /**
413
477
  * Get store statistics
414
478
  * @returns {Object}
@@ -466,7 +530,7 @@ export declare class EventStore {
466
530
  */
467
531
  getPerformanceMetrics(): Object;
468
532
  /**
469
- * Clear all caches
533
+ * Clear all caches, including the recurrence engine's cached expansions
470
534
  */
471
535
  clearCaches(): void;
472
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;
@@ -94,7 +94,7 @@ export declare class RecurrenceEngine {
94
94
  * const nextFive = RecurrenceEngine.takeOccurrences(event, 5, { after: new Date() });
95
95
  *
96
96
  * @param {import('./Event.js').Event} event - The event to query
97
- * @param {number} count - Maximum number of occurrences to return
97
+ * @param {number} count - Maximum number of occurrences to return (fractions are floored)
98
98
  * @param {import('../types.js').OccurrenceIteratorOptions} [options={}] - Window and timezone
99
99
  * @returns {import('../types.js').EventOccurrence[]} Up to `count` occurrences in chronological order
100
100
  */
@@ -239,7 +239,9 @@ export declare class RecurrenceEngine {
239
239
  private static _seekWeekCycle;
240
240
  /**
241
241
  * Find the next system-timezone offset transition after fromMs.
242
- * 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.
243
245
  * @param {number} fromMs - Search from this timestamp (exclusive)
244
246
  * @param {number} toMs - Extend cache coverage at least this far
245
247
  * @returns {number} Transition timestamp, or Infinity if none within coverage
@@ -15,10 +15,15 @@ export declare class RecurrenceEngineV2 {
15
15
  /**
16
16
  * Expand recurring event with advanced handling
17
17
  *
18
- * Occurrences before rangeStart are skipped without being generated:
19
- * daily, weekly, hourly and minutely rules seek straight to the range, so
20
- * a series that started years before the queried window is expanded at
21
- * the same cost as one that started yesterday.
18
+ * Occurrences before rangeStart are skipped without being generated for
19
+ * the rules seekToRange can seek: DAILY (without BYHOUR), WEEKLY (with or
20
+ * without BYDAY), HOURLY and MINUTELY. Such a series that started years
21
+ * before the queried window is expanded at the same cost as one that
22
+ * started yesterday. MONTHLY and YEARLY rules, and DAILY with BYHOUR,
23
+ * are stepped from DTSTART; they take few enough steps per year that
24
+ * this is cheap, but an expansion that would need more than
25
+ * MAX_ITERATIONS_HARD_LIMIT steps is truncated (with one console.warn
26
+ * per process).
22
27
  *
23
28
  * @param {import('./Event.js').Event} event - Recurring event
24
29
  * @param {Date} rangeStart - Start of expansion range
@@ -46,7 +51,7 @@ export declare class RecurrenceEngineV2 {
46
51
  * time and without the expansion cache: stored instance modifications
47
52
  * and exceptions are applied as each occurrence is produced, so changes
48
53
  * made through addModifiedInstance or addException are visible on the
49
- * next pull. Rules seekToRange can seek (plain daily and weekly, hourly,
54
+ * next pull. Rules seekToRange can seek (daily, weekly, hourly,
50
55
  * minutely) jump straight to `after`, and DST transitions are scanned
51
56
  * lazily ahead of the cursor instead of for the whole window up front.
52
57
  *
@@ -98,7 +103,7 @@ export declare class RecurrenceEngineV2 {
98
103
  * const nextFive = engine.takeOccurrences(event, 5, { after: new Date() });
99
104
  *
100
105
  * @param {import('./Event.js').Event} event - The event to query
101
- * @param {number} count - Maximum number of occurrences to return
106
+ * @param {number} count - Maximum number of occurrences to return (fractions are floored)
102
107
  * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
103
108
  * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
104
109
  */
@@ -136,8 +141,9 @@ export declare class RecurrenceEngineV2 {
136
141
  * without stepping through every occurrence in between.
137
142
  *
138
143
  * Applies to rules whose step is a fixed duration between system-timezone
139
- * transitions (plain DAILY and WEEKLY, HOURLY, MINUTELY); the step that
140
- * crosses a transition is taken with getNextDate so the result is exactly
144
+ * transitions (plain DAILY and WEEKLY, HOURLY, MINUTELY) and to WEEKLY
145
+ * rules with BYDAY, whose steps repeat in a weekly cycle; the steps that
146
+ * cross a transition are taken with getNextDate so the result is exactly
141
147
  * what stepping from DTSTART would produce. Never seeks past UNTIL, and
142
148
  * counts skipped steps against COUNT.
143
149
  *
@@ -148,6 +154,40 @@ export declare class RecurrenceEngineV2 {
148
154
  * @param {string} timezone - Expansion timezone
149
155
  */
150
156
  seekToRange(state: Object, rule: Object, rangeStart: Date, rangeEnd: Date, timezone: string): void;
157
+ /**
158
+ * Seek for WEEKLY rules with BYDAY. getNextWeekly picks the next weekday
159
+ * from the BYDAY list (in list order), so the step from each weekday is
160
+ * fixed and the walk from DTSTART settles into a cycle of weekdays that
161
+ * repeats every whole number of weeks. The cursor is stepped one
162
+ * occurrence at a time until it is on that cycle (at most six steps),
163
+ * then whole cycles are skipped arithmetically between system-timezone
164
+ * transitions, exactly as seekToRange does for fixed steps.
165
+ * @param {Object} state - Expansion state (currentDate and count are updated)
166
+ * @param {Object} rule - Parsed recurrence rule
167
+ * @param {Date} rangeStart - Start of expansion range
168
+ * @param {Date} rangeEnd - End of expansion range
169
+ * @param {string} timezone - Expansion timezone
170
+ * @private
171
+ */
172
+ private _seekWeekCycle;
173
+ /**
174
+ * Days getNextWeekly adds from each weekday (index 0-6) for a WEEKLY
175
+ * rule with BYDAY, or null when the rule cannot be seeked (an invalid
176
+ * interval or day code, which the expansion loop handles as before)
177
+ * @param {Object} rule - Parsed recurrence rule
178
+ * @returns {number[]|null} Delta table indexed by Date#getDay()
179
+ * @private
180
+ */
181
+ private _weekdayDeltas;
182
+ /**
183
+ * Weekday numbers (Date#getDay) of a rule's BYDAY entries in ascending
184
+ * order, computed once per parsed rule. Invalid day codes map to
185
+ * undefined and sort last.
186
+ * @param {Object} rule - Parsed recurrence rule with byDay
187
+ * @returns {number[]} Sorted weekday numbers
188
+ * @private
189
+ */
190
+ private _weekdayTargets;
151
191
  /**
152
192
  * Milliseconds per step for rules getNextDate advances by a fixed
153
193
  * duration while the system UTC offset is constant
@@ -248,8 +288,27 @@ export declare class RecurrenceEngineV2 {
248
288
  getDateKey(date: any): string;
249
289
  /**
250
290
  * Create cache key
291
+ *
292
+ * When given the event itself the key also covers everything the
293
+ * expansion depends on (DTSTART, end, recurrence rule), so a series that
294
+ * is updated, replaced or re-added under the same id can never be served
295
+ * a stale expansion. Keys always start with `<eventId>_`, which is what
296
+ * {@link RecurrenceEngineV2#clearEventCache} matches on.
297
+ * @param {import('./Event.js').Event|string} event - Recurring event, or just its id
298
+ * @param {Date} start - Start of expansion range
299
+ * @param {Date} end - End of expansion range
300
+ * @param {Object} options - Expansion options
301
+ * @returns {string} Cache key
302
+ */
303
+ getCacheKey(event: import('./Event.js').Event | string, start: Date, end: Date, options: Object): string;
304
+ /**
305
+ * Stable text form of a recurrence rule for cache keys. A rule that
306
+ * cannot be serialised gets a unique fingerprint, i.e. is never cached.
307
+ * @param {string|Object} rule - RRULE string or rule object
308
+ * @returns {string} Fingerprint
309
+ * @private
251
310
  */
252
- getCacheKey(eventId: any, start: any, end: any, options: any): string;
311
+ private _ruleFingerprint;
253
312
  /**
254
313
  * Cache occurrences
255
314
  */
package/types/index.d.ts CHANGED
@@ -18,5 +18,5 @@ export { RRuleParser } from './events/RRuleParser.js';
18
18
  export { TimezoneManager } from './timezone/TimezoneManager.js';
19
19
  export { ConflictDetector } from './conflicts/ConflictDetector.js';
20
20
  export { EnhancedCalendar } from './integration/EnhancedCalendar.js';
21
- export declare const VERSION = "2.5.0";
21
+ export declare const VERSION = "2.5.1";
22
22
  export { Calendar as default } from './calendar/Calendar.js';
@@ -13,8 +13,15 @@ export declare class EnhancedCalendar extends Calendar {
13
13
  expansionTime: never[];
14
14
  renderTime: never[];
15
15
  };
16
+ _unsubscribeCacheInvalidation: Function;
16
17
  _clearReindexTimeout: (() => void) | null | undefined;
17
18
  constructor(config: any);
19
+ /**
20
+ * Invalidate the enhanced engine's cached expansions for a store change
21
+ * @param {import('../types.js').EventStoreChange} change - Store change
22
+ * @private
23
+ */
24
+ private _invalidateOccurrenceCache;
18
25
  /**
19
26
  * Enhanced search with worker support
20
27
  */
package/types/types.d.ts CHANGED
@@ -789,6 +789,24 @@ export type EventsSetPayload = {
789
789
  */
790
790
  unchanged: import('./events/Event.js').Event[];
791
791
  };
792
+ export type EventSelectPayload = {
793
+ /**
794
+ * - The stored event (the master for an occurrence id)
795
+ */
796
+ event: import('./events/Event.js').Event;
797
+ /**
798
+ * - Id of the stored event, as kept in the state's selectedEventId
799
+ */
800
+ eventId: string;
801
+ /**
802
+ * - The occurrence id that was selected, or null for a stored event's id
803
+ */
804
+ occurrenceId: string | null;
805
+ /**
806
+ * - The selected occurrence as in view data, or null
807
+ */
808
+ occurrence: import('./events/Event.js').Event | null;
809
+ };
792
810
  export type QueryFilters = {
793
811
  /**
794
812
  * - Start date for range query
@@ -1393,6 +1411,14 @@ export type ConflictSummary = {
1393
1411
  * @property {import('./events/Event.js').Event[]} removed - Events removed by the operation
1394
1412
  * @property {import('./events/Event.js').Event[]} unchanged - Events left untouched
1395
1413
  */
1414
+ /**
1415
+ * Payload of the Calendar `eventSelect` event
1416
+ * @typedef {Object} EventSelectPayload
1417
+ * @property {import('./events/Event.js').Event} event - The stored event (the master for an occurrence id)
1418
+ * @property {string} eventId - Id of the stored event, as kept in the state's selectedEventId
1419
+ * @property {string|null} occurrenceId - The occurrence id that was selected, or null for a stored event's id
1420
+ * @property {import('./events/Event.js').Event|null} occurrence - The selected occurrence as in view data, or null
1421
+ */
1396
1422
  /**
1397
1423
  * @typedef {Object} QueryFilters
1398
1424
  * @property {Date} [start] - Start date for range query