A lightweight TypeScript framework that brings modular architecture to your applications. Build loosely-coupled, maintainable applications where different parts can communicate without knowing about each other directly.
Traditional DI vs Pandino Service Registry:
Traditional DI | Pandino Service Registry |
---|---|
Static dependency injection | Dynamic service discovery |
Compile-time wiring | Runtime service resolution |
Hard-coded dependencies | LDAP-filtered service selection |
Single service per interface | Multiple ranked services |
Manual lifecycle management | Automatic service lifecycle |
Feature | What it Solves | Benefit |
---|---|---|
π Service Registry | Hard-coded dependencies between modules | Services discover each other dynamically |
π¦ Bundle System | Monolithic application architecture | Modular containers with independent lifecycles |
π Dynamic Dependencies | Startup order dependencies | Bundles start in any order, dependencies resolve automatically |
π‘ Event System | Tight coupling between modules | Publish-subscribe messaging with topic-based routing |
βοΈ Configuration Management | Static application configuration | Runtime configuration updates without restarts |
ποΈ Declarative Services | Complex service wiring boilerplate | Decorator-based dependency injection |
βοΈ React Integration | Framework complexity in React apps | Hook-based service discovery in components |
import { Component, Service, Reference, Activate } from '@pandino/pandino';
import type { ComponentContext } from '@pandino/pandino';
// 1. Define service interfaces
interface UserService {
findUser(id: string): User | null;
createUser(data: UserData): User;
}
interface NotificationService {
notify(message: string): void;
}
// 2. Create service implementations with SCR decorators
@Component({ name: 'user.service', immediate: true })
@Service({ interfaces: ['UserService'] })
class UserServiceImpl implements UserService {
private users = new Map<string, User>();
@Activate
activate(context: ComponentContext): void {
console.log('UserService activated');
}
findUser(id: string): User | null {
return this.users.get(id) || null;
}
createUser(data: UserData): User {
const user = new User(data);
this.users.set(user.id, user);
return user;
}
}
@Component({ name: 'notification.service', immediate: true })
@Service({ interfaces: ['NotificationService'] })
class NotificationServiceImpl implements NotificationService {
@Activate
activate(): void {
console.log('NotificationService activated');
}
notify(message: string): void {
console.log(`π§ Notification: ${message}`);
}
}
@Component({ name: 'order.service' })
@Service({ interfaces: ['OrderService'] })
class OrderService {
// Services are injected automatically when available
@Reference({ interface: 'UserService' })
private userService?: UserService;
@Reference({ interface: 'NotificationService' })
private notificationService?: NotificationService;
@Activate
activate(): void {
console.log('OrderService activated with dependencies');
}
async createOrder(userId: string, items: Item[]): Promise<Order> {
// Dependencies resolved automatically - no imports needed!
const user = this.userService?.findUser(userId);
if (!user) throw new Error('User not found');
const order = new Order(user, items);
this.notificationService?.notify(`Order confirmed for ${user.email}`);
return order;
}
}
// 3. Register components with SCR
const scr = context.getService(context.getServiceReference('ServiceComponentRuntime')!);
const bundleId = context.getBundle().getBundleId();
await scr.registerComponent(UserServiceImpl, bundleId);
await scr.registerComponent(NotificationServiceImpl, bundleId);
await scr.registerComponent(OrderService, bundleId);
The Magic: All three services discover each other automatically. OrderService
gets both dependencies injected without knowing how they're implemented!
Package | Purpose | Documentation |
---|---|---|
@pandino/pandino |
Core framework with service registry, bundles, and built-in services | Core Documentation |
@pandino/react-hooks |
React integration with hooks and components | React Documentation |
Services are registered in a central registry and discovered by interface name and properties:
// Register with metadata
context.registerService('DatabaseService', new MySQLService(), {
'db.type': 'mysql',
'service.ranking': 100
});
// Discover by capabilities
const dbRefs = context.getServiceReferences('DatabaseService', '(db.type=mysql)');
Bundles are self-contained modules with independent lifecycles:
// Each bundle manages its own services
const databaseBundle = {
activator: {
start(context) { /* register database services */ },
stop(context) { /* cleanup */ }
}
};
Order doesn't matter - dependencies resolve when services become available:
await apiBundle.start(); // β
Starts immediately
await databaseBundle.start(); // β
API bundle automatically gets database service
A pattern where a dedicated bundle (extender) monitors other bundles and provides functionality based on their metadata:
β Extender Pattern Documentation
A service-oriented pattern that promotes loose coupling through a central service registry:
β Whiteboard Pattern Documentation
A pattern that allows a bundle (fragment) to attach to another bundle (host) and contribute its resources directly to the host:
β Fragment Pattern Documentation
Scenario | Traditional Approach | Pandino Approach |
---|---|---|
Microservices Architecture | Hard-coded service URLs | Dynamic service discovery |
Plugin Systems | Manual plugin loading | Bundle-based plugins with auto-discovery |
Feature Flags | Code-level toggles | Service-level feature activation |
Multi-tenant Apps | Complex configuration management | Service filtering by tenant properties |
A/B Testing | Conditional code blocks | Multiple service implementations with ranking |
Choose your integration approach:
npm install @pandino/pandino
npm install @pandino/pandino @pandino/react-hooks
We welcome contributions! Please see our Contributing Guide for details.
Eclipse Public License - v 2.0