@forcecalendar/core 2.3.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.
@@ -10,16 +10,151 @@ export declare class RecurrenceEngineV2 {
10
10
  modifiedInstances: Map<any, any>;
11
11
  exceptionStore: Map<any, any>;
12
12
  static MAX_OCCURRENCES_HARD_LIMIT: number;
13
+ static MAX_ITERATIONS_HARD_LIMIT: number;
13
14
  constructor();
14
15
  /**
15
16
  * Expand recurring event with advanced handling
16
- * @param {Event} event - Recurring event
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.
22
+ *
23
+ * @param {import('./Event.js').Event} event - Recurring event
17
24
  * @param {Date} rangeStart - Start of expansion range
18
25
  * @param {Date} rangeEnd - End of expansion range
19
26
  * @param {Object} options - Expansion options
20
- * @returns {Array} Expanded occurrences
27
+ * @param {number} [options.maxOccurrences=365] - Maximum number of occurrences to
28
+ * return. Only occurrences inside the range count towards this limit.
29
+ * @param {boolean} [options.includeModified=true] - Apply stored instance modifications
30
+ * @param {boolean} [options.includeCancelled=false] - Return exception dates as cancelled occurrences
31
+ * @param {string} [options.timezone] - Timezone for expansion (defaults to the event's)
32
+ * @param {boolean} [options.handleDST=true] - Adjust occurrences across DST transitions
33
+ * @returns {import('../types.js').ExpandedOccurrence[]} Expanded occurrences
21
34
  */
22
- expandEvent(event: Event, rangeStart: Date, rangeEnd: Date, options?: Object): any[];
35
+ expandEvent(event: import('./Event.js').Event, rangeStart: Date, rangeEnd: Date, options?: {
36
+ maxOccurrences?: number;
37
+ includeModified?: boolean;
38
+ includeCancelled?: boolean;
39
+ timezone?: string;
40
+ handleDST?: boolean;
41
+ }): import('../types.js').ExpandedOccurrence[];
42
+ /**
43
+ * Lazily iterate the occurrences of an event in chronological order.
44
+ *
45
+ * Yields what expandEvent returns for the window, one occurrence at a
46
+ * time and without the expansion cache: stored instance modifications
47
+ * and exceptions are applied as each occurrence is produced, so changes
48
+ * made through addModifiedInstance or addException are visible on the
49
+ * next pull. Rules seekToRange can seek (plain daily and weekly, hourly,
50
+ * minutely) jump straight to `after`, and DST transitions are scanned
51
+ * lazily ahead of the cursor instead of for the whole window up front.
52
+ *
53
+ * Both bounds are exclusive unless `inclusive` is set: an occurrence that
54
+ * starts exactly at `after` or `before` is skipped by default, so
55
+ * iterating from a known occurrence's start continues the series without
56
+ * repeating it; with `inclusive: true` the window is closed on both ends
57
+ * like expandEvent's range. A non-recurring event yields its single
58
+ * occurrence when it falls inside the window. Iteration ends at COUNT or
59
+ * UNTIL, at `before`, or — as a guard for rules that produce no
60
+ * occurrences — after MAX_ITERATIONS_HARD_LIMIT consecutive steps
61
+ * without one. The generator is single-use; call again for a fresh one.
62
+ *
63
+ * @example
64
+ * const engine = new RecurrenceEngineV2();
65
+ * for (const occurrence of engine.iterateOccurrences(event, { after: new Date() })) {
66
+ * if (occurrence.start > deadline) break;
67
+ * schedule(occurrence);
68
+ * }
69
+ *
70
+ * @param {import('./Event.js').Event} event - The event to iterate
71
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
72
+ * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>} Occurrences in chronological order
73
+ * @throws {TypeError} If `after` or `before` is not a valid Date or timestamp
74
+ */
75
+ iterateOccurrences(event: import('./Event.js').Event, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): Generator<import('../types.js').ExpandedOccurrence, void, undefined>;
76
+ /**
77
+ * First occurrence of an event after an instant, or null when the series
78
+ * has no occurrence after it. `after` is exclusive unless
79
+ * `options.inclusive` is set, so passing the start of a known occurrence
80
+ * returns the one that follows it.
81
+ *
82
+ * @example
83
+ * const upcoming = engine.nextOccurrence(event, new Date());
84
+ *
85
+ * @param {import('./Event.js').Event} event - The event to query
86
+ * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
87
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Further options
88
+ * @returns {import('../types.js').ExpandedOccurrence|null} The next occurrence, or null
89
+ */
90
+ nextOccurrence(event: import('./Event.js').Event, after?: Date | number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence | null;
91
+ /**
92
+ * The first `count` occurrences of an event inside a window, generated
93
+ * lazily so an open-ended series costs only the occurrences taken.
94
+ * `count` is capped at MAX_OCCURRENCES_HARD_LIMIT; fewer are returned
95
+ * when the series or the window ends first.
96
+ *
97
+ * @example
98
+ * const nextFive = engine.takeOccurrences(event, 5, { after: new Date() });
99
+ *
100
+ * @param {import('./Event.js').Event} event - The event to query
101
+ * @param {number} count - Maximum number of occurrences to return
102
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
103
+ * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
104
+ */
105
+ takeOccurrences(event: import('./Event.js').Event, count: number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence[];
106
+ /**
107
+ * Yield a non-recurring event's single occurrence if it starts inside
108
+ * the window
109
+ * @param {import('./Event.js').Event} event - The event
110
+ * @param {{ startMs: number, endMs: number }} window - Inclusive bounds
111
+ * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>}
112
+ * @private
113
+ */
114
+ private _iterateSingle;
115
+ /**
116
+ * Lazy counterpart of the expandEvent loop: seeks to the window, then
117
+ * steps the cursor and yields each in-window occurrence with the same
118
+ * DST adjustment, exception handling and instance modifications.
119
+ * @private
120
+ */
121
+ private _iterateRule;
122
+ /**
123
+ * Apply exceptions and stored instance modifications to a generated
124
+ * occurrence
125
+ * @param {import('./Event.js').Event} event - The recurring event
126
+ * @param {Object} occurrence - Occurrence from generateOccurrence
127
+ * @param {Object} rule - Parsed recurrence rule
128
+ * @param {boolean} includeCancelled - Return exception dates as cancelled occurrences
129
+ * @param {boolean} includeModified - Apply stored instance modifications
130
+ * @returns {Object|null} The occurrence, or null when it is excluded
131
+ * @private
132
+ */
133
+ private _applyOverrides;
134
+ /**
135
+ * Move the expansion cursor to the last occurrence before the range
136
+ * without stepping through every occurrence in between.
137
+ *
138
+ * 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
141
+ * what stepping from DTSTART would produce. Never seeks past UNTIL, and
142
+ * counts skipped steps against COUNT.
143
+ *
144
+ * @param {Object} state - Expansion state (currentDate and count are updated)
145
+ * @param {Object} rule - Parsed recurrence rule
146
+ * @param {Date} rangeStart - Start of expansion range
147
+ * @param {Date} rangeEnd - End of expansion range
148
+ * @param {string} timezone - Expansion timezone
149
+ */
150
+ seekToRange(state: Object, rule: Object, rangeStart: Date, rangeEnd: Date, timezone: string): void;
151
+ /**
152
+ * Milliseconds per step for rules getNextDate advances by a fixed
153
+ * duration while the system UTC offset is constant
154
+ * @param {Object} rule - Parsed recurrence rule
155
+ * @returns {number} Step length in milliseconds, or 0 when not fixed
156
+ */
157
+ getFixedStepMs(rule: Object): number;
23
158
  /**
24
159
  * Generate a single occurrence with timezone handling
25
160
  */
@@ -68,12 +203,18 @@ export declare class RecurrenceEngineV2 {
68
203
  /**
69
204
  * Find DST transitions in date range
70
205
  */
71
- findDSTTransitions(start: any, end: any, timezone: any): {
72
- date: Date;
73
- oldOffset: number;
74
- newOffset: number;
75
- type: string;
76
- }[];
206
+ findDSTTransitions(start: any, end: any, timezone: any): any[];
207
+ /**
208
+ * Walk the scan cursor one day at a time up to untilMs, appending each
209
+ * offset change. The cursor and last offset persist in `scan`, so the
210
+ * walk can be resumed later on the same day grid.
211
+ * @param {{ cursor: Date, lastOffset: number }} scan - Resumable scan position (mutated)
212
+ * @param {Array} transitions - Transition list to append to
213
+ * @param {number} untilMs - Scan through this timestamp (inclusive)
214
+ * @param {string} timezone - Timezone to probe
215
+ * @private
216
+ */
217
+ private _scanDSTTransitions;
77
218
  /**
78
219
  * Adjust occurrence for DST transitions
79
220
  */
@@ -117,6 +258,12 @@ export declare class RecurrenceEngineV2 {
117
258
  * Clone occurrence results before returning or caching.
118
259
  */
119
260
  cloneOccurrences(occurrences: any): any;
261
+ /**
262
+ * Clone a single occurrence, copying its Date and array fields.
263
+ * @param {import('../types.js').ExpandedOccurrence} occurrence - Occurrence to clone
264
+ * @returns {import('../types.js').ExpandedOccurrence} Independent copy
265
+ */
266
+ cloneOccurrence(occurrence: import('../types.js').ExpandedOccurrence): import('../types.js').ExpandedOccurrence;
120
267
  /**
121
268
  * Clear cache for specific event
122
269
  */
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.3.0";
21
+ export declare const VERSION = "2.5.0";
22
22
  export { Calendar as default } from './calendar/Calendar.js';
@@ -21,8 +21,36 @@ export declare class EnhancedCalendar extends Calendar {
21
21
  search(query: any, options?: {}): Promise<any>;
22
22
  /**
23
23
  * Get events with enhanced recurrence expansion
24
+ *
25
+ * Regular events overlapping the range are returned as stored. Every
26
+ * recurring series in the store is expanded with this calendar's
27
+ * RecurrenceEngineV2 (so instance modifications and cancellations apply),
28
+ * including series that started before the range. Occurrences are the
29
+ * engine's plain occurrence objects, with the id `<masterId>_<startMs>`
30
+ * (see `Event.occurrenceId`), `recurringEventId`, `isOccurrence: true` and
31
+ * `occurrenceStart`.
24
32
  */
25
- getEventsInRange(startDate: any, endDate: any, options?: {}): any[];
33
+ getEventsInRange(startDate: any, endDate: any, options?: {}): (import("../index.js").Event | {
34
+ id: string;
35
+ recurringEventId?: string;
36
+ title: string;
37
+ start: Date;
38
+ end: Date;
39
+ startUTC?: Date;
40
+ endUTC?: Date;
41
+ timezone: string;
42
+ originalStart?: Date;
43
+ allDay: boolean;
44
+ description?: string;
45
+ location?: string;
46
+ categories?: string[];
47
+ status?: import("../types.js").EventStatus;
48
+ cancellationReason?: string;
49
+ isRecurring: boolean;
50
+ isModified?: boolean;
51
+ isOccurrence: boolean;
52
+ occurrenceStart: Date;
53
+ })[];
26
54
  /**
27
55
  * Modify a single occurrence of a recurring event
28
56
  */
@@ -31,6 +59,43 @@ export declare class EnhancedCalendar extends Calendar {
31
59
  * Cancel a single occurrence of a recurring event
32
60
  */
33
61
  cancelOccurrence(eventId: any, occurrenceDate: any, reason?: string): void;
62
+ /**
63
+ * Lazily iterate the occurrences of an event through the enhanced
64
+ * engine, so occurrences changed with modifyOccurrence or cancelled with
65
+ * cancelOccurrence are reflected. Same semantics as
66
+ * Calendar#iterateOccurrences.
67
+ * @param {string} eventId - The event ID
68
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
69
+ * @returns {Generator<import('../types.js').ExpandedOccurrence, void, undefined>} Occurrences in chronological order
70
+ * @throws {Error} If no event with the ID exists
71
+ */
72
+ iterateOccurrences(eventId: string, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): Generator<import('../types.js').ExpandedOccurrence, void, undefined>;
73
+ /**
74
+ * First occurrence of an event after an instant through the enhanced
75
+ * engine, or null. Same semantics as Calendar#getNextOccurrence.
76
+ * @param {string} eventId - The event ID
77
+ * @param {Date|number} [after=null] - Instant to search from (defaults to the series start)
78
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Further options
79
+ * @returns {import('../types.js').ExpandedOccurrence|null} The next occurrence, or null
80
+ * @throws {Error} If no event with the ID exists
81
+ */
82
+ getNextOccurrence(eventId: string, after?: Date | number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence | null;
83
+ /**
84
+ * The first `count` occurrences of an event through the enhanced
85
+ * engine. Same semantics as Calendar#takeOccurrences.
86
+ * @param {string} eventId - The event ID
87
+ * @param {number} count - Maximum number of occurrences to return
88
+ * @param {import('../types.js').ExpandedOccurrenceIteratorOptions} [options={}] - Window and expansion options
89
+ * @returns {import('../types.js').ExpandedOccurrence[]} Up to `count` occurrences in chronological order
90
+ * @throws {Error} If no event with the ID exists
91
+ */
92
+ takeOccurrences(eventId: string, count: number, options?: import('../types.js').ExpandedOccurrenceIteratorOptions): import('../types.js').ExpandedOccurrence[];
93
+ /**
94
+ * Resolve an occurrence query to the stored event and its options, with
95
+ * the timezone defaulted as getEventsInRange does
96
+ * @private
97
+ */
98
+ private _occurrenceQuery;
34
99
  /**
35
100
  * Bulk operations for recurring events
36
101
  */
package/types/types.d.ts CHANGED
@@ -688,7 +688,7 @@ export type EventStoreChange = {
688
688
  /**
689
689
  * - Type of change
690
690
  */
691
- type: ('add' | 'update' | 'remove' | 'clear');
691
+ type: ('add' | 'update' | 'remove' | 'clear' | 'batch');
692
692
  /**
693
693
  * - Affected event
694
694
  */
@@ -701,11 +701,94 @@ export type EventStoreChange = {
701
701
  * - Previous events (for clear)
702
702
  */
703
703
  oldEvents?: import('./events/Event.js').Event[];
704
+ /**
705
+ * - Individual changes (for batch)
706
+ */
707
+ changes?: EventStoreChange[];
708
+ /**
709
+ * - Number of individual changes (for batch)
710
+ */
711
+ count?: number;
704
712
  /**
705
713
  * - Store version number
706
714
  */
707
715
  version: number;
708
716
  };
717
+ export type EventEquivalenceFn = (a: import('./events/Event.js').Event, b: import('./events/Event.js').Event) => boolean;
718
+ export type ReconcileOptions = {
719
+ /**
720
+ * - Remove stored events that are absent from the snapshot
721
+ */
722
+ removeMissing?: boolean;
723
+ /**
724
+ * - Comparator deciding whether a stored event is unchanged (defaults to Event.isEquivalent)
725
+ */
726
+ isEquivalent?: EventEquivalenceFn;
727
+ };
728
+ export type ReconciledUpdate = {
729
+ /**
730
+ * - Event now in the store
731
+ */
732
+ event: import('./events/Event.js').Event;
733
+ /**
734
+ * - Event it replaced
735
+ */
736
+ oldEvent: import('./events/Event.js').Event;
737
+ };
738
+ export type ReconcileResult = {
739
+ /**
740
+ * - Events that were not in the store before
741
+ */
742
+ added: import('./events/Event.js').Event[];
743
+ /**
744
+ * - Events whose data changed
745
+ */
746
+ updated: ReconciledUpdate[];
747
+ /**
748
+ * - Events removed from the store
749
+ */
750
+ removed: import('./events/Event.js').Event[];
751
+ /**
752
+ * - Stored events left untouched (same instances)
753
+ */
754
+ unchanged: import('./events/Event.js').Event[];
755
+ };
756
+ export type SetEventsOptions = {
757
+ /**
758
+ * - Apply only the differences instead of clearing and re-adding
759
+ */
760
+ reconcile?: boolean;
761
+ /**
762
+ * - Reconcile only: remove stored events absent from the snapshot
763
+ */
764
+ removeMissing?: boolean;
765
+ /**
766
+ * - Reconcile only: custom equivalence comparator
767
+ */
768
+ isEquivalent?: EventEquivalenceFn;
769
+ };
770
+ export type EventsSetPayload = {
771
+ /**
772
+ * - All events after the operation
773
+ */
774
+ events: import('./events/Event.js').Event[];
775
+ /**
776
+ * - Events added by the operation
777
+ */
778
+ added: import('./events/Event.js').Event[];
779
+ /**
780
+ * - Events replaced by the operation
781
+ */
782
+ updated: ReconciledUpdate[];
783
+ /**
784
+ * - Events removed by the operation
785
+ */
786
+ removed: import('./events/Event.js').Event[];
787
+ /**
788
+ * - Events left untouched
789
+ */
790
+ unchanged: import('./events/Event.js').Event[];
791
+ };
709
792
  export type QueryFilters = {
710
793
  /**
711
794
  * - Start date for range query
@@ -773,6 +856,132 @@ export type EventOccurrence = {
773
856
  * - ID of the parent recurring event
774
857
  */
775
858
  recurringEventId: string;
859
+ /**
860
+ * - Timezone the occurrence was expanded in
861
+ */
862
+ timezone?: string;
863
+ /**
864
+ * - Start of the series (DTSTART)
865
+ */
866
+ originalStart?: Date;
867
+ };
868
+ export type OccurrenceIteratorOptions = {
869
+ /**
870
+ * - Only occurrences starting after this instant (Date or timestamp)
871
+ */
872
+ after?: Date | number;
873
+ /**
874
+ * - Only occurrences starting before this instant (Date or timestamp)
875
+ */
876
+ before?: Date | number;
877
+ /**
878
+ * - Treat `after` and `before` as closed bounds
879
+ */
880
+ inclusive?: boolean;
881
+ /**
882
+ * - Timezone for expansion (defaults to the event's)
883
+ */
884
+ timezone?: string;
885
+ };
886
+ export type ExpandedOccurrenceIteratorOptions = {
887
+ /**
888
+ * - Only occurrences starting after this instant (Date or timestamp)
889
+ */
890
+ after?: Date | number;
891
+ /**
892
+ * - Only occurrences starting before this instant (Date or timestamp)
893
+ */
894
+ before?: Date | number;
895
+ /**
896
+ * - Treat `after` and `before` as closed bounds
897
+ */
898
+ inclusive?: boolean;
899
+ /**
900
+ * - Timezone for expansion (defaults to the event's)
901
+ */
902
+ timezone?: string;
903
+ /**
904
+ * - Apply stored instance modifications
905
+ */
906
+ includeModified?: boolean;
907
+ /**
908
+ * - Yield exception dates as cancelled occurrences
909
+ */
910
+ includeCancelled?: boolean;
911
+ /**
912
+ * - Adjust occurrences across DST transitions
913
+ */
914
+ handleDST?: boolean;
915
+ };
916
+ export type ExpandedOccurrence = {
917
+ /**
918
+ * - Occurrence ID (`<eventId>_<startTimestamp>` for recurring events)
919
+ */
920
+ id: string;
921
+ /**
922
+ * - ID of the parent recurring event
923
+ */
924
+ recurringEventId?: string;
925
+ /**
926
+ * - Event title
927
+ */
928
+ title: string;
929
+ /**
930
+ * - Occurrence start date
931
+ */
932
+ start: Date;
933
+ /**
934
+ * - Occurrence end date
935
+ */
936
+ end: Date;
937
+ /**
938
+ * - Occurrence start in UTC
939
+ */
940
+ startUTC?: Date;
941
+ /**
942
+ * - Occurrence end in UTC
943
+ */
944
+ endUTC?: Date;
945
+ /**
946
+ * - Timezone the occurrence was expanded in
947
+ */
948
+ timezone: string;
949
+ /**
950
+ * - Start of the series (DTSTART)
951
+ */
952
+ originalStart?: Date;
953
+ /**
954
+ * - Whether the event is all-day
955
+ */
956
+ allDay: boolean;
957
+ /**
958
+ * - Event description
959
+ */
960
+ description?: string;
961
+ /**
962
+ * - Event location
963
+ */
964
+ location?: string;
965
+ /**
966
+ * - Event categories
967
+ */
968
+ categories?: string[];
969
+ /**
970
+ * - 'confirmed', or 'cancelled' for exception dates yielded with includeCancelled
971
+ */
972
+ status?: EventStatus;
973
+ /**
974
+ * - Reason recorded for a cancelled occurrence
975
+ */
976
+ cancellationReason?: string;
977
+ /**
978
+ * - Whether the occurrence belongs to a recurring series
979
+ */
980
+ isRecurring: boolean;
981
+ /**
982
+ * - Whether a stored instance modification was applied
983
+ */
984
+ isModified?: boolean;
776
985
  };
777
986
  export type CalendarPlugin = {
778
987
  /**
@@ -1142,12 +1351,48 @@ export type ConflictSummary = {
1142
1351
  */
1143
1352
  /**
1144
1353
  * @typedef {Object} EventStoreChange
1145
- * @property {('add'|'update'|'remove'|'clear')} type - Type of change
1354
+ * @property {('add'|'update'|'remove'|'clear'|'batch')} type - Type of change
1146
1355
  * @property {import('./events/Event.js').Event} [event] - Affected event
1147
1356
  * @property {import('./events/Event.js').Event} [oldEvent] - Previous event state (for updates)
1148
1357
  * @property {import('./events/Event.js').Event[]} [oldEvents] - Previous events (for clear)
1358
+ * @property {EventStoreChange[]} [changes] - Individual changes (for batch)
1359
+ * @property {number} [count] - Number of individual changes (for batch)
1149
1360
  * @property {number} version - Store version number
1150
1361
  */
1362
+ /**
1363
+ * @typedef {(a: import('./events/Event.js').Event, b: import('./events/Event.js').Event) => boolean} EventEquivalenceFn
1364
+ */
1365
+ /**
1366
+ * @typedef {Object} ReconcileOptions
1367
+ * @property {boolean} [removeMissing=true] - Remove stored events that are absent from the snapshot
1368
+ * @property {EventEquivalenceFn} [isEquivalent] - Comparator deciding whether a stored event is unchanged (defaults to Event.isEquivalent)
1369
+ */
1370
+ /**
1371
+ * @typedef {Object} ReconciledUpdate
1372
+ * @property {import('./events/Event.js').Event} event - Event now in the store
1373
+ * @property {import('./events/Event.js').Event} oldEvent - Event it replaced
1374
+ */
1375
+ /**
1376
+ * @typedef {Object} ReconcileResult
1377
+ * @property {import('./events/Event.js').Event[]} added - Events that were not in the store before
1378
+ * @property {ReconciledUpdate[]} updated - Events whose data changed
1379
+ * @property {import('./events/Event.js').Event[]} removed - Events removed from the store
1380
+ * @property {import('./events/Event.js').Event[]} unchanged - Stored events left untouched (same instances)
1381
+ */
1382
+ /**
1383
+ * @typedef {Object} SetEventsOptions
1384
+ * @property {boolean} [reconcile=false] - Apply only the differences instead of clearing and re-adding
1385
+ * @property {boolean} [removeMissing=true] - Reconcile only: remove stored events absent from the snapshot
1386
+ * @property {EventEquivalenceFn} [isEquivalent] - Reconcile only: custom equivalence comparator
1387
+ */
1388
+ /**
1389
+ * @typedef {Object} EventsSetPayload
1390
+ * @property {import('./events/Event.js').Event[]} events - All events after the operation
1391
+ * @property {import('./events/Event.js').Event[]} added - Events added by the operation
1392
+ * @property {ReconciledUpdate[]} updated - Events replaced by the operation
1393
+ * @property {import('./events/Event.js').Event[]} removed - Events removed by the operation
1394
+ * @property {import('./events/Event.js').Event[]} unchanged - Events left untouched
1395
+ */
1151
1396
  /**
1152
1397
  * @typedef {Object} QueryFilters
1153
1398
  * @property {Date} [start] - Start date for range query
@@ -1169,6 +1414,55 @@ export type ConflictSummary = {
1169
1414
  * @property {Date} start - Occurrence start date
1170
1415
  * @property {Date} end - Occurrence end date
1171
1416
  * @property {string} recurringEventId - ID of the parent recurring event
1417
+ * @property {string} [timezone] - Timezone the occurrence was expanded in
1418
+ * @property {Date} [originalStart] - Start of the series (DTSTART)
1419
+ */
1420
+ /**
1421
+ * Window for lazy occurrence iteration. Both bounds are exclusive unless
1422
+ * `inclusive` is set: an occurrence starting exactly at `after` or `before`
1423
+ * is skipped by default, so iterating from a known occurrence's start
1424
+ * continues the series without repeating it. Omit a bound to leave that
1425
+ * end of the window open.
1426
+ * @typedef {Object} OccurrenceIteratorOptions
1427
+ * @property {Date|number} [after] - Only occurrences starting after this instant (Date or timestamp)
1428
+ * @property {Date|number} [before] - Only occurrences starting before this instant (Date or timestamp)
1429
+ * @property {boolean} [inclusive=false] - Treat `after` and `before` as closed bounds
1430
+ * @property {string} [timezone] - Timezone for expansion (defaults to the event's)
1431
+ */
1432
+ /**
1433
+ * Options for lazy occurrence iteration through RecurrenceEngineV2,
1434
+ * EventStore and Calendar: the OccurrenceIteratorOptions window plus the
1435
+ * expansion switches RecurrenceEngineV2.expandEvent accepts.
1436
+ * @typedef {Object} ExpandedOccurrenceIteratorOptions
1437
+ * @property {Date|number} [after] - Only occurrences starting after this instant (Date or timestamp)
1438
+ * @property {Date|number} [before] - Only occurrences starting before this instant (Date or timestamp)
1439
+ * @property {boolean} [inclusive=false] - Treat `after` and `before` as closed bounds
1440
+ * @property {string} [timezone] - Timezone for expansion (defaults to the event's)
1441
+ * @property {boolean} [includeModified=true] - Apply stored instance modifications
1442
+ * @property {boolean} [includeCancelled=false] - Yield exception dates as cancelled occurrences
1443
+ * @property {boolean} [handleDST=true] - Adjust occurrences across DST transitions
1444
+ */
1445
+ /**
1446
+ * Occurrence produced by RecurrenceEngineV2 (and therefore by EventStore
1447
+ * and Calendar occurrence queries)
1448
+ * @typedef {Object} ExpandedOccurrence
1449
+ * @property {string} id - Occurrence ID (`<eventId>_<startTimestamp>` for recurring events)
1450
+ * @property {string} [recurringEventId] - ID of the parent recurring event
1451
+ * @property {string} title - Event title
1452
+ * @property {Date} start - Occurrence start date
1453
+ * @property {Date} end - Occurrence end date
1454
+ * @property {Date} [startUTC] - Occurrence start in UTC
1455
+ * @property {Date} [endUTC] - Occurrence end in UTC
1456
+ * @property {string} timezone - Timezone the occurrence was expanded in
1457
+ * @property {Date} [originalStart] - Start of the series (DTSTART)
1458
+ * @property {boolean} allDay - Whether the event is all-day
1459
+ * @property {string} [description] - Event description
1460
+ * @property {string} [location] - Event location
1461
+ * @property {string[]} [categories] - Event categories
1462
+ * @property {EventStatus} [status] - 'confirmed', or 'cancelled' for exception dates yielded with includeCancelled
1463
+ * @property {string} [cancellationReason] - Reason recorded for a cancelled occurrence
1464
+ * @property {boolean} isRecurring - Whether the occurrence belongs to a recurring series
1465
+ * @property {boolean} [isModified] - Whether a stored instance modification was applied
1172
1466
  */
1173
1467
  /**
1174
1468
  * @typedef {Object} CalendarPlugin