@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/calendar/Calendar.js +93 -17
- package/core/events/Event.js +46 -15
- package/core/events/EventStore.js +184 -20
- package/core/events/RRuleParser.js +19 -3
- package/core/events/RecurrenceEngine.js +63 -18
- package/core/events/RecurrenceEngineV2.js +219 -43
- package/core/index.js +1 -1
- package/core/integration/EnhancedCalendar.js +38 -0
- package/core/timezone/TimezoneManager.js +45 -9
- package/core/types.js +9 -0
- package/package.json +1 -1
- package/types/calendar/Calendar.d.ts +50 -6
- package/types/events/Event.d.ts +11 -0
- package/types/events/EventStore.d.ts +76 -12
- package/types/events/RRuleParser.d.ts +10 -1
- package/types/events/RecurrenceEngine.d.ts +4 -2
- package/types/events/RecurrenceEngineV2.d.ts +68 -9
- package/types/index.d.ts +1 -1
- package/types/integration/EnhancedCalendar.d.ts +7 -0
- package/types/types.d.ts +26 -0
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
|
@@ -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 -
|
|
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 -
|
|
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 -
|
|
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
|
-
*
|
|
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
|
/**
|
package/types/events/Event.d.ts
CHANGED
|
@@ -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 -
|
|
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 -
|
|
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 -
|
|
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
|
|
351
|
-
* before the store is modified.
|
|
352
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the same cost as one that
|
|
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 (
|
|
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)
|
|
140
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|