calendaryjs 0.2.3 → 0.3.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 CHANGED
@@ -1,21 +1,147 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 calendaryjs
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Required Notice: Copyright © 2026 calendaryjs (https://github.com/vbilltran68/calendaryjs)
2
+
3
+ calendaryjs (the core engine) is licensed under the PolyForm Noncommercial
4
+ License 1.0.0, reproduced below. It is FREE for any noncommercial purpose.
5
+
6
+ COMMERCIAL USE REQUIRES A SEPARATE COMMERCIAL LICENSE. Running calendaryjs in
7
+ or for a commercial product, service, or revenue-generating site (including
8
+ ad-supported sites) is commercial use. To obtain a commercial license, see
9
+ COMMERCIAL.md or contact the author via the repository above.
10
+
11
+ The official plugins (packages/lunar, hijri, liturgical, ics) are MIT-licensed,
12
+ but they require this core, so commercial use of the combined work still
13
+ requires a commercial license for the core.
14
+
15
+ ----------------------------------------------------------------------
16
+
17
+ # PolyForm Noncommercial License 1.0.0
18
+
19
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
20
+
21
+ ## Acceptance
22
+
23
+ In order to get any license under these terms, you must agree
24
+ to them as both strict obligations and conditions to all
25
+ your licenses.
26
+
27
+ ## Copyright License
28
+
29
+ The licensor grants you a copyright license for the
30
+ software to do everything you might do with the software
31
+ that would otherwise infringe the licensor's copyright
32
+ in it for any permitted purpose. However, you may
33
+ only distribute the software according to [Distribution
34
+ License](#distribution-license) and make changes or new works
35
+ based on the software according to [Changes and New Works
36
+ License](#changes-and-new-works-license).
37
+
38
+ ## Distribution License
39
+
40
+ The licensor grants you an additional copyright license
41
+ to distribute copies of the software. Your license
42
+ to distribute covers distributing the software with
43
+ changes and new works permitted by [Changes and New Works
44
+ License](#changes-and-new-works-license).
45
+
46
+ ## Notices
47
+
48
+ You must ensure that anyone who gets a copy of any part of
49
+ the software from you also gets a copy of these terms or the
50
+ URL for them above, as well as copies of any plain-text lines
51
+ beginning with `Required Notice:` that the licensor provided
52
+ with the software. For example:
53
+
54
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
55
+
56
+ ## Changes and New Works License
57
+
58
+ The licensor grants you an additional copyright license to
59
+ make changes and new works based on the software for any
60
+ permitted purpose.
61
+
62
+ ## Patent License
63
+
64
+ The licensor grants you a patent license for the software that
65
+ covers patent claims the licensor can license, or becomes able
66
+ to license, that you would infringe by using the software.
67
+
68
+ ## Noncommercial Purposes
69
+
70
+ Any noncommercial purpose is a permitted purpose.
71
+
72
+ ## Personal Uses
73
+
74
+ Personal use for research, experiment, and testing for
75
+ the benefit of public knowledge, personal study, private
76
+ entertainment, hobby projects, amateur pursuits, or religious
77
+ observance, without any anticipated commercial application,
78
+ is use for a permitted purpose.
79
+
80
+ ## Noncommercial Organizations
81
+
82
+ Use by any charitable organization, educational institution,
83
+ public research organization, public safety or health
84
+ organization, environmental protection organization,
85
+ or government institution is use for a permitted purpose
86
+ regardless of the source of funding or obligations resulting
87
+ from the funding.
88
+
89
+ ## Fair Use
90
+
91
+ You may have "fair use" rights for the software under the
92
+ law. These terms do not limit them.
93
+
94
+ ## No Other Rights
95
+
96
+ These terms do not allow you to sublicense or transfer any of
97
+ your licenses to anyone else, or prevent the licensor from
98
+ granting licenses to anyone else. These terms do not imply
99
+ any other licenses.
100
+
101
+ ## Patent Defense
102
+
103
+ If you make any written claim that the software infringes or
104
+ contributes to infringement of any patent, your patent license
105
+ for the software granted under these terms ends immediately. If
106
+ your company makes such a claim, your patent license ends
107
+ immediately for work on behalf of your company.
108
+
109
+ ## Violations
110
+
111
+ The first time you are notified in writing that you have
112
+ violated any of these terms, or done anything with the software
113
+ not covered by your licenses, your licenses can nonetheless
114
+ continue if you come into full compliance with these terms,
115
+ and take practical steps to correct past violations, within
116
+ 32 days of receiving notice. Otherwise, all your licenses
117
+ end immediately.
118
+
119
+ ## No Liability
120
+
121
+ ***As far as the law allows, the software comes as is, without
122
+ any warranty or condition, and the licensor will not be liable
123
+ to you for any damages arising out of these terms or the use
124
+ or nature of the software, under any kind of legal claim.***
125
+
126
+ ## Definitions
127
+
128
+ The **licensor** is the individual or entity offering these
129
+ terms, and the **software** is the software the licensor makes
130
+ available under these terms.
131
+
132
+ **You** refers to the individual or entity agreeing to these
133
+ terms.
134
+
135
+ **Your company** is any legal entity, sole proprietorship,
136
+ or other kind of organization that you work for, plus all
137
+ organizations that have control over, are under the control of,
138
+ or are under common control with that organization. **Control**
139
+ means ownership of substantially all the assets of an entity,
140
+ or the power to direct its management and policies by vote,
141
+ contract, or otherwise. Control can be direct or indirect.
142
+
143
+ **Your licenses** are all the licenses granted to you for the
144
+ software under these terms.
145
+
146
+ **Use** means anything you do with the software requiring one
147
+ of your licenses.
package/README.md CHANGED
@@ -7,7 +7,13 @@
7
7
  system (lunar, hijri, liturgical… or your own) with a plugin.
8
8
  </p>
9
9
 
10
- - 🔁 **Recurrence engine** — weekly, monthly, nth-weekday, formula, relative
10
+ <p align="center">
11
+ <a href="https://www.npmjs.com/package/calendaryjs"><img alt="npm" src="https://img.shields.io/npm/v/calendaryjs?style=for-the-badge&color=f59e0b&labelColor=16120F" /></a>
12
+ <a href="https://socket.dev/npm/package/calendaryjs"><img alt="Socket" src="https://img.shields.io/badge/Socket-security-f59e0b?style=for-the-badge&labelColor=16120F" /></a>
13
+ <img alt="Zero dependencies" src="https://img.shields.io/badge/deps-zero-22c55e?style=for-the-badge&labelColor=16120F" />
14
+ </p>
15
+
16
+ - 🔁 **Recurrence engine** — daily, weekly, monthly, nth-weekday, formula, relative
11
17
  - 🔌 **Pluggable calendar systems** — one engine, every calendar
12
18
  - ✍️ **Fluent builder** — `every("week").on("mon", "wed", "fri")`
13
19
  - 🔍 **Query API** — filter events across groups & date ranges
@@ -64,6 +70,9 @@ every("year")
64
70
  .title("Thanksgiving");
65
71
  every(2, "weeks").on("tuesday").title("Standup"); // every other Tuesday
66
72
  every("week").on("monday", "wednesday", "friday").title("Gym"); // pick weekdays
73
+ every("month").on(nth(1, "monday")).title("Retro"); // 1st Monday of every month
74
+ every("month").on(-1).title("Rent"); // last day of every month
75
+ every(3, "days").title("Water plants"); // every 3 days
67
76
  once("2026-06-15").title("Wedding"); // a single date
68
77
  ```
69
78
 
@@ -77,6 +86,9 @@ once("2026-06-15").title("Wedding"); // a single date
77
86
  cal.getEvents("2026-12-25"); // a single day
78
87
  cal.getEventsInRange("2026-01-01", "2026-12-31"); // a range
79
88
  cal.search().group("holidays").range("2026-01-01", "2026-12-31").getEvents();
89
+
90
+ // Filter by kind, status, or source (each matches any of the listed values)
91
+ cal.search().type("weekly").status("confirmed").source("work-feed").getEvents();
80
92
  ```
81
93
 
82
94
  ## 4 · Add a calendar system
@@ -121,19 +133,30 @@ cal.load({
121
133
  cal.load(jsonString); // …or from a .cdy (JSON) string, read from disk or fetched
122
134
  ```
123
135
 
136
+ The reverse — **`cal.toCollection()`** — serializes events back out, declaring the plugins
137
+ their types need so the result re-loads cleanly. `JSON.stringify` it for a `.cdy` file:
138
+
139
+ ```ts
140
+ const doc = cal.toCollection({ name: "holidays" });
141
+ // → { collection: "holidays", plugins: ["calendaryjs-plugin-lunar"], events: [ … ] }
142
+
143
+ JSON.stringify(doc); // the .cdy form — write to disk, or POST it somewhere
144
+ ```
145
+
124
146
  ## Reference
125
147
 
126
148
  ### Event types
127
149
 
128
- | Type | Description | Example |
129
- | ------------- | ------------------------------------------ | ---------------------------- |
130
- | `const` | Fixed annual date | Christmas (Dec 25) |
131
- | `fixed` | One-time date | Wedding (Jun 15, 2025) |
132
- | `monthly` | Same day every month (interval/exclusions) | Payday (15th) |
133
- | `weekly` | One or more weekdays (interval/range) | Standup (Mon, Wed, Fri) |
134
- | `nth-weekday` | Nth weekday of a month, or anchored | Thanksgiving (4th Thu / Nov) |
135
- | `formula` | Custom formula | Last Monday of May |
136
- | `relative` | Offset from a registered anchor | 49 days after an anchor |
150
+ | Type | Description | Example |
151
+ | ------------- | -------------------------------------------------- | --------------------------- |
152
+ | `const` | Fixed annual date (negative day = from month end) | Christmas (Dec 25) |
153
+ | `fixed` | One-time date | Wedding (Jun 15, 2025) |
154
+ | `monthly` | Same day every month (interval; `-1` = last day) | Payday (15th) · Rent (-1) |
155
+ | `weekly` | One or more weekdays (interval/range) | Standup (Mon, Wed, Fri) |
156
+ | `daily` | Every day, or every N days | Water plants (every 3 days) |
157
+ | `nth-weekday` | Nth weekday of a month — or every month — anchored | 1st Monday of every month |
158
+ | `formula` | Custom formula | Last business day of May |
159
+ | `relative` | Offset from a registered anchor | 49 days after an anchor |
137
160
 
138
161
  Plugins add more types (e.g. `lunar`, `hijri`, `liturgical`).
139
162
 
@@ -162,4 +185,6 @@ Every event shares `BaseEventProperties` plus its type-specific date fields.
162
185
 
163
186
  ## License
164
187
 
165
- MIT
188
+ [PolyForm Noncommercial 1.0.0](./LICENSE) — free for any noncommercial purpose.
189
+ **Commercial use (incl. ad-supported sites) requires a commercial license** — see
190
+ [COMMERCIAL.md](https://github.com/vbilltran68/calendaryjs/blob/main/COMMERCIAL.md).
@@ -28,6 +28,9 @@ var MONTHS = {
28
28
  dec: 12
29
29
  };
30
30
  function nth(n, weekday, month) {
31
+ if (month === void 0) {
32
+ return { type: "nth-weekday", fields: { nth: n, dayOfWeek: WEEKDAYS[weekday] } };
33
+ }
31
34
  const m = typeof month === "number" ? month : MONTHS[month];
32
35
  if (!m) throw new Error(`unknown month "${month}"`);
33
36
  return { type: "nth-weekday", fields: { nth: n, dayOfWeek: WEEKDAYS[weekday], month: m } };
@@ -154,6 +157,7 @@ var BaseBuilder = class {
154
157
  return id;
155
158
  }
156
159
  };
160
+ var INTERVAL_TYPES = /* @__PURE__ */ new Set(["weekly", "monthly", "daily"]);
157
161
  var EventBuilder = class extends BaseBuilder {
158
162
  constructor(unit, interval) {
159
163
  super();
@@ -191,7 +195,10 @@ var EventBuilder = class extends BaseBuilder {
191
195
  return this;
192
196
  }
193
197
  const sel = selector;
194
- if (this.unit !== "year") throw new Error(`.on(<selector>) needs every("year")`);
198
+ const monthlyNth = sel.type === "nth-weekday" && this.unit === "month";
199
+ if (!monthlyNth && this.unit !== "year") {
200
+ throw new Error(`.on(<selector>) needs every("year")`);
201
+ }
195
202
  this.selectorType = sel.type;
196
203
  Object.assign(this.fields, sel.fields);
197
204
  return this;
@@ -226,6 +233,9 @@ var EventBuilder = class extends BaseBuilder {
226
233
  return this;
227
234
  }
228
235
  build() {
236
+ if (!this.selectorType && this.unit === "day") {
237
+ this.selectorType = "daily";
238
+ }
229
239
  if (!this.selectorType) {
230
240
  throw new Error(`every("${this.unit}") needs a position \u2014 call .on(...)`);
231
241
  }
@@ -236,7 +246,7 @@ var EventBuilder = class extends BaseBuilder {
236
246
  ...this.payload
237
247
  };
238
248
  if (this.interval > 1) {
239
- if (this.selectorType !== "weekly" && this.selectorType !== "monthly") {
249
+ if (!INTERVAL_TYPES.has(this.selectorType)) {
240
250
  throw new Error(
241
251
  `every(${this.interval}, \u2026) is not supported for "${this.selectorType}" yet`
242
252
  );
@@ -318,6 +328,9 @@ function weekly(weekday, ...moreWeekdays) {
318
328
  function monthly(day) {
319
329
  return every("month").on(day);
320
330
  }
331
+ function daily() {
332
+ return every("day");
333
+ }
321
334
  function yearly(month, day) {
322
335
  return every("year").on(date(month, day));
323
336
  }
@@ -328,6 +341,7 @@ function slug(value) {
328
341
  exports.EventBuilder = EventBuilder;
329
342
  exports.FixedBuilder = FixedBuilder;
330
343
  exports.RelativeBuilder = RelativeBuilder;
344
+ exports.daily = daily;
331
345
  exports.date = date;
332
346
  exports.every = every;
333
347
  exports.from = from;
@@ -1,4 +1,4 @@
1
- import { E as EventConfig } from '../events-Bm7R9XFB.cjs';
1
+ import { E as EventConfig } from '../events-DSiv9j6V.cjs';
2
2
 
3
3
  /**
4
4
  * A position selector — passed to {@link EventBuilder.on}. It contributes the
@@ -38,7 +38,13 @@ declare const MONTHS: {
38
38
  readonly dec: 12;
39
39
  };
40
40
  type MonthName = keyof typeof MONTHS;
41
- /** Annual position → an `nth-weekday` event: `nth(2, "sunday", "may")`. */
41
+ /**
42
+ * An `nth-weekday` position. With a month it's an annual date —
43
+ * `every("year").on(nth(2, "sunday", "may"))` (2nd Sunday of May). Without a
44
+ * month it repeats monthly — `every("month").on(nth(1, "monday"))` (1st Monday
45
+ * of every month).
46
+ */
47
+ declare function nth(n: 1 | 2 | 3 | 4 | -1, weekday: Weekday): Selector;
42
48
  declare function nth(n: 1 | 2 | 3 | 4 | -1, weekday: Weekday, month: MonthName | number): Selector;
43
49
  type Unit = "day" | "week" | "month" | "year";
44
50
  declare const PLURAL: Record<string, Unit>;
@@ -145,9 +151,11 @@ declare function once(dateStr: string): FixedBuilder;
145
151
  * a multi-day weekly: `weekly("monday","wednesday","friday")`.
146
152
  */
147
153
  declare function weekly(weekday: Weekday, ...moreWeekdays: Weekday[]): EventBuilder;
148
- /** `monthly(15)` = `every("month").on(15)`. */
154
+ /** `monthly(15)` = `every("month").on(15)`; `monthly(-1)` = the last day of every month. */
149
155
  declare function monthly(day: number): EventBuilder;
156
+ /** `daily()` = `every("day")` — every day. Use `every(N, "days")` for a wider cadence. */
157
+ declare function daily(): EventBuilder;
150
158
  /** `yearly(12, 25)` = `every("year").on(date(12, 25))`. */
151
159
  declare function yearly(month: number, day: number): EventBuilder;
152
160
 
153
- export { EventBuilder, FixedBuilder, type MonthName, RelativeBuilder, type Selector, type Weekday, date, every, from, monthly, nth, once, weekly, yearly };
161
+ export { EventBuilder, FixedBuilder, type MonthName, RelativeBuilder, type Selector, type Weekday, daily, date, every, from, monthly, nth, once, weekly, yearly };
@@ -1,4 +1,4 @@
1
- import { E as EventConfig } from '../events-Bm7R9XFB.js';
1
+ import { E as EventConfig } from '../events-DSiv9j6V.js';
2
2
 
3
3
  /**
4
4
  * A position selector — passed to {@link EventBuilder.on}. It contributes the
@@ -38,7 +38,13 @@ declare const MONTHS: {
38
38
  readonly dec: 12;
39
39
  };
40
40
  type MonthName = keyof typeof MONTHS;
41
- /** Annual position → an `nth-weekday` event: `nth(2, "sunday", "may")`. */
41
+ /**
42
+ * An `nth-weekday` position. With a month it's an annual date —
43
+ * `every("year").on(nth(2, "sunday", "may"))` (2nd Sunday of May). Without a
44
+ * month it repeats monthly — `every("month").on(nth(1, "monday"))` (1st Monday
45
+ * of every month).
46
+ */
47
+ declare function nth(n: 1 | 2 | 3 | 4 | -1, weekday: Weekday): Selector;
42
48
  declare function nth(n: 1 | 2 | 3 | 4 | -1, weekday: Weekday, month: MonthName | number): Selector;
43
49
  type Unit = "day" | "week" | "month" | "year";
44
50
  declare const PLURAL: Record<string, Unit>;
@@ -145,9 +151,11 @@ declare function once(dateStr: string): FixedBuilder;
145
151
  * a multi-day weekly: `weekly("monday","wednesday","friday")`.
146
152
  */
147
153
  declare function weekly(weekday: Weekday, ...moreWeekdays: Weekday[]): EventBuilder;
148
- /** `monthly(15)` = `every("month").on(15)`. */
154
+ /** `monthly(15)` = `every("month").on(15)`; `monthly(-1)` = the last day of every month. */
149
155
  declare function monthly(day: number): EventBuilder;
156
+ /** `daily()` = `every("day")` — every day. Use `every(N, "days")` for a wider cadence. */
157
+ declare function daily(): EventBuilder;
150
158
  /** `yearly(12, 25)` = `every("year").on(date(12, 25))`. */
151
159
  declare function yearly(month: number, day: number): EventBuilder;
152
160
 
153
- export { EventBuilder, FixedBuilder, type MonthName, RelativeBuilder, type Selector, type Weekday, date, every, from, monthly, nth, once, weekly, yearly };
161
+ export { EventBuilder, FixedBuilder, type MonthName, RelativeBuilder, type Selector, type Weekday, daily, date, every, from, monthly, nth, once, weekly, yearly };
@@ -26,6 +26,9 @@ var MONTHS = {
26
26
  dec: 12
27
27
  };
28
28
  function nth(n, weekday, month) {
29
+ if (month === void 0) {
30
+ return { type: "nth-weekday", fields: { nth: n, dayOfWeek: WEEKDAYS[weekday] } };
31
+ }
29
32
  const m = typeof month === "number" ? month : MONTHS[month];
30
33
  if (!m) throw new Error(`unknown month "${month}"`);
31
34
  return { type: "nth-weekday", fields: { nth: n, dayOfWeek: WEEKDAYS[weekday], month: m } };
@@ -152,6 +155,7 @@ var BaseBuilder = class {
152
155
  return id;
153
156
  }
154
157
  };
158
+ var INTERVAL_TYPES = /* @__PURE__ */ new Set(["weekly", "monthly", "daily"]);
155
159
  var EventBuilder = class extends BaseBuilder {
156
160
  constructor(unit, interval) {
157
161
  super();
@@ -189,7 +193,10 @@ var EventBuilder = class extends BaseBuilder {
189
193
  return this;
190
194
  }
191
195
  const sel = selector;
192
- if (this.unit !== "year") throw new Error(`.on(<selector>) needs every("year")`);
196
+ const monthlyNth = sel.type === "nth-weekday" && this.unit === "month";
197
+ if (!monthlyNth && this.unit !== "year") {
198
+ throw new Error(`.on(<selector>) needs every("year")`);
199
+ }
193
200
  this.selectorType = sel.type;
194
201
  Object.assign(this.fields, sel.fields);
195
202
  return this;
@@ -224,6 +231,9 @@ var EventBuilder = class extends BaseBuilder {
224
231
  return this;
225
232
  }
226
233
  build() {
234
+ if (!this.selectorType && this.unit === "day") {
235
+ this.selectorType = "daily";
236
+ }
227
237
  if (!this.selectorType) {
228
238
  throw new Error(`every("${this.unit}") needs a position \u2014 call .on(...)`);
229
239
  }
@@ -234,7 +244,7 @@ var EventBuilder = class extends BaseBuilder {
234
244
  ...this.payload
235
245
  };
236
246
  if (this.interval > 1) {
237
- if (this.selectorType !== "weekly" && this.selectorType !== "monthly") {
247
+ if (!INTERVAL_TYPES.has(this.selectorType)) {
238
248
  throw new Error(
239
249
  `every(${this.interval}, \u2026) is not supported for "${this.selectorType}" yet`
240
250
  );
@@ -316,6 +326,9 @@ function weekly(weekday, ...moreWeekdays) {
316
326
  function monthly(day) {
317
327
  return every("month").on(day);
318
328
  }
329
+ function daily() {
330
+ return every("day");
331
+ }
319
332
  function yearly(month, day) {
320
333
  return every("year").on(date(month, day));
321
334
  }
@@ -323,4 +336,4 @@ function slug(value) {
323
336
  return value.toLowerCase().replace(/đ/g, "d").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "event";
324
337
  }
325
338
 
326
- export { EventBuilder, FixedBuilder, RelativeBuilder, date, every, from, monthly, nth, once, weekly, yearly };
339
+ export { EventBuilder, FixedBuilder, RelativeBuilder, daily, date, every, from, monthly, nth, once, weekly, yearly };
@@ -228,8 +228,10 @@ interface ConstEvent<TMetadata extends Record<string, unknown> = Record<string,
228
228
  */
229
229
  month: number;
230
230
  /**
231
- * Day of the month (1-31)
231
+ * Day of the month. `1`–`31`, or **negative to count from the end**
232
+ * (`-1` = last day, `-2` = second-to-last).
232
233
  * @example 25 // 25th day
234
+ * @example -1 // last day of the month
233
235
  */
234
236
  day: number;
235
237
  /**
@@ -292,9 +294,12 @@ interface FixedEvent<TMetadata extends Record<string, unknown> = Record<string,
292
294
  interface MonthlyEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
293
295
  type: "monthly";
294
296
  /**
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
+ * Day of the month. `1`–`31`, or **negative to count from the end**
298
+ * (`-1` = last day of every month, `-2` = second-to-last).
299
+ * Note: a positive day past the month's length (e.g. 31 in February) is
300
+ * skipped for that month.
297
301
  * @example 15 // 15th of each month
302
+ * @example -1 // last day of every month
298
303
  */
299
304
  day: number;
300
305
  /**
@@ -372,6 +377,43 @@ interface WeeklyEvent<TMetadata extends Record<string, unknown> = Record<string,
372
377
  */
373
378
  excludeDates?: string[];
374
379
  }
380
+ /**
381
+ * DAILY - Recurring events every day, or every N days.
382
+ * Supports an interval, a date range, and date exclusions. With `interval > 1`
383
+ * the cadence is phased off `startDate` (or a fixed epoch when omitted) so
384
+ * "every N days" stays in step across the year boundary instead of resetting.
385
+ * @template TMetadata - Custom metadata type extending Record<string, unknown>
386
+ * @example
387
+ * // Every day
388
+ * { type: "daily", id: "standup" }
389
+ *
390
+ * // Every 3 days, anchored to a start date
391
+ * { type: "daily", id: "meds", interval: 3, startDate: "2025-01-01" }
392
+ */
393
+ interface DailyEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>, TCategory extends string = string> extends BaseEventProperties<TMetadata, TCategory> {
394
+ type: "daily";
395
+ /**
396
+ * Recurrence interval in days (default: 1).
397
+ * @example 3 // every third day
398
+ */
399
+ interval?: number;
400
+ /**
401
+ * Anchor date for interval phasing and the lower bound (YYYY-MM-DD).
402
+ * With `interval > 1`, occurrences land on `startDate`, `startDate + interval`, …
403
+ * @example "2025-01-06"
404
+ */
405
+ startDate?: string;
406
+ /**
407
+ * End date for the recurrence (YYYY-MM-DD, inclusive).
408
+ * @example "2025-12-31"
409
+ */
410
+ endDate?: string;
411
+ /**
412
+ * Specific dates to exclude (YYYY-MM-DD).
413
+ * @example ["2025-12-25"]
414
+ */
415
+ excludeDates?: string[];
416
+ }
375
417
  /**
376
418
  * FORMULA - Custom formula-based events
377
419
  * @template TMetadata - Custom metadata type extending Record<string, unknown>
@@ -434,11 +476,13 @@ interface NthWeekdayEvent<TMetadata extends Record<string, unknown> = Record<str
434
476
  * - `1`–`4`: 1st through 4th occurrence
435
477
  * - `-1`: last occurrence
436
478
  *
437
- * Required in simple mode (paired with `month`). Omit for anchored mode.
479
+ * Required in simple mode. Omit for anchored mode.
438
480
  */
439
481
  nth?: 1 | 2 | 3 | 4 | -1;
440
482
  /**
441
- * Month (1-12). Required in simple mode. Omit for anchored mode.
483
+ * Month (1-12). In simple mode, **omit to repeat every month** (e.g. the first
484
+ * Monday of every month); set it to pin one month (e.g. the 4th Thursday of
485
+ * November). Ignored in anchored mode.
442
486
  */
443
487
  month?: number;
444
488
  /**
@@ -488,7 +532,7 @@ interface RelativeEvent<TMetadata extends Record<string, unknown> = Record<strin
488
532
  weeks?: number;
489
533
  };
490
534
  }
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>;
535
+ 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> | DailyEvent<TMetadata, TCategory> | NthWeekdayEvent<TMetadata, TCategory> | FormulaEvent<TMetadata, TCategory> | RelativeEvent<TMetadata, TCategory>;
492
536
  /**
493
537
  * Generic event config - extended by plugins.
494
538
  * Use the generic parameter to enforce metadata types.
@@ -513,7 +557,7 @@ type EventConfig<TMetadata extends Record<string, unknown> = Record<string, unkn
513
557
  /**
514
558
  * Event type identifiers
515
559
  */
516
- type CoreEventType = "const" | "fixed" | "monthly" | "weekly" | "nth-weekday" | "formula" | "relative";
560
+ type CoreEventType = "const" | "fixed" | "monthly" | "weekly" | "daily" | "nth-weekday" | "formula" | "relative";
517
561
  /**
518
562
  * Custom event configuration for overriding event properties.
519
563
  * Used by generator packages to allow customization of generated events.
@@ -587,4 +631,4 @@ type CustomEventConfig<TMetadata extends Record<string, unknown> = Record<string
587
631
  */
588
632
  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
633
 
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 };
634
+ 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, DailyEvent as i, DateAnchor as j, EventException as k, EventExceptions as l, FormulaEvent as m, Reminder as n, WeeklyEvent as o };