signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
193 lines • 9.01 kB
TypeScript
import { StreamBundle } from './streambundle';
import { ContextMatcher, SignalKServer } from './types';
import { NormalizedDelta, SourceRef } from '@signalk/server-api';
interface StringKeyed {
[key: string]: any;
}
/**
* Build a `<label>.<src>` → `<label>.<canName>` map from the Signal K
* sources summary tree.
*
* Providers with `useCanName: true` emit deltas tagged with the canName
* form once the device's PGN 60928 has been observed. Frames that
* arrived before that point — common with UDP gateways that miss the
* early ISO Address Claim — leak out tagged by numeric src and create
* stale leaves that survive in the cache. Returning a translation map
* here lets callers collapse those duplicates back onto the canonical
* canName form.
*
* Providers with `useCanName` off never populate `n2k.canName`; the
* map stays empty and callers see refs unchanged.
*/
export declare function buildSrcToCanonicalMap(sources: unknown): Map<string, string>;
/**
* Collect the IDs of providers in `settings.pipedProviders` that are
* currently enabled. Skips entries with `enabled: false` (matching the
* convention in `pipedproviders.ts`: undefined is treated as enabled).
*
* Returns an empty Set when the settings tree is missing or empty —
* callers must skip the prune in that case so a fresh install (or a
* settings file we can't read) doesn't wipe out the entire cache.
*/
export declare function collectProviderIds(pipedProviders: unknown): Set<string>;
/**
* Remove cache entries whose `<providerId>.<rest>` key prefix is not in
* `knownProviderIds`. Mutates the input object and returns the number
* of entries removed.
*
* If `knownProviderIds` is empty, leaves the cache untouched so a
* misread settings file can't drop the user's whole history.
*/
export declare function pruneSourcesByProvider(cached: Record<string, unknown>, knownProviderIds: Set<string>): number;
export default class DeltaCache {
cache: StringKeyed;
lastModifieds: StringKeyed;
app: SignalKServer;
defaults: any;
sourceDeltas: StringKeyed;
cachedContextPaths: {
[context: string]: {
[path: string]: string[];
};
};
private sourcesCachePath;
private saveTimer;
private preferredSources;
private multiSourceTimer;
private lastEmittedMultiSourceCount;
private livePreferredEmitTimer;
private livePreferredDirtyPaths;
private canonicalMap;
private canonicalSnapshot;
private subscribedPaths;
private routesPath;
constructor(app: SignalKServer, streambundle: StreamBundle);
getContextAndPathParts(msg: NormalizedDelta): string[];
private canonicaliseSourceRef;
onValue(msg: NormalizedDelta): void;
private scheduleMultiSourceEmit;
private scheduleLivePreferredEmit;
/**
* Snapshot of the current "winning" source per path, keyed as
* `${context}\0${path}`. Updated by onValue on every accepted delta;
* reflects what the priority engine is actually routing right now.
*/
getLivePreferredSources(): Record<string, string>;
private emitMultiSourcePaths;
/**
* Remove all cached deltas for a given sourceRef and re-emit
* MULTISOURCEPATHS so the UI reflects the change. For every path whose
* preferred source pointed at the removed ref, pick a deterministic
* replacement from the remaining leaf entries so filterDeltasToPreferred
* doesn't fall back to whatever Object.keys iterates first.
*/
/**
* Inject the engine's "is this (path, sourceRef) routed?" predicate
* so onValue can avoid recording a preferred winner for paths the
* engine isn't filtering. Called from activateSourcePriorities each
* time the engine is rebuilt; pass null to drop the gate.
*/
setRoutesPathPredicate(fn: ((path: string, sourceRef: string) => boolean) | null): void;
removeSource(sourceRef: SourceRef): void;
private pickReplacementSource;
/**
* Remove a source delta entry by key, purge all cached data for the
* corresponding sourceRefs (numeric address and CAN Name forms), and
* persist the updated sources cache to disk.
*/
removeSourceDelta(key: string): void;
ingestDelta(delta: any): void;
setSourceDelta(key: string, delta: any): void;
private getSourcesCachePath;
private loadSourcesCache;
private scheduleSaveSourcesCache;
private saveSourcesCache;
deleteContext(contextKey: string): void;
pruneContexts(seconds: number): void;
buildFull(user: string, path: string[]): Record<string, any>;
/**
* Return paths on the self vessel that have more than one source.
* Result: { path: sourceRef[] } for each multi-source path.
*
* Persisted source priorities are also unioned in. Without this, a path
* whose user-ranked sources are temporarily silent (e.g. an MFD that has
* been powered off) drops to one or zero live publishers and disappears
* from the multi-source view — the admin-ui then shows the path's
* persisted entries under "Ungrouped" and surprises the user. Keeping
* the persisted view in lets the priorities page render the user's
* intent stably across power cycles.
*/
/**
* Return every (path, publishers[]) pair the cache has seen for the
* self vessel, regardless of publisher count. Used to seed the
* priority engine's `seenPublishersByPath` so routesPath flips on
* for already-contended paths immediately at boot, instead of
* flickering "no priority configured" warnings until live deltas
* land. Refs are returned in canonical (canName) form to match the
* engine's identity rule.
*/
getSelfPathPublishers(): Record<string, string[]>;
getMultiSourcePaths(): Record<string, string[]>;
/**
* Supplementary `<conn>.<src>` → `<conn>.<canName>` translation built
* from the per-source delta store. `buildSrcToCanonicalMap` goes blind
* when `app.signalk.sources` has no `n2k.canName` for a numeric src —
* the cold-boot / UDP-gateway late-join window the comment at the top
* of this file describes — but a metadata delta carrying the resolved
* canName may already sit in `sourceDeltas` keyed by the same numeric
* src. Recovering it here lets a device that left a stale numeric leaf
* AND a canName leaf in the cache collapse onto one ref, so the two
* forms of one physical device don't land in two reconciled groups and
* trip the "a source may belong to at most one active group" save
* validator.
*
* Each `sourceDeltas` entry carries exactly one canName, so the map is
* single-valued per key and never merges two physically-different
* devices. Keys are usually numeric-form (the `setSourceDelta` key,
* n2k-signalk.ts) but `loadSourcesCache` can hydrate canName-form keys
* from disk; those map onto themselves (a harmless identity), which
* leaves the ref unchanged.
*/
private buildSourceDeltaCanonicalMap;
/**
* Source-ref canonicaliser shared by the three cache-walk callers
* (getReconciledGroups, getSelfPathPublishers, getMultiSourcePaths).
* Prefers the live sources tree, then falls back to the canName
* recovered from the source-delta store for numeric refs the tree
* hasn't resolved yet. Routing all three through one closure keeps the
* reconciled groups, the engine's seenPublishersByPath seed and the
* multi-source UI from disagreeing about a device's ref during the
* late-join window.
*/
private reconcileCanonical;
/**
* Compute reconciled priority groups for the admin UI: saved groups
* are authoritative (composition is fixed by priorityGroups, not by
* who is currently live), unsaved sources fall through to connected-
* components discovery on multiSourcePaths.
*
* For each saved group:
* - sources = saved order + newcomers (sources currently publishing a
* group path but not in the saved list)
* - paths = paths historically published by ≥2 of the saved sources,
* excluding paths owned by another active group (the group with
* more current live publishers wins a shared path)
*
* For unsaved residue: standard connected-components grouping with
* the ≥2-live-publisher edge rule.
*/
getReconciledGroups(): Array<{
id: string;
matchedSavedId: string | null;
inactive?: boolean;
sources: string[];
paths: string[];
newcomerSources: string[];
}>;
getSources(): any;
buildFullFromDeltas(user: string, deltas: any[] | undefined, includeSources: boolean): Record<string, any>;
private filterDeltasToPreferred;
getCachedDeltas(contextFilter: ContextMatcher, user?: string, key?: string, sourcePolicy?: 'preferred' | 'all'): import("@signalk/server-api").Delta[];
}
export {};
//# sourceMappingURL=deltacache.d.ts.map