@camstack/addon-provider-dreo 0.2.60 → 0.2.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -12809,7 +12809,26 @@ var DiscoveryCandidateSchema = object({
12809
12809
  * identity ahead of adoption. Rendering metadata (unit, precision)
12810
12810
  * flows live through the cap STATUS SLICE after adoption.
12811
12811
  */
12812
- sourceInfo: SourceInfoSchema.optional()
12812
+ sourceInfo: SourceInfoSchema.optional(),
12813
+ /**
12814
+ * Set when this candidate is a device the provider ALREADY owns.
12815
+ *
12816
+ * A scan cannot generally produce the identity a device was onboarded under
12817
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12818
+ * comparison never matches and an owned device looks addable. Re-adopting one
12819
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12820
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12821
+ * three child cameras offline for four hours.
12822
+ *
12823
+ * A provider that can recognise its own devices says so here. Absent means
12824
+ * "not recognised", which is not the same as "known to be new" — a provider
12825
+ * that cannot tell simply never sets it.
12826
+ */
12827
+ alreadyOnboarded: boolean().optional(),
12828
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12829
+ onboardedDeviceId: number().optional(),
12830
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12831
+ onboardedName: string().optional()
12813
12832
  });
12814
12833
  /**
12815
12834
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33055,6 +33074,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33055
33074
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33056
33075
  * `getStatus().error`.
33057
33076
  */
33077
+ /**
33078
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33079
+ * Default: no-op — most providers have nothing to heal.
33080
+ *
33081
+ * This exists because a restored device self-hydrates from the DB: `create()`
33082
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33083
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33084
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33085
+ * been emptied failed all four bounded attempts against fields
33086
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33087
+ *
33088
+ * Implementations get every saved row, so a child can read its parent's blob.
33089
+ * A heal that throws is treated like any other restore failure: retried under
33090
+ * the bound, then reported — never swallowed.
33091
+ */
33092
+ async healSavedConfig(_saved, _allSaved) {}
33058
33093
  async onRestoreDevices(savedDevices) {
33059
33094
  const restored = /* @__PURE__ */ new Set();
33060
33095
  const failures = [];
@@ -33063,6 +33098,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33063
33098
  const Class = this.deviceClasses[saved.type];
33064
33099
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33065
33100
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33101
+ await this.healSavedConfig(saved, savedDevices);
33066
33102
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33067
33103
  restored.add(saved.id);
33068
33104
  };
package/dist/addon.mjs CHANGED
@@ -12810,7 +12810,26 @@ var DiscoveryCandidateSchema = object({
12810
12810
  * identity ahead of adoption. Rendering metadata (unit, precision)
12811
12811
  * flows live through the cap STATUS SLICE after adoption.
12812
12812
  */
12813
- sourceInfo: SourceInfoSchema.optional()
12813
+ sourceInfo: SourceInfoSchema.optional(),
12814
+ /**
12815
+ * Set when this candidate is a device the provider ALREADY owns.
12816
+ *
12817
+ * A scan cannot generally produce the identity a device was onboarded under
12818
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12819
+ * comparison never matches and an owned device looks addable. Re-adopting one
12820
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12821
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12822
+ * three child cameras offline for four hours.
12823
+ *
12824
+ * A provider that can recognise its own devices says so here. Absent means
12825
+ * "not recognised", which is not the same as "known to be new" — a provider
12826
+ * that cannot tell simply never sets it.
12827
+ */
12828
+ alreadyOnboarded: boolean().optional(),
12829
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12830
+ onboardedDeviceId: number().optional(),
12831
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12832
+ onboardedName: string().optional()
12814
12833
  });
12815
12834
  /**
12816
12835
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33056,6 +33075,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33056
33075
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33057
33076
  * `getStatus().error`.
33058
33077
  */
33078
+ /**
33079
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33080
+ * Default: no-op — most providers have nothing to heal.
33081
+ *
33082
+ * This exists because a restored device self-hydrates from the DB: `create()`
33083
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33084
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33085
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33086
+ * been emptied failed all four bounded attempts against fields
33087
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33088
+ *
33089
+ * Implementations get every saved row, so a child can read its parent's blob.
33090
+ * A heal that throws is treated like any other restore failure: retried under
33091
+ * the bound, then reported — never swallowed.
33092
+ */
33093
+ async healSavedConfig(_saved, _allSaved) {}
33059
33094
  async onRestoreDevices(savedDevices) {
33060
33095
  const restored = /* @__PURE__ */ new Set();
33061
33096
  const failures = [];
@@ -33064,6 +33099,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33064
33099
  const Class = this.deviceClasses[saved.type];
33065
33100
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33066
33101
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33102
+ await this.healSavedConfig(saved, savedDevices);
33067
33103
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33068
33104
  restored.add(saved.id);
33069
33105
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreo",
3
- "version": "0.2.60",
3
+ "version": "0.2.61",
4
4
  "description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
5
5
  "keywords": [
6
6
  "camstack",