@camstack/addon-provider-tuya 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
@@ -13593,7 +13593,26 @@ var DiscoveryCandidateSchema = object({
13593
13593
  * identity ahead of adoption. Rendering metadata (unit, precision)
13594
13594
  * flows live through the cap STATUS SLICE after adoption.
13595
13595
  */
13596
- sourceInfo: SourceInfoSchema.optional()
13596
+ sourceInfo: SourceInfoSchema.optional(),
13597
+ /**
13598
+ * Set when this candidate is a device the provider ALREADY owns.
13599
+ *
13600
+ * A scan cannot generally produce the identity a device was onboarded under
13601
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
13602
+ * comparison never matches and an owned device looks addable. Re-adopting one
13603
+ * overwrites its config with scan-derived values — that is how a Home Hub's
13604
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
13605
+ * three child cameras offline for four hours.
13606
+ *
13607
+ * A provider that can recognise its own devices says so here. Absent means
13608
+ * "not recognised", which is not the same as "known to be new" — a provider
13609
+ * that cannot tell simply never sets it.
13610
+ */
13611
+ alreadyOnboarded: boolean().optional(),
13612
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
13613
+ onboardedDeviceId: number().optional(),
13614
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
13615
+ onboardedName: string().optional()
13597
13616
  });
13598
13617
  /**
13599
13618
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33839,6 +33858,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33839
33858
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33840
33859
  * `getStatus().error`.
33841
33860
  */
33861
+ /**
33862
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33863
+ * Default: no-op — most providers have nothing to heal.
33864
+ *
33865
+ * This exists because a restored device self-hydrates from the DB: `create()`
33866
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33867
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33868
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33869
+ * been emptied failed all four bounded attempts against fields
33870
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33871
+ *
33872
+ * Implementations get every saved row, so a child can read its parent's blob.
33873
+ * A heal that throws is treated like any other restore failure: retried under
33874
+ * the bound, then reported — never swallowed.
33875
+ */
33876
+ async healSavedConfig(_saved, _allSaved) {}
33842
33877
  async onRestoreDevices(savedDevices) {
33843
33878
  const restored = /* @__PURE__ */ new Set();
33844
33879
  const failures = [];
@@ -33847,6 +33882,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33847
33882
  const Class = this.deviceClasses[saved.type];
33848
33883
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33849
33884
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33885
+ await this.healSavedConfig(saved, savedDevices);
33850
33886
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33851
33887
  restored.add(saved.id);
33852
33888
  };
package/dist/addon.mjs CHANGED
@@ -13592,7 +13592,26 @@ var DiscoveryCandidateSchema = object({
13592
13592
  * identity ahead of adoption. Rendering metadata (unit, precision)
13593
13593
  * flows live through the cap STATUS SLICE after adoption.
13594
13594
  */
13595
- sourceInfo: SourceInfoSchema.optional()
13595
+ sourceInfo: SourceInfoSchema.optional(),
13596
+ /**
13597
+ * Set when this candidate is a device the provider ALREADY owns.
13598
+ *
13599
+ * A scan cannot generally produce the identity a device was onboarded under
13600
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
13601
+ * comparison never matches and an owned device looks addable. Re-adopting one
13602
+ * overwrites its config with scan-derived values — that is how a Home Hub's
13603
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
13604
+ * three child cameras offline for four hours.
13605
+ *
13606
+ * A provider that can recognise its own devices says so here. Absent means
13607
+ * "not recognised", which is not the same as "known to be new" — a provider
13608
+ * that cannot tell simply never sets it.
13609
+ */
13610
+ alreadyOnboarded: boolean().optional(),
13611
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
13612
+ onboardedDeviceId: number().optional(),
13613
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
13614
+ onboardedName: string().optional()
13596
13615
  });
13597
13616
  /**
13598
13617
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33838,6 +33857,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33838
33857
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33839
33858
  * `getStatus().error`.
33840
33859
  */
33860
+ /**
33861
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33862
+ * Default: no-op — most providers have nothing to heal.
33863
+ *
33864
+ * This exists because a restored device self-hydrates from the DB: `create()`
33865
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33866
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33867
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33868
+ * been emptied failed all four bounded attempts against fields
33869
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33870
+ *
33871
+ * Implementations get every saved row, so a child can read its parent's blob.
33872
+ * A heal that throws is treated like any other restore failure: retried under
33873
+ * the bound, then reported — never swallowed.
33874
+ */
33875
+ async healSavedConfig(_saved, _allSaved) {}
33841
33876
  async onRestoreDevices(savedDevices) {
33842
33877
  const restored = /* @__PURE__ */ new Set();
33843
33878
  const failures = [];
@@ -33846,6 +33881,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33846
33881
  const Class = this.deviceClasses[saved.type];
33847
33882
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33848
33883
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33884
+ await this.healSavedConfig(saved, savedDevices);
33849
33885
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33850
33886
  restored.add(saved.id);
33851
33887
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-tuya",
3
- "version": "0.2.60",
3
+ "version": "0.2.61",
4
4
  "description": "Tuya / Smart Life device-provider addon for CamStack — account-onboarded (Tuya IoT cloud fetch of device localKeys) + LOCAL DP control via the @apocaliss92/nodetuya encrypted-LAN client, exposing switch / water-heater-family kettle entities",
5
5
  "keywords": [
6
6
  "camstack",