Skip to content

refactor: ESM migration#523

Open
KristjanESPERANTO wants to merge 1 commit into
jens-maus:masterfrom
KristjanESPERANTO:esm
Open

refactor: ESM migration#523
KristjanESPERANTO wants to merge 1 commit into
jens-maus:masterfrom
KristjanESPERANTO:esm

Conversation

@KristjanESPERANTO

@KristjanESPERANTO KristjanESPERANTO commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

This change follows up on issue #521 and moves node-ical to an ESM-first setup, while keeping CommonJS support in place for now.

It turned out to take a bit more than expected. To make the module split cleanly, I moved a fair amount of logic out of the files ical.js and node-ical.js into smaller, focused modules. That makes the code easier to maintain and gives a cleaner base for the module format transition.

I checked compatibility against our main consumers (ioBroker.ical and MagicMirror), and both still work with this setup.

Summary by CodeRabbit

  • New Features
    • Added first-class support for both ES module imports and CommonJS require(), including a CommonJS entry point.
    • Ensured the default export and typings reflect the same public API shape across module formats.
  • Documentation
    • Expanded “Module formats” documentation with ESM/CJS usage examples and updated guidance for top-level await.
    • Updated example scripts to use ES module syntax.
  • Bug Fixes
    • Improved recurring event expansion behavior, including time zone handling, exclusions, and recurrence overrides.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee504fc4-5894-4c4b-90c9-965d96f77a75

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The package is migrated to ESM with explicit CommonJS compatibility, shared parser and recurring-event modules, generated build artifacts, updated timezone tooling, converted examples and tests, and revised package documentation.

Changes

ESM package and calendar processing

Layer / File(s) Summary
Package entrypoints and module runtime
package.json, node-ical.js, node-ical.d.ts, lib/*.js
Package metadata exposes ESM and CommonJS entrypoints, while Temporal, timezone utilities, date utilities, and typings use ESM exports.
Shared parser and recurrence utilities
ical.js, lib/ical-parser-utils.js, lib/expand-recurring-event.js
Parsing, date handling, timezone resolution, recurrence normalization, component storage, EXDATE handling, and recurring expansion are centralized in shared utilities.
Shared API construction
node-ical.js, lib/core-api.js, lib/public-api.js
Sync, async, callback, URL, file, parsing, and recurring-event APIs are composed from shared factories and exposed through the package entrypoint.
Build and timezone data generation
build/*, package.json, .github/workflows/nodejs.yml, .gitignore
A CommonJS bundle and deterministic Windows timezone data are generated through updated scripts and CI steps.
Examples, tests, and compiler migration
examples/*, test/*, tsconfig.json, test/tsconfig.json
Examples and tests use ESM imports, module-format parity and regression behavior are validated, and TypeScript settings use Node ESM resolution.
Module usage documentation
README.md, build/README.md
Documentation describes ESM and CommonJS imports, runtime entry files, typings, example execution, and the updated Windows-zones script.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: jens-maus

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating the package to ESM.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
test/module-formats.test.js (1)

49-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated key-parity check across two tests.

The key-parity loop at Lines 61–63 (for (const key of Object.keys(esm.default)) { assert.ok(Object.hasOwn(cjs, key), ...) }) is repeated verbatim at Lines 73–75 in the next test. Consider consolidating into a shared helper or merging the assertions into a single test to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/module-formats.test.js` around lines 49 - 76, Consolidate the duplicated
ESM/CJS key-parity loop currently present in both tests. Either merge the
assertions into one test or extract a shared helper and reuse it from the tests,
while preserving the existing missing-key assertion message and API checks.
node-ical.d.ts (1)

112-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider more precise types for internal properties currently typed unknown.

objectHandlers, handleObject, and parseLines are typed as unknown, which gives consumers no type safety if they access these properties. If this is intentional to discourage direct use, consider adding a brief comment explaining that. Otherwise, typing them (e.g., as Record<string, (...args: unknown[]) => unknown> and (...args: unknown[]) => unknown) would improve the developer experience for advanced consumers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@node-ical.d.ts` around lines 112 - 125, Improve the declarations for
objectHandlers, handleObject, and parseLines in the default export object:
either replace unknown with accurate callable/record types for these internal
properties, or add a brief comment documenting that unknown is intentional to
discourage direct consumer use. Preserve the existing exported API shape and
type the properties consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build/update-windows-zones.js`:
- Around line 18-45: Update fetchText to enforce a request timeout so stalled
CLDR requests reject instead of hanging CI. Prefer replacing the manual
https.get redirect flow with Node 20’s fetch and AbortSignal.timeout(), while
preserving non-success HTTP errors and returning the response text for
successful requests.

In `@ical.js`:
- Around line 463-475: Update the named exports for handleObject, parseLines,
and parseICS in ical.js to bind each function to the ical object so calls retain
the required this context; apply the same binding treatment to the equivalent
exports in node-ical.js, while leaving objectHandlers unchanged.

In `@lib/expand-recurring-event.js`:
- Around line 94-120: Update getEventDurationMs and calculateEndTime to
represent full-day durations as calendar-day counts rather than fixed
milliseconds. For full-day events, compute the span between local calendar dates
and advance the start using local date arithmetic so DST transitions preserve
the correct end date; keep timed-event millisecond duration behavior unchanged.
- Around line 345-358: Update the recurring-instance expansion around
buildRecurringInstance so it also inspects event.recurrences overrides whose
effective start falls within the requested range, even when their RECURRENCE-ID
is outside searchFrom..searchTo. Merge these override-derived instances with the
RRULE results, deduplicate instances already produced through RRULE expansion,
then retain the existing range filtering and start-time sorting.

In `@lib/ical-parser-utils.js`:
- Around line 41-46: Update copyRecordFields to iterate and copy only source own
properties using Object.hasOwn, and avoid assignment-based writes for the
__proto__ UID by defining UID-keyed properties with Object.defineProperty or
using null-prototype records throughout the related merge/recurrence path. Apply
the same protection to the code around the UID handling at the referenced later
section, preserving normal field-copy behavior for other keys.
- Around line 721-733: Update freebusyParameter so it invokes the FREEBUSY
storage handler returned or registered by storeParameter, rather than merely
calling storeParameter and discarding its result. Ensure the raw value and
parameters are persisted on fb before dateParameter processes the start and end
parts.
- Around line 185-194: Update applyImplicitEndDate for DATE entries to advance
entry.start by one calendar day while preserving local midnight, rather than
using utcAdd(..., 1, 'days'). Keep the existing date-time cloning behavior and
return flow unchanged.

In `@package.json`:
- Around line 4-24: Update the package.json engines.node constraint to require
Node.js >=20.10.0, ensuring the ESM entry using JSON import attributes is not
exposed to unsupported Node 20.0–20.9 versions.

---

Nitpick comments:
In `@node-ical.d.ts`:
- Around line 112-125: Improve the declarations for objectHandlers,
handleObject, and parseLines in the default export object: either replace
unknown with accurate callable/record types for these internal properties, or
add a brief comment documenting that unknown is intentional to discourage direct
consumer use. Preserve the existing exported API shape and type the properties
consistently.

In `@test/module-formats.test.js`:
- Around line 49-76: Consolidate the duplicated ESM/CJS key-parity loop
currently present in both tests. Either merge the assertions into one test or
extract a shared helper and reuse it from the tests, while preserving the
existing missing-key assertion message and API checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b141778-ebaf-4a2d-b1e9-418028e5ec77

📥 Commits

Reviewing files that changed from the base of the PR and between 55a6735 and 85ae175.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (42)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

🧹 Nitpick comments (2)
test/module-formats.test.js (1)

49-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated key-parity check across two tests.

The key-parity loop at Lines 61–63 (for (const key of Object.keys(esm.default)) { assert.ok(Object.hasOwn(cjs, key), ...) }) is repeated verbatim at Lines 73–75 in the next test. Consider consolidating into a shared helper or merging the assertions into a single test to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/module-formats.test.js` around lines 49 - 76, Consolidate the duplicated
ESM/CJS key-parity loop currently present in both tests. Either merge the
assertions into one test or extract a shared helper and reuse it from the tests,
while preserving the existing missing-key assertion message and API checks.
node-ical.d.ts (1)

112-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider more precise types for internal properties currently typed unknown.

objectHandlers, handleObject, and parseLines are typed as unknown, which gives consumers no type safety if they access these properties. If this is intentional to discourage direct use, consider adding a brief comment explaining that. Otherwise, typing them (e.g., as Record<string, (...args: unknown[]) => unknown> and (...args: unknown[]) => unknown) would improve the developer experience for advanced consumers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@node-ical.d.ts` around lines 112 - 125, Improve the declarations for
objectHandlers, handleObject, and parseLines in the default export object:
either replace unknown with accurate callable/record types for these internal
properties, or add a brief comment documenting that unknown is intentional to
discourage direct consumer use. Preserve the existing exported API shape and
type the properties consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build/update-windows-zones.js`:
- Around line 18-45: Update fetchText to enforce a request timeout so stalled
CLDR requests reject instead of hanging CI. Prefer replacing the manual
https.get redirect flow with Node 20’s fetch and AbortSignal.timeout(), while
preserving non-success HTTP errors and returning the response text for
successful requests.

In `@ical.js`:
- Around line 463-475: Update the named exports for handleObject, parseLines,
and parseICS in ical.js to bind each function to the ical object so calls retain
the required this context; apply the same binding treatment to the equivalent
exports in node-ical.js, while leaving objectHandlers unchanged.

In `@lib/expand-recurring-event.js`:
- Around line 94-120: Update getEventDurationMs and calculateEndTime to
represent full-day durations as calendar-day counts rather than fixed
milliseconds. For full-day events, compute the span between local calendar dates
and advance the start using local date arithmetic so DST transitions preserve
the correct end date; keep timed-event millisecond duration behavior unchanged.
- Around line 345-358: Update the recurring-instance expansion around
buildRecurringInstance so it also inspects event.recurrences overrides whose
effective start falls within the requested range, even when their RECURRENCE-ID
is outside searchFrom..searchTo. Merge these override-derived instances with the
RRULE results, deduplicate instances already produced through RRULE expansion,
then retain the existing range filtering and start-time sorting.

In `@lib/ical-parser-utils.js`:
- Around line 41-46: Update copyRecordFields to iterate and copy only source own
properties using Object.hasOwn, and avoid assignment-based writes for the
__proto__ UID by defining UID-keyed properties with Object.defineProperty or
using null-prototype records throughout the related merge/recurrence path. Apply
the same protection to the code around the UID handling at the referenced later
section, preserving normal field-copy behavior for other keys.
- Around line 721-733: Update freebusyParameter so it invokes the FREEBUSY
storage handler returned or registered by storeParameter, rather than merely
calling storeParameter and discarding its result. Ensure the raw value and
parameters are persisted on fb before dateParameter processes the start and end
parts.
- Around line 185-194: Update applyImplicitEndDate for DATE entries to advance
entry.start by one calendar day while preserving local midnight, rather than
using utcAdd(..., 1, 'days'). Keep the existing date-time cloning behavior and
return flow unchanged.

In `@package.json`:
- Around line 4-24: Update the package.json engines.node constraint to require
Node.js >=20.10.0, ensuring the ESM entry using JSON import attributes is not
exposed to unsupported Node 20.0–20.9 versions.

---

Nitpick comments:
In `@node-ical.d.ts`:
- Around line 112-125: Improve the declarations for objectHandlers,
handleObject, and parseLines in the default export object: either replace
unknown with accurate callable/record types for these internal properties, or
add a brief comment documenting that unknown is intentional to discourage direct
consumer use. Preserve the existing exported API shape and type the properties
consistently.

In `@test/module-formats.test.js`:
- Around line 49-76: Consolidate the duplicated ESM/CJS key-parity loop
currently present in both tests. Either merge the assertions into one test or
extract a shared helper and reuse it from the tests, while preserving the
existing missing-key assertion message and API checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b141778-ebaf-4a2d-b1e9-418028e5ec77

📥 Commits

Reviewing files that changed from the base of the PR and between 55a6735 and 85ae175.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (42)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🛑 Comments failed to post (8)
build/update-windows-zones.js (1)

18-45: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify AbortSignal.timeout() and fetch() availability in Node 20+
node -e "
const hasFetch = typeof fetch === 'function';
const hasAbortTimeout = typeof AbortSignal.timeout === 'function';
console.log('fetch:', hasFetch);
console.log('AbortSignal.timeout:', hasAbortTimeout);
if (!hasFetch || !hasAbortTimeout) process.exit(1);
"

Repository: jens-maus/node-ical

Length of output: 195


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the file structure and relevant symbols
ast-grep outline build/update-windows-zones.js --view expanded || true

# Read the file with line numbers
wc -l build/update-windows-zones.js
cat -n build/update-windows-zones.js | sed -n '1,220p'

# Check whether https is used elsewhere in the file
rg -n "\bhttps\b|fetchText|AbortSignal\.timeout|globalThis\.fetch|fetch\(" build/update-windows-zones.js

Repository: jens-maus/node-ical

Length of output: 7600


Add a timeout to fetchText to avoid CI hangs.
https.get() here can wait indefinitely if the CLDR endpoint stalls. Since this script runs on Node 20+, switching to fetch() with AbortSignal.timeout() would also remove the manual redirect handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build/update-windows-zones.js` around lines 18 - 45, Update fetchText to
enforce a request timeout so stalled CLDR requests reject instead of hanging CI.
Prefer replacing the manual https.get redirect flow with Node 20’s fetch and
AbortSignal.timeout(), while preserving non-success HTTP errors and returning
the response text for successful requests.
ical.js (1)

463-475: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bind named exports that depend on this.

Directly importing handleObject, parseLines, or parseICS calls them with this === undefined; each accesses another method or property through this and will fail. Bind these exports to ical. The equivalent exports in node-ical.js need the same treatment.

Proposed fix
 const {
   objectHandlers,
-  handleObject,
-  parseLines,
-  parseICS,
 } = ical;
+const handleObject = ical.handleObject.bind(ical);
+const parseLines = ical.parseLines.bind(ical);
+const parseICS = ical.parseICS.bind(ical);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const {
  objectHandlers,
} = ical;
const handleObject = ical.handleObject.bind(ical);
const parseLines = ical.parseLines.bind(ical);
const parseICS = ical.parseICS.bind(ical);

export {
  objectHandlers,
  handleObject,
  parseLines,
  parseICS,
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ical.js` around lines 463 - 475, Update the named exports for handleObject,
parseLines, and parseICS in ical.js to bind each function to the ical object so
calls retain the required this context; apply the same binding treatment to the
equivalent exports in node-ical.js, while leaving objectHandlers unchanged.
lib/expand-recurring-event.js (2)

94-120: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Use calendar-day spans for full-day event ends.

Adding a fixed millisecond duration to local midnight produces 23:00 or 01:00 ends around DST transitions. Track full-day duration as a number of calendar dates and advance with local calendar arithmetic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/expand-recurring-event.js` around lines 94 - 120, Update
getEventDurationMs and calculateEndTime to represent full-day durations as
calendar-day counts rather than fixed milliseconds. For full-day events, compute
the span between local calendar dates and advance the start using local date
arithmetic so DST transitions preserve the correct end date; keep timed-event
millisecond duration behavior unchanged.

345-358: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Include overrides moved into the requested range.

Only RRULE dates inside searchFrom..searchTo are inspected. An override keyed by an earlier RECURRENCE-ID but moved into this range is therefore omitted. Merge unique event.recurrences whose effective starts overlap the range, while deduplicating overrides already reached through RRULE expansion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/expand-recurring-event.js` around lines 345 - 358, Update the
recurring-instance expansion around buildRecurringInstance so it also inspects
event.recurrences overrides whose effective start falls within the requested
range, even when their RECURRENCE-ID is outside searchFrom..searchTo. Merge
these override-derived instances with the RRULE results, deduplicate instances
already produced through RRULE expansion, then retain the existing range
filtering and start-time sorting.
lib/ical-parser-utils.js (3)

41-46: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Store UID keys without invoking object prototype setters.

A UID of __proto__ resolves to Object.prototype; the merge or recurrence path then writes parsed event fields onto the global prototype. Use Object.hasOwn, copy only own fields, and define UID properties with Object.defineProperty or use null-prototype records.

Proposed approach
+function setRecordField(target, key, value) {
+  Object.defineProperty(target, key, {
+    value,
+    enumerable: true,
+    configurable: true,
+    writable: true,
+  });
+}
+
 function copyRecordFields(target, source) {
-  for (const key in source) {
-    if (key !== null) {
-      target[key] = source[key];
-    }
+  for (const key of Object.keys(source)) {
+    setRecordField(target, key, source[key]);
   }
 }
...
-  if (parentEntry[uid] === undefined) {
-    parentEntry[uid] = sourceEntry;
+  if (!Object.hasOwn(parentEntry, uid)) {
+    setRecordField(parentEntry, uid, sourceEntry);

Also applies to: 99-120

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ical-parser-utils.js` around lines 41 - 46, Update copyRecordFields to
iterate and copy only source own properties using Object.hasOwn, and avoid
assignment-based writes for the __proto__ UID by defining UID-keyed properties
with Object.defineProperty or using null-prototype records throughout the
related merge/recurrence path. Apply the same protection to the code around the
UID handling at the referenced later section, preserving normal field-copy
behavior for other keys.

185-194: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Advance DATE values by a calendar day, not 24 elapsed hours.

utcAdd(..., 1, 'days') adds exactly 24 hours. Across DST transitions, the implicit end can become 23:00 or 01:00 instead of local midnight on the next date.

Proposed fix
   if (entry.duration === undefined) {
-    entry.end = entry.datetype === 'date-time'
-      ? cloneDateWithMeta(entry.start)
-      : cloneDateWithMeta(entry.start, utcAdd(entry.start, 1, 'days'));
+    entry.end = cloneDateWithMeta(entry.start);
+    if (entry.datetype !== 'date-time') {
+      entry.end.setDate(entry.end.getDate() + 1);
+    }
     return entry;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

function applyImplicitEndDate(entry, utcAdd) {
  if (entry.end) {
    return entry;
  }

  if (entry.duration === undefined) {
    entry.end = cloneDateWithMeta(entry.start);
    if (entry.datetype !== 'date-time') {
      entry.end.setDate(entry.end.getDate() + 1);
    }
    return entry;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ical-parser-utils.js` around lines 185 - 194, Update applyImplicitEndDate
for DATE entries to advance entry.start by one calendar day while preserving
local midnight, rather than using utcAdd(..., 1, 'days'). Keep the existing
date-time cloning behavior and return flow unchanged.

721-733: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invoke the FREEBUSY storage handler.

storeParameter(value, parameters, fb) only creates and discards a handler, so the raw FREEBUSY value and its parameters are never stored.

Proposed fix
-      storeParameter(value, parameters, fb);
+      storeParameter(name)(value, parameters, fb);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const freebusyParameter = function (name) {
    return function (value, parameters, curr) {
      const fb = addFBType({}, parameters);
      curr[name] ||= [];
      curr[name].push(fb);

      storeParameter(name)(value, parameters, fb);

      const parts = value.split('/');

      for (const [index, partName] of ['start', 'end'].entries()) {
        dateParameter(partName)(parts[index], parameters, fb);
      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ical-parser-utils.js` around lines 721 - 733, Update freebusyParameter so
it invokes the FREEBUSY storage handler returned or registered by
storeParameter, rather than merely calling storeParameter and discarding its
result. Ensure the raw value and parameters are persisted on fb before
dateParameter processes the start and end parts.
package.json (1)

4-24: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the engines.node field and verify compatibility with import attributes
cat package.json | jq '.engines'

Repository: jens-maus/node-ical

Length of output: 178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## package.json engines and module fields\n'
cat -n package.json | sed -n '1,80p'

printf '\n## referenced source snippets\n'
for f in lib/tz-utils.js lib/temporal.js; do
  echo "\n### $f"
  cat -n "$f" | sed -n '1,220p'
done

printf '\n## shipped entry files\n'
git ls-files 'node-ical.*' 'lib/*.js' 'windowsZones.json'

Repository: jens-maus/node-ical

Length of output: 11636


Bump engines.node to >=20.10.0. lib/tz-utils.js uses JSON import attributes, so >=20 still admits Node 20.0–20.9, which will fail to load the ESM entry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 4 - 24, Update the package.json engines.node
constraint to require Node.js >=20.10.0, ensuring the ESM entry using JSON
import attributes is not exposed to unsupported Node 20.0–20.9 versions.

Source: Learnings

@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

@jens-maus coderabbit seems satisfied. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant