Configuration and plugin loading utilities for the Ingest framework, providing file resolution and dynamic import capabilities with support for multiple file formats.
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']
});- ConfigLoader
- PluginLoader
- Plugin Resolution
- Bootstrap Process
- Error Handling
- Integration with Server
- Best Practices
- Examples
File loader specialized for configuration files with support for multiple file extensions and key extraction.
The following properties are available when instantiating a ConfigLoader.
| Property | Type | Description |
|---|---|---|
cwd |
string |
Current working directory (inherited) |
fs |
FileSystem |
Filesystem interface being used (inherited) |
The following methods are available when instantiating a ConfigLoader.
The following example shows how to load configuration files with fallback defaults.
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.jsonParameters
| Parameter | Type | Description |
|---|---|---|
filepath |
string |
Path to the configuration file |
defaults |
T |
Default value if file cannot be loaded (optional) |
Returns
A promise that resolves to the loaded configuration data or defaults.
The following example shows how to resolve configuration files with multiple extension support.
const resolved = await loader.resolveFile('./config');
// Tries: ./config/plugins.js, ./config/plugins.json,
// ./config/package.json, ./config/plugins.ts,
// ./config.js, ./config.json, ./config.tsParameters
| Parameter | Type | Description |
|---|---|---|
filepath |
string |
Path to resolve (default: current working directory) |
Returns
A promise that resolves to the resolved file path or null if not found.
ConfigLoader accepts the following options during instantiation for customized file loading behavior.
const loader = new ConfigLoader({
cwd: '/custom/working/directory',
fs: customFileSystem,
key: 'myPlugins',
extnames: ['/custom.js', '.custom.json']
});| Option | Type | Default | Description |
|---|---|---|---|
cwd |
string |
process.cwd() |
Working directory for file resolution |
fs |
FileSystem |
NodeFS |
Filesystem implementation |
key |
string |
'plugins' |
Key to extract from loaded objects |
extnames |
string[] |
See below | File extensions to try |
Default Extensions
[
'/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
]Extended configuration loader specialized for plugin management and bootstrapping with automatic dependency resolution.
The following properties are available when instantiating a PluginLoader.
| Property | Type | Description |
|---|---|---|
cwd |
string |
Current working directory (inherited) |
fs |
FileSystem |
Filesystem interface being used (inherited) |
The following methods are available when instantiating a PluginLoader.
The following example shows how to bootstrap all configured plugins with custom loading logic.
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
| Parameter | Type | Description |
|---|---|---|
loader |
(name: string, plugin: unknown) => Promise<void> |
Function to handle each loaded plugin |
Returns
The PluginLoader instance to allow method chaining.
The following example shows how to get the list of configured plugins for inspection.
const plugins = await pluginLoader.plugins();
// Returns: ['./src/plugin.js', '@my/plugin', 'local-plugin']Returns
A promise that resolves to an array of plugin paths.
PluginLoader accepts the following options during instantiation for flexible plugin management.
const loader = new PluginLoader({
cwd: process.cwd(),
plugins: ['./plugin1.js', '@scope/plugin2'],
modules: '/path/to/node_modules'
});| Option | Type | Description |
|---|---|---|
cwd |
string |
Working directory for file resolution |
fs |
FileSystem |
Filesystem implementation |
plugins |
string[] |
Array of plugin paths (optional) |
modules |
string |
Path to node_modules directory (optional) |
key |
string |
Key to extract from configuration files |
extnames |
string[] |
File extensions to try |
PluginLoader supports various plugin path formats for maximum flexibility in plugin organization.
Load plugins from local files and directories within your project.
const plugins = [
'./src/plugins/auth.js', // Relative path
'/absolute/path/plugin.js', // Absolute path
'./plugins' // Directory with plugins config
];Load plugins from installed NPM packages with automatic resolution.
const plugins = [
'@my-org/auth-plugin', // Scoped package
'express-session', // Regular package
'local-plugin/dist/index.js' // Package with specific entry
];Support for nested plugin configurations allows for modular plugin organization.
// plugins.json
{
"plugins": [
"./auth-plugin",
{
"plugins": ["./nested-plugin1", "./nested-plugin2"]
}
]
}The bootstrap process follows a systematic approach to ensure reliable plugin loading and initialization.
The bootstrap process executes the following steps in order:
- Load Plugin List: Resolves the plugins array from configuration
- Process Each Plugin: Iterates through each plugin path
- Handle Nested Configs: Recursively processes nested plugin arrays
- Resolve Plugin Path: Converts relative paths to absolute paths
- Extract Plugin Name: Generates a clean name for the plugin
- Call Loader Function: Invokes the provided loader with name and plugin
Plugins are loaded in the order they appear in the configuration, allowing for dependency management.
const plugins = [
'./plugins/database', // Load database connection first
'./plugins/auth', // Then authentication (depends on database)
'./plugins/api' // Finally API routes (depends on auth)
];PluginLoader provides comprehensive error handling for robust plugin management.
Common error scenarios include missing files, invalid configurations, and plugin initialization failures.
try {
await pluginLoader.bootstrap(loader);
} catch (error) {
// Handles missing files, invalid configurations, etc.
console.error('Plugin loading failed:', error.message);
}Implement error handling that allows the application to continue running even if some plugins fail.
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
}
});PluginLoader integrates seamlessly with the Server class for automatic plugin loading and configuration.
Use the server's built-in bootstrap method for standard plugin loading.
import { server } from '@stackpress/ingest/http';
const app = server();
// Bootstrap plugins from package.json
await app.bootstrap();Use a custom PluginLoader for advanced plugin management scenarios.
// 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);
}
});The following best practices ensure reliable and maintainable plugin management.
Organize plugins by functionality for better maintainability and understanding.
// Organize plugins by functionality
const plugins = [
'./plugins/auth', // Authentication
'./plugins/database', // Database connection
'./plugins/logging', // Logging setup
'@company/shared' // Shared company plugins
];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
}
});const isDev = process.env.NODE_ENV === 'development';
const plugins = [
'./plugins/core',
...(isDev ? ['./plugins/dev-tools'] : []),
...(process.env.ENABLE_ANALYTICS ? ['./plugins/analytics'] : [])
];Ensure plugins are loaded in the correct order to handle dependencies properly.
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
];The following examples demonstrate common Loader usage patterns for real-world applications.
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);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);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);
}
});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 });
}
});