feat: add universal adapters for browser compatibility
- Create universal adapters for cross-platform support (browser/Node/serverless) - Replace Node.js-specific imports with universal implementations - Add OPFS support for browser persistent storage - Maintain same BrainyData interface across all environments - Enable real Brainy usage in browser console UI - Keep package size optimized (no bloat) Universal adapters in /src/universal/: - uuid.ts: Cross-platform UUID generation - crypto.ts: Browser/Node crypto operations - fs.ts: OPFS/FileSystem/Memory storage adapter - path.ts: Universal path operations - events.ts: EventEmitter compatibility layer This enables 'write once, run anywhere' for Brainy while maintaining the exact same API. No breaking changes to existing code.
This commit is contained in:
parent
6e8869ecb7
commit
93804be354
31 changed files with 2120 additions and 2028 deletions
199
src/universal/events.ts
Normal file
199
src/universal/events.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/**
|
||||
* Universal Events implementation
|
||||
* Browser: Uses EventTarget API
|
||||
* Node.js: Uses built-in events module
|
||||
*/
|
||||
|
||||
import { isBrowser, isNode } from '../utils/environment.js'
|
||||
|
||||
let nodeEvents: any = null
|
||||
|
||||
// Dynamic import for Node.js events (only in Node.js environment)
|
||||
if (isNode()) {
|
||||
try {
|
||||
nodeEvents = await import('events')
|
||||
} catch {
|
||||
// Ignore import errors in non-Node environments
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal EventEmitter interface
|
||||
*/
|
||||
export interface UniversalEventEmitter {
|
||||
on(event: string, listener: (...args: any[]) => void): this
|
||||
off(event: string, listener: (...args: any[]) => void): this
|
||||
emit(event: string, ...args: any[]): boolean
|
||||
once(event: string, listener: (...args: any[]) => void): this
|
||||
removeAllListeners(event?: string): this
|
||||
listenerCount(event: string): number
|
||||
}
|
||||
|
||||
/**
|
||||
* Browser implementation using EventTarget
|
||||
*/
|
||||
class BrowserEventEmitter extends EventTarget implements UniversalEventEmitter {
|
||||
private listeners = new Map<string, Set<(...args: any[]) => void>>()
|
||||
|
||||
on(event: string, listener: (...args: any[]) => void): this {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set())
|
||||
}
|
||||
this.listeners.get(event)!.add(listener)
|
||||
|
||||
const handler = (e: Event) => {
|
||||
const customEvent = e as CustomEvent
|
||||
listener(...(customEvent.detail || []))
|
||||
}
|
||||
|
||||
// Store original listener reference for removal
|
||||
;(listener as any).__handler = handler
|
||||
this.addEventListener(event, handler)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
off(event: string, listener: (...args: any[]) => void): this {
|
||||
const eventListeners = this.listeners.get(event)
|
||||
if (eventListeners) {
|
||||
eventListeners.delete(listener)
|
||||
|
||||
const handler = (listener as any).__handler
|
||||
if (handler) {
|
||||
this.removeEventListener(event, handler)
|
||||
delete (listener as any).__handler
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
emit(event: string, ...args: any[]): boolean {
|
||||
const customEvent = new CustomEvent(event, { detail: args })
|
||||
this.dispatchEvent(customEvent)
|
||||
|
||||
const eventListeners = this.listeners.get(event)
|
||||
return eventListeners ? eventListeners.size > 0 : false
|
||||
}
|
||||
|
||||
once(event: string, listener: (...args: any[]) => void): this {
|
||||
const onceListener = (...args: any[]) => {
|
||||
this.off(event, onceListener)
|
||||
listener(...args)
|
||||
}
|
||||
|
||||
return this.on(event, onceListener)
|
||||
}
|
||||
|
||||
removeAllListeners(event?: string): this {
|
||||
if (event) {
|
||||
const eventListeners = this.listeners.get(event)
|
||||
if (eventListeners) {
|
||||
for (const listener of eventListeners) {
|
||||
this.off(event, listener)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [eventName] of this.listeners) {
|
||||
this.removeAllListeners(eventName)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
listenerCount(event: string): number {
|
||||
const eventListeners = this.listeners.get(event)
|
||||
return eventListeners ? eventListeners.size : 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node.js implementation using events.EventEmitter
|
||||
*/
|
||||
class NodeEventEmitter implements UniversalEventEmitter {
|
||||
private emitter: any
|
||||
|
||||
constructor() {
|
||||
this.emitter = new nodeEvents.EventEmitter()
|
||||
}
|
||||
|
||||
on(event: string, listener: (...args: any[]) => void): this {
|
||||
this.emitter.on(event, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
off(event: string, listener: (...args: any[]) => void): this {
|
||||
this.emitter.off(event, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
emit(event: string, ...args: any[]): boolean {
|
||||
return this.emitter.emit(event, ...args)
|
||||
}
|
||||
|
||||
once(event: string, listener: (...args: any[]) => void): this {
|
||||
this.emitter.once(event, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
removeAllListeners(event?: string): this {
|
||||
this.emitter.removeAllListeners(event)
|
||||
return this
|
||||
}
|
||||
|
||||
listenerCount(event: string): number {
|
||||
return this.emitter.listenerCount(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal EventEmitter class
|
||||
*/
|
||||
export class EventEmitter implements UniversalEventEmitter {
|
||||
private emitter: UniversalEventEmitter
|
||||
|
||||
constructor() {
|
||||
if (isBrowser()) {
|
||||
this.emitter = new BrowserEventEmitter()
|
||||
} else if (isNode() && nodeEvents) {
|
||||
this.emitter = new NodeEventEmitter()
|
||||
} else {
|
||||
this.emitter = new BrowserEventEmitter()
|
||||
}
|
||||
}
|
||||
|
||||
on(event: string, listener: (...args: any[]) => void): this {
|
||||
this.emitter.on(event, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
off(event: string, listener: (...args: any[]) => void): this {
|
||||
this.emitter.off(event, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
emit(event: string, ...args: any[]): boolean {
|
||||
return this.emitter.emit(event, ...args)
|
||||
}
|
||||
|
||||
once(event: string, listener: (...args: any[]) => void): this {
|
||||
this.emitter.once(event, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
removeAllListeners(event?: string): this {
|
||||
this.emitter.removeAllListeners(event)
|
||||
return this
|
||||
}
|
||||
|
||||
listenerCount(event: string): number {
|
||||
return this.emitter.listenerCount(event)
|
||||
}
|
||||
}
|
||||
|
||||
// Named export for compatibility
|
||||
export { EventEmitter as default }
|
||||
|
||||
// Re-export Node.js EventEmitter class if available
|
||||
export const NodeEventEmitterClass = nodeEvents?.EventEmitter || null
|
||||
Loading…
Add table
Add a link
Reference in a new issue