/** * Strata Core Runtime * Main entry point for browser runtime */ import { StrataStore, createStore, useStore } from '../store/store.sts'; import { StrataFetch } from '../fetch/fetch.sts'; interface StrataConfig { encryptionKey?: number[]; apiBaseUrl?: string; devMode?: boolean; } interface BroadcastHandler { (data: any): void; } class Strata { private worker: SharedWorker | null = null; private _tabId: string = ''; private messageHandlers: Map = new Map(); private broadcastHandlers: Map = new Map(); private pendingRequests: Map = new Map(); private config: StrataConfig = {}; private _fetch: StrataFetch | null = null; get tabId(): string { return this._tabId; } get fetch(): StrataFetch { if (!this._fetch) { throw new Error('Strata not initialized. Call strata.init() first.'); } return this._fetch; } async init(config: StrataConfig = {}): Promise { this.config = config; // Initialize SharedWorker if (typeof SharedWorker !== 'undefined') { this.worker = new SharedWorker( new URL('../worker/shared-worker.sts', import.meta.url), { type: 'module', name: 'strata-worker' } ); this.worker.port.onmessage = (event) => this.handleWorkerMessage(event.data); this.worker.port.start(); // Send encryption key if provided if (config.encryptionKey) { this.worker.port.postMessage({ type: 'setEncryptionKey', key: config.encryptionKey, }); } // Wait for connection await this.waitForConnection(); } else { // Fallback for browsers without SharedWorker support this._tabId = 'tab_' + Math.random().toString(36).slice(2, 10); console.warn('SharedWorker not supported, falling back to single-tab mode'); } // Initialize fetch this._fetch = new StrataFetch(this); // Handle page unload window.addEventListener('beforeunload', () => { this.disconnect(); }); if (config.devMode) { (window as any).__STRATA__ = this; console.log(`Strata initialized (tabId: ${this._tabId})`); } } private waitForConnection(): Promise { return new Promise((resolve) => { const handler = (message: any) => { if (message.type === 'connected') { this._tabId = message.tabId; resolve(); } }; this.addMessageHandler('connected', handler); }); } private handleWorkerMessage(message: any) { // Handle specific message types switch (message.type) { case 'connected': this._tabId = message.tabId; break; case 'fetch:response': case 'fetch:error': this.handleFetchResponse(message); break; case 'store:value': this.handleStoreValue(message); break; case 'broadcast': this.handleBroadcast(message); break; } // Call registered handlers const handlers = this.messageHandlers.get(message.type) || []; for (const handler of handlers) { handler(message); } } private handleFetchResponse(message: any) { const pending = this.pendingRequests.get(message.requestId); if (pending) { if (message.type === 'fetch:error') { pending.reject(new Error(message.error)); } else { pending.resolve(message.data); } this.pendingRequests.delete(message.requestId); } } private handleStoreValue(message: any) { // Handled by store subscriptions } private handleBroadcast(message: any) { const handlers = this.broadcastHandlers.get(message.event) || []; for (const handler of handlers) { handler(message.data); } // Also call wildcard handlers const wildcardHandlers = this.broadcastHandlers.get('*') || []; for (const handler of wildcardHandlers) { handler({ event: message.event, data: message.data }); } } private addMessageHandler(type: string, handler: Function) { if (!this.messageHandlers.has(type)) { this.messageHandlers.set(type, []); } this.messageHandlers.get(type)!.push(handler); } // Public API sendToWorker(message: any): void { if (this.worker) { this.worker.port.postMessage(message); } } async fetchViaWorker(url: string, options?: any): Promise { const requestId = crypto.randomUUID(); return new Promise((resolve, reject) => { this.pendingRequests.set(requestId, { resolve, reject }); this.sendToWorker({ type: 'fetch', url, options, requestId, }); // Timeout after 30 seconds setTimeout(() => { if (this.pendingRequests.has(requestId)) { this.pendingRequests.delete(requestId); reject(new Error('Request timeout')); } }, 30000); }); } broadcast(event: string, data?: any, targetTabs?: string[]): void { this.sendToWorker({ type: 'broadcast', event, data, targetTabs, }); } onBroadcast(event: string, handler: BroadcastHandler): () => void { if (!this.broadcastHandlers.has(event)) { this.broadcastHandlers.set(event, []); } this.broadcastHandlers.get(event)!.push(handler); // Return unsubscribe function return () => { const handlers = this.broadcastHandlers.get(event); if (handlers) { const index = handlers.indexOf(handler); if (index > -1) { handlers.splice(index, 1); } } }; } invalidateCache(pattern: string = '*'): void { this.sendToWorker({ type: 'cache:invalidate', pattern, }); } private disconnect(): void { this.sendToWorker({ type: 'disconnect' }); } } // Singleton instance export const strata = new Strata(); // Re-exports export { createStore, useStore }; export type { StrataStore };