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

Packages: Structural Organization Units

Packages

Packages are the building blocks of Doh's architectural organization—pure structure without behavior. They encode structural relationships, dependencies, and metadata into reusable units that Modules can depend on by name.

A Package is simply a Module without a callback: same registry (Doh.Packages), same dependency system, same analysis by the Auto-Packager. This unified treatment means that organizational structure and behavioral implementation follow the same rules.

The system provides:

  • First-class participation in dependency graphs as both dependents and dependencies
  • Identical processing to Modules through the Load System, including decorators and conditions
  • Shared namespace with Modules in Doh.Packages registry (a Module is simply a Package with a callback)
  • Declarative modeling of structure and relationships that becomes the extendable class for behavioral Modules

This architectural separation allows you to encode organizational structure independently of behavior, creating reusable relationship patterns that Modules can extend with execution logic.

Packages and SPR layering

Packages contribute to the layered scope used by Scoped Parameter Resolution (SPR)—enabling module callbacks to consume names that earlier modules produced without re-importing.

  • Packages build scope layers via their load arrays; Modules consume those layers via callback parameters.
  • A Package has no callback, so it never consumes; it only declares imports/dependencies that become part of the ancestor chain when Modules run.
  • Modules are both: a load‑array producer and a function‑callback consumer. Packages are producers only.
  • This enables suite patterns: Centralize imports (e.g., ESM libraries) in a higher‑level Package, and descendant Modules simply "ask by name" for the same exports without repeating imports.

Resolution is deterministic (current module's producers → ancestors), with no implicit global search. The Auto-Packager's manifest visibility + diagnostics mean "mystery capture" is detectable and fixable.

See Modules → Scoped Parameter Resolution (SPR) for full details and examples.

Architectural Foundation: Structure

Packages serve as the structural foundation of Doh applications by encoding organizational relationships into reusable primitives:

  • Dependency Graph Encoding: Define application structure as first-class relationships between components
  • Load System Integration: Process declarative dependencies through identical primitives as Modules
  • Build-Time Analysis: Enable Auto-Packager analysis for manifest generation
  • Distribution: Serve as a signal for Dohball packaging and distribution
  • Behavioral Extension: Provide the structural base that Modules extend with executable callbacks

Package Definition Methods

*.doh.yaml Files

The most declarative way to define packages is through YAML configuration files. The Auto-Packager discovers these files through filesystem scanning and uses YAML parsing to extract package definitions. Key features:

  • Each root-level key defines a separate package
  • Multiple packages can be defined in a single file
  • Any file with a .doh.yaml extension will be recognized

Simple String Definition (in YAML)

For packages with simple dependencies, you can use a shorthand notation in YAML:

simple_package: dependency_package

A powerful use of this is to make a packageName.doh.yaml file with packageName: doh_js in it, and nothing else. This depends on the Doh core, and is all that is needed to make a folder into a Dohball, ready to be baked and upgraded in other instances, even if there is no JS in the folder at all.

This is equivalent to:

simple_package:
  load:
    - dependency_package

Doh.Package() Function

For programmatic package definition, use the Doh.Package() function (defines a Package without a callback):

Doh.Package('package_name', {
    load: ['dependency1', 'dependency2'],
    install: ['npm:some-module']
});

Doh.Package() is a build-time declaration analyzed by the Auto-Packager's AST parser when scanning .js files. The Auto-Packager uses Abstract Syntax Tree analysis to extract complete structural information from the declaration, including complex object literals, computed values, and environment conditions. This call contributes rich metadata to manifests through static analysis; it does not execute code. Use Doh.Module() when you need to add a runtime callback to the same structural concept.

In Doh, modules and packages share the same global registry (Doh.Packages). A module is a package with a callback, defined atomically via Doh.Module('Name', [...], callback). Do not define a Doh.Package('Name', ...) and then add a callback later with Doh.Module('Name', ...). Because the Auto-Packager treats each declaration as a distinct definition, overlapping names from separate Doh.Package() and Doh.Module() calls are considered conflicts.

Other metafunctions like Doh.Pod(package_name, pod), Doh.CLI(package_name, cli_settings), and Doh.Install(package_name, dep_list) can be used alongside Doh.Package() OR Doh.Module() to contribute metadata.

Package Components

Load Block (Dependencies)

The core component of every package is its load block, which defines that package’s dependencies. It is processed by the Load System exactly like a Module’s load block:

load: [
    'dependency1',               // Basic dependency
    'await dependency2',         // Awaited dependency (loads before subsequent deps)
    'async dependency3',         // Asynchronous loading (starts immediately)
    'browser?? browser-only-dep', // Conditional dependency (browser environments only)
    'nodejs?? node-only-dep',     // Environment-specific dependency
    'import { func } from "es-module"', // ES module import
    'path/to/script.js',         // Direct file loading
    'styles/main.css'            // CSS loading
]

The load block supports a rich set of decorators and syntax for fine-grained control over dependency loading:

  1. Timing Decorators: await and async control the loading sequence
  2. Conditional Decorators: Environment checks like browser?? and nodejs??
  3. Complex Conditions: config.debug&&!production?? for configuration-based loading
  4. Type Specification: file.php > css for explicit resource type declaration

For complete details, see the Load System documentation.

Environment Branching with Packages

Environment branching is a key capability of the Doh Package system. It allows you to define different dependencies for different environments within a single package definition:

// Define a package with environment-specific dependencies
Doh.Package('cross_platform_feature', {
  load: [
    // Common dependencies loaded in all environments
    'core_utilities',
    'data_models',
    
    // Browser-specific dependencies
    'browser?? ui_components',
    'browser?? styles/main.css',
    
    // Node.js-specific dependencies
    'nodejs?? server_api',
    'nodejs?? import fs from "fs-extra"',
    
    // Configuration-based loading
    'Doh.pod.analytics?? analytics_module',
    
    // Combined conditions
    'browser&&Doh.pod.debug?? debug_tools'
  ]
});

Bifurcated Package Pattern

A common pattern for cross-platform features is to create a "bifurcated" package structure that branches into environment-specific implementations:

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

This pattern allows you to:

  1. Share common code across environments
  2. Keep environment-specific code separate
  3. Ensure environment-specific dependencies are only loaded when needed
  4. Organize related functionality under a single package namespace

Installation Guidance

Preferred approach:

  • Rely on auto-installation for imports. All ESM imports are auto-installed at 'latest' unless pinned.
  • Pin versions directly in your import specifiers (e.g., import x from 'pkg@^1.2.0').

Optional (advanced) installation block:

install: {
  'npm:package-name': '^1.2.3',  // Explicit requirement when needed
}

The Auto-Packager merges installs derived from pinned imports with any explicit install entries. Use explicit install when you need to declare non-imported dependencies or specialized constraints.

Pod Configuration

Packages can include pod configuration data:

pod: {
    key: 'value',
    nested: {
        config: true
    }
}

This data is merged into the global pod configuration and can be accessed via Doh.pod.key. Placing your keys in a container is preferred.

CLI Commands

Packages can register CLI commands:

cli: {
    'command-name': {
      file: 'path/to/script.js',
      help: 'description of command'
    }
}

These commands become available through the Doh CLI system.

Relationship to Modules

A Module in Doh is an extension of a Package that adds a callback function:

Doh.Module('module_name', ['dependency1', 'dependency2'], function() {
    // Module code here
});

Modules inherit all the capabilities of Packages but add execution logic:

  • They are registered in the same Doh.Packages namespace as plain packages
  • They use the same dependency resolution system
  • They support the same load decorators and syntax
  • They integrate with the Auto-Packager in the same way

For complete details, see the Module documentation.

Integration with Auto-Packager

The Auto-Packager provides AST-based analysis:

  1. Discovery: Uses filesystem scanning for .doh.yaml files and AST parsing for JavaScript Doh.Package() declarations
  2. Parsing: Extracts object literals, computed expressions, and conditional statements from package definitions
  3. Dependency Graph Analysis: Resolves dependency trees including environment branches and conditional loading
  4. Conflict Detection: Identifies cyclic dependencies and naming conflicts across the entire codebase
  5. Manifest Generation: Creates manifests with structural metadata, parameter signatures, and cross-references
  6. Dohball Creation: Packages code for distribution with content-based versioning

This integration enables:

  • Package management with compiler-grade analysis
  • Automatic dependency resolution with static analysis
  • Build-time package validation through AST inspection
  • Runtime package optimization using pre-computed manifests

For complete details, see the Auto-Packager documentation.

Package Resolution and Loading

When a package is requested (either directly or as a dependency):

  1. The Doh runtime looks up the package definition in the manifest
  2. The load block is processed, and dependencies are resolved
  3. Each dependency is loaded according to its type and decorators
  4. Once all dependencies are satisfied, the package is considered loaded
  5. If the package has an associated module callback (i.e., it was defined via Doh.Module()), that callback is executed

This process is handled by the Doh runtime, making dependency management seamless.

Real-World Package Examples

1. Cross-Platform Feature with Shared Code

// Main package with environment branching
Doh.Package('user_management', {
  load: [
    // Common functionality
    'user_management_common',
    
    // Environment-specific implementations
    'browser?? user_management_ui',
    'nodejs?? user_management_server'
  ]
});

This pattern creates a unified namespace for a feature while keeping environment-specific implementations separate.

2. Feature Flags and Configuration

// Define configuration in pod.yaml or via Doh.Pod()
Doh.Pod('feature_system', {
  features: {
    advanced: true,
    experimental: false
  }
});

// Package that uses configuration-based loading
Doh.Package('feature_system', {
  load: [
    // Core functionality
    'feature_core',
    
    // Conditional features based on configuration
    'config.features.advanced?? advanced_features',
    'config.features.experimental?? experimental_features',
    
    // Combine with environment conditions
    'browser&&config.features.advanced?? advanced_ui'
  ]
});

This approach allows features to be enabled or disabled through configuration without code changes.

3. NPM Integration Package

// Package that wraps an NPM module for use in Doh
Doh.Package('chart_library', {
  // Installation requirements
  install: {
    'npm:chart.js': '^3.0.0'
  },
  
  // Load the module with environment branching
  load: [
    // Browser: Load from ESM.sh
    'browser?? global import Chart from "chart.js"',
    
    // Node.js: Direct import
    'nodejs?? import Chart from "chart.js"'
  ]
});

This pattern makes external NPM packages available as Doh packages with proper environment handling.

Dohballs vs. Packages vs. Modules

It's important to distinguish between Packages and Dohballs:

  • Dohballs: Physical, versioned distribution units containing entire folders containing packages
  • Packages: Abstract definitions of names, dependencies, and metadata
  • Modules: Packages with a callback function that accepts scope parameters (registered in the same Doh.Packages registry)

A Dohball might contain multiple Package definitions, but the Package itself is not the collection of files—it's the abstract definition that is bundled into Dohballs.

Multiple Packages in a Single File

Doh encourages defining multiple packages in the same file:

// Package definition
Doh.Package('Package1', {
    load: ['dep1', 'dep2']
});

// Module definition (a package with a function)
Doh.Module('Module1', ['Package1'], function() {
    // Module code
});

// Another package definition
Doh.Package('Package2', {
    load: ['dep4', 'dep5', 'Module1']
});

This approach allows for organized, modular code without necessitating separate files for each package or module.

Processing Flow for Packages

When the Auto-Packager processes package definitions:

  1. Discovery Phase: Finds package definitions in .doh.yaml files, Doh.Package() calls, and Doh.Module() calls
  2. Parsing Phase: Extracts dependencies and other package metadata
  3. Resolution Phase: Builds the dependency graph and resolves environment-specific branches
  4. Validation Phase: Checks for cyclic dependencies and conflicts
  5. Manifest Phase: Generates package manifests for runtime use
  6. Dohball Phase: Optionally packages the code for distribution (bake)

During runtime loading, the Doh Load System handles:

  1. Request Phase: A package is requested via Doh.load() or as a dependency in Doh.Module() / Doh.Package()
  2. Lookup Phase: The package definition is found in the manifest
  3. Conditions Phase: Conditions are evaluated to determine which dependencies to load
  4. Loading Phase: Dependencies are loaded according to their type and decorators
  5. Completion Phase: The package is marked as loaded when all dependencies are satisfied

Best Practices

  1. Granular Packages: Create focused packages with clearly defined purposes
  2. Thoughtful Dependencies: Only include necessary dependencies in load blocks
  3. Conditional Branching: Use conditional decorators to separate environment-specific code
  4. Bifurcation Pattern: Follow the common-plus-specifics pattern for cross-platform features
  5. Descriptive Names: Use meaningful, consistent naming conventions
  6. YAML for Configuration: Use .doh.yaml for declarative package definitions. (Packages can only be defined once and cannot be used to extend or redefine modules)
  7. JavaScript for Logic: Use Doh.Module() when packages need associated code (Modules count as packages and cannot be used to extend or redefine packages)
  8. Smart Organization: Group related packages in the same file or directory
  9. Dependency Management: Leverage load decorators for optimal loading performance
  10. Configuration Awareness: Use pod configuration to control feature loading

Troubleshooting

  1. Package Not Found

    • Check the package name and path
    • Ensure it's defined correctly with Doh.Package() or in a .doh.yaml file
    • Run doh update to refresh manifests
  2. Dependency Not Loading

    • Verify the dependency exists and is spelled correctly
    • Check conditional decorators to ensure they match the current environment
    • Use Doh.loadstatus() to see what's loaded and what's missing
  3. Environment-Specific Issues

    • Verify conditional decorators (browser??, nodejs??) are correctly applied
    • Test in each target environment to confirm behavior
    • Use environment detection functions (IsBrowser(), IsNode()) to handle edge cases

Comparison to Traditional Package Systems

Unlike traditional package systems (npm, yarn, etc.), Doh Packages:

  1. Focus on Load-Time Behavior: They primarily define what should be loaded and how
  2. Are Abstract Definitions: They don't represent physical file structures
  3. Support Loading Controls: They offer control over when and how dependencies load
  4. Enable Cross-Environment Compatibility: They work consistently across Node.js, browsers, Deno, and Bun
  5. Integrate with the Framework: They connect directly to the runtime and Auto-Packager

Related Documentation

  • Load System - Understanding the dependency loading mechanism
  • Modules - Extending packages with runtime functions
  • Auto-Packager - How packages are discovered and processed
  • Dohballs - Packaging and distribution of bundles
  • Pods - Configuration system for packages
Last updated: 10/22/2025