A powerful TypeScript dynamic script loading and management library with real-time script updates, caching management, and timestamp persistence.
npm install @ticatec/script-loader


A powerful TypeScript dynamic script loading and management library with real-time script updates, caching management, and timestamp persistence.
- š Dynamic Script Loading - Runtime dynamic loading and updating of JavaScript scripts
- ā° Timestamp Persistence - Automatically save and restore last update timestamp
- š Real-time Updates - Periodically check for script updates and apply them automatically
- š¾ Smart Caching - In-memory caching of script modules with require cache management
- š„ Hot Reload - Support for script hot reloading without application restart
- š§© Singleton Manager - Centralized script management with DynaModuleManager
- š”ļø Error Handling - Comprehensive error handling and logging with log4js
- š Concurrency Protection - Prevent race conditions in duplicate loading operations
- š TypeScript Support - Complete type definitions and interfaces
``bash`
npm install @ticatec/script-loader
Create your custom script loader by extending BaseScriptLoader and implementing the getUpdatedScripts method:
`typescript
import { BaseScriptLoader } from '@ticatec/script-loader';
import type { DynaScript } from '@ticatec/script-loader/lib/DynaModuleManager';
class MyScriptLoader extends BaseScriptLoader {
/**
* Fetch updated scripts from your data source
* @param anchor - Get scripts updated after this timestamp
* @returns Array of DynaScript objects
*/
protected async getUpdatedScripts(anchor: Date): Promise
// Example: Fetch from database
const scripts = await db.query(
'SELECT * FROM scripts WHERE updated_at > ?',
[anchor]
);
return scripts.map(script => ({
keyCode: script.id, // Unique identifier
fileName: script.name, // Filename without extension
active: script.status === 'active',
latestUpdated: script.updated_at,
scriptCode: script.content // JavaScript code
}));
}
}
`
`typescript
import DynaModuleManager from '@ticatec/script-loader';
// Initialize the singleton manager
await DynaModuleManager.initialize(
MyScriptLoader, // Your loader class
'./scripts', // Script storage directory
5000 // Poll interval in milliseconds
);
`
`typescript
import DynaModuleManager from '@ticatec/script-loader';
// Get the initialized manager instance
const manager = DynaModuleManager.getInstance();
// Get a script module by its keyCode
const myScript = manager.get('script-key-123');
if (myScript) {
// Use the loaded module
// If your script exports functions/classes, use them directly
myScript.someFunction();
// Or if it exports default
const result = myScript.default();
}
`
The DynaScript interface defines the structure for script metadata:
`typescript`
interface DynaScript {
keyCode: string; // Unique identifier for the script
fileName: string; // Script filename (without .js extension)
active: boolean; // Whether the script is active
latestUpdated: Date; // Last update timestamp
scriptCode: string; // JavaScript code content
}
#### Static Methods
##### initialize
Initialize the singleton instance of DynaModuleManager. This method must be called before using getInstance().
- Parameters:
- loaderConstructor - Constructor of your BaseScriptLoader subclass...args
- - Arguments to pass to the loader constructor (typically scriptDir and pollIntervalMs)`
- Returns: Promise that resolves to the DynaModuleManager instance
- Example:
typescript`
await DynaModuleManager.initialize(MyScriptLoader, './scripts', 5000);
##### getInstance(): DynaModuleManager
Get the singleton instance of DynaModuleManager. Throws an error if not initialized.
- Returns: The DynaModuleManager instance
- Throws: Error if initialize() hasn't been called yet`
- Example:
typescript`
const manager = DynaModuleManager.getInstance();
#### Instance Methods
##### get(key: string): any | null
Retrieve a loaded script module by its keyCode.
- Parameters: key - Script's unique identifier (keyCode)null
- Returns: The loaded module, or if not found
#### Constructor
`typescript`
protected constructor(
scriptHome: string, // Script storage directory
pollIntervalMs: number // Polling interval in milliseconds
)
#### Abstract Method (Must Implement)
##### getUpdatedScripts(anchor: Date): Promise
Fetch updated scripts from your data source.
- Parameters: anchor - Timestamp to fetch scripts updated after this time
- Returns: Promise resolving to an array of DynaScript objects
#### Public Methods
##### init(): Promise
Initialize the script loader. This is called automatically by DynaModuleManager.
##### getModule(key: string): any | null
Get a loaded module by its keyCode.
- Parameters: key - Script's unique identifiernull
- Returns: The loaded module, or if not found
##### stopWatching(): void
Stop the periodic script update monitoring.
1. Directory Setup: Creates plugins directory under scriptHome.last_update_timestamp
2. Timestamp Loading: Reads file or starts from Unix epoch
3. Initial Load: Fetches and loads all scripts updated since the last timestamp
4. Watch Start: Begins periodic polling for script updates
``
āāāāāāāāāāāāāāāāāāā
ā getUpdatedScripts ā
ā (from your DB/API) ā
āāāāāāāāāā¬āāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāā
ā Process Scripts ā
ā ⢠active=true āāāāŗ Write to file & cache module
ā ⢠active=false āāāāŗ Delete file & clear cache
āāāāāāāāāā¬āāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāā
ā Update Timestamp ā
ā Save to .last_ ā
ā update_timestamp ā
āāāāāāāāāāāāāāāāāāā
``
scriptHome/
āāā .last_update_timestamp # Timestamp persistence file
āāā plugins/ # Script files directory
āāā script1.js
āāā script2.js
āāā ...
- Scripts are written as .js files in the plugins directoryrequire()
- Modules are loaded using Node.js
- Require cache is cleared before reloading to ensure fresh module load
- Inactive scripts are removed from both filesystem and require cache
`typescript
// Check for updates every 10 seconds
await DynaModuleManager.initialize(
MyScriptLoader,
'./scripts',
10000 // 10 seconds
);
const manager = DynaModuleManager.getInstance();
`
`typescript
class VersionedScriptLoader extends BaseScriptLoader {
protected async getUpdatedScripts(anchor: Date): Promise
const scripts = await this.fetchScripts(anchor);
return scripts.map(script => ({
keyCode: ${script.id}-v${script.version},${script.name}-v${script.version}
fileName: ,`
active: script.active,
latestUpdated: script.updated_at,
scriptCode: script.code
}));
}
}
Your dynamic scripts can export in various ways:
`javascript
// Option 1: Named exports
module.exports = {
execute: function() { / ... / },
config: { / ... / }
};
// Option 2: Default export
module.exports = function() { / ... / };
// Option 3: ES6-style (transpiled)
exports.default = class MyPlugin { / ... / };
`
Then use them:
`typescript
const script = manager.get('my-script');
// For named exports
script.execute();
// For default export
script();
// For ES6 default
new script.default();
`
The library includes comprehensive error handling:
- Timestamp Errors: Falls back to Unix epoch if timestamp file is corrupted
- File Operations: Logs errors and continues operation
- Script Loading: Catches and logs require() errors, returns null on failure
- Concurrency: Prevents simultaneous loading operations with isLoading flag
- Node.js: >= 16.0.0
- TypeScript: ^5.0.0 (for development)
- log4js: ^6.7.0 (optional peer dependency)
`bashBuild the library
npm run build
Best Practices
1. Implement Error Handling: Always handle potential null returns from
manager.get()
2. Script Format: Ensure your scripts follow Node.js module format
3. Unique Keys: Use unique and stable keyCode values
4. Active Status: Properly manage the active flag to enable/disable scripts
5. Timestamps: Ensure your data source provides accurate update timestampsTroubleshooting
$3
- Check that
getUpdatedScripts() returns valid DynaScript objects
- Verify scriptCode contains valid JavaScript
- Check log4js output for error messages$3
- Ensure the script file was created in the plugins directory
- Check for syntax errors in your script code
- Verify the keyCode matches exactly
$3
- Confirm
pollIntervalMs is set appropriately
- Verify latestUpdated` timestamps are increasingMIT License - see LICENSE file for details.
Henry Feng
- Email: huili.f@gmail.com
- GitHub: @ticatec
- GitHub: https://github.com/ticatec/scripts-loader
- Issues: https://github.com/ticatec/scripts-loader/issues
If this project helps you, please consider:
- ā Star the project on GitHub
- š Report issues
- š Sponsor the project