@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.
@@ -30,7 +30,7 @@ export declare class RecurrenceEngineV2 {
30
30
  * @param {boolean} [options.includeCancelled=false] - Return exception dates as cancelled occurrences
31
31
  * @param {string} [options.timezone] - Timezone for expansion (defaults to the event's)
32
32
  * @param {boolean} [options.handleDST=true] - Adjust occurrences across DST transitions
33
- * @returns {Array} Expanded occurrences
33
+ * @returns {import('../types.js').ExpandedOccurrence[]} Expanded occurrences
34
34
  */
35
35
  expandEvent(event: import('./Event.js').Event, rangeStart: Date, rangeEnd: Date, options?: {
36
36
  maxOccurrences?: number;
@@ -38,7 +38,99 @@ export declare class RecurrenceEngineV2 {
38
38
  includeCancelled?: boolean;
39
39
  timezone?: string;
40
40
  handleDST?: boolean;
41
- }): any[];
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;
42
134
  /**
43
135
  * Move the expansion cursor to the last occurrence before the range
44
136
  * without stepping through every occurrence in between.
@@ -111,12 +203,18 @@ export declare class RecurrenceEngineV2 {
111
203
  /**
112
204
  * Find DST transitions in date range
113
205
  */
114
- findDSTTransitions(start: any, end: any, timezone: any): {
115
- date: Date;
116
- oldOffset: number;
117
- newOffset: number;
118
- type: string;
119
- }[];
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;
120
218
  /**
121
219
  * Adjust occurrence for DST transitions
122
220
  */
@@ -160,6 +258,12 @@ export declare class RecurrenceEngineV2 {
160
258
  * Clone occurrence results before returning or caching.
161
259
  */
162
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;
163
267
  /**
164
268
  * Clear cache for specific event
165
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.4.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
@@ -856,6 +856,132 @@ export type EventOccurrence = {
856
856
  * - ID of the parent recurring event
857
857
  */
858
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;
859
985
  };
860
986
  export type CalendarPlugin = {
861
987
  /**
@@ -1288,6 +1414,55 @@ export type ConflictSummary = {
1288
1414
  * @property {Date} start - Occurrence start date
1289
1415
  * @property {Date} end - Occurrence end date
1290
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
1291
1466
  */
1292
1467
  /**
1293
1468
  * @typedef {Object} CalendarPlugin