prismcast 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +1 -1
- package/dist/app.js +27 -9
- package/dist/app.js.map +1 -1
- package/dist/browser/cdp.js +7 -7
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/channelSelection.d.ts +41 -11
- package/dist/browser/channelSelection.js +75 -24
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/display.d.ts +1 -1
- package/dist/browser/index.d.ts +8 -8
- package/dist/browser/index.js +42 -43
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/login.d.ts +2 -2
- package/dist/browser/login.js +1 -1
- package/dist/browser/manifestInterceptor.d.ts +77 -0
- package/dist/browser/manifestInterceptor.js +323 -0
- package/dist/browser/manifestInterceptor.js.map +1 -0
- package/dist/browser/precaching.js +4 -4
- package/dist/browser/tuning/comcastPolymer.d.ts +1 -1
- package/dist/browser/tuning/cox.d.ts +1 -1
- package/dist/browser/tuning/directv.d.ts +1 -1
- package/dist/browser/tuning/directv.js +61 -62
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.d.ts +1 -1
- package/dist/browser/tuning/fox.js +171 -14
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/hbo.d.ts +1 -1
- package/dist/browser/tuning/hbo.js +6 -6
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.d.ts +1 -1
- package/dist/browser/tuning/hulu.js +117 -110
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/shared.d.ts +3 -3
- package/dist/browser/tuning/shared.js +5 -5
- package/dist/browser/tuning/sling.d.ts +1 -1
- package/dist/browser/tuning/sling.js +28 -28
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.d.ts +1 -1
- package/dist/browser/tuning/spectrum.js +21 -22
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.d.ts +1 -1
- package/dist/browser/tuning/thumbnailRow.js +1 -1
- package/dist/browser/tuning/tileClick.d.ts +1 -1
- package/dist/browser/tuning/tileClick.js +5 -5
- package/dist/browser/tuning/xfinity.d.ts +1 -1
- package/dist/browser/tuning/youtubeTv.d.ts +1 -1
- package/dist/browser/tuning/youtubeTv.js +17 -18
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +20 -6
- package/dist/browser/video.js +30 -38
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.d.ts +1 -2
- package/dist/channels/index.js +73 -40
- package/dist/channels/index.js.map +1 -1
- package/dist/config/channelForm.d.ts +58 -0
- package/dist/config/channelForm.js +129 -0
- package/dist/config/channelForm.js.map +1 -0
- package/dist/config/consistencyProbe.d.ts +7 -0
- package/dist/config/consistencyProbe.js +143 -0
- package/dist/config/consistencyProbe.js.map +1 -0
- package/dist/config/health.d.ts +4 -3
- package/dist/config/health.js +75 -62
- package/dist/config/health.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +19 -12
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +9 -3
- package/dist/config/paths.js +12 -4
- package/dist/config/paths.js.map +1 -1
- package/dist/config/persistence.context.d.ts +8 -0
- package/dist/config/persistence.context.js +30 -0
- package/dist/config/persistence.context.js.map +1 -0
- package/dist/config/persistence.d.ts +110 -12
- package/dist/config/persistence.js +351 -32
- package/dist/config/persistence.js.map +1 -1
- package/dist/config/presets.d.ts +14 -4
- package/dist/config/presets.js +84 -75
- package/dist/config/presets.js.map +1 -1
- package/dist/config/profiles.d.ts +2 -2
- package/dist/config/profiles.js +7 -9
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/servicePacks.d.ts +2 -2
- package/dist/config/servicePacks.js +28 -28
- package/dist/config/servicePacks.js.map +1 -1
- package/dist/config/services.d.ts +62 -36
- package/dist/config/services.js +150 -136
- package/dist/config/services.js.map +1 -1
- package/dist/config/sites.d.ts +3 -3
- package/dist/config/sites.js +16 -16
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +500 -41
- package/dist/config/userChannels.js +1201 -312
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +103 -10
- package/dist/config/userConfig.js +277 -117
- package/dist/config/userConfig.js.map +1 -1
- package/dist/config/userProfiles.d.ts +4 -2
- package/dist/config/userProfiles.js +72 -26
- package/dist/config/userProfiles.js.map +1 -1
- package/dist/hdhr/channelMap.js +8 -11
- package/dist/hdhr/channelMap.js.map +1 -1
- package/dist/hdhr/deviceId.js +10 -9
- package/dist/hdhr/deviceId.js.map +1 -1
- package/dist/hdhr/discover.js +4 -5
- package/dist/hdhr/discover.js.map +1 -1
- package/dist/hdhr/index.js +6 -10
- package/dist/hdhr/index.js.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/native/codecInference.d.ts +44 -0
- package/dist/native/codecInference.js +327 -0
- package/dist/native/codecInference.js.map +1 -0
- package/dist/native/decrypt.d.ts +1 -1
- package/dist/native/index.d.ts +5 -5
- package/dist/native/index.js +42 -64
- package/dist/native/index.js.map +1 -1
- package/dist/native/probe.d.ts +27 -9
- package/dist/native/probe.js +158 -67
- package/dist/native/probe.js.map +1 -1
- package/dist/native/proxy.d.ts +4 -2
- package/dist/native/proxy.js +52 -49
- package/dist/native/proxy.js.map +1 -1
- package/dist/native/tokenExpiry.d.ts +22 -0
- package/dist/native/tokenExpiry.js +153 -0
- package/dist/native/tokenExpiry.js.map +1 -0
- package/dist/routes/auth.js +6 -10
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/components.js +2 -2
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/endpoints/browse.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/browse.js +195 -0
- package/dist/routes/config/channels/endpoints/browse.js.map +1 -0
- package/dist/routes/config/channels/endpoints/bulk.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/bulk.js +128 -0
- package/dist/routes/config/channels/endpoints/bulk.js.map +1 -0
- package/dist/routes/config/channels/endpoints/crud.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/crud.js +504 -0
- package/dist/routes/config/channels/endpoints/crud.js.map +1 -0
- package/dist/routes/config/channels/endpoints/importExport.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/importExport.js +131 -0
- package/dist/routes/config/channels/endpoints/importExport.js.map +1 -0
- package/dist/routes/config/channels/endpoints/predefined.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/predefined.js +78 -0
- package/dist/routes/config/channels/endpoints/predefined.js.map +1 -0
- package/dist/routes/config/channels/endpoints/prefs.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/prefs.js +58 -0
- package/dist/routes/config/channels/endpoints/prefs.js.map +1 -0
- package/dist/routes/config/channels/endpoints/service.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/service.js +149 -0
- package/dist/routes/config/channels/endpoints/service.js.map +1 -0
- package/dist/routes/config/channels/endpoints/tags.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/tags.js +185 -0
- package/dist/routes/config/channels/endpoints/tags.js.map +1 -0
- package/dist/routes/config/channels/http/handler.d.ts +10 -0
- package/dist/routes/config/channels/http/handler.js +20 -0
- package/dist/routes/config/channels/http/handler.js.map +1 -0
- package/dist/routes/config/channels/http/playlistHint.d.ts +25 -0
- package/dist/routes/config/channels/http/playlistHint.js +40 -0
- package/dist/routes/config/channels/http/playlistHint.js.map +1 -0
- package/dist/routes/config/channels/http/serviceWarning.d.ts +10 -0
- package/dist/routes/config/channels/http/serviceWarning.js +28 -0
- package/dist/routes/config/channels/http/serviceWarning.js.map +1 -0
- package/dist/routes/config/channels/index.d.ts +3 -3
- package/dist/routes/config/channels/index.js +1 -1
- package/dist/routes/config/channels/index.js.map +1 -1
- package/dist/routes/config/channels/{routes.d.ts → setup.d.ts} +1 -1
- package/dist/routes/config/channels/setup.js +23 -0
- package/dist/routes/config/channels/setup.js.map +1 -0
- package/dist/routes/config/channels/table.d.ts +9 -9
- package/dist/routes/config/channels/table.js +124 -76
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/http/envelope.d.ts +111 -0
- package/dist/routes/config/http/envelope.js +103 -0
- package/dist/routes/config/http/envelope.js.map +1 -0
- package/dist/routes/config/index.d.ts +9 -9
- package/dist/routes/config/index.js +2 -2
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/services.js +69 -68
- package/dist/routes/config/services.js.map +1 -1
- package/dist/routes/config/settings.d.ts +1 -1
- package/dist/routes/config/settings.js +45 -38
- package/dist/routes/config/settings.js.map +1 -1
- package/dist/routes/debug.js +12 -11
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/icons.js +2 -2
- package/dist/routes/icons.js.map +1 -1
- package/dist/routes/logs.js +19 -30
- package/dist/routes/logs.js.map +1 -1
- package/dist/routes/mpegts.js +1 -1
- package/dist/routes/playlist.d.ts +1 -1
- package/dist/routes/playlist.js +37 -26
- package/dist/routes/playlist.js.map +1 -1
- package/dist/routes/root/content.js +27 -6
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/index.js +2 -5
- package/dist/routes/root/index.js.map +1 -1
- package/dist/routes/root/scripts/channels.js +16 -16
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/config.js +59 -59
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/index.d.ts +4 -4
- package/dist/routes/root/scripts/shared.js +24 -10
- package/dist/routes/root/scripts/shared.js.map +1 -1
- package/dist/routes/root/scripts/status.d.ts +4 -0
- package/dist/routes/root/scripts/status.handlers.d.ts +182 -0
- package/dist/routes/root/scripts/status.handlers.js +713 -0
- package/dist/routes/root/scripts/status.handlers.js.map +1 -0
- package/dist/routes/root/scripts/status.js +86 -488
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.js +5 -5
- package/dist/routes/services.js +18 -15
- package/dist/routes/services.js.map +1 -1
- package/dist/routes/sse.d.ts +16 -0
- package/dist/routes/sse.js +29 -0
- package/dist/routes/sse.js.map +1 -0
- package/dist/routes/streams.js +10 -23
- package/dist/routes/streams.js.map +1 -1
- package/dist/routes/ui.d.ts +2 -2
- package/dist/routes/upgrade.js +6 -7
- package/dist/routes/upgrade.js.map +1 -1
- package/dist/service/commands.context.d.ts +6 -0
- package/dist/service/commands.context.js +47 -0
- package/dist/service/commands.context.js.map +1 -0
- package/dist/service/commands.d.ts +53 -8
- package/dist/service/commands.js +171 -188
- package/dist/service/commands.js.map +1 -1
- package/dist/service/generators.context.d.ts +6 -0
- package/dist/service/generators.context.js +45 -0
- package/dist/service/generators.context.js.map +1 -0
- package/dist/service/generators.d.ts +56 -16
- package/dist/service/generators.js +499 -383
- package/dist/service/generators.js.map +1 -1
- package/dist/service/index.d.ts +2 -2
- package/dist/streaming/clients.d.ts +2 -2
- package/dist/streaming/clients.js +2 -2
- package/dist/streaming/codec.d.ts +2 -2
- package/dist/streaming/codec.js +0 -1
- package/dist/streaming/codec.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +2 -2
- package/dist/streaming/fmp4Segmenter.js +12 -12
- package/dist/streaming/hls.d.ts +3 -3
- package/dist/streaming/hls.js +81 -70
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +3 -3
- package/dist/streaming/hlsResume.js +5 -5
- package/dist/streaming/hlsSegments.d.ts +5 -2
- package/dist/streaming/hlsSegments.js +14 -9
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/monitor.d.ts +12 -12
- package/dist/streaming/monitor.js +80 -54
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +34 -4
- package/dist/streaming/mp4Parser.js +111 -29
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +1 -1
- package/dist/streaming/mpegts.js +13 -10
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +1 -1
- package/dist/streaming/playlistBuilder.js +2 -2
- package/dist/streaming/preroll.d.ts +9 -9
- package/dist/streaming/preroll.js +39 -41
- package/dist/streaming/preroll.js.map +1 -1
- package/dist/streaming/pretune.js +4 -4
- package/dist/streaming/pretune.js.map +1 -1
- package/dist/streaming/recovery.d.ts +3 -3
- package/dist/streaming/recovery.js +4 -4
- package/dist/streaming/registry.d.ts +6 -6
- package/dist/streaming/setup.d.ts +9 -8
- package/dist/streaming/setup.js +97 -49
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +9 -3
- package/dist/streaming/showInfo.js +26 -18
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +4 -4
- package/dist/streaming/statusEmitter.js +2 -2
- package/dist/types/channels.d.ts +133 -34
- package/dist/types/channels.js +39 -1
- package/dist/types/channels.js.map +1 -1
- package/dist/types/config.d.ts +20 -1
- package/dist/types/index.d.ts +9 -8
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/profiles.d.ts +5 -4
- package/dist/types/selection.d.ts +48 -4
- package/dist/types/streaming.d.ts +1 -1
- package/dist/upgrade/commands.context.d.ts +6 -0
- package/dist/upgrade/commands.context.js +29 -0
- package/dist/upgrade/commands.context.js.map +1 -0
- package/dist/upgrade/commands.d.ts +29 -2
- package/dist/upgrade/commands.js +87 -134
- package/dist/upgrade/commands.js.map +1 -1
- package/dist/upgrade/detection.context.d.ts +6 -0
- package/dist/upgrade/detection.context.js +24 -0
- package/dist/upgrade/detection.context.js.map +1 -0
- package/dist/upgrade/detection.d.ts +140 -10
- package/dist/upgrade/detection.js +153 -66
- package/dist/upgrade/detection.js.map +1 -1
- package/dist/upgrade/index.d.ts +2 -2
- package/dist/utils/chromeFetch.d.ts +1 -1
- package/dist/utils/chromeFetch.js +1 -1
- package/dist/utils/clock.d.ts +14 -0
- package/dist/utils/clock.js +28 -0
- package/dist/utils/clock.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +1 -1
- package/dist/utils/debugFilter.js +6 -4
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +2 -3
- package/dist/utils/delay.js +10 -16
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/evaluate.js +12 -14
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.context.d.ts +6 -0
- package/dist/utils/ffmpeg.context.js +31 -0
- package/dist/utils/ffmpeg.context.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +85 -29
- package/dist/utils/ffmpeg.js +126 -129
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/fileLogger.d.ts +18 -2
- package/dist/utils/fileLogger.js +58 -30
- package/dist/utils/fileLogger.js.map +1 -1
- package/dist/utils/format.d.ts +1 -1
- package/dist/utils/format.js +2 -2
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/html.js +1 -3
- package/dist/utils/html.js.map +1 -1
- package/dist/utils/index.d.ts +21 -20
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.js +16 -20
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/m3u.d.ts +18 -3
- package/dist/utils/m3u.js +28 -14
- package/dist/utils/m3u.js.map +1 -1
- package/dist/utils/memo.d.ts +19 -0
- package/dist/utils/memo.js +53 -0
- package/dist/utils/memo.js.map +1 -0
- package/dist/utils/morganStream.js +2 -2
- package/dist/utils/morganStream.js.map +1 -1
- package/dist/utils/pid.d.ts +2 -2
- package/dist/utils/pid.js +2 -2
- package/dist/utils/platform.d.ts +1 -1
- package/dist/utils/platform.js +3 -3
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.js +9 -7
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/sanitize.d.ts +2 -2
- package/dist/utils/sanitize.js +4 -4
- package/dist/utils/timing.d.ts +6 -3
- package/dist/utils/timing.js +10 -6
- package/dist/utils/timing.js.map +1 -1
- package/dist/utils/version.d.ts +3 -2
- package/dist/utils/version.js +14 -27
- package/dist/utils/version.js.map +1 -1
- package/package.json +13 -7
- package/dist/native/intercept.d.ts +0 -35
- package/dist/native/intercept.js +0 -181
- package/dist/native/intercept.js.map +0 -1
- package/dist/routes/config/channels/routes.js +0 -1505
- package/dist/routes/config/channels/routes.js.map +0 -1
|
@@ -1,14 +1,39 @@
|
|
|
1
1
|
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
2
|
*
|
|
3
|
-
* persistence.ts: Transactional file store for PrismCast configuration persistence.
|
|
3
|
+
* persistence.ts: Transactional file store framework for PrismCast configuration persistence.
|
|
4
|
+
*
|
|
5
|
+
* This module is the single source of truth for every persistence concern in PrismCast:
|
|
6
|
+
*
|
|
7
|
+
* - Atomic writes (temp file + rename)
|
|
8
|
+
* - Serialized mutations (in-process queue)
|
|
9
|
+
* - Auto-recovery from .bak when the main file fails to parse
|
|
10
|
+
* - Versioned snapshots at release boundaries (subdirectory + bounded retention)
|
|
11
|
+
* - Declarative schema migrations with an audit trail
|
|
12
|
+
* - Pluggable pre-write integrity validators
|
|
13
|
+
* - Centralized release-boot snapshot coordination across all registered stores
|
|
14
|
+
*
|
|
15
|
+
* Each persisted file (channels, config, profiles, health) declares its data shape, default value, parser, current schema version, ordered migration list, and
|
|
16
|
+
* (optionally) an integrity validator. The framework wires the rest. Adding a new store is a one-line registration in createFileStore; adding a new migration
|
|
17
|
+
* is a one-line entry in the store's migration map.
|
|
18
|
+
*
|
|
19
|
+
* The framework is backend-agnostic by construction: every operation it performs (atomic temp+rename, backup copy, snapshot copy, post-write readback) is
|
|
20
|
+
* expressible against any durable store that offers stat/read/write/copy/rename/unlink with a path namespace. The abstraction surface is StorageBackend below;
|
|
21
|
+
* the default fs-backed adapter lives in persistence.context.ts. Production stores get the default backend implicitly via the createFileStore default parameter.
|
|
4
22
|
*/
|
|
5
23
|
import { LOG, stringifySorted } from "../utils/index.js";
|
|
6
|
-
import
|
|
24
|
+
import { createDefaultStorageBackend } from "./persistence.context.js";
|
|
7
25
|
import { getDataDir } from "./paths.js";
|
|
8
|
-
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
// Maximum number of snapshots retained per file. Snapshots are pruned by mtime - the SNAPSHOT_RETENTION most recently created are kept and the rest are
|
|
28
|
+
// deleted. The retention window balances forensic value (older snapshots become schema-incompatible with current code) against disk and directory clutter.
|
|
29
|
+
const SNAPSHOT_RETENTION = 5;
|
|
30
|
+
// Shared default backend. Stateless wrappers around node:fs/promises - safe to share across every store in the process. Tests pass options.backend explicitly to
|
|
31
|
+
// substitute alternative backends (e.g., an in-memory backend with failure injection); production paths use this default implicitly.
|
|
32
|
+
const defaultStorageBackend = createDefaultStorageBackend();
|
|
33
|
+
// Types.
|
|
9
34
|
/**
|
|
10
|
-
* Error thrown by `mutate()` when the backing file contains invalid JSON. Route handlers can catch this specifically to
|
|
11
|
-
* paths that swallow errors via try/catch will handle it like any other Error.
|
|
35
|
+
* Error thrown by `mutate()` when the backing file contains invalid JSON and recovery from .bak also failed. Route handlers can catch this specifically to
|
|
36
|
+
* return 400 instead of 500. Background code paths that swallow errors via try/catch will handle it like any other Error.
|
|
12
37
|
*/
|
|
13
38
|
export class FileStoreParseError extends Error {
|
|
14
39
|
name = "FileStoreParseError";
|
|
@@ -16,6 +41,27 @@ export class FileStoreParseError extends Error {
|
|
|
16
41
|
super("Cannot modify " + label + " (" + filePath + "): file contains invalid JSON. " + parseMessage);
|
|
17
42
|
}
|
|
18
43
|
}
|
|
44
|
+
// Store registry: every createFileStore call appends to this list so the release boot coordinator can iterate every persistence-managed file uniformly. Stores
|
|
45
|
+
// must be created at module load time (not lazily) so the registry is fully populated by the time snapshotAllForRelease runs.
|
|
46
|
+
const registeredStores = [];
|
|
47
|
+
/**
|
|
48
|
+
* Snapshots every registered store under the given label. Used at startup to capture a guaranteed restore point at release boundaries before any migrations
|
|
49
|
+
* can run. The label is typically `pre-v<package-version>` so each release boot leaves a snapshot named for the version that was running.
|
|
50
|
+
*
|
|
51
|
+
* Snapshots run in parallel since they are independent file copies. Failures within individual snapshots are logged by the snapshot helper itself and do not
|
|
52
|
+
* propagate; the coordinator always returns successfully so a single store's snapshot failure does not block startup.
|
|
53
|
+
* @param label - The snapshot label suffix.
|
|
54
|
+
*/
|
|
55
|
+
export async function snapshotAllForRelease(label) {
|
|
56
|
+
await Promise.all(registeredStores.map(async (store) => store.snapshot(label)));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Runs ensureMigrated() on every registered store. Called once at startup after snapshotAllForRelease so that any pending migrations execute against a file
|
|
60
|
+
* that already has its pre-version snapshot captured. Idempotent within a release - subsequent boots see no pending migrations and skip the upgrade write.
|
|
61
|
+
*/
|
|
62
|
+
export async function ensureAllMigrated() {
|
|
63
|
+
await Promise.all(registeredStores.map(async (store) => store.ensureMigrated()));
|
|
64
|
+
}
|
|
19
65
|
// Store factory.
|
|
20
66
|
/**
|
|
21
67
|
* Creates a transactional file store instance. Each instance owns a single JSON file and provides serialized, atomic access to it.
|
|
@@ -23,70 +69,303 @@ export class FileStoreParseError extends Error {
|
|
|
23
69
|
* Safety guarantees:
|
|
24
70
|
* - **Atomic writes:** data is written to a `.tmp` file and renamed over the original. `rename()` is atomic on POSIX and NTFS.
|
|
25
71
|
* - **Serialization:** a promise chain ensures only one `mutate()` runs at a time. Concurrent callers queue behind the active operation.
|
|
26
|
-
* - **Corruption guard:** `mutate()` throws `FileStoreParseError` if the file
|
|
27
|
-
* - **Backup rotation:** before each write, the current file is copied to `.bak`. One-deep rotation provides a recovery path.
|
|
72
|
+
* - **Corruption guard:** `mutate()` throws `FileStoreParseError` if both the main file and its `.bak` are unparseable, preventing save-over-corrupt cascades.
|
|
73
|
+
* - **Backup rotation:** before each write, the current file is copied to `.bak`. One-deep rotation provides a recovery path for the previous good version.
|
|
74
|
+
* - **Auto-recovery:** when the main file fails to parse, `read()` transparently restores from `.bak` (atomic temp+rename) and surfaces `recoveredFromBackup`
|
|
75
|
+
* on the result so callers can banner the event. Only when both files are unparseable does the result fall back to defaults with `parseError: true`.
|
|
76
|
+
* - **Versioned snapshots:** `snapshot(label)` writes a copy of the current file into a `snapshots/` subdirectory, named `<file>.<label>`, idempotent on the
|
|
77
|
+
* label. After each successful create the directory is pruned to at most SNAPSHOT_RETENTION entries per file (by mtime).
|
|
78
|
+
* - **Declarative migrations:** when `migrations` and `currentSchemaVersion` are provided, `read()` runs any pending migrations in memory and returns the
|
|
79
|
+
* upgraded data. `ensureMigrated()` persists the upgrade if any were applied. Migrations are version-keyed and idempotent across boots.
|
|
80
|
+
* - **Pre-write validation:** when `validate` is provided, `mutate()` invokes it with the pre-mutation snapshot and post-mutation state, surfacing integrity
|
|
81
|
+
* issues via the log.
|
|
82
|
+
* - **Post-write integrity check:** after every successful rename, the file is read back and byte-compared against what was written. On mismatch, the .bak is
|
|
83
|
+
* restored and the call throws - catches encoder bugs, partial writes, and any filesystem-level corruption that happened during the write.
|
|
28
84
|
*
|
|
29
85
|
* @template T - The in-memory data type that callers mutate.
|
|
30
86
|
* @param options - Store configuration.
|
|
31
|
-
* @returns A FileStore instance
|
|
87
|
+
* @returns A FileStore instance. Auto-registers in the global store registry so the release boot coordinator can snapshot it.
|
|
32
88
|
*/
|
|
33
89
|
export function createFileStore(options) {
|
|
34
90
|
// Serialization queue. Each mutate() call chains onto this promise so operations execute one at a time.
|
|
35
91
|
let queue = Promise.resolve();
|
|
36
92
|
// Lazy data directory creation. The directory is created once on the first write, then skipped for subsequent writes.
|
|
37
93
|
let dataDirEnsured = false;
|
|
94
|
+
// Resolve the backend: the caller may inject an alternative implementation; production stores get the shared default.
|
|
95
|
+
const backend = options.backend ?? defaultStorageBackend;
|
|
96
|
+
// Validate the migration configuration at construction time so misconfigurations surface immediately rather than during the first migration attempt.
|
|
97
|
+
if (options.migrations) {
|
|
98
|
+
if ((options.currentSchemaVersion === undefined) || !options.getSchemaVersion || !options.setSchemaVersion) {
|
|
99
|
+
throw new Error("Store '" + options.label + "' declares migrations but is missing currentSchemaVersion, getSchemaVersion, or setSchemaVersion.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Runs every pending migration on the in-memory data, mutating it in place. Returns the result so callers can decide whether to persist. Throws when a
|
|
104
|
+
* version gap appears in the migration map (indicates a programmer error - every intermediate version must have a migration).
|
|
105
|
+
*/
|
|
106
|
+
function runMigrations(data) {
|
|
107
|
+
if (!options.migrations || (options.currentSchemaVersion === undefined) || !options.getSchemaVersion || !options.setSchemaVersion) {
|
|
108
|
+
return { applied: [], fromVersion: 0, toVersion: 0 };
|
|
109
|
+
}
|
|
110
|
+
let currentVersion = options.getSchemaVersion(data);
|
|
111
|
+
const fromVersion = currentVersion;
|
|
112
|
+
const applied = [];
|
|
113
|
+
// Forward-compatible read: a file written by a newer PrismCast version reports a higher schemaVersion. We do not attempt to downgrade - just log and let
|
|
114
|
+
// the consumer code work with whatever it can. This keeps the file safe from a downgrade scenario where an old binary would otherwise mutate it.
|
|
115
|
+
if (currentVersion > options.currentSchemaVersion) {
|
|
116
|
+
LOG.warn("%s schema version %d is newer than this build supports (%d). Reading without migration.", options.label, currentVersion, options.currentSchemaVersion);
|
|
117
|
+
return { applied: [], fromVersion, toVersion: fromVersion };
|
|
118
|
+
}
|
|
119
|
+
while (currentVersion < options.currentSchemaVersion) {
|
|
120
|
+
const targetVersion = currentVersion + 1;
|
|
121
|
+
const migration = options.migrations[targetVersion];
|
|
122
|
+
if (!migration) {
|
|
123
|
+
throw new Error("Store '" + options.label + "' is missing a migration to schema version " + String(targetVersion) + ".");
|
|
124
|
+
}
|
|
125
|
+
migration.apply(data);
|
|
126
|
+
options.setSchemaVersion(data, targetVersion);
|
|
127
|
+
if (options.recordMigration) {
|
|
128
|
+
options.recordMigration(data, migration.description);
|
|
129
|
+
}
|
|
130
|
+
applied.push(migration.description);
|
|
131
|
+
currentVersion = targetVersion;
|
|
132
|
+
}
|
|
133
|
+
return { applied, fromVersion, toVersion: currentVersion };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Routes ValidationIssues to the log. Warnings log at warn level; errors log at error level. Both are logged without throwing.
|
|
137
|
+
*/
|
|
138
|
+
function handleValidationIssues(issues) {
|
|
139
|
+
if (issues.length === 0) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const issue of issues) {
|
|
143
|
+
const message = "Pre-write integrity check (" + options.label + ", " + issue.category + "): " + issue.description;
|
|
144
|
+
if (issue.severity === "error") {
|
|
145
|
+
LOG.error(message);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
LOG.warn(message);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Attempts to recover from .bak when the main file fails to parse. Reads .bak, parses it, and on success restores the main file from .bak via atomic temp+
|
|
154
|
+
* rename so the next backup-and-write cycle does not rotate the corrupt main file into .bak (which would destroy the only good copy). Returns the parsed
|
|
155
|
+
* recovered data, or null if .bak is missing or also unparseable.
|
|
156
|
+
*/
|
|
157
|
+
async function tryRecoverFromBackup(filePath) {
|
|
158
|
+
const bakPath = filePath + ".bak";
|
|
159
|
+
let bakContent;
|
|
160
|
+
try {
|
|
161
|
+
bakContent = await backend.readFile(bakPath);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
let data;
|
|
167
|
+
try {
|
|
168
|
+
data = options.parse(bakContent);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
// Restore main from .bak via atomic temp+rename. Best-effort: if the restore write fails, we still return the recovered data so the caller can proceed
|
|
174
|
+
// with valid in-memory state. A subsequent read would attempt recovery again and produce the same result.
|
|
175
|
+
const tmpPath = filePath + ".tmp";
|
|
176
|
+
try {
|
|
177
|
+
await backend.writeFile(tmpPath, bakContent);
|
|
178
|
+
await backend.rename(tmpPath, filePath);
|
|
179
|
+
}
|
|
180
|
+
catch (restoreError) {
|
|
181
|
+
LOG.warn("Recovered %s data from backup but failed to restore the main file: %s.", options.label, (restoreError instanceof Error) ? restoreError.message : String(restoreError));
|
|
182
|
+
try {
|
|
183
|
+
await backend.unlink(tmpPath);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Cleanup is best-effort.
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return data;
|
|
190
|
+
}
|
|
38
191
|
/**
|
|
39
|
-
* Reads the file from disk
|
|
192
|
+
* Reads the file from disk, parses it, runs any pending migrations in memory, and returns the result. On a parse failure of the main file, attempts to
|
|
193
|
+
* recover from .bak before falling back to defaults. Migrations applied here are not persisted by this method alone - callers who need to persist an upgrade
|
|
194
|
+
* should use ensureMigrated().
|
|
40
195
|
*/
|
|
41
196
|
async function read() {
|
|
42
197
|
const filePath = options.path();
|
|
198
|
+
let parsed = null;
|
|
199
|
+
let parseError = null;
|
|
43
200
|
try {
|
|
44
|
-
const content = await
|
|
201
|
+
const content = await backend.readFile(filePath);
|
|
45
202
|
try {
|
|
46
|
-
|
|
47
|
-
return { data, parseError: false };
|
|
203
|
+
parsed = { data: options.parse(content), recoveredFromBackup: false };
|
|
48
204
|
}
|
|
49
|
-
catch (
|
|
50
|
-
const message = (
|
|
51
|
-
|
|
52
|
-
|
|
205
|
+
catch (error) {
|
|
206
|
+
const message = (error instanceof Error) ? error.message : String(error);
|
|
207
|
+
// The main file is corrupt. Try to recover from the .bak rotation before falling back to defaults.
|
|
208
|
+
const recovered = await tryRecoverFromBackup(filePath);
|
|
209
|
+
if (recovered !== null) {
|
|
210
|
+
LOG.warn("Recovered %s from backup; main file was corrupt: %s.", options.label, message);
|
|
211
|
+
parsed = { data: recovered, recoveredFromBackup: true };
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
parseError = { message };
|
|
215
|
+
}
|
|
53
216
|
}
|
|
54
217
|
}
|
|
55
218
|
catch (error) {
|
|
56
|
-
// File doesn't exist
|
|
219
|
+
// File doesn't exist - this is normal on first run, use defaults. We deliberately do NOT auto-recover from .bak here: a missing main file is the
|
|
220
|
+
// first-run signal, and recovering from a stale .bak from a prior installation would be surprising. Operators wanting to restore a deleted main file
|
|
221
|
+
// can rename .bak manually.
|
|
57
222
|
if (error.code === "ENOENT") {
|
|
58
|
-
|
|
223
|
+
parsed = { data: options.defaultValue(), recoveredFromBackup: false };
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// Other read errors - log and use defaults.
|
|
227
|
+
LOG.warn("Failed to read %s file %s: %s. Using defaults.", options.label, filePath, (error instanceof Error) ? error.message : String(error));
|
|
228
|
+
parsed = { data: options.defaultValue(), recoveredFromBackup: false };
|
|
59
229
|
}
|
|
60
|
-
// Other read errors...log and use defaults.
|
|
61
|
-
LOG.warn("Failed to read %s file %s: %s. Using defaults.", options.label, filePath, (error instanceof Error) ? error.message : String(error));
|
|
62
|
-
return { data: options.defaultValue(), parseError: false };
|
|
63
230
|
}
|
|
231
|
+
if (parsed === null) {
|
|
232
|
+
// Both main and .bak failed to parse.
|
|
233
|
+
LOG.warn("Invalid JSON in %s file %s and no usable backup available: %s. Using defaults.", options.label, filePath, parseError?.message ?? "");
|
|
234
|
+
return {
|
|
235
|
+
data: options.defaultValue(),
|
|
236
|
+
migrationResult: { applied: [], fromVersion: 0, toVersion: 0 },
|
|
237
|
+
parseError: true,
|
|
238
|
+
parseErrorMessage: parseError?.message,
|
|
239
|
+
recoveredFromBackup: false
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// Run migrations in-memory. The data returned is always at currentSchemaVersion (or higher in the forward-compat case).
|
|
243
|
+
const migrationResult = runMigrations(parsed.data);
|
|
244
|
+
if (migrationResult.applied.length > 0) {
|
|
245
|
+
LOG.info("Migrated %s schema from v%d to v%d (%d migration(s) applied: %s).", options.label, migrationResult.fromVersion, migrationResult.toVersion, migrationResult.applied.length, migrationResult.applied.join(", "));
|
|
246
|
+
}
|
|
247
|
+
return { data: parsed.data, migrationResult, parseError: false, recoveredFromBackup: parsed.recoveredFromBackup };
|
|
64
248
|
}
|
|
65
249
|
/**
|
|
66
|
-
*
|
|
250
|
+
* Prunes old snapshots for a given file, keeping only the SNAPSHOT_RETENTION most recently created. Snapshots are matched by filename prefix so each file's
|
|
251
|
+
* snapshots are pruned independently. Failures (missing dir, unreadable entries, unlink errors) are best-effort - logged but never propagated, since pruning
|
|
252
|
+
* is a hygiene operation and must not block the create that triggered it.
|
|
253
|
+
*/
|
|
254
|
+
async function pruneSnapshots(snapshotDir, baseName) {
|
|
255
|
+
let entries;
|
|
256
|
+
try {
|
|
257
|
+
entries = await backend.readdir(snapshotDir);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Directory does not exist or is unreadable - nothing to prune.
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
// Match snapshots that belong to this file. Naming convention is `<baseName>.<label>` inside the snapshots dir, so prefix-matching is sufficient.
|
|
264
|
+
const prefix = baseName + ".";
|
|
265
|
+
const matching = entries.filter((entry) => entry.startsWith(prefix));
|
|
266
|
+
if (matching.length <= SNAPSHOT_RETENTION) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// Stat each candidate. Entries that fail to stat are dropped from the candidate list rather than blocking pruning of the rest.
|
|
270
|
+
const stats = await Promise.all(matching.map(async (name) => {
|
|
271
|
+
const fullPath = path.join(snapshotDir, name);
|
|
272
|
+
try {
|
|
273
|
+
const stat = await backend.stat(fullPath);
|
|
274
|
+
return { mtime: stat.mtimeMs, path: fullPath };
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}));
|
|
280
|
+
// Sort by mtime descending so the most recent are first; everything past the retention window is deleted. Unlinks run in parallel since they are
|
|
281
|
+
// independent; a failure on one entry does not block the rest.
|
|
282
|
+
const sorted = stats.filter((entry) => (entry !== null)).sort((a, b) => (b.mtime - a.mtime));
|
|
283
|
+
await Promise.all(sorted.slice(SNAPSHOT_RETENTION).map(async (old) => {
|
|
284
|
+
try {
|
|
285
|
+
await backend.unlink(old.path);
|
|
286
|
+
LOG.info("Pruned old snapshot %s.", old.path);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
LOG.warn("Failed to prune snapshot %s: %s.", old.path, (error instanceof Error) ? error.message : String(error));
|
|
290
|
+
}
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Creates a labeled snapshot copy of the current file inside a `snapshots/` subdirectory next to the source file. Idempotent on the label so repeated calls
|
|
295
|
+
* within the same release are no-ops after the first one. After a successful create, prunes old snapshots for this file so at most SNAPSHOT_RETENTION are
|
|
296
|
+
* retained (by mtime). Snapshots are version-keyed safety nets that survive normal .bak rotation, intended to be the restore-of-last-resort when a release
|
|
297
|
+
* introduces a data-shape regression that escapes the primary safeguards.
|
|
298
|
+
*/
|
|
299
|
+
async function snapshot(label) {
|
|
300
|
+
const filePath = options.path();
|
|
301
|
+
const baseName = path.basename(filePath);
|
|
302
|
+
const snapshotDir = path.join(path.dirname(filePath), "snapshots");
|
|
303
|
+
const snapshotPath = path.join(snapshotDir, baseName + "." + label);
|
|
304
|
+
// Idempotent: skip if a snapshot with this label already exists. The first boot under a given release captures the snapshot; subsequent boots see it and
|
|
305
|
+
// skip, so the snapshot reflects the file's state at first-boot of that release.
|
|
306
|
+
try {
|
|
307
|
+
await backend.access(snapshotPath);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// Snapshot does not exist yet; proceed to create it.
|
|
312
|
+
}
|
|
313
|
+
// Ensure the snapshots subdirectory exists. Best-effort: if mkdir fails the copyFile below surfaces the real error.
|
|
314
|
+
try {
|
|
315
|
+
await backend.mkdir(snapshotDir);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Best-effort; the copyFile call below will surface the underlying problem.
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
await backend.copyFile(filePath, snapshotPath);
|
|
322
|
+
LOG.info("Created snapshot of %s at %s.", options.label, snapshotPath);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
// Source file does not exist - first run, nothing to snapshot. Not an error.
|
|
326
|
+
if (error.code === "ENOENT") {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
LOG.warn("Failed to create snapshot of %s at %s: %s.", options.label, snapshotPath, (error instanceof Error) ? error.message : String(error));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
// Hygiene: keep only the most recent SNAPSHOT_RETENTION snapshots for this file.
|
|
333
|
+
await pruneSnapshots(snapshotDir, baseName);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Executes a single mutation: read (with migrations), guard, snapshot pre-state for validation, mutate, validate, backup, atomic write. Called under the
|
|
337
|
+
* serialization queue.
|
|
67
338
|
*/
|
|
68
339
|
async function doMutate(fn) {
|
|
69
340
|
const filePath = options.path();
|
|
70
|
-
// Read the current file state.
|
|
341
|
+
// Read the current file state. read() will transparently recover from .bak when the main file is corrupt and run any pending migrations; only when both
|
|
342
|
+
// main and .bak fail to parse does parseError surface here.
|
|
71
343
|
const result = await read();
|
|
72
|
-
// Corruption guard: refuse to modify a file that
|
|
344
|
+
// Corruption guard: refuse to modify a file that cannot be parsed and could not be recovered from backup. Prevents the cascade where a corrupt file gets
|
|
345
|
+
// overwritten with nearly-empty data.
|
|
73
346
|
if (result.parseError) {
|
|
74
347
|
throw new FileStoreParseError(options.label, filePath, result.parseErrorMessage ?? "Unknown parse error.");
|
|
75
348
|
}
|
|
349
|
+
// Capture pre-mutation state for the validator. Skip the clone when no validator is configured to avoid the overhead.
|
|
350
|
+
const prevState = options.validate ? structuredClone(result.data) : null;
|
|
76
351
|
// Apply the caller's mutation. Callbacks modify data in place.
|
|
77
352
|
fn(result.data);
|
|
353
|
+
// Run the validator if one is configured. Issues are logged without aborting the write.
|
|
354
|
+
if (options.validate && prevState) {
|
|
355
|
+
handleValidationIssues(options.validate(prevState, result.data));
|
|
356
|
+
}
|
|
78
357
|
// Apply the beforeWrite transform to produce the serializable form.
|
|
79
358
|
const serializable = options.beforeWrite ? options.beforeWrite(result.data) : result.data;
|
|
80
359
|
const content = stringifySorted(serializable) + "\n";
|
|
81
360
|
// Ensure the data directory exists on the first write.
|
|
82
361
|
if (!dataDirEnsured) {
|
|
83
|
-
await
|
|
362
|
+
await backend.mkdir(getDataDir());
|
|
84
363
|
dataDirEnsured = true;
|
|
85
364
|
}
|
|
86
|
-
// Backup: copy the current file to .bak before overwriting. Swallow ENOENT (file
|
|
365
|
+
// Backup: copy the current file to .bak before overwriting. Swallow ENOENT (file does not exist yet on first write).
|
|
87
366
|
const bakPath = filePath + ".bak";
|
|
88
367
|
try {
|
|
89
|
-
await
|
|
368
|
+
await backend.copyFile(filePath, bakPath);
|
|
90
369
|
}
|
|
91
370
|
catch (backupError) {
|
|
92
371
|
if (backupError.code !== "ENOENT") {
|
|
@@ -96,20 +375,43 @@ export function createFileStore(options) {
|
|
|
96
375
|
// Atomic write: write to a temp file, then rename over the original. rename() is atomic on POSIX and NTFS filesystems.
|
|
97
376
|
const tmpPath = filePath + ".tmp";
|
|
98
377
|
try {
|
|
99
|
-
await
|
|
100
|
-
await
|
|
378
|
+
await backend.writeFile(tmpPath, content);
|
|
379
|
+
await backend.rename(tmpPath, filePath);
|
|
101
380
|
}
|
|
102
381
|
catch (writeError) {
|
|
103
382
|
// Attempt cleanup of the temp file on failure.
|
|
104
383
|
try {
|
|
105
|
-
await
|
|
384
|
+
await backend.unlink(tmpPath);
|
|
106
385
|
}
|
|
107
386
|
catch {
|
|
108
387
|
// Cleanup is best-effort.
|
|
109
388
|
}
|
|
110
389
|
throw writeError;
|
|
111
390
|
}
|
|
112
|
-
|
|
391
|
+
// Post-write integrity check: read the file we just wrote and verify it byte-matches what we intended to write. Catches encoder bugs, partial writes, and
|
|
392
|
+
// any disk- or filesystem-level corruption that happened between writeFile and now. On a verification failure the main file is structurally suspect, so
|
|
393
|
+
// restore from .bak (which still holds the prior good state) and throw - the caller's expectation that the write succeeded was wrong.
|
|
394
|
+
//
|
|
395
|
+
// The readback may hit page cache rather than physical disk on most platforms, so this check does NOT prove durability - that requires fsync. It does
|
|
396
|
+
// prove integrity: what we asked the OS to write equals what the OS now reports as the file's contents.
|
|
397
|
+
let written;
|
|
398
|
+
try {
|
|
399
|
+
written = await backend.readFile(filePath);
|
|
400
|
+
}
|
|
401
|
+
catch (readbackError) {
|
|
402
|
+
LOG.error("Post-write readback failed for %s: %s.", filePath, (readbackError instanceof Error) ? readbackError.message : String(readbackError));
|
|
403
|
+
throw readbackError;
|
|
404
|
+
}
|
|
405
|
+
if (written !== content) {
|
|
406
|
+
LOG.error("Post-write integrity check failed for %s: %d bytes on disk, expected %d bytes. Restoring from backup.", filePath, written.length, content.length);
|
|
407
|
+
// Restore the prior good state. tryRecoverFromBackup handles the atomic rename of .bak -> main.
|
|
408
|
+
const recovered = await tryRecoverFromBackup(filePath);
|
|
409
|
+
if (recovered === null) {
|
|
410
|
+
LOG.error("Backup recovery also failed for %s. The file is in an inconsistent state and requires operator action.", filePath);
|
|
411
|
+
}
|
|
412
|
+
throw new Error("Post-write integrity check failed for " + options.label + " (" + filePath + "): on-disk content does not match the intended write.");
|
|
413
|
+
}
|
|
414
|
+
LOG.debug("persistence:write", "Saved %s to %s.", options.label, filePath);
|
|
113
415
|
}
|
|
114
416
|
/**
|
|
115
417
|
* Enqueues a mutation onto the serialization queue. The promise chain ensures only one mutation runs at a time. Errors propagate to the caller but do not
|
|
@@ -122,6 +424,23 @@ export function createFileStore(options) {
|
|
|
122
424
|
queue = operation.catch(() => { });
|
|
123
425
|
return operation;
|
|
124
426
|
}
|
|
125
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Verifies the file is at the current schema version, persisting any required upgrade. Idempotent - when the file is already current this is a single read
|
|
429
|
+
* with no write. The release boot coordinator calls this on every store after snapshots have been captured.
|
|
430
|
+
*/
|
|
431
|
+
async function ensureMigrated() {
|
|
432
|
+
// Peek to determine whether migrations are needed. read() runs migrations in memory but does not persist.
|
|
433
|
+
const peek = await read();
|
|
434
|
+
if (peek.migrationResult.applied.length === 0) {
|
|
435
|
+
return peek.migrationResult;
|
|
436
|
+
}
|
|
437
|
+
// Migrations were applied in memory - persist via a no-op mutate. mutate's internal read re-runs the migrations so the persisted state matches what we
|
|
438
|
+
// observed. The cost is one extra read, but only on boots where migrations were actually needed.
|
|
439
|
+
await mutate(() => { });
|
|
440
|
+
return peek.migrationResult;
|
|
441
|
+
}
|
|
442
|
+
const store = { ensureMigrated, mutate, read, snapshot };
|
|
443
|
+
registeredStores.push(store);
|
|
444
|
+
return store;
|
|
126
445
|
}
|
|
127
446
|
//# sourceMappingURL=persistence.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/config/persistence.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/config/persistence.ts"],"names":[],"mappings":"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"}
|
package/dist/config/presets.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import type { Config, Nullable } from "../types/index.
|
|
1
|
+
import type { Config, Nullable } from "../types/index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Setting paths that every quality preset populates. Declaring them as a tight object type (instead of a general Record<string, number>) keeps the lookups
|
|
4
|
+
* in this module statically typed - TypeScript knows each key is present, so we never have to narrow the result of preset.values[...] at call sites.
|
|
5
|
+
*/
|
|
6
|
+
export interface PresetValues {
|
|
7
|
+
"browser.viewport.height": number;
|
|
8
|
+
"browser.viewport.width": number;
|
|
9
|
+
"streaming.frameRate": number;
|
|
10
|
+
"streaming.videoBitsPerSecond": number;
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* A quality preset that sets multiple configuration values at once.
|
|
4
14
|
*/
|
|
@@ -6,11 +16,11 @@ export interface QualityPreset {
|
|
|
6
16
|
description: string;
|
|
7
17
|
id: string;
|
|
8
18
|
name: string;
|
|
9
|
-
values:
|
|
19
|
+
values: PresetValues;
|
|
10
20
|
}
|
|
11
21
|
/**
|
|
12
|
-
* Available video quality presets. These presets configure viewport dimensions, video bitrate, and frame rate for common use cases. The presets are ordered
|
|
13
|
-
* to highest quality.
|
|
22
|
+
* Available video quality presets. These presets configure viewport dimensions, video bitrate, and frame rate for common use cases. The presets are ordered
|
|
23
|
+
* from lowest to highest quality.
|
|
14
24
|
*/
|
|
15
25
|
export declare const VIDEO_QUALITY_PRESETS: QualityPreset[];
|
|
16
26
|
/**
|