UNPKG

signalk-server

Version:

An implementation of a [Signal K](http://signalk.org) server for boats.

233 lines 7.63 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ /** * WASM Plugin Delta Subscription Management * * Manages delta subscriptions for WASM plugins, including: * - Subscription pattern matching * - Buffering during hot-reload * - Subscription state preservation across reloads */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WasmSubscriptionManager = void 0; exports.getSubscriptionManager = getSubscriptionManager; exports.initializeSubscriptionManager = initializeSubscriptionManager; exports.resetSubscriptionManager = resetSubscriptionManager; const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)('signalk:wasm:subscriptions'); class WasmSubscriptionManager { // Active subscriptions by plugin ID subscriptions = new Map(); // Buffered deltas during reload buffers = new Map(); // Buffering state buffering = new Set(); /** * Register a delta subscription for a plugin */ register(pluginId, pattern, callback) { if (!this.subscriptions.has(pluginId)) { this.subscriptions.set(pluginId, []); } const subscription = { pluginId, pattern, callback }; this.subscriptions.get(pluginId).push(subscription); debug(`Registered subscription for ${pluginId}: ${pattern}`); } /** * Unregister all subscriptions for a plugin */ unregister(pluginId) { const count = this.subscriptions.get(pluginId)?.length || 0; this.subscriptions.delete(pluginId); debug(`Unregistered ${count} subscriptions for ${pluginId}`); } /** * Get all subscriptions for a plugin */ getSubscriptions(pluginId) { return this.subscriptions.get(pluginId) || []; } /** * Check if a delta path matches a subscription pattern */ matchesPattern(path, pattern) { if (pattern === '*') { return true; } // Simple glob-style matching // "navigation.*" matches "navigation.position", "navigation.courseOverGroundTrue", etc. const regexPattern = pattern .replace(/[\\^$+?()[\]{}|]/g, '\\$&') .replace(/\./g, '\\.') .replace(/\*/g, '.*'); const regex = new RegExp(`^${regexPattern}$`); return regex.test(path); } /** * Route a delta to subscribed plugins */ routeDelta(delta) { for (const [pluginId, subs] of this.subscriptions) { // Check if buffering for this plugin if (this.buffering.has(pluginId)) { this.bufferDelta(pluginId, delta); continue; } // Check if any subscription matches delta paths for (const sub of subs) { let matches = false; for (const update of delta.updates) { for (const pathValue of update.values) { if (this.matchesPattern(pathValue.path, sub.pattern)) { matches = true; break; } } if (matches) break; } if (matches) { try { sub.callback(delta); } catch (error) { debug(`Error in subscription callback for ${pluginId}:`, error); } break; // Only call once per plugin per delta } } } } /** * Start buffering deltas for a plugin (during reload) */ startBuffering(pluginId) { debug(`Started buffering deltas for ${pluginId}`); this.buffering.add(pluginId); this.buffers.set(pluginId, []); } /** * Stop buffering and return buffered deltas */ stopBuffering(pluginId) { debug(`Stopped buffering deltas for ${pluginId}`); this.buffering.delete(pluginId); const buffered = this.buffers.get(pluginId) || []; this.buffers.delete(pluginId); debug(`Returning ${buffered.length} buffered deltas for ${pluginId}`); return buffered; } /** * Buffer a delta for a plugin */ bufferDelta(pluginId, delta) { if (!this.buffers.has(pluginId)) { this.buffers.set(pluginId, []); } const buffer = this.buffers.get(pluginId); buffer.push(delta); // Limit buffer size to prevent memory issues const MAX_BUFFER_SIZE = 1000; if (buffer.length > MAX_BUFFER_SIZE) { buffer.shift(); // Remove oldest debug(`Buffer overflow for ${pluginId}, dropped oldest delta`); } } /** * Redirect delta routing to buffer for a plugin */ redirectToBuffer(pluginId) { this.startBuffering(pluginId); } /** * Restore normal delta routing for a plugin */ restore(pluginId) { this.stopBuffering(pluginId); } /** * Replay buffered deltas to a plugin's callback */ replayBuffered(pluginId, callback) { const buffered = this.buffers.get(pluginId) || []; debug(`Replaying ${buffered.length} buffered deltas to ${pluginId}`); for (const delta of buffered) { try { callback(delta); } catch (error) { debug(`Error replaying delta to ${pluginId}:`, error); } } // Clear buffer after replay this.buffers.delete(pluginId); } /** * Get statistics about subscriptions */ getStats() { let totalSubscriptions = 0; for (const subs of this.subscriptions.values()) { totalSubscriptions += subs.length; } let bufferedDeltas = 0; for (const buffer of this.buffers.values()) { bufferedDeltas += buffer.length; } return { totalSubscriptions, activePlugins: this.subscriptions.size, bufferingPlugins: this.buffering.size, bufferedDeltas }; } /** * Clear all subscriptions and buffers */ clear() { this.subscriptions.clear(); this.buffers.clear(); this.buffering.clear(); debug('Cleared all subscriptions and buffers'); } } exports.WasmSubscriptionManager = WasmSubscriptionManager; // Global singleton instance let subscriptionManager = null; /** * Get the global subscription manager */ function getSubscriptionManager() { if (!subscriptionManager) { subscriptionManager = new WasmSubscriptionManager(); } return subscriptionManager; } /** * Initialize the subscription manager */ function initializeSubscriptionManager() { if (subscriptionManager) { debug('Subscription manager already initialized'); return subscriptionManager; } subscriptionManager = new WasmSubscriptionManager(); debug('Subscription manager initialized'); return subscriptionManager; } /** * Reset the subscription manager singleton (for hotplug support) * This should be called after shutdown to allow re-initialization */ function resetSubscriptionManager() { debug('Resetting subscription manager singleton'); subscriptionManager = null; } //# sourceMappingURL=wasm-subscriptions.js.map