@forcecalendar/core 2.3.0 → 2.4.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.
@@ -5,14 +5,23 @@
5
5
  export declare class RecurrenceEngine {
6
6
  static _systemTransitions: any;
7
7
  static MAX_OCCURRENCES_HARD_LIMIT: number;
8
+ static MAX_ITERATIONS_HARD_LIMIT: number;
8
9
  static _ruleCache: Map<any, any>;
9
10
  static _RULE_CACHE_MAX: number;
10
11
  /**
11
12
  * Expand a recurring event into individual occurrences
13
+ *
14
+ * Occurrences before rangeStart are skipped without being generated:
15
+ * daily, weekly and sub-daily rules seek straight to the range, so the
16
+ * cost of a query does not grow with the age of the series, and a series
17
+ * that started years before the queried window is still expanded.
18
+ *
12
19
  * @param {import('./Event.js').Event} event - The recurring event
13
20
  * @param {Date} rangeStart - Start of the expansion range
14
21
  * @param {Date} rangeEnd - End of the expansion range
15
- * @param {number} [maxOccurrences=365] - Maximum number of occurrences to generate
22
+ * @param {number} [maxOccurrences=365] - Maximum number of occurrences to return.
23
+ * Only occurrences inside the range count towards this limit; occurrences
24
+ * between the series start and rangeStart do not consume it.
16
25
  * @param {string} [timezone] - Timezone for expansion (important for DST)
17
26
  * @returns {import('../types.js').EventOccurrence[]} Array of occurrence objects with start/end dates
18
27
  */
@@ -39,6 +48,56 @@ export declare class RecurrenceEngine {
39
48
  * @private
40
49
  */
41
50
  private static _expandFast;
51
+ /**
52
+ * Milliseconds per step for rules whose step is a fixed duration while
53
+ * the system UTC offset is constant. Only the sub-daily frequencies are
54
+ * reported here: DAILY and WEEKLY have their own numeric loop, and the
55
+ * calendar-based frequencies take too few steps per year to need seeking.
56
+ * @param {Object} rule - Parsed recurrence rule
57
+ * @returns {number} Step length in milliseconds, or 0 when not fixed
58
+ * @private
59
+ */
60
+ private static _fixedStepMs;
61
+ /**
62
+ * Skip the occurrences of a fixed-step rule that fall before
63
+ * rangeStartMs without visiting each one.
64
+ *
65
+ * While the system UTC offset is constant, a wall-clock step of the
66
+ * cursor is a constant number of milliseconds, so a whole run of steps
67
+ * collapses into one multiplication. The single step that crosses a
68
+ * system-timezone transition is taken with `advance` instead, so the
69
+ * cursor ends up exactly where stepping every occurrence would have put
70
+ * it. Stops at the last occurrence before rangeStartMs; the caller's loop
71
+ * takes the step into the range.
72
+ *
73
+ * @param {number} fromMs - Cursor position (an occurrence instant)
74
+ * @param {number} rangeStartMs - Seek target
75
+ * @param {number} rangeEndMs - Upper bound for transition lookup
76
+ * @param {number} stepMs - Step length while the UTC offset is constant
77
+ * @param {number} maxSteps - Steps still permitted under COUNT (Infinity if unbounded)
78
+ * @param {(cursor: Date) => void} advance - Wall-clock step, mutating the cursor
79
+ * @returns {{ ms: number, steps: number, nextSystemTransition: number }}
80
+ * Cursor position, steps taken and the next system transition after it
81
+ * @private
82
+ */
83
+ private static _seekFixedStep;
84
+ /**
85
+ * Seek for WEEKLY BYDAY rules, whose step pattern repeats every week:
86
+ * whole weeks are skipped arithmetically from any weekday in the BYDAY
87
+ * set, and single steps (identical to the expansion loop's) are only
88
+ * taken to reach the set, around system-timezone transitions and in the
89
+ * last week before the range.
90
+ *
91
+ * @param {number} fromMs - Cursor position (an occurrence instant)
92
+ * @param {number} weekday - Weekday of the cursor (Date#getDay)
93
+ * @param {number} rangeStartMs - Seek target
94
+ * @param {number} rangeEndMs - Upper bound for transition lookup
95
+ * @param {Object} rule - Parsed rule with compiled _byDaySet/_byDayDeltas
96
+ * @param {number} maxSteps - Steps still permitted under COUNT (Infinity if unbounded)
97
+ * @returns {{ ms: number, steps: number, weekday: number, nextSystemTransition: number }}
98
+ * @private
99
+ */
100
+ private static _seekWeekCycle;
42
101
  /**
43
102
  * Find the next system-timezone offset transition after fromMs.
44
103
  * Cached module-wide: the system timezone is fixed for the process.
@@ -10,16 +10,59 @@ 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
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
20
33
  * @returns {Array} 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
+ }): any[];
42
+ /**
43
+ * Move the expansion cursor to the last occurrence before the range
44
+ * without stepping through every occurrence in between.
45
+ *
46
+ * Applies to rules whose step is a fixed duration between system-timezone
47
+ * transitions (plain DAILY and WEEKLY, HOURLY, MINUTELY); the step that
48
+ * crosses a transition is taken with getNextDate so the result is exactly
49
+ * what stepping from DTSTART would produce. Never seeks past UNTIL, and
50
+ * counts skipped steps against COUNT.
51
+ *
52
+ * @param {Object} state - Expansion state (currentDate and count are updated)
53
+ * @param {Object} rule - Parsed recurrence rule
54
+ * @param {Date} rangeStart - Start of expansion range
55
+ * @param {Date} rangeEnd - End of expansion range
56
+ * @param {string} timezone - Expansion timezone
57
+ */
58
+ seekToRange(state: Object, rule: Object, rangeStart: Date, rangeEnd: Date, timezone: string): void;
59
+ /**
60
+ * Milliseconds per step for rules getNextDate advances by a fixed
61
+ * duration while the system UTC offset is constant
62
+ * @param {Object} rule - Parsed recurrence rule
63
+ * @returns {number} Step length in milliseconds, or 0 when not fixed
64
+ */
65
+ getFixedStepMs(rule: Object): number;
23
66
  /**
24
67
  * Generate a single occurrence with timezone handling
25
68
  */
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.4.0";
22
22
  export { Calendar as default } from './calendar/Calendar.js';
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
@@ -1142,12 +1225,48 @@ export type ConflictSummary = {
1142
1225
  */
1143
1226
  /**
1144
1227
  * @typedef {Object} EventStoreChange
1145
- * @property {('add'|'update'|'remove'|'clear')} type - Type of change
1228
+ * @property {('add'|'update'|'remove'|'clear'|'batch')} type - Type of change
1146
1229
  * @property {import('./events/Event.js').Event} [event] - Affected event
1147
1230
  * @property {import('./events/Event.js').Event} [oldEvent] - Previous event state (for updates)
1148
1231
  * @property {import('./events/Event.js').Event[]} [oldEvents] - Previous events (for clear)
1232
+ * @property {EventStoreChange[]} [changes] - Individual changes (for batch)
1233
+ * @property {number} [count] - Number of individual changes (for batch)
1149
1234
  * @property {number} version - Store version number
1150
1235
  */
1236
+ /**
1237
+ * @typedef {(a: import('./events/Event.js').Event, b: import('./events/Event.js').Event) => boolean} EventEquivalenceFn
1238
+ */
1239
+ /**
1240
+ * @typedef {Object} ReconcileOptions
1241
+ * @property {boolean} [removeMissing=true] - Remove stored events that are absent from the snapshot
1242
+ * @property {EventEquivalenceFn} [isEquivalent] - Comparator deciding whether a stored event is unchanged (defaults to Event.isEquivalent)
1243
+ */
1244
+ /**
1245
+ * @typedef {Object} ReconciledUpdate
1246
+ * @property {import('./events/Event.js').Event} event - Event now in the store
1247
+ * @property {import('./events/Event.js').Event} oldEvent - Event it replaced
1248
+ */
1249
+ /**
1250
+ * @typedef {Object} ReconcileResult
1251
+ * @property {import('./events/Event.js').Event[]} added - Events that were not in the store before
1252
+ * @property {ReconciledUpdate[]} updated - Events whose data changed
1253
+ * @property {import('./events/Event.js').Event[]} removed - Events removed from the store
1254
+ * @property {import('./events/Event.js').Event[]} unchanged - Stored events left untouched (same instances)
1255
+ */
1256
+ /**
1257
+ * @typedef {Object} SetEventsOptions
1258
+ * @property {boolean} [reconcile=false] - Apply only the differences instead of clearing and re-adding
1259
+ * @property {boolean} [removeMissing=true] - Reconcile only: remove stored events absent from the snapshot
1260
+ * @property {EventEquivalenceFn} [isEquivalent] - Reconcile only: custom equivalence comparator
1261
+ */
1262
+ /**
1263
+ * @typedef {Object} EventsSetPayload
1264
+ * @property {import('./events/Event.js').Event[]} events - All events after the operation
1265
+ * @property {import('./events/Event.js').Event[]} added - Events added by the operation
1266
+ * @property {ReconciledUpdate[]} updated - Events replaced by the operation
1267
+ * @property {import('./events/Event.js').Event[]} removed - Events removed by the operation
1268
+ * @property {import('./events/Event.js').Event[]} unchanged - Events left untouched
1269
+ */
1151
1270
  /**
1152
1271
  * @typedef {Object} QueryFilters
1153
1272
  * @property {Date} [start] - Start date for range query