signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
233 lines • 7.63 kB
JavaScript
;
/* 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