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

Packages

In Doh, Packages are first-class loadable units that are identical in structure to Modules but without a callback. Practically, a Package is a Module without executable code: it declares a name, a load array (its dependencies), and optional metadata (install, pod, cli). Because there’s no callback, a Package can be defined in JSON/YAML (.doh.yaml) or via Doh.Package('name', { ... }).

Key points:

  • Packages participate in the dependency graph as dependents and as dependencies.
  • A Package’s load array is its dependency list; it is processed by the Load System exactly like a Module’s.
  • Packages live in the same Doh.Packages registry and share the same namespace as Modules; a Module is simply a Package with a callback.

This separation lets you model structure and relationships declaratively, while Modules add behavior on top of that structure when needed.

Packages and SPR layering

Packages contribute to the layered scope used by Scoped Parameter Resolution (SPR):

  • Packages and Modules build 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 means you can 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.

Resolver (rule of four), summarized for Packages context:

  1. Current module’s producers (most specific)
  2. Ancestor modules exports then producers, in Resolution Order
  3. Doh.Globals[name] (shared state collection)
  4. globalThis[name]

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

The Central Role of Packages

Packages form the backbone of Doh’s architecture:

  • They define the dependency graph of your application as first-class dependents and dependencies
  • Their load arrays are processed identically to Modules, including decorators and conditions
  • They integrate with the Auto-Packager for manifest generation
  • They are used to reference and use distributable units (Dohballs)
  • A Module is a Package with a callback; both share the same registry and namespace

Package Definition Methods

*.doh.yaml Files

The most declarative way to define packages is through YAML configuration files. The Auto-Packager discovers these files and converts them into package entries (no callback). 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 compile-time declaration consumed by the Auto-Packager when scanning .js files. Conceptually, packages are just JSON (name, load, install, pod, cli, etc.). This call contributes that metadata to manifests; 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 automatically:

  1. Discovers package definitions (in .doh.yaml files and JavaScript files)
  2. Resolves the dependency graph
  3. Detects conflicts and cyclic dependencies
  4. Generates package manifests
  5. Creates Dohballs for distribution

This integration enables:

  • Zero-configuration package management
  • Automatic dependency resolution
  • Build-time package validation
  • Runtime package optimization

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 transparently 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 Rich Loading Controls: They offer fine-grained control over when and how dependencies load
  4. Enable Cross-Environment Compatibility: They work consistently across Node.js, browsers, Deno, and Bun
  5. Integrate Deeply 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: 8/23/2025