feat(integration): components now play nicer with each other

This commit is contained in:
2025-05-30 05:30:06 +00:00
parent 2c244c4a9a
commit 40db395591
19 changed files with 2849 additions and 264 deletions

View File

@ -9,14 +9,29 @@ import type { Email } from '../core/classes.email.js';
export class EmailRouter extends EventEmitter {
private routes: IEmailRoute[];
private patternCache: Map<string, boolean> = new Map();
private storageManager?: any; // StorageManager instance
private persistChanges: boolean;
/**
* Create a new email router
* @param routes Array of email routes
* @param options Router options
*/
constructor(routes: IEmailRoute[]) {
constructor(routes: IEmailRoute[], options?: {
storageManager?: any;
persistChanges?: boolean;
}) {
super();
this.routes = this.sortRoutesByPriority(routes);
this.storageManager = options?.storageManager;
this.persistChanges = options?.persistChanges ?? !!this.storageManager;
// If storage manager is provided, try to load persisted routes
if (this.storageManager) {
this.loadRoutes({ merge: true }).catch(error => {
console.error(`Failed to load persisted routes: ${error.message}`);
});
}
}
/**
@ -43,19 +58,26 @@ export class EmailRouter extends EventEmitter {
/**
* Update routes
* @param routes New routes
* @param persist Whether to persist changes (defaults to persistChanges setting)
*/
public updateRoutes(routes: IEmailRoute[]): void {
public async updateRoutes(routes: IEmailRoute[], persist?: boolean): Promise<void> {
this.routes = this.sortRoutesByPriority(routes);
this.clearCache();
this.emit('routesUpdated', this.routes);
// Persist if requested or if persistChanges is enabled
if (persist ?? this.persistChanges) {
await this.saveRoutes();
}
}
/**
* Set routes (alias for updateRoutes)
* @param routes New routes
* @param persist Whether to persist changes
*/
public setRoutes(routes: IEmailRoute[]): void {
this.updateRoutes(routes);
public async setRoutes(routes: IEmailRoute[], persist?: boolean): Promise<void> {
await this.updateRoutes(routes, persist);
}
/**
@ -367,4 +389,187 @@ export class EmailRouter extends EventEmitter {
return size;
}
/**
* Save current routes to storage
*/
public async saveRoutes(): Promise<void> {
if (!this.storageManager) {
this.emit('persistenceWarning', 'Cannot save routes: StorageManager not configured');
return;
}
try {
// Validate all routes before saving
for (const route of this.routes) {
if (!route.name || !route.match || !route.action) {
throw new Error(`Invalid route: ${JSON.stringify(route)}`);
}
}
const routesData = JSON.stringify(this.routes, null, 2);
await this.storageManager.set('/email/routes/config.json', routesData);
this.emit('routesPersisted', this.routes.length);
} catch (error) {
console.error(`Failed to save routes: ${error.message}`);
throw error;
}
}
/**
* Load routes from storage
* @param options Load options
*/
public async loadRoutes(options?: {
merge?: boolean; // Merge with existing routes
replace?: boolean; // Replace existing routes
}): Promise<IEmailRoute[]> {
if (!this.storageManager) {
this.emit('persistenceWarning', 'Cannot load routes: StorageManager not configured');
return [];
}
try {
const routesData = await this.storageManager.get('/email/routes/config.json');
if (!routesData) {
return [];
}
const loadedRoutes = JSON.parse(routesData) as IEmailRoute[];
// Validate loaded routes
for (const route of loadedRoutes) {
if (!route.name || !route.match || !route.action) {
console.warn(`Skipping invalid route: ${JSON.stringify(route)}`);
continue;
}
}
if (options?.replace) {
// Replace all routes
this.routes = this.sortRoutesByPriority(loadedRoutes);
} else if (options?.merge) {
// Merge with existing routes (loaded routes take precedence)
const routeMap = new Map<string, IEmailRoute>();
// Add existing routes
for (const route of this.routes) {
routeMap.set(route.name, route);
}
// Override with loaded routes
for (const route of loadedRoutes) {
routeMap.set(route.name, route);
}
this.routes = this.sortRoutesByPriority(Array.from(routeMap.values()));
}
this.clearCache();
this.emit('routesLoaded', loadedRoutes.length);
return loadedRoutes;
} catch (error) {
console.error(`Failed to load routes: ${error.message}`);
throw error;
}
}
/**
* Add a route
* @param route Route to add
* @param persist Whether to persist changes
*/
public async addRoute(route: IEmailRoute, persist?: boolean): Promise<void> {
// Validate route
if (!route.name || !route.match || !route.action) {
throw new Error('Invalid route: missing required fields');
}
// Check if route already exists
const existingIndex = this.routes.findIndex(r => r.name === route.name);
if (existingIndex >= 0) {
throw new Error(`Route '${route.name}' already exists`);
}
// Add route
this.routes.push(route);
this.routes = this.sortRoutesByPriority(this.routes);
this.clearCache();
this.emit('routeAdded', route);
this.emit('routesUpdated', this.routes);
// Persist if requested
if (persist ?? this.persistChanges) {
await this.saveRoutes();
}
}
/**
* Remove a route by name
* @param name Route name
* @param persist Whether to persist changes
*/
public async removeRoute(name: string, persist?: boolean): Promise<void> {
const index = this.routes.findIndex(r => r.name === name);
if (index < 0) {
throw new Error(`Route '${name}' not found`);
}
const removedRoute = this.routes.splice(index, 1)[0];
this.clearCache();
this.emit('routeRemoved', removedRoute);
this.emit('routesUpdated', this.routes);
// Persist if requested
if (persist ?? this.persistChanges) {
await this.saveRoutes();
}
}
/**
* Update a route
* @param name Route name
* @param route Updated route data
* @param persist Whether to persist changes
*/
public async updateRoute(name: string, route: IEmailRoute, persist?: boolean): Promise<void> {
// Validate route
if (!route.name || !route.match || !route.action) {
throw new Error('Invalid route: missing required fields');
}
const index = this.routes.findIndex(r => r.name === name);
if (index < 0) {
throw new Error(`Route '${name}' not found`);
}
// Update route
this.routes[index] = route;
this.routes = this.sortRoutesByPriority(this.routes);
this.clearCache();
this.emit('routeUpdated', route);
this.emit('routesUpdated', this.routes);
// Persist if requested
if (persist ?? this.persistChanges) {
await this.saveRoutes();
}
}
/**
* Get a route by name
* @param name Route name
* @returns Route or undefined
*/
public getRoute(name: string): IEmailRoute | undefined {
return this.routes.find(r => r.name === name);
}
}