/** * Pre-built selectors over {@link ProtectState}. Every selector is a **pure function returning config records** (`ProtectCameraConfig`, ...), never a Layer-3 `Camera` * projection - selectors live in the data layer, and a projection needs the client (Layer 3, built by the `DeviceRegistry`). * * Returning config records is load-bearing, not stylistic. {@link StateStore.observe} detects change with `Object.is`; the reducer keeps a record's reference stable * across dispatches that did not touch it (structural sharing), and these selectors keep a *derived array's* reference stable across dispatches that did not touch its * backing map (memoization on map identity). A selector that constructed a fresh `Camera` on each call would compare unequal every time, fire every observer on every * dispatch, and defeat the "refresh is invisible when nothing drifted" guarantee. * * Memoization is keyed on the backing `Map` reference via a `WeakMap`: when the map identity is unchanged, the previously computed array is returned (`Object.is`-equal * to the prior return) and is garbage-collected automatically once the state that held the map is gone. * * That map-identity memo is the right regime for a selector that returns *records* (`all`, `online`): those views must change reference whenever any member's config * changes, because the consumer reads the records. A selector that returns a *membership set* - the ids adopted by this controller - wants the opposite: it must stay * referentially stable through config churn and change only when an id enters or leaves the set. That is a second, distinct regime, **content memoization** (see * {@link memoizeArrayByContent}): layered over the map-identity memo, it returns the previously computed array whenever the freshly derived one is element-for-element * equal, so a `lastSeen`/stats patch that rebuilds the backing map does not wake a membership observer - only a real add, removal, or adoption flip does. * * @module StateSelectors */ import type { ProtectCameraConfig, ProtectChimeConfig, ProtectFobConfig, ProtectLightConfig, ProtectNvrConfig, ProtectNvrLiveviewConfig, ProtectNvrUserConfig, ProtectRelayConfig, ProtectRingtoneConfig, ProtectSensorConfig, ProtectViewerConfig } from "../types/index.ts"; import type { ProtectState } from "../protocol/reducer.ts"; /** * Whether a device is currently connected to the controller. The single definition of "online" shared by the `online` selectors and the device projections' * `isOnline` getter, so the collection views and the per-device view never disagree. * * @param device - Any device config (only its `state` field is read). * * @returns `true` when the device is fully connected. * * @category State */ export declare function isDeviceOnline(device: { state: string; }): boolean; /** * Whether a device is adopted by *this* controller - owned and managed here, rather than merely visible on the network or owned by a different NVR. The single * definition of "adopted by us", shared by the membership-id selectors so the collection views and any per-device check never disagree, exactly as {@link isDeviceOnline} * is the single definition of "online". * * The predicate is **ownership**, not **readiness**: `isAdopted` is set once any controller has adopted the device, and `isAdoptedByOther` marks one adopted by a * *different* NVR, so `isAdopted && !isAdoptedByOther` is precisely "ours". We deliberately do not also exclude the transient `isAdopting`: a device mid-adoption has not * yet flipped `isAdopted` true (so it is already excluded), and a device being *re*-provisioned is still owned by us - dropping it from the membership set on a transient * would churn the set, the opposite of what the membership selectors are for. "Can it stream right now?" is liveness ({@link isDeviceOnline}), a separate question. * * @param device - Any device config (only its adoption flags are read). * * @returns `true` when the device is adopted by this controller and not by another. * * @category State */ export declare function isDeviceAdopted(device: { isAdopted: boolean; isAdoptedByOther: boolean; }): boolean; /** * The base pair of selectors every id-keyed collection exposes: the full array (memoized on the backing map's identity) and an O(1) by-id lookup. * * @typeParam T - The collection's config record type. * * @category State */ export interface CollectionViews { all: (state: ProtectState) => readonly T[]; byId: (id: string) => (state: ProtectState) => T | undefined; } /** * The selectors every *device* collection exposes: the {@link CollectionViews} pair (records, memoized on map identity), the connected-only subset, and the * content-memoized set of ids adopted by this controller - the membership set a reactive consumer observes to react to devices being adopted or removed without waking on * every config patch. See {@link memoizeArrayByContent} for the content-vs-identity memoization distinction `adoptedIds` rests on. * * @typeParam T - The collection's config record type. * * @category State */ export interface CollectionSelectors extends CollectionViews { adoptedIds: (state: ProtectState) => readonly string[]; online: (state: ProtectState) => readonly T[]; } /** All cameras, memoized on the camera-map identity. @category State */ export declare const selectCameras: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/camera.ts").ProtectCameraConfigInterface[]; /** A single camera by id, or `undefined` when absent. @category State */ export declare const selectCamera: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/camera.ts").ProtectCameraConfigInterface | undefined; /** The connected cameras only. @category State */ export declare const selectOnlineCameras: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/camera.ts").ProtectCameraConfigInterface[]; /** The ids of the cameras adopted by this controller, content-memoized to change reference only when the membership set changes. @category State */ export declare const selectAdoptedCameraIds: (state: ProtectState) => readonly string[]; /** All chimes, memoized on the chime-map identity. @category State */ export declare const selectChimes: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/chime.ts").ProtectChimeConfigInterface[]; /** A single chime by id, or `undefined` when absent. @category State */ export declare const selectChime: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/chime.ts").ProtectChimeConfigInterface | undefined; /** The connected chimes only. @category State */ export declare const selectOnlineChimes: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/chime.ts").ProtectChimeConfigInterface[]; /** The ids of the chimes adopted by this controller, content-memoized to change reference only when the membership set changes. @category State */ export declare const selectAdoptedChimeIds: (state: ProtectState) => readonly string[]; /** All fobs, memoized on the fob-map identity. @category State */ export declare const selectFobs: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/fob.ts").ProtectFobConfigInterface[]; /** A single fob by id, or `undefined` when absent. @category State */ export declare const selectFob: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/fob.ts").ProtectFobConfigInterface | undefined; /** The connected fobs only. @category State */ export declare const selectOnlineFobs: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/fob.ts").ProtectFobConfigInterface[]; /** The ids of the fobs adopted by this controller, content-memoized to change reference only when the membership set changes. @category State */ export declare const selectAdoptedFobIds: (state: ProtectState) => readonly string[]; /** All lights, memoized on the light-map identity. @category State */ export declare const selectLights: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/light.ts").ProtectLightConfigInterface[]; /** A single light by id, or `undefined` when absent. @category State */ export declare const selectLight: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/light.ts").ProtectLightConfigInterface | undefined; /** The connected lights only. @category State */ export declare const selectOnlineLights: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/light.ts").ProtectLightConfigInterface[]; /** The ids of the lights adopted by this controller, content-memoized to change reference only when the membership set changes. @category State */ export declare const selectAdoptedLightIds: (state: ProtectState) => readonly string[]; /** All relays, memoized on the relay-map identity. @category State */ export declare const selectRelays: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/relay.ts").ProtectRelayConfigInterface[]; /** A single relay by id, or `undefined` when absent. @category State */ export declare const selectRelay: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/relay.ts").ProtectRelayConfigInterface | undefined; /** The connected relays only. @category State */ export declare const selectOnlineRelays: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/relay.ts").ProtectRelayConfigInterface[]; /** The ids of the relays adopted by this controller, content-memoized to change reference only when the membership set changes. @category State */ export declare const selectAdoptedRelayIds: (state: ProtectState) => readonly string[]; /** All sensors, memoized on the sensor-map identity. @category State */ export declare const selectSensors: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/sensor.ts").ProtectSensorConfigInterface[]; /** A single sensor by id, or `undefined` when absent. @category State */ export declare const selectSensor: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/sensor.ts").ProtectSensorConfigInterface | undefined; /** The connected sensors only. @category State */ export declare const selectOnlineSensors: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/sensor.ts").ProtectSensorConfigInterface[]; /** The ids of the sensors adopted by this controller, content-memoized to change reference only when the membership set changes. @category State */ export declare const selectAdoptedSensorIds: (state: ProtectState) => readonly string[]; /** All viewers, memoized on the viewer-map identity. @category State */ export declare const selectViewers: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/viewer.ts").ProtectViewerConfigInterface[]; /** A single viewer by id, or `undefined` when absent. @category State */ export declare const selectViewer: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/viewer.ts").ProtectViewerConfigInterface | undefined; /** The connected viewers only. @category State */ export declare const selectOnlineViewers: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/viewer.ts").ProtectViewerConfigInterface[]; /** The ids of the viewers adopted by this controller, content-memoized to change reference only when the membership set changes. @category State */ export declare const selectAdoptedViewerIds: (state: ProtectState) => readonly string[]; /** All liveviews, memoized on the liveview-map identity. Each record carries its `slots[].cameras`. @category State */ export declare const selectLiveviews: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/nvr.ts").ProtectNvrLiveviewConfigInterface[]; /** A single liveview by id, or `undefined` when absent. @category State */ export declare const selectLiveview: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/nvr.ts").ProtectNvrLiveviewConfigInterface | undefined; /** All ringtones, memoized on the ringtone-map identity. @category State */ export declare const selectRingtones: (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => readonly import("../types/nvr.ts").ProtectRingtoneConfigInterface[]; /** A single ringtone by id, or `undefined` when absent. @category State */ export declare const selectRingtone: (id: string) => (state: Readonly<{ authUserId: string | null; bootstrapId: number; cameras: ReadonlyMap; chimes: ReadonlyMap; fobs: ReadonlyMap; lights: ReadonlyMap; liveviews: ReadonlyMap; nvr: ProtectNvrConfig | null; relays: ReadonlyMap; ringtones: ReadonlyMap; sensors: ReadonlyMap; users: ReadonlyMap; viewers: ReadonlyMap; }>) => import("../types/nvr.ts").ProtectRingtoneConfigInterface | undefined; /** * The authenticated session's own user record, or `null` when there is no such user (before the first bootstrap, or if the id is absent from the roster). The * single-source primitive for session-identity facts: {@link selectIsAdmin} derives from it, and consumers can `observe` it directly to react to "who am I" changes. * Reads straight through the roster map, so the returned record reference is structurally-sharing-stable across dispatches that did not touch it - no memoization needed. * * @param state - The current state. * * @returns The authenticated user's config record, or `null`. * * @category State */ export declare function selectAuthUser(state: ProtectState): ProtectNvrUserConfig | null; /** * Whether the authenticated session has Super Admin (camera-write) privileges. Derived from the session user's record and the controller's permission grammar; `false` * before the first bootstrap or when the session's user is absent. Re-evaluated whenever the user's record changes, so a role change at the controller surfaces on the * next bootstrap refresh. * * @param state - The current state. * * @returns `true` when the authenticated user holds the administrative camera-write permission. * * @category State */ export declare function selectIsAdmin(state: ProtectState): boolean; /** * The controller's NVR configuration record, or `null` before the first bootstrap. The single-source primitive the {@link Nvr} singleton projection reads through (its * `config` getter and `observe()`), and the record the narrower NVR-derived selectors ({@link selectControllerName}, ...) pull their fields from. Reads straight through * the `nvr` singleton field, so the returned reference is structurally-sharing-stable across dispatches that did not touch it - no memoization needed; the observer's * `Object.is` dedup yields only when the record actually changes. * * @param state - The current state. * * @returns The NVR config record, or `null` when no bootstrap has been applied. * * @category State */ export declare function selectNvr(state: ProtectState): ProtectNvrConfig | null; /** * The controller's display label - its user-assigned name, else the always-present `marketName` - or `null` only before the first bootstrap (no `nvr` record yet), never * for a merely-unnamed controller (which surfaces its model). Single-sourced with `Nvr.name`, so the projection getter and this primitive never disagree; a consumer * needing the raw "was it explicitly named?" distinction reads `client.nvr.config.name`. Returns a primitive, so it needs no memoization: the value is its own * `Object.is` key for the observer's dedup, and two property reads are already cheaper than a cache lookup. * * @param state - The current state. * * @returns The NVR's display label, or `null` when the NVR is not yet known. * * @category State */ export declare function selectControllerName(state: ProtectState): string | null; /** * The controller's self-reported boot time (`nvr.upSince`), or `null` before the first bootstrap. We mirror the controller's own field name rather than inventing one, * exactly as {@link selectControllerName} mirrors `nvr.name` - the library renames only derived facts, never a value it passes straight through. * * This is the `ConnectionMonitor`'s **internal** reboot-detection input, not a consumer-facing selector, which is why it is not exported from the package surface. * Reboot is a derived, policy-laden fact with a single home: the monitor applies a noise-floor threshold to `upSince` (the wire value jitters by milliseconds across * bootstraps) and emits the blessed `controllerRebooted` event. A consumer wanting "did the controller reboot?" subscribes to that event; observing this raw selector * directly would re-derive reboot detection while bypassing the jitter threshold, firing spuriously on every refresh - a second, wrong source of truth. * * Returns a primitive, so it needs no memoization: even though the `nvr` record reference churns on nearly every refresh (`lastSeen`, `storageStats`), the monitor's * `Object.is` dedup over this selector yields only when the boot time itself changes. * * @param state - The current state. * * @returns The controller's boot time in epoch milliseconds, or `null` when the NVR is not yet known. * * @internal */ export declare function selectControllerUpSince(state: ProtectState): number | null; //# sourceMappingURL=selectors.d.ts.map