npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ya-modbus/driver-loader

v0.12.2

Published

Dynamic driver loader for ya-modbus device drivers

Readme

@ya-modbus/driver-loader

Dynamic driver loader for ya-modbus device drivers.

Installation

npm install @ya-modbus/driver-loader

Usage

Auto-detect driver from current directory

import { loadDriver } from '@ya-modbus/driver-loader'

const driver = await loadDriver({})

The current directory must hold a package with the ya-modbus-driver keyword. The loader imports the file that Node loads for import of that package: exports["."] (conditions node, import, module-sync, default), else main, else index.js. The entry must name the file with its extension, and Node's --conditions flag is not used.

The driver must be built, and built again after each change to its source. If the entry file is missing (no dist directory), or it is TypeScript source that Node cannot run, loadDriver throws a DriverNotBuiltError: build the driver first (npm run build), or run with Node's built-in type stripping (Node 22.18+ or 24).

Load specific driver package

import { loadDriver } from '@ya-modbus/driver-loader'

const driver = await loadDriver({ driverPackage: '@ya-modbus/driver-xymd1' })

Load a driver by short name

A plain lowercase name such as xymd1 is a short name. The loader tries these packages in order and uses the first one that is installed:

  1. @ya-modbus/driver-<name>
  2. ya-modbus-driver-<name>
  3. <name>, the name as given
// Loads @ya-modbus/driver-xymd1
const driver = await loadDriver({ driverPackage: 'xymd1' })

Anything else (scoped names, ya-modbus-driver-* names, paths) is used as given. If a candidate package is installed but fails to load, the search stops there and the DriverNotFoundError message shows the load error (the original error is its cause), so a later candidate does not hide it. If no candidate is installed, the message lists every package that was tried, and packageName is the preferred one (@ya-modbus/driver-<name>).

Error Handling

The driver-loader exports custom error classes for type-safe error handling:

import {
  loadDriver,
  ValidationError,
  DriverNotFoundError,
  DriverNotBuiltError,
  PackageJsonError,
} from '@ya-modbus/driver-loader'

try {
  const driver = await loadDriver({ driverPackage: 'my-driver' })
} catch (error) {
  // Type-safe error handling with instanceof
  if (error instanceof DriverNotFoundError) {
    if (error.cause) {
      // The package is installed but failed to load
      console.error(`Failed to load ${error.packageName}:`, error.cause)
    } else {
      console.error(`Package not found: ${error.packageName}`)
      console.error(`Install with: npm install ${error.packageName}`)
    }
  } else if (error instanceof DriverNotBuiltError) {
    console.error(`${error.packageName} is not built: run npm run build`)
  } else if (error instanceof ValidationError) {
    console.error(`Validation failed for field: ${error.field}`)
    console.error(`Error: ${error.message}`)
  } else if (error instanceof PackageJsonError) {
    console.error('package.json issue:', error.message)
  } else {
    console.error('Unexpected error:', error)
  }
}

Error Types:

  • ValidationError: Driver configuration validation failed, or a connection option is not in the driver's SUPPORTED_CONFIG

    • field?: string - The configuration field that failed validation
    • value?: unknown - The rejected value, when it is known
    • validValues?: readonly unknown[] - The values that the driver supports, when they are known
    • Example: Invalid DEFAULT_CONFIG, missing createDriver function, parity none for a driver that supports only odd
  • DriverNotFoundError: Driver package not found or cannot be loaded

    • packageName: string - The package that could not be found or loaded. For a package that is not installed this is the package to install
    • cause?: unknown - Set when the package is installed but fails to load: the original error
    • Example: Package not installed, wrong package name, syntax error in the driver
  • DriverNotBuiltError: Driver package is installed but not built

    • packageName: string - The package that cannot be loaded
    • cause?: unknown - The error Node reported
    • Example: Missing dist directory, TypeScript source that Node cannot run
  • PackageJsonError: package.json not found or invalid

    • Example: Missing package.json, invalid JSON, missing ya-modbus-driver keyword, exports without an entry for import

Custom Logging

You can provide a custom logger to control warning and debug output:

import { loadDriver, type Logger } from '@ya-modbus/driver-loader'

const logger: Logger = {
  warn: (msg) => myLogger.warning('[DRIVER]', msg),
  debug: (msg) => myLogger.debug('[DRIVER]', msg), // Optional
}

const driver = await loadDriver({
  driverPackage: 'my-driver',
  logger,
})

API

loadDriver(options?: LoadDriverOptions): Promise<LoadedDriver>

Loads a ya-modbus driver package and validates its exports.

Options:

  • driverPackage (optional): Name of the driver package to load, or a short name such as xymd1. If omitted, auto-detects from current directory's package.json.

Returns:

  • LoadedDriver object containing:
    • createDriver: Factory function to create driver instances (required)
    • devices: Device registry for multi-device drivers (optional)
    • defaultConfig: Default configuration for serial or TCP (optional)
    • supportedConfig: Configuration constraints (optional)

Throws:

  • DriverNotFoundError if driver package is not found or fails to load
  • DriverNotBuiltError if the driver is not built
  • Error if driver exports are invalid
  • Error if configuration validation fails

clearDriverCache(): void

Clears the driver cache. Useful for testing or when you need to reload drivers.

When to use:

  • In tests: Clear cache between test cases to ensure isolation
  • After package updates: Force reload of updated driver packages
  • During development: Reload drivers after making changes
import { clearDriverCache } from '@ya-modbus/driver-loader'

// In test setup
beforeEach(() => {
  clearDriverCache() // Ensure each test starts with clean cache
})

// After updating a driver package
await updateDriver('my-driver')
clearDriverCache() // Force reload on next loadDriver call

getDriverCacheStats(): DriverCacheStats

Returns cache statistics including hits, misses, and current cache size.

Returns:

  • DriverCacheStats object containing:
    • hits: Number of cache hits
    • misses: Number of cache misses
    • size: Number of cached drivers

Cache Behavior:

  • Automatic caching: Drivers are cached by package name after first load
  • Cache key: Name of the package that was loaded (e.g., '@ya-modbus/driver-xymd1'). A short name and the full name share one entry. If two candidates of a short name are installed, a candidate that was loaded earlier in the process is returned
  • Cache lifetime: Persists for the lifetime of the Node.js process
  • Auto-detect mode: Even auto-detected drivers are cached by their package name
  • No cache on errors: Failed loads are not cached, allowing retry

Example:

import { loadDriver, getDriverCacheStats } from '@ya-modbus/driver-loader'

// First load - cache miss
await loadDriver({ driverPackage: 'my-driver' })
console.log(getDriverCacheStats()) // { hits: 0, misses: 1, size: 1 }

// Second load - cache hit
await loadDriver({ driverPackage: 'my-driver' })
console.log(getDriverCacheStats()) // { hits: 1, misses: 1, size: 1 }

getCallerPackageInfo(): PackageInfo

Returns the version and description from the package.json of the package that contains the calling module. CLIs use it to report their own version without hardcoding it.

It finds the caller from the call stack, so it works the same in the ESM and CommonJS builds of a package.

It does not work when the caller and driver-loader are bundled into one file, because both then appear as the same file on the call stack.

Returns:

  • PackageInfo object containing:
    • version: Version from the closest package.json that has one
    • description: Description from that package.json (empty string if missing)

Throws:

  • Error if the calling module cannot be determined
  • Error if no package.json with a version is found above the calling module
import { getCallerPackageInfo } from '@ya-modbus/driver-loader'

program.version(getCallerPackageInfo().version)

isValidDriverPackageName(name: unknown): boolean

Checks a driver package name against the shared naming rule for applications that read the driver name from a configuration, such as the MQTT bridge. Allowed forms:

  • @ya-modbus/driver-<name>
  • ya-modbus-driver-<name>
  • @<scope>/ya-modbus-driver-<name>

<name> and <scope> use only lowercase letters, numbers and hyphens. Paths (../x, /opt/x), sub paths, backslashes and any other package name are rejected. loadDriver does not check this rule. If your tool reads the driver name from a config file, call assertValidDriverPackageName before loadDriver.

assertValidDriverPackageName(name: string): void

Same check as isValidDriverPackageName. Throws a ValidationError (field driverPackage) that shows the rejected name and lists the allowed forms.

DRIVER_PACKAGE_NAME_RULE is the text of the rule ("must be ..."), for applications that build their own error message.

import { assertValidDriverPackageName, loadDriver } from '@ya-modbus/driver-loader'

assertValidDriverPackageName(config.driver)
await loadDriver({ driverPackage: config.driver })

validateSerialOptions(options, driver?: LoadedDriver): void

Checks baudRate, parity, dataBits, stopBits and slaveId against the driver's SUPPORTED_CONFIG. An option that is not set, or that the driver does not limit, passes. A value that the driver does not support throws a ValidationError with field, value, validValues and a message such as Invalid parity 'none'. This driver supports: odd (default: odd).

applyDriverDefaults(options, driver?: LoadedDriver, device?: string)

Returns a copy of options where each missing baudRate, parity, dataBits, stopBits and slaveId is taken from the driver's serial DEFAULT_CONFIG (slaveId from defaultAddress). With device, the device's defaultConfig from DEVICES is used when it has one. Options with a host (TCP), and options for a driver without a serial DEFAULT_CONFIG, are returned unchanged.

getEffectiveDriverMetadata(driver: LoadedDriver, device?: string): LoadedDriver

Returns the driver with defaultConfig and supportedConfig of the selected device from DEVICES, or of the driver when the device has none. Pass the result to validateSerialOptions to check the options of a device.

import {
  applyDriverDefaults,
  getEffectiveDriverMetadata,
  loadDriver,
  validateSerialOptions,
} from '@ya-modbus/driver-loader'

const driver = await loadDriver({ driverPackage: '@ya-modbus/driver-or-we-516' })
const effective = getEffectiveDriverMetadata(driver, device)

validateSerialOptions(options, effective) // the options the user set
const merged = applyDriverDefaults(options, driver, device)
validateSerialOptions(merged, effective) // catches defaults that the driver does not support

Testing Utilities

The package provides testing utilities for applications using driver-loader.

import { createMockDriver, mockSystemDeps } from '@ya-modbus/driver-loader/testing'

// Create a mock driver
const mockDriver = createMockDriver({
  defaultConfig: { baudRate: 9600 },
  devices: { test: { manufacturer: 'Test', model: 'Model' } },
})

// Create mock system dependencies
const deps = mockSystemDeps({
  importModule: jest.fn().mockResolvedValue(mockDriver),
})

// Use with loadDriver in tests
const driver = await loadDriver({ driverPackage: 'test-driver' }, deps)

createMockDriver(options?: MockDriverOptions): LoadedDriver

Creates a mock driver for testing.

Options:

  • createDriver: Custom createDriver implementation (default: jest.fn())
  • defaultConfig: Mock default configuration
  • supportedConfig: Mock supported configuration
  • devices: Mock device registry

mockSystemDeps(options?: MockSystemDepsOptions): SystemDependencies

Creates mock system dependencies for testing.

Options:

  • readFile: Custom readFile implementation
  • importModule: Custom importModule implementation
  • getCwd: Custom getCwd implementation (default: '/mock/cwd')

Troubleshooting

Package Not Found

If you encounter Driver package not found errors:

  1. Verify the package is installed:

    npm list @ya-modbus/driver-<name>
  2. Install the driver package:

    npm install @ya-modbus/driver-<name>
  3. Check the package name:

    • Ensure the package name in your code matches the actual npm package name
    • Driver packages follow the naming rule described under isValidDriverPackageName (for example ya-modbus-driver-<device> or @<scope>/ya-modbus-driver-<device>)

If the message starts with Failed to load driver package, the package is installed but its code failed to load. Read the reason in the message (or error.cause) instead of reinstalling.

Driver Not Built

If you encounter DriverNotBuiltError (message Cannot load driver <name>: ...):

  1. Build the driver:

    npm run build
  2. Check the entry point: exports["."] (or main) in package.json must name the built JavaScript file with its extension, for example ./dist/esm/index.js.

  3. TypeScript source: Node 22.18+ and 24 run .ts files themselves, but only outside node_modules, and only if the source uses erasable TypeScript syntax (no enums) and imports files with their .ts extension. Build the driver otherwise.

Module Resolution Issues

If TypeScript or Node.js can't find @ya-modbus/driver-loader:

  1. Check your package.json:

    npm list @ya-modbus/driver-loader
  2. Reinstall dependencies:

    npm install
  3. TypeScript module resolution:

    • Ensure "moduleResolution": "node" or "moduleResolution": "bundler" in tsconfig.json
    • Check that "types" field isn't excluding driver-loader
  4. For local driver development:

    • The loader imports the entry file from exports or main in package.json
    • Build your driver before testing: npm run build

Validation Failures

If driver validation fails with configuration errors:

  1. ValidationError - Invalid DEFAULT_CONFIG:

    // ❌ Wrong
    export const DEFAULT_CONFIG = { speed: 9600 } // Should be "baudRate"
    
    // ✅ Correct
    export const DEFAULT_CONFIG = { baudRate: 9600 }
  2. ValidationError - Invalid SUPPORTED_CONFIG:

    // ❌ Wrong
    export const SUPPORTED_CONFIG = { baudRates: [9600] } // Should be "validBaudRates"
    
    // ✅ Correct
    export const SUPPORTED_CONFIG = { validBaudRates: [9600, 19200] }
  3. ValidationError - Missing createDriver:

    // ❌ Wrong
    export function makeDriver() {
      ...
    }
    
    // ✅ Correct
    export function createDriver() {
      ...
    }
  4. PackageJsonError - Not a driver package:

    Add the ya-modbus-driver keyword to your package.json:

    {
      "name": "ya-modbus-driver-mydevice",
      "keywords": ["ya-modbus-driver"]
    }
  5. Configuration inconsistency warnings:

    Ensure DEFAULT_CONFIG values are within SUPPORTED_CONFIG constraints:

    // ❌ Inconsistent
    export const DEFAULT_CONFIG = { baudRate: 115200 }
    export const SUPPORTED_CONFIG = { validBaudRates: [9600, 19200] }
    
    // ✅ Consistent
    export const DEFAULT_CONFIG = { baudRate: 9600 }
    export const SUPPORTED_CONFIG = { validBaudRates: [9600, 19200] }

Getting More Help

For additional debugging:

  1. Enable verbose logging:

    const logger = {
      warn: (msg: string) => console.warn('[DRIVER]', msg),
      debug: (msg: string) => console.debug('[DRIVER]', msg),
    }
    
    const driver = await loadDriver({ logger })
  2. Check cache statistics:

    import { getDriverCacheStats } from '@ya-modbus/driver-loader'
    
    console.log(getDriverCacheStats())
  3. Clear the cache:

    import { clearDriverCache } from '@ya-modbus/driver-loader'
    clearDriverCache()

License

GPL-3.0-or-later