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

Modules: Packages with Executable Logic

Modules

We found that combining Package structure with executable logic works well. A Module is simply a Package with a callback—it combines declarative structural relationships with runtime behavior in a single atomic declaration, removing the artificial separation between "what it needs" and "what it does."

This approach has enabled static analysis: the Auto-Packager analyzes the Module's structure at build-time while the runtime executes its callback with precise dependency injection. You declare both structure and behavior in one place; the system handles the orchestration.

The system unifies:

  • Structural Declaration: Dependencies (load), metadata, and installation requirements
  • Behavioral Implementation: Executable callbacks with parameter injection
  • Dependency Graph Participation: Relationships as both producer and consumer
  • Runtime Orchestration: Execution after dependency resolution

Modules share the Doh.Packages registry with Packages, representing the principle that structure and behavior are complementary aspects of the same organizational primitive. The distinction is that Packages provide structure only, while Modules provide structure plus execution.

Unified Structural and Behavioral System

Modules integrate seamlessly with the Package system through shared primitives:

  1. Atomic Declaration: Every Module creates both structure and behavior in Doh.Packages[name], eliminating the need for separate Package declarations
  2. Inherited Capabilities: All Package primitives (load blocks, installation, pod configuration) are available to Modules
  3. Shared Processing: Module dependencies use identical Load System primitives as Package dependencies
  4. Unified Analysis: The Auto-Packager treats both through the same analysis and manifest generation process

This architectural unity means Doh.Module() provides both organizational structure and behavioral implementation as a single, atomic primitive—eliminating the artificial separation between "what it needs" and "what it does."

Module Declaration: Structure and Behavior

Modules use the Doh.Module function to define both dependencies and implementation in a single declaration:

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, Using SPR for 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: Automatic NPM Dependency Management

    Doh includes an Auto-Installer that automatically manages npm dependencies based on import statements in your module load blocks. This eliminates the need for manual Doh.Install() declarations in most cases.

    How the Auto-Installer Works with Modules:

    1. Import Detection: Scans module and package load blocks for import statements referencing npm packages
    2. Version Analysis: Parses version specifiers from import statements (e.g., react@^18.0.0)
    3. Automatic Installation: Installs missing packages when doh upgrade is run (which first runs doh update to get current manifest info)
    4. Smart Versioning: Supports full semver version specifications in module load blocks:
      • import React from "react" - Installs latest version
      • import React from "react@^18.0.0" - Installs compatible with 18.0.0
      • import React from "react@~18.2.0" - Installs compatible within 18.2.x
      • import React from "react@18.2.0" - Installs exact version 18.2.0

    Triggering the Auto-Installer:

    # Install any missing npm dependencies found in module load blocks
    doh upgrade
    
    # Or upgrade specific packages and install any missing npm deps
    doh upgrade my_specific_package
    

    Important: doh update only scans and reports dependencies - it does NOT install them. doh upgrade provides complete dependency management by running doh update before and after installations to ensure manifests are always current.

    Migration from Doh.Install():

    The Auto-Installer makes most Doh.Install() statements obsolete:

    // OLD WAY (deprecated but still works)
    Doh.Install('my_module', {
      'npm:react': '^18.0.0',
      'npm:axios': '',
      'npm:date-fns': '~2.30.0'
    });
    
    // NEW WAY (recommended)
    Doh.Module('my_module', [
      'import React from "react@^18.0.0"',
      'import axios from "axios"',
      'import * as dateFns from "date-fns@~2.30.0"'
    ], function(React, axios, dateFns) {
      // Dependencies auto-installed by doh upgrade
    });
    

    Module-Specific Installation Notes:

    • Auto-Installer only works with declarative module load blocks - NOT with dynamic Doh.load() calls
    • All imports in module load blocks are auto-installed when doh upgrade is run
    • Version pinning works in module load blocks - Semver syntax is used for installation
    • Prefer pinning versions directly in module import specifiers to capture version intent alongside usage
    • Doh.Install remains supported for advanced cases (non-imported artifacts, scripts, or legacy flows), but module load blocks with Auto-Installer are the recommended approach
  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 use Doh's 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): Clean, DRY Module Suites

Scoped Parameter Resolution (SPR) is a pattern for writing clean, reusable module suites, made possible by the Auto-Packager's build-time analysis. It allows modules to consume dependencies provided by their ancestors without re-importing them, enabling suites to declare dependencies once at a high level and consume them by name further down.

This is not a replacement for explicit imports—SPR is a complementary pattern that emerges from manifest-driven architecture. Standard ESM imports remain the foundation; SPR provides an elegant way to share those imports across module hierarchies without repetition.

The Mental Model: Load builds scope; params consume scope

The pattern is simple and powerful:

  • load arrays produce scope - Dependencies declared in load arrays become available to descendant modules
  • Callback params consume that scope by name - Module callbacks can request any dependency from their ancestor chain by parameter name

This creates clean separation of concerns: declare dependencies where they logically belong, consume them where they're needed.

  • Auto-Packager catalogs callback parameters, records them in manifests, and evaluates env decorators so the suite stays coherent across branches
  • Resolution is by name through a deterministic scope tree (current module producers → ancestors), with no implicit globalThis (unless you explicitly use container defaults)

Primary Resolution: Deterministic Scope Tree Search

The system searches only the scope tree created by your load ancestry:

  1. This module's own producers (most specific)
  2. Ancestor modules' exports then producers, in Resolution Order (your load ancestry)

Resolution is deterministic and analyzable: The Auto-Packager's manifest visibility + diagnostics mean "mystery capture" is detectable and fixable.

If a name is not found, the parameter receives undefined with a diagnostic (no silent failures).

Source Declarations: Pinning to Shared Scopes

Some parameter forms are NOT resolution—they're source declarations that pin names to shared scopes:

  • function(Store = {}) { … }pins Store to Doh.Globals.Store (creates if missing)
  • function(Registry = { globalThis: {} }) { … }pins Registry to globalThis.Registry (creates if missing)

These provide an undersurface for shorthand resolution, NOT a named-based system that absolves module maintainers of responsibility. When you use source declarations, you're explicitly managing shared state rather than relying on automatic resolution.

Parameter Forms

Resolution-based (search scope tree):

  • Simple names: function(core, auth) — resolved by name against the scope tree
  • Destructuring: function({ format, parse }) — properties resolved independently, then re-packed
  • String aliases: function(db = 'Database') — resolve using the lookup name against the scope tree
  • Object repack: function(opts = { format: 'dateFnsFormat' }) — build object by resolving each key's lookup name

Source declarations (explicit shared containers):

  • function(Store = {}) — pins Store to Doh.Globals.Store (creates if missing)
  • function(Registry = { globalThis: {} }) — pins Registry to globalThis.Registry (creates if missing)

Special injections:

  • $ resolves to jQuery; DohPath is injected as a bound instance (not globalized).

When to use SPR

Use SPR when you want any of these benefits:

  • Pooled ESM results across a suite (one pinned copy everyone reuses)
  • Env/config branching where consumers should stay the same across browser?? / nodejs?? forks
  • Shared state objects via {} default containers (intentionally globalized)
  • Decoupled cross-use (consumer doesn't need to import a module it doesn't directly depend on)

Otherwise, just import—that's the default, and it's great for direct dependencies.

Concrete module suite recipe (the Doh-way, plus SPR)

// 1) Producers: one place to pin and import
Doh.Module('suite.producers', [
  'import { format, parse } from "date-fns@~2.30.0"'
], function(format, parse) {
  // Nothing else; just producing names into scope
});

// 2) Common consumer: **reuse** the producers via params
Doh.Module('suite.common', ['suite.producers'], function(format, parse) {
  // format/parse resolved by name from the suite's scope
});

// 3) Env branches declared declaratively; consumers keep params clean
Doh.Module('suite.ui', [
  'suite.common',
  'browser?? ui_lib'
], function(format) {
  // 'format' still comes from suite.producers
});

Doh.Module('suite.server', [
  'suite.common',
  'nodejs?? server_lib'
], function(parse) {
  // 'parse' still comes from suite.producers
});

// 4) Repack pattern: alias multiple lookups into one param
Doh.Module('suite.tools', [
  'import path from "path"',
  'suite.producers'
], function(utils = { format, parse, path }) {
  // utils is a local object packed from independent name lookups
});

Why this works (the benefits)

  • Centralized pinning & fewer duplicate imports in suites
  • Cleaner env-branching; identical consumers across forks
  • Pinning lives centrally and manifests know who consumes what
  • Deterministic & analyzable (manifests + diagnostics)
  • Auto-Packager makes this tractable

Mini patterns

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

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

// B. Pin to shared container (source declaration - NOT scope tree resolution)
Doh.Module('State', [
], function(Store = {}) {
  // Store is pinned to Doh.Globals.Store (created if missing)
  // This is a source declaration, not scope tree resolution
  Store.count = (Store.count || 0) + 1;
});

// Another module can access the same container via source declaration:
Doh.Module('StateHelper', [
], function(Store = {}) {
  // Store references the same Doh.Globals.Store
  // Both modules explicitly manage this shared state
  Store.count = (Store.count || 0) + 1;
});

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

// D. Repack by destructuring (scope tree resolution)
Doh.Module('Formats', [
  'import { format, parse } from "date-fns"'
], function(utils = { format, parse }) {
  // utils.format and utils.parse resolved from scope tree, then re-packed
});

What it's not (dispelling the fear)

  • Not global DI: Resolution is scoped to your load ancestry
  • Not magical: Explicit scope tree with manifest visibility
  • Not a "name-based injector" to be feared: It's scoped reuse, not a service locator
  • Not automatic dependency management: You're still responsible for your dependencies—SPR is shorthand resolution

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 integrates with the Module system through AST analysis:

  1. Module Discovery: Uses Abstract Syntax Tree parsing to find Doh.Module() calls and extract their structure
  2. Dependency Analysis: Analyzes load statements, conditional decorators, and environment branches to build dependency graphs
  3. Function Signature Analysis: Performs AST analysis of callback functions to extract parameter patterns, destructuring, default values, and parameter metadata
  4. Manifest Generation: Creates manifests containing structural metadata, parameter signatures, inheritance chains, and cross-references

This integration ensures:

  • Correct load order for modules based on dependency analysis
  • Proper initialization of parameters through AST-extracted function signatures
  • Bundling and optimization based on structural metadata
  • Consistent behavior across environments through manifest data

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. SPR (Scope Tree Resolution) Confusion

    • Remember that parameters are resolved by name, not position
    • Use descriptive parameter names that match their corresponding modules
    • Remember: Doh.Globals is NOT automatically searched - only explicitly pinned objects via ={} are accessible from Doh.Globals

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: 10/22/2025