@med1802/repository-manager
v3.2.0
Published
A lightweight, type-safe repository manager with dependency injection, event-driven architecture, lifecycle management, and multi-workspace support for TypeScript/JavaScript applications.
Readme
🔄 Repository Manager
A lightweight, type-safe repository manager with dependency injection, event-driven architecture, lifecycle management, and multi-workspace support for TypeScript/JavaScript applications.
✨ Features
- ✅ Dependency Injection - Inject dependencies into repositories
- ✅ Event-Driven Architecture - Built-in signal broadcasting for inter-repository communication
- ✅ Plugin System - Define repositories as plugins
- ✅ Lifecycle Management - Automatic connection/disconnection with reference counting
- ✅ Multi-Workspace Support - Manage multiple isolated workspaces with different dependencies
- ✅ Type Safety - Full TypeScript support with generics
- ✅ Lazy Initialization - Repository instances are created only when needed
- ✅ Clean API - Simple
createWorkspacepattern - ✅ Logging - Built-in logging with colored console output
- ✅ Memory Efficient - Automatic cleanup when no connections remain
- ✅ Middleware Support - Intercept and modify repository method calls
- ✅ Signal Broadcasting - Fire-and-forget event system for decoupled communication
📦 Installation
npm install @med1802/repository-manager🚀 Quick Start
import { repositoryManager } from "@med1802/repository-manager";
// Define your repository interface
interface IUserRepository {
getUsers(): Promise<User[]>;
createUser(user: User): Promise<User>;
}
// Create manager instance
const manager = repositoryManager();
// Define dependencies
const dependencies = {
httpClient: {
get: async (url: string) => fetch(url).then((r) => r.json()),
post: async (url: string, data: any) =>
fetch(url, { method: "POST", body: JSON.stringify(data) }),
},
};
// Create workspace with repositories
const { queryRepository } = manager.workspaceClient({
id: "app",
logging: true,
dependencies,
onSetup({ useRepository }) {
useRepository<IUserRepository>({
id: "user-repo",
install({ instance }): IUserRepository {
const { dependencies, signal } = instance;
return {
async getUsers() {
return dependencies.httpClient.get("/api/users");
},
async createUser(user) {
const result = await dependencies.httpClient.post("/api/users", user);
// Broadcast signal to other repositories
signal({
type: "user.created",
repositoryId: "notification-repo",
message: result,
});
return result;
},
};
},
onConnect: () => {
console.log("User repository initialized");
},
onDisconnect: () => {
console.log("User repository cleaned up");
},
});
}
});
// Query and use repository
const { repository: userRepo, disconnect } =
queryRepository<IUserRepository>("user-repo");
const users = await userRepo.getUsers();
// Cleanup when done
disconnect();📖 Core Concepts
1. Manager → Workspace → Repository
Repository Manager follows a hierarchical pattern:
const manager = repositoryManager(); // Global manager
const workspace = manager.workspaceClient({ // Workspace instance
id: "app",
dependencies,
logging: true,
onSetup({ useRepository }) { // Define repositories
useRepository({...});
}
});
workspace.queryRepository("repo-id"); // Query repositories2. Workspace Pattern
Workspace is the entry point for all operations. It encapsulates:
- Dependencies (shared services/infrastructure)
- Repository definitions via
onSetup - Signal broadcasting for events
- Logging configuration
const { queryRepository } = manager.workspaceClient({
id: "app-workspace",
logging: true,
dependencies: {
httpClient: myHttpClient,
cache: myCache,
},
onSetup({ useRepository }) {
// Register repositories here
useRepository({...});
},
});3. Signal Broadcasting System
The built-in signal broadcaster enables fire-and-forget inter-repository communication:
const { queryRepository } = manager.workspaceClient({
id: "app",
dependencies,
onSetup({ useRepository }) {
// Repository that broadcasts signals
useRepository({
id: "user-repo",
install({ instance }) {
const { signal } = instance;
return {
async createUser(user) {
const result = await saveUser(user);
// Broadcast signal to other repositories
signal({
type: "user.created",
repositoryId: "notification-repo",
message: result,
});
return result;
},
};
},
});
// Repository that listens to signals
useRepository({
id: "notification-repo",
onSignal(event, repo) {
if (event.type === "user.created") {
console.log("New user:", event.message);
// Send welcome email, etc.
}
},
install({ instance }) {
return {
// repository methods...
};
},
});
},
});📚 API Reference
repositoryManager()
Creates a repository manager instance.
Returns: Manager object with workspace method
Example:
const manager = repositoryManager();manager.workspaceClient<D>(config)
Creates a workspace client with dependencies and repositories.
Parameters:
config: IWorkspaceConfig<D>id: string- Unique identifier for the workspacedependencies: D- Dependencies to inject into repositoriesonSetup: ({ useRepository }) => void- Setup function to register repositorieslogging?: boolean- Enable/disable logging (default:false)
Returns: Object with workspace methods:
queryRepository- Query repositories
Example:
const { queryRepository } = manager.workspaceClient({
id: "app",
dependencies: {
httpClient: myHttpClient,
database: myDb,
},
onSetup({ useRepository }) {
useRepository({
id: "user-repo",
install({ instance }) {
// repository implementation
},
});
},
logging: true,
});Repository Definition
Defines a repository within the workspace.
Repository Structure:
IRepositoryConfig<D, R>id: string- Unique identifier for the repositoryinstall: ({ instance }) => R- Factory function that returns repository instanceinstance.dependencies- Injected dependenciesinstance.signal- Function to broadcast signals to other repositories
onSignal?: (event, repo) => void- Called when a signal is receivedonConnect?: () => void- Called when repository is first connectedonDisconnect?: () => void- Called when repository is last disconnectedmiddlewares?: Middleware[]- Array of middleware functions
Example:
onSetup({ useRepository }) {
useRepository<IUserRepository>({
id: "user-repo",
install({ instance }): IUserRepository {
const { dependencies, signal } = instance;
return {
async createUser(user) {
const result = await dependencies.httpClient.post("/users", user);
// Broadcast signal
signal({
type: "user.created",
repositoryId: "notification-repo",
message: result,
});
return result;
},
};
},
onSignal(event, repo) {
console.log("Signal received:", event);
},
onConnect: () => console.log("Connected"),
onDisconnect: () => console.log("Disconnected"),
});
}queryRepository<R>(id)
Queries a repository from the workspace.
Parameters:
id: string- Repository identifier
Returns: Object with:
repository: R- The repository instancedisconnect: () => void- Function to disconnect and cleanup
Throws: Error if repository is not found
Example:
const { repository, disconnect } =
queryRepository<IUserRepository>("user-repo");
await repository.getUsers();
disconnect();signal<P>(payload)
Broadcast a signal to notify a target repository (fire-and-forget).
Parameters:
payload: ISignalPayload<P>type: string- Signal type identifierrepositoryId: string- Target repository identifiermessage?: P- Optional payload data
Example:
signal({
type: "user.created",
repositoryId: "notification-repo",
message: { userId: "123", email: "[email protected]" },
});onSignal<P>(event, repo)
Handle incoming signals from other repositories.
Parameters:
event: ISignalSubscribePayload<P>- Signal payloadtype: string- Signal typesource: string- Source repositorymessage: P- Signal data
repo: R- Repository instance with all methods
Example:
onSignal(event, repo) {
if (event.type === "user.created") {
console.log("User created:", event.message);
console.log("From repository:", event.source);
// Call repository methods
repo.sendEmail(event.message.email);
}
}🎯 Advanced Usage
Signal Broadcasting Benefits
The built-in signal broadcasting system enables decoupled communication between repositories:
Benefits:
- Loose Coupling - Repositories don't need to know about each other
- Scalability - Easy to add new listeners without modifying existing code
- Event Sourcing - Track all events in your system
- Side Effects - Handle cross-cutting concerns (logging, analytics, notifications)
When to Use:
- Cross-repository notifications
- Audit logging
- Analytics tracking
- Email/SMS notifications
- Cache invalidation
- Webhook triggers
Event-Driven Communication
Repositories can communicate through the signal broadcasting system:
const { queryRepository } = manager.workspaceClient({
id: "app",
dependencies: {
httpClient: myHttpClient,
emailService: myEmailService,
},
onSetup({ useRepository }) {
// User repository broadcasts signals
useRepository({
id: "user-repo",
install({ instance }) {
const { dependencies, signal } = instance;
return {
async createUser(user) {
const result = await dependencies.httpClient.post("/users", user);
signal({
type: "user.created",
repositoryId: "notification-repo",
message: result,
});
return result;
},
async deleteUser(userId) {
await dependencies.httpClient.delete(`/users/${userId}`);
signal({
type: "user.deleted",
repositoryId: "notification-repo",
message: { userId },
});
},
};
},
});
// Notification repository listens to signals
useRepository({
id: "notification-repo",
onSignal(event, repo) {
if (event.type === "user.created") {
repo.sendWelcomeEmail(event.message.email);
}
if (event.type === "user.deleted") {
console.log("User deleted:", event.message.userId);
}
},
install({ instance }) {
const { dependencies } = instance;
return {
sendWelcomeEmail(email) {
dependencies.emailService.send({
to: email,
subject: "Welcome!",
});
},
};
},
});
// Analytics repository also listens to signals
useRepository({
id: "analytics-repo",
onSignal(event, repo) {
if (event.type === "user.created") {
console.log("Track new user:", event.message);
}
},
install({ instance }) {
return {
// analytics methods...
};
},
});
},
});Multiple Workspaces
Create multiple isolated workspaces with different dependencies:
// API workspace
const apiWorkspace = manager.workspaceClient({
id: "api",
dependencies: {
httpClient: apiClient,
cache: redisCache,
},
onSetup({ useRepository }) {
// API repositories
},
logging: true,
});
// Database workspace
const dbWorkspace = manager.workspaceClient({
id: "database",
dependencies: {
db: postgresClient,
logger: winstonLogger,
},
onSetup({ useRepository }) {
// Database repositories
},
logging: false,
});
// Each workspace has isolated repositories and signal broadcastersLifecycle Management
Repositories use reference counting for automatic lifecycle:
// First query creates instance (onConnect called)
const conn1 = queryRepository("user-repo"); // Connections: 1
// Subsequent queries reuse instance (onConnect NOT called)
const conn2 = queryRepository("user-repo"); // Connections: 2
const conn3 = queryRepository("user-repo"); // Connections: 3
// Disconnect reduces count
conn1.disconnect(); // Connections: 2
conn2.disconnect(); // Connections: 1
// Last disconnect destroys instance (onDisconnect called)
conn3.disconnect(); // Connections: 0, instance destroyedMiddleware
Intercept and modify repository method calls:
const loggingMiddleware: Middleware = (method, args, next) => {
console.log(`Calling ${method}`, args);
const result = next();
console.log(`${method} returned:`, result);
return result;
};
const cacheMiddleware: Middleware = (method, args, next) => {
const cacheKey = `${method}-${JSON.stringify(args)}`;
if (cache.has(cacheKey)) return cache.get(cacheKey);
const result = next();
cache.set(cacheKey, result);
return result;
};
const { queryRepository } = manager.workspaceClient({
dependencies,
onSetup({ useRepository }) {
useRepository({
id: "user-repo",
install({ instance }) {
return {
getUsers: () => instance.dependencies.httpClient.get("/users"),
};
},
middlewares: [loggingMiddleware, cacheMiddleware],
});
},
});Common Middleware Use Cases:
- Logging - Log all method calls and results
- Caching - Cache method results
- Validation - Validate arguments before execution
- Performance Monitoring - Measure execution time
- Error Handling - Centralized error handling and retry logic
- Authentication - Check permissions before execution
Logging
Enable logging to see connection lifecycle and events:
const { queryRepository } = manager.workspaceClient({
id: "app",
dependencies,
onSetup({ useRepository }) {
// repositories here
},
logging: true, // Enables colored console output
});