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 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: huili.f@gmail.com
- 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