LearnNewsExamplesServices
Frontmatter
id6937
titleImplement `mergeStrategy` for Reactive Configs
stateClosed
labels
enhancement
assigneestobiu
createdAtJul 4, 2025, 2:14 PM
updatedAtOct 23, 2025, 12:56 AM
githubUrlhttps://github.com/neomjs/neo/issues/6937
authortobiu
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJul 4, 2025, 6:57 PM

Implement mergeStrategy for Reactive Configs

Closed v10.0.0-beta.5 enhancement
tobiu
tobiu commented on Jul 4, 2025, 2:14 PM

Summary

The reactive config system (_ suffix) provides powerful reactivity, but it currently lacks a declarative mechanism for merging default config values with instance-specific values at creation time. This feature proposes implementing the mergeStrategy property on the core.Config class to allow for intelligent merging of configs (e.g., objects or arrays) when an instance is created, instead of simple replacement.

The Problem

Currently, if a class defines a default value for a config (e.g., a style object), and an instance is created with a new style object, the default is completely replaced. The developer must manually read the default, clone it, and merge it with their new values, which is verbose and inefficient.

Example (Current Behavior):

// in MyComponent.mjs
static config = {
    // Default style
    style_: { color: 'blue', fontWeight: 'bold' }
}

// On creation
const instance = Neo.create(MyComponent, {
    // This object REPLACES the default, it does not merge with it.
    style: { fontSize: '14px' }
});

// Result: instance.style is { fontSize: '14px' }
// Desired: { color: 'blue', fontWeight: 'bold', fontSize: '14px' }

Proposed Solution

The solution is to activate the mergeStrategy property that already exists as a placeholder in Neo.core.Config. This strategy will be applied only once during the instance's construction phase. Subsequent set() calls will continue to replace the value, preserving predictable runtime behavior.

This will be implemented in two main steps:

  1. Neo.setupClass() Enhancement:

    • The setupClass method in src/Neo.mjs will be modified to recognize config descriptors.
    • When a config is defined using { [isDescriptor]: true, ... }, setupClass will:
      • Extract the entire descriptor object and store it in a new, merged static property on the constructor, e.g., MyClass.configDescriptors.
      • Extract the value property from the descriptor and place it in the standard MyClass.config object as the default value.
  2. core.Base#mergeConfig() Implementation:

    • The mergeConfig method in src/core/Base.mjs is the ideal location to apply the one-time merge.
    • During instance construction, this method will iterate over the incoming configs.
    • For each config, it will check this.constructor.configDescriptors for a mergeStrategy (e.g., 'shallow' or 'deep').
    • If a strategy other than the default ('replace') is found, it will merge the static default value from this.constructor.config with the instance-specific value from the creation config object.
    • This merged value will then be assigned to the instance.

Example (Proposed Behavior)

With this change, developers can declaratively control the merge behavior.

import {isDescriptor} from '../core/ConfigSymbols.mjs';

// in MyComponent.mjs
static config = {
    style_: {
        [isDescriptor]: true,
        value: { color: 'blue', fontWeight: 'bold' },
        merge: 'shallow' // or 'deep'
    }
}

// On creation, the merge is applied automatically
const instance = Neo.create(MyComponent, {
    style: { fontSize: '14px', fontWeight: 'normal' }
});
// Result: instance.style is { color: 'blue', fontWeight: 'normal', fontSize: '14px' }

// Subsequent set() calls will REPLACE the value as expected
instance.style = { backgroundColor: 'red' };
// Result: instance.style is { backgroundColor: 'red' }

This provides a more intuitive, powerful, and declarative way to handle complex configurations at creation time without adding runtime overhead.

tobiu assigned to @tobiu on Jul 4, 2025, 2:14 PM
tobiu added the enhancement label on Jul 4, 2025, 2:14 PM
tobiu referenced in commit 529e8d4 - "#6937 Neo.setupClass() => storing config descriptors" on Jul 4, 2025, 3:58 PM
tobiu referenced in commit c31ceb6 - "#6937 Neo.mergeConfig() => rudimentary base-line implementation" on Jul 4, 2025, 4:28 PM
tobiu referenced in commit 277a71a - "#6937 core.Base: mergeConfig()" on Jul 4, 2025, 4:32 PM
tobiu referenced in commit 8c2c232 - "#6937 component.Base: beforeSetAlign() => removed the manual merge => now handled by the Config atom strategy." on Jul 4, 2025, 4:50 PM
tobiu referenced in commit e872f80 - "#6937 core.Config: initDescriptor() => shortening the logic" on Jul 4, 2025, 5:08 PM
tobiu referenced in commit 7643e2e - "#6937 Neo.mergeConfig() => using deep as the default" on Jul 4, 2025, 5:17 PM
tobiu referenced in commit adf70e8 - "#6937 core.Config: default merge strategy => replace, JSDoc comments polishing" on Jul 4, 2025, 5:28 PM
tobiu referenced in commit 2eb32fc - "#6937 Neo.mergeConfig() => back to replace as the default" on Jul 4, 2025, 5:28 PM
tobiu referenced in commit 5059bc8 - "#6937 component.Base: descriptors for align_, style_, wrapperStyle_" on Jul 4, 2025, 5:43 PM
tobiu referenced in commit 45f2bdf - "#6937 Neo.merge() => null or undefined check" on Jul 4, 2025, 6:14 PM
tobiu referenced in commit f496197 - "#6937 component.Base: mergeConfig() => further polishing" on Jul 4, 2025, 6:25 PM
tobiu referenced in commit 8d94ab9 - "#6937 component.Base: style, wrapperStyle => merge shallow" on Jul 4, 2025, 6:33 PM
tobiu closed this issue on Jul 4, 2025, 6:57 PM