@jucie.io/engine
v1.1.62
Published
A powerful, extensible service engine with middleware, async operations, and lifecycle management (includes core + services)
Maintainers
Readme
@jucie.io/engine
A powerful, extensible service engine for JavaScript applications with middleware support, async operations, and comprehensive lifecycle management.
Important Notice
This package is made publicly available for use but comes with no support, maintenance commitments, or warranty of any kind. It is provided strictly "as-is" under the MIT license.
- Issues and feature requests may not be addressed
- No timeline or commitment for updates
- Not actively seeking contributions
- Use at your own risk
If you choose to use this package, please ensure it meets your needs through your own testing and evaluation.
Features
- 🚀 Service Architecture: Clean, modular service system with lifecycle management
- 🔧 Middleware Support: Composable middleware with sync/async support
- ⚡ Async Operations: Full async/await support for actions and middleware
- 🛡️ Security: Built-in protection against prototype pollution and namespace conflicts
- 🔗 Dependency Management: Automatic service dependency resolution
- ⚙️ Configuration: Flexible service configuration with defaults merging
- 🧪 Well Tested: Comprehensive test suite with 33+ test cases
- 📦 TypeScript Ready: Full TypeScript definitions included
Installation
npm install @jucie.io/engineQuick Start
Method Pattern
import { Engine, ServiceProvider } from '@jucie.io/engine';
// Create an engine instance
const engine = Engine.create();
// Define a service using method pattern
class MyService extends ServiceProvider {
static manifest = {
name: 'My Service',
namespace: 'myService',
version: '1.0.0'
};
actions(useContext, config) {
return {
greet: (name) => `Hello, ${name}!`,
fetchData: async () => {
// Async operations supported
return await fetch('/api/data').then(r => r.json());
}
};
}
middleware(useContext, config) {
return (action, ctx, next) => {
console.log(`Executing action: ${action}`);
return next();
};
}
}
// Install the service
await engine.install(MyService);
// Use the service
const greeting = engine.myService.greet('World');
console.log(greeting); // "Hello, World!"Setup Pattern
import { Engine, ServiceProvider } from '@jucie.io/engine';
// Create an engine instance
const engine = Engine.create();
// Define a service using setup pattern
class MyService extends ServiceProvider {
static manifest = {
name: 'My Service',
namespace: 'myService',
version: '1.0.0'
};
setup({ defineActions, defineMiddleware }) {
defineActions((useContext, config) => {
return {
greet: (name) => `Hello, ${name}!`,
fetchData: async () => {
return await fetch('/api/data').then(r => r.json());
}
};
});
defineMiddleware((useContext, config) => {
return (action, ctx, next) => {
console.log(`Executing action: ${action}`);
return next();
};
});
}
}
// Install the service
await engine.install(MyService);
// Use the service
const greeting = engine.myService.greet('World');
console.log(greeting); // "Hello, World!"Service Architecture
ServiceProvider Base Class
All services extend the ServiceProvider base class and implement the following lifecycle methods:
Required Properties
static manifest = {
name: 'Service Name',
namespace: 'serviceName', // Must be valid JS identifier
version: '1.0.0'
};Service Definition Patterns
The ServiceProvider supports two patterns for defining service functionality:
Method Pattern (Traditional approach):
actions(useContext, config)- Define service actions (required)middleware(useContext, config)- Define middleware functions (optional)getters(useContext, config)- Define read-only properties (optional)initialize(useContext, config)- Setup logic called directly (optional)uninstall(useContext, config)- Cleanup logic called directly (optional)
Setup Pattern (Declarative approach):
setup({ defineActions, defineMiddleware, defineGetters, defineInitialize, defineUninstall })- Declarative service definition
Method Pattern Example
class DatabaseService extends ServiceProvider {
static manifest = {
name: 'Database Service',
namespace: 'db',
version: '1.0.0',
defaults: { connectionString: 'sqlite:memory:' }
};
#connection = null;
initialize(useContext, config) {
this.#connection = createConnection(this.config.connectionString);
}
getters(useContext, config) {
return {
isConnected: () => this.#connection !== null
};
}
middleware(useContext, config) {
return (action, ctx, next) => {
console.log(`Database action: ${action}`);
return next();
};
}
actions(useContext, config) {
return {
async query: (sql, params = []) => {
return await this.#connection.query(sql, params);
},
async transaction: async (callback) => {
const tx = await this.#connection.beginTransaction();
try {
const result = await callback(tx);
await tx.commit();
return result;
} catch (error) {
await tx.rollback();
throw error;
}
}
};
}
uninstall(useContext, config) {
if (this.#connection) {
this.#connection.close();
this.#connection = null;
}
}
}Setup Pattern Example
class CacheService extends ServiceProvider {
static manifest = {
name: 'Cache Service',
namespace: 'cache',
version: '1.0.0',
defaults: { ttl: 300000, maxSize: 1000 }
};
#cache = new Map();
setup({ defineMiddleware, defineGetters, defineActions, defineInitialize, defineUninstall }) {
// Define middleware
defineMiddleware((useContext, config) => {
return (action, ctx, next) => {
console.log(`Cache action: ${action}`);
return next();
};
});
// Define getters
defineGetters((useContext, config) => {
return {
size: () => this.#cache.size,
isEnabled: () => this.config.maxSize > 0
};
});
// Define actions
defineActions((useContext, config) => {
return {
get: (key) => {
const item = this.#cache.get(key);
if (!item) return null;
if (Date.now() > item.expires) {
this.#cache.delete(key);
return null;
}
return item.value;
},
set: (key, value, ttl = this.config.ttl) => {
if (this.#cache.size >= this.config.maxSize) {
// Remove oldest entry
const firstKey = this.#cache.keys().next().value;
this.#cache.delete(firstKey);
}
this.#cache.set(key, {
value,
expires: Date.now() + ttl
});
},
clear: () => {
this.#cache.clear();
}
};
});
// Define initialize
defineInitialize((useContext, config) => {
console.log('Cache service initialized');
});
// Define uninstall
defineUninstall((useContext, config) => {
this.#cache.clear();
console.log('Cache service cleaned up');
});
}
}Pattern Comparison
| Feature | Method Pattern | Setup Pattern | |---------|---------------|---------------| | Syntax | Class methods | Single setup function | | Organization | Separate methods | Centralized definition | | Flexibility | High - conditional methods | Medium - declarative | | Readability | Good for simple services | Excellent for complex services | | Type Safety | Standard class methods | Requires proper typing | | Use Case | Simple to medium services | Complex services with many definitions |
Middleware System
The engine supports powerful middleware composition for intercepting and processing action calls:
class LoggingService extends ServiceProvider {
static manifest = {
name: 'Logging Service',
namespace: 'logging',
version: '1.0.0'
};
middleware(useContext, config) {
return [
// Sync middleware
(action, ctx, next) => {
console.log(`[${new Date().toISOString()}] Starting ${action}`);
const start = Date.now();
const result = next();
console.log(`[${new Date().toISOString()}] Completed ${action} in ${Date.now() - start}ms`);
return result;
},
// Async middleware
async (action, ctx, next) => {
// Authentication check
const user = await authenticateUser();
if (!user) {
throw new Error('Unauthorized');
}
ctx.user = user;
return next();
}
];
}
actions(useContext, config) {
return {
log: (message) => console.log(message)
};
}
}Service Dependencies
Services can declare dependencies that are automatically installed:
class AuthService extends ServiceProvider {
static manifest = {
name: 'Auth Service',
namespace: 'auth',
version: '1.0.0'
};
actions(useContext, config) {
const { engine } = useContext();
return {
login: (credentials) => {
// Use dependency
return engine.db.query('SELECT * FROM users WHERE email = ?', [credentials.email]);
}
};
}
}
class ApiService extends ServiceProvider {
static manifest = {
name: 'API Service',
namespace: 'api',
version: '1.0.0',
dependencies: [AuthService, DatabaseService] // Auto-install dependencies
};
actions(useContext, config) {
const { engine } = useContext();
return {
handleRequest: (req, res) => {
// Dependencies are available
const user = engine.auth.getCurrentUser();
return engine.db.query('SELECT * FROM posts WHERE userId = ?', [user.id]);
}
};
}
}Configuration
Services support configuration with defaults:
class ConfigurableService extends ServiceProvider {
static manifest = {
name: 'Configurable Service',
namespace: 'configurable',
version: '1.0.0',
defaults: {
timeout: 5000,
retries: 3,
debug: false
}
};
actions(useContext, config) {
return {
getConfig: () => this.config,
getTimeout: () => this.config.timeout
};
}
}
// Install with custom configuration
const configuredService = ConfigurableService.configure({
timeout: 10000,
debug: true
});
await engine.install(configuredService);
console.log(engine.configurable.getTimeout()); // 10000Global Engine Context
The engine provides global context management:
import { provideEngine, useEngine, hasEngine } from '@jucie.io/engine';
// Provide engine globally
provideEngine(engine);
// Use engine from anywhere
const globalEngine = useEngine();
const hasGlobalEngine = hasEngine();Error Handling
The engine provides comprehensive error handling with context:
class ErrorProneService extends ServiceProvider {
static manifest = {
name: 'Error Service',
namespace: 'error',
version: '1.0.0'
};
actions(useContext, config) {
return {
riskyOperation: () => {
throw new Error('Something went wrong');
}
};
}
}
try {
await engine.install(ErrorProneService);
engine.error.riskyOperation();
} catch (error) {
console.log(error.action); // 'riskyOperation'
console.log(error.namespace); // 'error'
console.log(error.message); // 'Something went wrong'
}Security Features
The engine includes built-in security protections:
- Prototype Pollution Prevention: Blocks
__proto__andconstructornamespace usage - Namespace Validation: Ensures valid JavaScript identifiers
- Reserved Namespace Protection: Prevents conflicts with engine methods
- Input Sanitization: Validates service manifests and configurations
API Reference
Engine
Engine.create(config?)
Creates a new engine instance.
engine.install(...services)
Installs one or more services.
engine.uninstall(...namespaces)
Uninstalls services by namespace.
engine.use(...definitions)
Adds middleware or other definitions directly.
ServiceProvider
ServiceProvider.configure(options)
Creates a configured service instance.
Static Properties
manifest- Service metadata and configuration
Lifecycle Methods
actions(useContext, config)- Define service actionsmiddleware(useContext, config)- Define middleware functionsgetters(useContext, config)- Define read-only propertiesinitialize(useContext, config)- Setup logic called directlyuninstall(useContext, config)- Cleanup logic called directly
Testing
The engine includes a comprehensive test suite:
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverageDevelopment
# Install dependencies
npm install
# Build the project
npm run build
# Watch for changes
npm run watch
# Run benchmarks
npm run benchLicense
MIT License with Commons Clause - See LICENSE file for details.
TL;DR: Free to use in your projects, but you cannot sell this software as a competing product or service.
