@camstack/addon-provider-reolink 1.2.216 → 1.2.217

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/addon.js +1916 -1113
  3. package/dist/addon.mjs +1916 -1113
  4. package/package.json +5 -2
package/dist/addon.js CHANGED
@@ -101028,7 +101028,7 @@ function buildInitialStatus2(config, enabled) {
101028
101028
  };
101029
101029
  }
101030
101030
  //#endregion
101031
- //#region ../types/dist/event-category-DmZDPDf_.mjs
101031
+ //#region ../types/dist/event-category-CaURIwQA.mjs
101032
101032
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
101033
101033
  EventCategory["SystemBoot"] = "system.boot";
101034
101034
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -101744,6 +101744,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
101744
101744
  EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
101745
101745
  /** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
101746
101746
  EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
101747
+ /** A migration failed or was blocked; the Alert Center re-reads the ledger. */
101748
+ EventCategory["MigrationFailed"] = "migration.failed";
101749
+ /** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
101750
+ EventCategory["MigrationApplied"] = "migration.applied";
101747
101751
  EventCategory["DeviceUpdated"] = "device.updated";
101748
101752
  /**
101749
101753
  * Transport-level connectivity. Emitted by the device driver when
@@ -101806,6 +101810,378 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
101806
101810
  return EventCategory;
101807
101811
  }({});
101808
101812
  //#endregion
101813
+ //#region ../types/dist/device-type-CEqjmUnR.mjs
101814
+ /**
101815
+ import { errMsg } from '@camstack/types'
101816
+ * Extract a human-readable message from an unknown error value.
101817
+ * Replaces the ubiquitous `errMsg(err)` pattern.
101818
+ */
101819
+ function errMsg$2(err) {
101820
+ if (err instanceof Error) return err.message;
101821
+ if (typeof err === "string") return err;
101822
+ return String(err);
101823
+ }
101824
+ var DeviceType = /* @__PURE__ */ function(DeviceType) {
101825
+ DeviceType["Camera"] = "camera";
101826
+ DeviceType["Hub"] = "hub";
101827
+ DeviceType["Light"] = "light";
101828
+ DeviceType["Siren"] = "siren";
101829
+ DeviceType["Switch"] = "switch";
101830
+ DeviceType["Sensor"] = "sensor";
101831
+ DeviceType["Thermostat"] = "thermostat";
101832
+ /** Air-conditioner / heat-pump climate device (HVAC) — shares the
101833
+ * `climate-control` cap surface with `Thermostat` but renders a
101834
+ * dedicated AC-appropriate control UI (mode chips, fan speed,
101835
+ * independent vertical/horizontal swing). Sources: native Gree, and
101836
+ * reusable by other AC integrations. */
101837
+ DeviceType["Climate"] = "climate";
101838
+ DeviceType["Button"] = "button";
101839
+ /** Generic stateless event emitter — carries a device's EXACT declared
101840
+ * event vocabulary verbatim (no normalization). Installed with the
101841
+ * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
101842
+ * HA bus events (e.g. `zha_event`, generic). */
101843
+ DeviceType["EventEmitter"] = "event-emitter";
101844
+ /** Firmware/software update entity — current vs available version,
101845
+ * updatable flag, update state, and an install action. Installed with
101846
+ * the `update` cap. Sources: Homematic firmware-update channels (and
101847
+ * reusable by other providers, e.g. HA `update.*` entities). */
101848
+ DeviceType["Update"] = "update";
101849
+ DeviceType["Generic"] = "generic";
101850
+ /** Generic notification delivery target (HA `notify.<service>`, future
101851
+ * Telegram / Discord / ntfy / SMTP, …). One device per delivery
101852
+ * endpoint; the `notifier` cap defines the send surface. */
101853
+ DeviceType["Notifier"] = "notifier";
101854
+ /** Pre-recorded action sequence with optional parameters
101855
+ * (HA `script.*`). Runnable via `script-runner` cap. */
101856
+ DeviceType["Script"] = "script";
101857
+ /** Automation rule (HA `automation.*`) — enable/disable + manual
101858
+ * trigger surface exposed via `automation-control` cap. */
101859
+ DeviceType["Automation"] = "automation";
101860
+ /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
101861
+ DeviceType["Lock"] = "lock";
101862
+ /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
101863
+ * `valve.*`). `cover` cap with sub-roles for variant. */
101864
+ DeviceType["Cover"] = "cover";
101865
+ /** Pipe / water / gas valve with open/close/stop and optional
101866
+ * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
101867
+ * modelled on the same open/closed lifecycle. */
101868
+ DeviceType["Valve"] = "valve";
101869
+ /** Humidifier / dehumidifier with on/off + target humidity + mode
101870
+ * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
101871
+ * modelled on the same target / mode lifecycle. */
101872
+ DeviceType["Humidifier"] = "humidifier";
101873
+ /** Water heater / boiler with target temperature + operation mode +
101874
+ * away mode (HA `water_heater.*`). `water-heater` cap — a
101875
+ * climate-family actuator. */
101876
+ DeviceType["WaterHeater"] = "water-heater";
101877
+ /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
101878
+ DeviceType["Fan"] = "fan";
101879
+ /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
101880
+ * the camera surface — those use `Camera`. `media-player` cap. */
101881
+ DeviceType["MediaPlayer"] = "media-player";
101882
+ /** Security panel / alarm system (HA `alarm_control_panel.*`).
101883
+ * `alarm-panel` cap. */
101884
+ DeviceType["AlarmPanel"] = "alarm-panel";
101885
+ /** Generic user-settable input (HA `number` / `input_number` / `select`
101886
+ * / `input_select` / `text` / `input_text` / `input_datetime`).
101887
+ * Sub-type via `DeviceRole`: NumericControl / SelectControl /
101888
+ * TextControl / DateTimeControl. */
101889
+ DeviceType["Control"] = "control";
101890
+ /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
101891
+ * `presence` cap. */
101892
+ DeviceType["Presence"] = "presence";
101893
+ /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
101894
+ * `weather` cap. */
101895
+ DeviceType["Weather"] = "weather";
101896
+ /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
101897
+ DeviceType["Vacuum"] = "vacuum";
101898
+ /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
101899
+ * `lawn-mower-control` cap. */
101900
+ DeviceType["LawnMower"] = "lawn-mower";
101901
+ /** Physical HA device group — parent container for entity-children
101902
+ * adopted from a single HA device entry. Not renderable as a
101903
+ * standalone device; exists only to anchor child entities. */
101904
+ DeviceType["Container"] = "container";
101905
+ /** Single still-image entity (HA `image.*`). Read-only display of an
101906
+ * `entity_picture` signed URL the browser loads directly. `image` cap. */
101907
+ DeviceType["Image"] = "image";
101908
+ /** Smart pet feeder — cloud-connected food dispenser with a bowl food
101909
+ * level, battery, desiccant life, feeding state and manual-feed /
101910
+ * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
101911
+ * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
101912
+ * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
101913
+ * integrations sharing the same food/desiccant/hopper surface. */
101914
+ DeviceType["PetFeeder"] = "pet-feeder";
101915
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
101916
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
101917
+ * of a container and is BUILT as a composed device (D967). */
101918
+ DeviceType["PetFountain"] = "pet-fountain";
101919
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
101920
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
101921
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
101922
+ * A plug-built dishwasher carries running-state and power instead. */
101923
+ DeviceType["Dishwasher"] = "dishwasher";
101924
+ /** Washing machine. A plug-built one is running while its power stays
101925
+ * above a threshold. No program cap — that surface is the dishwasher's. */
101926
+ DeviceType["Washer"] = "washer";
101927
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
101928
+ DeviceType["Dryer"] = "dryer";
101929
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
101930
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
101931
+ * address, connection state) and the actions the box really supports
101932
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
101933
+ * adopted beside it — and its connection settings stay on the integration. */
101934
+ DeviceType["Gateway"] = "gateway";
101935
+ return DeviceType;
101936
+ }({});
101937
+ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
101938
+ DeviceFeature["BatteryOperated"] = "battery-operated";
101939
+ DeviceFeature["Rebootable"] = "rebootable";
101940
+ /**
101941
+ * Device supports an on-demand re-sync of its derived spec with its
101942
+ * upstream source — drives the generic Re-sync button. The owning
101943
+ * provider implements the action via the `device-adoption.resync` cap.
101944
+ */
101945
+ DeviceFeature["Resyncable"] = "resyncable";
101946
+ DeviceFeature["NativeSnapshot"] = "native-snapshot";
101947
+ DeviceFeature["DoorbellButton"] = "doorbell-button";
101948
+ DeviceFeature["TwoWayAudio"] = "two-way-audio";
101949
+ DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
101950
+ /**
101951
+ * Camera supports the on-firmware autotrack subsystem (subject-
101952
+ * following). Distinct from `PanTiltZoom` because not every PTZ
101953
+ * camera ships autotrack — the admin UI uses this flag to gate
101954
+ * the autotrack toggle / settings card without re-deriving from
101955
+ * the cap registry. Mirrors `ptz-autotrack` cap registration:
101956
+ * driver sets this feature when probe confirms the firmware
101957
+ * surface, and registers the cap in the same code path.
101958
+ */
101959
+ DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
101960
+ /**
101961
+ * Accessory exposes a "trigger on motion" toggle — the parent camera's
101962
+ * motion detection automatically activates this device. Mirrors
101963
+ * `motion-trigger` cap registration: drivers set this feature in the
101964
+ * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
101965
+ *
101966
+ * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
101967
+ * fast scalar without binding fetch), notifier rules, and `listAll`
101968
+ * filters that want "all devices with on-motion behaviour".
101969
+ */
101970
+ DeviceFeature["MotionTrigger"] = "motion-trigger";
101971
+ /** Light supports rgb-triplet color via `color` cap. */
101972
+ DeviceFeature["LightColorRgb"] = "light-color-rgb";
101973
+ /** Light supports HSV color via `color` cap. */
101974
+ DeviceFeature["LightColorHsv"] = "light-color-hsv";
101975
+ /** Light supports color-temperature (mired) via `color` cap. */
101976
+ DeviceFeature["LightColorMired"] = "light-color-mired";
101977
+ /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
101978
+ * targetHigh). Gates the range slider UI. */
101979
+ DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
101980
+ /** Thermostat exposes target humidity and/or current humidity
101981
+ * readings. Gates the humidity controls. */
101982
+ DeviceFeature["ClimateHumidity"] = "climate-humidity";
101983
+ /** Thermostat exposes a fan-mode selector. */
101984
+ DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
101985
+ /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
101986
+ DeviceFeature["ClimatePreset"] = "climate-preset";
101987
+ /** Thermostat exposes a vertical louver swing toggle. Gates the
101988
+ * vertical-swing switch in the climate UI. Independent of horizontal. */
101989
+ DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
101990
+ /** Thermostat exposes a horizontal louver swing toggle. Gates the
101991
+ * horizontal-swing switch in the climate UI. Independent of vertical. */
101992
+ DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
101993
+ /** Cover exposes intermediate position control (0..100). Gates the
101994
+ * position slider UI. */
101995
+ DeviceFeature["CoverPositionable"] = "cover-positionable";
101996
+ /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
101997
+ DeviceFeature["CoverTilt"] = "cover-tilt";
101998
+ /** Valve exposes intermediate position control (0..100). Gates the
101999
+ * position slider / drag surface UI. */
102000
+ DeviceFeature["ValvePositionable"] = "valve-positionable";
102001
+ /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
102002
+ DeviceFeature["FanSpeed"] = "fan-speed";
102003
+ /** Fan exposes a preset mode selector. */
102004
+ DeviceFeature["FanPreset"] = "fan-preset";
102005
+ /** Fan exposes blade direction (forward/reverse) — typical of
102006
+ * ceiling fans. */
102007
+ DeviceFeature["FanDirection"] = "fan-direction";
102008
+ /** Fan exposes an oscillation toggle. */
102009
+ DeviceFeature["FanOscillating"] = "fan-oscillating";
102010
+ /** Lock requires a PIN code on lock/unlock. Gates the code-entry
102011
+ * field on the UI lock-controls panel. */
102012
+ DeviceFeature["LockPinRequired"] = "lock-pin-required";
102013
+ /** Lock supports a latch-release ("open door") action distinct from
102014
+ * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
102015
+ * `supported_features`. Gates the Open Door button in the UI. */
102016
+ DeviceFeature["LockOpen"] = "lock-open";
102017
+ /** Media player exposes a seek-to-position surface. */
102018
+ DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
102019
+ /** Media player exposes a volume-level setter. */
102020
+ DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
102021
+ /** Media player exposes a mute toggle distinct from volume=0. */
102022
+ DeviceFeature["MediaPlayerMute"] = "media-player-mute";
102023
+ /** Media player exposes a shuffle toggle. */
102024
+ DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
102025
+ /** Media player exposes a repeat mode (off / all / one). */
102026
+ DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
102027
+ /** Media player exposes a source / input selector. */
102028
+ DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
102029
+ /** Media player exposes a play-arbitrary-media surface (URL / id). */
102030
+ DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
102031
+ /** Media player exposes next-track. */
102032
+ DeviceFeature["MediaPlayerNext"] = "media-player-next";
102033
+ /** Media player exposes previous-track. */
102034
+ DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
102035
+ /** Media player exposes stop distinct from pause. */
102036
+ DeviceFeature["MediaPlayerStop"] = "media-player-stop";
102037
+ /** Alarm panel requires a PIN code on arm/disarm. */
102038
+ DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
102039
+ /** Presence device carries GPS coordinates (lat/lng/accuracy) in
102040
+ * addition to a textual location. */
102041
+ DeviceFeature["PresenceGps"] = "presence-gps";
102042
+ /** Notifier accepts an inline / URL image attachment. */
102043
+ DeviceFeature["NotifierImage"] = "notifier-image";
102044
+ /** Notifier accepts a priority hint (high/normal/low). */
102045
+ DeviceFeature["NotifierPriority"] = "notifier-priority";
102046
+ /** Notifier accepts a free-form `data` payload for platform-specific
102047
+ * fields. */
102048
+ DeviceFeature["NotifierData"] = "notifier-data";
102049
+ /** Notifier supports interactive action buttons / callbacks. */
102050
+ DeviceFeature["NotifierActions"] = "notifier-actions";
102051
+ /** Notifier supports per-call recipient targeting (multi-user). */
102052
+ DeviceFeature["NotifierRecipients"] = "notifier-recipients";
102053
+ /** Script runner accepts a variables map on each run invocation. */
102054
+ DeviceFeature["ScriptVariables"] = "script-variables";
102055
+ /** Automation `trigger` accepts a skipCondition flag — fires the
102056
+ * automation's actions while bypassing its condition block. */
102057
+ DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
102058
+ /** Robot vacuum exposes a live cleaning map (image child). Gates the
102059
+ * map tile in the vacuum UI. */
102060
+ DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
102061
+ /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
102062
+ * switches group. */
102063
+ DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
102064
+ /** Robot mower exposes a live mowing map (SVG image child). Gates the
102065
+ * mower map tile. */
102066
+ DeviceFeature["MowerHasMap"] = "mower-has-map";
102067
+ /** Robot mower exposes targeted mowing (all-area / zones / edges /
102068
+ * spots) — gates the Mow action + map selectors. */
102069
+ DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
102070
+ return DeviceFeature;
102071
+ }({});
102072
+ /**
102073
+ * Semantic role a device plays within its parent. Populated by driver
102074
+ * addons when creating accessory devices (Reolink siren/floodlight/
102075
+ * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
102076
+ * admin UI to pick icons, labels, and widgets — a `Switch` with
102077
+ * `role: Floodlight` renders as a bulb with a brightness slider,
102078
+ * whereas a `Switch` with `role: Siren` renders as a klaxon.
102079
+ *
102080
+ * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
102081
+ * sqlite as a nullable TEXT column — old rows keep working unchanged.
102082
+ */
102083
+ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
102084
+ DeviceRole["Siren"] = "siren";
102085
+ DeviceRole["Floodlight"] = "floodlight";
102086
+ DeviceRole["Spotlight"] = "spotlight";
102087
+ DeviceRole["PirSensor"] = "pir-sensor";
102088
+ DeviceRole["Chime"] = "chime";
102089
+ DeviceRole["Autotrack"] = "autotrack";
102090
+ DeviceRole["Nightvision"] = "nightvision";
102091
+ DeviceRole["PrivacyMask"] = "privacy-mask";
102092
+ DeviceRole["Doorbell"] = "doorbell";
102093
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
102094
+ DeviceRole["CoverBlind"] = "cover-blind";
102095
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
102096
+ DeviceRole["CoverShutter"] = "cover-shutter";
102097
+ /** Exterior awning. HA `device_class: awning`. */
102098
+ DeviceRole["CoverAwning"] = "cover-awning";
102099
+ /** Curtain / drape. HA `device_class: curtain`. */
102100
+ DeviceRole["CoverCurtain"] = "cover-curtain";
102101
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
102102
+ DeviceRole["CoverGarage"] = "cover-garage";
102103
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
102104
+ DeviceRole["CoverGate"] = "cover-gate";
102105
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
102106
+ DeviceRole["CoverDoor"] = "cover-door";
102107
+ /** Window. HA `device_class: window`. */
102108
+ DeviceRole["CoverWindow"] = "cover-window";
102109
+ /** Virtual HA toggle (input_boolean.*) — distinguishable from a
102110
+ * real Switch device for UI rendering / export adapters. */
102111
+ DeviceRole["BinaryHelper"] = "binary-helper";
102112
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
102113
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
102114
+ DeviceRole["Outlet"] = "outlet";
102115
+ /** Generic motion / occupancy / moving event source. Distinct from
102116
+ * the camera accessory PirSensor role: that one is a camera child;
102117
+ * this is a standalone HA / 3rd-party motion sensor. */
102118
+ DeviceRole["MotionSensor"] = "motion-sensor";
102119
+ DeviceRole["ContactSensor"] = "contact-sensor";
102120
+ DeviceRole["LeakSensor"] = "leak-sensor";
102121
+ DeviceRole["SmokeSensor"] = "smoke-sensor";
102122
+ DeviceRole["COSensor"] = "co-sensor";
102123
+ DeviceRole["GasSensor"] = "gas-sensor";
102124
+ DeviceRole["TamperSensor"] = "tamper-sensor";
102125
+ DeviceRole["VibrationSensor"] = "vibration-sensor";
102126
+ DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
102127
+ DeviceRole["SoundSensor"] = "sound-sensor";
102128
+ /** Fallback for `binary_sensor` without a known `device_class`. */
102129
+ DeviceRole["BinarySensor"] = "binary-sensor";
102130
+ DeviceRole["TemperatureSensor"] = "temperature-sensor";
102131
+ DeviceRole["HumiditySensor"] = "humidity-sensor";
102132
+ DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
102133
+ DeviceRole["PressureSensor"] = "pressure-sensor";
102134
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
102135
+ DeviceRole["WindSensor"] = "wind-sensor";
102136
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
102137
+ DeviceRole["RainSensor"] = "rain-sensor";
102138
+ /** UV index (weather-station `uv-sensor` cap). */
102139
+ DeviceRole["UvSensor"] = "uv-sensor";
102140
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
102141
+ * Distinct from AmbientLightSensor (lux). */
102142
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
102143
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
102144
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
102145
+ DeviceRole["PowerSensor"] = "power-sensor";
102146
+ DeviceRole["EnergySensor"] = "energy-sensor";
102147
+ DeviceRole["VoltageSensor"] = "voltage-sensor";
102148
+ DeviceRole["CurrentSensor"] = "current-sensor";
102149
+ DeviceRole["AirQualitySensor"] = "air-quality-sensor";
102150
+ /** Battery level (numeric % via `sensor` OR low-bool via
102151
+ * `binary_sensor` — the cap distinguishes via the value type). */
102152
+ DeviceRole["BatterySensor"] = "battery-sensor";
102153
+ /** Fallback for `sensor` numeric without a known `device_class`. */
102154
+ DeviceRole["NumericSensor"] = "numeric-sensor";
102155
+ /** String / enum state (HA `sensor` with `state_class: enum` or
102156
+ * `attributes.options`). */
102157
+ DeviceRole["EnumSensor"] = "enum-sensor";
102158
+ /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
102159
+ * or `date`). The slice carries the raw ISO string verbatim (hosted on
102160
+ * the `enum-sensor` cap); the UI renders it locale-formatted. */
102161
+ DeviceRole["DateTimeSensor"] = "datetime-sensor";
102162
+ /** Last-resort fallback when nothing else matches. */
102163
+ DeviceRole["GenericSensor"] = "generic-sensor";
102164
+ DeviceRole["NumericControl"] = "numeric-control";
102165
+ DeviceRole["SelectControl"] = "select-control";
102166
+ DeviceRole["TextControl"] = "text-control";
102167
+ DeviceRole["DateTimeControl"] = "datetime-control";
102168
+ /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
102169
+ * rich features (image, priority, channel routing). */
102170
+ DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
102171
+ /** Chat / messaging service (HA `notify.telegram_*`,
102172
+ * `notify.discord_*`, etc.). */
102173
+ DeviceRole["MessagingNotifier"] = "messaging-notifier";
102174
+ /** Email-based delivery (HA `notify.smtp`, etc.). */
102175
+ DeviceRole["EmailNotifier"] = "email-notifier";
102176
+ /** Fallback when the notifier service name doesn't match a known
102177
+ * pattern. */
102178
+ DeviceRole["GenericNotifier"] = "generic-notifier";
102179
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
102180
+ * cap. A `DeviceType.Generic` device. */
102181
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
102182
+ return DeviceRole;
102183
+ }({});
102184
+ //#endregion
101809
102185
  //#region ../../node_modules/zod/v4/core/core.js
101810
102186
  var _a$1;
101811
102187
  function $constructor(name, initializer, params) {
@@ -106484,7 +106860,7 @@ var ZodIssueCode = {
106484
106860
  var ZodFirstPartyTypeKind;
106485
106861
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
106486
106862
  //#endregion
106487
- //#region ../types/dist/sleep-DSVWZfJC.mjs
106863
+ //#region ../types/dist/sleep-CSYuxssh.mjs
106488
106864
  /**
106489
106865
  * The audio chunk plane's byte format, and the ONE expansion from a coded
106490
106866
  * window to float samples (D455).
@@ -107004,6 +107380,102 @@ function emitReadiness(bus, params) {
107004
107380
  ts
107005
107381
  }));
107006
107382
  }
107383
+ var NOT_RUN = {
107384
+ declared: false,
107385
+ snapshot: null,
107386
+ applied: [],
107387
+ failed: [],
107388
+ blocked: [],
107389
+ skipped: [],
107390
+ blockOwner: []
107391
+ };
107392
+ /**
107393
+ * Offer `migrations` to the engine and run what it says to run, in its order. Never
107394
+ * throws: an unreachable or refusing engine is logged and reported `declared: false`,
107395
+ * because the hub (and this addon's boot) must not hang on a migration.
107396
+ */
107397
+ async function runAddonMigrations(host, migrations, logger) {
107398
+ const first = migrations[0];
107399
+ if (first === void 0) return NOT_RUN;
107400
+ const owner = first.meta.owner;
107401
+ const log = logger.withTags({ migrationOwner: owner });
107402
+ const byId = new Map(migrations.map((m) => [m.meta.id, m]));
107403
+ let declaration;
107404
+ try {
107405
+ declaration = await host.declareOwner({
107406
+ owner,
107407
+ metas: migrations.map((m) => m.meta)
107408
+ });
107409
+ } catch (err) {
107410
+ log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg$2(err) } });
107411
+ return {
107412
+ ...NOT_RUN,
107413
+ blockOwner: migrations.filter((m) => m.meta.onFailure === "block-owner").map((m) => m.meta.id)
107414
+ };
107415
+ }
107416
+ const applied = [];
107417
+ const failed = [];
107418
+ const blocked = [];
107419
+ const skipped = [];
107420
+ const settle = (id, status) => {
107421
+ if (status === "applied") applied.push(id);
107422
+ else if (status === "failed") failed.push(id);
107423
+ else if (status === "blocked") blocked.push(id);
107424
+ else skipped.push(id);
107425
+ };
107426
+ for (const step of declaration.steps) {
107427
+ const bound = byId.get(step.id);
107428
+ if (bound === void 0 || step.action === "skip") {
107429
+ skipped.push(step.id);
107430
+ continue;
107431
+ }
107432
+ const tags = { migrationId: step.id };
107433
+ try {
107434
+ const gate = await host.startMigration({ id: step.id });
107435
+ if (!gate.proceed) {
107436
+ log.warn("migration not run", {
107437
+ tags,
107438
+ meta: {
107439
+ state: gate.state,
107440
+ reason: gate.reason
107441
+ }
107442
+ });
107443
+ settle(step.id, gate.state === "pending" ? "blocked" : gate.state);
107444
+ continue;
107445
+ }
107446
+ const result = await bound.execute();
107447
+ const outcome = await host.finishMigration({
107448
+ id: step.id,
107449
+ result
107450
+ });
107451
+ if (outcome.status === "applied") log.info("migration applied", {
107452
+ tags,
107453
+ meta: { rowsChanged: outcome.rowsChanged }
107454
+ });
107455
+ else log.error("migration failed", {
107456
+ tags,
107457
+ meta: { error: outcome.error }
107458
+ });
107459
+ settle(step.id, outcome.status);
107460
+ } catch (err) {
107461
+ log.error("migration could not be completed with the engine", {
107462
+ tags,
107463
+ meta: { error: errMsg$2(err) }
107464
+ });
107465
+ failed.push(step.id);
107466
+ }
107467
+ }
107468
+ const blockOwner = [...failed, ...blocked].filter((id) => byId.get(id)?.meta.onFailure === "block-owner");
107469
+ return {
107470
+ declared: true,
107471
+ snapshot: declaration.snapshot,
107472
+ applied,
107473
+ failed,
107474
+ blocked,
107475
+ skipped,
107476
+ blockOwner
107477
+ };
107478
+ }
107007
107479
  /**
107008
107480
  * Build a {@link DurableState} over a single store key. Transport-agnostic:
107009
107481
  * `read`/`write` are the addon-store or device-store accessors. The whole
@@ -108059,6 +108531,28 @@ var BaseAddon = class {
108059
108531
  return normalized;
108060
108532
  }
108061
108533
  /**
108534
+ * Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
108535
+ * engine. Call it at the point of the boot where the data must already have the new
108536
+ * shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
108537
+ * restored devices — with the catalogue bound to the addon's ports
108538
+ * (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
108539
+ *
108540
+ * An engine that cannot be reached or refuses the catalogue is logged and runs
108541
+ * nothing; the boot continues. A migration that FAILED (or was blocked by one)
108542
+ * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
108543
+ * serve data whose shape it cannot trust; `tolerate` migrations never do.
108544
+ */
108545
+ async runMigrations(migrations) {
108546
+ const api = this.ctx.api.migrations;
108547
+ const report = await runAddonMigrations({
108548
+ declareOwner: (input) => api.declareOwner.mutate(input),
108549
+ startMigration: (input) => api.startMigration.mutate(input),
108550
+ finishMigration: (input) => api.finishMigration.mutate(input)
108551
+ }, migrations, this.ctx.logger);
108552
+ if (report.blockOwner.length > 0) throw new Error(`migration(s) ${report.blockOwner.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
108553
+ return report;
108554
+ }
108555
+ /**
108062
108556
  * Called by the isolated-process runner AFTER `broker.start()`.
108063
108557
  * In-process addons never need this because the hub broker is already
108064
108558
  * running when `initialize()` fires. For forked children the broker
@@ -109168,407 +109662,6 @@ var RecordKindDeclarationSchema = object({
109168
109662
  }).strict();
109169
109663
  /** The kind names: lower-case, dash separated, short enough for an index name. */
109170
109664
  var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
109171
- var READING_QUANTITIES = [
109172
- "temperature",
109173
- "pressure",
109174
- "speed",
109175
- "precipitation",
109176
- "precipitation-rate",
109177
- "length",
109178
- "area",
109179
- "illuminance",
109180
- "irradiance",
109181
- "power",
109182
- "energy",
109183
- "voltage",
109184
- "current",
109185
- "apparent-power",
109186
- "humidity",
109187
- "percentage",
109188
- "concentration-volume",
109189
- "concentration-mass",
109190
- "concentration-ppb",
109191
- "volume",
109192
- "duration",
109193
- "duration-ms",
109194
- "information",
109195
- "sound-level",
109196
- "signal-level",
109197
- "colour-temperature",
109198
- "angle",
109199
- "bitrate",
109200
- "rate",
109201
- "mass",
109202
- "index",
109203
- "ratio",
109204
- "count",
109205
- "none",
109206
- "device"
109207
- ];
109208
- var ReadingQuantitySchema = _enum(READING_QUANTITIES);
109209
- new Set(READING_QUANTITIES);
109210
- /**
109211
- * Stored unit symbol for a quantity. `none` means the number is dimensionless
109212
- * (or an index / ratio / count). `device` means the cap has no single unit.
109213
- */
109214
- var QUANTITY_BASE = {
109215
- temperature: "°C",
109216
- pressure: "hPa",
109217
- speed: "m/s",
109218
- precipitation: "mm",
109219
- "precipitation-rate": "mm/h",
109220
- length: "m",
109221
- area: "m²",
109222
- illuminance: "lx",
109223
- irradiance: "W/m²",
109224
- power: "W",
109225
- energy: "kWh",
109226
- voltage: "V",
109227
- current: "A",
109228
- "apparent-power": "VA",
109229
- humidity: "%",
109230
- percentage: "%",
109231
- "concentration-volume": "ppm",
109232
- "concentration-mass": "µg/m³",
109233
- "concentration-ppb": "ppb",
109234
- volume: "L",
109235
- duration: "s",
109236
- "duration-ms": "ms",
109237
- information: "B",
109238
- "sound-level": "dBFS",
109239
- "signal-level": "dBm",
109240
- "colour-temperature": "mired",
109241
- angle: "°",
109242
- bitrate: "kbps",
109243
- rate: "fps",
109244
- mass: "kg",
109245
- index: "none",
109246
- ratio: "none",
109247
- count: "none",
109248
- none: "none",
109249
- device: "device"
109250
- };
109251
- /** The stored unit, or `undefined` when the quantity has no unit symbol. */
109252
- function baseUnitOf(quantity) {
109253
- const symbol = QUANTITY_BASE[quantity];
109254
- if (symbol === "none" || symbol === "device") return void 0;
109255
- return symbol;
109256
- }
109257
- function readingMeta(quantity, extras) {
109258
- return {
109259
- quantity,
109260
- ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
109261
- ...extras?.label !== void 0 ? { label: extras.label } : {}
109262
- };
109263
- }
109264
- /** Shared Zod schemas used across streaming capabilities. */
109265
- var CamProfileSchema = _enum([
109266
- "high",
109267
- "mid",
109268
- "low"
109269
- ]);
109270
- /**
109271
- * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
109272
- *
109273
- * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
109274
- * that consumer owns". It lives beside `CamProfile` because a rung is the only
109275
- * operator-facing way to name a stream: a cam-stream id is broker-internal and
109276
- * moves when a slot is reassigned, so a stored id silently stops meaning what
109277
- * the operator picked. Today's one user is the audio analyser, whose rule is
109278
- * "the lowest AVAILABLE native rung" (D537).
109279
- */
109280
- var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
109281
- var CamStreamKindSchema = _enum([
109282
- "pull-rtsp",
109283
- "pull-rtmp",
109284
- "pull-http",
109285
- "pull-flv",
109286
- "pull-rfc4571",
109287
- "push-annexb",
109288
- "derived"
109289
- ]);
109290
- var CamStreamResolutionSchema = object({
109291
- width: number().int().positive().meta(readingMeta("none")),
109292
- height: number().int().positive().meta(readingMeta("none"))
109293
- });
109294
- var CameraStreamSchema = object({
109295
- /** Stable, provider-assigned id unique within the (deviceId) scope. */
109296
- camStreamId: string().min(1),
109297
- deviceId: number().int().nonnegative(),
109298
- kind: CamStreamKindSchema,
109299
- /** Required for pull-* kinds. Ignored for push-annexb. */
109300
- url: string().optional(),
109301
- codec: string().optional(),
109302
- resolution: CamStreamResolutionSchema.optional(),
109303
- fps: number().positive().optional(),
109304
- /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
109305
- label: string().optional(),
109306
- /**
109307
- * Device-level features the publisher advertised (e.g. `battery-operated`).
109308
- * The broker, snapshot orchestrator, and prebuffer manager all consult
109309
- * this list to derive policy — relaxed stall watchdog for battery
109310
- * cams, prebuffer off by default, longer snapshot rate-limit, etc.
109311
- *
109312
- * Single source of truth replacing per-stream flags like the
109313
- * historical `allowStall`: if the publisher knows the camera is
109314
- * battery-powered, every downstream service derives the right policy
109315
- * from this list.
109316
- */
109317
- deviceFeatures: array(string()).optional(),
109318
- /**
109319
- * Whether this stream participates in the broker's automatic profile
109320
- * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
109321
- * use `false` when they want a stream to be SELECTABLE in the UI but not
109322
- * picked by default — e.g. Reolink publishes its native Baichuan streams
109323
- * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
109324
- * mirrors as `autoEligible: false` (still pickable per slot, just not
109325
- * the auto choice). Manual `assignProfile` calls remain valid for
109326
- * non-eligible streams.
109327
- */
109328
- autoEligible: boolean().optional(),
109329
- /**
109330
- * The profile slot the PUBLISHER says this stream is: `low` for a stream
109331
- * whose only sensible role is the low-bandwidth one, `high` for a main
109332
- * stream. Consulted by the broker's automatic ranking
109333
- * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
109334
- * an operator's manual assignment still wins, and an absent hint is the
109335
- * pixel-ranked default every camera had before this field existed.
109336
- *
109337
- * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
109338
- * storage class is written for on its own (`recordings` is high|mid,
109339
- * `recordingsLow` is low) and the one profile scrub does not read. The robot
109340
- * camera's single 720p relay is a `low` in every respect except the slot the
109341
- * heuristic put it in.
109342
- */
109343
- profileHint: CamProfileSchema.optional(),
109344
- /**
109345
- * Transport-specific opaque metadata. The broker passes it through to
109346
- * the source reader without inspecting it. Currently used by
109347
- * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
109348
- * route RTP packets to the right depacketizer without an in-band
109349
- * DESCRIBE phase). Other kinds typically leave it undefined.
109350
- */
109351
- metadata: record(string(), unknown()).optional()
109352
- });
109353
- var ProfileSlotStatusSchema = _enum([
109354
- "unassigned",
109355
- "idle",
109356
- "connecting",
109357
- "streaming",
109358
- "error"
109359
- ]);
109360
- var ProfileSlotSchema = object({
109361
- deviceId: number().int().nonnegative(),
109362
- profile: CamProfileSchema,
109363
- /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
109364
- brokerId: string(),
109365
- /** `null` when the profile is unassigned. */
109366
- sourceCamStreamId: string().nullable(),
109367
- status: ProfileSlotStatusSchema,
109368
- resolution: CamStreamResolutionSchema.optional(),
109369
- codec: string().optional(),
109370
- preBufferSec: number().nonnegative().optional(),
109371
- errorMessage: string().optional()
109372
- });
109373
- /**
109374
- * Zod schema for StreamSourceEntry — the canonical stream descriptor
109375
- * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
109376
- */
109377
- var StreamSourceEntrySchema$1 = object({
109378
- id: string(),
109379
- label: string(),
109380
- protocol: _enum([
109381
- "rtsp",
109382
- "rtmp",
109383
- "annexb",
109384
- "http-mjpeg",
109385
- "webrtc",
109386
- "custom"
109387
- ]),
109388
- url: string().optional(),
109389
- resolution: object({
109390
- width: number(),
109391
- height: number()
109392
- }).readonly().optional(),
109393
- fps: number().optional(),
109394
- bitrate: number().optional(),
109395
- codec: string().optional(),
109396
- profileHint: CamProfileSchema.optional()
109397
- });
109398
- object({
109399
- type: string(),
109400
- url: string(),
109401
- videoCodec: string().optional(),
109402
- audioCodec: string().optional(),
109403
- metadata: record(string(), unknown()).readonly().optional()
109404
- });
109405
- /**
109406
- * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
109407
- * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
109408
- * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
109409
- * (the runner's native-crop and pipeline-executor inputs) can carry handles
109410
- * over tRPC / Moleculer.
109411
- *
109412
- * The `satisfies` assertion below pins this schema to the interface: if a
109413
- * field is added to / removed from `FrameHandle` without a matching schema
109414
- * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
109415
- * `FrameHandle` and the build fails — no silent drift.
109416
- */
109417
- var FrameHandleSchema = object({
109418
- shmId: string(),
109419
- slot: number().int().nonnegative(),
109420
- seq: number().int().nonnegative(),
109421
- width: number().int().positive(),
109422
- height: number().int().positive(),
109423
- format: _enum([
109424
- "jpeg",
109425
- "rgb",
109426
- "bgr",
109427
- "yuv420",
109428
- "gray"
109429
- ]),
109430
- pts: number(),
109431
- byteLength: number().int().nonnegative(),
109432
- nodeId: string(),
109433
- slotCount: number().int().positive()
109434
- });
109435
- /**
109436
- * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
109437
- * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
109438
- * sample bytes plus the track parameters a consumer needs to interpret them.
109439
- *
109440
- * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
109441
- * Float32 PCM), so unlike video frames they ship their bytes INLINE over
109442
- * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
109443
- * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
109444
- * it.
109445
- */
109446
- var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
109447
- var DecodedAudioChunkSchema = object({
109448
- data: _instanceof(Uint8Array),
109449
- sampleRate: number().int().positive(),
109450
- channels: number().int().positive(),
109451
- timestamp: number(),
109452
- /**
109453
- * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
109454
- * byte, for any peer that never heard of this field. A coded window
109455
- * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
109456
- * subscription that DECLARED it accepts one, so absence can never mean
109457
- * "coded bytes a consumer will read as floats" (D455).
109458
- */
109459
- format: AudioChunkFormatSchema.optional()
109460
- });
109461
- /**
109462
- * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
109463
- * its bytes inline: a consumer asks the broker for the decoded audio-chunk
109464
- * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
109465
- * the consumer drains via `pullAudioChunks`.
109466
- */
109467
- var SubscribeAudioChunksInputSchema = object({
109468
- brokerId: string(),
109469
- /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
109470
- tag: string().optional(),
109471
- /**
109472
- * Byte formats this subscriber can READ, best first. The broker serves the
109473
- * chunk's own format when it is in this list and expands to `f32le`
109474
- * otherwise, so a subscriber is never handed bytes it cannot interpret.
109475
- *
109476
- * Absent (or without the source format) means `f32le` — the behaviour every
109477
- * subscriber had before D455, unchanged. This is the negotiation half of
109478
- * the source-bytes lever: it is what lets the broker and its consumers
109479
- * deploy one at a time across three nodes.
109480
- */
109481
- accept: array(AudioChunkFormatSchema).readonly().optional()
109482
- });
109483
- /** Result of `stream-broker.subscribeAudioChunks`. */
109484
- var SubscribeAudioChunksResultSchema = object({
109485
- /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
109486
- subscriptionId: string() });
109487
- var BrokerStatsSchema = object({
109488
- status: _enum([
109489
- "idle",
109490
- "connecting",
109491
- "streaming",
109492
- "error",
109493
- "stopped"
109494
- ]),
109495
- inputFps: number(),
109496
- encodedSubscribers: number(),
109497
- uptimeMs: number(),
109498
- bitrateKbps: number(),
109499
- idrIntervalMs: number(),
109500
- codec: string().optional(),
109501
- totalBytes: number(),
109502
- packetCount: number(),
109503
- rtspClients: number(),
109504
- pipeClients: number(),
109505
- preBufferSec: number(),
109506
- preBufferMs: number(),
109507
- preBufferPackets: number(),
109508
- /**
109509
- * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
109510
- * when the stream has no audio track or the broker is in cold start.
109511
- * `supported = false` means the codec was detected but the local
109512
- * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
109513
- * AAC pipeline wired). Surfaced in the UI device-overview so operators
109514
- * can pre-pick the audio-analysis model that matches the codec.
109515
- */
109516
- audio: object({
109517
- codec: string(),
109518
- sampleRate: number(),
109519
- channels: number(),
109520
- supported: boolean()
109521
- }).nullable().optional(),
109522
- /**
109523
- * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
109524
- * distributing none of the device's audio, on live or recording.
109525
- *
109526
- * Present so a silent camera can be told apart from a broken one on the
109527
- * stream panel itself, without cross-referencing the switch group: a
109528
- * broker holding an `audio` track descriptor while `audioMuted` is true is
109529
- * working exactly as asked. `audioMutedDropped` counts the audio units
109530
- * thrown away since the current dial — it is how you confirm from stats
109531
- * alone that the mute is on the packet path and not merely persisted.
109532
- */
109533
- audioMuted: boolean().optional(),
109534
- audioMutedDropped: number().optional()
109535
- });
109536
- /**
109537
- * Exporter-facing "profile restream" entry. Returned by
109538
- * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
109539
- * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
109540
- * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
109541
- * RTSP restream that aliases the profile's assigned source broker, so
109542
- * every consumer dialling a profile converges on the same single pull.
109543
- * `codec`/`resolution` describe the assigned SOURCE camStream (for the
109544
- * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
109545
- * (raw per-camStream, live-view only).
109546
- */
109547
- var ProfileRtspEntrySchema = object({
109548
- profile: CamProfileSchema,
109549
- /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
109550
- brokerId: string(),
109551
- url: string(),
109552
- mutedUrl: string(),
109553
- enabled: boolean(),
109554
- codec: string().optional(),
109555
- resolution: CamStreamResolutionSchema.optional()
109556
- });
109557
- /**
109558
- * The model weight formats a catalog entry can ship. Schema-free on purpose:
109559
- * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
109560
- * (the model downloader in `@camstack/system`) needs only the list and must
109561
- * reach it through `@camstack/types/addon` without building the cap schema
109562
- * graph (D28).
109563
- */
109564
- var MODEL_FORMATS = [
109565
- "onnx",
109566
- "coreml",
109567
- "openvino",
109568
- "tflite",
109569
- "pt",
109570
- "gguf"
109571
- ];
109572
109665
  object({
109573
109666
  field: string().min(1),
109574
109667
  on: literal("advance")
@@ -109661,6 +109754,407 @@ function optionalMethod(input, output, options) {
109661
109754
  function event(data) {
109662
109755
  return { data };
109663
109756
  }
109757
+ var READING_QUANTITIES = [
109758
+ "temperature",
109759
+ "pressure",
109760
+ "speed",
109761
+ "precipitation",
109762
+ "precipitation-rate",
109763
+ "length",
109764
+ "area",
109765
+ "illuminance",
109766
+ "irradiance",
109767
+ "power",
109768
+ "energy",
109769
+ "voltage",
109770
+ "current",
109771
+ "apparent-power",
109772
+ "humidity",
109773
+ "percentage",
109774
+ "concentration-volume",
109775
+ "concentration-mass",
109776
+ "concentration-ppb",
109777
+ "volume",
109778
+ "duration",
109779
+ "duration-ms",
109780
+ "information",
109781
+ "sound-level",
109782
+ "signal-level",
109783
+ "colour-temperature",
109784
+ "angle",
109785
+ "bitrate",
109786
+ "rate",
109787
+ "mass",
109788
+ "index",
109789
+ "ratio",
109790
+ "count",
109791
+ "none",
109792
+ "device"
109793
+ ];
109794
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
109795
+ new Set(READING_QUANTITIES);
109796
+ /**
109797
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
109798
+ * (or an index / ratio / count). `device` means the cap has no single unit.
109799
+ */
109800
+ var QUANTITY_BASE = {
109801
+ temperature: "°C",
109802
+ pressure: "hPa",
109803
+ speed: "m/s",
109804
+ precipitation: "mm",
109805
+ "precipitation-rate": "mm/h",
109806
+ length: "m",
109807
+ area: "m²",
109808
+ illuminance: "lx",
109809
+ irradiance: "W/m²",
109810
+ power: "W",
109811
+ energy: "kWh",
109812
+ voltage: "V",
109813
+ current: "A",
109814
+ "apparent-power": "VA",
109815
+ humidity: "%",
109816
+ percentage: "%",
109817
+ "concentration-volume": "ppm",
109818
+ "concentration-mass": "µg/m³",
109819
+ "concentration-ppb": "ppb",
109820
+ volume: "L",
109821
+ duration: "s",
109822
+ "duration-ms": "ms",
109823
+ information: "B",
109824
+ "sound-level": "dBFS",
109825
+ "signal-level": "dBm",
109826
+ "colour-temperature": "mired",
109827
+ angle: "°",
109828
+ bitrate: "kbps",
109829
+ rate: "fps",
109830
+ mass: "kg",
109831
+ index: "none",
109832
+ ratio: "none",
109833
+ count: "none",
109834
+ none: "none",
109835
+ device: "device"
109836
+ };
109837
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
109838
+ function baseUnitOf(quantity) {
109839
+ const symbol = QUANTITY_BASE[quantity];
109840
+ if (symbol === "none" || symbol === "device") return void 0;
109841
+ return symbol;
109842
+ }
109843
+ function readingMeta(quantity, extras) {
109844
+ return {
109845
+ quantity,
109846
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
109847
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
109848
+ };
109849
+ }
109850
+ /** Shared Zod schemas used across streaming capabilities. */
109851
+ var CamProfileSchema = _enum([
109852
+ "high",
109853
+ "mid",
109854
+ "low"
109855
+ ]);
109856
+ /**
109857
+ * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
109858
+ *
109859
+ * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
109860
+ * that consumer owns". It lives beside `CamProfile` because a rung is the only
109861
+ * operator-facing way to name a stream: a cam-stream id is broker-internal and
109862
+ * moves when a slot is reassigned, so a stored id silently stops meaning what
109863
+ * the operator picked. Today's one user is the audio analyser, whose rule is
109864
+ * "the lowest AVAILABLE native rung" (D537).
109865
+ */
109866
+ var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
109867
+ var CamStreamKindSchema = _enum([
109868
+ "pull-rtsp",
109869
+ "pull-rtmp",
109870
+ "pull-http",
109871
+ "pull-flv",
109872
+ "pull-rfc4571",
109873
+ "push-annexb",
109874
+ "derived"
109875
+ ]);
109876
+ var CamStreamResolutionSchema = object({
109877
+ width: number().int().positive().meta(readingMeta("none")),
109878
+ height: number().int().positive().meta(readingMeta("none"))
109879
+ });
109880
+ var CameraStreamSchema = object({
109881
+ /** Stable, provider-assigned id unique within the (deviceId) scope. */
109882
+ camStreamId: string().min(1),
109883
+ deviceId: number().int().nonnegative(),
109884
+ kind: CamStreamKindSchema,
109885
+ /** Required for pull-* kinds. Ignored for push-annexb. */
109886
+ url: string().optional(),
109887
+ codec: string().optional(),
109888
+ resolution: CamStreamResolutionSchema.optional(),
109889
+ fps: number().positive().optional(),
109890
+ /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
109891
+ label: string().optional(),
109892
+ /**
109893
+ * Device-level features the publisher advertised (e.g. `battery-operated`).
109894
+ * The broker, snapshot orchestrator, and prebuffer manager all consult
109895
+ * this list to derive policy — relaxed stall watchdog for battery
109896
+ * cams, prebuffer off by default, longer snapshot rate-limit, etc.
109897
+ *
109898
+ * Single source of truth replacing per-stream flags like the
109899
+ * historical `allowStall`: if the publisher knows the camera is
109900
+ * battery-powered, every downstream service derives the right policy
109901
+ * from this list.
109902
+ */
109903
+ deviceFeatures: array(string()).optional(),
109904
+ /**
109905
+ * Whether this stream participates in the broker's automatic profile
109906
+ * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
109907
+ * use `false` when they want a stream to be SELECTABLE in the UI but not
109908
+ * picked by default — e.g. Reolink publishes its native Baichuan streams
109909
+ * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
109910
+ * mirrors as `autoEligible: false` (still pickable per slot, just not
109911
+ * the auto choice). Manual `assignProfile` calls remain valid for
109912
+ * non-eligible streams.
109913
+ */
109914
+ autoEligible: boolean().optional(),
109915
+ /**
109916
+ * The profile slot the PUBLISHER says this stream is: `low` for a stream
109917
+ * whose only sensible role is the low-bandwidth one, `high` for a main
109918
+ * stream. Consulted by the broker's automatic ranking
109919
+ * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
109920
+ * an operator's manual assignment still wins, and an absent hint is the
109921
+ * pixel-ranked default every camera had before this field existed.
109922
+ *
109923
+ * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
109924
+ * storage class is written for on its own (`recordings` is high|mid,
109925
+ * `recordingsLow` is low) and the one profile scrub does not read. The robot
109926
+ * camera's single 720p relay is a `low` in every respect except the slot the
109927
+ * heuristic put it in.
109928
+ */
109929
+ profileHint: CamProfileSchema.optional(),
109930
+ /**
109931
+ * Transport-specific opaque metadata. The broker passes it through to
109932
+ * the source reader without inspecting it. Currently used by
109933
+ * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
109934
+ * route RTP packets to the right depacketizer without an in-band
109935
+ * DESCRIBE phase). Other kinds typically leave it undefined.
109936
+ */
109937
+ metadata: record(string(), unknown()).optional()
109938
+ });
109939
+ var ProfileSlotStatusSchema = _enum([
109940
+ "unassigned",
109941
+ "idle",
109942
+ "connecting",
109943
+ "streaming",
109944
+ "error"
109945
+ ]);
109946
+ var ProfileSlotSchema = object({
109947
+ deviceId: number().int().nonnegative(),
109948
+ profile: CamProfileSchema,
109949
+ /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
109950
+ brokerId: string(),
109951
+ /** `null` when the profile is unassigned. */
109952
+ sourceCamStreamId: string().nullable(),
109953
+ status: ProfileSlotStatusSchema,
109954
+ resolution: CamStreamResolutionSchema.optional(),
109955
+ codec: string().optional(),
109956
+ preBufferSec: number().nonnegative().optional(),
109957
+ errorMessage: string().optional()
109958
+ });
109959
+ /**
109960
+ * Zod schema for StreamSourceEntry — the canonical stream descriptor
109961
+ * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
109962
+ */
109963
+ var StreamSourceEntrySchema$1 = object({
109964
+ id: string(),
109965
+ label: string(),
109966
+ protocol: _enum([
109967
+ "rtsp",
109968
+ "rtmp",
109969
+ "annexb",
109970
+ "http-mjpeg",
109971
+ "webrtc",
109972
+ "custom"
109973
+ ]),
109974
+ url: string().optional(),
109975
+ resolution: object({
109976
+ width: number(),
109977
+ height: number()
109978
+ }).readonly().optional(),
109979
+ fps: number().optional(),
109980
+ bitrate: number().optional(),
109981
+ codec: string().optional(),
109982
+ profileHint: CamProfileSchema.optional()
109983
+ });
109984
+ object({
109985
+ type: string(),
109986
+ url: string(),
109987
+ videoCodec: string().optional(),
109988
+ audioCodec: string().optional(),
109989
+ metadata: record(string(), unknown()).readonly().optional()
109990
+ });
109991
+ /**
109992
+ * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
109993
+ * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
109994
+ * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
109995
+ * (the runner's native-crop and pipeline-executor inputs) can carry handles
109996
+ * over tRPC / Moleculer.
109997
+ *
109998
+ * The `satisfies` assertion below pins this schema to the interface: if a
109999
+ * field is added to / removed from `FrameHandle` without a matching schema
110000
+ * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
110001
+ * `FrameHandle` and the build fails — no silent drift.
110002
+ */
110003
+ var FrameHandleSchema = object({
110004
+ shmId: string(),
110005
+ slot: number().int().nonnegative(),
110006
+ seq: number().int().nonnegative(),
110007
+ width: number().int().positive(),
110008
+ height: number().int().positive(),
110009
+ format: _enum([
110010
+ "jpeg",
110011
+ "rgb",
110012
+ "bgr",
110013
+ "yuv420",
110014
+ "gray"
110015
+ ]),
110016
+ pts: number(),
110017
+ byteLength: number().int().nonnegative(),
110018
+ nodeId: string(),
110019
+ slotCount: number().int().positive()
110020
+ });
110021
+ /**
110022
+ * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
110023
+ * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
110024
+ * sample bytes plus the track parameters a consumer needs to interpret them.
110025
+ *
110026
+ * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
110027
+ * Float32 PCM), so unlike video frames they ship their bytes INLINE over
110028
+ * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
110029
+ * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
110030
+ * it.
110031
+ */
110032
+ var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
110033
+ var DecodedAudioChunkSchema = object({
110034
+ data: _instanceof(Uint8Array),
110035
+ sampleRate: number().int().positive(),
110036
+ channels: number().int().positive(),
110037
+ timestamp: number(),
110038
+ /**
110039
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
110040
+ * byte, for any peer that never heard of this field. A coded window
110041
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
110042
+ * subscription that DECLARED it accepts one, so absence can never mean
110043
+ * "coded bytes a consumer will read as floats" (D455).
110044
+ */
110045
+ format: AudioChunkFormatSchema.optional()
110046
+ });
110047
+ /**
110048
+ * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
110049
+ * its bytes inline: a consumer asks the broker for the decoded audio-chunk
110050
+ * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
110051
+ * the consumer drains via `pullAudioChunks`.
110052
+ */
110053
+ var SubscribeAudioChunksInputSchema = object({
110054
+ brokerId: string(),
110055
+ /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
110056
+ tag: string().optional(),
110057
+ /**
110058
+ * Byte formats this subscriber can READ, best first. The broker serves the
110059
+ * chunk's own format when it is in this list and expands to `f32le`
110060
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
110061
+ *
110062
+ * Absent (or without the source format) means `f32le` — the behaviour every
110063
+ * subscriber had before D455, unchanged. This is the negotiation half of
110064
+ * the source-bytes lever: it is what lets the broker and its consumers
110065
+ * deploy one at a time across three nodes.
110066
+ */
110067
+ accept: array(AudioChunkFormatSchema).readonly().optional()
110068
+ });
110069
+ /** Result of `stream-broker.subscribeAudioChunks`. */
110070
+ var SubscribeAudioChunksResultSchema = object({
110071
+ /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
110072
+ subscriptionId: string() });
110073
+ var BrokerStatsSchema = object({
110074
+ status: _enum([
110075
+ "idle",
110076
+ "connecting",
110077
+ "streaming",
110078
+ "error",
110079
+ "stopped"
110080
+ ]),
110081
+ inputFps: number(),
110082
+ encodedSubscribers: number(),
110083
+ uptimeMs: number(),
110084
+ bitrateKbps: number(),
110085
+ idrIntervalMs: number(),
110086
+ codec: string().optional(),
110087
+ totalBytes: number(),
110088
+ packetCount: number(),
110089
+ rtspClients: number(),
110090
+ pipeClients: number(),
110091
+ preBufferSec: number(),
110092
+ preBufferMs: number(),
110093
+ preBufferPackets: number(),
110094
+ /**
110095
+ * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
110096
+ * when the stream has no audio track or the broker is in cold start.
110097
+ * `supported = false` means the codec was detected but the local
110098
+ * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
110099
+ * AAC pipeline wired). Surfaced in the UI device-overview so operators
110100
+ * can pre-pick the audio-analysis model that matches the codec.
110101
+ */
110102
+ audio: object({
110103
+ codec: string(),
110104
+ sampleRate: number(),
110105
+ channels: number(),
110106
+ supported: boolean()
110107
+ }).nullable().optional(),
110108
+ /**
110109
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
110110
+ * distributing none of the device's audio, on live or recording.
110111
+ *
110112
+ * Present so a silent camera can be told apart from a broken one on the
110113
+ * stream panel itself, without cross-referencing the switch group: a
110114
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
110115
+ * working exactly as asked. `audioMutedDropped` counts the audio units
110116
+ * thrown away since the current dial — it is how you confirm from stats
110117
+ * alone that the mute is on the packet path and not merely persisted.
110118
+ */
110119
+ audioMuted: boolean().optional(),
110120
+ audioMutedDropped: number().optional()
110121
+ });
110122
+ /**
110123
+ * Exporter-facing "profile restream" entry. Returned by
110124
+ * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
110125
+ * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
110126
+ * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
110127
+ * RTSP restream that aliases the profile's assigned source broker, so
110128
+ * every consumer dialling a profile converges on the same single pull.
110129
+ * `codec`/`resolution` describe the assigned SOURCE camStream (for the
110130
+ * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
110131
+ * (raw per-camStream, live-view only).
110132
+ */
110133
+ var ProfileRtspEntrySchema = object({
110134
+ profile: CamProfileSchema,
110135
+ /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
110136
+ brokerId: string(),
110137
+ url: string(),
110138
+ mutedUrl: string(),
110139
+ enabled: boolean(),
110140
+ codec: string().optional(),
110141
+ resolution: CamStreamResolutionSchema.optional()
110142
+ });
110143
+ /**
110144
+ * The model weight formats a catalog entry can ship. Schema-free on purpose:
110145
+ * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
110146
+ * (the model downloader in `@camstack/server/system`) needs only the list and must
110147
+ * reach it through `@camstack/types/addon` without building the cap schema
110148
+ * graph (D28).
110149
+ */
110150
+ var MODEL_FORMATS = [
110151
+ "onnx",
110152
+ "coreml",
110153
+ "openvino",
110154
+ "tflite",
110155
+ "pt",
110156
+ "gguf"
110157
+ ];
109664
110158
  /**
109665
110159
  * `addon-pages-source` — collection cap exposing per-provider raw page
109666
110160
  * declarations. Every addon that contributes a UI page registers a
@@ -109936,6 +110430,7 @@ var CAP_NAMES = Object.freeze([
109936
110430
  "mesh-network",
109937
110431
  "meter",
109938
110432
  "metrics-provider",
110433
+ "migrations",
109939
110434
  "model-convert",
109940
110435
  "model-distributor",
109941
110436
  "motion",
@@ -109968,6 +110463,7 @@ var CAP_NAMES = Object.freeze([
109968
110463
  "plate-gallery",
109969
110464
  "platform-probe",
109970
110465
  "power-meter",
110466
+ "power-source",
109971
110467
  "presence",
109972
110468
  "pressure-sensor",
109973
110469
  "privacy-mask",
@@ -111319,6 +111815,13 @@ Object.freeze({
111319
111815
  "valueType": "number"
111320
111816
  }
111321
111817
  ],
111818
+ "power-source": [{
111819
+ "path": "switchOn",
111820
+ "valueType": "boolean"
111821
+ }, {
111822
+ "path": "offSeconds",
111823
+ "valueType": "number"
111824
+ }],
111322
111825
  "presence": [
111323
111826
  {
111324
111827
  "path": "state",
@@ -111868,379 +112371,7 @@ function sleep(ms) {
111868
112371
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
111869
112372
  }
111870
112373
  //#endregion
111871
- //#region ../types/dist/err-msg-BD5buxvT.mjs
111872
- var DeviceType = /* @__PURE__ */ function(DeviceType) {
111873
- DeviceType["Camera"] = "camera";
111874
- DeviceType["Hub"] = "hub";
111875
- DeviceType["Light"] = "light";
111876
- DeviceType["Siren"] = "siren";
111877
- DeviceType["Switch"] = "switch";
111878
- DeviceType["Sensor"] = "sensor";
111879
- DeviceType["Thermostat"] = "thermostat";
111880
- /** Air-conditioner / heat-pump climate device (HVAC) — shares the
111881
- * `climate-control` cap surface with `Thermostat` but renders a
111882
- * dedicated AC-appropriate control UI (mode chips, fan speed,
111883
- * independent vertical/horizontal swing). Sources: native Gree, and
111884
- * reusable by other AC integrations. */
111885
- DeviceType["Climate"] = "climate";
111886
- DeviceType["Button"] = "button";
111887
- /** Generic stateless event emitter — carries a device's EXACT declared
111888
- * event vocabulary verbatim (no normalization). Installed with the
111889
- * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
111890
- * HA bus events (e.g. `zha_event`, generic). */
111891
- DeviceType["EventEmitter"] = "event-emitter";
111892
- /** Firmware/software update entity — current vs available version,
111893
- * updatable flag, update state, and an install action. Installed with
111894
- * the `update` cap. Sources: Homematic firmware-update channels (and
111895
- * reusable by other providers, e.g. HA `update.*` entities). */
111896
- DeviceType["Update"] = "update";
111897
- DeviceType["Generic"] = "generic";
111898
- /** Generic notification delivery target (HA `notify.<service>`, future
111899
- * Telegram / Discord / ntfy / SMTP, …). One device per delivery
111900
- * endpoint; the `notifier` cap defines the send surface. */
111901
- DeviceType["Notifier"] = "notifier";
111902
- /** Pre-recorded action sequence with optional parameters
111903
- * (HA `script.*`). Runnable via `script-runner` cap. */
111904
- DeviceType["Script"] = "script";
111905
- /** Automation rule (HA `automation.*`) — enable/disable + manual
111906
- * trigger surface exposed via `automation-control` cap. */
111907
- DeviceType["Automation"] = "automation";
111908
- /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
111909
- DeviceType["Lock"] = "lock";
111910
- /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
111911
- * `valve.*`). `cover` cap with sub-roles for variant. */
111912
- DeviceType["Cover"] = "cover";
111913
- /** Pipe / water / gas valve with open/close/stop and optional
111914
- * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
111915
- * modelled on the same open/closed lifecycle. */
111916
- DeviceType["Valve"] = "valve";
111917
- /** Humidifier / dehumidifier with on/off + target humidity + mode
111918
- * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
111919
- * modelled on the same target / mode lifecycle. */
111920
- DeviceType["Humidifier"] = "humidifier";
111921
- /** Water heater / boiler with target temperature + operation mode +
111922
- * away mode (HA `water_heater.*`). `water-heater` cap — a
111923
- * climate-family actuator. */
111924
- DeviceType["WaterHeater"] = "water-heater";
111925
- /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
111926
- DeviceType["Fan"] = "fan";
111927
- /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
111928
- * the camera surface — those use `Camera`. `media-player` cap. */
111929
- DeviceType["MediaPlayer"] = "media-player";
111930
- /** Security panel / alarm system (HA `alarm_control_panel.*`).
111931
- * `alarm-panel` cap. */
111932
- DeviceType["AlarmPanel"] = "alarm-panel";
111933
- /** Generic user-settable input (HA `number` / `input_number` / `select`
111934
- * / `input_select` / `text` / `input_text` / `input_datetime`).
111935
- * Sub-type via `DeviceRole`: NumericControl / SelectControl /
111936
- * TextControl / DateTimeControl. */
111937
- DeviceType["Control"] = "control";
111938
- /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
111939
- * `presence` cap. */
111940
- DeviceType["Presence"] = "presence";
111941
- /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
111942
- * `weather` cap. */
111943
- DeviceType["Weather"] = "weather";
111944
- /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
111945
- DeviceType["Vacuum"] = "vacuum";
111946
- /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
111947
- * `lawn-mower-control` cap. */
111948
- DeviceType["LawnMower"] = "lawn-mower";
111949
- /** Physical HA device group — parent container for entity-children
111950
- * adopted from a single HA device entry. Not renderable as a
111951
- * standalone device; exists only to anchor child entities. */
111952
- DeviceType["Container"] = "container";
111953
- /** Single still-image entity (HA `image.*`). Read-only display of an
111954
- * `entity_picture` signed URL the browser loads directly. `image` cap. */
111955
- DeviceType["Image"] = "image";
111956
- /** Smart pet feeder — cloud-connected food dispenser with a bowl food
111957
- * level, battery, desiccant life, feeding state and manual-feed /
111958
- * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
111959
- * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
111960
- * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
111961
- * integrations sharing the same food/desiccant/hopper surface. */
111962
- DeviceType["PetFeeder"] = "pet-feeder";
111963
- /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
111964
- * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
111965
- * of a container and is BUILT as a composed device (D967). */
111966
- DeviceType["PetFountain"] = "pet-fountain";
111967
- /** Built-in dishwasher — program lifecycle, door / remote-start gates,
111968
- * salt and rinse-aid levels. Installed with the `dishwasher` cap.
111969
- * Sources: native Bosch Home Connect, reusable by similar integrations.
111970
- * A plug-built dishwasher carries running-state and power instead. */
111971
- DeviceType["Dishwasher"] = "dishwasher";
111972
- /** Washing machine. A plug-built one is running while its power stays
111973
- * above a threshold. No program cap — that surface is the dishwasher's. */
111974
- DeviceType["Washer"] = "washer";
111975
- /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
111976
- DeviceType["Dryer"] = "dryer";
111977
- /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
111978
- * other radio-to-IP bridges). It carries the box's own facts (firmware,
111979
- * address, connection state) and the actions the box really supports
111980
- * (`reboot`, `device-admin-link`). Products are NOT its children — they are
111981
- * adopted beside it — and its connection settings stay on the integration. */
111982
- DeviceType["Gateway"] = "gateway";
111983
- return DeviceType;
111984
- }({});
111985
- var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
111986
- DeviceFeature["BatteryOperated"] = "battery-operated";
111987
- DeviceFeature["Rebootable"] = "rebootable";
111988
- /**
111989
- * Device supports an on-demand re-sync of its derived spec with its
111990
- * upstream source — drives the generic Re-sync button. The owning
111991
- * provider implements the action via the `device-adoption.resync` cap.
111992
- */
111993
- DeviceFeature["Resyncable"] = "resyncable";
111994
- DeviceFeature["NativeSnapshot"] = "native-snapshot";
111995
- DeviceFeature["DoorbellButton"] = "doorbell-button";
111996
- DeviceFeature["TwoWayAudio"] = "two-way-audio";
111997
- DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
111998
- /**
111999
- * Camera supports the on-firmware autotrack subsystem (subject-
112000
- * following). Distinct from `PanTiltZoom` because not every PTZ
112001
- * camera ships autotrack — the admin UI uses this flag to gate
112002
- * the autotrack toggle / settings card without re-deriving from
112003
- * the cap registry. Mirrors `ptz-autotrack` cap registration:
112004
- * driver sets this feature when probe confirms the firmware
112005
- * surface, and registers the cap in the same code path.
112006
- */
112007
- DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
112008
- /**
112009
- * Accessory exposes a "trigger on motion" toggle — the parent camera's
112010
- * motion detection automatically activates this device. Mirrors
112011
- * `motion-trigger` cap registration: drivers set this feature in the
112012
- * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
112013
- *
112014
- * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
112015
- * fast scalar without binding fetch), notifier rules, and `listAll`
112016
- * filters that want "all devices with on-motion behaviour".
112017
- */
112018
- DeviceFeature["MotionTrigger"] = "motion-trigger";
112019
- /** Light supports rgb-triplet color via `color` cap. */
112020
- DeviceFeature["LightColorRgb"] = "light-color-rgb";
112021
- /** Light supports HSV color via `color` cap. */
112022
- DeviceFeature["LightColorHsv"] = "light-color-hsv";
112023
- /** Light supports color-temperature (mired) via `color` cap. */
112024
- DeviceFeature["LightColorMired"] = "light-color-mired";
112025
- /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
112026
- * targetHigh). Gates the range slider UI. */
112027
- DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
112028
- /** Thermostat exposes target humidity and/or current humidity
112029
- * readings. Gates the humidity controls. */
112030
- DeviceFeature["ClimateHumidity"] = "climate-humidity";
112031
- /** Thermostat exposes a fan-mode selector. */
112032
- DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
112033
- /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
112034
- DeviceFeature["ClimatePreset"] = "climate-preset";
112035
- /** Thermostat exposes a vertical louver swing toggle. Gates the
112036
- * vertical-swing switch in the climate UI. Independent of horizontal. */
112037
- DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
112038
- /** Thermostat exposes a horizontal louver swing toggle. Gates the
112039
- * horizontal-swing switch in the climate UI. Independent of vertical. */
112040
- DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
112041
- /** Cover exposes intermediate position control (0..100). Gates the
112042
- * position slider UI. */
112043
- DeviceFeature["CoverPositionable"] = "cover-positionable";
112044
- /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
112045
- DeviceFeature["CoverTilt"] = "cover-tilt";
112046
- /** Valve exposes intermediate position control (0..100). Gates the
112047
- * position slider / drag surface UI. */
112048
- DeviceFeature["ValvePositionable"] = "valve-positionable";
112049
- /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
112050
- DeviceFeature["FanSpeed"] = "fan-speed";
112051
- /** Fan exposes a preset mode selector. */
112052
- DeviceFeature["FanPreset"] = "fan-preset";
112053
- /** Fan exposes blade direction (forward/reverse) — typical of
112054
- * ceiling fans. */
112055
- DeviceFeature["FanDirection"] = "fan-direction";
112056
- /** Fan exposes an oscillation toggle. */
112057
- DeviceFeature["FanOscillating"] = "fan-oscillating";
112058
- /** Lock requires a PIN code on lock/unlock. Gates the code-entry
112059
- * field on the UI lock-controls panel. */
112060
- DeviceFeature["LockPinRequired"] = "lock-pin-required";
112061
- /** Lock supports a latch-release ("open door") action distinct from
112062
- * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
112063
- * `supported_features`. Gates the Open Door button in the UI. */
112064
- DeviceFeature["LockOpen"] = "lock-open";
112065
- /** Media player exposes a seek-to-position surface. */
112066
- DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
112067
- /** Media player exposes a volume-level setter. */
112068
- DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
112069
- /** Media player exposes a mute toggle distinct from volume=0. */
112070
- DeviceFeature["MediaPlayerMute"] = "media-player-mute";
112071
- /** Media player exposes a shuffle toggle. */
112072
- DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
112073
- /** Media player exposes a repeat mode (off / all / one). */
112074
- DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
112075
- /** Media player exposes a source / input selector. */
112076
- DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
112077
- /** Media player exposes a play-arbitrary-media surface (URL / id). */
112078
- DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
112079
- /** Media player exposes next-track. */
112080
- DeviceFeature["MediaPlayerNext"] = "media-player-next";
112081
- /** Media player exposes previous-track. */
112082
- DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
112083
- /** Media player exposes stop distinct from pause. */
112084
- DeviceFeature["MediaPlayerStop"] = "media-player-stop";
112085
- /** Alarm panel requires a PIN code on arm/disarm. */
112086
- DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
112087
- /** Presence device carries GPS coordinates (lat/lng/accuracy) in
112088
- * addition to a textual location. */
112089
- DeviceFeature["PresenceGps"] = "presence-gps";
112090
- /** Notifier accepts an inline / URL image attachment. */
112091
- DeviceFeature["NotifierImage"] = "notifier-image";
112092
- /** Notifier accepts a priority hint (high/normal/low). */
112093
- DeviceFeature["NotifierPriority"] = "notifier-priority";
112094
- /** Notifier accepts a free-form `data` payload for platform-specific
112095
- * fields. */
112096
- DeviceFeature["NotifierData"] = "notifier-data";
112097
- /** Notifier supports interactive action buttons / callbacks. */
112098
- DeviceFeature["NotifierActions"] = "notifier-actions";
112099
- /** Notifier supports per-call recipient targeting (multi-user). */
112100
- DeviceFeature["NotifierRecipients"] = "notifier-recipients";
112101
- /** Script runner accepts a variables map on each run invocation. */
112102
- DeviceFeature["ScriptVariables"] = "script-variables";
112103
- /** Automation `trigger` accepts a skipCondition flag — fires the
112104
- * automation's actions while bypassing its condition block. */
112105
- DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
112106
- /** Robot vacuum exposes a live cleaning map (image child). Gates the
112107
- * map tile in the vacuum UI. */
112108
- DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
112109
- /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
112110
- * switches group. */
112111
- DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
112112
- /** Robot mower exposes a live mowing map (SVG image child). Gates the
112113
- * mower map tile. */
112114
- DeviceFeature["MowerHasMap"] = "mower-has-map";
112115
- /** Robot mower exposes targeted mowing (all-area / zones / edges /
112116
- * spots) — gates the Mow action + map selectors. */
112117
- DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
112118
- return DeviceFeature;
112119
- }({});
112120
- /**
112121
- * Semantic role a device plays within its parent. Populated by driver
112122
- * addons when creating accessory devices (Reolink siren/floodlight/
112123
- * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
112124
- * admin UI to pick icons, labels, and widgets — a `Switch` with
112125
- * `role: Floodlight` renders as a bulb with a brightness slider,
112126
- * whereas a `Switch` with `role: Siren` renders as a klaxon.
112127
- *
112128
- * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
112129
- * sqlite as a nullable TEXT column — old rows keep working unchanged.
112130
- */
112131
- var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
112132
- DeviceRole["Siren"] = "siren";
112133
- DeviceRole["Floodlight"] = "floodlight";
112134
- DeviceRole["Spotlight"] = "spotlight";
112135
- DeviceRole["PirSensor"] = "pir-sensor";
112136
- DeviceRole["Chime"] = "chime";
112137
- DeviceRole["Autotrack"] = "autotrack";
112138
- DeviceRole["Nightvision"] = "nightvision";
112139
- DeviceRole["PrivacyMask"] = "privacy-mask";
112140
- DeviceRole["Doorbell"] = "doorbell";
112141
- /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
112142
- DeviceRole["CoverBlind"] = "cover-blind";
112143
- /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
112144
- DeviceRole["CoverShutter"] = "cover-shutter";
112145
- /** Exterior awning. HA `device_class: awning`. */
112146
- DeviceRole["CoverAwning"] = "cover-awning";
112147
- /** Curtain / drape. HA `device_class: curtain`. */
112148
- DeviceRole["CoverCurtain"] = "cover-curtain";
112149
- /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
112150
- DeviceRole["CoverGarage"] = "cover-garage";
112151
- /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
112152
- DeviceRole["CoverGate"] = "cover-gate";
112153
- /** Door. HA `device_class: door`. A security actuator: exports gate it. */
112154
- DeviceRole["CoverDoor"] = "cover-door";
112155
- /** Window. HA `device_class: window`. */
112156
- DeviceRole["CoverWindow"] = "cover-window";
112157
- /** Virtual HA toggle (input_boolean.*) — distinguishable from a
112158
- * real Switch device for UI rendering / export adapters. */
112159
- DeviceRole["BinaryHelper"] = "binary-helper";
112160
- /** A smart plug: a `switch` whose source says it is an outlet (HA
112161
- * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
112162
- DeviceRole["Outlet"] = "outlet";
112163
- /** Generic motion / occupancy / moving event source. Distinct from
112164
- * the camera accessory PirSensor role: that one is a camera child;
112165
- * this is a standalone HA / 3rd-party motion sensor. */
112166
- DeviceRole["MotionSensor"] = "motion-sensor";
112167
- DeviceRole["ContactSensor"] = "contact-sensor";
112168
- DeviceRole["LeakSensor"] = "leak-sensor";
112169
- DeviceRole["SmokeSensor"] = "smoke-sensor";
112170
- DeviceRole["COSensor"] = "co-sensor";
112171
- DeviceRole["GasSensor"] = "gas-sensor";
112172
- DeviceRole["TamperSensor"] = "tamper-sensor";
112173
- DeviceRole["VibrationSensor"] = "vibration-sensor";
112174
- DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
112175
- DeviceRole["SoundSensor"] = "sound-sensor";
112176
- /** Fallback for `binary_sensor` without a known `device_class`. */
112177
- DeviceRole["BinarySensor"] = "binary-sensor";
112178
- DeviceRole["TemperatureSensor"] = "temperature-sensor";
112179
- DeviceRole["HumiditySensor"] = "humidity-sensor";
112180
- DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
112181
- DeviceRole["PressureSensor"] = "pressure-sensor";
112182
- /** Wind speed or direction (weather-station `wind-sensor` cap). */
112183
- DeviceRole["WindSensor"] = "wind-sensor";
112184
- /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
112185
- DeviceRole["RainSensor"] = "rain-sensor";
112186
- /** UV index (weather-station `uv-sensor` cap). */
112187
- DeviceRole["UvSensor"] = "uv-sensor";
112188
- /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
112189
- * Distinct from AmbientLightSensor (lux). */
112190
- DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
112191
- /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
112192
- DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
112193
- DeviceRole["PowerSensor"] = "power-sensor";
112194
- DeviceRole["EnergySensor"] = "energy-sensor";
112195
- DeviceRole["VoltageSensor"] = "voltage-sensor";
112196
- DeviceRole["CurrentSensor"] = "current-sensor";
112197
- DeviceRole["AirQualitySensor"] = "air-quality-sensor";
112198
- /** Battery level (numeric % via `sensor` OR low-bool via
112199
- * `binary_sensor` — the cap distinguishes via the value type). */
112200
- DeviceRole["BatterySensor"] = "battery-sensor";
112201
- /** Fallback for `sensor` numeric without a known `device_class`. */
112202
- DeviceRole["NumericSensor"] = "numeric-sensor";
112203
- /** String / enum state (HA `sensor` with `state_class: enum` or
112204
- * `attributes.options`). */
112205
- DeviceRole["EnumSensor"] = "enum-sensor";
112206
- /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
112207
- * or `date`). The slice carries the raw ISO string verbatim (hosted on
112208
- * the `enum-sensor` cap); the UI renders it locale-formatted. */
112209
- DeviceRole["DateTimeSensor"] = "datetime-sensor";
112210
- /** Last-resort fallback when nothing else matches. */
112211
- DeviceRole["GenericSensor"] = "generic-sensor";
112212
- DeviceRole["NumericControl"] = "numeric-control";
112213
- DeviceRole["SelectControl"] = "select-control";
112214
- DeviceRole["TextControl"] = "text-control";
112215
- DeviceRole["DateTimeControl"] = "datetime-control";
112216
- /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
112217
- * rich features (image, priority, channel routing). */
112218
- DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
112219
- /** Chat / messaging service (HA `notify.telegram_*`,
112220
- * `notify.discord_*`, etc.). */
112221
- DeviceRole["MessagingNotifier"] = "messaging-notifier";
112222
- /** Email-based delivery (HA `notify.smtp`, etc.). */
112223
- DeviceRole["EmailNotifier"] = "email-notifier";
112224
- /** Fallback when the notifier service name doesn't match a known
112225
- * pattern. */
112226
- DeviceRole["GenericNotifier"] = "generic-notifier";
112227
- /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
112228
- * cap. A `DeviceType.Generic` device. */
112229
- DeviceRole["IrTransmitter"] = "ir-transmitter";
112230
- return DeviceRole;
112231
- }({});
112232
- /**
112233
- import { errMsg } from '@camstack/types'
112234
- * Extract a human-readable message from an unknown error value.
112235
- * Replaces the ubiquitous `errMsg(err)` pattern.
112236
- */
112237
- function errMsg$2(err) {
112238
- if (err instanceof Error) return err.message;
112239
- if (typeof err === "string") return err;
112240
- return String(err);
112241
- }
112242
- //#endregion
112243
- //#region ../types/dist/composition-C7WdThmP.mjs
112374
+ //#region ../types/dist/composition-BBzVf2xd.mjs
112244
112375
  /**
112245
112376
  * The ffmpeg build every CamStack node downloads, per platform and arch.
112246
112377
  *
@@ -113459,6 +113590,71 @@ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
113459
113590
  var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
113460
113591
  new Set(EVENT_OWNER_TYPES);
113461
113592
  /**
113593
+ * What a published package needs from the rest of the install (D989).
113594
+ *
113595
+ * One published framework package (`@camstack/server`) carries the kernel, the
113596
+ * builtins and the capability contracts, so "which addon runs on which
113597
+ * framework" is a question of version, and the answer is DECLARED by the
113598
+ * package that knows it:
113599
+ *
113600
+ * - an addon says the oldest server it runs on (`requires.server: ">=X"`);
113601
+ * - the server says the oldest addon it works with
113602
+ * (`requires.addons: { "@camstack/addon-x": ">=Y" }`) and the oldest shell
113603
+ * around it (`requires.wrapper: { desktop, image }`);
113604
+ * - a wrapper (the desktop app, the container starter) says which server
113605
+ * range it can host, and reports its own kind and version.
113606
+ *
113607
+ * The publish workflow STAMPS these after the version bump
113608
+ * (`scripts/ci/stamp-requires.mjs`); nobody types a number by hand.
113609
+ *
113610
+ * A requirement that cannot be checked is NOT met: a shell that reports no
113611
+ * version counts as below the minimum, and an unreadable running version is
113612
+ * named in the result (a failed read is never a pass).
113613
+ */
113614
+ /** `>=MAJOR.MINOR.PATCH[-prerelease]` — the only range shape a requirement uses. */
113615
+ var MinVersionSchema = string().regex(/^>=\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, "a minimum is written \">=X.Y.Z\"");
113616
+ object({
113617
+ /** The oldest `@camstack/server` this package runs on. */
113618
+ server: MinVersionSchema.optional(),
113619
+ /** The oldest installed version of each named addon package. */
113620
+ addons: record(string(), MinVersionSchema).optional(),
113621
+ /** The oldest shell around the server, per shell kind. */
113622
+ wrapper: object({
113623
+ desktop: MinVersionSchema.optional(),
113624
+ image: MinVersionSchema.optional()
113625
+ }).optional()
113626
+ });
113627
+ /** One unmet requirement; each names the versions on both sides. */
113628
+ var UnmetRequirementSchema = discriminatedUnion("kind", [
113629
+ object({
113630
+ kind: literal("server-too-old"),
113631
+ required: string(),
113632
+ /** `null` — the running server version could not be read. */
113633
+ running: string().nullable()
113634
+ }),
113635
+ object({
113636
+ kind: literal("addon-too-old"),
113637
+ packageName: string(),
113638
+ required: string(),
113639
+ /** `null` — the addon is not installed (or its version is unreadable). */
113640
+ running: string().nullable()
113641
+ }),
113642
+ object({
113643
+ kind: literal("wrapper-too-old"),
113644
+ wrapper: _enum(["desktop", "image"]),
113645
+ required: string(),
113646
+ /** `null` — the wrapper reported no version. */
113647
+ running: string().nullable()
113648
+ })
113649
+ ]);
113650
+ var RequiredAddonUpdateSchema = object({
113651
+ packageName: string(),
113652
+ /** The minimum the target server declares. */
113653
+ required: string(),
113654
+ /** What this node has; `null` when the addon is not installed. */
113655
+ installed: string().nullable()
113656
+ });
113657
+ /**
113462
113658
  * Which integration instance a settings form is for. An addon that serves several instances
113463
113659
  * (`instanceMode: multiple`) can show a different form per instance — an NVR integration
113464
113660
  * needs its connection, the default integration needs the addon's own sections.
@@ -113953,6 +114149,613 @@ var CameraSwitchGroupSchema = object({
113953
114149
  fetchedAt: number()
113954
114150
  });
113955
114151
  /**
114152
+ * Query filter for settings-store collections.
114153
+ */
114154
+ var QueryFilterSchema = object({
114155
+ where: record(string(), unknown()).optional(),
114156
+ whereIn: record(string(), array(unknown())).optional(),
114157
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
114158
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
114159
+ whereNot: record(string(), unknown()).optional(),
114160
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
114161
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
114162
+ whereNotIn: record(string(), array(unknown())).optional(),
114163
+ orderBy: object({
114164
+ field: string(),
114165
+ direction: _enum(["asc", "desc"])
114166
+ }).optional(),
114167
+ limit: number().optional(),
114168
+ offset: number().optional()
114169
+ });
114170
+ /**
114171
+ * The predicate half of a filter, for BULK MUTATIONS.
114172
+ *
114173
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
114174
+ * meaning for a statement that rewrites a set, and accepting them would invite
114175
+ * a caller to believe `limit` bounds the damage. Every field is optional here
114176
+ * only so the shape stays composable — the implementation REJECTS a filter
114177
+ * that compiles to no predicate, because that is the whole collection.
114178
+ */
114179
+ var MutationFilterSchema = object({
114180
+ where: record(string(), unknown()).optional(),
114181
+ whereIn: record(string(), array(unknown())).optional(),
114182
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
114183
+ whereNot: record(string(), unknown()).optional(),
114184
+ whereNotIn: record(string(), array(unknown())).optional()
114185
+ });
114186
+ /**
114187
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
114188
+ *
114189
+ * `as` names the slot in the result, so the SAME column may be asked twice with
114190
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
114191
+ * a `Record<column, op>` shape could not express.
114192
+ */
114193
+ var AggregateFieldSchema = object({
114194
+ /** Result key. */
114195
+ as: string().min(1),
114196
+ /** Column to aggregate. Must be a real column of a declared collection. */
114197
+ field: string().min(1),
114198
+ op: _enum([
114199
+ "sum",
114200
+ "min",
114201
+ "max"
114202
+ ])
114203
+ });
114204
+ /**
114205
+ * `COUNT(*)` plus one number per requested field.
114206
+ *
114207
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
114208
+ * that really is 0 are different facts, and an accounting caller that renders
114209
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
114210
+ */
114211
+ var AggregateResultSchema = object({
114212
+ count: number().int(),
114213
+ values: record(string(), number().nullable())
114214
+ });
114215
+ /** A single stored record: `{ id, data }`. */
114216
+ var SettingsRecordSchema = object({
114217
+ id: string(),
114218
+ data: record(string(), unknown())
114219
+ });
114220
+ /**
114221
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
114222
+ * optional.
114223
+ *
114224
+ * A separate schema rather than loosening the shared one: every other method
114225
+ * on this cap addresses a row BY its id, and making that field optional
114226
+ * everywhere would turn a forgotten key into a silently generated one on
114227
+ * `update` and `delete` as well.
114228
+ */
114229
+ var BulkRecordSchema = object({
114230
+ id: string().optional(),
114231
+ data: record(string(), unknown())
114232
+ });
114233
+ /**
114234
+ * The database FILES one `sqlite-settings` engine process owns (D851).
114235
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
114236
+ * serving thread, so a new one is a code change, never a string a caller
114237
+ * invents. `settings` is `camstack.db` and the default for every collection
114238
+ * that names no file.
114239
+ */
114240
+ var DataStoreDatabaseSchema = _enum([
114241
+ "settings",
114242
+ "events",
114243
+ "history"
114244
+ ]);
114245
+ DataStoreDatabaseSchema.options;
114246
+ /**
114247
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
114248
+ * The three that a "replace this set, bump that revision" write needs, plus
114249
+ * the batched upsert a rollup writer needs.
114250
+ */
114251
+ var TransactOpSchema = discriminatedUnion("op", [
114252
+ object({
114253
+ op: literal("deleteWhere"),
114254
+ collection: string(),
114255
+ filter: MutationFilterSchema
114256
+ }),
114257
+ object({
114258
+ op: literal("insertMany"),
114259
+ collection: string(),
114260
+ records: array(BulkRecordSchema).readonly()
114261
+ }),
114262
+ object({
114263
+ op: literal("update"),
114264
+ collection: string(),
114265
+ id: string(),
114266
+ data: record(string(), unknown())
114267
+ }),
114268
+ object({
114269
+ op: literal("upsertMany"),
114270
+ collection: string(),
114271
+ conflict: array(string()).readonly(),
114272
+ records: array(BulkRecordSchema).readonly()
114273
+ })
114274
+ ]);
114275
+ /**
114276
+ * The precondition of a transaction: the row `key` of `collection` must hold
114277
+ * `equals` in `column`, read INSIDE the transaction.
114278
+ */
114279
+ var TransactGuardSchema = object({
114280
+ collection: string(),
114281
+ key: string(),
114282
+ column: string(),
114283
+ equals: unknown()
114284
+ });
114285
+ /**
114286
+ * Column declaration for a structured (SQL-backed) collection.
114287
+ *
114288
+ * Logical types — the backend translates each to the matching SQLite
114289
+ * storage class and handles per-type marshaling:
114290
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
114291
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
114292
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
114293
+ */
114294
+ var CollectionColumnSchema = object({
114295
+ name: string(),
114296
+ type: _enum([
114297
+ "TEXT",
114298
+ "INTEGER",
114299
+ "REAL",
114300
+ "JSON",
114301
+ "BOOLEAN"
114302
+ ]),
114303
+ primaryKey: boolean().optional(),
114304
+ notNull: boolean().optional(),
114305
+ unique: boolean().optional(),
114306
+ /**
114307
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
114308
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
114309
+ * collection surface simply cannot express their existing tables — which is
114310
+ * why they were still on the uncapped `table*` API.
114311
+ */
114312
+ defaultValue: union([
114313
+ string(),
114314
+ number(),
114315
+ boolean()
114316
+ ]).optional()
114317
+ });
114318
+ var CollectionIndexSchema = object({
114319
+ name: string(),
114320
+ columns: array(string()).readonly(),
114321
+ unique: boolean().optional()
114322
+ });
114323
+ /**
114324
+ * The input of `declareCollection` — ONE schema for the door and for the
114325
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
114326
+ * the payload untouched).
114327
+ *
114328
+ * - `database` — the file the collection lives in (D851). Absent means
114329
+ * `settings`. A declaration naming a file other than the one the table is
114330
+ * already in is REFUSED by the engine, never moved.
114331
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
114332
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
114333
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
114334
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
114335
+ * address one row by id are refused.
114336
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
114337
+ */
114338
+ var DeclareCollectionInputSchema = object({
114339
+ namespace: string().optional(),
114340
+ collection: string(),
114341
+ columns: array(CollectionColumnSchema).readonly(),
114342
+ indexes: array(CollectionIndexSchema).readonly().optional(),
114343
+ database: DataStoreDatabaseSchema.optional(),
114344
+ primaryKey: array(string()).readonly().optional(),
114345
+ withoutRowid: boolean().optional()
114346
+ });
114347
+ method(object({
114348
+ namespace: string().optional(),
114349
+ collection: string(),
114350
+ key: string()
114351
+ }), unknown(), { auth: "admin" }), method(object({
114352
+ namespace: string().optional(),
114353
+ collection: string(),
114354
+ key: string(),
114355
+ value: unknown()
114356
+ }), _void(), {
114357
+ kind: "mutation",
114358
+ auth: "admin"
114359
+ }), method(object({
114360
+ namespace: string().optional(),
114361
+ collection: string(),
114362
+ filter: QueryFilterSchema.optional(),
114363
+ columns: array(string()).readonly().optional()
114364
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
114365
+ namespace: string().optional(),
114366
+ collection: string(),
114367
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
114368
+ * index on the collection. Never empty. */
114369
+ conflict: array(string()).readonly(),
114370
+ record: BulkRecordSchema
114371
+ }), object({
114372
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
114373
+ * row's own id on an update. */
114374
+ id: union([string(), number()]) }), {
114375
+ kind: "mutation",
114376
+ auth: "admin"
114377
+ }), method(object({
114378
+ namespace: string().optional(),
114379
+ collection: string(),
114380
+ conflict: array(string()).readonly(),
114381
+ records: array(BulkRecordSchema).readonly()
114382
+ }), object({ upserted: number().int() }), {
114383
+ kind: "mutation",
114384
+ auth: "admin"
114385
+ }), method(object({
114386
+ namespace: string().optional(),
114387
+ collection: string(),
114388
+ record: object({
114389
+ id: string().optional(),
114390
+ data: record(string(), unknown())
114391
+ })
114392
+ }), object({
114393
+ /**
114394
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
114395
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
114396
+ * primary key — which is the only place an auto key is knowable.
114397
+ */
114398
+ id: union([string(), number()]) }), {
114399
+ kind: "mutation",
114400
+ auth: "admin"
114401
+ }), method(object({
114402
+ namespace: string().optional(),
114403
+ collection: string(),
114404
+ records: array(BulkRecordSchema).readonly()
114405
+ }), object({ inserted: number().int() }), {
114406
+ kind: "mutation",
114407
+ auth: "admin"
114408
+ }), method(object({
114409
+ namespace: string().optional(),
114410
+ guard: TransactGuardSchema.optional(),
114411
+ ops: array(TransactOpSchema).readonly()
114412
+ }), object({
114413
+ applied: boolean(),
114414
+ current: unknown().optional()
114415
+ }), {
114416
+ kind: "mutation",
114417
+ auth: "admin"
114418
+ }), method(object({
114419
+ namespace: string().optional(),
114420
+ collection: string(),
114421
+ id: string(),
114422
+ data: record(string(), unknown())
114423
+ }), _void(), {
114424
+ kind: "mutation",
114425
+ auth: "admin"
114426
+ }), method(object({
114427
+ namespace: string().optional(),
114428
+ collection: string(),
114429
+ key: string()
114430
+ }), _void(), {
114431
+ kind: "mutation",
114432
+ auth: "admin"
114433
+ }), method(object({
114434
+ namespace: string().optional(),
114435
+ collection: string(),
114436
+ filter: MutationFilterSchema
114437
+ }), object({ deleted: number().int() }), {
114438
+ kind: "mutation",
114439
+ auth: "admin"
114440
+ }), method(object({
114441
+ namespace: string().optional(),
114442
+ collection: string(),
114443
+ filter: MutationFilterSchema,
114444
+ data: record(string(), unknown())
114445
+ }), object({ updated: number().int() }), {
114446
+ kind: "mutation",
114447
+ auth: "admin"
114448
+ }), method(object({
114449
+ namespace: string().optional(),
114450
+ collection: string(),
114451
+ filter: QueryFilterSchema.optional()
114452
+ }), number(), { auth: "admin" }), method(object({
114453
+ namespace: string().optional(),
114454
+ collection: string(),
114455
+ fields: array(AggregateFieldSchema).readonly(),
114456
+ filter: QueryFilterSchema.optional()
114457
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
114458
+ namespace: string().optional(),
114459
+ collection: string(),
114460
+ field: string(),
114461
+ bucketSize: number().int().positive(),
114462
+ origin: number().int(),
114463
+ filter: QueryFilterSchema.optional()
114464
+ }), array(object({
114465
+ bucket: number().int(),
114466
+ count: number().int()
114467
+ })).readonly(), { auth: "admin" }), method(object({
114468
+ namespace: string().optional(),
114469
+ collection: string()
114470
+ }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
114471
+ kind: "mutation",
114472
+ auth: "admin"
114473
+ });
114474
+ var MigrationIdSchema = string().regex(/^[a-z0-9-]+\/\d{4}-\d{2}-\d{2}-[a-z0-9-]+$/, "a migration id is \"<owner>/<yyyy-mm-dd>-<slug>\"");
114475
+ var MigrationLevelSchema = _enum([
114476
+ "hub-store",
114477
+ "addon-state",
114478
+ "node-config",
114479
+ "viewer",
114480
+ "addon-internal"
114481
+ ]);
114482
+ var MigrationStoreSchema = _enum([
114483
+ "settings",
114484
+ "events",
114485
+ "history",
114486
+ "node-local",
114487
+ "addon-files"
114488
+ ]);
114489
+ var MigrationScopeSchema = _enum(["cluster", "per-node"]);
114490
+ /** What depends on a migration while it is pending: `block-owner` is the default. */
114491
+ var MigrationOnFailureSchema = _enum(["block-owner", "tolerate"]);
114492
+ /**
114493
+ * `sql` is a hub-only body over the owner's connection. `plan` returns edits the
114494
+ * engine applies inside the migration's own transaction (hub-owned: computed in
114495
+ * that transaction; addon-owned: computed by the owner and shipped to the engine).
114496
+ * `procedure` is addon-owned work that is not SQL (keychain, integration registry,
114497
+ * device tombstones); the ledger records its outcome and it must be idempotent.
114498
+ */
114499
+ var MigrationBodyKindSchema = _enum([
114500
+ "sql",
114501
+ "plan",
114502
+ "procedure"
114503
+ ]);
114504
+ var MigrationMetaSchema = object({
114505
+ id: MigrationIdSchema,
114506
+ /** `hub`, an addon id, `node` or `viewer`. */
114507
+ owner: string().regex(/^[a-z0-9-]+$/),
114508
+ level: MigrationLevelSchema,
114509
+ /** Strictly increasing per owner: the order within an owner. */
114510
+ seq: number().int().positive(),
114511
+ /** Ids of any owner: the order across owners. */
114512
+ dependsOn: array(MigrationIdSchema),
114513
+ store: MigrationStoreSchema,
114514
+ scope: MigrationScopeSchema,
114515
+ onFailure: MigrationOnFailureSchema,
114516
+ bodyKind: MigrationBodyKindSchema,
114517
+ description: string().min(1),
114518
+ /** The marked field the migration's spec must assert survived. */
114519
+ fixtureMark: string().min(1)
114520
+ }).refine((m) => m.id.startsWith(`${m.owner}/`), {
114521
+ message: "the id must start with \"<owner>/\"",
114522
+ path: ["id"]
114523
+ });
114524
+ /** A cycle in the dependency graph, named by the ids on it. */
114525
+ var MigrationCycleError = class extends Error {
114526
+ ids;
114527
+ constructor(ids) {
114528
+ super(`migration dependsOn cycle: ${ids.join(" -> ")}`);
114529
+ this.ids = ids;
114530
+ this.name = "MigrationCycleError";
114531
+ }
114532
+ };
114533
+ /**
114534
+ * Topological order over `dependsOn` plus the implicit edge "a lower `seq` of an
114535
+ * owner runs first". Ties are broken by `(owner, seq)`. Throws
114536
+ * {@link MigrationCycleError} on a cycle. A `dependsOn` id that is not in the list
114537
+ * is ignored here — the catalogue schema is what refuses it.
114538
+ */
114539
+ function orderMigrations(items) {
114540
+ const byId = new Map(items.map((item) => [item.id, item]));
114541
+ const deps = /* @__PURE__ */ new Map();
114542
+ const ownerSorted = /* @__PURE__ */ new Map();
114543
+ for (const item of items) {
114544
+ const list = ownerSorted.get(item.owner) ?? [];
114545
+ list.push(item);
114546
+ ownerSorted.set(item.owner, list);
114547
+ }
114548
+ for (const item of items) deps.set(item.id, new Set(item.dependsOn.filter((id) => byId.has(id))));
114549
+ for (const list of ownerSorted.values()) {
114550
+ list.sort((a, b) => a.seq - b.seq);
114551
+ for (let i = 1; i < list.length; i++) {
114552
+ const previous = list[i - 1];
114553
+ const current = list[i];
114554
+ if (previous !== void 0 && current !== void 0) deps.get(current.id)?.add(previous.id);
114555
+ }
114556
+ }
114557
+ const tie = (a, b) => a.owner < b.owner ? -1 : a.owner > b.owner ? 1 : a.seq - b.seq;
114558
+ const done = /* @__PURE__ */ new Set();
114559
+ const ordered = [];
114560
+ let remaining = [...items];
114561
+ while (remaining.length > 0) {
114562
+ const ready = remaining.filter((item) => [...deps.get(item.id) ?? []].every((dep) => done.has(dep)));
114563
+ if (ready.length === 0) throw new MigrationCycleError(remaining.map((item) => item.id).sort());
114564
+ ready.sort(tie);
114565
+ const next = ready[0];
114566
+ if (next === void 0) break;
114567
+ ordered.push(next);
114568
+ done.add(next.id);
114569
+ remaining = remaining.filter((item) => item.id !== next.id);
114570
+ }
114571
+ return ordered;
114572
+ }
114573
+ /** Every way a list of migrations (in declaration order) is not a catalogue. */
114574
+ function catalogueIssues(items) {
114575
+ const issues = [];
114576
+ const seen = /* @__PURE__ */ new Set();
114577
+ const known = new Set(items.map((item) => item.id));
114578
+ const lastSeq = /* @__PURE__ */ new Map();
114579
+ const seqUsed = /* @__PURE__ */ new Map();
114580
+ for (const item of items) {
114581
+ if (seen.has(item.id)) issues.push({
114582
+ code: "duplicate-id",
114583
+ ids: [item.id],
114584
+ message: `duplicate id ${item.id}`
114585
+ });
114586
+ seen.add(item.id);
114587
+ const used = seqUsed.get(item.owner) ?? /* @__PURE__ */ new Set();
114588
+ if (used.has(item.seq)) issues.push({
114589
+ code: "duplicate-seq",
114590
+ ids: [item.id],
114591
+ message: `owner ${item.owner} has two migrations with seq ${item.seq}`
114592
+ });
114593
+ else if (item.seq < (lastSeq.get(item.owner) ?? 0)) issues.push({
114594
+ code: "seq-order",
114595
+ ids: [item.id],
114596
+ message: `seq ${item.seq} of ${item.id} does not increase in declaration order (owner ${item.owner})`
114597
+ });
114598
+ used.add(item.seq);
114599
+ seqUsed.set(item.owner, used);
114600
+ lastSeq.set(item.owner, Math.max(item.seq, lastSeq.get(item.owner) ?? 0));
114601
+ for (const dep of item.dependsOn) if (!known.has(dep)) issues.push({
114602
+ code: "unknown-dependency",
114603
+ ids: [item.id, dep],
114604
+ message: `${item.id} dependsOn unknown id ${dep}`
114605
+ });
114606
+ }
114607
+ try {
114608
+ orderMigrations(items);
114609
+ } catch (err) {
114610
+ if (err instanceof MigrationCycleError) issues.push({
114611
+ code: "cycle",
114612
+ ids: err.ids,
114613
+ message: err.message
114614
+ });
114615
+ else throw err;
114616
+ }
114617
+ return issues;
114618
+ }
114619
+ array(MigrationMetaSchema).superRefine((items, ctx) => {
114620
+ for (const issue of catalogueIssues(items)) ctx.addIssue({
114621
+ code: "custom",
114622
+ message: `${issue.code}: ${issue.message}`
114623
+ });
114624
+ });
114625
+ /**
114626
+ * The canonical JSON (`preconditionOf`) of the value a plan read; the apply step
114627
+ * re-reads, and a plan whose read no longer matches is refused (the migration is
114628
+ * `failed`, nothing it edited is kept).
114629
+ */
114630
+ var PreconditionSchema = string().min(1).optional();
114631
+ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
114632
+ object({
114633
+ op: literal("setKeys"),
114634
+ scope: string().min(1),
114635
+ values: record(string(), unknown()),
114636
+ precondition: PreconditionSchema
114637
+ }),
114638
+ object({
114639
+ op: literal("deleteKeys"),
114640
+ scope: string().min(1),
114641
+ keys: array(string().min(1)).min(1),
114642
+ precondition: PreconditionSchema
114643
+ }),
114644
+ object({
114645
+ op: literal("putRow"),
114646
+ table: string().min(1),
114647
+ key: record(string(), unknown()),
114648
+ row: record(string(), unknown()),
114649
+ precondition: PreconditionSchema
114650
+ }),
114651
+ object({
114652
+ op: literal("deleteRow"),
114653
+ table: string().min(1),
114654
+ key: record(string(), unknown()),
114655
+ precondition: PreconditionSchema
114656
+ }),
114657
+ object({
114658
+ op: literal("upsertRecord"),
114659
+ kind: string().min(1),
114660
+ recordKey: string().min(1),
114661
+ value: unknown(),
114662
+ precondition: PreconditionSchema
114663
+ }),
114664
+ object({
114665
+ op: literal("deleteRecord"),
114666
+ kind: string().min(1),
114667
+ recordKey: string().min(1),
114668
+ precondition: PreconditionSchema
114669
+ })
114670
+ ])) });
114671
+ /**
114672
+ * What a `procedure` body reports. `already-done` means the rerun observed the
114673
+ * work finished (a crash between the work and the ledger row, or a manual fix)
114674
+ * and did none of it again; the ledger records `applied` either way.
114675
+ */
114676
+ var MigrationProcedureResultSchema = object({
114677
+ outcome: _enum(["done", "already-done"]),
114678
+ summary: record(string(), unknown())
114679
+ });
114680
+ /** What an owner hands the engine when one of its migrations ran. */
114681
+ var MigrationOwnerResultSchema = discriminatedUnion("kind", [
114682
+ object({
114683
+ kind: literal("plan"),
114684
+ plan: MigrationPlanSchema
114685
+ }),
114686
+ object({
114687
+ kind: literal("procedure"),
114688
+ result: MigrationProcedureResultSchema
114689
+ }),
114690
+ object({
114691
+ kind: literal("failed"),
114692
+ error: string().min(1)
114693
+ })
114694
+ ]);
114695
+ /**
114696
+ * `held`: a restore of a pre-migration snapshot undid this id; it does not rerun
114697
+ * until the operator retries or a new code version ships.
114698
+ * `skipped-newer`: the database knows an id this code does not.
114699
+ */
114700
+ var MigrationStatusSchema = _enum([
114701
+ "applied",
114702
+ "failed",
114703
+ "blocked",
114704
+ "running",
114705
+ "held",
114706
+ "skipped-newer"
114707
+ ]);
114708
+ var MigrationLedgerEntrySchema = object({
114709
+ id: string(),
114710
+ database: DataStoreDatabaseSchema,
114711
+ owner: string(),
114712
+ level: MigrationLevelSchema,
114713
+ seq: number().int().nonnegative(),
114714
+ status: MigrationStatusSchema,
114715
+ attempts: number().int().nonnegative(),
114716
+ appliedAt: number().nullable(),
114717
+ codeVersion: string().nullable(),
114718
+ /** What the apply changed: rows per table, as the engine counted them. */
114719
+ planSummary: record(string(), unknown()).nullable(),
114720
+ progress: record(string(), unknown()).nullable(),
114721
+ error: string().nullable(),
114722
+ /** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
114723
+ reason: string().nullable(),
114724
+ nodeId: string().nullable(),
114725
+ bootId: string().nullable()
114726
+ });
114727
+ /** A catalogued migration that is not applied, with where it stands. */
114728
+ var MigrationPendingEntrySchema = object({
114729
+ meta: MigrationMetaSchema,
114730
+ database: DataStoreDatabaseSchema,
114731
+ state: _enum([
114732
+ "pending",
114733
+ "failed",
114734
+ "blocked",
114735
+ "held"
114736
+ ]),
114737
+ reason: string().nullable()
114738
+ });
114739
+ /** Rows per table before and after a dry run, and what it would have changed. */
114740
+ var MigrationDryRunResultSchema = object({
114741
+ id: string(),
114742
+ ok: boolean(),
114743
+ error: string().nullable(),
114744
+ rowsChanged: number().int().nonnegative(),
114745
+ tables: array(object({
114746
+ table: string(),
114747
+ before: number().int().nonnegative(),
114748
+ after: number().int().nonnegative()
114749
+ }))
114750
+ });
114751
+ /** One of an owner's migrations, as the engine tells the owner to treat it this boot. */
114752
+ var MigrationOwnerStepSchema = object({
114753
+ id: MigrationIdSchema,
114754
+ action: _enum(["run", "skip"]),
114755
+ /** The ledger state the engine found: `pending` has no row yet. */
114756
+ state: union([literal("pending"), MigrationStatusSchema])
114757
+ });
114758
+ /**
113956
114759
  * The trainer contract — what the hub hands the cloud trainer and what it
113957
114760
  * gets back (cloud training, D805+).
113958
114761
  *
@@ -117905,6 +118708,41 @@ var ACTUATOR_CAP_VOCABULARY = {
117905
118708
  icon: "droplets"
117906
118709
  }]
117907
118710
  },
118711
+ "power-source": {
118712
+ scope: "device",
118713
+ noun: t("Power source", "Alimentazione"),
118714
+ actions: [
118715
+ {
118716
+ id: "powerSource.on",
118717
+ method: "powerOn",
118718
+ label: t("Power on", "Dai corrente"),
118719
+ phrase: t("Power on {device}", "Dai corrente a {device}"),
118720
+ icon: "power"
118721
+ },
118722
+ {
118723
+ id: "powerSource.off",
118724
+ method: "powerOff",
118725
+ label: t("Power off", "Togli corrente"),
118726
+ phrase: t("Cut the power of {device}", "Togli la corrente a {device}"),
118727
+ icon: "power-off"
118728
+ },
118729
+ {
118730
+ id: "powerSource.forceRestart",
118731
+ method: "forceRestart",
118732
+ label: t("Force restart", "Riavvio forzato"),
118733
+ phrase: t("Force restart {device} by cutting its power", "Riavvia {device} togliendo la corrente"),
118734
+ icon: "refresh-cw"
118735
+ }
118736
+ ],
118737
+ state: [{
118738
+ kind: "boolean",
118739
+ field: "switchOn",
118740
+ label: t("Power switch on", "Presa accesa"),
118741
+ whenTrue: t("When the power of {device} comes on", "Quando arriva la corrente a {device}"),
118742
+ whenFalse: t("When the power of {device} is cut", "Quando manca la corrente a {device}"),
118743
+ icon: "power"
118744
+ }]
118745
+ },
117908
118746
  "lock-control": {
117909
118747
  scope: "device",
117910
118748
  noun: t("Lock", "Serratura"),
@@ -118380,7 +119218,7 @@ var ACTUATOR_CAP_VOCABULARY = {
118380
119218
  method: "setDirection",
118381
119219
  label: t("Spin forward", "Rotazione avanti"),
118382
119220
  phrase: t("Spin {device} forward", "Fai girare {device} in avanti"),
118383
- icon: "rotate-cw",
119221
+ icon: "refresh-cw",
118384
119222
  effect: {
118385
119223
  field: "direction",
118386
119224
  value: "forward"
@@ -121458,6 +122296,7 @@ var SYSTEM_CAP_VOCABULARY = {
121458
122296
  "login-method": system(IDENTITY),
121459
122297
  "mesh-network": system(NETWORK),
121460
122298
  "metrics-provider": system(CLUSTER),
122299
+ migrations: system(INTERNAL),
121461
122300
  "model-convert": system(ML),
121462
122301
  "model-distributor": system(ML),
121463
122302
  "mqtt-broker": system(NETWORK),
@@ -143801,7 +144640,7 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
143801
144640
  * rebuilds without manual reload.
143802
144641
  *
143803
144642
  * The hub-local builtin `addon-pages-aggregator` (see
143804
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
144643
+ * `@camstack/server/system/builtins/addon-pages-aggregator`) registers the
143805
144644
  * provider. Splitting the public aggregator from the raw collection
143806
144645
  * keeps both ends in codegen — there's no hand-written
143807
144646
  * `addon-pages.router.ts` wrapper anymore.
@@ -144101,7 +144940,7 @@ method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" });
144101
144940
  * manual reload — same scheme used by `addon-pages`.
144102
144941
  *
144103
144942
  * The hub-local builtin `addon-widgets-aggregator` (see
144104
- * `@camstack/system/builtins/addon-widgets-aggregator`) registers the
144943
+ * `@camstack/server/system/builtins/addon-widgets-aggregator`) registers the
144105
144944
  * provider. Splitting the public aggregator from the raw collection
144106
144945
  * keeps both ends in codegen — there's no hand-written wrapper.
144107
144946
  */
@@ -144462,329 +145301,6 @@ method(object({
144462
145301
  password: string()
144463
145302
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
144464
145303
  /**
144465
- * Query filter for settings-store collections.
144466
- */
144467
- var QueryFilterSchema = object({
144468
- where: record(string(), unknown()).optional(),
144469
- whereIn: record(string(), array(unknown())).optional(),
144470
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
144471
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
144472
- whereNot: record(string(), unknown()).optional(),
144473
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
144474
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
144475
- whereNotIn: record(string(), array(unknown())).optional(),
144476
- orderBy: object({
144477
- field: string(),
144478
- direction: _enum(["asc", "desc"])
144479
- }).optional(),
144480
- limit: number().optional(),
144481
- offset: number().optional()
144482
- });
144483
- /**
144484
- * The predicate half of a filter, for BULK MUTATIONS.
144485
- *
144486
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
144487
- * meaning for a statement that rewrites a set, and accepting them would invite
144488
- * a caller to believe `limit` bounds the damage. Every field is optional here
144489
- * only so the shape stays composable — the implementation REJECTS a filter
144490
- * that compiles to no predicate, because that is the whole collection.
144491
- */
144492
- var MutationFilterSchema = object({
144493
- where: record(string(), unknown()).optional(),
144494
- whereIn: record(string(), array(unknown())).optional(),
144495
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
144496
- whereNot: record(string(), unknown()).optional(),
144497
- whereNotIn: record(string(), array(unknown())).optional()
144498
- });
144499
- /**
144500
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
144501
- *
144502
- * `as` names the slot in the result, so the SAME column may be asked twice with
144503
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
144504
- * a `Record<column, op>` shape could not express.
144505
- */
144506
- var AggregateFieldSchema = object({
144507
- /** Result key. */
144508
- as: string().min(1),
144509
- /** Column to aggregate. Must be a real column of a declared collection. */
144510
- field: string().min(1),
144511
- op: _enum([
144512
- "sum",
144513
- "min",
144514
- "max"
144515
- ])
144516
- });
144517
- /**
144518
- * `COUNT(*)` plus one number per requested field.
144519
- *
144520
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
144521
- * that really is 0 are different facts, and an accounting caller that renders
144522
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
144523
- */
144524
- var AggregateResultSchema = object({
144525
- count: number().int(),
144526
- values: record(string(), number().nullable())
144527
- });
144528
- /** A single stored record: `{ id, data }`. */
144529
- var SettingsRecordSchema = object({
144530
- id: string(),
144531
- data: record(string(), unknown())
144532
- });
144533
- /**
144534
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
144535
- * optional.
144536
- *
144537
- * A separate schema rather than loosening the shared one: every other method
144538
- * on this cap addresses a row BY its id, and making that field optional
144539
- * everywhere would turn a forgotten key into a silently generated one on
144540
- * `update` and `delete` as well.
144541
- */
144542
- var BulkRecordSchema = object({
144543
- id: string().optional(),
144544
- data: record(string(), unknown())
144545
- });
144546
- /**
144547
- * The database FILES one `sqlite-settings` engine process owns (D851).
144548
- * Closed on purpose: a file is a thing the engine opens, backs up and gives a
144549
- * serving thread, so a new one is a code change, never a string a caller
144550
- * invents. `settings` is `camstack.db` and the default for every collection
144551
- * that names no file.
144552
- */
144553
- var DataStoreDatabaseSchema = _enum([
144554
- "settings",
144555
- "events",
144556
- "history"
144557
- ]);
144558
- DataStoreDatabaseSchema.options;
144559
- /**
144560
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
144561
- * The three that a "replace this set, bump that revision" write needs, plus
144562
- * the batched upsert a rollup writer needs.
144563
- */
144564
- var TransactOpSchema = discriminatedUnion("op", [
144565
- object({
144566
- op: literal("deleteWhere"),
144567
- collection: string(),
144568
- filter: MutationFilterSchema
144569
- }),
144570
- object({
144571
- op: literal("insertMany"),
144572
- collection: string(),
144573
- records: array(BulkRecordSchema).readonly()
144574
- }),
144575
- object({
144576
- op: literal("update"),
144577
- collection: string(),
144578
- id: string(),
144579
- data: record(string(), unknown())
144580
- }),
144581
- object({
144582
- op: literal("upsertMany"),
144583
- collection: string(),
144584
- conflict: array(string()).readonly(),
144585
- records: array(BulkRecordSchema).readonly()
144586
- })
144587
- ]);
144588
- /**
144589
- * The precondition of a transaction: the row `key` of `collection` must hold
144590
- * `equals` in `column`, read INSIDE the transaction.
144591
- */
144592
- var TransactGuardSchema = object({
144593
- collection: string(),
144594
- key: string(),
144595
- column: string(),
144596
- equals: unknown()
144597
- });
144598
- /**
144599
- * Column declaration for a structured (SQL-backed) collection.
144600
- *
144601
- * Logical types — the backend translates each to the matching SQLite
144602
- * storage class and handles per-type marshaling:
144603
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
144604
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
144605
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
144606
- */
144607
- var CollectionColumnSchema = object({
144608
- name: string(),
144609
- type: _enum([
144610
- "TEXT",
144611
- "INTEGER",
144612
- "REAL",
144613
- "JSON",
144614
- "BOOLEAN"
144615
- ]),
144616
- primaryKey: boolean().optional(),
144617
- notNull: boolean().optional(),
144618
- unique: boolean().optional(),
144619
- /**
144620
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
144621
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
144622
- * collection surface simply cannot express their existing tables — which is
144623
- * why they were still on the uncapped `table*` API.
144624
- */
144625
- defaultValue: union([
144626
- string(),
144627
- number(),
144628
- boolean()
144629
- ]).optional()
144630
- });
144631
- var CollectionIndexSchema = object({
144632
- name: string(),
144633
- columns: array(string()).readonly(),
144634
- unique: boolean().optional()
144635
- });
144636
- /**
144637
- * The input of `declareCollection` — ONE schema for the door and for the
144638
- * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
144639
- * the payload untouched).
144640
- *
144641
- * - `database` — the file the collection lives in (D851). Absent means
144642
- * `settings`. A declaration naming a file other than the one the table is
144643
- * already in is REFUSED by the engine, never moved.
144644
- * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
144645
- * as `primaryKey: true` on that column. A composite-keyed collection has no
144646
- * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
144647
- * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
144648
- * address one row by id are refused.
144649
- * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
144650
- */
144651
- var DeclareCollectionInputSchema = object({
144652
- namespace: string().optional(),
144653
- collection: string(),
144654
- columns: array(CollectionColumnSchema).readonly(),
144655
- indexes: array(CollectionIndexSchema).readonly().optional(),
144656
- database: DataStoreDatabaseSchema.optional(),
144657
- primaryKey: array(string()).readonly().optional(),
144658
- withoutRowid: boolean().optional()
144659
- });
144660
- method(object({
144661
- namespace: string().optional(),
144662
- collection: string(),
144663
- key: string()
144664
- }), unknown(), { auth: "admin" }), method(object({
144665
- namespace: string().optional(),
144666
- collection: string(),
144667
- key: string(),
144668
- value: unknown()
144669
- }), _void(), {
144670
- kind: "mutation",
144671
- auth: "admin"
144672
- }), method(object({
144673
- namespace: string().optional(),
144674
- collection: string(),
144675
- filter: QueryFilterSchema.optional(),
144676
- columns: array(string()).readonly().optional()
144677
- }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
144678
- namespace: string().optional(),
144679
- collection: string(),
144680
- /** Declared columns identifying the slot; must be covered by a UNIQUE
144681
- * index on the collection. Never empty. */
144682
- conflict: array(string()).readonly(),
144683
- record: BulkRecordSchema
144684
- }), object({
144685
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
144686
- * row's own id on an update. */
144687
- id: union([string(), number()]) }), {
144688
- kind: "mutation",
144689
- auth: "admin"
144690
- }), method(object({
144691
- namespace: string().optional(),
144692
- collection: string(),
144693
- conflict: array(string()).readonly(),
144694
- records: array(BulkRecordSchema).readonly()
144695
- }), object({ upserted: number().int() }), {
144696
- kind: "mutation",
144697
- auth: "admin"
144698
- }), method(object({
144699
- namespace: string().optional(),
144700
- collection: string(),
144701
- record: object({
144702
- id: string().optional(),
144703
- data: record(string(), unknown())
144704
- })
144705
- }), object({
144706
- /**
144707
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
144708
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
144709
- * primary key — which is the only place an auto key is knowable.
144710
- */
144711
- id: union([string(), number()]) }), {
144712
- kind: "mutation",
144713
- auth: "admin"
144714
- }), method(object({
144715
- namespace: string().optional(),
144716
- collection: string(),
144717
- records: array(BulkRecordSchema).readonly()
144718
- }), object({ inserted: number().int() }), {
144719
- kind: "mutation",
144720
- auth: "admin"
144721
- }), method(object({
144722
- namespace: string().optional(),
144723
- guard: TransactGuardSchema.optional(),
144724
- ops: array(TransactOpSchema).readonly()
144725
- }), object({
144726
- applied: boolean(),
144727
- current: unknown().optional()
144728
- }), {
144729
- kind: "mutation",
144730
- auth: "admin"
144731
- }), method(object({
144732
- namespace: string().optional(),
144733
- collection: string(),
144734
- id: string(),
144735
- data: record(string(), unknown())
144736
- }), _void(), {
144737
- kind: "mutation",
144738
- auth: "admin"
144739
- }), method(object({
144740
- namespace: string().optional(),
144741
- collection: string(),
144742
- key: string()
144743
- }), _void(), {
144744
- kind: "mutation",
144745
- auth: "admin"
144746
- }), method(object({
144747
- namespace: string().optional(),
144748
- collection: string(),
144749
- filter: MutationFilterSchema
144750
- }), object({ deleted: number().int() }), {
144751
- kind: "mutation",
144752
- auth: "admin"
144753
- }), method(object({
144754
- namespace: string().optional(),
144755
- collection: string(),
144756
- filter: MutationFilterSchema,
144757
- data: record(string(), unknown())
144758
- }), object({ updated: number().int() }), {
144759
- kind: "mutation",
144760
- auth: "admin"
144761
- }), method(object({
144762
- namespace: string().optional(),
144763
- collection: string(),
144764
- filter: QueryFilterSchema.optional()
144765
- }), number(), { auth: "admin" }), method(object({
144766
- namespace: string().optional(),
144767
- collection: string(),
144768
- fields: array(AggregateFieldSchema).readonly(),
144769
- filter: QueryFilterSchema.optional()
144770
- }), AggregateResultSchema, { auth: "admin" }), method(object({
144771
- namespace: string().optional(),
144772
- collection: string(),
144773
- field: string(),
144774
- bucketSize: number().int().positive(),
144775
- origin: number().int(),
144776
- filter: QueryFilterSchema.optional()
144777
- }), array(object({
144778
- bucket: number().int(),
144779
- count: number().int()
144780
- })).readonly(), { auth: "admin" }), method(object({
144781
- namespace: string().optional(),
144782
- collection: string()
144783
- }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
144784
- kind: "mutation",
144785
- auth: "admin"
144786
- });
144787
- /**
144788
145304
  * How long one snapshot may take end to end: the pinned copy of the whole
144789
145305
  * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
144790
145306
  * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
@@ -146698,7 +147214,7 @@ method(object({
146698
147214
  kind: "mutation",
146699
147215
  auth: "admin"
146700
147216
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
146701
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
147217
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
146702
147218
  var NUMBER = {
146703
147219
  kind: "fixed",
146704
147220
  result: ["number"]
@@ -147709,6 +148225,57 @@ id: union([string(), number()]) }), {
147709
148225
  kind: "mutation",
147710
148226
  auth: "admin"
147711
148227
  });
148228
+ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
148229
+ id: string().min(1),
148230
+ error: string().min(1)
148231
+ }), MigrationLedgerEntrySchema, {
148232
+ kind: "mutation",
148233
+ auth: "admin"
148234
+ }), method(object({ id: string().min(1) }), object({ entries: array(MigrationLedgerEntrySchema) }), {
148235
+ kind: "mutation",
148236
+ auth: "admin"
148237
+ }), method(object({
148238
+ database: DataStoreDatabaseSchema,
148239
+ snapshotId: string().min(1)
148240
+ }), object({
148241
+ scheduled: boolean(),
148242
+ held: array(string()),
148243
+ restartRequired: boolean()
148244
+ }), {
148245
+ kind: "mutation",
148246
+ auth: "admin"
148247
+ }), method(object({
148248
+ owner: string().min(1),
148249
+ metas: array(MigrationMetaSchema)
148250
+ }), object({
148251
+ bootId: string(),
148252
+ snapshot: _enum([
148253
+ "none",
148254
+ "written",
148255
+ "refused"
148256
+ ]),
148257
+ steps: array(MigrationOwnerStepSchema)
148258
+ }), {
148259
+ kind: "mutation",
148260
+ auth: "admin"
148261
+ }), method(object({ id: string().min(1) }), object({
148262
+ proceed: boolean(),
148263
+ state: union([literal("pending"), MigrationStatusSchema]),
148264
+ reason: string().nullable()
148265
+ }), {
148266
+ kind: "mutation",
148267
+ auth: "admin"
148268
+ }), method(object({
148269
+ id: string().min(1),
148270
+ result: MigrationOwnerResultSchema
148271
+ }), object({
148272
+ status: MigrationStatusSchema,
148273
+ error: string().nullable(),
148274
+ rowsChanged: number().int().nonnegative()
148275
+ }), {
148276
+ kind: "mutation",
148277
+ auth: "admin"
148278
+ });
147712
148279
  /**
147713
148280
  * records - an addon's own facts and documents (D881): ledgers, audit rows,
147714
148281
  * config documents, mirrors. The third storage API next to `ctx.settings`
@@ -162613,7 +163180,18 @@ var ServerUpdateActionResultSchema = object({
162613
163180
  * store (D877), present when the pre-flight refused the update
162614
163181
  * (`reason: 'addon-would-refuse'`). Optional for version skew.
162615
163182
  */
162616
- blockers: array(UpdateBlockerSchema).optional()
163183
+ blockers: array(UpdateBlockerSchema).optional(),
163184
+ /**
163185
+ * The minimums the target server declares and this node does not meet
163186
+ * (D989), present when the update was refused with
163187
+ * `reason: 'requirements-unmet'`. Each names the versions on both sides.
163188
+ */
163189
+ unmet: array(UnmetRequirementSchema).optional(),
163190
+ /**
163191
+ * The addons this node has below the minimum the staged server declares —
163192
+ * the ones to update WITH the server. Present on an accepted update.
163193
+ */
163194
+ requiredAddonUpdates: array(RequiredAddonUpdateSchema).optional()
162617
163195
  });
162618
163196
  var LaunchOverridesSchema = object({
162619
163197
  /** The persisted heap ceiling in MB, or `null` when none is set. */
@@ -162726,7 +163304,7 @@ var SnapshotImageSchema = object({
162726
163304
  * straight from the camera (HTTP snapshot URL, ONVIF action, etc.).
162727
163305
  *
162728
163306
  * - **Wrapper** providers (kind:'wrapper'): register as a system
162729
- * provider (SnapshotAddon in `@camstack/system/builtins/snapshot`). The
163307
+ * provider (SnapshotAddon in `@camstack/server/system/builtins/snapshot`). The
162730
163308
  * wrapper owns the cache and invokes the native via
162731
163309
  * `ctx.getNativeProvider(snapshotCapability, deviceId)` on miss.
162732
163310
  *
@@ -165679,7 +166257,9 @@ var PackageUpdateSchema = object({
165679
166257
  latestVersion: string(),
165680
166258
  category: _enum(["addon", "core"]),
165681
166259
  requiresRestart: boolean(),
165682
- isSystem: boolean()
166260
+ isSystem: boolean(),
166261
+ /** Held: what the newer version needs and this node does not meet (D989). */
166262
+ blockedBy: array(UnmetRequirementSchema).readonly().optional()
165683
166263
  });
165684
166264
  var PackageVersionInfoSchema = object({
165685
166265
  version: string(),
@@ -167135,19 +167715,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
167135
167715
  reason: _enum(["unknown-template", "not-a-media-player"]),
167136
167716
  detail: string()
167137
167717
  })]);
167138
- var deviceInput = object({ deviceId: number().int().nonnegative() });
167718
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
167139
167719
  DeviceType.MediaPlayer;
167140
- method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
167720
+ method(deviceInput$1, RemoteConfigSchema, { auth: "admin" }), method(deviceInput$1.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
167141
167721
  kind: "mutation",
167142
167722
  auth: "admin"
167143
- }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
167723
+ }), method(deviceInput$1, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput$1.extend({
167144
167724
  role: RemoteRoleSchema,
167145
167725
  /** Sends in a row (volume steps). */
167146
167726
  repeat: number().int().min(1).max(50).default(1)
167147
167727
  }), RemoteSendResultSchema, {
167148
167728
  kind: "mutation",
167149
167729
  auth: "protected"
167150
- }), method(deviceInput.extend({
167730
+ }), method(deviceInput$1.extend({
167151
167731
  command: RemoteMediaCommandSchema,
167152
167732
  muted: boolean().optional(),
167153
167733
  source: string().optional(),
@@ -170962,7 +171542,7 @@ var lawnMowerControlCapability = {
170962
171542
  * clients — they reverse-connect to the hub. Exposing their interfaces
170963
171543
  * via the same surface would leak internal topology with no upside.
170964
171544
  *
170965
- * Implementation in `@camstack/system/builtins/local-network/`.
171545
+ * Implementation in `@camstack/server/system/builtins/local-network/`.
170966
171546
  */
170967
171547
  /** Coarse classification derived from the interface name + IP range. */
170968
171548
  var InterfaceKindEnum = _enum([
@@ -171459,6 +172039,117 @@ var lockControlCapability = {
171459
172039
  * whether persisting is worth a SQLite commit. */
171460
172040
  volatileStateFields: ["lastChangedAt"]
171461
172041
  };
172042
+ var PowerSourceRefusalReasonSchema = _enum([
172043
+ "no-power-source",
172044
+ "switch-missing",
172045
+ "switch-offline",
172046
+ "switch-unknown",
172047
+ "restart-in-progress",
172048
+ "switch-call-failed"
172049
+ ]);
172050
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
172051
+ accepted: literal(false),
172052
+ reason: PowerSourceRefusalReasonSchema,
172053
+ /** One line for the operator, naming the switch. */
172054
+ message: string()
172055
+ })]);
172056
+ var PowerSourceInfoSchema = object({
172057
+ /** False when the device has no power source linked; the rest is then empty. */
172058
+ linked: boolean(),
172059
+ switchDeviceId: number().nullable(),
172060
+ switchName: string().nullable(),
172061
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
172062
+ switchOn: boolean().nullable(),
172063
+ switchOnline: boolean().nullable(),
172064
+ restartInProgress: boolean(),
172065
+ offSeconds: number().int()
172066
+ });
172067
+ /** The composed slice: the switch state the link mirrors, plus the stored off time. */
172068
+ var PowerSourceStateSchema = object({
172069
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
172070
+ switchOn: boolean().nullable(),
172071
+ offSeconds: number().int(),
172072
+ lastFetchedAt: number()
172073
+ });
172074
+ var POWER_SOURCE_DEVICE_TYPES = [
172075
+ DeviceType.Gateway,
172076
+ DeviceType.Hub,
172077
+ DeviceType.Camera,
172078
+ DeviceType.Sensor,
172079
+ DeviceType.MediaPlayer,
172080
+ DeviceType.Vacuum,
172081
+ DeviceType.Generic,
172082
+ DeviceType.Washer,
172083
+ DeviceType.Dryer,
172084
+ DeviceType.Dishwasher,
172085
+ DeviceType.PetFountain,
172086
+ DeviceType.Fan,
172087
+ DeviceType.Humidifier,
172088
+ DeviceType.Climate,
172089
+ DeviceType.WaterHeater
172090
+ ];
172091
+ var powerSourceCustomize = {
172092
+ version: 1,
172093
+ targets: [{
172094
+ id: "switch",
172095
+ cardinality: "scalar",
172096
+ vocabulary: {
172097
+ cap: "power-source",
172098
+ stateField: "switchOn"
172099
+ },
172100
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
172101
+ apply: "add",
172102
+ path: "switchOn",
172103
+ sources: [{
172104
+ kind: "switch",
172105
+ transforms: [{ kind: "identity" }]
172106
+ }],
172107
+ companions: [{
172108
+ kind: "constant",
172109
+ path: "offSeconds",
172110
+ value: 10,
172111
+ when: "add"
172112
+ }, {
172113
+ kind: "clock",
172114
+ path: "lastFetchedAt",
172115
+ when: "always"
172116
+ }],
172117
+ sourceReport: { status: "assignment-status" }
172118
+ }]
172119
+ };
172120
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
172121
+ var powerSourceCapability = {
172122
+ name: "power-source",
172123
+ scope: "device",
172124
+ mode: "singleton",
172125
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
172126
+ methods: {
172127
+ getStatus: systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }),
172128
+ powerOn: systemMethod(deviceInput, PowerSourceActionResultSchema, {
172129
+ kind: "mutation",
172130
+ auth: "admin"
172131
+ }),
172132
+ powerOff: systemMethod(deviceInput, PowerSourceActionResultSchema, {
172133
+ kind: "mutation",
172134
+ auth: "admin"
172135
+ }),
172136
+ forceRestart: systemMethod(deviceInput, PowerSourceActionResultSchema, {
172137
+ kind: "mutation",
172138
+ auth: "admin"
172139
+ })
172140
+ },
172141
+ runtimeState: PowerSourceStateSchema,
172142
+ customize: powerSourceCustomize,
172143
+ /**
172144
+ * Runtime-state durability: **session** — mirrored from the linked switch on
172145
+ * its next report; a restored value would outlive the reading it names.
172146
+ *
172147
+ * See `RuntimeStateDurability`. Enforced by
172148
+ * `scripts/check-runtime-state-durability.ts`.
172149
+ */
172150
+ durability: "session",
172151
+ history: "none"
172152
+ };
171462
172153
  /**
171463
172154
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
171464
172155
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -172759,7 +173450,7 @@ onStatusChanged: { data: object({
172759
173450
  * jitter, and observed/peak bandwidth per device + per client.
172760
173451
  *
172761
173452
  * Implementation lives in the server's `NetworkQualityService` (thin
172762
- * wrapper over the shared `NetworkQualityTracker` from `@camstack/system`).
173453
+ * wrapper over the shared `NetworkQualityTracker` from `@camstack/server/system`).
172763
173454
  * The provider is registered from `trpc.router.ts` against the existing
172764
173455
  * service instance — no addon owns this state.
172765
173456
  *
@@ -175068,7 +175759,7 @@ var ptzAutotrackCapability = {
175068
175759
  * - **Native**: the camera's own firmware autotrack, registered per device by
175069
175760
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
175070
175761
  * model-dependent).
175071
- * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
175762
+ * - **Wrapper**: the emulated implementation (`@camstack/server/system`'s `autotrack` builtin),
175072
175763
  * which follows a subject with `ptz.move` pulses driven by the detection
175073
175764
  * pipeline. `defaultActive: false` — the operator binds it per camera in
175074
175765
  * the bindings UI.
@@ -180918,6 +181609,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
180918
181609
  petFeeder: petFeederCapability,
180919
181610
  petFountain: petFountainCapability,
180920
181611
  powerMeter: powerMeterCapability,
181612
+ powerSource: powerSourceCapability,
180921
181613
  presence: presenceCapability,
180922
181614
  pressureSensor: pressureSensorCapability,
180923
181615
  privacyMask: privacyMaskCapability,
@@ -186682,6 +187374,69 @@ Object.freeze({
186682
187374
  access: "view",
186683
187375
  role: "admin"
186684
187376
  },
187377
+ "migrations.declareOwner": {
187378
+ capName: "migrations",
187379
+ capScope: "system",
187380
+ addonId: null,
187381
+ access: "create",
187382
+ role: "admin"
187383
+ },
187384
+ "migrations.dryRun": {
187385
+ capName: "migrations",
187386
+ capScope: "system",
187387
+ addonId: null,
187388
+ access: "view",
187389
+ role: "admin"
187390
+ },
187391
+ "migrations.finishMigration": {
187392
+ capName: "migrations",
187393
+ capScope: "system",
187394
+ addonId: null,
187395
+ access: "create",
187396
+ role: "admin"
187397
+ },
187398
+ "migrations.listLedger": {
187399
+ capName: "migrations",
187400
+ capScope: "system",
187401
+ addonId: null,
187402
+ access: "view",
187403
+ role: "admin"
187404
+ },
187405
+ "migrations.listPending": {
187406
+ capName: "migrations",
187407
+ capScope: "system",
187408
+ addonId: null,
187409
+ access: "view",
187410
+ role: "admin"
187411
+ },
187412
+ "migrations.markFailed": {
187413
+ capName: "migrations",
187414
+ capScope: "system",
187415
+ addonId: null,
187416
+ access: "create",
187417
+ role: "admin"
187418
+ },
187419
+ "migrations.restorePreMigration": {
187420
+ capName: "migrations",
187421
+ capScope: "system",
187422
+ addonId: null,
187423
+ access: "create",
187424
+ role: "admin"
187425
+ },
187426
+ "migrations.retry": {
187427
+ capName: "migrations",
187428
+ capScope: "system",
187429
+ addonId: null,
187430
+ access: "create",
187431
+ role: "admin"
187432
+ },
187433
+ "migrations.startMigration": {
187434
+ capName: "migrations",
187435
+ capScope: "system",
187436
+ addonId: null,
187437
+ access: "create",
187438
+ role: "admin"
187439
+ },
186685
187440
  "modelConvert.convert": {
186686
187441
  capName: "model-convert",
186687
187442
  capScope: "system",
@@ -189314,6 +190069,34 @@ Object.freeze({
189314
190069
  access: "view",
189315
190070
  role: "viewer"
189316
190071
  },
190072
+ "powerSource.forceRestart": {
190073
+ capName: "power-source",
190074
+ capScope: "device",
190075
+ addonId: null,
190076
+ access: "create",
190077
+ role: "admin"
190078
+ },
190079
+ "powerSource.getStatus": {
190080
+ capName: "power-source",
190081
+ capScope: "device",
190082
+ addonId: null,
190083
+ access: "view",
190084
+ role: "admin"
190085
+ },
190086
+ "powerSource.powerOff": {
190087
+ capName: "power-source",
190088
+ capScope: "device",
190089
+ addonId: null,
190090
+ access: "create",
190091
+ role: "admin"
190092
+ },
190093
+ "powerSource.powerOn": {
190094
+ capName: "power-source",
190095
+ capScope: "device",
190096
+ addonId: null,
190097
+ access: "create",
190098
+ role: "admin"
190099
+ },
189317
190100
  "presence.getStatus": {
189318
190101
  capName: "presence",
189319
190102
  capScope: "device",
@@ -194614,6 +195397,26 @@ Object.freeze({
194614
195397
  form: "single",
194615
195398
  optional: false
194616
195399
  }],
195400
+ "powerSource.forceRestart": [{
195401
+ name: "deviceId",
195402
+ form: "single",
195403
+ optional: false
195404
+ }],
195405
+ "powerSource.getStatus": [{
195406
+ name: "deviceId",
195407
+ form: "single",
195408
+ optional: false
195409
+ }],
195410
+ "powerSource.powerOff": [{
195411
+ name: "deviceId",
195412
+ form: "single",
195413
+ optional: false
195414
+ }],
195415
+ "powerSource.powerOn": [{
195416
+ name: "deviceId",
195417
+ form: "single",
195418
+ optional: false
195419
+ }],
194617
195420
  "presence.getStatus": [{
194618
195421
  name: "deviceId",
194619
195422
  form: "single",