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
JavaScript
'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