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.
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/index.cjs +1356 -0
- package/dist/index.d.cts +1289 -0
- package/dist/index.d.ts +1289 -0
- package/dist/index.js +1341 -0
- package/package.json +79 -0
package/dist/index.d.cts
ADDED
|
@@ -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 };
|