Skip to content

feat: add deepMerge utility and PlainObject type#129

Merged
teneplaysofficial merged 5 commits into
mainfrom
dev
Mar 7, 2026
Merged

feat: add deepMerge utility and PlainObject type#129
teneplaysofficial merged 5 commits into
mainfrom
dev

Conversation

@teneplaysofficial

@teneplaysofficial teneplaysofficial commented Mar 7, 2026

Copy link
Copy Markdown
Owner
  • introduce deepMerge for immutable recursive object merging
  • support array strategies: replace, concat, merge
  • deprecate mergeObj in favor of deepMerge
  • add PlainObject type in @js-utils-kit/types
  • update tsconfig

Summary by CodeRabbit

  • New Features

    • Added a deep merge utility with configurable array strategies (replace, concat, merge)
    • Introduced a PlainObject type and exposed the deep merge API publicly
  • Deprecations

    • Existing merge utility marked deprecated in favor of the new deep merge
  • Tests

    • Added comprehensive tests covering merge behavior, arrays, nesting and immutability
  • Chores

    • Updated TypeScript config references and build docs script

- introduce `deepMerge` for immutable recursive object merging
- support array strategies: `replace`, `concat`, `merge`
- deprecate `mergeObj` in favor of `deepMerge`
- add `PlainObject` type in `@js-utils-kit/types`
- update tsconfig config filename and extends paths
- remove compilerOptions from `packages/@js-utils-kit` tsconfig files
- adjust affected file paths after tsconfig changes
@changeset-bot

changeset-bot Bot commented Mar 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8556835

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@js-utils-kit/types Minor
@js-utils-kit/object Minor
@js-utils-kit/array Patch
@js-utils-kit/constants Patch
@js-utils-kit/core Patch
@js-utils-kit/env Patch
@js-utils-kit/fs Patch
@js-utils-kit/number Patch
@js-utils-kit/pm Patch
@js-utils-kit/string Patch
js-utils-kit Patch
juk-cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Mar 7, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new deepMerge utility and DeepMergeOptions to the object package, introduces PlainObject type in types, re-exports/deprecates mergeObj, adds tests, updates package deps, and standardizes many package tsconfig extends to the root tsconfig.json.

Changes

Cohort / File(s) Summary
New Type Definition
packages/@js-utils-kit/types/src/PlainObject.ts, packages/@js-utils-kit/types/src/index.ts
Add and export PlainObject type alias (Record<PropertyKey, unknown>).
Deep Merge Implementation & API
packages/@js-utils-kit/object/src/deepMerge.ts, packages/@js-utils-kit/object/src/index.ts, exports.json, .changeset/solid-masks-spend.md
Add deepMerge and DeepMergeOptions; expose from object index; update export metadata and changeset.
Deprecation
packages/@js-utils-kit/object/src/mergeObj.ts, .changeset/polite-spies-happen.md
Mark mergeObj as deprecated (JSDoc) in favor of deepMerge; add changeset for types.
Tests
packages/@js-utils-kit/object/test/deepMerge.test.ts
Add comprehensive tests for merging behavior, array strategies, immutability, and edge cases.
Package Dependencies
packages/@js-utils-kit/object/package.json
Add workspace dependencies: @js-utils-kit/types, @js-utils-kit/valid.
TSConfig Migration
packages/*/tsconfig.json, packages/@js-utils-kit/*/tsconfig.json, packages/@js-utils-kit/object/tsconfig.json, packages/@js-utils-kit/types/tsconfig.json, packages/js-utils-kit/tsconfig.json, packages/juk-cli/tsconfig.json, scripts/new-project.js
Change many packages to extend root tsconfig.json instead of tsconfig.base.json; remove local compilerOptions like composite and rootDir.
Scripts & tooling
package.json, scripts/exports.js, scripts/new-project.js, CONTRIBUTING.md, eslint.config.ts
Update build:docs script to typedoc; switch tsconfig references used by scripts and templates from tsconfig.base.json to tsconfig.json.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through objects, nibbling keys with care,
I stitched arrays—concat, merge, or just replace—so fair,
A PlainObject now takes a bow in light,
mergeObj tiptoes back; deepMerge basks bright,
I munch a carrot and twitch my whiskers in delight. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the two main additions: the deepMerge utility and PlainObject type, which are the primary feature additions in this changeset.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances object manipulation capabilities by introducing a new, highly configurable deepMerge utility, which replaces the older mergeObj function. It also standardizes the project's TypeScript configuration by renaming the base tsconfig file and streamlining package-level configurations. Additionally, a fundamental PlainObject type has been added to improve type safety and clarity across the codebase.

Highlights

  • New deepMerge Utility: Introduced a new deepMerge utility for immutable recursive object merging, offering configurable array strategies (replace, concat, merge).
  • Deprecation of mergeObj: The existing mergeObj utility has been deprecated in favor of the more robust deepMerge function.
  • New PlainObject Type: Added a PlainObject type to @js-utils-kit/types to represent generic JavaScript objects with arbitrary property keys.
  • TypeScript Configuration Refactor: The base TypeScript configuration file was renamed from tsconfig.base.json to tsconfig.json, and all package-specific tsconfig.json files were updated to extend the new path and remove redundant compilerOptions.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • exports.json
    • Updated export summary to include deepMerge function and DeepMergeOptions type.
    • Added PlainObject to type exports.
    • Marked mergeObj as a deprecated export.
    • Adjusted export counts to reflect additions and deprecations.
  • package.json
    • Updated the build:docs script to remove explicit tsconfig.base.json reference.
    • Added @js-utils-kit/types and @js-utils-kit/valid as dependencies for @js-utils-kit/object.
  • packages/@js-utils-kit/array/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/charset/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/constants/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/core/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/env/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/fs/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/number/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/object/package.json
    • Added @js-utils-kit/types and @js-utils-kit/valid as dependencies.
  • packages/@js-utils-kit/object/src/deepMerge.ts
    • Added the new deepMerge utility function and its DeepMergeOptions interface.
  • packages/@js-utils-kit/object/src/index.ts
    • Exported the new deepMerge utility.
  • packages/@js-utils-kit/object/src/mergeObj.ts
    • Marked the mergeObj function as deprecated.
  • packages/@js-utils-kit/object/test/deepMerge.test.ts
    • Added comprehensive test cases for the deepMerge utility.
  • packages/@js-utils-kit/object/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/pm/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/string/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/system/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/types/src/PlainObject.ts
    • Added the PlainObject type definition.
  • packages/@js-utils-kit/types/src/index.ts
    • Exported the new PlainObject type.
  • packages/@js-utils-kit/types/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/@js-utils-kit/valid/tsconfig.json
    • Updated the extends path to ../../../tsconfig.json and removed compilerOptions.
  • packages/js-utils-kit/tsconfig.json
    • Updated the extends path to ../../tsconfig.json.
  • packages/juk-cli/tsconfig.json
    • Updated the extends path to ../../tsconfig.json.
  • scripts/exports.js
    • Updated the tsConfigFilePath to reference tsconfig.json.
  • scripts/new-project.js
    • Updated the new project tsconfig.json template to extend ../../../tsconfig.json and removed compilerOptions.
  • tsconfig.base.json
    • Renamed to tsconfig.json.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new deepMerge utility and a PlainObject type, deprecates mergeObj, and refactors TypeScript configuration files for better maintainability. However, the deepMerge function is vulnerable to prototype manipulation due to a lack of sanitization for sensitive keys like __proto__, which could lead to Denial of Service or unexpected behavior. Additionally, the deepMerge function does not correctly handle properties with symbol keys, contradicting the PlainObject type definition, and there are minor code redundancies that could be improved for clarity.

Comment thread packages/@js-utils-kit/object/src/deepMerge.ts
Comment thread packages/@js-utils-kit/object/src/deepMerge.ts Outdated
Comment thread packages/@js-utils-kit/object/src/deepMerge.ts Outdated
@codecov

codecov Bot commented Mar 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@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: 4

Caution

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

⚠️ Outside diff range comments (2)
scripts/new-project.js (2)

64-70: ⚠️ Potential issue | 🟡 Minor

value.trim() doesn't reassign the result.

String.trim() returns a new string; it doesn't mutate value. A whitespace-only input like " " will pass the !value check but become empty after trimming on line 94.

Proposed fix
     validate: (value) => {
-      value.trim();
+      value = value.trim();
 
       if (!value) return 'Library name cannot be empty';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/new-project.js` around lines 64 - 70, The validate function in
scripts/new-project.js is calling value.trim() without using its result, so
whitespace-only names pass validation; change the validator to trim the input
and use the trimmed string for subsequent checks and existence tests (e.g.,
assign trimmed = value.trim() or reassign value = value.trim()), then check if
trimmed is empty and call existsSync(join('packages', trimmed)) when validating
uniqueness.

79-83: ⚠️ Potential issue | 🟡 Minor

Same issue: value.trim() result is discarded.

Proposed fix
     validate: (value) => {
-      value.trim();
+      value = value.trim();
 
       if (!value) return 'Library description cannot be empty';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/new-project.js` around lines 79 - 83, The validate handler in
scripts/new-project.js discards the result of value.trim(), so
empty-or-whitespace input passes validation; update the validate function (the
validate: (value) => { ... } block) to assign or use the trimmed string (e.g.,
set value = value.trim() or use const trimmed = value.trim()) and then check
that trimmed for emptiness, returning 'Library description cannot be empty' when
empty and true when valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/`@js-utils-kit/object/src/deepMerge.ts:
- Around line 22-27: The current isDeepMergeOptions detection treats any plain
object whose keys are a subset of OPTION_KEYS as options, which mistakenly
consumes valid data objects (e.g., { arrayStrategy: 'concat' }); change
detection to require an explicit marker so only intentionally-passed options are
treated as options: add a unique symbol constant (e.g.,
DEEP_MERGE_OPTIONS_SYMBOL) and update isDeepMergeOptions to return true only if
the value is an object and (value has the symbol marker OR its keys are
OPTION_KEYS but also include the marker), then export a small helper (e.g.,
createDeepMergeOptions) that attaches that symbol to options objects; update any
call sites that relied on implicit detection to use the helper or pass options
via the marker so real data objects are no longer misinterpreted (references:
isDeepMergeOptions, OPTION_KEYS, DeepMergeOptions, deepMerge).
- Around line 115-149: The merge currently aliases caller-owned arrays when
incoming is an array but existing is not (the fall-through path that does
result[key] = incoming); update deepMerge so any time incoming is an array you
assign a shallow-cloned array instead of the original (e.g., result[key] =
[...(incoming as unknown[])]) so mutations to the merged result don't affect the
source; keep the existing arrayStrategy logic for when both are arrays and
ensure the object-merge branch still clones arrays when delegating (use the same
spread clone when creating result entries from incoming arrays).
- Around line 105-149: The deepMerge implementation can be exploited via
prototype pollution because it iterates Object.keys(param) and assigns into the
plain result object; update deepMerge to guard against dangerous property names
by (a) creating the accumulator with no prototype (use Object.create(null) for
result) and (b) filtering out keys ['__proto__','constructor','prototype']
before processing each key in the loop (references: deepMerge function, result
variable, params loop and the inner for (const key of Object.keys(param))
block). Ensure both the root result and recursive merges use the same safeguards
so incoming objects cannot mutate prototypes.

In `@packages/`@js-utils-kit/types/src/PlainObject.ts:
- Around line 1-2: deepMerge currently uses Object.keys and drops symbol-keyed
properties even though PlainObject uses PropertyKey (which includes symbols);
update the deepMerge implementation (the deepMerge function) to enumerate source
object keys with Reflect.ownKeys(source) instead of Object.keys(source), iterate
that keys array and handle symbol and string keys the same way you handle string
keys (merge or assign values, recurse for nested objects), ensuring you use the
same type guards/assign logic already present so symbol-keyed properties are
preserved during the merge.

---

Outside diff comments:
In `@scripts/new-project.js`:
- Around line 64-70: The validate function in scripts/new-project.js is calling
value.trim() without using its result, so whitespace-only names pass validation;
change the validator to trim the input and use the trimmed string for subsequent
checks and existence tests (e.g., assign trimmed = value.trim() or reassign
value = value.trim()), then check if trimmed is empty and call
existsSync(join('packages', trimmed)) when validating uniqueness.
- Around line 79-83: The validate handler in scripts/new-project.js discards the
result of value.trim(), so empty-or-whitespace input passes validation; update
the validate function (the validate: (value) => { ... } block) to assign or use
the trimmed string (e.g., set value = value.trim() or use const trimmed =
value.trim()) and then check that trimmed for emptiness, returning 'Library
description cannot be empty' when empty and true when valid.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5aa8fd8-6c80-42e3-90b6-51a70361c51c

📥 Commits

Reviewing files that changed from the base of the PR and between 05f86ff and 7432ec5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (29)
  • .changeset/polite-spies-happen.md
  • .changeset/solid-masks-spend.md
  • exports.json
  • package.json
  • packages/@js-utils-kit/array/tsconfig.json
  • packages/@js-utils-kit/charset/tsconfig.json
  • packages/@js-utils-kit/constants/tsconfig.json
  • packages/@js-utils-kit/core/tsconfig.json
  • packages/@js-utils-kit/env/tsconfig.json
  • packages/@js-utils-kit/fs/tsconfig.json
  • packages/@js-utils-kit/number/tsconfig.json
  • packages/@js-utils-kit/object/package.json
  • packages/@js-utils-kit/object/src/deepMerge.ts
  • packages/@js-utils-kit/object/src/index.ts
  • packages/@js-utils-kit/object/src/mergeObj.ts
  • packages/@js-utils-kit/object/test/deepMerge.test.ts
  • packages/@js-utils-kit/object/tsconfig.json
  • packages/@js-utils-kit/pm/tsconfig.json
  • packages/@js-utils-kit/string/tsconfig.json
  • packages/@js-utils-kit/system/tsconfig.json
  • packages/@js-utils-kit/types/src/PlainObject.ts
  • packages/@js-utils-kit/types/src/index.ts
  • packages/@js-utils-kit/types/tsconfig.json
  • packages/@js-utils-kit/valid/tsconfig.json
  • packages/js-utils-kit/tsconfig.json
  • packages/juk-cli/tsconfig.json
  • scripts/exports.js
  • scripts/new-project.js
  • tsconfig.json

Comment thread packages/@js-utils-kit/object/src/deepMerge.ts
Comment thread packages/@js-utils-kit/object/src/deepMerge.ts
Comment thread packages/@js-utils-kit/object/src/deepMerge.ts
Comment thread packages/@js-utils-kit/types/src/PlainObject.ts

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
exports.json (1)

181-192: ⚠️ Potential issue | 🟠 Major

DeepMergeOptions is being published with the wrong public contract.

Line 181 and Line 1000 expose DeepMergeOptions, but packages/@js-utils-kit/object/src/deepMerge.ts, Lines 7-13 currently only allow arrayStrategy?: 'replace' | 'append'. The PR objective says this release should support replace, concat, and merge, so the exported type is already out of sync with the API you're shipping. Consumers will be unable to type valid options from the announced surface.

Also applies to: 1000-1005

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@exports.json` around lines 181 - 192, The exported type DeepMergeOptions is
out of sync with the implementation in
packages/@js-utils-kit/object/src/deepMerge.ts — update the DeepMergeOptions
definition to allow arrayStrategy values 'replace' | 'append' | 'concat' |
'merge' (or otherwise align the exported contract with the actual supported
strategies) so consumers can type the new options; locate the DeepMergeOptions
type in deepMerge.ts and adjust its union to include 'concat' and 'merge' (and
ensure the exports.json entries remain consistent).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@exports.json`:
- Around line 992-1013: The deepMerge implementation currently mutates its first
argument and references an undefined variable in the array branch, so don't
deprecate mergeObj yet; update deepMerge (in
packages/@js-utils-kit/object/src/deepMerge.ts, function deepMerge) to create a
fresh result (e.g., clone or shallow-copy params[0] into result) before merging
so no input is mutated, fix the array-branch variable by using the correct
incoming value rather than the undefined `incoming`, and ensure recursive
merging returns new objects/arrays (preserving immutability); once deepMerge
matches mergeObj's behavior and has unit tests covering object/array recursion
and non-mutation, then deprecate mergeObj.

---

Outside diff comments:
In `@exports.json`:
- Around line 181-192: The exported type DeepMergeOptions is out of sync with
the implementation in packages/@js-utils-kit/object/src/deepMerge.ts — update
the DeepMergeOptions definition to allow arrayStrategy values 'replace' |
'append' | 'concat' | 'merge' (or otherwise align the exported contract with the
actual supported strategies) so consumers can type the new options; locate the
DeepMergeOptions type in deepMerge.ts and adjust its union to include 'concat'
and 'merge' (and ensure the exports.json entries remain consistent).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7d9ae2b-514e-4028-ad58-e145c334bbcc

📥 Commits

Reviewing files that changed from the base of the PR and between 7432ec5 and 2033b0c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • exports.json
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Comment thread exports.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.

♻️ Duplicate comments (2)
packages/@js-utils-kit/object/src/deepMerge.ts (2)

117-150: ⚠️ Potential issue | 🟠 Major

Clone incoming arrays before the object branch.

Arrays are only copied when both sides are already arrays. When the incoming value is the first array seen for a key, this path skips the clone, so the merged result can still share caller-owned array state. Add an isArray(incoming) branch before the object branch.

Suggested patch
       if (isArray(existing) && isArray(incoming)) {
         switch (arrayStrategy) {
           case 'concat':
             result[key] = [...(existing as unknown[]), ...(incoming as unknown[])];
             break;
@@
 
         continue;
       }
 
+      if (isArray(incoming)) {
+        result[key] = [...(incoming as unknown[])];
+        continue;
+      }
+
       // Object
       if (isObject(incoming)) {
         result[key] =
           isObject(existing) && !Array.isArray(existing)
             ? deepMerge(existing as PlainObject, incoming as PlainObject, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@js-utils-kit/object/src/deepMerge.ts around lines 117 - 150, The
merge currently only clones arrays when both existing and incoming are arrays,
so when incoming is an array and existing is not the merged result can retain
caller-owned array state; before the "Object" branch in deepMerge (inside
packages/@js-utils-kit/object/src/deepMerge.ts) add an isArray(incoming) branch
that handles cases where existing is not an array by cloning incoming
(respecting arrayStrategy: 'concat'/'merge'/'replace' semantics or at minimum
copying incoming into result[key] as a new array) so result[key] never
references the original incoming array; update logic around existing, incoming,
result, arrayStrategy, key and reuse the same switch/handling used for the
"Array" branch or a simplified clone when appropriate.

22-28: ⚠️ Potential issue | 🔴 Critical

Disambiguate options from merge data.

Any trailing plain object whose keys are limited to arrayStrategy is still treated as config, so valid payloads like deepMerge({}, { arrayStrategy: 'concat' }) get dropped instead of merged. This makes part of the input space unrepresentable. Please switch to an explicit options marker/helper or a separate options parameter instead of key-based inference.

Also applies to: 101-103

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@js-utils-kit/object/src/deepMerge.ts around lines 22 - 28, The
current isDeepMergeOptions implementation (and related checks around
OPTION_KEYS) wrongly treats any plain object whose keys match OPTION_KEYS as the
options object, causing valid payloads like deepMerge({}, { arrayStrategy:
'concat' }) to be treated as config and dropped; change the API to use an
explicit options marker or a separate options parameter: either add a unique
property/symbol (e.g. __isDeepMergeOptions or a Symbol) and update
isDeepMergeOptions to detect that marker (instead of relying on OPTION_KEYS), or
refactor the public deepMerge signature to accept options as a separate last
parameter and remove key-based inference; update all references to
isDeepMergeOptions, DeepMergeOptions, and OPTION_KEYS accordingly so callers and
internal logic unambiguously distinguish options from data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/`@js-utils-kit/object/src/deepMerge.ts:
- Around line 117-150: The merge currently only clones arrays when both existing
and incoming are arrays, so when incoming is an array and existing is not the
merged result can retain caller-owned array state; before the "Object" branch in
deepMerge (inside packages/@js-utils-kit/object/src/deepMerge.ts) add an
isArray(incoming) branch that handles cases where existing is not an array by
cloning incoming (respecting arrayStrategy: 'concat'/'merge'/'replace' semantics
or at minimum copying incoming into result[key] as a new array) so result[key]
never references the original incoming array; update logic around existing,
incoming, result, arrayStrategy, key and reuse the same switch/handling used for
the "Array" branch or a simplified clone when appropriate.
- Around line 22-28: The current isDeepMergeOptions implementation (and related
checks around OPTION_KEYS) wrongly treats any plain object whose keys match
OPTION_KEYS as the options object, causing valid payloads like deepMerge({}, {
arrayStrategy: 'concat' }) to be treated as config and dropped; change the API
to use an explicit options marker or a separate options parameter: either add a
unique property/symbol (e.g. __isDeepMergeOptions or a Symbol) and update
isDeepMergeOptions to detect that marker (instead of relying on OPTION_KEYS), or
refactor the public deepMerge signature to accept options as a separate last
parameter and remove key-based inference; update all references to
isDeepMergeOptions, DeepMergeOptions, and OPTION_KEYS accordingly so callers and
internal logic unambiguously distinguish options from data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6557a07c-15ff-4e1f-8f16-a528fa8e6d11

📥 Commits

Reviewing files that changed from the base of the PR and between 2033b0c and 8556835.

📒 Files selected for processing (3)
  • CONTRIBUTING.md
  • eslint.config.ts
  • packages/@js-utils-kit/object/src/deepMerge.ts
✅ Files skipped from review due to trivial changes (1)
  • CONTRIBUTING.md

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