UNPKG

signalk-server

Version:

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

193 lines 9.01 kB
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