@camstack/addon-provider-amcrest 0.2.61 → 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
@@ -12749,7 +12749,26 @@ var DiscoveryCandidateSchema = object({
12749
12749
  * identity ahead of adoption. Rendering metadata (unit, precision)
12750
12750
  * flows live through the cap STATUS SLICE after adoption.
12751
12751
  */
12752
- sourceInfo: SourceInfoSchema.optional()
12752
+ sourceInfo: SourceInfoSchema.optional(),
12753
+ /**
12754
+ * Set when this candidate is a device the provider ALREADY owns.
12755
+ *
12756
+ * A scan cannot generally produce the identity a device was onboarded under
12757
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12758
+ * comparison never matches and an owned device looks addable. Re-adopting one
12759
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12760
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12761
+ * three child cameras offline for four hours.
12762
+ *
12763
+ * A provider that can recognise its own devices says so here. Absent means
12764
+ * "not recognised", which is not the same as "known to be new" — a provider
12765
+ * that cannot tell simply never sets it.
12766
+ */
12767
+ alreadyOnboarded: boolean().optional(),
12768
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12769
+ onboardedDeviceId: number().optional(),
12770
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12771
+ onboardedName: string().optional()
12753
12772
  });
12754
12773
  /**
12755
12774
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33377,6 +33396,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33377
33396
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33378
33397
  * `getStatus().error`.
33379
33398
  */
33399
+ /**
33400
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33401
+ * Default: no-op — most providers have nothing to heal.
33402
+ *
33403
+ * This exists because a restored device self-hydrates from the DB: `create()`
33404
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33405
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33406
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33407
+ * been emptied failed all four bounded attempts against fields
33408
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33409
+ *
33410
+ * Implementations get every saved row, so a child can read its parent's blob.
33411
+ * A heal that throws is treated like any other restore failure: retried under
33412
+ * the bound, then reported — never swallowed.
33413
+ */
33414
+ async healSavedConfig(_saved, _allSaved) {}
33380
33415
  async onRestoreDevices(savedDevices) {
33381
33416
  const restored = /* @__PURE__ */ new Set();
33382
33417
  const failures = [];
@@ -33385,6 +33420,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33385
33420
  const Class = this.deviceClasses[saved.type];
33386
33421
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33387
33422
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33423
+ await this.healSavedConfig(saved, savedDevices);
33388
33424
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33389
33425
  restored.add(saved.id);
33390
33426
  };
package/dist/addon.mjs CHANGED
@@ -12750,7 +12750,26 @@ var DiscoveryCandidateSchema = object({
12750
12750
  * identity ahead of adoption. Rendering metadata (unit, precision)
12751
12751
  * flows live through the cap STATUS SLICE after adoption.
12752
12752
  */
12753
- sourceInfo: SourceInfoSchema.optional()
12753
+ sourceInfo: SourceInfoSchema.optional(),
12754
+ /**
12755
+ * Set when this candidate is a device the provider ALREADY owns.
12756
+ *
12757
+ * A scan cannot generally produce the identity a device was onboarded under
12758
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12759
+ * comparison never matches and an owned device looks addable. Re-adopting one
12760
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12761
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12762
+ * three child cameras offline for four hours.
12763
+ *
12764
+ * A provider that can recognise its own devices says so here. Absent means
12765
+ * "not recognised", which is not the same as "known to be new" — a provider
12766
+ * that cannot tell simply never sets it.
12767
+ */
12768
+ alreadyOnboarded: boolean().optional(),
12769
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12770
+ onboardedDeviceId: number().optional(),
12771
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12772
+ onboardedName: string().optional()
12754
12773
  });
12755
12774
  /**
12756
12775
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33378,6 +33397,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33378
33397
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33379
33398
  * `getStatus().error`.
33380
33399
  */
33400
+ /**
33401
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33402
+ * Default: no-op — most providers have nothing to heal.
33403
+ *
33404
+ * This exists because a restored device self-hydrates from the DB: `create()`
33405
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33406
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33407
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33408
+ * been emptied failed all four bounded attempts against fields
33409
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33410
+ *
33411
+ * Implementations get every saved row, so a child can read its parent's blob.
33412
+ * A heal that throws is treated like any other restore failure: retried under
33413
+ * the bound, then reported — never swallowed.
33414
+ */
33415
+ async healSavedConfig(_saved, _allSaved) {}
33381
33416
  async onRestoreDevices(savedDevices) {
33382
33417
  const restored = /* @__PURE__ */ new Set();
33383
33418
  const failures = [];
@@ -33386,6 +33421,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33386
33421
  const Class = this.deviceClasses[saved.type];
33387
33422
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33388
33423
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33424
+ await this.healSavedConfig(saved, savedDevices);
33389
33425
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33390
33426
  restored.add(saved.id);
33391
33427
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-amcrest",
3
- "version": "0.2.61",
3
+ "version": "0.2.62",
4
4
  "description": "Amcrest/Dahua camera device provider addon for CamStack — Dahua CGI over HTTP(S) with digest auth (snapshot, RTSP catalog, PTZ, image/day-night config)",
5
5
  "keywords": [
6
6
  "camstack",