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

Modules

Modules in Doh are executable units of code that extend the Package concept by adding a runtime function (callback). Packages and Modules share the same structure and registry; a Module is a Package with a callback, while a Package is a Module without one (declarative only). Both can participate in dependency graphs as dependents and dependencies.

Important: A module is a package with a callback. Both modules and packages live in the same Doh.Packages registry and share the same name namespace. Doh.Module('X', ...) defines both the package metadata and the executable callback in one atomic declaration. Do not mix Doh.Package('X', ...) and Doh.Module('X', ...) for the same name in separate places; the Auto-Packager treats each declaration as its own entity and overlapping names are considered conflicts.

In the Doh architecture:

  • Packages define dependencies (via load) and optional metadata and can be consumed as first-class dependencies
  • Modules add executable code and runtime behavior on top of the same definition
  • Patterns (defined within Modules) create reusable components and templates

A Module is essentially a Package with code, leveraging the same dependency system while adding execution logic. It is registered in Doh.Packages under its module name.

Relationship to Packages

Modules are directly tied to the Package system:

  1. Every Module automatically creates Doh.Packages[name] with the same name, including the callback. Avoid a separate Doh.Package(name, ...) for the same name.
  2. Modules inherit all Package capabilities (load blocks, installation instructions, pod configuration)
  3. Module dependencies use the same Load System as Package dependencies
  4. The Auto-Packager manages both Packages and Modules through the same process

This integration means you can use Doh.Module() to define both a Package and its implementation in one step.

Defining a Module

Modules are defined using the Doh.Module function:

Doh.Module(moduleName, dependencies, callback);
  • moduleName: A string identifying the module (becomes the Package name as well)
  • dependencies: An optional array of dependency strings or a single dependency string
  • callback: A function that defines the module's behavior, receiving globals as parameters

Example:

Doh.Module('MyModule', ['dependency1', 'dependency2'], function(SharedObject) {
    // Module implementation using SharedObject
});

Module Dependencies

Dependencies can be specified in several ways:

  1. No dependencies:

    Doh.Module('SimpleModule', function() {
        // No dependencies
    });
    
  2. Single dependency as a string:

    Doh.Module('SingleDependencyModule', 'dependency', function() {
        // Single dependency
    });
    
  3. Multiple dependencies as an array:

    Doh.Module('MultiDependencyModule', ['dep1', 'dep2'], function() {
        // Multiple dependencies
    });
    

Dependency Types

Doh supports various types of dependencies, all using the Load System:

  1. Other Doh modules:

    Doh.Module('ModuleA', ['ModuleB'], function() {
        // ModuleB is another Doh module
    });
    
  2. ES modules (with Auto-Installer):

    Doh.Module('ESMUser', ['import { func } from "esm-module"'], function(func) {
        // func is imported from an ES module
        // NPM packages are auto-installed when you run 'doh upgrade'
    });
    
    // NPM packages with version pinning
    Doh.Module('NPMUser', [
        'import React from "react@^18.0.0"',        // Auto-install React 18.x.x
        'import { format } from "date-fns@~2.30.0"' // Auto-install date-fns 2.30.x
    ], function(React, format) {
        // Dependencies are automatically installed by the Auto-Installer
    });
    

    Auto-Installer Note: NPM packages referenced in import statements are automatically installed when you run doh upgrade (which runs doh update before and after installations to ensure manifests are current). This makes most Doh.Install() statements obsolete. The Auto-Installer supports full semver version specifications directly in import statements.

  3. JavaScript files:

    Doh.Module('ScriptUser', ['path/to/script.js'], function() {
        // script.js is loaded before this module
    });
    
  4. CSS files:

    Doh.Module('StyleUser', ['path/to/styles.css'], function() {
        // styles.css is loaded before this module
    });
    

Advanced Load Statements

Modules leverage Doh's powerful Load System with decorators:

Doh.Module('AdvancedModule', [
    // Standard module dependency
    'core-module',
    
    // ES module import 
    'import { component } from "ui-library"',
    
    // Await decorator - ensures this loads before subsequent dependencies
    'await critical-dependency',
    
    // Async decorator - starts loading immediately
    'async non-blocking-dependency',
    
    // Environment conditional - only loads in browser
    'browser?? browser-specific-module',
    
    // Environment conditional - only loads in Node.js
    'nodejs?? node-specific-module',
    
    // Configuration conditional - only loads in debug mode
    'config.debug?? debug-tools',
    
    // Explicit file type declaration
    'dynamic-content.php > js'
], function(component) {
    // Module implementation
});

For complete details on load decorators and syntax, see the Load System documentation.

Environment Branching for Multi-Environment Modules

A key strength of Doh Modules is their ability to handle multiple environments efficiently. The most common pattern is to bifurcate a module into environment-specific implementations with shared common code:

// Main package that branches by environment
Doh.Package('feature_name', {
  load: [
    // Common functionality shared across environments
    'feature_name_common',
    // Environment-specific implementations loaded conditionally
    'browser?? feature_name_ui',     // Only loads in browser environments  
    'nodejs?? feature_name_server'   // Only loads in Node.js environments
  ]
});

// Common module with functionality shared by all environments
Doh.Module('feature_name_common', [], function(FeatureName) {
  // Create shared interface
  FeatureName = FeatureName || {};
  
  // Add common utilities, data structures, and logic
  FeatureName.config = {
    // Shared configuration
  };
  
  FeatureName.utils = {
    // Common utility functions
    formatData: function(data) {
      // Implementation
    }
  };
  
  // Return the shared object for use by environment-specific modules
  return FeatureName;
});

// Browser-specific implementation
Doh.Module('feature_name_ui', [
  // Always load common functionality first
  'feature_name_common',
  // Browser-specific dependencies
  'html',
  '^/feature_name.css'
], function(FeatureName) {
  // Extend the shared object with browser-specific functionality
  FeatureName.renderUI = function(container) {
    // Browser-specific UI rendering
  };
  
  // Create DOM-related components
  FeatureName.components = {
    // Component implementations
  };
});

// Server-specific implementation
Doh.Module('feature_name_server', [
  // Always load common functionality first
  'feature_name_common',
  // Server-specific dependencies
  'express_router',
  'import fs from "fs-extra"'
], function(FeatureName, Router, fs) {
  // Add API routes
  Router.AddRoute('/api/feature', function(data, req, res) {
    // Handle API request
    const result = FeatureName.utils.formatData(data);
    Router.SendJSON(res, result);
  });
  
  // Add server-specific methods
  FeatureName.saveToFile = async function(data, path) {
    // Implementation using Node.js fs module
    await fs.writeFile(path, JSON.stringify(data));
  };
});

This pattern keeps code organized while ensuring that browser-specific code (like UI components) is only loaded in browsers, and server-specific code (like file system operations) is only loaded in Node.js.

Scoped Parameter Resolution (SPR)

SPR is how module callback parameters are wired in Doh: your function asks by name and the system resolves each name against a layered scope created by your load arrays. No DI tokens—just names.

Mental model

  • Load arrays build scope; callback parameters consume scope.
  • Only Packages and Modules build layers. Packages are declarative (no callback); Modules have both, a load array producer and the function callback consumer. The Auto‑Packager scans both definitions and the callback parameter list so resolution is predictable and fast.

Resolver (rule of four)

For every requested name (including properties inside destructured params), resolution follows:

  1. This module’s own producers (most specific)
  2. Ancestor modules exports then producers, in Resolution Order (your load ancestry)
  3. Doh.Globals[name] (Shared state collection)
  4. globalThis[name]

If still not found, the parameter is undefined and a diagnostic is logged. Legacy compatibility: enabling Doh.pod.ignore_missing_module_params (discouraged) will auto‑create a placeholder {} in Doh.Globals[name]. Prefer explicit construction via parameter defaults (e.g., param = {} or param = { globalThis: {} }).

Parameter forms

  • Simple names: function(core, auth) { … } — each resolved by name.
  • Destructuring: function({ format, parse }) { … } — properties are resolved independently, then re‑packed.
  • Alias via string default: function(db = 'Database') { … } — resolve using the lookup name "Database".
  • Object default mapping: function(opts = { format: 'dateFnsFormat', parse: 'dateFnsParse' }) { … } — build opts by resolving each key’s lookup name.
  • Create shared containers:
    • function(Store = {}) { … } — ensures Doh.Globals.Store exists and injects it.
    • function(Registry = { globalThis: {} }) { … } — ensures globalThis.Registry exists and injects it.
  • Specials: $ resolves to jQuery; DohPath is injected as a file‑bound Overloaded instance (not globalized).

Why it’s useful

  1. Reuse imports across a suite: import once high up; descendants just ask by name.
  2. Decouple producers from consumers: produce into the layered scope; later modules consume by name (aliases and mapping help keep it clean).
  3. Break cycles: when the load graph would be cyclic, share state via a named container (Store = {}) and consume by name.

Mini patterns

// A. Reuse the same ESM export in children
Doh.Module('UsingESM_common', [
  'import { format, parse } from "date-fns"'
], function(format, parse) { /* both resolved by name */ });

Doh.Module('consumer', [
  'UsingESM_common'
], function(format) { /* resolved by name from ancestor producer list */ });


// B. Create a shared container if missing (Doh.Globals)
Doh.Module('State', [
], function(Store = {}) {
  Store.count = (Store.count || 0) + 1;
});
// Add a participant of the share:
Doh.Module('StateHelper', [
], function(Store = {}) {
  Store.count = (Store.count || 0) + 1;
});

// C. Alias a parameter name
Doh.Module('AliasExample', [
  'import Database from "sqlite"'
], function(db = 'Database') {
  // Resolves using lookup name "Database"
});

// D. Repack by destructuring (equivalent to asking for format and parse by name)
Doh.Module('Formats', [
  'import { format, parse } from "date-fns"'
], function(utils = { format, parse }) {
  // utils.format and utils.parse are the references here
});

What it’s not

  • Not token‑based DI. No provider registration—names are resolved against layered scope.
  • Not closure access. You receive exports via the same path an ancestor used.
  • Not file‑scoped ESM. Composition is via registry + load graph, not files.
  • Not driven by Doh.load(). SPR governs callback parameters; load arrays are declarative imports, independent of parameter order.

Return value semantics

The callback’s return value becomes the module’s export. It is stored in Doh.ModuleExports[name] and available via Doh.Loaded['name']/await Doh.load('name'). There is no automatic merging with the parameter map; if you want to export a composed API object, return it explicitly from your module, or attach to/create a shared global or globalThis.

Module Lifecycle

Modules follow a specific lifecycle managed by the Doh runtime:

  1. Definition: When Doh.Module is called, registering the module with the system
  2. Dependency Resolution: When the module is first required, triggering the loading of dependencies
  3. Execution: When all dependencies are loaded and the callback function is invoked
  4. Completion: When the callback execution finishes (for async callbacks, after the Promise resolves)
  5. Globalization: When the module's parameters are fully established in the global scope

Only after a module's callback has completely finished execution is the module considered loaded, which may trigger other modules waiting on it.

Asynchronous Modules

Modules can have asynchronous callbacks, allowing for await operations:

Doh.Module('AsyncModule', async function(database) {
    // Initialize asynchronously
    database.connection = await establishDatabaseConnection();
    
    // Set up tables
    await database.createTablesIfNotExist();
    
    // Only after these async operations is the module considered loaded
});

Integration with Auto-Packager

The Auto-Packager deeply integrates with the Module system:

  1. Module Discovery: It automatically scans for Doh.Module() calls in your codebase
  2. Dependency Analysis: It builds a complete dependency graph for all modules
  3. Parameter Inspection: It analyzes callback parameters to identify required globals
  4. Manifest Generation: It creates manifests listing modules, dependencies, and globals

This integration ensures:

  • Correct load order for modules based on dependencies
  • Proper initialization of global parameters
  • Efficient bundling of related modules
  • Consistent behavior across environments

Cross-Environment Capability

Modules can adapt to different JavaScript environments using conditional dependencies and runtime checks:

Doh.Module('CrossPlatformModule', [
    // Core dependencies for all environments
    'core-lib',
    
    // Browser-specific dependencies
    'browser?? dom-helpers',
    'browser?? styles/main.css',
    
    // Node.js-specific dependencies
    'nodejs?? fs-utilities',
    
    // Deno-specific dependencies
    'deno?? deno-specific-helper',
    
    // Bun-specific dependencies
    'bun?? bun-optimized-helper'
], function(coreFunctions) {
    // Environment-specific initialization
    if (IsBrowser()) {
        initializeBrowserFeatures();
    } else if (IsNode()) {
        initializeNodeFeatures();
        if (IsDeno()) {
            initializeDenoFeatures();
        } else if (IsBun()) {
            initializeBunFeatures();
        }
    }
    
    // Common functionality
    coreFunctions.initialize();
});

Data Flow Between Environment-Specific Modules

When using the bifurcated pattern (common + environment-specific modules), it's important to understand how data flows between modules:

  1. The common module typically creates and initializes a shared object
  2. Environment-specific modules extend this shared object with specialized functionality
  3. Data can flow in both directions through this shared object
  4. Environment-specific modules never communicate directly with each other (they only exist in their respective environments)

Example workflow:

// 1. Common module creates shared object (Doh auto-initializes unknown paramaters)
Doh.Module('feature_common', function(Feature={}) {
    
    // Define shared data structures
    Feature.data = {};
    
    // Define common methods
    Feature.initialize = function(config) {
        Feature.data.config = config;
    };
    
    return { Feature };
});

// 2. Browser module extends with UI capabilities (Feature is known now, so Doh links it to the original)
Doh.Module('feature_browser', ['feature_common'], function(Feature={}) {
    // Add browser-specific method
    Feature.renderUI = function(container) {
        // Create UI based on Feature.data.config
    };
    
    // Listen for UI events that modify shared data
    Feature.onSubmit = function(formData) {
        // Update shared data
        Feature.data.userInput = formData;
    };
});

// 3. Server module extends with data persistence (And here again)
Doh.Module('feature_server', ['feature_common'], function(Feature={}) {
    // Add server-specific method
    Feature.saveData = function() {
        // Save Feature.data to database
    };
    
    // Process data submitted from browser
    Feature.processUserInput = function() {
        if (Feature.data.userInput) {
            // Process the data submitted from the browser
        }
    };
});

Advanced Features

Pattern Definition

Modules commonly define Patterns, which are reusable component templates:

Doh.Module('UIModule', function() {
    Pattern('Button', 'html', {
        moc: {
            click: 'method'
        },
        tag: 'button',
        html: 'Click me',
        click: function() {
            this.html = 'Clicked!';
        },
        html_phase: function(){
            this.e.on('click', this.click);
        }
    });
});

Installation Requirements

Modules can specify installation requirements using Doh.Install():

Doh.Install('MyModule', {
    // NPM package dependency
    'npm:some-package': '^1.0.0',
});

Pod Configuration

Modules can define configuration data using Doh.Pod():

Doh.Pod('MyModule', {
    // Module configuration
    options: {
        theme: 'light',
        features: ['search', 'filter']
    }
});

Real-World Examples

1. Feature Module with Environment Branching

A typical feature module with environment-specific functionality:

// Main package that branches by environment
Doh.Package('user_system', {
  load: [
    'user_common',               // Common user functionality
    'browser?? user_browser',    // Browser UI implementation
    'nodejs?? user_server'       // Server API implementation
  ]
});

// Common user functionality
Doh.Module('user_common', function() {
  const User = {};
  
  // Shared data structures and validation logic
  User.validateEmail = function(email) {
    return /\S+@\S+\.\S+/.test(email);
  };
  
  User.validatePassword = function(password) {
    return password.length >= 8;
  };
  
  return User;
});

// Browser-specific implementation
Doh.Module('user_browser', [
  'user_common',
  'html',
  '^/styles/user.css'
], function(User) {
  // Add browser-specific UI functionality
  User.renderLoginForm = function(container) {
    const form = $('<form>').html(`
      <input type="email" id="email" placeholder="Email">
      <input type="password" id="password" placeholder="Password">
      <button type="submit">Login</button>
    `);
    
    form.on('submit', function(e) {
      e.preventDefault();
      
      const email = $('#email').val();
      const password = $('#password').val();
      
      if (User.validateEmail(email) && User.validatePassword(password)) {
        // Submit login request
        User.login(email, password);
      }
    });
    
    $(container).append(form);
  };
  
  User.login = function(email, password) {
    // Send login request to server
    $.post('/api/login', { email, password })
      .then(response => {
        if (response.success) {
          User.currentUser = response.user;
          // Trigger login event
          $(document).trigger('user:login', User.currentUser);
        }
      });
  };
});

// Server-specific implementation
Doh.Module('user_server', [
  'user_common',
  'express_router',
  'import bcrypt from "bcrypt"',
  'dataforge'
], function(User, Router, bcrypt) {
  // Set up user database
  User.db = df.Create('users');
  
  // Add server-specific API endpoints
  Router.AddRoute('/api/login', async function(data, req, res) {
    const { email, password } = data;
    
    if (!User.validateEmail(email) || !User.validatePassword(password)) {
      return Router.SendJSON(res, { 
        success: false, 
        error: 'Invalid email or password format' 
      });
    }
    
    // Look up user by email
    const user = await User.db.ops.Find({ email }).first();
    
    if (!user) {
      return Router.SendJSON(res, { 
        success: false, 
        error: 'User not found' 
      });
    }
    
    // Verify password
    const passwordMatch = await bcrypt.compare(password, user.passwordHash);
    
    if (!passwordMatch) {
      return Router.SendJSON(res, { 
        success: false, 
        error: 'Invalid password' 
      });
    }
    
    // Create session
    req.session.userId = user.id;
    
    // Return success
    Router.SendJSON(res, { 
      success: true, 
      user: {
        id: user.id,
        email: user.email,
        name: user.name
      }
    });
  });
});

2. Configuration-Based Modules

Using pod configuration to customize module behavior:

// Define default configuration
Doh.Pod('feature_system', {
  features: {
    advanced: true,
    experimental: false
  },
  theme: 'light'
});

// Module that adapts based on configuration
Doh.Module('feature_system', [
  // Basic functionality
  'core_components',
  
  // Advanced features conditionally loaded based on configuration
  'Doh.pod.features.advanced?? advanced_features',
  
  // Experimental features conditionally loaded based on configuration
  'Doh.pod.features.experimental?? experimental_features',
], function(core) {
  // Initialize with configuration
  core.initialize(Doh.pod.features);
});

Best Practices

  1. Single Responsibility: Keep modules focused on a single responsibility or feature area
  2. Meaningful Names: Use clear, descriptive names for modules and parameter names
  3. Smart Dependencies: Use load decorators to optimize loading performance
  4. Environment Branching: Use a common + environment-specific module pattern for cross-platform code
  5. Shared Objects: Use shared parameter objects for communication between related modules
  6. Global Scope Hygiene: Treat shared parameters carefully to avoid conflicts
  7. Async When Needed: Use async modules for operations requiring initialization
  8. Parameter Documentation: Document expected parameters and their structure
  9. Pattern Organization: Group related patterns within a module
  10. Clear Module Boundaries: Establish clear interfaces between modules

Troubleshooting

Common issues and solutions:

  1. Module Not Loading

    • Check for errors in the browser console or Node.js output
    • Use Doh.loadstatus() to see which modules are loaded and loading
    • Verify all dependencies are available and properly named
  2. Undefined Parameters

    • Ensure the parameter is properly initialized by a dependency
    • Check if the module that should provide the parameter is loaded first
    • Verify the parameter name matches exactly between modules
  3. Load Order Issues

    • Use await decorator for critical dependencies
    • Review the dependency order in your load block
    • Check the Auto-Packager manifest for unexpected dependency paths
  4. Cross-Environment Problems

    • Use environment-specific conditional decorators
    • Implement environment checks in your module code
    • Test modules in all target environments
  5. Parameter Resolution Confusion

    • Remember that parameters are resolved by name, not position
    • Use descriptive parameter names that match their corresponding modules
    • Check Doh.Globals to see what objects are available for parameter resolution

Related Documentation

  • Load System - Understanding the core dependency loading mechanism
  • Packages - The foundation of module dependency structure
  • Auto-Packager - How modules are discovered and analyzed
  • Patterns - Defining reusable components within modules
  • Pods - Configuration system for modules
Last updated: 8/23/2025