calendaryjs 0.2.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.
@@ -0,0 +1,1289 @@
1
+ /**
2
+ * Date string in YYYY-MM-DD format.
3
+ * Template literal type provides compile-time format validation.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const date: DateString = "2025-12-25"; // ✓ Valid
8
+ * const bad: DateString = "25-12-2025"; // ✗ Error: wrong format
9
+ * ```
10
+ */
11
+ type DateString = `${number}-${number}-${number}`;
12
+ /**
13
+ * Map of dates to override (force reschedule).
14
+ * Unlike onConflict which handles conflicts, this unconditionally moves events.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const overrideDates: OverrideDatesMap = {
19
+ * "2026-02-18": "2026-02-20", // Move from Feb 18 to Feb 20
20
+ * "2027-02-10": "2027-02-12", // Move from Feb 10 to Feb 12
21
+ * };
22
+ * ```
23
+ */
24
+ type OverrideDatesMap = Record<DateString, DateString>;
25
+ /**
26
+ * Base properties shared by all event types (ICS-compatible)
27
+ *
28
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
29
+ * @template TCategory - Category type (default: string, can be enum for type safety)
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Default usage
34
+ * type Event = BaseEventProperties;
35
+ *
36
+ * // With typed categories
37
+ * type LiturgicalCategory = "solemnity" | "feast" | "memorial";
38
+ * type LiturgicalEvent = BaseEventProperties<LiturgicalMetadata, LiturgicalCategory>;
39
+ * ```
40
+ */
41
+ interface BaseEventProperties<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> {
42
+ id: string;
43
+ title: string;
44
+ keywords?: string[];
45
+ allDay?: boolean;
46
+ startTime?: string;
47
+ endTime?: string;
48
+ duration?: number;
49
+ description?: string;
50
+ location?: string;
51
+ url?: string;
52
+ /**
53
+ * Origin of the event — the plugin, feed, or ICS subscription that produced it.
54
+ * Lets a consumer customize/filter in bulk by source (like toggling a calendar
55
+ * in Google/Apple Calendar) and namespaces the ICS export `UID`.
56
+ * Distinct from `groupId` (a user-defined group) and `sourceEventId` (the
57
+ * originating config id).
58
+ */
59
+ source?: string;
60
+ categories?: TCategory[];
61
+ /**
62
+ * Display precedence when several events land on the same day, like CSS
63
+ * `z-index`: **higher = on top**, default `0`. The core only sorts by this
64
+ * number (highest first); it assigns no meaning to the values — the consumer
65
+ * does. Ties keep generation order (stable).
66
+ */
67
+ priority?: number;
68
+ status?: "confirmed" | "tentative" | "cancelled";
69
+ color?: string;
70
+ icon?: string;
71
+ metadata?: TMetadata;
72
+ reminders?: Reminder[];
73
+ /**
74
+ * Conflict resolver for handling events on the same day.
75
+ * Called during event generation to determine action when this event
76
+ * would occur on a specific date.
77
+ *
78
+ * Default behavior (when not specified): keep all events
79
+ */
80
+ onConflict?: ConflictResolver;
81
+ /**
82
+ * Force reschedule dates unconditionally.
83
+ * Unlike onConflict which handles conflicts, this always moves events.
84
+ * Key is the original computed date (YYYY-MM-DD), value is the new date.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * overrideDates: {
89
+ * "2026-02-18": "2026-02-20", // Move from Feb 18 to Feb 20
90
+ * }
91
+ * ```
92
+ */
93
+ overrideDates?: OverrideDatesMap;
94
+ /**
95
+ * Per-occurrence exceptions keyed by the event's ORIGINAL computed date
96
+ * (YYYY-MM-DD) — maps ICS `EXDATE` / `RECURRENCE-ID`. `{ skip: true }` drops
97
+ * that occurrence; `{ override }` replaces properties for that one instance.
98
+ */
99
+ exceptions?: EventExceptions;
100
+ /**
101
+ * Recurrence validity window (`YYYY-MM-DD`, **both bounds inclusive**).
102
+ * Occurrences before `startDate` or after `endDate` are dropped — applied
103
+ * centrally during generation, so it works for ANY event type (const, monthly,
104
+ * weekly, nth-weekday, plugin types…), not just the few that ship their own
105
+ * range fields.
106
+ *
107
+ * Maps iCalendar semantics: `startDate` ↔ `DTSTART` lower bound, `endDate` ↔
108
+ * `RRULE;UNTIL` (which RFC 5545 defines as **inclusive**). Both are DATE values
109
+ * (all-day); recurrences are date-based, so no time component is involved.
110
+ *
111
+ * @example
112
+ * // Weekly stand-up, but only for July 2026
113
+ * { type: "weekly", id: "standup", dayOfWeek: 1,
114
+ * startDate: "2026-07-01", endDate: "2026-07-31" }
115
+ */
116
+ startDate?: string;
117
+ endDate?: string;
118
+ /**
119
+ * Per-occurrence **dynamic** fields, derived from each occurrence's date. A map
120
+ * of `fieldName → template`, where the template may contain date tokens
121
+ * (`{YYYY}` `{MM}` `{DD}` zero-padded · `{M}` `{D}` no padding). Each occurrence
122
+ * gets the field set to the interpolated value.
123
+ *
124
+ * Applied during generation, **after** the base value and **before** a
125
+ * per-date `exceptions[date].override` (which always wins). Typically used for a
126
+ * date-specific `url`, but any string field works (`title`, `description`,
127
+ * `location`, …).
128
+ *
129
+ * @example
130
+ * // Every Sunday's link points at a date page
131
+ * { type: "weekly", id: "mass", dayOfWeek: 0, title: "Mass",
132
+ * templates: { url: "https://x.com/mass/{D}-{M}" } }
133
+ * // 2026-07-07 → .../mass/7-7 · 2026-07-14 → .../mass/14-7
134
+ */
135
+ templates?: Record<string, string>;
136
+ }
137
+ interface Reminder {
138
+ type: "notification" | "email";
139
+ trigger: number;
140
+ message?: string;
141
+ }
142
+ /**
143
+ * A single per-occurrence exception (see `BaseEventProperties.exceptions`).
144
+ * - `{ skip: true }` — drop this occurrence (ICS `EXDATE`).
145
+ * - `{ override }` — replace properties for this one instance (ICS
146
+ * `RECURRENCE-ID`). Changes display properties only; to move an occurrence to
147
+ * another date use `overrideDates`.
148
+ */
149
+ type EventException = {
150
+ skip: true;
151
+ } | {
152
+ override: Partial<Omit<BaseEventProperties, "id" | "exceptions">>;
153
+ };
154
+ /**
155
+ * Map of an event's ORIGINAL computed dates (YYYY-MM-DD) to per-occurrence
156
+ * exceptions.
157
+ */
158
+ type EventExceptions = Record<DateString, EventException>;
159
+ /**
160
+ * Conflict action when multiple events occur on the same day.
161
+ *
162
+ * - `keep` - Keep this event (default behavior, allows multiple events on same day)
163
+ * - `drop` - Remove this event when conflict exists
164
+ * - `reschedule` - Move this event to a different date
165
+ */
166
+ type ConflictAction = {
167
+ action: "keep";
168
+ } | {
169
+ action: "drop";
170
+ } | {
171
+ action: "reschedule";
172
+ to: DateString;
173
+ };
174
+ /**
175
+ * Map of dates to conflict actions.
176
+ * Dates not in the map default to "keep".
177
+ */
178
+ type ConflictMap = Partial<Record<DateString, ConflictAction>>;
179
+ /**
180
+ * Function to determine conflict action for a specific date.
181
+ */
182
+ type ConflictResolverFn = (date: DateString) => ConflictAction;
183
+ /**
184
+ * Resolver to determine conflict action for events.
185
+ * Can be either a function for dynamic logic or a map for static date lists.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * // Option 1: Function for complex logic
190
+ * const onConflict: ConflictResolver = (date) => {
191
+ * if (date === "2026-02-18") {
192
+ * return { action: "reschedule", to: "2026-02-17" };
193
+ * }
194
+ * return { action: "keep" };
195
+ * };
196
+ *
197
+ * // Option 2: Map for static date lists (cleaner for many dates)
198
+ * const onConflict: ConflictResolver = {
199
+ * "2026-02-18": { action: "reschedule", to: "2026-02-17" },
200
+ * "2027-11-24": { action: "drop" },
201
+ * "2028-02-18": { action: "reschedule", to: "2028-02-19" },
202
+ * // Dates not in map default to "keep"
203
+ * };
204
+ * ```
205
+ */
206
+ type ConflictResolver = ConflictResolverFn | ConflictMap;
207
+ /**
208
+ * CONST - Fixed annual events (e.g., Christmas on Dec 25)
209
+ * Supports year exclusions and date range constraints.
210
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
211
+ * @example
212
+ * // Basic annual event
213
+ * { type: "const", id: "christmas", month: 12, day: 25 }
214
+ *
215
+ * // Annual event with exclusions
216
+ * { type: "const", id: "liberation-day",
217
+ * month: 4, day: 30, excludeYears: [2025, 2040, 2043] }
218
+ *
219
+ * // Annual event with date range
220
+ * { type: "const", id: "company-anniversary",
221
+ * month: 3, day: 15, startYear: 2020, endYear: 2030 }
222
+ */
223
+ interface ConstEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
224
+ type: "const";
225
+ /**
226
+ * Month of the event (1-12)
227
+ * @example 12 // December
228
+ */
229
+ month: number;
230
+ /**
231
+ * Day of the month (1-31)
232
+ * @example 25 // 25th day
233
+ */
234
+ day: number;
235
+ /**
236
+ * Years to exclude from recurrence
237
+ * @example [2025, 2040, 2043] // Skip these years
238
+ */
239
+ excludeYears?: number[];
240
+ /**
241
+ * First year the event occurs (inclusive)
242
+ * @example 2020 // Start from 2020
243
+ */
244
+ startYear?: number;
245
+ /**
246
+ * Last year the event occurs (inclusive)
247
+ * @example 2030 // End at 2030
248
+ */
249
+ endYear?: number;
250
+ }
251
+ /**
252
+ * FIXED - One-time events on a specific date
253
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
254
+ * @example
255
+ * { type: "fixed", id: "wedding",
256
+ * year: 2025, month: 6, day: 15 }
257
+ */
258
+ interface FixedEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
259
+ type: "fixed";
260
+ /**
261
+ * Year of the event
262
+ * @example 2025
263
+ */
264
+ year: number;
265
+ /**
266
+ * Month of the event (1-12)
267
+ * @example 6 // June
268
+ */
269
+ month: number;
270
+ /**
271
+ * Day of the month (1-31)
272
+ * @example 15
273
+ */
274
+ day: number;
275
+ }
276
+ /**
277
+ * MONTHLY - Recurring events on the same day every month
278
+ * Supports intervals, month exclusions, and date exclusions.
279
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
280
+ * @example
281
+ * // Every 15th of the month
282
+ * { type: "monthly", id: "payday", day: 15 }
283
+ *
284
+ * // Quarterly (every 3 months starting January)
285
+ * { type: "monthly", id: "quarterly-review",
286
+ * day: 1, interval: 3, startMonth: 1 }
287
+ *
288
+ * // Monthly except December
289
+ * { type: "monthly", id: "team-lunch",
290
+ * day: 10, excludeMonths: [12] }
291
+ */
292
+ interface MonthlyEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
293
+ type: "monthly";
294
+ /**
295
+ * Day of the month (1-31)
296
+ * Note: If day exceeds month's days (e.g., 31 in February), event is skipped for that month
297
+ * @example 15 // 15th of each month
298
+ */
299
+ day: number;
300
+ /**
301
+ * Recurrence interval in months (default: 1)
302
+ * @example 3 // Every 3 months (quarterly)
303
+ */
304
+ interval?: number;
305
+ /**
306
+ * Starting month for interval calculation (1-12, default: 1)
307
+ * Used with interval to determine which months the event occurs
308
+ * @example 1 // Start counting from January
309
+ */
310
+ startMonth?: number;
311
+ /**
312
+ * Months to exclude (1-12)
313
+ * @example [7, 8] // Skip July and August
314
+ */
315
+ excludeMonths?: number[];
316
+ /**
317
+ * Specific dates to exclude (ISO format: YYYY-MM-DD)
318
+ * @example ["2025-12-15"] // Skip this specific date
319
+ */
320
+ excludeDates?: string[];
321
+ }
322
+ /** A weekday index: 0 = Sunday, 1 = Monday, … 6 = Saturday. */
323
+ type WeekdayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
324
+ /**
325
+ * WEEKLY - Recurring events on one or more days every week
326
+ * Supports multiple weekdays, intervals, date range, and date exclusions.
327
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
328
+ * @example
329
+ * // Every Monday
330
+ * { type: "weekly", id: "standup", dayOfWeek: 1 }
331
+ *
332
+ * // Every Monday, Wednesday & Friday (like an alarm's "Repeat")
333
+ * { type: "weekly", id: "logwork", dayOfWeek: [1, 3, 5] }
334
+ *
335
+ * // Every other Wednesday
336
+ * { type: "weekly", id: "biweekly-sync",
337
+ * dayOfWeek: 3, interval: 2 }
338
+ *
339
+ * // Every Friday with date range
340
+ * { type: "weekly", id: "sprint-review",
341
+ * dayOfWeek: 5, startDate: "2025-01-01", endDate: "2025-12-31" }
342
+ */
343
+ interface WeeklyEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
344
+ type: "weekly";
345
+ /**
346
+ * Day of the week, or several. `0=Sunday, 1=Monday, ..., 6=Saturday`.
347
+ * A list fires on each selected day every week (ICS `BYDAY` semantics); with
348
+ * `interval`, all selected days share one reference week.
349
+ * @example 1 // Monday
350
+ * @example [1, 3, 5] // Monday, Wednesday, Friday
351
+ */
352
+ dayOfWeek: WeekdayIndex | WeekdayIndex[];
353
+ /**
354
+ * Recurrence interval in weeks (default: 1)
355
+ * @example 2 // Every 2 weeks (bi-weekly)
356
+ */
357
+ interval?: number;
358
+ /**
359
+ * Start date for the recurrence (ISO format: YYYY-MM-DD)
360
+ * Used as reference point for interval calculation
361
+ * @example "2025-01-06" // First Monday of 2025
362
+ */
363
+ startDate?: string;
364
+ /**
365
+ * End date for the recurrence (ISO format: YYYY-MM-DD)
366
+ * @example "2025-12-31"
367
+ */
368
+ endDate?: string;
369
+ /**
370
+ * Specific dates to exclude (ISO format: YYYY-MM-DD)
371
+ * @example ["2025-12-25"] // Skip Christmas
372
+ */
373
+ excludeDates?: string[];
374
+ }
375
+ /**
376
+ * FORMULA - Custom formula-based events
377
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
378
+ */
379
+ interface FormulaEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
380
+ type: "formula";
381
+ formula: string;
382
+ params?: Record<string, unknown>;
383
+ }
384
+ /**
385
+ * Anchor point for nth-weekday events relative to a fixed date.
386
+ */
387
+ interface DateAnchor {
388
+ /** Month (1-12) */
389
+ month: number;
390
+ /** Day of month (1-31) */
391
+ day: number;
392
+ }
393
+ /**
394
+ * NTH-WEEKDAY - Recurring events on the Nth weekday of a month, or the first
395
+ * matching weekday relative to a fixed-date anchor.
396
+ *
397
+ * Supports two modes:
398
+ *
399
+ * **Simple mode** — nth occurrence of a weekday within a calendar month:
400
+ * ```typescript
401
+ * // Mother's Day: 2nd Sunday of May
402
+ * { type: "nth-weekday", nth: 2, dayOfWeek: 0, month: 5 }
403
+ *
404
+ * // Thanksgiving (US): 4th Thursday of November
405
+ * { type: "nth-weekday", nth: 4, dayOfWeek: 4, month: 11 }
406
+ *
407
+ * // Last Monday of August
408
+ * { type: "nth-weekday", nth: -1, dayOfWeek: 1, month: 8 }
409
+ * ```
410
+ *
411
+ * **Anchored mode** — first matching weekday on or after a fixed date,
412
+ * optionally bounded and with a fallback:
413
+ * ```typescript
414
+ * // Holy Family: first Sunday after Dec 25, fallback Dec 30
415
+ * { type: "nth-weekday", dayOfWeek: 0,
416
+ * after: { month: 12, day: 26 }, before: { month: 12, day: 31 },
417
+ * fallback: { month: 12, day: 30 } }
418
+ *
419
+ * // Baptism of the Lord: first Sunday on/after Jan 7
420
+ * { type: "nth-weekday", dayOfWeek: 0, after: { month: 1, day: 7 } }
421
+ * ```
422
+ *
423
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
424
+ */
425
+ interface NthWeekdayEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
426
+ type: "nth-weekday";
427
+ /**
428
+ * Day of the week (0=Sunday, 1=Monday, ..., 6=Saturday)
429
+ * @example 0 // Sunday
430
+ */
431
+ dayOfWeek: 0 | 1 | 2 | 3 | 4 | 5 | 6;
432
+ /**
433
+ * Which occurrence within the month:
434
+ * - `1`–`4`: 1st through 4th occurrence
435
+ * - `-1`: last occurrence
436
+ *
437
+ * Required in simple mode (paired with `month`). Omit for anchored mode.
438
+ */
439
+ nth?: 1 | 2 | 3 | 4 | -1;
440
+ /**
441
+ * Month (1-12). Required in simple mode. Omit for anchored mode.
442
+ */
443
+ month?: number;
444
+ /**
445
+ * Find the first matching `dayOfWeek` on or after this date.
446
+ * When provided, `nth` and `month` are ignored.
447
+ */
448
+ after?: DateAnchor;
449
+ /**
450
+ * Upper-bound anchor. If the first match found via `after` falls on or
451
+ * after this date, the `fallback` date is used instead (if supplied).
452
+ */
453
+ before?: DateAnchor;
454
+ /**
455
+ * Fallback date used when no `dayOfWeek` is found between `after` and
456
+ * `before` (inclusive). Only meaningful when `before` is also set.
457
+ */
458
+ fallback?: DateAnchor;
459
+ /** Years to skip entirely. */
460
+ excludeYears?: number[];
461
+ /** First year the event applies (inclusive). */
462
+ startYear?: number;
463
+ /** Last year the event applies (inclusive). */
464
+ endYear?: number;
465
+ }
466
+ /**
467
+ * Core event types (without plugins)
468
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
469
+ */
470
+ /**
471
+ * RELATIVE - A date computed as an offset from a named anchor.
472
+ * The anchor is a resolver (year → Date) registered by the consumer or a plugin;
473
+ * the offset shifts from it. The core knows no specific anchors — it only
474
+ * resolves whatever name was registered. Compile target of the builder's
475
+ * `from(anchor).plus(...)`.
476
+ *
477
+ * @example
478
+ * // 49 days after whatever the "anchor" resolver returns for that year
479
+ * { type: "relative", id: "x", anchor: "anchor", offset: { days: 49 } }
480
+ */
481
+ interface RelativeEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
482
+ type: "relative";
483
+ /** Name of a registered anchor resolver (year → Date). */
484
+ anchor: string;
485
+ /** Offset from the anchor date. */
486
+ offset: {
487
+ days?: number;
488
+ weeks?: number;
489
+ };
490
+ }
491
+ type CoreEventConfig<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> = ConstEvent<TMetadata, TCategory> | FixedEvent<TMetadata, TCategory> | MonthlyEvent<TMetadata, TCategory> | WeeklyEvent<TMetadata, TCategory> | NthWeekdayEvent<TMetadata, TCategory> | FormulaEvent<TMetadata, TCategory> | RelativeEvent<TMetadata, TCategory>;
492
+ /**
493
+ * Generic event config - extended by plugins.
494
+ * Use the generic parameter to enforce metadata types.
495
+ *
496
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
497
+ *
498
+ * @example
499
+ * ```typescript
500
+ * // Basic usage (any metadata)
501
+ * const event: EventConfig = { type: "const", id: "x", ... };
502
+ *
503
+ * // With typed metadata
504
+ * interface MyMetadata { rank: string; color: string; }
505
+ * const event: EventConfig<MyMetadata> = { ... metadata: { rank: "high", color: "red" } };
506
+ * ```
507
+ */
508
+ type EventConfig<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> = CoreEventConfig<TMetadata, TCategory> | {
509
+ type: string;
510
+ metadata?: TMetadata;
511
+ [key: string]: unknown;
512
+ };
513
+ /**
514
+ * Event type identifiers
515
+ */
516
+ type CoreEventType = "const" | "fixed" | "monthly" | "weekly" | "nth-weekday" | "formula" | "relative";
517
+ /**
518
+ * Custom event configuration for overriding event properties.
519
+ * Used by generator packages to allow customization of generated events.
520
+ *
521
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
522
+ * @template TExclude - Fields to exclude from customization (default: never)
523
+ *
524
+ * @example
525
+ * ```typescript
526
+ * // Allow all fields except id
527
+ * const customConfig: CustomEventConfig<{ localName: string }> = {
528
+ * title: "Easter Sunday",
529
+ * keywords: ["easter", "resurrection"],
530
+ * metadata: { localName: "Pâques" },
531
+ * url: "https://example.com/easter",
532
+ * };
533
+ *
534
+ * // Force reschedule to different dates
535
+ * const customConfig: CustomEventConfig = {
536
+ * overrideDates: {
537
+ * "2026-02-18": "2026-02-20", // Move Ash Wednesday
538
+ * },
539
+ * };
540
+ *
541
+ * // Restrict certain fields from customization
542
+ * type RestrictedConfig = CustomEventConfig<MyMetadata, "status" | "reminders">;
543
+ * // Now status and reminders cannot be customized
544
+ * ```
545
+ */
546
+ type CustomEventConfig<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string, TExclude extends keyof BaseEventProperties<TMetadata, TCategory> = never> = Partial<Omit<BaseEventProperties<TMetadata, TCategory>, "id" | "metadata" | TExclude>> & {
547
+ /**
548
+ * Partial metadata to merge with the base event's metadata.
549
+ * Only specify the fields you want to add or override.
550
+ * The base metadata fields will be preserved.
551
+ *
552
+ * @example
553
+ * ```typescript
554
+ * // Only add extra fields - base metadata (rank, season, etc.) is preserved
555
+ * metadata: { wikiFileUrl: "christmas.md" }
556
+ * ```
557
+ */
558
+ metadata?: Partial<TMetadata>;
559
+ /**
560
+ * Force reschedule dates unconditionally.
561
+ * Unlike onConflict which handles conflicts, this always moves events.
562
+ * Key is the original computed date, value is the new date.
563
+ */
564
+ overrideDates?: OverrideDatesMap;
565
+ };
566
+ /**
567
+ * Resolver function that provides custom configuration for each event key.
568
+ * Returns partial event properties that will be merged with the generated event.
569
+ *
570
+ * @template TKey - Event key type (e.g., MassKey, LunarKey)
571
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
572
+ * @template TExclude - Fields to exclude from customization (default: never)
573
+ *
574
+ * @example
575
+ * ```typescript
576
+ * // Liturgical events - allow all customization
577
+ * const resolver: CustomEventResolver<MassKey, LiturgicalMetadata> = (key) => ({
578
+ * title: localTexts[key]?.title,
579
+ * metadata: { localName: localTexts[key]?.name },
580
+ * });
581
+ *
582
+ * // Lunar events - restrict status and reminders
583
+ * const resolver: CustomEventResolver<LunarKey, LunarMetadata, "status" | "reminders"> = (key) => ({
584
+ * title: lunarTexts[key]?.title,
585
+ * });
586
+ * ```
587
+ */
588
+ type CustomEventResolver<TKey extends string, TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string, TExclude extends keyof BaseEventProperties = never> = (key: TKey) => CustomEventConfig<TMetadata, TCategory, TExclude> | undefined;
589
+
590
+ /**
591
+ * Group configuration
592
+ */
593
+ interface GroupConfig {
594
+ id: string;
595
+ name?: string;
596
+ description?: string;
597
+ color?: string;
598
+ enabled?: boolean;
599
+ events: EventConfig[];
600
+ }
601
+ /**
602
+ * Anything that compiles to a plain event config — e.g. the fluent builder.
603
+ * The core duck-types on `build()`, so it never depends on the builder.
604
+ */
605
+ interface Buildable {
606
+ build(): EventConfig;
607
+ }
608
+ /**
609
+ * `addGroup` input: events may be plain configs or builders (compiled on the way
610
+ * in). Storage is always plain configs ({@link Group}).
611
+ */
612
+ interface GroupInput extends Omit<GroupConfig, "events"> {
613
+ events: (EventConfig | Buildable)[];
614
+ }
615
+ /**
616
+ * A shareable, portable bundle of events (a `.cdy` document is its JSON form).
617
+ * Inert data: a manifest of the plugins its events need + the events themselves.
618
+ * Load it with `cal.load()` after registering the declared plugins.
619
+ */
620
+ interface Collection {
621
+ /** Collection name; used as the group id when `id` is omitted. */
622
+ collection?: string;
623
+ /** Collection version (semver-ish, informational). */
624
+ version?: string;
625
+ /** Plugin package names the events need — validated on load. */
626
+ plugins?: string[];
627
+ /** Group id (defaults to `collection`, else `"collection"`). */
628
+ id?: string;
629
+ /** Group display name. */
630
+ name?: string;
631
+ /** The events — plain configs (as in a `.cdy` file) or builders. */
632
+ events: (EventConfig | Buildable)[];
633
+ }
634
+ /**
635
+ * Internal group representation
636
+ */
637
+ interface Group extends GroupConfig {
638
+ enabled: boolean;
639
+ }
640
+
641
+ /**
642
+ * Calendar event - the output after processing event configs.
643
+ * Extends BaseEventProperties with computed fields.
644
+ *
645
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
646
+ * @template TCategory - Category type (default: string, can be enum for type safety)
647
+ *
648
+ * @example
649
+ * ```typescript
650
+ * // Default usage
651
+ * const event: CalendarEvent = { ... };
652
+ *
653
+ * // With typed metadata and categories
654
+ * interface LiturgicalMetadata { rank: string; vestmentColor: string; }
655
+ * type LiturgicalCategory = "solemnity" | "feast" | "memorial";
656
+ * const event: CalendarEvent<LiturgicalMetadata, LiturgicalCategory> = { ... };
657
+ * ```
658
+ */
659
+ interface CalendarEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
660
+ /** Original event ID from the event config */
661
+ sourceEventId: string;
662
+ /** Date in YYYY-MM-DD format */
663
+ date: DateString;
664
+ /** Group ID this event belongs to */
665
+ groupId: string;
666
+ /** Group name (optional, for convenience) */
667
+ groupName?: string;
668
+ }
669
+
670
+ /**
671
+ * Date components - structured date representation
672
+ * Use this instead of string dates to prevent format errors
673
+ */
674
+ interface DateComponents {
675
+ /** Calendar year */
676
+ year: number;
677
+ /** Month (1-12) */
678
+ month: number;
679
+ /** Day of month (1-31) */
680
+ day: number;
681
+ }
682
+ /**
683
+ * Base calendar day - the fundamental unit returned by getDays()
684
+ *
685
+ * This is the core day structure that plugins can extend via DayEnricher.
686
+ * Contains only universal calendar properties.
687
+ * Clients should handle formatting themselves.
688
+ *
689
+ * @template TMetadata - Custom metadata type for events
690
+ * @template TCategory - Category type for events
691
+ *
692
+ * @example
693
+ * ```typescript
694
+ * // Default usage
695
+ * const day: CalendarDay = cal.getDay("2025-12-25");
696
+ *
697
+ * // With typed metadata
698
+ * interface LiturgicalMetadata { rank: string; }
699
+ * const day: CalendarDay<LiturgicalMetadata> = cal.getDay("2025-12-25");
700
+ * day.events[0].metadata?.rank; // TypeScript knows this exists
701
+ * ```
702
+ */
703
+ interface CalendarDay<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends DateComponents {
704
+ /** Date in YYYY-MM-DD format (derived from year/month/day, for convenience) */
705
+ date: string;
706
+ /** Day of week (0=Sunday, 1=Monday, ..., 6=Saturday) */
707
+ dayOfWeek: number;
708
+ /** Events on this day, sorted by priority (highest first; can be empty) */
709
+ events: CalendarEvent<TMetadata, TCategory>[];
710
+ /** Whether this is a Sunday */
711
+ isSunday: boolean;
712
+ /** Whether this is a weekday (Monday-Saturday) */
713
+ isWeekday: boolean;
714
+ }
715
+ /**
716
+ * Day enricher interface - plugins implement this to add data to days
717
+ *
718
+ * @example
719
+ * ```typescript
720
+ * // Lunar plugin enricher
721
+ * const lunarEnricher: DayEnricher = {
722
+ * name: 'lunar',
723
+ * priority: 10,
724
+ * enrich: (day) => ({
725
+ * ...day,
726
+ * lunarDate: solarToLunar(day.year, day.month, day.day)
727
+ * })
728
+ * };
729
+ * ```
730
+ */
731
+ interface DayEnricher {
732
+ /** Unique name for this enricher */
733
+ name: string;
734
+ /** Priority order (lower runs first, default: 100) */
735
+ priority: number;
736
+ /** Enrich a day with additional data */
737
+ enrich: (day: CalendarDay<any, any>) => CalendarDay<any, any>;
738
+ }
739
+ /**
740
+ * Options for getDays() method
741
+ */
742
+ interface GetDaysOptions {
743
+ /** Start date (inclusive) - DateComponents or YYYY-MM-DD string */
744
+ from: DateComponents | string;
745
+ /** End date (inclusive) - DateComponents or YYYY-MM-DD string */
746
+ to: DateComponents | string;
747
+ /** Filter by group IDs (optional) */
748
+ groups?: string[];
749
+ /** Filter by event types (optional) */
750
+ types?: string[];
751
+ }
752
+
753
+ /**
754
+ * Context passed to plugins during installation
755
+ * (Currently empty - reserved for future use)
756
+ */
757
+ interface PluginContext {
758
+ }
759
+ /**
760
+ * Event type handler - processes a specific event type
761
+ */
762
+ interface EventTypeHandler {
763
+ validate(event: unknown): boolean;
764
+ generate(event: EventConfig, year: number): Date[];
765
+ }
766
+ /**
767
+ * Formula function type
768
+ */
769
+ type FormulaFn = (year: number, params?: Record<string, unknown>) => Date;
770
+ /**
771
+ * Calendary interface for plugins.
772
+ * Defines the methods plugins can use during installation.
773
+ * This avoids circular dependency with the actual implementation.
774
+ */
775
+ interface CalendaryForPlugin {
776
+ use(plugin: CalendaryPlugin): this;
777
+ hasPlugin(name: string): boolean;
778
+ registerFormula(name: string, fn: FormulaFn): this;
779
+ registerDayEnricher(enricher: DayEnricher): this;
780
+ hasDayEnricher(name: string): boolean;
781
+ addGroup(config: GroupConfig): this;
782
+ removeGroup(groupId: string): this;
783
+ }
784
+ /**
785
+ * Plugin interface
786
+ *
787
+ * Plugins work with any Calendary instance regardless of its generic parameters.
788
+ * The install function receives a Calendary with unknown metadata/category types.
789
+ */
790
+ interface CalendaryPlugin {
791
+ name: string;
792
+ version: string;
793
+ dependencies?: string[];
794
+ install(calendary: CalendaryForPlugin, context?: PluginContext): void;
795
+ eventTypes?: Record<string, EventTypeHandler>;
796
+ formulas?: Record<string, FormulaFn>;
797
+ }
798
+ /**
799
+ * Plugin factory function type
800
+ */
801
+ type PluginFactory = (options?: Record<string, unknown>) => CalendaryPlugin;
802
+
803
+ /**
804
+ * Base localizable text for any event type.
805
+ * Plugins extend this pattern with their own key types.
806
+ *
807
+ * @example
808
+ * ```typescript
809
+ * // Plugin defines its texts type
810
+ * type LiturgicalTexts = Record<MassKey, EventText>;
811
+ *
812
+ * // User provides translations
813
+ * const viTexts: LiturgicalTexts = {
814
+ * EASTER_SUNDAY: { title: "Easter Sunday", keywords: ["easter"] },
815
+ * // ...
816
+ * };
817
+ * ```
818
+ */
819
+ interface EventText {
820
+ /** Event title (required) */
821
+ title: string;
822
+ /** Search keywords for filtering/search (optional) */
823
+ keywords?: string[];
824
+ }
825
+
826
+ /**
827
+ * Metadata query type for searching nested metadata objects.
828
+ * Supports deep object matching and value comparisons.
829
+ *
830
+ * @template TMetadata - Custom metadata type for type-safe queries
831
+ */
832
+ type MetadataQuery<TMetadata extends Record<string, unknown> = Record<string, unknown>> = Partial<TMetadata> & {
833
+ [key: string]: unknown;
834
+ };
835
+ /**
836
+ * Sort field options for search results
837
+ */
838
+ type SortField = "date" | "priority" | "id";
839
+ /**
840
+ * Sort order options
841
+ */
842
+ type SortOrder = "asc" | "desc";
843
+ /**
844
+ * Enhanced search options extending basic query options
845
+ */
846
+ interface SearchOptions {
847
+ from?: string;
848
+ to?: string;
849
+ date?: string;
850
+ groups?: string[];
851
+ search?: string;
852
+ titleContains?: string;
853
+ idContains?: string;
854
+ descriptionContains?: string;
855
+ keywordsContain?: string[];
856
+ categories?: string[];
857
+ hasAllCategories?: string[];
858
+ metadata?: MetadataQuery;
859
+ minPriority?: number;
860
+ maxPriority?: number;
861
+ sortBy?: SortField;
862
+ sortOrder?: SortOrder;
863
+ limit?: number;
864
+ offset?: number;
865
+ }
866
+ /**
867
+ * Fluent search builder for advanced event queries.
868
+ * Provides case-insensitive text search, metadata search, and more.
869
+ *
870
+ * @template TMetadata - Custom metadata type for events
871
+ * @template TCategory - Category type for events
872
+ *
873
+ * @example
874
+ * ```typescript
875
+ * // Get events
876
+ * const events = cal.search()
877
+ * .text('christmas')
878
+ * .categories(['holiday'])
879
+ * .metadata({ season: 'advent' })
880
+ * .dateFrom('2025-01-01')
881
+ * .dateTo('2025-12-31')
882
+ * .sortBy('date', 'asc')
883
+ * .getEvents();
884
+ *
885
+ * // Get days containing matching events
886
+ * const days = cal.search()
887
+ * .text('easter')
888
+ * .getDays();
889
+ * ```
890
+ */
891
+ declare class SearchBuilder<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> {
892
+ private options;
893
+ private executor;
894
+ constructor(executor: (options: SearchOptions) => CalendarEvent<TMetadata, TCategory>[]);
895
+ /**
896
+ * Search text in title and description (case-insensitive)
897
+ */
898
+ text(search: string): this;
899
+ /**
900
+ * Search text in title only (case-insensitive)
901
+ */
902
+ title(search: string): this;
903
+ /**
904
+ * Search text in id only (case-insensitive)
905
+ */
906
+ id(search: string): this;
907
+ /**
908
+ * Search text in description only (case-insensitive)
909
+ */
910
+ description(search: string): this;
911
+ /**
912
+ * Search by keywords (match any)
913
+ */
914
+ keywords(keywords: string[]): this;
915
+ /**
916
+ * Search by single keyword
917
+ */
918
+ keyword(keyword: string): this;
919
+ /**
920
+ * Filter by date range (inclusive on both ends)
921
+ */
922
+ range(from: string, to: string): this;
923
+ /**
924
+ * Filter events from this date onwards (inclusive, uses >=)
925
+ * @param date - Start date in YYYY-MM-DD format
926
+ */
927
+ dateFrom(date: string): this;
928
+ /**
929
+ * Filter events up to this date (inclusive, uses <=)
930
+ * @param date - End date in YYYY-MM-DD format
931
+ */
932
+ dateTo(date: string): this;
933
+ /**
934
+ * Filter by specific date
935
+ */
936
+ date(date: string): this;
937
+ /**
938
+ * Filter by groups
939
+ */
940
+ groups(groupIds: string[]): this;
941
+ /**
942
+ * Filter by single group
943
+ */
944
+ group(groupId: string): this;
945
+ /**
946
+ * Filter by categories (match any)
947
+ */
948
+ categories(categories: string[]): this;
949
+ /**
950
+ * Filter by category (single)
951
+ */
952
+ category(category: string): this;
953
+ /**
954
+ * Filter by categories (must have all)
955
+ */
956
+ hasAllCategories(categories: string[]): this;
957
+ /**
958
+ * Search by metadata key-value pairs.
959
+ * Supports nested objects and case-insensitive string matching.
960
+ *
961
+ * @example
962
+ * ```typescript
963
+ * // Simple key-value
964
+ * .metadata({ season: 'advent' })
965
+ *
966
+ * // Nested object
967
+ * .metadata({ location: { country: 'fr' } })
968
+ *
969
+ * // Multiple conditions (AND)
970
+ * .metadata({ rank: 'solemnity', vestmentColor: 'white' })
971
+ * ```
972
+ */
973
+ metadata(query: MetadataQuery<TMetadata>): this;
974
+ /**
975
+ * Filter by minimum priority (higher = more important; default 0)
976
+ */
977
+ minPriority(priority: number): this;
978
+ /**
979
+ * Filter by maximum priority (higher = more important; default 0)
980
+ */
981
+ maxPriority(priority: number): this;
982
+ /**
983
+ * Sort results by field and order
984
+ */
985
+ sortBy(field: SortField, order?: SortOrder): this;
986
+ /**
987
+ * Limit number of results
988
+ */
989
+ limit(count: number): this;
990
+ /**
991
+ * Offset results (for pagination)
992
+ */
993
+ offset(count: number): this;
994
+ /**
995
+ * Get the first matching event or undefined
996
+ */
997
+ first(): CalendarEvent<TMetadata, TCategory> | undefined;
998
+ /**
999
+ * Check if any events match the search criteria
1000
+ */
1001
+ exists(): boolean;
1002
+ /**
1003
+ * Count matching events
1004
+ */
1005
+ count(): number;
1006
+ /**
1007
+ * Get matching events with typed result.
1008
+ * Returns only the events that match the search criteria.
1009
+ *
1010
+ * @returns Array of matching events
1011
+ *
1012
+ * @example
1013
+ * ```typescript
1014
+ * // Basic usage
1015
+ * const events = cal.search().text('christmas').getEvents();
1016
+ *
1017
+ * // With typed calendar (generics flow automatically)
1018
+ * const cal = calendary<LiturgicalMetadata>();
1019
+ * const events = cal.search().metadata({ rank: 'solemnity' }).getEvents();
1020
+ * // events[0].metadata?.rank is typed!
1021
+ * ```
1022
+ */
1023
+ getEvents(): CalendarEvent<TMetadata, TCategory>[];
1024
+ /**
1025
+ * Get calendar days containing matching events.
1026
+ * Returns days with their events sorted by keyword match score (highest first),
1027
+ * then by priority.
1028
+ *
1029
+ * @returns Array of calendar days containing matching events
1030
+ *
1031
+ * @example
1032
+ * ```typescript
1033
+ * // Basic usage - get days with matching events
1034
+ * const days = cal.search().text('christmas').getDays();
1035
+ *
1036
+ * // With typed calendar (generics flow automatically)
1037
+ * const cal = calendary<LiturgicalMetadata>();
1038
+ * const days = cal.search().category('holiday').getDays();
1039
+ * // days[0].events[0].metadata?.rank is typed!
1040
+ * ```
1041
+ */
1042
+ getDays(): CalendarDay<TMetadata, TCategory>[];
1043
+ }
1044
+
1045
+ declare const IS_CALENDARY = "$isCalendary";
1046
+ /**
1047
+ * Check if value is a Calendary instance
1048
+ */
1049
+ declare const isCalendary: <TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string>(d: unknown) => d is CalendaryInstance<TMetadata, TCategory>;
1050
+ /**
1051
+ * CalendaryInstance class - the main instance
1052
+ *
1053
+ * @template TMetadata - Custom metadata type for events
1054
+ * @template TCategory - Category type for events
1055
+ *
1056
+ * @example
1057
+ * ```typescript
1058
+ * // Default usage
1059
+ * const cal = calendary();
1060
+ *
1061
+ * // With typed metadata
1062
+ * interface LiturgicalMetadata { rank: string; vestmentColor: string; }
1063
+ * const cal = calendary<LiturgicalMetadata>();
1064
+ * const day = cal.getDay("2025-12-25");
1065
+ * day.events[0].metadata?.rank; // TypeScript knows this exists
1066
+ * ```
1067
+ */
1068
+ declare class CalendaryInstance<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> {
1069
+ [IS_CALENDARY]: boolean;
1070
+ private plugins;
1071
+ private eventTypeHandlers;
1072
+ private formulas;
1073
+ private groups;
1074
+ private cache;
1075
+ private index;
1076
+ private dirty;
1077
+ private extensionData;
1078
+ private dayEnrichers;
1079
+ constructor(cfg?: {
1080
+ x?: Record<string, unknown>;
1081
+ });
1082
+ private registerCoreHandlers;
1083
+ /**
1084
+ * Clone this instance
1085
+ */
1086
+ clone(): CalendaryInstance<TMetadata, TCategory>;
1087
+ /**
1088
+ * Register a plugin (Day.js style: use/extend)
1089
+ */
1090
+ use(plugin: CalendaryPlugin): this;
1091
+ hasPlugin(name: string): boolean;
1092
+ registerFormula(name: string, fn: FormulaFn): this;
1093
+ addGroup(config: GroupInput): this;
1094
+ /**
1095
+ * Load a {@link Collection} — a portable bundle of events (a `.cdy` document
1096
+ * is its JSON form). Pass an object or a JSON string. The declared plugins must
1097
+ * already be registered (`cal.use(...)`); a clear error lists any that aren't.
1098
+ * Nothing is fetched or executed behind your back.
1099
+ */
1100
+ load(collection: Collection | string): this;
1101
+ removeGroup(groupId: string): this;
1102
+ getGroup(groupId: string): Group | undefined;
1103
+ getGroups(): Group[];
1104
+ setGroupEnabled(groupId: string, enabled: boolean): this;
1105
+ private generateEvents;
1106
+ private createCalendarEvent;
1107
+ getEvents(date: string): CalendarEvent<TMetadata, TCategory>[];
1108
+ getEventsInRange(from: string, to: string): CalendarEvent<TMetadata, TCategory>[];
1109
+ getEventsByGroup(groupId: string, from?: string, to?: string): CalendarEvent<TMetadata, TCategory>[];
1110
+ getUpcomingEvents(days: number): CalendarEvent<TMetadata, TCategory>[];
1111
+ /**
1112
+ * Register a day enricher to add custom data to CalendarDay objects
1113
+ */
1114
+ registerDayEnricher(enricher: DayEnricher): this;
1115
+ /**
1116
+ * Check if a day enricher is registered
1117
+ */
1118
+ hasDayEnricher(name: string): boolean;
1119
+ /**
1120
+ * Get calendar days for a date range
1121
+ */
1122
+ getDays(options: GetDaysOptions): CalendarDay<TMetadata, TCategory>[];
1123
+ /**
1124
+ * Get a single calendar day
1125
+ */
1126
+ getDay(date: GetDaysOptions["from"]): CalendarDay<TMetadata, TCategory>;
1127
+ /**
1128
+ * Create a search builder for advanced event queries.
1129
+ * Supports text search, metadata search, categories, and more.
1130
+ *
1131
+ * @example
1132
+ * ```typescript
1133
+ * // Search by text (case-insensitive)
1134
+ * cal.search().text('christmas').getEvents();
1135
+ *
1136
+ * // Search by metadata
1137
+ * cal.search().metadata({ season: 'advent' }).getEvents();
1138
+ *
1139
+ * // Combined search
1140
+ * cal.search()
1141
+ * .text('easter')
1142
+ * .categories(['liturgical'])
1143
+ * .range('2025-01-01', '2025-12-31')
1144
+ * .sortBy('date', 'asc')
1145
+ * .getEvents();
1146
+ * ```
1147
+ */
1148
+ search(): SearchBuilder<TMetadata, TCategory>;
1149
+ private executeSearch;
1150
+ invalidateCache(): this;
1151
+ }
1152
+ /**
1153
+ * Factory function interface with generics support
1154
+ *
1155
+ * @template TMetadata - Custom metadata type for events
1156
+ * @template TCategory - Category type for events
1157
+ */
1158
+ interface CalendaryFn {
1159
+ <TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string>(cfg?: {
1160
+ x?: Record<string, unknown>;
1161
+ }): CalendaryInstance<TMetadata, TCategory>;
1162
+ isCalendary: typeof isCalendary;
1163
+ }
1164
+ /**
1165
+ * Factory function (Day.js style) with generics support
1166
+ *
1167
+ * @template TMetadata - Custom metadata type for events
1168
+ * @template TCategory - Category type for events
1169
+ *
1170
+ * @example
1171
+ * ```typescript
1172
+ * // Default usage
1173
+ * const cal = calendary();
1174
+ *
1175
+ * // With typed metadata
1176
+ * interface LiturgicalMetadata { rank: string; vestmentColor: string; }
1177
+ * const cal = calendary<LiturgicalMetadata>();
1178
+ * const day = cal.getDay("2025-12-25");
1179
+ * day.events[0].metadata?.rank; // TypeScript knows this exists
1180
+ *
1181
+ * // With typed metadata and categories
1182
+ * type LiturgicalCategory = "solemnity" | "feast" | "memorial";
1183
+ * const cal = calendary<LiturgicalMetadata, LiturgicalCategory>();
1184
+ * ```
1185
+ */
1186
+ declare const calendary: CalendaryFn;
1187
+
1188
+ /**
1189
+ * Format a Date object to ISO date string (YYYY-MM-DD)
1190
+ */
1191
+ declare function formatDate(date: Date): string;
1192
+ /**
1193
+ * Parse an ISO date string to Date object
1194
+ */
1195
+ declare function parseDate(dateStr: string): Date;
1196
+ /**
1197
+ * Get the year from a date string or Date object
1198
+ */
1199
+ declare function getYear(date: string | Date): number;
1200
+ /**
1201
+ * Add days to a date
1202
+ */
1203
+ declare function addDays(date: Date, days: number): Date;
1204
+ /**
1205
+ * Get the number of days between two dates
1206
+ */
1207
+ declare function daysBetween(date1: Date, date2: Date): number;
1208
+ /**
1209
+ * Validate if a string is a valid date in YYYY-MM-DD format.
1210
+ * Checks both format and actual date validity.
1211
+ *
1212
+ * @example
1213
+ * ```typescript
1214
+ * isValidDateString("2025-12-25"); // true
1215
+ * isValidDateString("2025-02-30"); // false (invalid date)
1216
+ * isValidDateString("25-12-2025"); // false (wrong format)
1217
+ * ```
1218
+ */
1219
+ declare function isValidDateString(date: string): boolean;
1220
+
1221
+ /**
1222
+ * Replace date tokens in a template string with parts of a `YYYY-MM-DD` date.
1223
+ * Powers per-occurrence dynamic fields (see `BaseEventProperties.templates`) —
1224
+ * e.g. a `url` that differs per occurrence.
1225
+ *
1226
+ * Tokens (literal, no locale): `{YYYY}` `{MM}` `{DD}` (zero-padded) · `{M}` `{D}`
1227
+ * (no padding). Anything else is left untouched.
1228
+ *
1229
+ * @example
1230
+ * interpolateDateTokens("https://x.com/mass/{D}-{M}", "2026-07-07"); // .../mass/7-7
1231
+ * interpolateDateTokens("https://x.com/mass/{DD}-{MM}", "2026-07-07"); // .../mass/07-07
1232
+ */
1233
+ declare function interpolateDateTokens(template: string, date: DateString): string;
1234
+
1235
+ /**
1236
+ * Resolve conflict action from a ConflictResolver (function or map).
1237
+ * Returns "keep" if resolver is undefined or date not in map.
1238
+ *
1239
+ * @param resolver - ConflictResolver (function or map) or undefined
1240
+ * @param date - Date string in YYYY-MM-DD format
1241
+ * @returns ConflictAction for the given date
1242
+ *
1243
+ * @example
1244
+ * ```typescript
1245
+ * // With function resolver
1246
+ * const fn: ConflictResolver = (date) => ({ action: "keep" });
1247
+ * resolveConflict(fn, "2025-01-01"); // { action: "keep" }
1248
+ *
1249
+ * // With map resolver
1250
+ * const map: ConflictResolver = { "2025-01-01": { action: "drop" } };
1251
+ * resolveConflict(map, "2025-01-01"); // { action: "drop" }
1252
+ * resolveConflict(map, "2025-01-02"); // { action: "keep" } (default)
1253
+ *
1254
+ * // With undefined
1255
+ * resolveConflict(undefined, "2025-01-01"); // { action: "keep" }
1256
+ * ```
1257
+ */
1258
+ declare function resolveConflict(resolver: ConflictResolver | undefined, date: DateString): ConflictAction;
1259
+
1260
+ /**
1261
+ * Options for merging event configurations
1262
+ */
1263
+ interface MergeEventConfigOptions {
1264
+ /**
1265
+ * Fields to skip during merge (in addition to metadata which is handled separately)
1266
+ */
1267
+ skipFields?: string[];
1268
+ }
1269
+ /**
1270
+ * Merge custom event configuration into base configuration.
1271
+ * - Skips undefined, empty strings, and empty arrays
1272
+ * - Deep merges metadata (original + custom)
1273
+ *
1274
+ * @param base - Base event configuration
1275
+ * @param custom - Custom configuration to merge (can be undefined)
1276
+ * @param options - Merge options
1277
+ * @returns Merged configuration
1278
+ *
1279
+ * @example
1280
+ * ```typescript
1281
+ * const base = { id: "event-1", title: "Event", metadata: { a: 1, b: 2 } };
1282
+ * const custom = { title: "Custom Event", metadata: { b: 3, c: 4 } };
1283
+ * const result = mergeEventConfig(base, custom);
1284
+ * // Result: { id: "event-1", title: "Custom Event", metadata: { a: 1, b: 3, c: 4 } }
1285
+ * ```
1286
+ */
1287
+ declare function mergeEventConfig<T extends Record<string, unknown>>(base: T, custom: Partial<T> | undefined, options?: MergeEventConfigOptions): T;
1288
+
1289
+ export { type BaseEventProperties, type Buildable, type CalendarDay, type CalendarEvent, CalendaryInstance as Calendary, type CalendaryForPlugin, CalendaryInstance, type CalendaryPlugin, type Collection, type ConflictAction, type ConflictMap, type ConflictResolver, type ConflictResolverFn, type ConstEvent, type CoreEventConfig, type CoreEventType, type CustomEventConfig, type CustomEventResolver, type DateAnchor, type DateComponents, type DateString, type DayEnricher, type EventConfig, type EventText, type EventTypeHandler, type FixedEvent, type FormulaEvent, type FormulaFn, type GetDaysOptions, type Group, type GroupConfig, type GroupInput, type MergeEventConfigOptions, type MetadataQuery, type MonthlyEvent, type NthWeekdayEvent, type OverrideDatesMap, type PluginContext, type PluginFactory, type RelativeEvent, type Reminder, SearchBuilder, type SearchOptions, type SortField, type SortOrder, type WeekdayIndex, type WeeklyEvent, addDays, calendary, daysBetween, formatDate, getYear, interpolateDateTokens, isCalendary, isValidDateString, mergeEventConfig, parseDate, resolveConflict };