calendaryjs 0.2.1 → 0.2.3

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,590 @@
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
+ export type { BaseEventProperties as B, ConflictResolver as C, DateString as D, EventConfig as E, FixedEvent as F, MonthlyEvent as M, NthWeekdayEvent as N, OverrideDatesMap as O, RelativeEvent as R, WeekdayIndex as W, ConflictAction as a, ConflictMap as b, ConflictResolverFn as c, ConstEvent as d, CoreEventConfig as e, CoreEventType as f, CustomEventConfig as g, CustomEventResolver as h, DateAnchor as i, FormulaEvent as j, Reminder as k, WeeklyEvent as l };