@camstack/system 1.2.316 → 1.2.317
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/auth/auth-manager.d.ts +11 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +347 -181
- package/dist/builtins/composer/composer.addon.mjs +347 -181
- package/dist/builtins/composer/composition-evaluation.d.ts +37 -0
- package/dist/builtins/composer/composition-runtime.d.ts +11 -52
- package/dist/builtins/composer/composition-target.d.ts +50 -0
- package/dist/builtins/composer/existing-target.d.ts +19 -1
- package/dist/builtins/composer/own-field-reads.d.ts +19 -0
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +265 -29
- package/dist/builtins/device-manager/device-manager.addon.mjs +265 -29
- package/dist/builtins/device-manager/device-provider-context.d.ts +6 -0
- package/dist/builtins/device-manager/device-source-activation.d.ts +26 -1
- package/dist/builtins/device-manager/device-state-mirror.d.ts +41 -9
- package/dist/builtins/device-manager/mirror-row-writer.d.ts +59 -1
- package/dist/builtins/doorbell/binding-mirror.d.ts +45 -81
- package/dist/builtins/doorbell/doorbell-extension-slot.d.ts +4 -3
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +53 -11
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +207 -82
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +207 -82
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +47 -3
- package/dist/builtins/local-auth/local-auth.addon.mjs +46 -4
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +4 -4
- package/dist/builtins/system-config/system-config.addon.mjs +4 -4
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-AAltfBUH.mjs → child-cap-dispatch-C2VINHu1.mjs} +70 -18
- package/dist/{child-cap-dispatch-DzcUd_ct.js → child-cap-dispatch-C8uts1P4.js} +70 -18
- package/dist/{composition-sources-fUyF4z39.mjs → composition-sources-DVClu006.mjs} +1 -1
- package/dist/{composition-sources-BwE1POnF.js → composition-sources-Dep7nBwu.js} +1 -1
- package/dist/{dist-BPvku43i.mjs → dist-Dori4pf3.mjs} +85 -6
- package/dist/{dist-RAl10EfG.js → dist-a6p0V-IL.js} +85 -6
- package/dist/index.js +739 -350
- package/dist/index.mjs +737 -352
- package/dist/kernel/bootstrap-config.d.ts +8 -0
- package/dist/kernel/capability-registry.d.ts +30 -0
- package/dist/kernel/config-manager.d.ts +75 -129
- package/dist/kernel/index.d.ts +5 -2
- package/dist/kernel/moleculer/runtime-state-load.d.ts +19 -0
- package/dist/kernel/settings-door-view.d.ts +24 -1
- package/dist/kernel/settings-read-error.d.ts +37 -0
- package/dist/kernel/settings-view-types.d.ts +105 -0
- package/dist/kernel/store-settings-view.d.ts +17 -0
- package/dist/kernel/system-settings-mirror.d.ts +34 -0
- package/dist/kernel/system-settings-policy.d.ts +47 -0
- package/dist/{retired-settings-keys-D3rGTGq5.js → retired-settings-keys-DC1uHWPZ.js} +1 -1
- package/dist/{retired-settings-keys-DqVWSrVw.mjs → retired-settings-keys-DVcKRgzm.mjs} +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AppConfig, BootstrapConfig } from './config-schema.js';
|
|
2
|
+
/** config.yaml, env overrides applied, parsed. */
|
|
3
|
+
export declare function loadBootstrapConfig(configPath: string): BootstrapConfig;
|
|
4
|
+
export declare function writeBootstrapSection(configPath: string, section: string, data: Record<string, unknown>): BootstrapConfig;
|
|
5
|
+
/**
|
|
6
|
+
* Returns a merged view of bootstrap config + runtime defaults for backward compat.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildRawAppConfig(bootstrapConfig: BootstrapConfig): AppConfig;
|
|
@@ -31,6 +31,17 @@ export type CollectionConfigReader = (capability: string) => readonly string[] |
|
|
|
31
31
|
* cannot present it. See docs/decisions/adr-0188-an-unregister-carries-proof-of-ownership.md.
|
|
32
32
|
*/
|
|
33
33
|
export type ProviderOwnerToken = string;
|
|
34
|
+
/** What {@link CapabilityRegistry.applyPersistedPreferences} changed. */
|
|
35
|
+
export interface PersistedPreferenceReplay {
|
|
36
|
+
readonly singletons: ReadonlyArray<{
|
|
37
|
+
readonly capability: string;
|
|
38
|
+
readonly addonId: string;
|
|
39
|
+
}>;
|
|
40
|
+
readonly disabled: ReadonlyArray<{
|
|
41
|
+
readonly capability: string;
|
|
42
|
+
readonly addonId: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
34
45
|
export declare class CapabilityRegistry {
|
|
35
46
|
private readonly capabilities;
|
|
36
47
|
private readonly logger;
|
|
@@ -234,6 +245,25 @@ export declare class CapabilityRegistry {
|
|
|
234
245
|
* successor's live registration ([D188](../../../../docs/decisions/adr-0188-an-unregister-carries-proof-of-ownership.md)).
|
|
235
246
|
*/
|
|
236
247
|
unregisterProvider(capabilityName: string, addonId: string, owner?: ProviderOwnerToken): void;
|
|
248
|
+
/**
|
|
249
|
+
* Re-apply the operator's PERSISTED preferences to providers that
|
|
250
|
+
* registered before those preferences could be read — once, when the boot
|
|
251
|
+
* read of them settles (D675).
|
|
252
|
+
*
|
|
253
|
+
* `registerProvider` consults the preference readers at the moment a
|
|
254
|
+
* provider registers. With the settings engine isolated (D231) the
|
|
255
|
+
* preferences arrive over an async door, so a provider can register before
|
|
256
|
+
* they are known; the readers then answer "no preference" and the default
|
|
257
|
+
* wins. This replays the answer the readers give NOW over every registered
|
|
258
|
+
* provider: a singleton whose persisted choice is registered and not active
|
|
259
|
+
* is switched to it, and a collection provider the operator disabled is
|
|
260
|
+
* disabled again. It is one pass, triggered by the read settling — not a
|
|
261
|
+
* wait, a poll or a retry (D3).
|
|
262
|
+
*
|
|
263
|
+
* `skip` names the capabilities that must not be switched live (infra caps
|
|
264
|
+
* need a restart — the same rule `capabilities.setPreference` applies).
|
|
265
|
+
*/
|
|
266
|
+
applyPersistedPreferences(skip: (capability: string) => boolean): PersistedPreferenceReplay;
|
|
237
267
|
/** Enable a previously disabled collection provider. */
|
|
238
268
|
enableCollectionProvider(capability: string, addonId: string): void;
|
|
239
269
|
/** Disable a collection provider (keeps it registered but excluded from active list). */
|
|
@@ -1,101 +1,16 @@
|
|
|
1
1
|
import { AppConfig } from './config-schema.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
writeAddonStore(patch: Record<string, unknown>): Promise<void>;
|
|
8
|
-
readDeviceStore(deviceId: number): Promise<Record<string, unknown>>;
|
|
9
|
-
/** See `IAddonContext['settings'].readDeviceStoreBatch` — optional there and
|
|
10
|
-
* here for the same reason: only the door-backed view can answer it in one
|
|
11
|
-
* range scan, and a caller must fall back rather than refuse. */
|
|
12
|
-
readDeviceStoreBatch?(deviceIds: readonly number[]): Promise<ReadonlyMap<number, Record<string, unknown>>>;
|
|
13
|
-
writeDeviceStore(deviceId: number, patch: Record<string, unknown>): Promise<void>;
|
|
14
|
-
clearDeviceStore(deviceId: number): Promise<void>;
|
|
15
|
-
getSection(section: string): Promise<Record<string, unknown>>;
|
|
16
|
-
setSection(section: string, patch: Record<string, unknown>): Promise<void>;
|
|
17
|
-
/**
|
|
18
|
-
* Per-device runtime state — separate namespace from
|
|
19
|
-
* `readDeviceStore` / `writeDeviceStore` (which is the addon's
|
|
20
|
-
* config blob: host, password, deviceCache, …). Holds mutable
|
|
21
|
-
* runtime signals discovered after boot: battery snapshot, sleep
|
|
22
|
-
* flag, last-seen timestamps. Stored under a kernel-internal
|
|
23
|
-
* `__device-state` namespace so the device's config schema
|
|
24
|
-
* never has to know about it (clean separation of operator
|
|
25
|
-
* intent from runtime telemetry).
|
|
26
|
-
*
|
|
27
|
-
* Patch semantics: shallow merge. Pass the FULL persistent slice
|
|
28
|
-
* each call (the kernel doesn't try to compute deltas) — concrete
|
|
29
|
-
* implementations of `DeviceRuntimeState` already coalesce in
|
|
30
|
-
* memory, so the kernel just persists the latest blob.
|
|
31
|
-
*/
|
|
32
|
-
readDeviceRuntimeState(deviceId: number): Promise<Record<string, unknown>>;
|
|
33
|
-
writeDeviceRuntimeState(deviceId: number, data: Record<string, unknown>): Promise<void>;
|
|
34
|
-
clearDeviceRuntimeState(deviceId: number): Promise<void>;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* A CONCRETE backend behind {@link ConfigManager.createSettingsView} — the
|
|
38
|
-
* sync-`ISettingsStore` one or the async-door one.
|
|
39
|
-
*
|
|
40
|
-
* `readDeviceStoreBatch` is required here and optional on
|
|
41
|
-
* {@link AddonSettingsView} because both of these can answer it, so the view
|
|
42
|
-
* the kernel hands an addon never has to decide whether the method exists.
|
|
43
|
-
* The optionality on the public interface stays for the implementation that
|
|
44
|
-
* genuinely cannot batch: a forked runner's UDS-backed view.
|
|
45
|
-
*/
|
|
46
|
-
export interface SettingsViewBackend extends AddonSettingsView {
|
|
47
|
-
readDeviceStoreBatch(deviceIds: readonly number[]): Promise<ReadonlyMap<number, Record<string, unknown>>>;
|
|
48
|
-
}
|
|
49
|
-
export interface ISettingsStore {
|
|
50
|
-
getSystem(key: string): unknown;
|
|
51
|
-
setSystem(key: string, value: unknown): void;
|
|
52
|
-
getAllSystem(): Record<string, unknown>;
|
|
53
|
-
/** Addon-level global settings (apply to every device unless overridden). */
|
|
54
|
-
getAllAddon(addonId: string): Record<string, unknown>;
|
|
55
|
-
setAllAddon(addonId: string, config: Record<string, unknown>): void;
|
|
56
|
-
/** Legacy per-provider settings (used by some older wiring paths). */
|
|
57
|
-
getAllProvider(providerId: string): Record<string, unknown>;
|
|
58
|
-
setProvider(providerId: string, key: string, value: unknown): void;
|
|
59
|
-
/** Legacy flat per-device settings. Kept for backward compatibility
|
|
60
|
-
* with pre-addon wiring; new code should use the addon-device API. */
|
|
61
|
-
getAllDevice(deviceId: string): Record<string, unknown>;
|
|
62
|
-
setDevice(deviceId: string, key: string, value: unknown): void;
|
|
63
|
-
/**
|
|
64
|
-
* Multi-level addon settings: per-device overrides for a specific addon.
|
|
65
|
-
*
|
|
66
|
-
* Resolution chain when the frontend asks for the effective value of a
|
|
67
|
-
* field on a given device:
|
|
68
|
-
* 1. schema default (from `ConfigUISchema.field.default`)
|
|
69
|
-
* 2. `getAllAddon(addonId)` — addon global value
|
|
70
|
-
* 3. `getAddonDevice(addonId, devId)` — per-device override (wins)
|
|
71
|
-
*
|
|
72
|
-
* Only fields declared with `scope: 'device'` in the addon's
|
|
73
|
-
* `ConfigUISchema` should ever be persisted here.
|
|
74
|
-
*/
|
|
75
|
-
getAddonDevice(addonId: string, deviceId: string): Record<string, unknown>;
|
|
76
|
-
setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void;
|
|
77
|
-
clearAddonDevice(addonId: string, deviceId: string): void;
|
|
78
|
-
/**
|
|
79
|
-
* Per-device runtime state — the whole cap-slice map as ONE row in the
|
|
80
|
-
* `device-runtime-state` collection.
|
|
81
|
-
*
|
|
82
|
-
* These are separate from `getAddonDevice`/`setAddonDevice` because runtime
|
|
83
|
-
* state is not an addon's config: it belongs to the device, it is replaced
|
|
84
|
-
* wholesale on every flush, and it is written orders of magnitude more often.
|
|
85
|
-
* It USED to share that keyspace under a synthetic `__device-state` addonId,
|
|
86
|
-
* which cost one row (and one INSERT) per cap slice per flush and left the
|
|
87
|
-
* collection it is named for empty on every installation. The backend keeps
|
|
88
|
-
* reading the legacy prefix; see `SqliteSettingsBackend.getDeviceRuntimeState`.
|
|
89
|
-
*/
|
|
90
|
-
getDeviceRuntimeState(deviceId: string): Record<string, unknown>;
|
|
91
|
-
setDeviceRuntimeState(deviceId: string, blob: Record<string, unknown>): void;
|
|
92
|
-
clearDeviceRuntimeState(deviceId: string): void;
|
|
93
|
-
}
|
|
2
|
+
import { FeatureManifest, ISettingsStoreProvider } from '@camstack/types';
|
|
3
|
+
import { ISettingsStore, KernelAddonSettingsView } from './settings-view-types.js';
|
|
4
|
+
import { SystemSettingsPreload } from './system-settings-mirror.js';
|
|
5
|
+
export type { AddonSettingsView, ISettingsStore, KernelAddonSettingsView, SettingsViewBackend, } from './settings-view-types.js';
|
|
6
|
+
export type { SystemSettingsPreload } from './system-settings-mirror.js';
|
|
94
7
|
export declare class ConfigManager {
|
|
95
8
|
private readonly configPath;
|
|
96
9
|
private bootstrapConfig;
|
|
97
10
|
private settingsStore;
|
|
98
11
|
private settingsDoor;
|
|
12
|
+
/** Boot-time mirror of the preloaded `system-settings` keys (door mode, D675). */
|
|
13
|
+
private readonly systemMirror;
|
|
99
14
|
constructor(configPath: string);
|
|
100
15
|
/**
|
|
101
16
|
* Wire the settings-store backend. Called once, after the `settings-store`
|
|
@@ -109,9 +24,25 @@ export declare class ConfigManager {
|
|
|
109
24
|
* prefers the sync store when both are present.
|
|
110
25
|
*/
|
|
111
26
|
setSettingsDoor(door: ISettingsStoreProvider): void;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves once the boot read of the preloaded `system-settings` keys has
|
|
29
|
+
* settled — the hook the addon registry uses to log it and to replay the
|
|
30
|
+
* capability preferences onto providers that registered before it (D675).
|
|
31
|
+
* `null` when no door has been wired (a sync store needs no preload).
|
|
32
|
+
*/
|
|
33
|
+
whenSystemSettingsPreloaded(): Promise<SystemSettingsPreload> | null;
|
|
34
|
+
private backendMode;
|
|
35
|
+
/** The refusal every sync addon-scoped reader raises without a sync store. */
|
|
36
|
+
private refuseSyncRead;
|
|
112
37
|
/**
|
|
113
38
|
* Get a config value by dot-notation path.
|
|
114
|
-
* Priority: bootstrap config (yaml) → settings-store → RUNTIME_DEFAULTS
|
|
39
|
+
* Priority: bootstrap config (yaml + env) → settings-store → RUNTIME_DEFAULTS.
|
|
40
|
+
*
|
|
41
|
+
* The store layer (D675) never answers a bootstrap-owned key outside the
|
|
42
|
+
* allow-list (`storeMayAnswer`). With only the door (production) it answers
|
|
43
|
+
* the PRELOADED keys from the boot mirror and REFUSES the rest
|
|
44
|
+
* ({@link SettingsReadUnavailableError}) — it used to skip the store in
|
|
45
|
+
* silence. Before any backend is wired it is absent (boot-time readers).
|
|
115
46
|
*
|
|
116
47
|
* Returns `unknown` — callers must narrow via `typeof`/type guards, OR use
|
|
117
48
|
* the generic overload at their own risk. The generic overload exists only
|
|
@@ -122,10 +53,19 @@ export declare class ConfigManager {
|
|
|
122
53
|
get<T>(configPath: string): T | undefined;
|
|
123
54
|
private resolveConfigValue;
|
|
124
55
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
56
|
+
* The settings-store layer of {@link get}: the stored value, `undefined`
|
|
57
|
+
* when the store holds nothing (or may not answer this key), or a THROW
|
|
58
|
+
* when the store cannot be asked.
|
|
127
59
|
*/
|
|
128
|
-
|
|
60
|
+
private readStoreLayer;
|
|
61
|
+
/**
|
|
62
|
+
* Write a value to the settings-store. Resolves only once the write LANDED:
|
|
63
|
+
* on the isolated engine it awaits the door, and a failed write REJECTS and
|
|
64
|
+
* is not reported back by the mirror (D675 — it used to be fire-and-forget,
|
|
65
|
+
* so a lost write read as saved for the life of the process). Rejects when
|
|
66
|
+
* no store is wired.
|
|
67
|
+
*/
|
|
68
|
+
set(key: string, value: unknown): Promise<void>;
|
|
129
69
|
/**
|
|
130
70
|
* Bulk-read all keys that belong to a logical section.
|
|
131
71
|
* A "section" is the first segment of a dot-notation key (e.g. 'features', 'logging').
|
|
@@ -140,30 +80,48 @@ export declare class ConfigManager {
|
|
|
140
80
|
* shadowing the rest of the section that only lives in config.yaml.
|
|
141
81
|
* The kernel is agnostic about the store backend — any `ISettingsStore`
|
|
142
82
|
* implementation (sqlite, redis, in-memory, …) behaves identically here.
|
|
83
|
+
*
|
|
84
|
+
* D675: a bootstrap-owned section takes ONLY allow-listed keys from the
|
|
85
|
+
* store (a stored `auth.jwtSecret` never replaces env), and with only the
|
|
86
|
+
* door wired this sync read THROWS — the settings view's `getSection` reads
|
|
87
|
+
* the door.
|
|
143
88
|
*/
|
|
144
89
|
getSection(section: string): Record<string, unknown>;
|
|
90
|
+
/** Runtime defaults, then config.yaml + env — the layers under the store. */
|
|
91
|
+
private sectionBase;
|
|
92
|
+
/**
|
|
93
|
+
* {@link getSection} through the async door — what the door-backed settings
|
|
94
|
+
* view answers (D675). Same layers, same allow-list; a failed door read
|
|
95
|
+
* THROWS rather than handing back the defaults as if nothing were stored.
|
|
96
|
+
*/
|
|
97
|
+
private readSectionThroughDoor;
|
|
145
98
|
/**
|
|
146
99
|
* Bulk-write a logical section to the settings-store.
|
|
147
100
|
* Each entry in `data` is persisted under the key `section.<entryKey>`.
|
|
148
101
|
*/
|
|
149
102
|
setSection(section: string, data: Record<string, unknown>): void;
|
|
150
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Read all config for an addon — SYNC store only. THROWS without one: its
|
|
105
|
+
* old bootstrap fallback answered `{}` for every addon on every
|
|
106
|
+
* isolated-engine hub (D675). Async three-way: {@link readAddonGlobalStrict}.
|
|
107
|
+
*/
|
|
151
108
|
getAddonConfig(addonId: string): Record<string, unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* One addon's global store, three-way (D675): the record, `{}` when the
|
|
111
|
+
* store ANSWERED with nothing, or a THROW when it could not be asked.
|
|
112
|
+
* Behind `addonSettingsRaw.getGlobal` and `$hub.getAddonConfig`.
|
|
113
|
+
*/
|
|
114
|
+
readAddonGlobalStrict(addonId: string): Promise<Record<string, unknown>>;
|
|
115
|
+
/** One addon's per-device overrides, three-way like {@link readAddonGlobalStrict}. */
|
|
116
|
+
readAddonDeviceStrict(addonId: string, deviceId: number): Promise<Record<string, unknown>>;
|
|
152
117
|
/** Write (bulk-replace) config for an addon to addon_settings. */
|
|
153
118
|
setAddonConfig(addonId: string, config: Record<string, unknown>): void;
|
|
154
|
-
/** Read all config for a provider from provider_settings. */
|
|
155
|
-
getProviderConfig(providerId: string): Record<string, unknown>;
|
|
156
|
-
/** Write (upsert) a single key for a provider to provider_settings. */
|
|
157
|
-
setProviderConfig(providerId: string, key: string, value: unknown): void;
|
|
158
|
-
/** Read all config for a device from device_settings. */
|
|
159
|
-
getDeviceConfig(deviceId: string): Record<string, unknown>;
|
|
160
|
-
/** Write (upsert) a single key for a device to device_settings. */
|
|
161
|
-
setDeviceConfig(deviceId: string, key: string, value: unknown): void;
|
|
162
119
|
/**
|
|
163
120
|
* Read per-device overrides for a specific addon. Returns a flat record
|
|
164
121
|
* of field → value. Missing keys fall back to the addon-global value via
|
|
165
122
|
* `getAddonConfig(addonId)` — that composition is the resolver service's
|
|
166
123
|
* responsibility, not ConfigManager's.
|
|
124
|
+
* SYNC store only — THROWS without one (D675); async: {@link readAddonDeviceStrict}.
|
|
167
125
|
*/
|
|
168
126
|
getAddonDevice(addonId: string, deviceId: string): Record<string, unknown>;
|
|
169
127
|
/**
|
|
@@ -178,9 +136,8 @@ export declare class ConfigManager {
|
|
|
178
136
|
*/
|
|
179
137
|
clearAddonDevice(addonId: string, deviceId: string): void;
|
|
180
138
|
/**
|
|
181
|
-
* The device's persisted runtime-state blob.
|
|
182
|
-
*
|
|
183
|
-
* which is what every caller already handles.
|
|
139
|
+
* The device's persisted runtime-state blob. THROWS before the store is
|
|
140
|
+
* wired: a store not asked is not an empty row (D677, D675).
|
|
184
141
|
*/
|
|
185
142
|
getDeviceRuntimeState(deviceId: string): Record<string, unknown>;
|
|
186
143
|
/**
|
|
@@ -208,42 +165,31 @@ export declare class ConfigManager {
|
|
|
208
165
|
* That is what made `storageMigration.start` fail on its first durable write
|
|
209
166
|
* while every other addon's persistence worked. See D294.
|
|
210
167
|
*/
|
|
211
|
-
createSettingsView(addonId: string):
|
|
168
|
+
createSettingsView(addonId: string): KernelAddonSettingsView;
|
|
212
169
|
/** The async-door backend of {@link createSettingsView}. */
|
|
213
170
|
private createDoorBackedSettingsView;
|
|
214
171
|
/** The sync-`ISettingsStore` backend of {@link createSettingsView}. */
|
|
215
172
|
private createStoreSettingsView;
|
|
173
|
+
/** Is a SYNC `ISettingsStore` wired? (The store-backed view's D294-window test.) */
|
|
174
|
+
hasSyncStore(): boolean;
|
|
216
175
|
/** Get a value from the parsed bootstrap config.
|
|
217
176
|
* Generic overload is a documented type-level bridge — callers are responsible
|
|
218
177
|
* for passing a T that matches the config.yaml shape. */
|
|
219
178
|
getBootstrap(configPath: string): unknown;
|
|
220
179
|
getBootstrap<T>(configPath: string): T | undefined;
|
|
221
|
-
/** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
|
|
222
|
-
get features(): FeatureManifest;
|
|
223
180
|
/**
|
|
224
|
-
*
|
|
181
|
+
* Features — the sync store when wired, else RUNTIME_DEFAULTS DELIBERATELY
|
|
182
|
+
* (not `get()`, which refuses in door mode, D675): nothing writes
|
|
183
|
+
* `features.*` beyond sqlite-settings' first-boot seed of these defaults.
|
|
225
184
|
*/
|
|
185
|
+
get features(): FeatureManifest;
|
|
186
|
+
/** Bootstrap config + runtime defaults, for backward compat. */
|
|
226
187
|
get raw(): AppConfig;
|
|
227
|
-
/** Sections that live in config.yaml. Everything else goes to the settings-store. */
|
|
228
|
-
private static readonly BOOTSTRAP_SECTIONS;
|
|
229
188
|
/**
|
|
230
|
-
* Atomically update one top-level section
|
|
231
|
-
*
|
|
232
|
-
* Runtime settings must use setSection() which writes to the settings-store.
|
|
189
|
+
* Atomically update one top-level bootstrap section (server, auth, mode) of
|
|
190
|
+
* config.yaml and sync in-memory. Runtime settings use setSection().
|
|
233
191
|
*/
|
|
234
192
|
update(section: string, data: Record<string, unknown>): void;
|
|
235
|
-
/**
|
|
236
|
-
* Deep-set a value in a nested plain object using a dot-notation path.
|
|
237
|
-
* Returns a new object (immutable).
|
|
238
|
-
*/
|
|
239
|
-
private setNested;
|
|
240
|
-
/**
|
|
241
|
-
* Apply env var overrides onto the raw YAML object.
|
|
242
|
-
* Only bootstrap-level env vars are applied.
|
|
243
|
-
*/
|
|
244
|
-
private applyEnvOverrides;
|
|
245
|
-
private loadYaml;
|
|
246
|
-
private warnDefaultCredentials;
|
|
247
193
|
private getFromBootstrap;
|
|
248
194
|
private getFromRuntimeDefaults;
|
|
249
195
|
/**
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export { overlayGetStatus, UNCLAIMED } from './status-overlay.js';
|
|
|
19
19
|
export type { ClaimedStatusResolver, StatusOverlay, StatusOverlayAnswer } from './status-overlay.js';
|
|
20
20
|
export { describeProviderKindDrift } from './provider-kind-drift.js';
|
|
21
21
|
export type { ProviderKindHint } from './provider-kind-drift.js';
|
|
22
|
-
export type { CapabilityRouter, CapabilityRouterFactory, ConfigReader, ProviderOwnerToken, } from './capability-registry.js';
|
|
22
|
+
export type { CapabilityRouter, CapabilityRouterFactory, ConfigReader, PersistedPreferenceReplay, ProviderOwnerToken, } from './capability-registry.js';
|
|
23
23
|
export { CustomActionRegistry, type CustomActionEntry } from './custom-action-registry.js';
|
|
24
24
|
export { INFRA_CAPABILITIES, isInfraCapability } from './infra-capabilities.js';
|
|
25
25
|
export type { InfraCapability } from './infra-capabilities.js';
|
|
@@ -27,7 +27,10 @@ export { isolatedBuiltinPhase, partitionIsolatedBuiltinIds, runHubAddonBoot, wai
|
|
|
27
27
|
export type { IsolatedBuiltinPhase, IsolatedBuiltinWaves, CapabilityName, WaitUntilReadyOptions, HubAddonBootSteps, } from './isolated-builtin-phase.js';
|
|
28
28
|
export { createDoorSettingsView } from './settings-door-view.js';
|
|
29
29
|
export { ConfigManager } from './config-manager.js';
|
|
30
|
-
export type { ISettingsStore } from './config-manager.js';
|
|
30
|
+
export type { ISettingsStore, KernelAddonSettingsView, SystemSettingsPreload, } from './config-manager.js';
|
|
31
|
+
export { SettingsReadUnavailableError, isSettingsReadUnavailable } from './settings-read-error.js';
|
|
32
|
+
export type { SettingsReadRefusal } from './settings-read-error.js';
|
|
33
|
+
export { storeMayAnswer, STORE_READABLE_BOOTSTRAP_KEYS } from './system-settings-policy.js';
|
|
31
34
|
export { bootstrapSchema, RUNTIME_DEFAULTS, DEFAULT_DATA_PATH } from './config-schema.js';
|
|
32
35
|
export type { BootstrapConfig, AppConfig, ServerMode } from './config-schema.js';
|
|
33
36
|
export type { AddonSettingsView } from './config-manager.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IScopedLogger } from '@camstack/types';
|
|
2
|
+
/** Where the load was for — named on the refusal line. */
|
|
3
|
+
export type RuntimeStateLoadPhase = 'create' | 'accessory-child' | 'rebuild' | 'context';
|
|
4
|
+
/** A device build refused because its runtime state could not be loaded. */
|
|
5
|
+
export declare class RuntimeStateLoadError extends Error {
|
|
6
|
+
readonly deviceId: number;
|
|
7
|
+
constructor(deviceId: number, reason: string, options?: ErrorOptions);
|
|
8
|
+
}
|
|
9
|
+
export interface RuntimeStateLoadTarget {
|
|
10
|
+
readonly deviceId: number;
|
|
11
|
+
readonly stableId: string;
|
|
12
|
+
readonly phase: RuntimeStateLoadPhase;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Load the device's runtime state, or throw {@link RuntimeStateLoadError}
|
|
16
|
+
* after one warn naming the device. An answer that is not an object is a
|
|
17
|
+
* failed load too — it would hydrate as nothing.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadRuntimeStateOrRefuse(load: () => Promise<unknown>, logger: IScopedLogger, target: RuntimeStateLoadTarget): Promise<Record<string, unknown>>;
|
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
import { ISettingsStoreProvider } from '@camstack/types';
|
|
2
2
|
import { SettingsViewBackend } from './config-manager.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every `system-settings` row under `prefix`, keyed by the suffix — or a throw.
|
|
5
|
+
*
|
|
6
|
+
* `system-settings` stores the value ITSELF (`setSystem` / `door.set` write
|
|
7
|
+
* `JSON.stringify(value)`), mostly scalars. The engine's `query` hands a KV row
|
|
8
|
+
* back parsed only when its text is an object or an array; a scalar comes back
|
|
9
|
+
* as `{ value: <its JSON text> }` — `"12h"` arrives as `{ value: '"12h"' }`.
|
|
10
|
+
* That is not a value anyone stored, and guessing it back would misread a
|
|
11
|
+
* stored object that happens to be `{ value: … }`. So the range read only
|
|
12
|
+
* ENUMERATES the keys (same keyset walk and refusal as every prefix read
|
|
13
|
+
* here), and each value is taken from `get`, which parses the row exactly
|
|
14
|
+
* (D675). These reads are small and rare: a section's handful of keys when a
|
|
15
|
+
* form opens, and the capability preferences once at boot.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadSystemSettingsPrefix(door: ISettingsStoreProvider, prefix: string,
|
|
18
|
+
/** Suffixes the caller may read. A refused one is never FETCHED — the
|
|
19
|
+
* `auth` section's credential rows never enter this process (D675). */
|
|
20
|
+
mayRead?: (suffix: string) => boolean): Promise<Record<string, unknown>>;
|
|
21
|
+
/**
|
|
22
|
+
* The `system-settings` section surface a door-backed view delegates to.
|
|
23
|
+
* `getSection` is ASYNC: it reads the section through the door (D675) — a
|
|
24
|
+
* synchronous reader has no store to ask when the engine is isolated.
|
|
25
|
+
*/
|
|
3
26
|
export interface DoorSectionAccess {
|
|
4
|
-
getSection(section: string): Record<string, unknown
|
|
27
|
+
getSection(section: string): Promise<Record<string, unknown>>;
|
|
5
28
|
setSection(section: string, patch: Record<string, unknown>): void | Promise<void>;
|
|
6
29
|
}
|
|
7
30
|
export declare function createDoorSettingsView(addonId: string, door: ISettingsStoreProvider, sections: DoorSectionAccess): SettingsViewBackend;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The refusal a SYNCHRONOUS settings reader raises when it cannot answer — D675.
|
|
3
|
+
*
|
|
4
|
+
* `ConfigManager`'s sync readers (`get`, `getSection`, `getAddonConfig`, …)
|
|
5
|
+
* used to answer `{}` / a runtime default whenever no sync `ISettingsStore`
|
|
6
|
+
* was wired. Since D231 isolated `sqlite-settings`, hub-main never gets one:
|
|
7
|
+
* only the async door. So every such read answered "nothing stored" for the
|
|
8
|
+
* life of the process, and "nothing stored" is exactly what a store holding
|
|
9
|
+
* nothing looks like (D315, D393). The readers now refuse instead, and this
|
|
10
|
+
* is what they throw, so a caller can tell "the store could not be asked"
|
|
11
|
+
* from every other failure and from "never written".
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Why the read was refused.
|
|
15
|
+
*
|
|
16
|
+
* - `door-only` — the engine is isolated: only the async door is wired, and a
|
|
17
|
+
* synchronous caller cannot await it. Use the settings view instead.
|
|
18
|
+
* - `not-wired` — no backend at all yet (the D294 window, before
|
|
19
|
+
* `setSettingsStore` / `setSettingsDoor`).
|
|
20
|
+
* - `preload-pending` — the key is one of the few the kernel loads from the
|
|
21
|
+
* door into memory at boot, and that one read has not settled yet.
|
|
22
|
+
* - `preload-failed` — that boot read failed; the value is unknown until the
|
|
23
|
+
* next boot (one read, no polling — D3).
|
|
24
|
+
* - `read-failed` — the async read itself threw.
|
|
25
|
+
*/
|
|
26
|
+
export type SettingsReadRefusal = 'door-only' | 'not-wired' | 'preload-pending' | 'preload-failed' | 'read-failed';
|
|
27
|
+
export declare class SettingsReadUnavailableError extends Error {
|
|
28
|
+
/** The reader that refused, e.g. `ConfigManager.getAddonConfig`. */
|
|
29
|
+
readonly reader: string;
|
|
30
|
+
readonly reason: SettingsReadRefusal;
|
|
31
|
+
readonly name = "SettingsReadUnavailableError";
|
|
32
|
+
constructor(
|
|
33
|
+
/** The reader that refused, e.g. `ConfigManager.getAddonConfig`. */
|
|
34
|
+
reader: string, reason: SettingsReadRefusal, detail: string);
|
|
35
|
+
}
|
|
36
|
+
/** Narrow an unknown throw to {@link SettingsReadUnavailableError}. */
|
|
37
|
+
export declare function isSettingsReadUnavailable(err: unknown): err is SettingsReadUnavailableError;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { AddonStoreRead, StoredKeyRead } from '@camstack/types';
|
|
2
|
+
export interface AddonSettingsView {
|
|
3
|
+
readAddonStore(): Promise<Record<string, unknown>>;
|
|
4
|
+
/** See `IAddonContext['settings'].readAddonStoreResult` (D651). */
|
|
5
|
+
readAddonStoreResult?(): Promise<AddonStoreRead>;
|
|
6
|
+
/** See `IAddonContext['settings'].readAddonStoreKeyResult` (D675). */
|
|
7
|
+
readAddonStoreKeyResult?(key: string): Promise<StoredKeyRead>;
|
|
8
|
+
writeAddonStore(patch: Record<string, unknown>): Promise<void>;
|
|
9
|
+
readDeviceStore(deviceId: number): Promise<Record<string, unknown>>;
|
|
10
|
+
/** See `IAddonContext['settings'].readDeviceStoreBatch` — optional there and
|
|
11
|
+
* here for the same reason: only the door-backed view can answer it in one
|
|
12
|
+
* range scan, and a caller must fall back rather than refuse. */
|
|
13
|
+
readDeviceStoreBatch?(deviceIds: readonly number[]): Promise<ReadonlyMap<number, Record<string, unknown>>>;
|
|
14
|
+
writeDeviceStore(deviceId: number, patch: Record<string, unknown>): Promise<void>;
|
|
15
|
+
clearDeviceStore(deviceId: number): Promise<void>;
|
|
16
|
+
getSection(section: string): Promise<Record<string, unknown>>;
|
|
17
|
+
setSection(section: string, patch: Record<string, unknown>): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Per-device runtime state — separate namespace from
|
|
20
|
+
* `readDeviceStore` / `writeDeviceStore` (which is the addon's
|
|
21
|
+
* config blob: host, password, deviceCache, …). Holds mutable
|
|
22
|
+
* runtime signals discovered after boot: battery snapshot, sleep
|
|
23
|
+
* flag, last-seen timestamps. Stored under a kernel-internal
|
|
24
|
+
* `__device-state` namespace so the device's config schema
|
|
25
|
+
* never has to know about it (clean separation of operator
|
|
26
|
+
* intent from runtime telemetry).
|
|
27
|
+
*
|
|
28
|
+
* Patch semantics: shallow merge. Pass the FULL persistent slice
|
|
29
|
+
* each call (the kernel doesn't try to compute deltas) — concrete
|
|
30
|
+
* implementations of `DeviceRuntimeState` already coalesce in
|
|
31
|
+
* memory, so the kernel just persists the latest blob.
|
|
32
|
+
*/
|
|
33
|
+
readDeviceRuntimeState(deviceId: number): Promise<Record<string, unknown>>;
|
|
34
|
+
writeDeviceRuntimeState(deviceId: number, data: Record<string, unknown>): Promise<void>;
|
|
35
|
+
clearDeviceRuntimeState(deviceId: number): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A CONCRETE backend behind {@link ConfigManager.createSettingsView} — the
|
|
39
|
+
* sync-`ISettingsStore` one or the async-door one.
|
|
40
|
+
*
|
|
41
|
+
* `readDeviceStoreBatch` is required here and optional on
|
|
42
|
+
* {@link AddonSettingsView} because both of these can answer it, so the view
|
|
43
|
+
* the kernel hands an addon never has to decide whether the method exists.
|
|
44
|
+
* The optionality on the public interface stays for the implementation that
|
|
45
|
+
* genuinely cannot batch: a forked runner's UDS-backed view.
|
|
46
|
+
*/
|
|
47
|
+
export interface SettingsViewBackend extends AddonSettingsView {
|
|
48
|
+
readDeviceStoreBatch(deviceIds: readonly number[]): Promise<ReadonlyMap<number, Record<string, unknown>>>;
|
|
49
|
+
readAddonStoreKeyResult(key: string): Promise<StoredKeyRead>;
|
|
50
|
+
}
|
|
51
|
+
export interface ISettingsStore {
|
|
52
|
+
getSystem(key: string): unknown;
|
|
53
|
+
setSystem(key: string, value: unknown): void;
|
|
54
|
+
getAllSystem(): Record<string, unknown>;
|
|
55
|
+
/** Addon-level global settings (apply to every device unless overridden). */
|
|
56
|
+
getAllAddon(addonId: string): Record<string, unknown>;
|
|
57
|
+
setAllAddon(addonId: string, config: Record<string, unknown>): void;
|
|
58
|
+
/** Legacy per-provider settings (used by some older wiring paths). */
|
|
59
|
+
getAllProvider(providerId: string): Record<string, unknown>;
|
|
60
|
+
setProvider(providerId: string, key: string, value: unknown): void;
|
|
61
|
+
/** Legacy flat per-device settings. Kept for backward compatibility
|
|
62
|
+
* with pre-addon wiring; new code should use the addon-device API. */
|
|
63
|
+
getAllDevice(deviceId: string): Record<string, unknown>;
|
|
64
|
+
setDevice(deviceId: string, key: string, value: unknown): void;
|
|
65
|
+
/**
|
|
66
|
+
* Multi-level addon settings: per-device overrides for a specific addon.
|
|
67
|
+
*
|
|
68
|
+
* Resolution chain when the frontend asks for the effective value of a
|
|
69
|
+
* field on a given device:
|
|
70
|
+
* 1. schema default (from `ConfigUISchema.field.default`)
|
|
71
|
+
* 2. `getAllAddon(addonId)` — addon global value
|
|
72
|
+
* 3. `getAddonDevice(addonId, devId)` — per-device override (wins)
|
|
73
|
+
*
|
|
74
|
+
* Only fields declared with `scope: 'device'` in the addon's
|
|
75
|
+
* `ConfigUISchema` should ever be persisted here.
|
|
76
|
+
*/
|
|
77
|
+
getAddonDevice(addonId: string, deviceId: string): Record<string, unknown>;
|
|
78
|
+
setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void;
|
|
79
|
+
clearAddonDevice(addonId: string, deviceId: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Per-device runtime state — the whole cap-slice map as ONE row in the
|
|
82
|
+
* `device-runtime-state` collection.
|
|
83
|
+
*
|
|
84
|
+
* These are separate from `getAddonDevice`/`setAddonDevice` because runtime
|
|
85
|
+
* state is not an addon's config: it belongs to the device, it is replaced
|
|
86
|
+
* wholesale on every flush, and it is written orders of magnitude more often.
|
|
87
|
+
* It USED to share that keyspace under a synthetic `__device-state` addonId,
|
|
88
|
+
* which cost one row (and one INSERT) per cap slice per flush and left the
|
|
89
|
+
* collection it is named for empty on every installation. The backend keeps
|
|
90
|
+
* reading the legacy prefix; see `SqliteSettingsBackend.getDeviceRuntimeState`.
|
|
91
|
+
*/
|
|
92
|
+
getDeviceRuntimeState(deviceId: string): Record<string, unknown>;
|
|
93
|
+
setDeviceRuntimeState(deviceId: string, blob: Record<string, unknown>): void;
|
|
94
|
+
clearDeviceRuntimeState(deviceId: string): void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The settings view `ConfigManager.createSettingsView` returns. Its three-way
|
|
98
|
+
* reads are REQUIRED here (optional on the public {@link AddonSettingsView}
|
|
99
|
+
* only for a forked runner's UDS view), so a caller holding a `ConfigManager`
|
|
100
|
+
* never has to decide whether they exist.
|
|
101
|
+
*/
|
|
102
|
+
export interface KernelAddonSettingsView extends AddonSettingsView {
|
|
103
|
+
readAddonStoreResult(): Promise<AddonStoreRead>;
|
|
104
|
+
readAddonStoreKeyResult(key: string): Promise<StoredKeyRead>;
|
|
105
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SettingsViewBackend } from './settings-view-types.js';
|
|
2
|
+
/** The `ConfigManager` surface this backend reads and writes through. */
|
|
3
|
+
export interface StoreViewHost {
|
|
4
|
+
hasSyncStore(): boolean;
|
|
5
|
+
getBootstrap(configPath: string): unknown;
|
|
6
|
+
getAddonConfig(addonId: string): Record<string, unknown>;
|
|
7
|
+
setAddonConfig(addonId: string, config: Record<string, unknown>): void;
|
|
8
|
+
getAddonDevice(addonId: string, deviceId: string): Record<string, unknown>;
|
|
9
|
+
setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void;
|
|
10
|
+
clearAddonDevice(addonId: string, deviceId: string): void;
|
|
11
|
+
getDeviceRuntimeState(deviceId: string): Record<string, unknown>;
|
|
12
|
+
setDeviceRuntimeState(deviceId: string, blob: Record<string, unknown>): void;
|
|
13
|
+
clearDeviceRuntimeState(deviceId: string): void;
|
|
14
|
+
getSection(section: string): Record<string, unknown>;
|
|
15
|
+
setSection(section: string, data: Record<string, unknown>): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createStoreSettingsView(addonId: string, cm: StoreViewHost): SettingsViewBackend;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ISettingsStoreProvider } from '@camstack/types';
|
|
2
|
+
/** The outcome of the one boot read, for the caller that logs and replays it. */
|
|
3
|
+
export type SystemSettingsPreload = {
|
|
4
|
+
readonly kind: 'loaded';
|
|
5
|
+
readonly keyCount: number;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'failed';
|
|
8
|
+
readonly error: string;
|
|
9
|
+
};
|
|
10
|
+
export declare class SystemSettingsMirror {
|
|
11
|
+
private state;
|
|
12
|
+
/** The door the current read belongs to; a replaced door owns the mirror. */
|
|
13
|
+
private door;
|
|
14
|
+
private preload;
|
|
15
|
+
/**
|
|
16
|
+
* Writes to preloaded keys made through THIS process. They win over the boot
|
|
17
|
+
* read (they are newer) and survive a failed one, so an operator's Save is
|
|
18
|
+
* reported back even when the boot read was lost.
|
|
19
|
+
*/
|
|
20
|
+
private readonly writes;
|
|
21
|
+
/** Start the ONE read for `door`. Called by `ConfigManager.setSettingsDoor`. */
|
|
22
|
+
load(door: ISettingsStoreProvider): void;
|
|
23
|
+
/** The in-flight / settled boot read, or `null` before any door. */
|
|
24
|
+
whenLoaded(): Promise<SystemSettingsPreload> | null;
|
|
25
|
+
/**
|
|
26
|
+
* A preloaded key's value: `undefined` when the store holds none, or a THROW
|
|
27
|
+
* (`preload-pending` / `preload-failed`) when it is not known.
|
|
28
|
+
*/
|
|
29
|
+
get(key: string): unknown;
|
|
30
|
+
/** Record a write to a preloaded key so the mirror reports it. */
|
|
31
|
+
noteWrite(key: string, value: unknown): void;
|
|
32
|
+
/** Every row under the preloaded prefixes plus each preloaded key. Never rejects. */
|
|
33
|
+
private read;
|
|
34
|
+
}
|