Home Doh Ref
Dohballs
  • 📁 doh_modules
    • 📦 dataforge
    • 📦 express
    • 📁 sso
    • 📁 user

Melded Object Composition (MOC)

MOC

Melded Object Composition (MOC) is Doh's system for controlling exactly how properties and methods combine when creating object instances from potentially multiple inherited Patterns. Think of it like defining custom recipes for merging ingredients: when you inherit multiple Patterns (ingredients), MOC specifies how their properties (e.g., arrays, objects, functions) should be intelligently blended together in the final object.

This makes object composition highly flexible and predictable, enabling complex components to be built reliably from smaller, reusable parts.

This guide covers:

  • Understanding MOC definitions and how they control property behavior
  • Working with different types of property melding (Array, Object, Deep)
  • Managing method melding for different execution patterns
  • Using type validation and exclusion rules
  • Combining multiple MOC behaviors for complex properties

MOC Definitions Meld: Remember that moc blocks from all inherited patterns are melded together using the standard Resolution Order. You only need to define moc entries in a derived pattern if you are introducing new properties/methods that require specific melding or are defining new lifecycle phases. Core phases like object_phase and builder_phase are typically defined in the base object pattern and don't need redeclaration.

Important: Default behavior is Override-first. Unless a property declares a MOC melder (e.g., Array, Object, Method, {}), property values are replaced by later applications rather than melded. Method melding is opt-in. Use the Override melder to explicitly force replacement.

Core Concepts

MOC Definitions

At its core, MOC is defined through the moc property within a Pattern's idea (or an idea passed to New()). This property holds an object where keys are property names and values specify the MOC behaviors (the "recipe") for that property:

Pattern('ExamplePattern', {
    moc: {
        arrayProp: 'Array',           // Array melding with type validation
        deepProp: {},                 // Deep melding with nested control
        methodProp: 'Method',         // Method melding with context binding
        overrideProp: 'Override',     // Explicitly override this property
        stringProp: 'IsString',       // Type validation only
        notNullProp: 'NotNull',       // Null/undefined exclusion
        multiProp: ['Array', 'NotNull'] // Multiple MOC behaviors
    },
    
    // Properties controlled by the above MOC definitions
    arrayProp: [1, 2, 3],
    deepProp: { nested: { value: true } },
    methodProp: function() { /* ... */ },
    overrideProp: function() { /* This will be replaced */},
    stringProp: 'example',
    notNullProp: 'must have a value',
    multiProp: [4, 5, 6]
});

MOC Tracks

MOC operations are organized into logical "tracks" or categories, allowing different aspects of property behavior to be controlled independently. This means you can combine a merging strategy (like Array) with validation (like NotNull) without conflict.

The main tracks are:

  1. melder: Controls how property values combine during inheritance (e.g., Array, Object, Concat, {}).
  2. typeof: Manages type validation (e.g., IsString, IsArrayOrFalse). Only one typeof rule applies (the first one encountered in the inheritance chain).
  3. not*: Handles type exclusion (e.g., NotNull, NotUndefined, NotString). Multiple not rules are cumulative.
  4. async_method: Specifically controls asynchronous behavior for method melding types (used implicitly by Async_* melders).

Each track operates mostly independently when resolving the final behavior for a property.

Type System

MOC integrates with Doh's type system to provide validation and type-checking capabilities.

Type Validation

Type validation ensures properties conform to expected types:

Pattern('TypeValidationExample', {
  moc: {
    types: [
      // Basic Types
      'IsAny',                   // Any value (never fails)
      'IsAnything',              // Alias for IsAny
      'IsDefined',               // Not undefined and not null
      'IsSet',                   // Alias for IsDefined (changing soon to be for Set's, instead)
      'HasValue',                // Not undefined, null, or "" (if string)
      'IsLiteral',               // Static values (not objects/arrays)
      'IsBoolean',               // true or false
      'IsString',                // String type
      'IsNumber',                // Number (excluding NaN)
      'IsInt',                   // Integer numbers only
      'IsArray',                 // Array type
      'IsObjectObject',          // Plain object (not array/literal)
      'IsObject',                // Alias for IsObjectObject
      'IsIterable',              // Has Symbol.iterator
      'IsFunction',              // Function type
      'IsPromise',               // Promise instance
      'IsDohObject',             // Doh object instance

      // Or False Variants
      'IsStringOrFalse',         // String or false
      'IsNumberOrFalse',         // Number or false
      'IsIntOrFalse',            // Integer or false
      'IsLiteralOrFalse',        // Static value or false
      'IsArrayOrFalse',          // Array or false
      'IsObjectOrFalse',         // Object or false
      'IsFunctionOrFalse',       // Function or false
      'IsPromiseOrFalse',        // Promise or false
      'IsDohObjectOrFalse',      // Doh object or false
      'IsPromiseOrFunctionOrFalse', // Promise, function, or false

      // Composite Types
      'IsStringOrArray',         // String or array
      'IsStringOrNumber',        // String or number
      'IsArrayOrObjectObject',   // Array or plain object
      'IsStringOrArrayOrObjectObject', // String, array, or plain object

      // Composite Or False Variants
      'IsStringOrArrayOrFalse',  // String, array, or false
      'IsArrayOrObjectObjectOrFalse', // Array, plain object, or false
      'IsStringOrArrayOrObjectObjectOrFalse', // String, array, plain object, or false

      // Special Validation 
      'IsKeySafe',               // Safe to use as object key

      // Different from HasValue in that it requires a String value
      'IsStringAndHasValue',     // String that is not empty

      // Different from IsNumber in that it can be a string of digits,
      // or decimal number with a decimal point, or a signed number
      // excludes NaN, Infinity, and non-numeric strings
      'IsOnlyNumbers',           // Any number type, or String containing only numeric characters

      // Object State
      'IsObjectObjectAndEmpty',   // Empty plain object
      'IsObjectObjectAndNotEmpty', // Non-empty plain object
      'IsObjectObjectAndNotEmptyOrFalse', // Non-empty plain object or false
      'IsArrayAndEmpty',         // Empty array
      'IsArrayAndNotEmpty'       // Non-empty array
    ]
  }
});

Most type validation operators ignore null and undefined values by default, allowing properties to be optional. Use NotNull or NotUndefined to require a value.

Type Exclusion

Type exclusion operators prevent properties from having certain types:

Pattern('TypeExclusionExample', {
  moc: {
    value: [
      'NotUndefined',   // Cannot be undefined
      'NotNull',        // Cannot be null
      'NotString',      // Cannot be string
      'NotNumber',      // Cannot be number
      'NotBoolean',     // Cannot be boolean
      'NotArray',       // Cannot be array
      'NotObject',      // Cannot be object
      'NotFunction',    // Cannot be function
      'NotPromise',     // Cannot be Promise
      'NotDohObject'    // Cannot be Doh object
    ]
  }
});

Unlike validation, multiple exclusions can be combined to create sophisticated type constraints.

Melding Types

MOC provides various melding strategies for combining properties during inheritance.

Override (Override)

The Override melder explicitly prevents any melding behavior and enforces standard property replacement. This reflects Doh's default semantics: when a property has no MOC melder, it is replaced rather than melded. Override is provided to disambiguate intent and to force replacement.

This is useful for making your code's intent clear, especially with methods. It ensures that a method is replaced in the traditional object-oriented sense, rather than being combined with the base method.

Pattern('BaseWithMelding', {
    moc: { 
        doSomething: 'Method' // This method is designed to be melded
    },
    doSomething: function() {
        console.log('Base action');
    }
});

Pattern('ChildWithOverride', 'BaseWithMelding', {
    moc: {
        doSomething: 'Override' // Explicitly state we are replacing, not melding
    },
    doSomething: function() {
        console.log('Child action completely replaces base action');
    }
});

const obj = New('ChildWithOverride');
obj.doSomething(); 
// Console output:
// "Child action completely replaces base action"

Array (Array)

Combines arrays with duplicate removal:

Pattern('ArrayMeldingExample', {
    moc: { 
        items: 'Array'  // Automatically includes IsArray validation
    },
    items: [1, 2]
});

const obj = New('ArrayMeldingExample', {
    items: [2, 3]
});
console.log(obj.items); // [1, 2, 3]

Concatenation (Concat)

Combines strings or arrays without removing duplicates:

Pattern('ConcatExample', {
    moc: {
        text: 'Concat',
        list: 'Concat'
    },
    text: 'Hello ',
    list: [1, 2]
});

const obj = New('ConcatExample', {
    text: 'World',
    list: [2, 3]
});
console.log(obj.text);  // "Hello World"
console.log(obj.list);  // [1, 2, 2, 3]

Object (Object)

Performs shallow merging of objects using Object.assign:

Pattern('ObjectMeldingExample', {
    moc: {
        config: 'Object'
    },
    config: {
        a: 1,
        b: { x: 1 }
    }
});

const obj = New('ObjectMeldingExample', {
    config: {
        b: { y: 2 },
        c: 3
    }
});
console.log(obj.config);  // { a: 1, b: { y: 2 }, c: 3 }

Deep Object ({})

Recursively merges objects with nested MOC control:

Pattern('DeepMeldingExample', {
    moc: {
        settings: {  // Empty object = deep melding
            theme: 'IsString',
            counts: 'Array',
            nested: {  // Nested MOC definitions
                enabled: 'IsBoolean'
            }
        }
    },
    settings: {
        theme: 'light',
        counts: [1],
        nested: {
            enabled: true
        }
    }
});

const obj = New('DeepMeldingExample', {
    settings: {
        counts: [2],
        nested: {
            enabled: false
        }
    }
});
console.log(obj.settings);
// {
//   theme: 'light',
//   counts: [1, 2],
//   nested: { enabled: false }
// }

Method Melding Types

All method melding types (Method, Chain, Blender, etc.) are opt-in and are designed to combine functions from the inheritance chain into a single executable method. By default (no melder), methods are replaced. If you need classic override semantics in the presence of inherited method melders, use the 'Override' melder to force replacement and avoid melding.

MOC provides sophisticated control over how methods are combined during inheritance.

Common Method Melding Features

All method melding types share these core behaviors:

  • Preserve this binding in all contexts (callbacks, events, timers)
  • Support pre/post hooks via pre_methodName and post_methodName
  • Allow method stacking from multiple patterns

Method (Method)

The standard method melding type. Executes methods in Pattern Resolution Order (base to derived) and intelligently merges truthy return values onto this object.

Pattern('MethodExample', {
    moc: { 
        handler: 'Method'
    },
    pre_handler: function() {
        console.log('Pre-processing');
    },
    handler: function(arg1, arg2) {
        console.log('Base handling', arg1, arg2);
        return { newProp: 'value' };  // Merged onto this
    },
    post_handler: function() {
        console.log('Post-processing');
    }
});

Pattern('ExtendedMethod', 'MethodExample', {
    handler: function(arg1, arg2) {
        console.log('Extended handling', arg1, arg2);
        return { anotherProp: 'value2' };  // Also merged onto this
    }
});

Key behaviors:

  • Executes in Pattern Resolution Order (base to derived)
  • Merges truthy return values onto this
  • Return false to short-circuit with false
  • Return true to short-circuit with this
  • Arguments passed through to all methods
  • Pre-hooks run first, then methods, then post-hooks

Phase (Phase)

Designates a method as a lifecycle phase. Methods of this type are executed in a specific order determined by __.phase_order() within the object.machine() function during the synchronous New() execution. They automatically support pre_ and post_ hooks (e.g., pre_myPhase, post_myPhase) which are executed immediately before and after the main phase method during the machine execution, without needing explicit MOC declarations for the hooks themselves.

IMPORTANT: Phases and their pre_/post_ hooks can be asynchronous (async) only when using AsyncNew(). When using regular New(), phases must remain synchronous as they are part of the core synchronous object construction process. Using async phase handlers with New() will generate warnings.

If you need asynchronous initialization:

  • Option 1 (Recommended): Use AsyncNew() with async phase handlers
  • Option 2: Use a standard Method or Async_Method and call it after the synchronous phases complete
  • Regular methods still benefit from MOC's ordered execution across the inheritance chain and pre_/post_ hooks (which can be async for Method/Async_Method), providing structured flow control for your asynchronous logic
// Example using AsyncNew() with async phases (Recommended approach)
Pattern('AsyncCustomPhaseExample', {
    moc: { 
        custom_setup_phase: 'Phase'  // Still declared as 'Phase'
    },
    
    async pre_custom_setup_phase() {
        console.log('Pre custom setup (async)');
        this.preData = await fetch('/api/pre-data').then(r => r.json());
    },
    
    async custom_setup_phase() {
        console.log('Running custom_setup_phase (async)');
        this.setupData = await fetch('/api/setup').then(r => r.json());
        return { customInitialized: true, setupId: this.setupData.id };
    },
    
    async post_custom_setup_phase() {
        console.log('Post custom setup (async)');
        await this.finalizeSetup();
        return { setupComplete: true };
    },
    
    async finalizeSetup() {
        // Additional async work
        await new Promise(resolve => setTimeout(resolve, 100));
        this.finalData = { timestamp: Date.now() };
    }
});

// CORRECT: Use AsyncNew for patterns with async phases
const asyncObj = await AsyncNew('AsyncCustomPhaseExample');

// Alternative approach: Synchronous phases with separate async method
Pattern('MixedPhaseExample', {
    moc: { 
        setup_phase: 'Phase',           // Synchronous phase
        async_init: 'Async_Method'      // Separate async method
    },
    
    setup_phase() {
        console.log('Synchronous setup');
        this.ready = false;
        return { basicSetup: true };
    },
    
    async async_init() {
        console.log('Async initialization');
        this.data = await fetch('/api/data').then(r => r.json());
        this.ready = true;
        return { asyncComplete: true };
    }
});

// Can use regular New(), then call async method separately
const mixedObj = New('MixedPhaseExample');
await mixedObj.async_init(); // Call async method after construction

Key behaviors:

  • Auto-executed during object construction by the machine
  • Can be async when using AsyncNew(), synchronous with regular New()
  • Used for core object lifecycle steps
  • Return values merged onto this
  • Automatically get pre_ and post_ hooks, which can also be async with AsyncNew()

This_Blender (This_Blender)

Functionally identical to Method. This name exists for semantic clarity, emphasizing that return values blend onto this. Method is the original name and remains fully supported. Choose whichever name makes the intent clearer in your context.

Pattern('ThisBlenderExample', {
    moc: { 
        configure: 'This_Blender'
    },
    configure: function(options) {
        console.log('Configuring with', options);
        return { configured: true };  // Merged onto this
    }
});

Key behaviors:

  • 'Method' is just the original name for what is now technically This_Blender
  • 'This_Blender' emphasizes that return values blend into this
  • 'Method' will never be deprecated, and is often the preferred name
  • Phase is the only MOC type that cannot be made asynchronous. Its pre_ and post_ hooks must also be synchronous.

Chain (Chain)

Executes methods in Pattern Resolution Order, passing the return value of one method as the first argument to the next, enabling sequential processing or data transformation pipelines.

Pattern('ChainExample', {
    moc: { 
        process: 'Chain'
    },
    process: function(data) {
        console.log('Processing', data);
        return data + 1;  // Passed as first arg to next method
    }
});

Pattern('ExtendedChain', 'ChainExample', {
    process: function(data) {
        console.log('Extended processing', data);
        return data * 2;  // Final return value
    }
});

const obj = New('ExtendedChain');
console.log(obj.process(5));  // Outputs: 12 ((5 + 1) * 2)

Key behaviors:

  • Executes in Pattern Resolution Order
  • Each method's return value becomes first argument of next method
  • Original arguments passed after chained value
  • Common for data transformation chains

Blender (Blender)

Executes methods in Pattern Resolution Order, merging return values onto a target object passed as the first argument. Useful for enhancing or modifying an external object.

Pattern('BlenderExample', {
    moc: { 
        enhance: 'Blender'
    },
    enhance: function(target, extra) {
        console.log('Enhancing with', extra);
        return { base: true };  // Merged onto target
    }
});

Pattern('ExtendedBlender', 'BlenderExample', {
    enhance: function(target, extra) {
        return { extended: true };  // Also merged onto target
    }
});

const obj = New('ExtendedBlender');
const result = obj.enhance({}, 'extra');
console.log(result);  // { base: true, extended: true }

Key behaviors:

  • Executes in Pattern Resolution Order
  • First argument is target object
  • Return values merged onto target object
  • Common for object enhancement scenarios

Funnel (Funnel)

Like Blender, but executes methods in reverse Pattern Resolution Order (derived to base). Merges return values onto the target object (first argument). Useful for applying final modifications or configurations.

Note: Like all method melding types except Phase, Funnel methods can be async if declared using the Async_Funnel type.

Pattern('FunnelExample', {
    moc: { 
        finalize: 'Funnel'
    },
    finalize: function(target) {
        console.log('Base finalizing');
        return { base: true };  // Applied last
    }
});

Pattern('ExtendedFunnel', 'FunnelExample', {
    finalize: function(target) {
        console.log('Extended finalizing');
        return { extended: true };  // Applied first
    }
});

const obj = New('ExtendedFunnel', {
    finalize: function(target) {
        console.log('Idea finalizing');
        return { idea: true };  // Applied first
    }
});

const result = obj.finalize({});
console.log(result);
// { idea: true, extended: true, base: true }

Key behaviors:

  • Executes in reverse Pattern Resolution Order (derived to base)
  • First argument is target object
  • Return values merged onto target object
  • Common for finalization scenarios

This_Funnel (This_Funnel)

Like Method/This_Blender, but executes in reverse Pattern Resolution Order (derived to base). Merges return values onto this. Often used for cleanup or teardown logic.

Pattern('ThisFunnelExample', {
    moc: { 
        cleanup: 'This_Funnel'
    },
    cleanup: function() {
        console.log('Base cleanup');
        return { baseClean: true };  // Applied last onto this
    }
});

Pattern('ExtendedThisFunnel', 'ThisFunnelExample', {
    cleanup: function() {
        console.log('Extended cleanup');
        return { extendedClean: true };  // Applied first onto this
    }
});

Key behaviors:

  • Executes in reverse Pattern Resolution Order
  • Return values merged onto this
  • Common for cleanup/teardown scenarios

Chain_Funnel (Chain_Funnel)

Like Chain, but executes in reverse Pattern Resolution Order (derived to base). Passes return values sequentially as the first argument to the next method. Useful for bottom-up transformations.

Pattern('ChainFunnelExample', {
    moc: { 
        transform: 'Chain_Funnel'
    },
    transform: function(value) {
        console.log('Base transform', value);
        return value + 1;  // Applied last
    }
});

Pattern('ExtendedChainFunnel', 'ChainFunnelExample', {
    transform: function(value) {
        console.log('Extended transform', value);
        return value * 2;  // Applied first
    }
});

const obj = New('ExtendedChainFunnel');
console.log(obj.transform(5));  // Outputs: 11 ((5 * 2) + 1)

Key behaviors:

  • Executes in reverse Pattern Resolution Order
  • Each return value becomes first argument of next method
  • Common for bottom-up transformations

Async Variants

Each method type (except Phase) has an async variant prefixed with Async_:

Pattern('AsyncExample', {
    moc: {
        method: 'Async_Method',
        chain: 'Async_Chain',
        blender: 'Async_Blender',
        funnel: 'Async_Funnel',
        this_funnel: 'Async_This_Funnel',
        chain_funnel: 'Async_Chain_Funnel',
        // init_phase: 'Phase' // NO Async_Phase! Phases must be sync.
    },
    async method() {
        await someAsyncOperation();
        return { done: true };
    }
});

Key behaviors:

  • Methods can use async/await
  • Execution order preserved
  • Returns Promise that resolves to normal return type
  • Pre/post hooks can also be async
  • Async and sync methods can be mixed in chain
  • Phase methods can be async when using AsyncNew() (unlike other method types which have dedicated async variants)

Advanced Features

Nested MOC Control

The {} melder enables MOC definitions for nested properties without adding MOC metadata to the actual objects:

Pattern('NestedMOCExample', {
    moc: {
        config: {  // Deep melding with nested MOC
            theme: 'IsString',
            counts: 'Array',
            nested: {  // Further nesting
                enabled: 'IsBoolean',
                values: 'Array'
            }
        }
    }
});

This is particularly useful when the config object needs to be passed to APIs that don't expect additional properties.

Multiple MOC Options

Properties can have multiple MOC options from different tracks:

Pattern('MultipleOptionsExample', {
    moc: {
        // Combines Array melding, type validation, and exclusions
        items: ['Array', 'NotNull', 'NotUndefined']
    }
});

Automatic Type Checking

Melders automatically include appropriate type validation:

Pattern('AutoTypeCheckExample', {
    moc: {
        list: 'Array',       // Includes IsArray
        obj: 'Object',       // Includes IsObjectObject
        fn: 'Method',        // Includes IsFunction
        text: 'Concat'       // Includes IsString or IsArray
    }
});

Integration with Other Doh Systems

MOC is tightly integrated with several other core Doh systems:

  1. Pattern System: MOC definitions in patterns control how properties are inherited and combined when creating new objects.

  2. Inheritance System: MOC works with Doh's inheritance system to determine how properties are resolved and merged across the inheritance chain.

  3. Object Lifecycle: Method MOC types, especially Phase, play a crucial role in the object lifecycle, controlling how initialization methods are executed. Remember that Phase methods and their pre_/post_ hooks must be synchronous.

  4. Sub-Object-Builder: MOC definitions help the Sub-Object-Builder system determine how nested objects should be constructed and composed.

Last updated: 8/23/2025