signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
187 lines • 7.56 kB
JavaScript
;
/*
* Persistence of source-priority state in its own file, `priorities.json`,
* alongside `settings.json`.
*
* Why separate: priority configuration is user-accumulated state that grows
* with every source seen on the bus. When it drifts into an inconsistent
* shape (wrong refs after a transport swap, outdated aliases, bad groups)
* users want a way to reset it without touching the rest of the server
* configuration. Keeping it in its own file makes the recovery path as
* simple as deleting one file.
*
* The In-Memory representation under `app.config.settings.*` is unchanged —
* callers keep reading and writing the same keys. Only the file I/O
* boundary is split.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PRIORITIES_KEYS = void 0;
exports.getPrioritiesFilePath = getPrioritiesFilePath;
exports.loadPrioritiesIntoSettings = loadPrioritiesIntoSettings;
exports.migratePrioritiesIntoSeparateFile = migratePrioritiesIntoSeparateFile;
exports.splitPrioritiesFromSettings = splitPrioritiesFromSettings;
exports.writePrioritiesFile = writePrioritiesFile;
exports.resetPriorities = resetPriorities;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const atomicWrite_1 = require("../atomicWrite");
const debug_1 = require("../debug");
const debug = (0, debug_1.createDebug)('signalk-server:priorities-file');
const PRIORITIES_FILE = 'priorities.json';
exports.PRIORITIES_KEYS = [
'priorityGroups',
'priorityOverrides',
'priorityDefaults',
'sourceAliases',
'ignoredInstanceConflicts'
];
const LEGACY_KEYS = ['sourcePriorities', 'sourcePriorityOverrides'];
/**
* Fold any legacy schema keys into the new shape. Older priorities.json
* files used `sourcePriorities` (per-path map) and `sourcePriorityOverrides`
* (paths list). The new model has only `priorityOverrides` (per-path map
* of explicit user overrides); group rankings are resolved by the engine
* dynamically. The legacy per-path map maps directly onto the new
* priorityOverrides map; the override-paths list is no longer needed
* because override-ness is implicit in the path having an entry.
*/
function foldLegacyKeys(stored) {
if ('sourcePriorities' in stored && !('priorityOverrides' in stored)) {
stored.priorityOverrides = stored.sourcePriorities;
}
for (const k of LEGACY_KEYS)
delete stored[k];
}
function getPrioritiesFilePath(app) {
return path_1.default.join(app.config.configPath, PRIORITIES_FILE);
}
function readPrioritiesFile(app) {
const file = getPrioritiesFilePath(app);
if (!fs_1.default.existsSync(file))
return undefined;
try {
return JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
}
catch (e) {
console.error(`Error reading ${PRIORITIES_FILE} — ignoring. Delete the file to reset.`, e);
return undefined;
}
}
/**
* Merge stored priorities into in-memory settings. When a priorities.json
* exists on disk, its values take precedence over whatever happened to be
* in settings.json (because settings.json might still carry stale copies
* left over from an older install). When it does not exist yet, leave
* settings alone — the migration step below is responsible for creating
* it.
*/
function loadPrioritiesIntoSettings(app) {
const stored = readPrioritiesFile(app);
if (!stored)
return;
foldLegacyKeys(stored);
for (const key of exports.PRIORITIES_KEYS) {
if (key in stored) {
app.config.settings[key] = stored[key];
}
}
// Strip legacy keys from in-memory settings so they don't get re-persisted.
for (const k of LEGACY_KEYS)
delete app.config.settings[k];
debug('Loaded priorities from %s', PRIORITIES_FILE);
}
/**
* One-shot migration from settings.json to priorities.json. Called at
* server start after settings are loaded. If the separate file does not
* exist yet but settings.json has any priority keys, copy them over and
* strip them from settings. The caller is responsible for persisting the
* stripped settings.json — we only mutate in-memory state and write the
* new priorities.json.
*
* Returns true if settings.json changed and should be written out.
*/
function migratePrioritiesIntoSeparateFile(app) {
if (readPrioritiesFile(app))
return false;
const settings = app.config.settings;
// Fold legacy keys into the new shape before extracting, so settings.json
// installs from older versions emerge as priorityOverrides on disk.
foldLegacyKeys(settings);
const present = {};
for (const key of exports.PRIORITIES_KEYS) {
if (key in settings) {
present[key] = settings[key];
}
}
if (Object.keys(present).length === 0) {
return false;
}
const file = getPrioritiesFilePath(app);
try {
// Sync write at boot is fine — the event loop isn't serving traffic
// yet. atomicWriteFileSync ensures we don't leave a half-written
// priorities.json behind if the process is killed mid-migration.
(0, atomicWrite_1.atomicWriteFileSync)(file, JSON.stringify(present, null, 2));
}
catch (e) {
console.error(`Failed to create ${PRIORITIES_FILE}:`, e);
return false;
}
for (const key of exports.PRIORITIES_KEYS) {
delete settings[key];
}
console.log(`Migrated priority state from settings.json to ${PRIORITIES_FILE}`);
return true;
}
/**
* Extract the priority-related keys from a settings object, returning a
* shallow copy of the remaining settings. Used by writeSettingsFile so
* the on-disk settings.json never carries priority state.
*/
function splitPrioritiesFromSettings(settings) {
const settingsWithoutPriorities = { ...settings };
const priorities = {};
for (const key of exports.PRIORITIES_KEYS) {
if (key in settingsWithoutPriorities) {
priorities[key] = settingsWithoutPriorities[key];
delete settingsWithoutPriorities[key];
}
}
return { settingsWithoutPriorities, priorities };
}
async function writePrioritiesFile(app, priorities) {
const file = getPrioritiesFilePath(app);
await (0, atomicWrite_1.atomicWriteFile)(file, JSON.stringify(priorities, null, 2));
}
/**
* Wipe priority state: remove the file and reset in-memory keys to
* empty/undefined. After this call, the caller should also persist
* settings.json (in case it still had priority keys — shouldn't, but
* cheap to be safe).
*/
async function resetPriorities(app) {
const file = getPrioritiesFilePath(app);
try {
await fs_1.default.promises.unlink(file);
}
catch (e) {
if (e.code !== 'ENOENT') {
console.error(`Failed to remove ${PRIORITIES_FILE}:`, e);
throw e;
}
}
for (const key of exports.PRIORITIES_KEYS) {
delete app.config.settings[key];
}
// Defensive: legacy keys should never be set in-memory after load,
// but a hand-edited settings.json that bypassed loadPrioritiesIntoSettings
// could still have them. Strip on reset so the next save can't carry
// them back to disk.
for (const k of LEGACY_KEYS)
delete app.config.settings[k];
console.log(`Priority state reset (${PRIORITIES_FILE} removed)`);
}
//# sourceMappingURL=priorities-file.js.map