UNPKG

connectic

Version:

Connectic is a javascript library for pub sub messaging, event handling, and data synchronization in real-time applications.

1,759 lines (1,751 loc) 150 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /* connectic - description */ // src/errors.ts var BusErrorCode = /* @__PURE__ */ ((BusErrorCode2) => { BusErrorCode2[BusErrorCode2["NOT_FOUND"] = 404] = "NOT_FOUND"; BusErrorCode2[BusErrorCode2["FORBIDDEN"] = 403] = "FORBIDDEN"; BusErrorCode2[BusErrorCode2["TIMEOUT"] = 408] = "TIMEOUT"; BusErrorCode2[BusErrorCode2["CONFLICT"] = 409] = "CONFLICT"; BusErrorCode2[BusErrorCode2["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE"; BusErrorCode2[BusErrorCode2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS"; BusErrorCode2[BusErrorCode2["INTERNAL_ERROR"] = 500] = "INTERNAL_ERROR"; BusErrorCode2[BusErrorCode2["BAD_REQUEST"] = 400] = "BAD_REQUEST"; BusErrorCode2[BusErrorCode2["GONE"] = 410] = "GONE"; BusErrorCode2[BusErrorCode2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE"; BusErrorCode2[BusErrorCode2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY"; return BusErrorCode2; })(BusErrorCode || {}); var BusError = class _BusError extends Error { /** * Creates a new BusError instance * @param message Human-readable error description * @param busCode Specific error code for programmatic handling * @param details Additional error context and debugging information */ constructor(message, busCode, details) { super(message); this.busCode = busCode; this.details = details; this.name = "BusError"; Object.setPrototypeOf(this, _BusError.prototype); if (Error.captureStackTrace) { Error.captureStackTrace(this, _BusError); } } /** * Returns a JSON representation of the error * Useful for logging and debugging */ toJSON() { return { name: this.name, message: this.message, busCode: this.busCode, details: this.details, stack: this.stack }; } /** * Returns a string representation including error code */ toString() { return `${this.name} [${this.busCode}]: ${this.message}`; } /** * Check if this error indicates a temporary failure that might be retried */ isRetryable() { return [ 408 /* TIMEOUT */, 429 /* TOO_MANY_REQUESTS */, 503 /* SERVICE_UNAVAILABLE */, 500 /* INTERNAL_ERROR */ ].includes(this.busCode); } /** * Check if this error indicates a client-side issue */ isClientError() { return this.busCode >= 400 && this.busCode < 500; } /** * Check if this error indicates a server-side issue */ isServerError() { return this.busCode >= 500 && this.busCode < 600; } }; var BusErrorFactory = class { /** * Creates a NOT_FOUND error when no responders are available */ static notFound(event, details) { return new BusError( `No responders available for event: ${event}`, 404 /* NOT_FOUND */, { event, ...details } ); } /** * Creates a TIMEOUT error when requests exceed timeout limit */ static timeout(event, timeoutMs, details) { return new BusError( `Request timeout after ${timeoutMs}ms for event: ${event}`, 408 /* TIMEOUT */, { event, timeoutMs, ...details } ); } /** * Creates a FORBIDDEN error when middleware validation fails */ static forbidden(event, reason, details) { return new BusError( `Access forbidden for event: ${event}. Reason: ${reason}`, 403 /* FORBIDDEN */, { event, reason, ...details } ); } /** * Creates a CONFLICT error when multiple responders exist */ static conflict(event, responderCount, details) { return new BusError( `Multiple responders (${responderCount}) for event: ${event}`, 409 /* CONFLICT */, { event, responderCount, ...details } ); } /** * Creates a PAYLOAD_TOO_LARGE error when payload exceeds limits */ static payloadTooLarge(event, size, limit, details) { return new BusError( `Payload size ${size} bytes exceeds limit of ${limit} bytes for event: ${event}`, 413 /* PAYLOAD_TOO_LARGE */, { event, size, limit, ...details } ); } /** * Creates a TOO_MANY_REQUESTS error when rate limiting is triggered */ static tooManyRequests(event, limit, window2, details) { return new BusError( `Rate limit exceeded: ${limit} requests per ${window2}ms for event: ${event}`, 429 /* TOO_MANY_REQUESTS */, { event, limit, window: window2, ...details } ); } /** * Creates an INTERNAL_ERROR for unexpected failures */ static internal(message, originalError, details) { return new BusError( `Internal error: ${message}`, 500 /* INTERNAL_ERROR */, { originalError: originalError?.message, stack: originalError?.stack, ...details } ); } /** * Creates a BAD_REQUEST error for malformed requests */ static badRequest(event, reason, details) { return new BusError( `Bad request for event: ${event}. ${reason}`, 400 /* BAD_REQUEST */, { event, reason, ...details } ); } /** * Creates a GONE error when responder was available but is now offline */ static gone(event, details) { return new BusError( `Responder for event: ${event} is no longer available`, 410 /* GONE */, { event, ...details } ); } /** * Creates a SERVICE_UNAVAILABLE error for temporary responder issues */ static serviceUnavailable(event, retryAfter, details) { const message = retryAfter ? `Service unavailable for event: ${event}. Retry after ${retryAfter}ms` : `Service temporarily unavailable for event: ${event}`; return new BusError( message, 503 /* SERVICE_UNAVAILABLE */, { event, retryAfter, ...details } ); } /** * Creates an UNPROCESSABLE_ENTITY error for valid but rejected payloads */ static unprocessableEntity(event, reason, details) { return new BusError( `Unprocessable request for event: ${event}. ${reason}`, 422 /* UNPROCESSABLE_ENTITY */, { event, reason, ...details } ); } }; function isBusError(error) { return error instanceof BusError; } function hasBusErrorCode(error, code) { return isBusError(error) && error.busCode === code; } function wrapError(error, event) { if (isBusError(error)) { if (event && !error.details?.event) { return new BusError( error.message, error.busCode, { ...error.details, event, wrappedFrom: event } ); } return error; } if (error instanceof Error) { const busError = BusErrorFactory.internal(error.message, error, { event }); if (error.stack) { busError.stack = error.stack; } return busError; } return BusErrorFactory.internal( `Unknown error: ${String(error)}`, void 0, { event, originalError: error } ); } var ErrorMessages = { INVALID_EVENT_NAME: "Event name must be a non-empty string", INVALID_PAYLOAD: "Event payload must be serializable", INVALID_HANDLER: "Event handler must be a function", INVALID_TIMEOUT: "Timeout must be a positive number", INVALID_RETRIES: "Retries must be a non-negative number", BUS_DESTROYED: "Bus instance has been destroyed", MIDDLEWARE_ERROR: "Middleware execution failed", CACHE_ERROR: "Cache operation failed", STATE_NOT_FOUND: "State key not found", COMPUTED_CIRCULAR_DEPENDENCY: "Circular dependency detected in computed state" }; function createValidationError(field, value, expected) { return BusErrorFactory.badRequest( "validation", `Invalid ${field}: expected ${expected}, got ${typeof value}`, { field, value, expected } ); } // src/core/utils.ts function generateId(length = 16) { const timestamp = Date.now().toString(36); const randomPart = Array.from( { length }, () => Math.random().toString(36).charAt(2) ).join(""); return `${timestamp}_${randomPart}`; } function isValidEventName(name) { return typeof name === "string" && name.length > 0 && name.length <= 255 && !/^\s|\s$/.test(name); } function isValidTimeout(timeout) { return typeof timeout === "number" && timeout > 0 && timeout <= Number.MAX_SAFE_INTEGER && Number.isFinite(timeout); } function isValidRetries(retries) { return typeof retries === "number" && retries >= 0 && retries <= 100 && Number.isInteger(retries); } function isValidHandler(handler) { return typeof handler === "function"; } function deepClone(obj) { if (obj === null || typeof obj !== "object") { return obj; } if (typeof structuredClone !== "undefined") { try { return structuredClone(obj); } catch { } } try { return JSON.parse(JSON.stringify(obj)); } catch (error) { throw BusErrorFactory.internal( `Failed to clone object: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0, { originalObject: obj } ); } } function getGlobalStore() { const REGISTRY_KEY = "__CONNECTIC_BUS_REGISTRY_v1__"; if (typeof window !== "undefined") { if (!window[REGISTRY_KEY]) { window[REGISTRY_KEY] = /* @__PURE__ */ new Map(); } return window[REGISTRY_KEY]; } if (typeof global !== "undefined") { if (!global[REGISTRY_KEY]) { global[REGISTRY_KEY] = /* @__PURE__ */ new Map(); } return global[REGISTRY_KEY]; } if (typeof globalThis !== "undefined") { if (!globalThis[REGISTRY_KEY]) { globalThis[REGISTRY_KEY] = /* @__PURE__ */ new Map(); } return globalThis[REGISTRY_KEY]; } if (!moduleStore) { moduleStore = /* @__PURE__ */ new Map(); } return moduleStore; } var moduleStore; function safeExecute(fn, context, fallbackValue) { try { return fn(); } catch (error) { console.warn(`Error in ${context}:`, error); return fallbackValue; } } function estimateObjectSize(obj) { const seen = /* @__PURE__ */ new WeakSet(); function calculate(obj2) { if (obj2 === null || obj2 === void 0) { return 0; } if (typeof obj2 === "boolean") { return 4; } if (typeof obj2 === "number") { return 8; } if (typeof obj2 === "string") { return obj2.length * 2; } if (typeof obj2 === "function") { return obj2.toString().length * 2; } if (seen.has(obj2)) { return 0; } seen.add(obj2); if (Array.isArray(obj2)) { return obj2.reduce((size, item) => size + calculate(item), 0); } if (typeof obj2 === "object") { return Object.keys(obj2).reduce((size, key) => { return size + key.length * 2 + calculate(obj2[key]); }, 0); } return 0; } return calculate(obj); } function hashString(str) { let hash = 0; if (str.length === 0) return hash; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash); } function createCacheKey(event, payload) { if (!payload) { return event; } try { const payloadStr = JSON.stringify(payload); const hash = hashString(payloadStr); return `${event}:${hash}`; } catch { return `${event}:${Date.now()}`; } } function validateParameters(event, handler, timeout, retries) { if (!isValidEventName(event)) { throw createValidationError("event", event, "non-empty string"); } if (handler !== void 0 && !isValidHandler(handler)) { throw createValidationError("handler", handler, "function"); } } function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function exponentialBackoff(attempt, baseDelay = 1e3, maxDelay = 3e4) { const delay2 = baseDelay * Math.pow(2, attempt); return Math.min(delay2, maxDelay); } // src/core/event-bus.ts var EventBus = class { constructor() { this.listeners = /* @__PURE__ */ new Map(); this.stats = { totalEvents: 0, totalSubscriptions: 0, totalUnsubscriptions: 0, errorCount: 0 }; this.isDestroyed = false; this.maxListeners = 100; } // Per event limit to prevent memory leaks /** * Emits an event to all registered listeners * @param event Event name to emit * @param payload Data to send with the event */ emit(event, payload) { this.throwIfDestroyed(); try { validateParameters(event); this.stats.totalEvents++; const handlers = this.listeners.get(event); if (!handlers || handlers.size === 0) { return; } handlers.forEach((handler) => { safeExecute(() => handler(payload), `event handler for '${event}'`); }); } catch (error) { this.stats.errorCount++; throw wrapError(error, event); } } /** * Subscribes to an event * @param event Event name to listen for * @param handler Function to call when event is emitted * @returns Unsubscribe function */ on(event, handler) { this.throwIfDestroyed(); try { validateParameters(event, handler); if (!this.listeners.has(event)) { this.listeners.set(event, /* @__PURE__ */ new Set()); } const eventListeners = this.listeners.get(event); if (eventListeners.size >= this.maxListeners) { throw BusErrorFactory.internal( `Maximum listeners (${this.maxListeners}) exceeded for event: ${event}`, void 0, { event, currentListeners: eventListeners.size } ); } eventListeners.add(handler); this.stats.totalSubscriptions++; return () => this.off(event, handler); } catch (error) { this.stats.errorCount++; throw wrapError(error, event); } } /** * Subscribes to an event for one-time execution * @param event Event name to listen for * @param handler Function to call when event is emitted (once) * @returns Unsubscribe function */ once(event, handler) { this.throwIfDestroyed(); let unsubscribed = false; let internalUnsubscribe = null; const onceHandler = (payload) => { if (unsubscribed) return; unsubscribed = true; if (internalUnsubscribe) { internalUnsubscribe(); internalUnsubscribe = null; } try { handler(payload); } catch (error) { console.error(`Error in once handler for ${event}:`, error); } }; internalUnsubscribe = this.on(event, onceHandler); return () => { if (!unsubscribed) { unsubscribed = true; if (internalUnsubscribe) { internalUnsubscribe(); internalUnsubscribe = null; } } }; } /** * Removes a specific event handler * @param event Event name * @param handler Handler function to remove */ off(event, handler) { this.throwIfDestroyed(); try { validateParameters(event, handler); const eventListeners = this.listeners.get(event); if (!eventListeners) { return; } const removed = eventListeners.delete(handler); if (removed) { this.stats.totalUnsubscriptions++; } if (eventListeners.size === 0) { this.listeners.delete(event); } } catch (error) { this.stats.errorCount++; throw wrapError(error, event); } } /** * Removes all listeners for an event, or all listeners if no event specified * @param event Optional event name to target specific event */ removeAllListeners(event) { this.throwIfDestroyed(); try { if (event !== void 0) { validateParameters(event); const eventListeners = this.listeners.get(event); if (eventListeners) { this.stats.totalUnsubscriptions += eventListeners.size; this.listeners.delete(event); } } else { let totalRemoved = 0; this.listeners.forEach((listeners) => { totalRemoved += listeners.size; }); this.stats.totalUnsubscriptions += totalRemoved; this.listeners.clear(); } } catch (error) { this.stats.errorCount++; throw wrapError(error, event); } } /** * Gets the number of listeners for a specific event * @param event Event name * @returns Number of listeners */ getListenerCount(event) { this.throwIfDestroyed(); try { validateParameters(event); return this.listeners.get(event)?.size || 0; } catch (error) { this.stats.errorCount++; throw wrapError(error, event); } } /** * Checks if an event has any listeners * @param event Event name * @returns True if event has listeners */ hasListeners(event) { this.throwIfDestroyed(); try { validateParameters(event); return this.getListenerCount(event) > 0; } catch (error) { this.stats.errorCount++; throw wrapError(error, event); } } /** * Gets all event names that have listeners * @returns Array of event names */ getEventNames() { this.throwIfDestroyed(); return Array.from(this.listeners.keys()); } /** * Gets comprehensive statistics about the event bus * @returns Bus statistics object */ getStats() { const activeListeners = Array.from(this.listeners.values()).reduce( (total, listeners) => total + listeners.size, 0 ); const memoryUsage = estimateObjectSize(this.listeners); return { totalEvents: this.stats.totalEvents, totalRequests: 0, // Will be overridden by request-response layer activeListeners, cacheSize: 0, // Will be overridden by cache layer memoryUsage }; } /** * Gets detailed internal statistics for debugging * @returns Extended statistics object */ getDetailedStats() { const baseStats = this.getStats(); return { ...baseStats, totalSubscriptions: this.stats.totalSubscriptions, totalUnsubscriptions: this.stats.totalUnsubscriptions, errorCount: this.stats.errorCount, eventCount: this.listeners.size, maxListenersPerEvent: this.maxListeners, isDestroyed: this.isDestroyed }; } /** * Sets the maximum number of listeners per event * @param max Maximum listeners (default: 100) */ setMaxListeners(max) { if (typeof max !== "number" || max < 1 || !Number.isInteger(max)) { throw BusErrorFactory.badRequest( "setMaxListeners", "Max listeners must be a positive integer", { provided: max } ); } this.maxListeners = max; } /** * Gets the current maximum listeners setting * @returns Maximum listeners per event */ getMaxListeners() { return this.maxListeners; } /** * Checks if the event bus has been destroyed * @returns True if destroyed */ isDestroyedState() { return this.isDestroyed; } /** * Destroys the event bus and cleans up all resources * This operation is irreversible */ destroy() { if (this.isDestroyed) { return; } try { this.listeners.clear(); this.stats = { totalEvents: 0, totalSubscriptions: 0, totalUnsubscriptions: 0, errorCount: 0 }; this.isDestroyed = true; } catch (error) { this.isDestroyed = true; throw wrapError(error, "destroy"); } } /** * Creates a filtered view of the event bus for a specific namespace * @param namespace Namespace prefix to filter events * @returns Namespaced event bus wrapper */ createNamespace(namespace) { this.throwIfDestroyed(); if (!isValidEventName(namespace)) { throw BusErrorFactory.badRequest( "createNamespace", "Namespace must be a valid event name", { namespace } ); } return new NamespacedEventBus(this, namespace); } /** * Throws an error if the bus has been destroyed * @private */ throwIfDestroyed() { if (this.isDestroyed) { throw BusErrorFactory.gone("event-bus", "Event bus has been destroyed"); } } }; var NamespacedEventBus = class { constructor(eventBus, namespace) { this.eventBus = eventBus; this.namespace = namespace; this.isDestroyed = false; } /** * Emits a namespaced event * @param event Event name (will be prefixed with namespace) * @param payload Event payload */ emit(event, payload) { this.eventBus.emit(`${this.namespace}:${event}`, payload); } /** * Subscribes to a namespaced event * @param event Event name (will be prefixed with namespace) * @param handler Event handler * @returns Unsubscribe function */ on(event, handler) { return this.eventBus.on(`${this.namespace}:${event}`, handler); } /** * Subscribes to a namespaced event for one-time execution * @param event Event name (will be prefixed with namespace) * @param handler Event handler * @returns Unsubscribe function */ once(event, handler) { this.throwIfDestroyed(); let unsubscribed = false; let internalUnsubscribe = null; const onceHandler = (payload) => { if (unsubscribed) return; unsubscribed = true; if (internalUnsubscribe) { internalUnsubscribe(); internalUnsubscribe = null; } try { handler(payload); } catch (error) { console.error(`Error in once handler for ${event}:`, error); } }; internalUnsubscribe = this.on(event, onceHandler); return () => { if (!unsubscribed) { unsubscribed = true; if (internalUnsubscribe) { internalUnsubscribe(); internalUnsubscribe = null; } } }; } /** * Removes a specific handler from a namespaced event * @param event Event name (will be prefixed with namespace) * @param handler Handler to remove */ off(event, handler) { this.eventBus.off(`${this.namespace}:${event}`, handler); } /** * Removes all listeners for a namespaced event * @param event Optional event name (will be prefixed with namespace) */ removeAllListeners(event) { if (event !== void 0) { this.eventBus.removeAllListeners(`${this.namespace}:${event}`); } else { const allEvents = this.eventBus.getEventNames(); const namespacePrefix = `${this.namespace}:`; allEvents.filter((eventName) => eventName.startsWith(namespacePrefix)).forEach((eventName) => this.eventBus.removeAllListeners(eventName)); } } /** * Gets listener count for a namespaced event * @param event Event name (will be prefixed with namespace) * @returns Number of listeners */ getListenerCount(event) { return this.eventBus.getListenerCount(`${this.namespace}:${event}`); } /** * Checks if a namespaced event has listeners * @param event Event name (will be prefixed with namespace) * @returns True if event has listeners */ hasListeners(event) { return this.eventBus.hasListeners(`${this.namespace}:${event}`); } /** * Gets all event names within this namespace * @returns Array of event names (without namespace prefix) */ getEventNames() { const allEvents = this.eventBus.getEventNames(); const namespacePrefix = `${this.namespace}:`; return allEvents.filter((eventName) => eventName.startsWith(namespacePrefix)).map((eventName) => eventName.substring(namespacePrefix.length)); } /** * Gets the namespace prefix * @returns Namespace string */ getNamespace() { return this.namespace; } throwIfDestroyed() { if (this.isDestroyed) { throw BusErrorFactory.gone("event-bus", "Event bus has been destroyed"); } } /** * Creates a sub-namespace * @param subNamespace Sub-namespace name * @returns Nested namespaced event bus */ // namespace(subNamespace: string): NamespacedEventBus { // return this.eventBus.createNamespace(`${this.namespace}:${subNamespace}`) // } /** * Destroys this namespaced view (removes all listeners in namespace) */ destroy() { this.isDestroyed = true; this.removeAllListeners(); } }; // src/core/registry.ts var BusRegistry = class { /** * Initializes the registry and sets up global storage * @private */ static initialize() { if (this.isInitialized) { return; } const INIT_KEY = "CONNECTIC_INITIALIZING"; const INSTANCES_KEY = "CONNECTIC_INSTANCES"; try { const globalStore = getGlobalStore(); if (globalStore.has(INIT_KEY)) { let attempts = 0; while (globalStore.has(INIT_KEY) && attempts < 100) { const delay2 = Math.min(attempts * 10, 100); const start = Date.now(); while (Date.now() - start < delay2) { } attempts++; } if (globalStore.has(INIT_KEY)) { throw new Error("Registry initialization deadlock detected"); } } globalStore.set(INIT_KEY, true); try { if (globalStore.has(INSTANCES_KEY)) { this.instances = globalStore.get(INSTANCES_KEY); } else { globalStore.set(INSTANCES_KEY, this.instances); } this.isInitialized = true; } finally { globalStore.delete(INIT_KEY); } } catch (error) { console.warn( "Failed to initialize global bus registry, using fallback storage:", error ); this.fallbackStore = /* @__PURE__ */ new Map(); this.isInitialized = true; } } /** * Gets the storage instance (global or fallback) * @private */ static getStorage() { this.initialize(); return this.fallbackStore || this.instances; } /** * Creates a new bus instance or returns existing one * @param config Bus configuration * @param BusClass Constructor for the bus implementation * @returns Bus instance */ static create(config, BusClass) { try { if (!isValidEventName(config.name)) { throw BusErrorFactory.badRequest( "registry.create", "Bus name must be a valid identifier", { name: config.name } ); } const storage = this.getStorage(); if (storage.has(config.name)) { const existingBus = storage.get(config.name); if (!existingBus.isDestroyedState()) { console.warn( `Bus with name "${config.name}" already exists. Returning existing instance.` ); return existingBus; } else { storage.delete(config.name); } } const bus = new BusClass(config); storage.set(config.name, bus); this.setupBusCleanup(config.name, bus); return bus; } catch (error) { throw wrapError(error, `registry.create:${config.name}`); } } /** * Retrieves an existing bus instance * @param name Bus name * @returns Bus instance or null if not found */ static get(name) { try { if (!isValidEventName(name)) { throw BusErrorFactory.badRequest( "registry.get", "Bus name must be a valid identifier", { name } ); } const storage = this.getStorage(); const bus = storage.get(name); if (!bus) { return null; } if (bus.isDestroyedState()) { storage.delete(name); return null; } return bus; } catch (error) { throw wrapError(error, `registry.get:${name}`); } } /** * Checks if a bus with the given name exists * @param name Bus name * @returns True if bus exists and is not destroyed */ static has(name) { try { if (!isValidEventName(name)) { return false; } const storage = this.getStorage(); const bus = storage.get(name); if (!bus) { return false; } if (bus.isDestroyedState()) { storage.delete(name); return false; } return true; } catch (error) { console.warn(`Error checking bus existence for "${name}":`, error); return false; } } /** * Removes and destroys a bus instance * @param name Bus name * @returns True if bus was removed, false if not found */ static remove(name) { try { if (!isValidEventName(name)) { throw BusErrorFactory.badRequest( "registry.remove", "Bus name must be a valid identifier", { name } ); } const storage = this.getStorage(); const bus = storage.get(name); if (!bus) { return false; } try { if (!bus.isDestroyedState()) { bus.destroy(); } } catch (error) { console.warn(`Error destroying bus "${name}":`, error); } return storage.delete(name); } catch (error) { throw wrapError(error, `registry.remove:${name}`); } } /** * Removes and destroys all bus instances */ static clear() { try { const storage = this.getStorage(); storage.forEach((bus, name) => { try { if (!bus.isDestroyedState()) { bus.destroy(); } } catch (error) { console.warn(`Error destroying bus "${name}" during clear:`, error); } }); storage.clear(); } catch (error) { throw wrapError(error, "registry.clear"); } } /** * Gets all registered bus names * @returns Array of bus names */ static getAll() { try { const storage = this.getStorage(); const names = []; storage.forEach((bus, name) => { if (!bus.isDestroyedState()) { names.push(name); } else { storage.delete(name); } }); return names; } catch (error) { throw wrapError(error, "registry.getAll"); } } /** * Gets statistics for all registered buses * @returns Object mapping bus names to their stats */ static getAllStats() { try { const storage = this.getStorage(); const stats = {}; storage.forEach((bus, name) => { if (!bus.isDestroyedState()) { try { stats[name] = bus.getStats(); } catch (error) { stats[name] = { error: error instanceof Error ? error.message : String(error) }; } } }); return stats; } catch (error) { throw wrapError(error, "registry.getAllStats"); } } /** * Gets registry metadata and health information * @returns Registry information */ static getRegistryInfo() { try { const storage = this.getStorage(); let validBuses = 0; let destroyedBuses = 0; storage.forEach((bus) => { if (bus.isDestroyedState()) { destroyedBuses++; } else { validBuses++; } }); return { totalBuses: storage.size, validBuses, destroyedBuses, isInitialized: this.isInitialized, usingFallbackStorage: this.fallbackStore !== null, storageType: this.fallbackStore ? "fallback" : "global" }; } catch (error) { return { error: error instanceof Error ? error.message : String(error), isInitialized: this.isInitialized, usingFallbackStorage: this.fallbackStore !== null }; } } /** * Performs cleanup operations on the registry * Removes destroyed buses and optimizes memory usage */ static cleanup() { try { const storage = this.getStorage(); const toRemove = []; storage.forEach((bus, name) => { if (bus.isDestroyedState()) { toRemove.push(name); } }); toRemove.forEach((name) => { storage.delete(name); }); if (typeof global !== "undefined" && global.gc) { global.gc(); } } catch (error) { console.warn("Registry cleanup failed:", error); } } /** * Sets up automatic cleanup when a bus is destroyed * @private */ static setupBusCleanup(name, bus) { const originalDestroy = bus.destroy.bind(bus); bus.destroy = () => { try { originalDestroy(); } finally { const storage = this.getStorage(); storage.delete(name); } }; } /** * Force reinitialization of the registry (for testing) * @private */ static _reset() { this.instances.clear(); this.fallbackStore = null; this.isInitialized = false; } }; BusRegistry.instances = /* @__PURE__ */ new Map(); BusRegistry.fallbackStore = null; BusRegistry.isInitialized = false; // src/core/middleware.ts var MiddlewareManager = class { constructor(bus) { this.bus = bus; this.hooks = /* @__PURE__ */ new Map(); this.isDestroyed = false; this.plugins = []; this.pluginDependencies = /* @__PURE__ */ new Map(); } // Bus reference for plugin installation /** * Adds a plugin to the bus * @param plugin Plugin to install */ addPlugin(plugin) { this.throwIfDestroyed(); try { if (!plugin || typeof plugin !== "object") { throw BusErrorFactory.badRequest( "addPlugin", "Plugin must be an object", { plugin } ); } if (typeof plugin.name !== "string" || !plugin.name.trim()) { throw BusErrorFactory.badRequest( "addPlugin", "Plugin must have a non-empty name", { plugin: plugin.name } ); } if (typeof plugin.install !== "function") { throw BusErrorFactory.badRequest( "addPlugin", "Plugin must have an install function", { plugin: plugin.name } ); } if (this.plugins.some((p) => p.name === plugin.name)) { throw BusErrorFactory.conflict("addPlugin", 1, { message: `Plugin "${plugin.name}" is already installed` }); } const dependencies = this.getPluginDependencies(plugin); this.pluginDependencies.set(plugin.name, dependencies); for (const dep of dependencies) { if (!this.hasPlugin(dep)) { throw BusErrorFactory.badRequest( "addPlugin", `Plugin "${plugin.name}" depends on "${dep}" which is not installed`, { plugin: plugin.name, missingDependency: dep } ); } } plugin.install(this.bus); this.plugins.push(plugin); } catch (error) { throw wrapError(error, `addPlugin:${plugin?.name || "unknown"}`); } } /** * Removes a plugin by name * @param pluginName Name of plugin to remove * @returns True if plugin was removed */ removePlugin(pluginName) { this.throwIfDestroyed(); try { const pluginIndex = this.plugins.findIndex((p) => p.name === pluginName); if (pluginIndex === -1) { return false; } const plugin = this.plugins[pluginIndex]; if (typeof plugin.uninstall === "function") { try { plugin.uninstall(this.bus); } catch (error) { console.warn(`Error uninstalling plugin "${pluginName}":`, error); } } this.plugins.splice(pluginIndex, 1); return true; } catch (error) { throw wrapError(error, `removePlugin:${pluginName}`); } } /** * Gets all installed plugins * @returns Array of plugin names */ getPlugins() { return this.plugins.map((p) => p.name); } /** * Checks if a plugin is installed * @param pluginName Plugin name to check * @returns True if plugin is installed */ hasPlugin(pluginName) { return this.plugins.some((p) => p.name === pluginName); } /** * Gets plugin dependencies * @private */ getPluginDependencies(plugin) { if ("dependencies" in plugin && Array.isArray(plugin.dependencies)) { return plugin.dependencies; } return []; } /** * Adds a lifecycle hook * @param type Hook type * @param handler Hook handler function */ addHook(type, handler) { this.throwIfDestroyed(); try { if (!this.hooks.has(type)) { this.hooks.set(type, []); } if (typeof handler !== "function") { throw BusErrorFactory.badRequest( "addHook", "Hook handler must be a function", { type, handler: typeof handler } ); } const handlers = this.hooks.get(type) || []; handlers.push(handler); this.hooks.set(type, handlers); } catch (error) { throw wrapError(error, `addHook:${type}`); } } /** * Removes a specific lifecycle hook * @param type Hook type * @param handler Hook handler to remove */ removeHook(type, handler) { this.throwIfDestroyed(); try { const hooks = this.hooks.get(type); if (!hooks) { return; } const index = hooks.indexOf(handler); if (index > -1) { hooks.splice(index, 1); this.hooks.set(type, hooks); } } catch (error) { throw wrapError(error, `removeHook:${type}`); } } /** * Executes all hooks of a specific type * @param type Hook type to execute * @param event Event name * @param payload Optional payload */ runHooks(type, event, payload) { this.throwIfDestroyed(); try { const hooks = this.hooks.get(type); if (!hooks || hooks.length === 0) { return; } hooks.forEach((hook, index) => { safeExecute( () => hook(event, payload), `${type} hook #${index} for event '${event}'` ); }); } catch (error) { throw wrapError(error, `runHooks:${type}:${event}`); } } /** * Gets statistics about middleware usage * @returns Middleware statistics */ getStats() { return { pluginCount: this.plugins.length, plugins: this.plugins.map((p) => p.name), hookCounts: { beforeEmit: (this.hooks.get("beforeEmit") || []).length, afterEmit: (this.hooks.get("afterEmit") || []).length, beforeOn: (this.hooks.get("beforeOn") || []).length, afterOn: (this.hooks.get("afterOn") || []).length }, totalHooks: Array.from(this.hooks.values()).reduce( (sum, hooks) => sum + hooks.length, 0 ), isDestroyed: this.isDestroyed }; } /** * Destroys the middleware manager and cleans up resources */ destroy() { if (this.isDestroyed) { return; } try { const uninstallOrder = this.calculateDestructionOrder(); uninstallOrder.forEach((plugin) => { try { if (typeof plugin.uninstall === "function") { plugin.uninstall(this.bus); } } catch (error) { console.warn( `Error uninstalling plugin "${plugin.name}" during destroy:`, error ); } }); this.hooks.clear(); this.plugins = []; this.pluginDependencies.clear(); this.isDestroyed = true; } catch (error) { this.isDestroyed = true; throw wrapError(error, "middleware.destroy"); } } /** * Calculates destruction order for plugins * @private */ calculateDestructionOrder() { const result = []; const visited = /* @__PURE__ */ new Set(); const visiting = /* @__PURE__ */ new Set(); const visit = (pluginName) => { if (visited.has(pluginName)) return; if (visiting.has(pluginName)) { return; } visiting.add(pluginName); this.plugins.forEach((plugin2) => { const deps = this.pluginDependencies.get(plugin2.name) || []; if (deps.includes(pluginName) && !visited.has(plugin2.name)) { visit(plugin2.name); } }); visiting.delete(pluginName); visited.add(pluginName); const plugin = this.plugins.find((p) => p.name === pluginName); if (plugin) { result.push(plugin); } }; this.plugins.forEach((plugin) => { visit(plugin.name); }); return result; } /** * Checks if middleware manager is destroyed * @returns True if destroyed */ isDestroyedState() { return this.isDestroyed; } /** * Throws error if middleware manager is destroyed * @private */ throwIfDestroyed() { if (this.isDestroyed) { throw BusErrorFactory.gone( "middleware", "Middleware manager has been destroyed" ); } } }; var ResponderBuilder = class { constructor(eventName, bus) { this.eventName = eventName; this.bus = bus; this.middlewares = []; this.handlerFn = null; this.isInstalled = false; this.composedHandler = null; } /** * Adds middleware to the responder chain * @param middleware Middleware function to add * @returns This builder for chaining */ use(middleware) { if (this.isInstalled) { throw BusErrorFactory.badRequest( "responder.use", "Cannot add middleware after handler is installed", { event: this.eventName } ); } if (typeof middleware !== "function") { throw BusErrorFactory.badRequest( "responder.use", "Middleware must be a function", { event: this.eventName, middleware: typeof middleware } ); } this.middlewares.push(middleware); return this; } /** * Sets the final handler function and installs the responder * @param handlerFn Handler function for processing requests */ handler(handlerFn) { if (this.isInstalled) { throw BusErrorFactory.badRequest( "responder.handler", "Handler already installed for this responder", { event: this.eventName } ); } if (typeof handlerFn !== "function") { throw BusErrorFactory.badRequest( "responder.handler", "Handler must be a function", { event: this.eventName, handler: typeof handlerFn } ); } this.handlerFn = handlerFn; this.installResponder(); this.isInstalled = true; } destroy() { if (!this.isInstalled) { throw BusErrorFactory.badRequest( "responder.destroy", "Responder has not been installed yet", { event: this.eventName } ); } if (!this.composedHandler) { console.warn( `Responder for event "${this.eventName}" has no composed handler to destroy` ); return; } this.bus.off(this.eventName, this.composedHandler); this.isInstalled = false; this.middlewares = []; this.handlerFn = null; this.composedHandler = null; } /** * Installs the responder with middleware chain on the bus * @private */ installResponder() { const composedHandler = async (payload) => { try { const processedPayload = await this.executeMiddleware(payload); if (this.handlerFn) { const result = await this.handlerFn(processedPayload); if (processedPayload.__correlationId) { this.bus.emit(`response:${processedPayload.__correlationId}`, { __correlationId: processedPayload.__correlationId, response: result }); } return result; } throw BusErrorFactory.internal( "No handler function available", void 0, { event: this.eventName } ); } catch (error) { if (payload.__correlationId) { this.bus.emit(`response:${payload.__correlationId}`, { __correlationId: payload.__correlationId, __error: error }); } if (error instanceof BusError) { throw error; } throw wrapError(error, this.eventName); } }; this.composedHandler = composedHandler; this.bus.on(this.eventName, composedHandler); } /** * Executes the middleware chain * @param payload Initial payload * @returns Processed payload after middleware * @private */ async executeMiddleware(payload) { if (this.middlewares.length === 0) { return payload; } let currentPayload = payload; let cancelled = false; let cancelReason; const executionContext = { startTime: Date.now(), executedMiddleware: [] }; for (let i = 0; i < this.middlewares.length; i++) { if (cancelled) { break; } const middleware = this.middlewares[i]; let nextCalled = false; let middlewareError = null; try { await new Promise((resolve, reject) => { const next = () => { if (nextCalled) { reject( new BusError( "next() called multiple times in middleware", 429 /* TOO_MANY_REQUESTS */, { event: this.eventName, middlewareIndex: i, executionContext } ) ); return; } nextCalled = true; resolve(); }; const cancel = (reason) => { cancelled = true; cancelReason = reason; nextCalled = true; resolve(); }; try { const middlewarePromise = Promise.resolve( middleware(currentPayload, next, cancel) ); const timeoutPromise = new Promise((_, timeoutReject) => { setTimeout(() => { timeoutReject( new BusError(`Middleware timeout after 30 seconds`, 408, { event: this.eventName, middlewareIndex: i }) ); }, 3e4); }); Promise.race([middlewarePromise, timeoutPromise]).catch(reject); } catch (syncError) { reject(syncError); } }); executionContext.executedMiddleware.push(i); if (cancelled) { throw BusErrorFactory.forbidden( this.eventName, cancelReason || "Request cancelled by middleware", { middlewareIndex: i, executionContext } ); } if (!nextCalled) { throw BusErrorFactory.forbidden( this.eventName, "Middleware did not call next() or cancel()", { middlewareIndex: i, executionContext } ); } } catch (error) { middlewareError = error instanceof Error ? error : new Error(String(error)); console.warn( `Middleware ${i} failed for event ${this.eventName}:`, middlewareError.message ); throw wrapError(middlewareError, `${this.eventName}:middleware:${i}`); } } return currentPayload; } /** * Gets information about this responder * @returns Responder information */ getInfo() { return { eventName: this.eventName, middlewareCount: this.middlewares.length, isInstalled: this.isInstalled, hasHandler: this.handlerFn !== null }; } }; var BuiltinMiddleware = class { /** * Creates a logging middleware * @param options Logging options * @returns Middleware function */ static logger(options = {}) { const { logPayload = true, prefix = "[connectic]" } = options; return (payload, next) => { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); if (logPayload) { console.log(`${prefix} ${timestamp} Request:`, payload); } else { console.log(`${prefix} ${timestamp} Request received`); } next(); }; } /** * Creates a validation middleware using a validation function * @param validator Validation function * @returns Middleware function */ static validator(validator) { return (payload, next, cancel) => { const result = validator(payload); if (result === true) { next(); } else { const reason = typeof result === "string" ? result : "Validation failed"; cancel(reason); } }; } /** * Creates a rate limiting middleware * @param options Rate limiting options * @returns Middleware function */ static rateLimit(options) { const requests = /* @__PURE__ */ new Map(); const { maxRequests, windowMs } = options; return (payload, next, cancel) => { const key = payload?.key; const now = Date.now(); const windowStart = now - windowMs; const userRequests = requests.get(key) || []; const validRequests = userRequests.filter((time) => time > windowStart); if (validRequests.length >= maxRequests) { cancel( `Rate limit exceeded: ${maxRequests} requests per ${windowMs}ms` ); return; } validRequests.push(now); requests.set(key, validRequests); next(); }; } /** * Creates a timeout middleware * @param timeoutMs Timeout in milliseconds * @returns Middleware function */ static timeout(timeoutMs) { return async (_payload, next, cancel) => { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new BusError(`Middleware timeout after ${timeoutMs}ms`, 408)); }, timeoutMs); }); const nextPromise = new Promise((resolve) => { next(); resolve(); }); try { await Promise.race([nextPromise, timeoutPromise]); } catch (error) { if (error instanceof BusError && error.busCode === 408) { cancel("Middleware execution timeout"); } else { throw error; } } }; } }; // src/core/shared-state.ts var SharedStateManager = class { constructor(bus) { this.bus = bus; this.states = /* @__PURE__ */ new Map(); this.isDestroyed = false; this.dependencyGraph = /* @__PURE__ */ new Map(); this.currentAccessStack = /* @__PURE__ */ new Set(); } /** * Creates a new shared state instance * @param key Unique state identifier * @param initialValue Initial state value * @returns SharedState instance */ createState(key, initialValue) { this.throwIfDestroyed(); try { validateParameters(key); if (this.states.has(key