Ingest Ingest Composable Server/less IO Framework
GitHub

Reference Layer

Loader

Loader keeps plugin and configuration discovery out of the main application setup flow.

Class ReferenceMethods, Properties, Examples

On This Page

Structural Map

  1. 1. ConfigLoader
  2. 1.1. Properties
  3. 1.2. Methods
  4. 1.3. Configuration Options
  5. 2. PluginLoader
  6. 2.1. Properties
  7. 2.2. Methods
  8. 2.3. Plugin Configuration
  9. 3. Plugin Resolution
  10. 3.1. Local Plugins
  11. 3.2. NPM Packages
  12. 3.3. Nested Plugin Configurations
  13. 4. Bootstrap Process
  14. 4.1. Bootstrap Steps
  15. 4.2. Plugin Loading Order
  16. 5. Error Handling
  17. 5.1. Error Types
  18. 5.2. Graceful Degradation
  19. 6. Integration with Server
  20. 6.1. Automatic Bootstrap
  21. 6.2. Custom Plugin Loading
  22. 7. Best Practices
  23. 7.1. Plugin Organization
  24. 7.2. Error Resilience
  25. 7.3. Development vs Production
  26. 7.4. Plugin Dependencies
  27. 8. Examples
  28. 8.1. Basic Configuration Loading
  29. 8.2. Plugin System Implementation
  30. 8.3. Dynamic Plugin Discovery
  31. 8.4. Conditional Plugin Loading

Loader keeps plugin and configuration discovery out of the main application setup flow.

Configuration and plugin loading utilities for the Ingest framework, providing file resolution and dynamic import capabilities with support for multiple file formats.

TypeScript
import { ConfigLoader, PluginLoader } from '@stackpress/ingest';

const configLoader = new ConfigLoader({
  key: 'plugins',
  extnames: ['.js', '.json', '.ts']
});

const pluginLoader = new PluginLoader({
  cwd: process.cwd(),
  plugins: ['./src/plugin.js', '@my/plugin']
});
  1. ConfigLoader
  2. PluginLoader
  3. Plugin Resolution
  4. Bootstrap Process
  5. Error Handling
  6. Integration with Server
  7. Best Practices
  8. Examples

1. ConfigLoader

File loader specialized for configuration files with support for multiple file extensions and key extraction.

1.1. Properties

The following properties are available when instantiating a ConfigLoader.

PropertyTypeDescription
cwdstringCurrent working directory (inherited)
fsFileSystemFilesystem interface being used (inherited)

1.2. Methods

The following methods are available when instantiating a ConfigLoader.

1.2.1. Loading Configuration Files

The following example shows how to load configuration files with fallback defaults.

TypeScript
const config = await loader.load('./config.json', { 
  default: 'value' 
});

// Load with automatic key extraction
const plugins = await loader.load('./package.json'); 
// Extracts the 'plugins' key from package.json

Parameters

ParameterTypeDescription
filepathstringPath to the configuration file
defaultsTDefault value if file cannot be loaded (optional)

Returns

A promise that resolves to the loaded configuration data or defaults.

1.2.2. Resolving Configuration Files

The following example shows how to resolve configuration files with multiple extension support.

TypeScript
const resolved = await loader.resolveFile('./config');
// Tries: ./config/plugins.js, ./config/plugins.json, 
//        ./config/package.json, ./config/plugins.ts, 
//        ./config.js, ./config.json, ./config.ts

Parameters

ParameterTypeDescription
filepathstringPath to resolve (default: current working directory)

Returns

A promise that resolves to the resolved file path or null if not found.

1.3. Configuration Options

ConfigLoader accepts the following options during instantiation for customized file loading behavior.

TypeScript
const loader = new ConfigLoader({
  cwd: '/custom/working/directory',
  fs: customFileSystem,
  key: 'myPlugins',
  extnames: ['/custom.js', '.custom.json']
});
OptionTypeDefaultDescription
cwdstringprocess.cwd()Working directory for file resolution
fsFileSystemNodeFSFilesystem implementation
keystring'plugins'Key to extract from loaded objects
extnamesstring[]See belowFile extensions to try

Default Extensions

TypeScript
[
  '/plugins.js',    // Directory-specific plugins file
  '/plugins.json',  // Directory-specific plugins config
  '/package.json',  // Package configuration
  '/plugins.ts',    // TypeScript plugins file
  '.js',           // JavaScript file
  '.json',         // JSON file
  '.ts'            // TypeScript file
]

2. PluginLoader

Extended configuration loader specialized for plugin management and bootstrapping with automatic dependency resolution.

2.1. Properties

The following properties are available when instantiating a PluginLoader.

PropertyTypeDescription
cwdstringCurrent working directory (inherited)
fsFileSystemFilesystem interface being used (inherited)

2.2. Methods

The following methods are available when instantiating a PluginLoader.

2.2.1. Bootstrapping Plugins

The following example shows how to bootstrap all configured plugins with custom loading logic.

TypeScript
await pluginLoader.bootstrap(async (name, plugin) => {
  console.log(`Loading plugin: ${name}`);
  
  if (typeof plugin === 'function') {
    // Plugin is a function, call it with context
    await plugin(server);
  } else {
    // Plugin is a configuration object
    server.configure(plugin);
  }
});

Parameters

ParameterTypeDescription
loader(name: string, plugin: unknown) => Promise<void>Function to handle each loaded plugin

Returns

The PluginLoader instance to allow method chaining.

2.2.2. Getting Plugin List

The following example shows how to get the list of configured plugins for inspection.

TypeScript
const plugins = await pluginLoader.plugins();
// Returns: ['./src/plugin.js', '@my/plugin', 'local-plugin']

Returns

A promise that resolves to an array of plugin paths.

2.3. Plugin Configuration

PluginLoader accepts the following options during instantiation for flexible plugin management.

TypeScript
const loader = new PluginLoader({
  cwd: process.cwd(),
  plugins: ['./plugin1.js', '@scope/plugin2'],
  modules: '/path/to/node_modules'
});
OptionTypeDescription
cwdstringWorking directory for file resolution
fsFileSystemFilesystem implementation
pluginsstring[]Array of plugin paths (optional)
modulesstringPath to node_modules directory (optional)
keystringKey to extract from configuration files
extnamesstring[]File extensions to try

3. Plugin Resolution

PluginLoader supports various plugin path formats for maximum flexibility in plugin organization.

3.1. Local Plugins

Load plugins from local files and directories within your project.

TypeScript
const plugins = [
  './src/plugins/auth.js',      // Relative path
  '/absolute/path/plugin.js',   // Absolute path
  './plugins'                   // Directory with plugins config
];

3.2. NPM Packages

Load plugins from installed NPM packages with automatic resolution.

TypeScript
const plugins = [
  '@my-org/auth-plugin',        // Scoped package
  'express-session',            // Regular package
  'local-plugin/dist/index.js'  // Package with specific entry
];

3.3. Nested Plugin Configurations

Support for nested plugin configurations allows for modular plugin organization.

TypeScript
// plugins.json
{
  "plugins": [
    "./auth-plugin",
    {
      "plugins": ["./nested-plugin1", "./nested-plugin2"]
    }
  ]
}

4. Bootstrap Process

The bootstrap process follows a systematic approach to ensure reliable plugin loading and initialization.

4.1. Bootstrap Steps

The bootstrap process executes the following steps in order:

  1. Load Plugin List: Resolves the plugins array from configuration
  2. Process Each Plugin: Iterates through each plugin path
  3. Handle Nested Configs: Recursively processes nested plugin arrays
  4. Resolve Plugin Path: Converts relative paths to absolute paths
  5. Extract Plugin Name: Generates a clean name for the plugin
  6. Call Loader Function: Invokes the provided loader with name and plugin

4.2. Plugin Loading Order

Plugins are loaded in the order they appear in the configuration, allowing for dependency management.

TypeScript
const plugins = [
  './plugins/database',  // Load database connection first
  './plugins/auth',      // Then authentication (depends on database)
  './plugins/api'        // Finally API routes (depends on auth)
];

5. Error Handling

PluginLoader provides comprehensive error handling for robust plugin management.

5.1. Error Types

Common error scenarios include missing files, invalid configurations, and plugin initialization failures.

TypeScript
try {
  await pluginLoader.bootstrap(loader);
} catch (error) {
  // Handles missing files, invalid configurations, etc.
  console.error('Plugin loading failed:', error.message);
}

5.2. Graceful Degradation

Implement error handling that allows the application to continue running even if some plugins fail.

TypeScript
await pluginLoader.bootstrap(async (name, plugin) => {
  try {
    await loadPlugin(name, plugin);
    console.log(`✓ Loaded plugin: ${name}`);
  } catch (error) {
    console.error(`✗ Failed to load plugin ${name}:`, error.message);
    // Continue loading other plugins
  }
});

6. Integration with Server

PluginLoader integrates seamlessly with the Server class for automatic plugin loading and configuration.

6.1. Automatic Bootstrap

Use the server's built-in bootstrap method for standard plugin loading.

TypeScript
import { server } from '@stackpress/ingest/http';

const app = server();

// Bootstrap plugins from package.json
await app.bootstrap();

6.2. Custom Plugin Loading

Use a custom PluginLoader for advanced plugin management scenarios.

TypeScript
// Or use custom plugin loader
const pluginLoader = new PluginLoader({
  plugins: ['./custom-plugin.js']
});

await pluginLoader.bootstrap(async (name, plugin) => {
  if (typeof plugin === 'function') {
    plugin(app);
  }
});

7. Best Practices

The following best practices ensure reliable and maintainable plugin management.

7.1. Plugin Organization

Organize plugins by functionality for better maintainability and understanding.

TypeScript
// Organize plugins by functionality
const plugins = [
  './plugins/auth',      // Authentication
  './plugins/database',  // Database connection
  './plugins/logging',   // Logging setup
  '@company/shared'      // Shared company plugins
];

7.2. Error Resilience

TypeScript
await pluginLoader.bootstrap(async (name, plugin) => {
  try {
    await loadPlugin(name, plugin);
    console.log(`✓ Loaded plugin: ${name}`);
  } catch (error) {
    console.error(`✗ Failed to load plugin ${name}:`, error.message);
    // Continue loading other plugins
  }
});

7.3. Development vs Production

TypeScript
const isDev = process.env.NODE_ENV === 'development';

const plugins = [
  './plugins/core',
  ...(isDev ? ['./plugins/dev-tools'] : []),
  ...(process.env.ENABLE_ANALYTICS ? ['./plugins/analytics'] : [])
];

7.4. Plugin Dependencies

Ensure plugins are loaded in the correct order to handle dependencies properly.

TypeScript
const plugins = [
  './plugins/config',     // Load configuration first
  './plugins/database',   // Database depends on config
  './plugins/auth',       // Auth depends on database
  './plugins/routes'      // Routes depend on auth
];

8. Examples

The following examples demonstrate common Loader usage patterns for real-world applications.

8.1. Basic Configuration Loading

TypeScript
import { ConfigLoader } from '@stackpress/ingest';

const configLoader = new ConfigLoader({
  key: 'database',
  extnames: ['.json', '.js', '.env.js']
});

// Load database configuration
const dbConfig = await configLoader.load('./config/database', {
  host: 'localhost',
  port: 5432,
  database: 'myapp'
});

console.log('Database config:', dbConfig);

8.2. Plugin System Implementation

TypeScript
import { PluginLoader } from '@stackpress/ingest';
import { server } from '@stackpress/ingest/http';

const app = server();

const pluginLoader = new PluginLoader({
  cwd: process.cwd(),
  plugins: [
    './plugins/cors',
    './plugins/auth',
    './plugins/api',
    '@company/monitoring'
  ]
});

await pluginLoader.bootstrap(async (name, plugin) => {
  console.log(`Loading plugin: ${name}`);
  
  if (typeof plugin === 'function') {
    // Function plugin - call with server instance
    await plugin(app);
  } else if (plugin && typeof plugin === 'object') {
    // Configuration plugin - apply settings
    if (plugin.routes) {
      app.use(plugin.routes);
    }
    if (plugin.middleware) {
      plugin.middleware.forEach((mw: any) => app.use(mw));
    }
  }
  
  console.log(`✓ Plugin ${name} loaded successfully`);
});

console.log('All plugins loaded, starting server...');
app.listen(3000);

8.3. Dynamic Plugin Discovery

TypeScript
import { PluginLoader } from '@stackpress/ingest';
import { readdir } from 'fs/promises';
import { join } from 'path';

async function discoverPlugins(pluginsDir: string) {
  const entries = await readdir(pluginsDir, { withFileTypes: true });
  const plugins = [];
  
  for (const entry of entries) {
    if (entry.isDirectory()) {
      const pluginPath = join(pluginsDir, entry.name);
      plugins.push(pluginPath);
    }
  }
  
  return plugins;
}

// Discover and load plugins dynamically
const discoveredPlugins = await discoverPlugins('./src/plugins');

const pluginLoader = new PluginLoader({
  plugins: [
    './plugins/core',  // Always load core plugins first
    ...discoveredPlugins
  ]
});

await pluginLoader.bootstrap(async (name, plugin) => {
  try {
    if (typeof plugin === 'function') {
      await plugin(app);
    }
    console.log(`✓ Loaded plugin: ${name}`);
  } catch (error) {
    console.error(`✗ Failed to load plugin ${name}:`, error);
  }
});

8.4. Conditional Plugin Loading

TypeScript
import { PluginLoader } from '@stackpress/ingest';

const environment = process.env.NODE_ENV || 'development';
const features = process.env.FEATURES?.split(',') || [];

// Build plugin list based on environment and features
const plugins = [
  './plugins/core',
  './plugins/database'
];

// Environment-specific plugins
if (environment === 'development') {
  plugins.push('./plugins/dev-tools', './plugins/hot-reload');
} else if (environment === 'production') {
  plugins.push('./plugins/monitoring', './plugins/performance');
}

// Feature-specific plugins
if (features.includes('auth')) {
  plugins.push('./plugins/auth');
}

if (features.includes('analytics')) {
  plugins.push('./plugins/analytics');
}

const pluginLoader = new PluginLoader({ plugins });

await pluginLoader.bootstrap(async (name, plugin) => {
  console.log(`Loading ${name} for ${environment} environment`);
  
  if (typeof plugin === 'function') {
    await plugin(app, { environment, features });
  }
});