@camstack/addon-provider-petkit 0.2.60 → 0.2.62

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
@@ -13875,7 +13875,26 @@ var DiscoveryCandidateSchema = object({
13875
13875
  * identity ahead of adoption. Rendering metadata (unit, precision)
13876
13876
  * flows live through the cap STATUS SLICE after adoption.
13877
13877
  */
13878
- sourceInfo: SourceInfoSchema.optional()
13878
+ sourceInfo: SourceInfoSchema.optional(),
13879
+ /**
13880
+ * Set when this candidate is a device the provider ALREADY owns.
13881
+ *
13882
+ * A scan cannot generally produce the identity a device was onboarded under
13883
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
13884
+ * comparison never matches and an owned device looks addable. Re-adopting one
13885
+ * overwrites its config with scan-derived values — that is how a Home Hub's
13886
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
13887
+ * three child cameras offline for four hours.
13888
+ *
13889
+ * A provider that can recognise its own devices says so here. Absent means
13890
+ * "not recognised", which is not the same as "known to be new" — a provider
13891
+ * that cannot tell simply never sets it.
13892
+ */
13893
+ alreadyOnboarded: boolean().optional(),
13894
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
13895
+ onboardedDeviceId: number().optional(),
13896
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
13897
+ onboardedName: string().optional()
13879
13898
  });
13880
13899
  /**
13881
13900
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -34121,6 +34140,22 @@ var BaseDeviceProvider = class extends BaseAddon {
34121
34140
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
34122
34141
  * `getStatus().error`.
34123
34142
  */
34143
+ /**
34144
+ * Repair a row's PERSISTED config blob immediately before it is restored.
34145
+ * Default: no-op — most providers have nothing to heal.
34146
+ *
34147
+ * This exists because a restored device self-hydrates from the DB: `create()`
34148
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
34149
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
34150
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
34151
+ * been emptied failed all four bounded attempts against fields
34152
+ * (`host`, `password`) it inherits from its parent and never dials itself.
34153
+ *
34154
+ * Implementations get every saved row, so a child can read its parent's blob.
34155
+ * A heal that throws is treated like any other restore failure: retried under
34156
+ * the bound, then reported — never swallowed.
34157
+ */
34158
+ async healSavedConfig(_saved, _allSaved) {}
34124
34159
  async onRestoreDevices(savedDevices) {
34125
34160
  const restored = /* @__PURE__ */ new Set();
34126
34161
  const failures = [];
@@ -34129,6 +34164,7 @@ var BaseDeviceProvider = class extends BaseAddon {
34129
34164
  const Class = this.deviceClasses[saved.type];
34130
34165
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
34131
34166
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
34167
+ await this.healSavedConfig(saved, savedDevices);
34132
34168
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
34133
34169
  restored.add(saved.id);
34134
34170
  };
package/dist/addon.mjs CHANGED
@@ -13874,7 +13874,26 @@ var DiscoveryCandidateSchema = object({
13874
13874
  * identity ahead of adoption. Rendering metadata (unit, precision)
13875
13875
  * flows live through the cap STATUS SLICE after adoption.
13876
13876
  */
13877
- sourceInfo: SourceInfoSchema.optional()
13877
+ sourceInfo: SourceInfoSchema.optional(),
13878
+ /**
13879
+ * Set when this candidate is a device the provider ALREADY owns.
13880
+ *
13881
+ * A scan cannot generally produce the identity a device was onboarded under
13882
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
13883
+ * comparison never matches and an owned device looks addable. Re-adopting one
13884
+ * overwrites its config with scan-derived values — that is how a Home Hub's
13885
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
13886
+ * three child cameras offline for four hours.
13887
+ *
13888
+ * A provider that can recognise its own devices says so here. Absent means
13889
+ * "not recognised", which is not the same as "known to be new" — a provider
13890
+ * that cannot tell simply never sets it.
13891
+ */
13892
+ alreadyOnboarded: boolean().optional(),
13893
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
13894
+ onboardedDeviceId: number().optional(),
13895
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
13896
+ onboardedName: string().optional()
13878
13897
  });
13879
13898
  /**
13880
13899
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -34120,6 +34139,22 @@ var BaseDeviceProvider = class extends BaseAddon {
34120
34139
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
34121
34140
  * `getStatus().error`.
34122
34141
  */
34142
+ /**
34143
+ * Repair a row's PERSISTED config blob immediately before it is restored.
34144
+ * Default: no-op — most providers have nothing to heal.
34145
+ *
34146
+ * This exists because a restored device self-hydrates from the DB: `create()`
34147
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
34148
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
34149
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
34150
+ * been emptied failed all four bounded attempts against fields
34151
+ * (`host`, `password`) it inherits from its parent and never dials itself.
34152
+ *
34153
+ * Implementations get every saved row, so a child can read its parent's blob.
34154
+ * A heal that throws is treated like any other restore failure: retried under
34155
+ * the bound, then reported — never swallowed.
34156
+ */
34157
+ async healSavedConfig(_saved, _allSaved) {}
34123
34158
  async onRestoreDevices(savedDevices) {
34124
34159
  const restored = /* @__PURE__ */ new Set();
34125
34160
  const failures = [];
@@ -34128,6 +34163,7 @@ var BaseDeviceProvider = class extends BaseAddon {
34128
34163
  const Class = this.deviceClasses[saved.type];
34129
34164
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
34130
34165
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
34166
+ await this.healSavedConfig(saved, savedDevices);
34131
34167
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
34132
34168
  restored.add(saved.id);
34133
34169
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-petkit",
3
- "version": "0.2.60",
3
+ "version": "0.2.62",
4
4
  "description": "PetKit smart-feeder device-provider addon for CamStack — wraps the @apocaliss92/nodepetkit PetKit cloud client",
5
5
  "keywords": [
6
6
  "camstack",