Skip to content

Rework raw.js to function like the new defer mechanism.#879

Merged
jdmarshall merged 2 commits intonode-config:masterfrom
jdmarshall:rawESM
Feb 17, 2026
Merged

Rework raw.js to function like the new defer mechanism.#879
jdmarshall merged 2 commits intonode-config:masterfrom
jdmarshall:rawESM

Conversation

@jdmarshall
Copy link
Copy Markdown
Collaborator

@jdmarshall jdmarshall commented Feb 13, 2026

This should be the last part of the config API that blocks ESM usage.

addresses #878

Summary by CodeRabbit

  • New Features

    • Added a RawConfig utility to mark/preserve raw objects through config processing.
    • Executable config bootstrap callbacks now receive a raw entry in their context.
  • Deprecations

    • Legacy raw() helper deprecated — now delegates to RawConfig and emits a runtime deprecation warning.
  • Exports

    • RawConfig is exported from the primary utility surface (previous export path consolidated).

@jdmarshall jdmarshall added this to the 4.4 milestone Feb 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds a public RawConfig class and factory in lib/util.js, threads RawConfig.raw into bootstrap callback context for executable config files, makes raw.js delegate to RawConfig.raw with a deprecation warning, and updates lib/config.js to import RawConfig from ./util.js.

Changes

Cohort / File(s) Summary
Util — RawConfig & bootstrap context
lib/util.js
Adds RawConfig class and RawConfig.raw() factory; treats RawConfig instances as pass-through in cloneDeep, equalsDeep, and deep-extend logic; includes raw in bootstrap callback typedef and runtime context; exports RawConfig.
Deprecated raw wrapper → delegation
raw.js
Replaces in-file wrapper with raw(rawObj) that delegates to RawConfig.raw(rawObj), emitting a one-time deprecation warning via Util.errorOnce(); preserves public exports.
Import source update
lib/config.js
Switches RawConfig import to destructure from ./util.js alongside Util and Load (replaces previous ../raw import); no behavioral change beyond import source.

Sequence Diagram(s)

sequenceDiagram
  participant Loader as Load
  participant Util as Util
  participant Raw as RawConfig
  participant ConfigFn as Config File (function)
  Loader->>ConfigFn: invoke({ defer, util: Util, raw: Raw.raw })
  ConfigFn->>Raw: (optional) call Raw.raw(...)
  ConfigFn-->>Loader: return config object
  Loader->>Util: clone/merge (treat RawConfig as pass-through)
  Loader-->>Caller: final config
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I nibbled bytes and kept them whole,
A raw little parcel, safe for your goal.
Bootstrap carries my quiet cheer,
No copying fuss — I stay right here,
Hooray — configs hop without a toll!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: reworking raw.js to function like the defer mechanism, which is the central objective of the PR and supported by the summaries showing raw.js delegation to RawConfig with deprecation warnings.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
lib/util.js (4)

460-465: RawConfig pass-through in cloneDeep is correct but could return early.

Setting child = parent preserves the reference (intended). Since RawConfig has no enumerable own properties, the subsequent for...in loop is a no-op, so the result is correct. However, unlike the Buffer case (line 463), this doesn't return child early, meaning the instance is unnecessarily registered in the circular-reference tracking arrays.

Consider adding an early return for consistency with the Buffer path:

♻️ Optional: return early like Buffer
       } else if (parent instanceof RawConfig) {
-        child = parent;
+        return parent;
       } else {

651-670: Pre-existing: Date assignment at line 652 can be overwritten by the fall-through.

The RawConfig addition to the RegExp condition (line 656) is correct — both are properly guarded by the if/else if chain starting at line 655.

However, the standalone if block for Date (lines 651–653) has no else and falls through to the RegExp/RawConfig if at line 655. When mergeFrom[prop] is a Date, it's assigned at line 652, but then the else if at line 658 can fire (since Util.isObject(date) is true), potentially calling extendDeep on it. This is pre-existing and not introduced by this PR, but worth noting since you're editing this block.

♻️ Optional: chain the Date block into the if/else
-        if (mergeFrom[prop] instanceof Date) {
+        if (mergeFrom[prop] instanceof Date) {
           mergeInto[prop] = mergeFrom[prop];
-        }
-
-        if ((mergeFrom[prop] instanceof RegExp) ||
+        } else if ((mergeFrom[prop] instanceof RegExp) ||
             (mergeFrom[prop] instanceof RawConfig)) {

1040-1045: Bootstrap callback context looks good; minor JSDoc nit.

Passing raw: RawConfig.raw as a static method reference is consistent with how defer: deferConfig is passed.

Line 1041: @type bootstrapCallback should use braces per JSDoc convention: @type {bootstrapCallback}.

📝 Fix JSDoc syntax
-      /** `@type` bootstrapCallback */
+      /** `@type` {bootstrapCallback} */

17-22: JSDoc type {RawConfig.raw} is non-standard.

RawConfig.raw is a static method, so {RawConfig.raw} isn't a valid JSDoc type reference. Consider using {typeof RawConfig.raw} or an explicit function signature like {function(Object): RawConfig}.


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@lib/util.js`:
- Around line 1020-1023: Fix the JSDoc typo: change the annotation `@type
defebootstrapCallback` to the correct `@type {bootstrapCallback}` so the
documented type matches the actual callback definition; update the comment
immediately above where `let fn = configObject;` is declared (referencing
symbols `configObject`, `fn`) and ensure the typedef name `bootstrapCallback`
matches the callback used earlier in the file (lines that reference `Util` and
`RawConfig.raw`).

In `@raw.js`:
- Around line 1-6: lib/config.js currently imports RawConfig indirectly via
raw.js (which calls Util.errorOnce and delegates to RawConfig.raw), creating a
dependency on a deprecated module; change the require in lib/config.js to import
RawConfig directly from lib/util (the module that defines and exports RawConfig)
and update any references to RawConfig there to use the directly imported symbol
so behavior is unchanged but the deprecated internal dependency is removed.
🧹 Nitpick comments (2)
lib/util.js (2)

453-454: Consider an early return for RawConfig in cloneDeep, similar to the Buffer branch.

Currently child = parent falls through to the circular-reference tracking and the for…in property loop. Since RawConfig has no enumerable own properties, the loop is a no-op, but an early return would make the intent clearer and avoid unnecessary work.

Proposed fix
       } else if (parent instanceof RawConfig) {
-        child = parent;
+        return parent;
       } else {

23-24: raw property is missing its type in the @param JSDoc.

The context object documents types for util and defer but omits the type for raw. For consistency and IDE support, consider adding it.

Proposed fix
- * `@param` context { util: Util, defer: deferConfig, raw } - utility functions for startup
+ * `@param` context {{ util: Util, defer: deferConfig, raw: RawConfig.raw }} - utility functions for startup

@jdmarshall jdmarshall force-pushed the rawESM branch 2 times, most recently from dd3c1d3 to f1889d0 Compare February 13, 2026 06:04
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/util.js (1)

640-659: ⚠️ Potential issue | 🔴 Critical

Breaking the if/else chain causes double-processing of Date (and other) values.

The if on line 644 is no longer chained as an else if to the Date check on line 640. This means when mergeFrom[prop] is a Date:

  1. Line 641 assigns it correctly.
  2. Execution falls through to line 644's if, which doesn't match.
  3. The else if on line 647 sees both sides as objects (typeof Date === 'object') and calls extendDeep on the Date, corrupting it.

Re-attach the chain with else if:

🐛 Proposed fix
       if (mergeFrom[prop] instanceof Date) {
         mergeInto[prop] = mergeFrom[prop];
-      }
-
-      if ((mergeFrom[prop] instanceof RegExp) ||
+      } else if ((mergeFrom[prop] instanceof RegExp) ||
           (mergeFrom[prop] instanceof RawConfig)) {
🧹 Nitpick comments (1)
lib/util.js (1)

16-26: Minor JSDoc formatting for @callback parameter.

The @param on line 24 uses informal syntax. Consider proper JSDoc:

- * `@param` context { util: Util, defer: deferConfig, raw } - utility functions for startup
+ * `@param` {Object} context - utility functions for startup
+ * `@param` {typeof Util} context.util - Util class
+ * `@param` {typeof deferConfig} context.defer - deferred config factory
+ * `@param` {typeof RawConfig.raw} context.raw - raw config factory

@jdmarshall jdmarshall merged commit 4354ee2 into node-config:master Feb 17, 2026
2 checks passed
@jdmarshall jdmarshall deleted the rawESM branch February 26, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant