fixed build libs
This commit is contained in:
parent
b03231b849
commit
42baadae38
26 changed files with 981 additions and 541 deletions
|
|
@ -1,206 +0,0 @@
|
|||
import { beforeEach, describe, expect, it, mock } from 'bun:test';
|
||||
import { SimpleEventBus } from './simple-event-bus';
|
||||
import type { EventHandler, EventSubscription } from './types';
|
||||
|
||||
describe('EventBus', () => {
|
||||
let eventBus: SimpleEventBus;
|
||||
|
||||
beforeEach(() => {
|
||||
eventBus = new SimpleEventBus();
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
it('should subscribe to events', () => {
|
||||
const handler: EventHandler = mock(async () => {});
|
||||
|
||||
const subscription = eventBus.subscribe('test-event', handler);
|
||||
|
||||
expect(subscription).toBeDefined();
|
||||
expect(subscription.id).toBeDefined();
|
||||
expect(subscription.event).toBe('test-event');
|
||||
});
|
||||
|
||||
it('should allow multiple subscribers to same event', () => {
|
||||
const handler1 = mock(async () => {});
|
||||
const handler2 = mock(async () => {});
|
||||
|
||||
const sub1 = eventBus.subscribe('event', handler1);
|
||||
const sub2 = eventBus.subscribe('event', handler2);
|
||||
|
||||
expect(sub1.id).not.toBe(sub2.id);
|
||||
});
|
||||
|
||||
it('should support pattern subscriptions', () => {
|
||||
const handler = mock(async () => {});
|
||||
|
||||
const subscription = eventBus.subscribe('user.*', handler);
|
||||
|
||||
expect(subscription.event).toBe('user.*');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsubscribe', () => {
|
||||
it('should unsubscribe by subscription object', () => {
|
||||
const handler = mock(async () => {});
|
||||
const subscription = eventBus.subscribe('event', handler);
|
||||
|
||||
const result = eventBus.unsubscribe(subscription);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should unsubscribe by id', () => {
|
||||
const handler = mock(async () => {});
|
||||
const subscription = eventBus.subscribe('event', handler);
|
||||
|
||||
const result = eventBus.unsubscribe(subscription.id);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-existent subscription', () => {
|
||||
const result = eventBus.unsubscribe('non-existent-id');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish', () => {
|
||||
it('should publish events to subscribers', async () => {
|
||||
const handler = mock(async (data: any) => {});
|
||||
eventBus.subscribe('test-event', handler);
|
||||
|
||||
await eventBus.publish('test-event', { message: 'hello' });
|
||||
|
||||
expect(handler).toHaveBeenCalledWith({ message: 'hello' }, 'test-event');
|
||||
});
|
||||
|
||||
it('should publish to multiple subscribers', async () => {
|
||||
const handler1 = mock(async () => {});
|
||||
const handler2 = mock(async () => {});
|
||||
|
||||
eventBus.subscribe('event', handler1);
|
||||
eventBus.subscribe('event', handler2);
|
||||
|
||||
await eventBus.publish('event', { data: 'test' });
|
||||
|
||||
expect(handler1).toHaveBeenCalledWith({ data: 'test' }, 'event');
|
||||
expect(handler2).toHaveBeenCalledWith({ data: 'test' }, 'event');
|
||||
});
|
||||
|
||||
it('should match pattern subscriptions', async () => {
|
||||
const handler = mock(async () => {});
|
||||
eventBus.subscribe('user.*', handler);
|
||||
|
||||
await eventBus.publish('user.created', { id: 1 });
|
||||
await eventBus.publish('user.updated', { id: 2 });
|
||||
await eventBus.publish('order.created', { id: 3 });
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(2);
|
||||
expect(handler).toHaveBeenCalledWith({ id: 1 }, 'user.created');
|
||||
expect(handler).toHaveBeenCalledWith({ id: 2 }, 'user.updated');
|
||||
});
|
||||
|
||||
it('should handle errors in handlers gracefully', async () => {
|
||||
const errorHandler = mock(async () => {
|
||||
throw new Error('Handler error');
|
||||
});
|
||||
const successHandler = mock(async () => {});
|
||||
|
||||
eventBus.subscribe('event', errorHandler);
|
||||
eventBus.subscribe('event', successHandler);
|
||||
|
||||
await eventBus.publish('event', {});
|
||||
|
||||
expect(successHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('publishSync', () => {
|
||||
it('should publish synchronously', () => {
|
||||
const results: any[] = [];
|
||||
const handler = (data: any) => {
|
||||
results.push(data);
|
||||
};
|
||||
|
||||
eventBus.subscribe('sync-event', handler as any);
|
||||
eventBus.publishSync('sync-event', { value: 42 });
|
||||
|
||||
expect(results).toEqual([{ value: 42 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('once', () => {
|
||||
it('should subscribe for single event', async () => {
|
||||
const handler = mock(async () => {});
|
||||
|
||||
eventBus.once('once-event', handler);
|
||||
|
||||
await eventBus.publish('once-event', { first: true });
|
||||
await eventBus.publish('once-event', { second: true });
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
expect(handler).toHaveBeenCalledWith({ first: true }, 'once-event');
|
||||
});
|
||||
});
|
||||
|
||||
describe('off', () => {
|
||||
it('should remove all handlers for event', async () => {
|
||||
const handler1 = mock(async () => {});
|
||||
const handler2 = mock(async () => {});
|
||||
|
||||
eventBus.subscribe('event', handler1);
|
||||
eventBus.subscribe('event', handler2);
|
||||
|
||||
eventBus.off('event');
|
||||
|
||||
await eventBus.publish('event', {});
|
||||
|
||||
expect(handler1).not.toHaveBeenCalled();
|
||||
expect(handler2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove specific handler', async () => {
|
||||
const handler1 = mock(async () => {});
|
||||
const handler2 = mock(async () => {});
|
||||
|
||||
eventBus.subscribe('event', handler1);
|
||||
eventBus.subscribe('event', handler2);
|
||||
|
||||
eventBus.off('event', handler1);
|
||||
|
||||
await eventBus.publish('event', {});
|
||||
|
||||
expect(handler1).not.toHaveBeenCalled();
|
||||
expect(handler2).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasSubscribers', () => {
|
||||
it('should check for subscribers', () => {
|
||||
expect(eventBus.hasSubscribers('event')).toBe(false);
|
||||
|
||||
const sub = eventBus.subscribe('event', async () => {});
|
||||
expect(eventBus.hasSubscribers('event')).toBe(true);
|
||||
|
||||
eventBus.unsubscribe(sub);
|
||||
expect(eventBus.hasSubscribers('event')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('should clear all subscriptions', async () => {
|
||||
const handler = mock(async () => {});
|
||||
|
||||
eventBus.subscribe('event1', handler);
|
||||
eventBus.subscribe('event2', handler);
|
||||
|
||||
eventBus.clear();
|
||||
|
||||
await eventBus.publish('event1', {});
|
||||
await eventBus.publish('event2', {});
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,51 +1,77 @@
|
|||
import type { EventHandler, EventSubscription } from './types';
|
||||
import type { EventHandler, EventSubscription, EventBusMessage } from './types';
|
||||
|
||||
/**
|
||||
* Simple in-memory event bus for testing
|
||||
*/
|
||||
export class SimpleEventBus {
|
||||
private subscriptions = new Map<string, Set<EventSubscription>>();
|
||||
private subscriptionById = new Map<string, EventSubscription>();
|
||||
private subscriptions = new Map<string, Set<{ id: string; handler: EventHandler }>>();
|
||||
private subscriptionById = new Map<string, { id: string; channel: string; handler: EventHandler }>();
|
||||
private nextId = 1;
|
||||
|
||||
subscribe(event: string, handler: EventHandler): EventSubscription {
|
||||
const subscription: EventSubscription = {
|
||||
id: `sub-${this.nextId++}`,
|
||||
event,
|
||||
handler,
|
||||
pattern: event.includes('*'),
|
||||
};
|
||||
subscribe(channel: string, handler: EventHandler): EventSubscription {
|
||||
const id = `sub-${this.nextId++}`;
|
||||
const subscription = { id, handler };
|
||||
|
||||
if (!this.subscriptions.has(event)) {
|
||||
this.subscriptions.set(event, new Set());
|
||||
if (!this.subscriptions.has(channel)) {
|
||||
this.subscriptions.set(channel, new Set());
|
||||
}
|
||||
this.subscriptions.get(event)!.add(subscription);
|
||||
this.subscriptionById.set(subscription.id, subscription);
|
||||
this.subscriptions.get(channel)!.add(subscription);
|
||||
this.subscriptionById.set(id, { id, channel, handler });
|
||||
|
||||
return subscription;
|
||||
return { channel, handler };
|
||||
}
|
||||
|
||||
unsubscribe(idOrSubscription: string | EventSubscription): boolean {
|
||||
const id = typeof idOrSubscription === 'string' ? idOrSubscription : idOrSubscription.id;
|
||||
const subscription = this.subscriptionById.get(id);
|
||||
|
||||
if (!subscription) {
|
||||
if (typeof idOrSubscription === 'string') {
|
||||
const subscription = this.subscriptionById.get(idOrSubscription);
|
||||
if (!subscription) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const channelSubs = this.subscriptions.get(subscription.channel);
|
||||
if (channelSubs) {
|
||||
channelSubs.forEach(sub => {
|
||||
if (sub.id === idOrSubscription) {
|
||||
channelSubs.delete(sub);
|
||||
}
|
||||
});
|
||||
if (channelSubs.size === 0) {
|
||||
this.subscriptions.delete(subscription.channel);
|
||||
}
|
||||
}
|
||||
|
||||
this.subscriptionById.delete(idOrSubscription);
|
||||
return true;
|
||||
} else {
|
||||
// Unsubscribe by matching handler and channel
|
||||
const channelSubs = this.subscriptions.get(idOrSubscription.channel);
|
||||
if (channelSubs) {
|
||||
let removed = false;
|
||||
channelSubs.forEach(sub => {
|
||||
if (sub.handler === idOrSubscription.handler) {
|
||||
channelSubs.delete(sub);
|
||||
this.subscriptionById.delete(sub.id);
|
||||
removed = true;
|
||||
}
|
||||
});
|
||||
if (channelSubs.size === 0) {
|
||||
this.subscriptions.delete(idOrSubscription.channel);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const eventSubs = this.subscriptions.get(subscription.event);
|
||||
if (eventSubs) {
|
||||
eventSubs.delete(subscription);
|
||||
if (eventSubs.size === 0) {
|
||||
this.subscriptions.delete(subscription.event);
|
||||
}
|
||||
}
|
||||
|
||||
this.subscriptionById.delete(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
async publish(event: string, data: any): Promise<void> {
|
||||
const message: EventBusMessage = {
|
||||
id: `msg-${this.nextId++}`,
|
||||
type: event,
|
||||
source: 'simple-event-bus',
|
||||
timestamp: Date.now(),
|
||||
data,
|
||||
};
|
||||
|
||||
const handlers: EventHandler[] = [];
|
||||
|
||||
// Direct matches
|
||||
|
|
@ -64,7 +90,7 @@ export class SimpleEventBus {
|
|||
// Execute all handlers
|
||||
await Promise.all(
|
||||
handlers.map(handler =>
|
||||
handler(data, event).catch(err => {
|
||||
Promise.resolve(handler(message)).catch(err => {
|
||||
// Silently catch errors
|
||||
})
|
||||
)
|
||||
|
|
@ -72,6 +98,14 @@ export class SimpleEventBus {
|
|||
}
|
||||
|
||||
publishSync(event: string, data: any): void {
|
||||
const message: EventBusMessage = {
|
||||
id: `msg-${this.nextId++}`,
|
||||
type: event,
|
||||
source: 'simple-event-bus',
|
||||
timestamp: Date.now(),
|
||||
data,
|
||||
};
|
||||
|
||||
const handlers: EventHandler[] = [];
|
||||
|
||||
// Direct matches
|
||||
|
|
@ -90,7 +124,7 @@ export class SimpleEventBus {
|
|||
// Execute all handlers synchronously
|
||||
handlers.forEach(handler => {
|
||||
try {
|
||||
handler(data, event);
|
||||
handler(message);
|
||||
} catch {
|
||||
// Silently catch errors
|
||||
}
|
||||
|
|
@ -98,12 +132,20 @@ export class SimpleEventBus {
|
|||
}
|
||||
|
||||
once(event: string, handler: EventHandler): EventSubscription {
|
||||
const wrappedHandler: EventHandler = async (data, evt) => {
|
||||
await handler(data, evt);
|
||||
this.unsubscribe(subscription.id);
|
||||
let subId: string;
|
||||
const wrappedHandler: EventHandler = async (message) => {
|
||||
await handler(message);
|
||||
this.unsubscribe(subId);
|
||||
};
|
||||
|
||||
const subscription = this.subscribe(event, wrappedHandler);
|
||||
// Find the subscription ID
|
||||
this.subscriptionById.forEach((value, key) => {
|
||||
if (value.handler === wrappedHandler) {
|
||||
subId = key;
|
||||
}
|
||||
});
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
|
|
@ -121,10 +163,19 @@ export class SimpleEventBus {
|
|||
// Remove specific handler
|
||||
const subs = this.subscriptions.get(event);
|
||||
if (subs) {
|
||||
const toRemove = Array.from(subs).filter(s => s.handler === handler);
|
||||
toRemove.forEach(sub => {
|
||||
subs.delete(sub);
|
||||
this.subscriptionById.delete(sub.id);
|
||||
const toRemove: string[] = [];
|
||||
subs.forEach(sub => {
|
||||
if (sub.handler === handler) {
|
||||
toRemove.push(sub.id);
|
||||
}
|
||||
});
|
||||
toRemove.forEach(id => {
|
||||
subs.forEach(sub => {
|
||||
if (sub.id === id) {
|
||||
subs.delete(sub);
|
||||
}
|
||||
});
|
||||
this.subscriptionById.delete(id);
|
||||
});
|
||||
if (subs.size === 0) {
|
||||
this.subscriptions.delete(event);
|
||||
|
|
@ -147,4 +198,4 @@ export class SimpleEventBus {
|
|||
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
||||
return regex.test(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue