@ticatec/script-loader
v0.1.5
Published
A powerful TypeScript dynamic script loading and management library with real-time script updates, caching management, and timestamp persistence.
Readme
@ticatec/script-loader
A TypeScript library for polling-based dynamic script loading in Node.js, with timestamp persistence and file-based module management.
Features
- 🚀 Dynamic Script Loading - Fetch scripts at runtime and write them to local files
- ⏰ Timestamp Persistence - Save and restore last update time via
.last_update_timestamp - 🔄 Polling Updates - Periodically check for updates and refresh local files
- 💾 Require Cache Management - Clear Node.js
requirecache when scripts change - 🧩 Singleton Manager - Access the loader through
DynaModuleManager - 🛡️ Error Handling - Logged failures for IO and module load errors
- 🔒 Concurrency Protection - Avoid overlapping update runs
- 📝 TypeScript Support - Type definitions included
Installation
npm install @ticatec/script-loaderQuick Start
Step 1: Implement BaseScriptLoader
Create your custom loader by extending BaseScriptLoader and implementing getUpdatedScripts.
import { BaseScriptLoader } from '@ticatec/script-loader';
import type { DynaScript } from '@ticatec/script-loader/lib/DynaModuleManager';
class MyScriptLoader extends BaseScriptLoader {
constructor(scriptHome: string, pollIntervalMs: number) {
super(scriptHome, pollIntervalMs);
}
/**
* Fetch scripts updated after the anchor timestamp
*/
protected async getUpdatedScripts(anchor: Date): Promise<DynaScript[]> {
const scripts = await db.query(
'SELECT * FROM scripts WHERE updated_at > ?',
[anchor]
);
return scripts.map(script => ({
keyCode: script.id,
fileName: script.name,
active: script.status === 'active',
latestUpdated: script.updated_at,
scriptCode: script.content
}));
}
}Step 2: Initialize DynaModuleManager
import DynaModuleManager from '@ticatec/script-loader';
await DynaModuleManager.initialize(
MyScriptLoader,
'./scripts',
5000
);Step 3: Use Loaded Scripts
import DynaModuleManager from '@ticatec/script-loader';
const manager = DynaModuleManager.getInstance();
const myScript = manager.get('script-key-123');
if (myScript) {
myScript.someFunction?.();
const result = myScript.default?.();
}API Documentation
DynaScript Interface
interface DynaScript {
keyCode: string;
fileName: string;
active: boolean;
latestUpdated: Date;
scriptCode: string;
}DynaModuleManager
initialize<Args>(loaderConstructor, ...args): Promise<DynaModuleManager>
- Parameters:
loaderConstructor- Constructor of yourBaseScriptLoadersubclass...args- Passed to the loader constructor (e.g.scriptHome,pollIntervalMs)
- Returns: A
DynaModuleManagerinstance (singleton)
getInstance(): DynaModuleManager
- Returns: The initialized singleton instance
- Throws: Error if
initialize()has not been called
get(key: string): any | null
- Returns: The module loaded via
require(filePath), ornullif missing or load fails
shutdown(): void
- Stops the polling timer
CommonScriptManager
CommonScriptManager is a lightweight in-memory registry for shared script instances or objects.
Basic Usage
import CommonScriptManager from '@ticatec/script-loader/lib/CommonScriptManager';
const registry = CommonScriptManager.getInstance();
registry.put('rule:order', {
validate(order: any) {
return order.total > 0;
}
});
const rule = registry.get<{ validate(order: any): boolean }>('rule:order');
if (rule) {
rule.validate({ total: 10 });
}
registry.remove('rule:order');BaseScriptLoader
Constructor
protected constructor(scriptHome: string, pollIntervalMs: number)Abstract Method
protected abstract getUpdatedScripts(anchor: Date): Promise<Array<DynaScript>>;Public Methods
init(): Promise<void>getModule(key: string): any | nullstopWatching(): void
Hook
protected afterRemoveModule(keyCode: string, modFile: string): void- Override if you need cleanup after a module is removed from cache
How It Works
Initialization Flow
- Directory Setup: Ensures
scriptHome/pluginsexists - Timestamp Loading: Reads
.last_update_timestampor starts from Unix epoch - Initial Load: Fetches all scripts updated since the last timestamp
- Polling: Periodically fetches updates and writes files
Directory Structure
scriptHome/
├── .last_update_timestamp
└── plugins/
├── script1.js
├── script2.js
└── ...Module Caching Behavior
- Scripts are written as
.jsfiles underplugins getModule()loads modules via Node.jsrequire()- When a script is updated, its
requirecache entry is cleared - When a script is inactive, the file is deleted and cache is cleared
Requirements
- Node.js: >= 16.0.0
- TypeScript: ^5.0.0 (development)
- log4js: ^6.7.0 (optional peer dependency)
Development
npm run build
npm run typecheck
npm run clean
npm run publish-publicBest Practices
- Handle
null:manager.get()can returnnullif load fails - Stable Keys: Use unique and stable
keyCodevalues - Valid JS: Ensure
scriptCodeis valid Node.js module syntax - Timestamps: Provide accurate
latestUpdatedvalues to avoid missing updates
Troubleshooting
Scripts Not Loading
- Confirm
getUpdatedScripts()returns validDynaScriptobjects - Verify
scriptCodecontains valid JavaScript - Check log4js output for errors
Module Returns null
- Verify the file exists under
plugins - Check for syntax errors in script code
- Ensure the keyCode matches exactly
License
MIT License - see LICENSE.
Author
Henry Feng
- Email: [email protected]
- GitHub: @ticatec
Repository
- GitHub: https://github.com/ticatec/scripts-loader
- Issues: https://github.com/ticatec/scripts-loader/issues
Support
- Star the project on GitHub
- Report issues on GitHub
- Sponsor: https://github.com/sponsors/ticatec
