Package Exports
- @ticatec/script-loader
- @ticatec/script-loader/lib/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@ticatec/script-loader) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@ticatec/script-loader
A powerful TypeScript dynamic script loading and management library with real-time script updates, caching management, and timestamp persistence.
Features
- 🚀 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
Installation
npm install @ticatec/script-loaderQuick Start
Step 1: Implement BaseScriptLoader
Create your custom script loader by extending BaseScriptLoader and implementing the getUpdatedScripts method:
import BaseScriptLoader from '@ticatec/script-loader';
import { 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<DynaScript[]> {
// 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
}));
}
}Step 2: Initialize the DynaModuleManager
import DynaModuleManager from '@ticatec/script-loader/lib/DynaModuleManager';
// Initialize the singleton manager
const manager = await DynaModuleManager.getInstance(
MyScriptLoader, // Your loader class
'./scripts', // Script storage directory
5000 // Poll interval in milliseconds
);Step 3: Use Loaded Scripts
// 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();
}API Documentation
DynaScript Interface
The DynaScript interface defines the structure for script metadata:
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
}DynaModuleManager
Static Methods
getInstance(loaderConstructor, scriptDir, pollIntervalMs): Promise<DynaModuleManager>
Get or create the singleton instance of DynaModuleManager.
- Parameters:
loaderConstructor- Constructor of your BaseScriptLoader subclassscriptDir- Directory path for storing scriptspollIntervalMs- Polling interval in milliseconds
- Returns: Promise that resolves to the DynaModuleManager instance
Instance Methods
get(key: string): any | null
Retrieve a loaded script module by its keyCode.
- Parameters:
key- Script's unique identifier (keyCode) - Returns: The loaded module, or
nullif not found
BaseScriptLoader
Constructor
protected constructor(
scriptHome: string, // Script storage directory
pollIntervalMs: number // Polling interval in milliseconds
)Abstract Method (Must Implement)
getUpdatedScripts(anchor: Date): Promise<Array<DynaScript>>
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<void>
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 identifier - Returns: The loaded module, or
nullif not found
stopWatching(): void
Stop the periodic script update monitoring.
How It Works
Initialization Flow
- Directory Setup: Creates
pluginsdirectory underscriptHome - Timestamp Loading: Reads
.last_update_timestampfile or starts from Unix epoch - Initial Load: Fetches and loads all scripts updated since the last timestamp
- Watch Start: Begins periodic polling for script updates
Script Lifecycle
┌─────────────────┐
│ 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 │
└─────────────────┘Directory Structure
scriptHome/
├── .last_update_timestamp # Timestamp persistence file
└── plugins/ # Script files directory
├── script1.js
├── script2.js
└── ...Module Caching
- Scripts are written as
.jsfiles in thepluginsdirectory - Modules are loaded using Node.js
require() - Require cache is cleared before reloading to ensure fresh module load
- Inactive scripts are removed from both filesystem and require cache
Advanced Usage
Custom Polling Interval
// Check for updates every 10 seconds
const manager = await DynaModuleManager.getInstance(
MyScriptLoader,
'./scripts',
10000 // 10 seconds
);Implementing Script Versioning
class VersionedScriptLoader extends BaseScriptLoader {
protected async getUpdatedScripts(anchor: Date): Promise<DynaScript[]> {
const scripts = await this.fetchScripts(anchor);
return scripts.map(script => ({
keyCode: `${script.id}-v${script.version}`,
fileName: `${script.name}-v${script.version}`,
active: script.active,
latestUpdated: script.updated_at,
scriptCode: script.code
}));
}
}Handling Script Exports
Your dynamic scripts can export in various ways:
// 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:
const script = manager.get('my-script');
// For named exports
script.execute();
// For default export
script();
// For ES6 default
new script.default();Error Handling
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
isLoadingflag
Requirements
- Node.js: >= 16.0.0
- TypeScript: ^5.0.0 (for development)
- log4js: ^6.7.0 (optional peer dependency)
Development
# Build the library
npm run build
# Type checking
npm run typecheck
# Clean build artifacts
npm run clean
# Publish to npm
npm run publish-publicBest Practices
- Implement Error Handling: Always handle potential null returns from
manager.get() - Script Format: Ensure your scripts follow Node.js module format
- Unique Keys: Use unique and stable keyCode values
- Active Status: Properly manage the active flag to enable/disable scripts
- Timestamps: Ensure your data source provides accurate update timestamps
Troubleshooting
Scripts Not Loading
- Check that
getUpdatedScripts()returns valid DynaScript objects - Verify
scriptCodecontains valid JavaScript - Check log4js output for error messages
Module Returns Null
- Ensure the script file was created in the plugins directory
- Check for syntax errors in your script code
- Verify the keyCode matches exactly
Hot Reload Not Working
- Confirm
pollIntervalMsis set appropriately - Verify
latestUpdatedtimestamps are increasing - Check that require cache is being cleared
License
MIT License - see LICENSE file for details.
Author
Henry Feng
- Email: huili.f@gmail.com
- GitHub: @ticatec
Repository
- GitHub: https://github.com/ticatec/scripts-loader
- Issues: https://github.com/ticatec/scripts-loader/issues
Support
If this project helps you, please consider:
- ⭐ Star the project on GitHub
- 🐛 Report issues
- 💖 Sponsor the project