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

Object Instantiation Lifecycle

Pattern Lifecycle

This guide explains the object instantiation lifecycle in Doh - the step-by-step process by which Doh creates and initializes object instances using AsyncNew() (the standard) or New() (for synchronous-only cases). Understanding this construction flow and the role of lifecycle phases helps with effectively using and extending Doh objects.

This guide covers:

  • The AsyncNew() function (standard) and New() function (synchronous-only) and the object creation process.
  • The standard object lifecycle phases and their execution order.
  • How inheritance and pattern mixing occur during instantiation.
  • Handling constructor arguments and object initialization.
  • Using the machine function for phase execution control.

Overview of Object Creation (AsyncNew() as Standard)

In Doh, object instances are created using AsyncNew(), which is the standard and recommended approach. It enables async phases in the construction lifecycle for all objects and their children. The New() function is available for synchronous-only cases, but AsyncNew() is recommended wherever possible. These functions orchestrate the transformation of a Pattern definition and an optional input idea object into a Doh object instance, ready for use. The process involves resolving inheritance, melding properties, and executing lifecycle phases in a specific order.

// Standard approach: Creating a new object instance (recommended)
let myObject = await AsyncNew('pattern_name', idea_object, phase);

// Synchronous-only: Creating a new object instance (use only when needed)
let mySyncObject = New('pattern_name', idea_object, phase);

Why use AsyncNew()?

  • Enables async phases for all objects and children in the construction lifecycle
  • Works with both synchronous and asynchronous patterns
  • Future-proof: allows patterns to add async phases without code changes
  • Recommended as the standard initialization method

The AsyncNew() and New() Functions

AsyncNew() Function (Standard)

The AsyncNew() function is the standard and recommended way to create objects in Doh. It enables async phases in the construction lifecycle for all objects and their children:

  1. await AsyncNew(pattern, idea, phase) - Create a new object asynchronously, enabling async phase execution
  2. await AsyncNew(pattern, idea) - Create a new object asynchronously, defaulting to 'final' phase
  3. await AsyncNew(pattern) - Create a new object using the pattern, with empty idea

Key features:

  • Returns a Promise: Must be awaited since it returns a Promise
  • Enables async phase handlers: Phase methods marked with async can safely use await
  • Works for all objects and children: Async phases work throughout the entire object hierarchy
  • Automatic pattern loading: Loads the pattern if it's not already available
  • Compatible with sync patterns: Works perfectly with patterns that only have synchronous phase handlers
  • Standard approach: Recommended for all object initialization
// Standard approach: Use AsyncNew() for all patterns
Pattern('MyPattern', {
    object_phase() {
        this.data = 'initialized';
    }
});
const obj = await AsyncNew('MyPattern');

// Async pattern: AsyncNew() enables async phases
Pattern('AsyncPattern', {
    async object_phase() {
        this.data = await fetch('/api/data');
        this.processedData = await this.data.json();
    }
});
const asyncObj = await AsyncNew('AsyncPattern');

New() Function (Synchronous-Only)

The New() function is available for synchronous-only object creation. Use AsyncNew() instead unless you specifically need synchronous-only construction:

  1. New(pattern, idea, phase) - Create a new object with the specified pattern and idea, machined to the specified phase
  2. New(pattern, idea) - Create a new object, defaulting to 'final' phase
  3. New(idea) - Create a new object using idea.pattern as the pattern
  4. New(existingObject, phase) - Machine an existing object to the specified phase

Limitations:

  • Synchronous only: Cannot handle async phase handlers (will generate warnings)
  • No async phases: Objects and children cannot use async phases in construction
  • Use only when needed: Prefer AsyncNew() for all new code

The Object Construction Flow

When AsyncNew() (or New()) is called, it initiates the following construction flow:

1. Pattern and Idea Processing

First, the function determines the primary pattern and initial properties (idea):

  • If the pattern is a string, it uses that as the idea's pattern
  • If the pattern is an array, it merges the array items into idea.inherits
  • If the pattern is an object, it treats that as the idea

2. Inheritance Resolution

The function then resolves all the patterns that the object should inherit from:

// Collect all patterns from both the pattern's inherits and the idea's inherits
var patterns = Doh.meld_objects(
  Doh.extend_inherits(Patterns[idea.pattern].inherits), 
  Doh.extend_inherits(idea.inherits)
);

// Add the main pattern last
patterns[idea.pattern] = true;

3. Pre-Construction Phase

Before creating the object, constructor arguments are prepared:

// Process constructor arguments through pre_constructor hooks
for (let i = 0; i < pre_melded_constructors.length; i++) {
  test_args = pre_melded_constructors[i](constructor_args);
  if (test_args && IsIterable(test_args)) constructor_args = test_args;
}

4. Object Creation

The actual object is created using either the default prototype constructor or a custom one:

// Default PrototypeConstructor creates a proper prototype chain
object = prototype_constructor(...constructor_args);
object.inherited = object.inherited || {};

5. Constructor Execution

After object creation, constructors are called in inheritance order:

// Run all constructor methods in pattern order
for (let i = 0; i < melded_constructors.length; i++) {
  melded_constructors[i].call(object, ...constructor_args);
}
if (Object.hasOwn(idea, 'constructor')) {
  idea.constructor.call(object, ...constructor_args);
}

6. Pattern Mixing

Each resolved pattern is then mixed into the object instance using Doh.mixin_pattern. This function applies the MOC rules during the process. By default, properties are replaced (override-first). When a property declares a MOC melder (e.g., 'Method', 'Array', 'Object', deep {}), the specified melding behavior is applied instead. Validation then occurs ('IsString', 'IsArray', 'IsObject', etc, or the default: 'IsAny'). See the MOC documentation for complete melding strategies:

// Mix each pattern into the object
// Doh.mixin_pattern applies MOC rules during this process
for (i in patterns) {
  // Only mix in hard inherits (true)
  if (patterns[i] === true) Doh.mixin_pattern(object, i);
  else delete patterns[i]; // Clean up soft inherits
}

7. Inherits List Creation

The inherits property is updated to be an ordered list of inherited patterns:

object.inherits = [];
for (i in patterns) {
  object.inherits.push(i);
}

8. Machine Attachment and Phase Processing

Finally, the object's machine function is attached. This function controls the execution of lifecycle phases.

object.machine = function (phase) {
  // Cycle through defined phases (from MOC definition)
  for (let phase_name of object.moc.__.phase_order()) {
    if (!object.machine.completed[phase_name]) { // Run each phase only once
      object.machine.phase = phase_name;
      object.machine.completed[phase_name] = false; // Mark as running
      
      // Execute the melded methods for this phase
      // Note: 'Phase' is a MOC type, see /doh_js/docs/patterns/moc.md
      object[phase_name].apply(object);
      
      object.machine.completed[phase_name] = true; // Mark as complete
    }
    // Stop if the target phase is reached
    if (phase_name == phase) return object;
    }
  return object;
};

// Initialize completion state
object.machine.completed = {};

// Execute phases up to the requested phase (or 'final' by default)
object.machine(phase || 'final'); 

This machine ensures phases run in the correct order (defined by the combined MOC definitions of the inherited patterns) and only once per instance.

Core Phases Defined by object Pattern

The base object pattern defines two primary lifecycle phases that run for all Doh object instances:

1. Object Phase (object_phase)

The object_phase is the primary initialization phase. Its responsibilities include setting up internal properties like melded and handling static property connections.

// Simplified view of object_phase from the base 'object' pattern
object_phase: function () {
  // Ensure this.melded refers to the final MOC definition
  this.melded = this.moc || {};
  Doh.mimic(this, 'melded', this, 'moc');
  
}

Asynchronous Object Phase:

When using AsyncNew(), the object_phase can be declared as async and use await:

Pattern('AsyncInitPattern', {
    async object_phase() {
        // Can safely use await in AsyncNew() context
        this.config = await fetch('/api/config').then(r => r.json());
        this.melded = this.moc || {};
        Doh.mimic(this, 'melded', this, 'moc');
        return { initialized: true };
    }
});

// Must use AsyncNew() for patterns with async phases
const obj = await AsyncNew('AsyncInitPattern');

2. Builder Phase (builder_phase)

The builder_phase handles the automatic construction of child objects defined within the instance, integrating with the Sub-Object-Builder system.

// Simplified view of builder_phase from the base 'object' pattern
builder_phase: function () {
  // 1. Collect properties marked for auto-building into this.built
  Doh.collect_buildable_ideas(this, this.moc, this, '');
  
  // 2. Set up parent-child linkage helpers (this.builder_method, etc.)
  // ... (builder helper setup) ...
  
  // 3. Build child objects if any were found
  if (this.built) {
    // Define the internal function that builds each child
    this.machine_built = function (targetPhase) { 
      for (let prop_name in this.built) {
        if (IsUndefined(this.built[prop_name])) { // Check if already built
          let idea = this[prop_name]; 
          // Instantiate child, machining it ONLY up to targetPhase
          // Note: When AsyncNew() is used, children can also use async phases
          this[prop_name] = this.built[prop_name] = New(idea, targetPhase);
          this[prop_name].builder = this; // Set parent reference
        }
      }
    };
    // Execute the build, machining children to the phase specified 
    // in this.machine_built_to, or 'final' if not specified.
    this.machine_built(this.machine_built_to || 'final'); 
  }
}

This phase uses an internal machine_built(phase) function to iterate through properties marked for building (collected in this.built) and instantiates them using New(), passing down a target phase. When AsyncNew() is used for the parent object, children can also use async phases in their construction lifecycle.

Additional Object Methods (from object pattern)

The base object pattern also provides utility methods available on all instances:

Best Practices

  1. Understanding Phase Order:

    • Always remember that object_phase runs before builder_phase
    • Define child objects that need auto-building before or during object_phase
  2. Choosing New() vs AsyncNew():

    • Use AsyncNew() wherever possible - it's the standard and recommended approach
    • AsyncNew() enables async phases for all objects and children in the construction lifecycle
    • AsyncNew() is fully compatible with synchronous patterns
    • Use New() only when you specifically need synchronous-only construction and are certain no async phases are needed
    • Using async phase handlers with New() will generate warnings
  3. Pattern Inheritance:

    • The 'object' pattern is always at the base of all inheritance
    • Patterns in the inheritance chain are processed in order
  4. Machine Usage:

    • Use object.machine() to advance an object through phases
    • Check object.machine.completed to see which phases have run
  5. Custom Phases:

    • Define custom phases in your pattern's MOC definition using the 'phase' type.
    • They will be run after the core phases (object_phase, builder_phase).
    • Custom phases can be async when used with AsyncNew()
    • Remember that phases automatically support pre_ and post_ hooks (see the MOC documentation for details).
  6. Asynchronous Phase Design:

    • Mark phase handlers as async when they need to use await
    • Always use AsyncNew() when patterns have async phases
    • Phase return values are still merged onto this for async phases
    • Pre/post hooks for async phases can also be async

When extending or creating patterns, focus on the phase methods (object_phase, builder_phase, and custom phases) to initialize your objects and build any child components. Use AsyncNew() as the standard initialization method - it enables async phases throughout the entire object hierarchy, allowing all objects and children to use async phases in their construction lifecycle.

Last updated: 12/15/2025