UNPKG

signalk-server

Version:

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

187 lines 7.56 kB
"use strict"; /* * 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