@camstack/addon-provider-homeassistant 1.2.660 → 1.2.662

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.662] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - The integration form is declared as the instance form; brokers and their stored values are unchanged.
15
+
16
+ ## [1.2.661] - 2026-11-05
17
+
18
+ ### Changed
19
+
20
+ - Copying an integration into its record reads the leftover rows through the migration-only door and resolves their secrets in this addon
21
+
10
22
  ## [1.2.660] - 2026-11-05
11
23
 
12
24
  ### Changed
package/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_legacy_integration_rows = require("./legacy-integration-rows-BWVmjfDO.js");
2
+ const require_legacy_integration_rows = require("./legacy-integration-rows-BaTsNMtt.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_zlib = require("node:zlib");
5
5
  //#region src/ha-admin-link.ts
@@ -9107,7 +9107,12 @@ var HaProviderAddon = class HaProviderAddon extends require_legacy_integration_r
9107
9107
  if (this.ctx.secrets === void 0) throw new Error("provider-homeassistant: ctx.secrets is required");
9108
9108
  return {
9109
9109
  readStored: (key) => this.readStoredGlobalKey(key),
9110
- ...require_legacy_integration_rows.haInstanceSettingsPortsOver(this.ctx.api.integrations),
9110
+ ...require_legacy_integration_rows.haInstanceSettingsPortsOver({
9111
+ list: this.ctx.api.integrations.list,
9112
+ readLegacyIntegrationRows: this.ctx.api.migrations.readLegacyIntegrationRows,
9113
+ patchLegacyIntegrationRows: this.ctx.api.migrations.patchLegacyIntegrationRows,
9114
+ deleteLegacyIntegrationRowKeys: this.ctx.api.migrations.deleteLegacyIntegrationRowKeys
9115
+ }, this.ctx.secrets ?? null),
9111
9116
  ...haInstanceRecordPortsOver(records.kind(haBrokersEntryKind)),
9112
9117
  ...require_legacy_integration_rows.flattenPortsOver(records.kind(haBrokersEntryKind))
9113
9118
  };
@@ -9321,7 +9326,7 @@ var HaProviderAddon = class HaProviderAddon extends require_legacy_integration_r
9321
9326
  eb.subscribe({ category: "integration.disabled" }, handler);
9322
9327
  eb.subscribe({ category: "integration.deleted" }, handler);
9323
9328
  }
9324
- globalSettingsSchema(_cap) {
9329
+ async integrationFormSchema(_instance) {
9325
9330
  return this.schema({ sections: [{
9326
9331
  id: "ha-broker",
9327
9332
  title: "Home Assistant broker",
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as fanControlCapability, $t as createEvent, A as bestLocationMatch, At as runningFromMediaPlayer, B as climateControlCapability, Bt as tryConvertDelta, C as accessoriesCapability, Ct as pressureSensorCapability, D as ambientLightSensorCapability, Dt as resolveAttachmentUrls, E as alarmPanelCapability, Et as quantityFromUnit, F as buildAddonRouteProvider, Ft as smokeCapability, G as contactCapability, Gt as vibrationCapability, H as connectivityCapability, Ht as updateCapability, I as buildDeviceAdminUrl, J as deviceAdminLinkCapability, K as controlCapability, Kt as waterHeaterCapability, L as buttonCapability, Lt as switchCapability, M as boardKeySourceCapability, Mt as runningStateCapability, N as brightnessCapability, Nt as scriptRunnerCapability, O as automationControlCapability, P as brokerCapability, Q as eventEmitterCapability, Qt as baseUnitOf, R as calendarSourceCapability, Rt as tamperCapability, S as TargetSchema, Sn as EventCategory, St as presenceCapability, T as airQualitySensorCapability, Tt as publicEndpointReaderFrom, Ut as vacuumControlCapability, V as colorCapability, Vt as tryConvertUnit, Wt as valveCapability, Xt as RecordSecretUnavailableError, Y as deviceAdoptionCapability, Z as enumSensorCapability, _n as url, at as humiditySensorCapability, b as RUNNING_STATE_COLD, bn as DeviceType, bt as preconditionOf, cn as boolean, ct as integrationInstancesCapability, dn as number, dt as mediaPlayerCapability, en as defineAddonMigrations, et as flattenNestedRecords, fn as object, ft as motionCapability, gn as unknown, gt as numericSensorCapability, hn as union, ht as notifierCapability, it as humidifierCapability, j as binaryCapability, jt as runningFromVacuumState, k as batteryCapability, kt as runningFromLawnMower, ln as discriminatedUnion, lt as lawnMowerControlCapability, m as EnumSensorDateTimeFormatSchema, mn as string, mt as notificationOutputCapability, n as HA_BROKER_KIND, nn as defineRecordKinds, nt as floodCapability, o as BaseDevice, on as _enum, ot as imageCapability, pn as record, pt as normalizeUnit, q as coverCapability, qt as weatherCapability, rn as entryBody, rt as gasCapability, s as BaseDeviceProvider, sn as array, st as ingestReading, t as haInstanceSettingsPortsOver, tn as defineEntryKind, tt as flattenPortsOver, un as literal, ut as lockControlCapability, vn as DeviceFeature, vt as parseAdminBase, w as addonRoutesCapability, x as RecordIntegrationInstances, xn as errMsg, xt as prepareNotification, yn as DeviceRole, yt as powerMeterCapability, z as carbonMonoxideCapability, zt as temperatureSensorCapability } from "./legacy-integration-rows-BIbFoT26.mjs";
1
+ import { $ as fanControlCapability, $t as createEvent, A as bestLocationMatch, At as runningFromMediaPlayer, B as climateControlCapability, Bt as tryConvertDelta, C as accessoriesCapability, Ct as pressureSensorCapability, D as ambientLightSensorCapability, Dt as resolveAttachmentUrls, E as alarmPanelCapability, Et as quantityFromUnit, F as buildAddonRouteProvider, Ft as smokeCapability, G as contactCapability, Gt as vibrationCapability, H as connectivityCapability, Ht as updateCapability, I as buildDeviceAdminUrl, J as deviceAdminLinkCapability, K as controlCapability, Kt as waterHeaterCapability, L as buttonCapability, Lt as switchCapability, M as boardKeySourceCapability, Mt as runningStateCapability, N as brightnessCapability, Nt as scriptRunnerCapability, O as automationControlCapability, P as brokerCapability, Q as eventEmitterCapability, Qt as baseUnitOf, R as calendarSourceCapability, Rt as tamperCapability, S as TargetSchema, Sn as EventCategory, St as presenceCapability, T as airQualitySensorCapability, Tt as publicEndpointReaderFrom, Ut as vacuumControlCapability, V as colorCapability, Vt as tryConvertUnit, Wt as valveCapability, Xt as RecordSecretUnavailableError, Y as deviceAdoptionCapability, Z as enumSensorCapability, _n as url, at as humiditySensorCapability, b as RUNNING_STATE_COLD, bn as DeviceType, bt as preconditionOf, cn as boolean, ct as integrationInstancesCapability, dn as number, dt as mediaPlayerCapability, en as defineAddonMigrations, et as flattenNestedRecords, fn as object, ft as motionCapability, gn as unknown, gt as numericSensorCapability, hn as union, ht as notifierCapability, it as humidifierCapability, j as binaryCapability, jt as runningFromVacuumState, k as batteryCapability, kt as runningFromLawnMower, ln as discriminatedUnion, lt as lawnMowerControlCapability, m as EnumSensorDateTimeFormatSchema, mn as string, mt as notificationOutputCapability, n as HA_BROKER_KIND, nn as defineRecordKinds, nt as floodCapability, o as BaseDevice, on as _enum, ot as imageCapability, pn as record, pt as normalizeUnit, q as coverCapability, qt as weatherCapability, rn as entryBody, rt as gasCapability, s as BaseDeviceProvider, sn as array, st as ingestReading, t as haInstanceSettingsPortsOver, tn as defineEntryKind, tt as flattenPortsOver, un as literal, ut as lockControlCapability, vn as DeviceFeature, vt as parseAdminBase, w as addonRoutesCapability, x as RecordIntegrationInstances, xn as errMsg, xt as prepareNotification, yn as DeviceRole, yt as powerMeterCapability, z as carbonMonoxideCapability, zt as temperatureSensorCapability } from "./legacy-integration-rows-uGrBo3a3.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
4
4
  //#region src/ha-admin-link.ts
@@ -9086,7 +9086,12 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
9086
9086
  if (this.ctx.secrets === void 0) throw new Error("provider-homeassistant: ctx.secrets is required");
9087
9087
  return {
9088
9088
  readStored: (key) => this.readStoredGlobalKey(key),
9089
- ...haInstanceSettingsPortsOver(this.ctx.api.integrations),
9089
+ ...haInstanceSettingsPortsOver({
9090
+ list: this.ctx.api.integrations.list,
9091
+ readLegacyIntegrationRows: this.ctx.api.migrations.readLegacyIntegrationRows,
9092
+ patchLegacyIntegrationRows: this.ctx.api.migrations.patchLegacyIntegrationRows,
9093
+ deleteLegacyIntegrationRowKeys: this.ctx.api.migrations.deleteLegacyIntegrationRowKeys
9094
+ }, this.ctx.secrets ?? null),
9090
9095
  ...haInstanceRecordPortsOver(records.kind(haBrokersEntryKind)),
9091
9096
  ...flattenPortsOver(records.kind(haBrokersEntryKind))
9092
9097
  };
@@ -9300,7 +9305,7 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
9300
9305
  eb.subscribe({ category: "integration.disabled" }, handler);
9301
9306
  eb.subscribe({ category: "integration.deleted" }, handler);
9302
9307
  }
9303
- globalSettingsSchema(_cap) {
9308
+ async integrationFormSchema(_instance) {
9304
9309
  return this.schema({ sections: [{
9305
9310
  id: "ha-broker",
9306
9311
  title: "Home Assistant broker",
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
- const require_legacy_integration_rows = require("../legacy-integration-rows-BWVmjfDO.js");
27
+ const require_legacy_integration_rows = require("../legacy-integration-rows-BaTsNMtt.js");
28
28
  let node_crypto = require("node:crypto");
29
29
  require("node:zlib");
30
30
  let node_fs = require("node:fs");
@@ -5781,7 +5781,12 @@ async function readAllLinks(handle) {
5781
5781
  function haExportMigrationPorts(deps) {
5782
5782
  return {
5783
5783
  readStored: deps.readStored,
5784
- ...require_legacy_integration_rows.haInstanceSettingsPortsOver(deps.api.integrations),
5784
+ ...require_legacy_integration_rows.haInstanceSettingsPortsOver({
5785
+ list: deps.api.integrations.list,
5786
+ readLegacyIntegrationRows: deps.api.migrations.readLegacyIntegrationRows,
5787
+ patchLegacyIntegrationRows: deps.api.migrations.patchLegacyIntegrationRows,
5788
+ deleteLegacyIntegrationRowKeys: deps.api.migrations.deleteLegacyIntegrationRowKeys
5789
+ }, deps.secrets ?? null),
5785
5790
  warn: deps.warn,
5786
5791
  putLinks: async (brokerId, body) => {
5787
5792
  if (await deps.links.get(brokerId) !== null) return "exists";
@@ -8108,6 +8113,7 @@ var HaExportAddon = class extends require_legacy_integration_rows.BaseAddon {
8108
8113
  await this.runMigrations(require_legacy_integration_rows.defineAddonMigrations(ADDON_MIGRATIONS, haExportMigrationPorts({
8109
8114
  readStored: async () => this.resolveGlobalStore(),
8110
8115
  api: this.ctx.api,
8116
+ secrets: this.ctx.secrets ?? null,
8111
8117
  links: legacyLinks,
8112
8118
  targets: targetsHandle,
8113
8119
  clearSettingKeys: async (keys) => {
@@ -1,4 +1,4 @@
1
- import { F as buildAddonRouteProvider, It as statusInstanceOf, Jt as BaseAddon, Ot as runExportedAction, Pt as sendToExportedTarget, Qt as baseUnitOf, Sn as EventCategory, U as consumableFormOf, W as consumableKindOf, X as deviceExportCapability, Zt as RevConflictError, _ as HvacModeSchema, _t as oauthIntegrationCapability, a as AlarmArmModeSchema, an as nodePin, bt as preconditionOf, c as CAMERA_SWITCH_ORDER, cn as boolean, ct as integrationInstancesCapability, d as ConsumableFormSchema, dn as number, en as defineAddonMigrations, f as ConsumableItemSchema, fn as object, g as FanDirectionSchema, gn as unknown, h as ExportInstanceUnknownError, i as listHomeAssistantBrokers, in as isReadingQuantity, l as COCO_TO_MACRO, mn as string, on as _enum, p as ConsumableStatusSchema, sn as array, t as haInstanceSettingsPortsOver, tn as defineEntryKind, u as CameraSwitchIdSchema, v as MediaPlayerRepeatSchema, w as addonRoutesCapability, wt as pruneDeletedEntities, x as RecordIntegrationInstances, y as PLUG_APPLIANCE_TYPES } from "../legacy-integration-rows-BIbFoT26.mjs";
1
+ import { F as buildAddonRouteProvider, It as statusInstanceOf, Jt as BaseAddon, Ot as runExportedAction, Pt as sendToExportedTarget, Qt as baseUnitOf, Sn as EventCategory, U as consumableFormOf, W as consumableKindOf, X as deviceExportCapability, Zt as RevConflictError, _ as HvacModeSchema, _t as oauthIntegrationCapability, a as AlarmArmModeSchema, an as nodePin, bt as preconditionOf, c as CAMERA_SWITCH_ORDER, cn as boolean, ct as integrationInstancesCapability, d as ConsumableFormSchema, dn as number, en as defineAddonMigrations, f as ConsumableItemSchema, fn as object, g as FanDirectionSchema, gn as unknown, h as ExportInstanceUnknownError, i as listHomeAssistantBrokers, in as isReadingQuantity, l as COCO_TO_MACRO, mn as string, on as _enum, p as ConsumableStatusSchema, sn as array, t as haInstanceSettingsPortsOver, tn as defineEntryKind, u as CameraSwitchIdSchema, v as MediaPlayerRepeatSchema, w as addonRoutesCapability, wt as pruneDeletedEntities, x as RecordIntegrationInstances, y as PLUG_APPLIANCE_TYPES } from "../legacy-integration-rows-uGrBo3a3.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  import "node:zlib";
4
4
  import "node:fs";
@@ -5743,7 +5743,12 @@ async function readAllLinks(handle) {
5743
5743
  function haExportMigrationPorts(deps) {
5744
5744
  return {
5745
5745
  readStored: deps.readStored,
5746
- ...haInstanceSettingsPortsOver(deps.api.integrations),
5746
+ ...haInstanceSettingsPortsOver({
5747
+ list: deps.api.integrations.list,
5748
+ readLegacyIntegrationRows: deps.api.migrations.readLegacyIntegrationRows,
5749
+ patchLegacyIntegrationRows: deps.api.migrations.patchLegacyIntegrationRows,
5750
+ deleteLegacyIntegrationRowKeys: deps.api.migrations.deleteLegacyIntegrationRowKeys
5751
+ }, deps.secrets ?? null),
5747
5752
  warn: deps.warn,
5748
5753
  putLinks: async (brokerId, body) => {
5749
5754
  if (await deps.links.get(brokerId) !== null) return "exists";
@@ -8070,6 +8075,7 @@ var HaExportAddon = class extends BaseAddon {
8070
8075
  await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, haExportMigrationPorts({
8071
8076
  readStored: async () => this.resolveGlobalStore(),
8072
8077
  api: this.ctx.api,
8078
+ secrets: this.ctx.secrets ?? null,
8073
8079
  links: legacyLinks,
8074
8080
  targets: targetsHandle,
8075
8081
  clearSettingKeys: async (keys) => {
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
5981
5981
  var ZodFirstPartyTypeKind;
5982
5982
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5983
5983
  //#endregion
5984
- //#region ../types/dist/sleep-D1jjWAr8.mjs
5984
+ //#region ../types/dist/sleep-DEZaLbiu.mjs
5985
5985
  /**
5986
5986
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5987
5987
  * window to float samples (D455).
@@ -6800,6 +6800,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6800
6800
  }
6801
6801
  return out;
6802
6802
  }
6803
+ /** Every scope, in the order the guard and the docs list them. */
6804
+ var SCOPED_SETTINGS_SCOPES = [
6805
+ "cluster",
6806
+ "device-defaults",
6807
+ "node"
6808
+ ];
6803
6809
  /**
6804
6810
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6805
6811
  *
@@ -7691,6 +7697,13 @@ var BaseAddon = class {
7691
7697
  */
7692
7698
  secretStatePaths = [];
7693
7699
  _addonSecretTree = null;
7700
+ /**
7701
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7702
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7703
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7704
+ */
7705
+ _scopedByCap = /* @__PURE__ */ new Map();
7706
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7694
7707
  _deviceSecretTree = null;
7695
7708
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7696
7709
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7760,6 +7773,7 @@ var BaseAddon = class {
7760
7773
  this._ctx = context;
7761
7774
  this._secretSettings = null;
7762
7775
  }
7776
+ await this.loadScopedSchemas();
7763
7777
  await this.resolveConfig();
7764
7778
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7765
7779
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7769,6 +7783,7 @@ var BaseAddon = class {
7769
7783
  const normalized = normalizeAddonInitResult(result);
7770
7784
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7771
7785
  this._registeredCapNames = providers.map((p) => p.capability.name);
7786
+ await this.loadScopedSchemas();
7772
7787
  return normalized;
7773
7788
  }
7774
7789
  /**
@@ -7857,6 +7872,16 @@ var BaseAddon = class {
7857
7872
  schema(schema) {
7858
7873
  return schema;
7859
7874
  }
7875
+ /**
7876
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7877
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7878
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7879
+ * must already know which keys are per node. Names the providers register at init are added
7880
+ * once known.
7881
+ */
7882
+ scopedSettingsCapabilities() {
7883
+ return [];
7884
+ }
7860
7885
  /** Override to provide global-level settings UI schema. */
7861
7886
  globalSettingsSchema(_cap) {
7862
7887
  return null;
@@ -7900,7 +7925,8 @@ var BaseAddon = class {
7900
7925
  return [];
7901
7926
  }
7902
7927
  async getGlobalSettings(overlay, cap, nodeId) {
7903
- const schema = this.globalSettingsSchema(cap);
7928
+ await this.loadScopedSchemas(cap);
7929
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7904
7930
  if (!schema) return { sections: [] };
7905
7931
  const projected = await this.readRawProjectedStore(nodeId, cap);
7906
7932
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -7935,10 +7961,9 @@ var BaseAddon = class {
7935
7961
  *
7936
7962
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7937
7963
  * needs its connection fields, the default integration the addon's own sections. It is the
7938
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7939
- * unchanged (`integrations.setSettings` for an account integration).
7964
+ * SHAPE of the form only — the values live on the instance record.
7940
7965
  */
7941
- async integrationSettingsSchema(_instance) {
7966
+ async integrationFormSchema(_instance) {
7942
7967
  return null;
7943
7968
  }
7944
7969
  /**
@@ -7984,7 +8009,7 @@ var BaseAddon = class {
7984
8009
  * per-instance schema when it declares one, else its global schema (what they always showed).
7985
8010
  */
7986
8011
  async getIntegrationFormSchema(instance, nodeId) {
7987
- const scoped = await this.integrationSettingsSchema(instance);
8012
+ const scoped = await this.integrationFormSchema(instance);
7988
8013
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7989
8014
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7990
8015
  }
@@ -8032,6 +8057,7 @@ var BaseAddon = class {
8032
8057
  * resolved blob back is a no-op for every unchanged secret.
8033
8058
  */
8034
8059
  async resolveGlobalStore(nodeId, cap) {
8060
+ await this.loadScopedSchemas(cap);
8035
8061
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8036
8062
  return this.projectForNode(raw, nodeId, cap);
8037
8063
  }
@@ -8042,6 +8068,7 @@ var BaseAddon = class {
8042
8068
  }
8043
8069
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8044
8070
  async readRawProjectedStore(nodeId, cap) {
8071
+ await this.loadScopedSchemas(cap);
8045
8072
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8046
8073
  return this.projectForNode(raw, nodeId, cap);
8047
8074
  }
@@ -8144,6 +8171,7 @@ var BaseAddon = class {
8144
8171
  await this.applyGlobalPatch(patch, nodeId);
8145
8172
  }
8146
8173
  async applyGlobalPatch(barePatch, nodeId) {
8174
+ await this.loadScopedSchemas();
8147
8175
  const keys = this.perNodeKeys();
8148
8176
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8149
8177
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8157,7 +8185,8 @@ var BaseAddon = class {
8157
8185
  await this.resolveConfig();
8158
8186
  await this.onConfigChanged();
8159
8187
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8160
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8188
+ const legacy = this.globalSettingsSchema();
8189
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8161
8190
  }
8162
8191
  /**
8163
8192
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8165,14 +8194,67 @@ var BaseAddon = class {
8165
8194
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8166
8195
  * settings API behaves exactly like the legacy node-agnostic one.
8167
8196
  */
8168
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8197
+ /**
8198
+ * Load the scoped schemas of every capability this addon declares (the override and the
8199
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8200
+ * derived from the previous set. Idempotent.
8201
+ */
8202
+ async loadScopedSchemas(extraCap) {
8203
+ const caps = new Set([
8204
+ ...this.scopedSettingsCapabilities(),
8205
+ ...this._registeredCapNames,
8206
+ ...extraCap === void 0 ? [] : [extraCap]
8207
+ ]);
8208
+ let added = false;
8209
+ for (const cap of caps) {
8210
+ if (this._scopedByCap.has(cap)) continue;
8211
+ const loaded = [];
8212
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8213
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8214
+ if (schema !== null) loaded.push({
8215
+ scope,
8216
+ schema
8217
+ });
8218
+ }
8219
+ this._scopedByCap.set(cap, loaded);
8220
+ added = true;
8221
+ }
8222
+ if (!added) return;
8223
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8224
+ this._addonSecretTree = null;
8225
+ }
8226
+ /**
8227
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8228
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8229
+ */
8230
+ scopedFormSchema(cap) {
8231
+ const byId = /* @__PURE__ */ new Map();
8232
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8233
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8234
+ }
8235
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8236
+ loadedScopedSchemas(cap) {
8237
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8238
+ return [...this._scopedByCap.values()].flat();
8239
+ }
8240
+ /**
8241
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8242
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8243
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8244
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8245
+ * Call {@link loadScopedSchemas} first (the async seams do).
8246
+ */
8169
8247
  perNodeKeys(cap) {
8170
8248
  const cacheKey = cap ?? "";
8171
- const cached = this._perNodeKeysCache.get(cacheKey);
8249
+ const cached = this._nodeKeysByCap.get(cacheKey);
8172
8250
  if (cached) return cached;
8173
- const schema = this.globalSettingsSchema(cap);
8174
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8175
- this._perNodeKeysCache.set(cacheKey, keys);
8251
+ const legacy = this.globalSettingsSchema(cap);
8252
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8253
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8254
+ if (scope !== "node") continue;
8255
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8256
+ }
8257
+ this._nodeKeysByCap.set(cacheKey, keys);
8176
8258
  return keys;
8177
8259
  }
8178
8260
  /**
@@ -8182,10 +8264,9 @@ var BaseAddon = class {
8182
8264
  * time to return its response before the addon is torn down and
8183
8265
  * re-initialised by `AddonRegistryService.restartAddon`.
8184
8266
  */
8185
- maybeAutoRestart(patchKeys, schema) {
8186
- if (!schema) return;
8267
+ maybeAutoRestart(patchKeys, schemas) {
8187
8268
  const restartKeys = /* @__PURE__ */ new Set();
8188
- for (const section of schema.sections) for (const field of section.fields) {
8269
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8189
8270
  if (field.type === "separator" || field.type === "info") continue;
8190
8271
  if (field.requiresRestart) restartKeys.add(field.key);
8191
8272
  }
@@ -8381,12 +8462,14 @@ var BaseAddon = class {
8381
8462
  * here, in the addon's own process, so the hub keeps no copy of it.
8382
8463
  */
8383
8464
  async getDeclaredSettingsKeys() {
8465
+ await this.loadScopedSchemas();
8384
8466
  return { keys: [...this.declaredKeySet()].toSorted() };
8385
8467
  }
8386
8468
  declaredKeySet() {
8387
8469
  const keys = new Set(Object.keys(this.defaults));
8388
8470
  const schema = this.globalSettingsSchema();
8389
8471
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8472
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8390
8473
  for (const key of this._declaredStateKeys) keys.add(key);
8391
8474
  for (const path of this.secretStatePaths) {
8392
8475
  const root = path.split(/[.[]/, 1)[0];
@@ -8482,7 +8565,11 @@ var BaseAddon = class {
8482
8565
  }
8483
8566
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8484
8567
  addonSecretTree() {
8485
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8568
+ if (this._addonSecretTree === null) {
8569
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8570
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8571
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8572
+ }
8486
8573
  return this._addonSecretTree;
8487
8574
  }
8488
8575
  deviceSecretTree() {
@@ -8732,6 +8819,23 @@ function dropPerNodeFields(fields) {
8732
8819
  }
8733
8820
  return kept;
8734
8821
  }
8822
+ /** The keys of the value-holding fields, recursing into layout containers. */
8823
+ function collectValueFieldKeys(fields) {
8824
+ const collected = [];
8825
+ for (const field of fields) {
8826
+ if (field.type === "group") {
8827
+ collected.push(...collectValueFieldKeys(field.fields));
8828
+ continue;
8829
+ }
8830
+ if (field.type === "sub-tabs") {
8831
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8832
+ continue;
8833
+ }
8834
+ if (field.type === "separator" || field.type === "info") continue;
8835
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8836
+ }
8837
+ return collected;
8838
+ }
8735
8839
  function collectFieldKeys(fields) {
8736
8840
  const collected = [];
8737
8841
  for (const field of fields) {
@@ -13586,12 +13690,22 @@ var RecordIntegrationInstances = class {
13586
13690
  ...this.hooks.description,
13587
13691
  mintsId: true
13588
13692
  };
13589
- instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13693
+ /**
13694
+ * The record an id names. A caller holding the `integrations` registry id (the Integrations
13695
+ * page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
13696
+ * record id answers as it is. An id that names nothing comes back unchanged.
13697
+ */
13698
+ async resolveRecordId(id) {
13699
+ if (await this.handle.get(id) !== null) return id;
13700
+ const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
13701
+ return linked === void 0 ? id : linked.id;
13702
+ }
13703
+ instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
13590
13704
  listInstances = async () => {
13591
13705
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
13592
13706
  };
13593
13707
  getInstance = async ({ id }) => {
13594
- const record = await this.handle.get(id);
13708
+ const record = await this.handle.get(await this.resolveRecordId(id));
13595
13709
  return record === null ? null : this.viewOf(record);
13596
13710
  };
13597
13711
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
@@ -13608,7 +13722,8 @@ var RecordIntegrationInstances = class {
13608
13722
  await this.hooks.onChanged?.(minted, "created");
13609
13723
  return { id: minted };
13610
13724
  };
13611
- updateInstance = async ({ id, name, values }) => {
13725
+ updateInstance = async ({ id: askedId, name, values }) => {
13726
+ const id = await this.resolveRecordId(askedId);
13612
13727
  const patch = {};
13613
13728
  for (const [key, value] of Object.entries(values)) {
13614
13729
  if (value === void 0) continue;
@@ -13643,7 +13758,10 @@ var RecordIntegrationInstances = class {
13643
13758
  status: "unsupported",
13644
13759
  error: "This integration declares no connection test"
13645
13760
  };
13646
- return this.hooks.test(input);
13761
+ return this.hooks.test(input.id === null ? input : {
13762
+ ...input,
13763
+ id: await this.resolveRecordId(input.id)
13764
+ });
13647
13765
  };
13648
13766
  /** The flat provider object to hand to `registerProvider`. */
13649
13767
  toProvider() {
@@ -52301,12 +52419,12 @@ var deviceExportCapability = {
52301
52419
  kind: "poll"
52302
52420
  },
52303
52421
  /**
52304
- * Each export provider contributes its own per-device "Export" panel
52305
- * (enabled toggle, preferred stream, addon-specific knobs like the
52306
- * HA-MQTT discovery prefix). The three framework methods
52307
- * `getDeviceSettingsContribution` / `getDeviceLiveContribution` /
52308
- * `applyDeviceSettingsPatch` are auto-injected on the provider
52309
- * interface and routed to the device-details aggregator.
52422
+ * Each export provider MAY contribute its own per-device "Export" panel
52423
+ * (enabled toggle, preferred stream, addon-specific knobs). The three
52424
+ * framework methods are mounted on the cap router when present.
52425
+ * `IDeviceExportProvider` leaves them optional: an exporter with no
52426
+ * per-device panel omits them (a `deviceConfig` cap's UI is derived and
52427
+ * never hand-written — D14 — and an exporter is not one of those).
52310
52428
  */
52311
52429
  exposesDeviceSettings: true,
52312
52430
  methods: {
@@ -56784,11 +56902,7 @@ var CreateIntegrationInputSchema = object({
56784
56902
  info: record(string(), unknown()).optional(),
56785
56903
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56786
56904
  originStableId: string().optional(),
56787
- settings: record(string(), unknown()).optional(),
56788
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56789
- * the keychain, not the table (D787). */
56790
- secretKeys: array(string()).optional(),
56791
- skipRestart: boolean().optional()
56905
+ settings: record(string(), unknown()).optional()
56792
56906
  });
56793
56907
  var UpdateIntegrationInputSchema = object({
56794
56908
  id: string(),
@@ -56809,22 +56923,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56809
56923
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56810
56924
  kind: "mutation",
56811
56925
  auth: "admin"
56812
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56813
- id: string(),
56814
- settings: record(string(), unknown()),
56815
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56816
- secretKeys: array(string()).optional(),
56817
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56818
- skipRestart: boolean().optional()
56819
- }), object({ success: boolean() }), {
56820
- kind: "mutation",
56821
- auth: "admin"
56822
- }), method(object({ id: string() }), object({
56823
- rows: number().int().nonnegative(),
56824
- secrets: number().int().nonnegative()
56825
- }), {
56826
- kind: "mutation",
56827
- auth: "admin"
56828
56926
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56829
56927
  addonId: string(),
56830
56928
  settings: record(string(), unknown())
@@ -59801,6 +59899,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59801
59899
  }), {
59802
59900
  kind: "mutation",
59803
59901
  auth: "admin"
59902
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59903
+ rows: number().int().nonnegative(),
59904
+ secrets: number().int().nonnegative()
59905
+ }), {
59906
+ kind: "mutation",
59907
+ auth: "admin"
59908
+ }), method(object({
59909
+ integrationId: string().min(1),
59910
+ patch: record(string(), unknown())
59911
+ }), object({ written: boolean() }), {
59912
+ kind: "mutation",
59913
+ auth: "admin"
59914
+ }), method(object({
59915
+ integrationId: string().min(1),
59916
+ keys: array(string().min(1))
59917
+ }), object({
59918
+ present: boolean(),
59919
+ removed: record(string(), unknown())
59920
+ }), {
59921
+ kind: "mutation",
59922
+ auth: "admin"
59804
59923
  });
59805
59924
  method(object({
59806
59925
  sourceUrl: string(),
@@ -86621,13 +86740,6 @@ Object.freeze({
86621
86740
  access: "create",
86622
86741
  role: "admin"
86623
86742
  },
86624
- "integrations.clearSettings": {
86625
- capName: "integrations",
86626
- capScope: "system",
86627
- addonId: null,
86628
- access: "delete",
86629
- role: "admin"
86630
- },
86631
86743
  "integrations.create": {
86632
86744
  capName: "integrations",
86633
86745
  capScope: "system",
@@ -86663,13 +86775,6 @@ Object.freeze({
86663
86775
  access: "view",
86664
86776
  role: "admin"
86665
86777
  },
86666
- "integrations.getSettings": {
86667
- capName: "integrations",
86668
- capScope: "system",
86669
- addonId: null,
86670
- access: "view",
86671
- role: "admin"
86672
- },
86673
86778
  "integrations.list": {
86674
86779
  capName: "integrations",
86675
86780
  capScope: "system",
@@ -86677,13 +86782,6 @@ Object.freeze({
86677
86782
  access: "view",
86678
86783
  role: "admin"
86679
86784
  },
86680
- "integrations.setSettings": {
86681
- capName: "integrations",
86682
- capScope: "system",
86683
- addonId: null,
86684
- access: "create",
86685
- role: "admin"
86686
- },
86687
86785
  "integrations.testConnection": {
86688
86786
  capName: "integrations",
86689
86787
  capScope: "system",
@@ -87678,6 +87776,13 @@ Object.freeze({
87678
87776
  access: "view",
87679
87777
  role: "admin"
87680
87778
  },
87779
+ "migrations.clearLegacyIntegrationRows": {
87780
+ capName: "migrations",
87781
+ capScope: "system",
87782
+ addonId: null,
87783
+ access: "delete",
87784
+ role: "admin"
87785
+ },
87681
87786
  "migrations.declareOwner": {
87682
87787
  capName: "migrations",
87683
87788
  capScope: "system",
@@ -87685,6 +87790,13 @@ Object.freeze({
87685
87790
  access: "create",
87686
87791
  role: "admin"
87687
87792
  },
87793
+ "migrations.deleteLegacyIntegrationRowKeys": {
87794
+ capName: "migrations",
87795
+ capScope: "system",
87796
+ addonId: null,
87797
+ access: "delete",
87798
+ role: "admin"
87799
+ },
87688
87800
  "migrations.dryRun": {
87689
87801
  capName: "migrations",
87690
87802
  capScope: "system",
@@ -87720,6 +87832,20 @@ Object.freeze({
87720
87832
  access: "create",
87721
87833
  role: "admin"
87722
87834
  },
87835
+ "migrations.patchLegacyIntegrationRows": {
87836
+ capName: "migrations",
87837
+ capScope: "system",
87838
+ addonId: null,
87839
+ access: "create",
87840
+ role: "admin"
87841
+ },
87842
+ "migrations.readLegacyIntegrationRows": {
87843
+ capName: "migrations",
87844
+ capScope: "system",
87845
+ addonId: null,
87846
+ access: "view",
87847
+ role: "admin"
87848
+ },
87723
87849
  "migrations.restorePreMigration": {
87724
87850
  capName: "migrations",
87725
87851
  capScope: "system",
@@ -98399,13 +98525,18 @@ var HA_BROKER_KIND = "home-assistant";
98399
98525
  async function listHomeAssistantBrokers(query) {
98400
98526
  return (await query({})).filter((broker) => broker.kind === HA_BROKER_KIND);
98401
98527
  }
98402
- function haInstanceSettingsPortsOver(api) {
98528
+ function refsOfKeys(stored, keys) {
98529
+ const picked = {};
98530
+ for (const key of keys) if (Object.prototype.hasOwnProperty.call(stored, key)) picked[key] = stored[key];
98531
+ return collectSecretMarkerRefs(picked);
98532
+ }
98533
+ function haInstanceSettingsPortsOver(api, secrets = null) {
98403
98534
  const listInstances = async () => {
98404
98535
  const all = await api.list.query();
98405
98536
  const rows = [];
98406
98537
  for (const integration of all) {
98407
98538
  if (integration.addonId !== "provider-homeassistant") continue;
98408
- const settings = await api.getSettings.query({ id: integration.id });
98539
+ const settings = await api.readLegacyIntegrationRows.query({ integrationId: integration.id });
98409
98540
  const brokerId = settings["brokerId"];
98410
98541
  rows.push({
98411
98542
  integrationId: integration.id,
@@ -98415,31 +98546,26 @@ function haInstanceSettingsPortsOver(api) {
98415
98546
  }
98416
98547
  return rows;
98417
98548
  };
98549
+ const owns = async (integrationId) => {
98550
+ return (await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant");
98551
+ };
98418
98552
  return {
98419
98553
  listInstances,
98420
98554
  patchInstanceSettings: async (integrationId, patch) => {
98421
- if (!(await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant")) return false;
98422
- const stored = await api.getSettings.query({ id: integrationId });
98423
- await api.setSettings.mutate({
98424
- id: integrationId,
98425
- settings: {
98426
- ...stored,
98427
- ...patch
98428
- },
98429
- skipRestart: true
98430
- });
98431
- return true;
98555
+ if (!await owns(integrationId)) return false;
98556
+ return (await api.patchLegacyIntegrationRows.mutate({
98557
+ integrationId,
98558
+ patch: { ...patch }
98559
+ })).written;
98432
98560
  },
98433
98561
  clearInstanceSettingsKeys: async (integrationId, keys) => {
98434
- if (!(await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant")) return false;
98435
- const stored = await api.getSettings.query({ id: integrationId });
98436
- const drop = new Set(keys);
98437
- await api.setSettings.mutate({
98438
- id: integrationId,
98439
- settings: Object.fromEntries(Object.entries(stored).filter(([key]) => !drop.has(key))),
98440
- skipRestart: true
98441
- });
98442
- return true;
98562
+ if (!await owns(integrationId)) return false;
98563
+ const refs = refsOfKeys(await api.readLegacyIntegrationRows.query({ integrationId }), keys);
98564
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
98565
+ return (await api.deleteLegacyIntegrationRowKeys.mutate({
98566
+ integrationId,
98567
+ keys
98568
+ })).present;
98443
98569
  }
98444
98570
  };
98445
98571
  }
@@ -5980,7 +5980,7 @@ var ZodIssueCode = {
5980
5980
  var ZodFirstPartyTypeKind;
5981
5981
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5982
5982
  //#endregion
5983
- //#region ../types/dist/sleep-D1jjWAr8.mjs
5983
+ //#region ../types/dist/sleep-DEZaLbiu.mjs
5984
5984
  /**
5985
5985
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5986
5986
  * window to float samples (D455).
@@ -6799,6 +6799,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6799
6799
  }
6800
6800
  return out;
6801
6801
  }
6802
+ /** Every scope, in the order the guard and the docs list them. */
6803
+ var SCOPED_SETTINGS_SCOPES = [
6804
+ "cluster",
6805
+ "device-defaults",
6806
+ "node"
6807
+ ];
6802
6808
  /**
6803
6809
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6804
6810
  *
@@ -7690,6 +7696,13 @@ var BaseAddon = class {
7690
7696
  */
7691
7697
  secretStatePaths = [];
7692
7698
  _addonSecretTree = null;
7699
+ /**
7700
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7701
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7702
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7703
+ */
7704
+ _scopedByCap = /* @__PURE__ */ new Map();
7705
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7693
7706
  _deviceSecretTree = null;
7694
7707
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7695
7708
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7759,6 +7772,7 @@ var BaseAddon = class {
7759
7772
  this._ctx = context;
7760
7773
  this._secretSettings = null;
7761
7774
  }
7775
+ await this.loadScopedSchemas();
7762
7776
  await this.resolveConfig();
7763
7777
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7764
7778
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7768,6 +7782,7 @@ var BaseAddon = class {
7768
7782
  const normalized = normalizeAddonInitResult(result);
7769
7783
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7770
7784
  this._registeredCapNames = providers.map((p) => p.capability.name);
7785
+ await this.loadScopedSchemas();
7771
7786
  return normalized;
7772
7787
  }
7773
7788
  /**
@@ -7856,6 +7871,16 @@ var BaseAddon = class {
7856
7871
  schema(schema) {
7857
7872
  return schema;
7858
7873
  }
7874
+ /**
7875
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7876
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7877
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7878
+ * must already know which keys are per node. Names the providers register at init are added
7879
+ * once known.
7880
+ */
7881
+ scopedSettingsCapabilities() {
7882
+ return [];
7883
+ }
7859
7884
  /** Override to provide global-level settings UI schema. */
7860
7885
  globalSettingsSchema(_cap) {
7861
7886
  return null;
@@ -7899,7 +7924,8 @@ var BaseAddon = class {
7899
7924
  return [];
7900
7925
  }
7901
7926
  async getGlobalSettings(overlay, cap, nodeId) {
7902
- const schema = this.globalSettingsSchema(cap);
7927
+ await this.loadScopedSchemas(cap);
7928
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7903
7929
  if (!schema) return { sections: [] };
7904
7930
  const projected = await this.readRawProjectedStore(nodeId, cap);
7905
7931
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -7934,10 +7960,9 @@ var BaseAddon = class {
7934
7960
  *
7935
7961
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7936
7962
  * needs its connection fields, the default integration the addon's own sections. It is the
7937
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7938
- * unchanged (`integrations.setSettings` for an account integration).
7963
+ * SHAPE of the form only — the values live on the instance record.
7939
7964
  */
7940
- async integrationSettingsSchema(_instance) {
7965
+ async integrationFormSchema(_instance) {
7941
7966
  return null;
7942
7967
  }
7943
7968
  /**
@@ -7983,7 +8008,7 @@ var BaseAddon = class {
7983
8008
  * per-instance schema when it declares one, else its global schema (what they always showed).
7984
8009
  */
7985
8010
  async getIntegrationFormSchema(instance, nodeId) {
7986
- const scoped = await this.integrationSettingsSchema(instance);
8011
+ const scoped = await this.integrationFormSchema(instance);
7987
8012
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7988
8013
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7989
8014
  }
@@ -8031,6 +8056,7 @@ var BaseAddon = class {
8031
8056
  * resolved blob back is a no-op for every unchanged secret.
8032
8057
  */
8033
8058
  async resolveGlobalStore(nodeId, cap) {
8059
+ await this.loadScopedSchemas(cap);
8034
8060
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8035
8061
  return this.projectForNode(raw, nodeId, cap);
8036
8062
  }
@@ -8041,6 +8067,7 @@ var BaseAddon = class {
8041
8067
  }
8042
8068
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8043
8069
  async readRawProjectedStore(nodeId, cap) {
8070
+ await this.loadScopedSchemas(cap);
8044
8071
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8045
8072
  return this.projectForNode(raw, nodeId, cap);
8046
8073
  }
@@ -8143,6 +8170,7 @@ var BaseAddon = class {
8143
8170
  await this.applyGlobalPatch(patch, nodeId);
8144
8171
  }
8145
8172
  async applyGlobalPatch(barePatch, nodeId) {
8173
+ await this.loadScopedSchemas();
8146
8174
  const keys = this.perNodeKeys();
8147
8175
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8148
8176
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8156,7 +8184,8 @@ var BaseAddon = class {
8156
8184
  await this.resolveConfig();
8157
8185
  await this.onConfigChanged();
8158
8186
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8159
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8187
+ const legacy = this.globalSettingsSchema();
8188
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8160
8189
  }
8161
8190
  /**
8162
8191
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8164,14 +8193,67 @@ var BaseAddon = class {
8164
8193
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8165
8194
  * settings API behaves exactly like the legacy node-agnostic one.
8166
8195
  */
8167
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8196
+ /**
8197
+ * Load the scoped schemas of every capability this addon declares (the override and the
8198
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8199
+ * derived from the previous set. Idempotent.
8200
+ */
8201
+ async loadScopedSchemas(extraCap) {
8202
+ const caps = new Set([
8203
+ ...this.scopedSettingsCapabilities(),
8204
+ ...this._registeredCapNames,
8205
+ ...extraCap === void 0 ? [] : [extraCap]
8206
+ ]);
8207
+ let added = false;
8208
+ for (const cap of caps) {
8209
+ if (this._scopedByCap.has(cap)) continue;
8210
+ const loaded = [];
8211
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8212
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8213
+ if (schema !== null) loaded.push({
8214
+ scope,
8215
+ schema
8216
+ });
8217
+ }
8218
+ this._scopedByCap.set(cap, loaded);
8219
+ added = true;
8220
+ }
8221
+ if (!added) return;
8222
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8223
+ this._addonSecretTree = null;
8224
+ }
8225
+ /**
8226
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8227
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8228
+ */
8229
+ scopedFormSchema(cap) {
8230
+ const byId = /* @__PURE__ */ new Map();
8231
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8232
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8233
+ }
8234
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8235
+ loadedScopedSchemas(cap) {
8236
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8237
+ return [...this._scopedByCap.values()].flat();
8238
+ }
8239
+ /**
8240
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8241
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8242
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8243
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8244
+ * Call {@link loadScopedSchemas} first (the async seams do).
8245
+ */
8168
8246
  perNodeKeys(cap) {
8169
8247
  const cacheKey = cap ?? "";
8170
- const cached = this._perNodeKeysCache.get(cacheKey);
8248
+ const cached = this._nodeKeysByCap.get(cacheKey);
8171
8249
  if (cached) return cached;
8172
- const schema = this.globalSettingsSchema(cap);
8173
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8174
- this._perNodeKeysCache.set(cacheKey, keys);
8250
+ const legacy = this.globalSettingsSchema(cap);
8251
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8252
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8253
+ if (scope !== "node") continue;
8254
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8255
+ }
8256
+ this._nodeKeysByCap.set(cacheKey, keys);
8175
8257
  return keys;
8176
8258
  }
8177
8259
  /**
@@ -8181,10 +8263,9 @@ var BaseAddon = class {
8181
8263
  * time to return its response before the addon is torn down and
8182
8264
  * re-initialised by `AddonRegistryService.restartAddon`.
8183
8265
  */
8184
- maybeAutoRestart(patchKeys, schema) {
8185
- if (!schema) return;
8266
+ maybeAutoRestart(patchKeys, schemas) {
8186
8267
  const restartKeys = /* @__PURE__ */ new Set();
8187
- for (const section of schema.sections) for (const field of section.fields) {
8268
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8188
8269
  if (field.type === "separator" || field.type === "info") continue;
8189
8270
  if (field.requiresRestart) restartKeys.add(field.key);
8190
8271
  }
@@ -8380,12 +8461,14 @@ var BaseAddon = class {
8380
8461
  * here, in the addon's own process, so the hub keeps no copy of it.
8381
8462
  */
8382
8463
  async getDeclaredSettingsKeys() {
8464
+ await this.loadScopedSchemas();
8383
8465
  return { keys: [...this.declaredKeySet()].toSorted() };
8384
8466
  }
8385
8467
  declaredKeySet() {
8386
8468
  const keys = new Set(Object.keys(this.defaults));
8387
8469
  const schema = this.globalSettingsSchema();
8388
8470
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8471
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8389
8472
  for (const key of this._declaredStateKeys) keys.add(key);
8390
8473
  for (const path of this.secretStatePaths) {
8391
8474
  const root = path.split(/[.[]/, 1)[0];
@@ -8481,7 +8564,11 @@ var BaseAddon = class {
8481
8564
  }
8482
8565
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8483
8566
  addonSecretTree() {
8484
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8567
+ if (this._addonSecretTree === null) {
8568
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8569
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8570
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8571
+ }
8485
8572
  return this._addonSecretTree;
8486
8573
  }
8487
8574
  deviceSecretTree() {
@@ -8731,6 +8818,23 @@ function dropPerNodeFields(fields) {
8731
8818
  }
8732
8819
  return kept;
8733
8820
  }
8821
+ /** The keys of the value-holding fields, recursing into layout containers. */
8822
+ function collectValueFieldKeys(fields) {
8823
+ const collected = [];
8824
+ for (const field of fields) {
8825
+ if (field.type === "group") {
8826
+ collected.push(...collectValueFieldKeys(field.fields));
8827
+ continue;
8828
+ }
8829
+ if (field.type === "sub-tabs") {
8830
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8831
+ continue;
8832
+ }
8833
+ if (field.type === "separator" || field.type === "info") continue;
8834
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8835
+ }
8836
+ return collected;
8837
+ }
8734
8838
  function collectFieldKeys(fields) {
8735
8839
  const collected = [];
8736
8840
  for (const field of fields) {
@@ -13585,12 +13689,22 @@ var RecordIntegrationInstances = class {
13585
13689
  ...this.hooks.description,
13586
13690
  mintsId: true
13587
13691
  };
13588
- instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13692
+ /**
13693
+ * The record an id names. A caller holding the `integrations` registry id (the Integrations
13694
+ * page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
13695
+ * record id answers as it is. An id that names nothing comes back unchanged.
13696
+ */
13697
+ async resolveRecordId(id) {
13698
+ if (await this.handle.get(id) !== null) return id;
13699
+ const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
13700
+ return linked === void 0 ? id : linked.id;
13701
+ }
13702
+ instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
13589
13703
  listInstances = async () => {
13590
13704
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
13591
13705
  };
13592
13706
  getInstance = async ({ id }) => {
13593
- const record = await this.handle.get(id);
13707
+ const record = await this.handle.get(await this.resolveRecordId(id));
13594
13708
  return record === null ? null : this.viewOf(record);
13595
13709
  };
13596
13710
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
@@ -13607,7 +13721,8 @@ var RecordIntegrationInstances = class {
13607
13721
  await this.hooks.onChanged?.(minted, "created");
13608
13722
  return { id: minted };
13609
13723
  };
13610
- updateInstance = async ({ id, name, values }) => {
13724
+ updateInstance = async ({ id: askedId, name, values }) => {
13725
+ const id = await this.resolveRecordId(askedId);
13611
13726
  const patch = {};
13612
13727
  for (const [key, value] of Object.entries(values)) {
13613
13728
  if (value === void 0) continue;
@@ -13642,7 +13757,10 @@ var RecordIntegrationInstances = class {
13642
13757
  status: "unsupported",
13643
13758
  error: "This integration declares no connection test"
13644
13759
  };
13645
- return this.hooks.test(input);
13760
+ return this.hooks.test(input.id === null ? input : {
13761
+ ...input,
13762
+ id: await this.resolveRecordId(input.id)
13763
+ });
13646
13764
  };
13647
13765
  /** The flat provider object to hand to `registerProvider`. */
13648
13766
  toProvider() {
@@ -52300,12 +52418,12 @@ var deviceExportCapability = {
52300
52418
  kind: "poll"
52301
52419
  },
52302
52420
  /**
52303
- * Each export provider contributes its own per-device "Export" panel
52304
- * (enabled toggle, preferred stream, addon-specific knobs like the
52305
- * HA-MQTT discovery prefix). The three framework methods
52306
- * `getDeviceSettingsContribution` / `getDeviceLiveContribution` /
52307
- * `applyDeviceSettingsPatch` are auto-injected on the provider
52308
- * interface and routed to the device-details aggregator.
52421
+ * Each export provider MAY contribute its own per-device "Export" panel
52422
+ * (enabled toggle, preferred stream, addon-specific knobs). The three
52423
+ * framework methods are mounted on the cap router when present.
52424
+ * `IDeviceExportProvider` leaves them optional: an exporter with no
52425
+ * per-device panel omits them (a `deviceConfig` cap's UI is derived and
52426
+ * never hand-written — D14 — and an exporter is not one of those).
52309
52427
  */
52310
52428
  exposesDeviceSettings: true,
52311
52429
  methods: {
@@ -56783,11 +56901,7 @@ var CreateIntegrationInputSchema = object({
56783
56901
  info: record(string(), unknown()).optional(),
56784
56902
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56785
56903
  originStableId: string().optional(),
56786
- settings: record(string(), unknown()).optional(),
56787
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56788
- * the keychain, not the table (D787). */
56789
- secretKeys: array(string()).optional(),
56790
- skipRestart: boolean().optional()
56904
+ settings: record(string(), unknown()).optional()
56791
56905
  });
56792
56906
  var UpdateIntegrationInputSchema = object({
56793
56907
  id: string(),
@@ -56808,22 +56922,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56808
56922
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56809
56923
  kind: "mutation",
56810
56924
  auth: "admin"
56811
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56812
- id: string(),
56813
- settings: record(string(), unknown()),
56814
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56815
- secretKeys: array(string()).optional(),
56816
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56817
- skipRestart: boolean().optional()
56818
- }), object({ success: boolean() }), {
56819
- kind: "mutation",
56820
- auth: "admin"
56821
- }), method(object({ id: string() }), object({
56822
- rows: number().int().nonnegative(),
56823
- secrets: number().int().nonnegative()
56824
- }), {
56825
- kind: "mutation",
56826
- auth: "admin"
56827
56925
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56828
56926
  addonId: string(),
56829
56927
  settings: record(string(), unknown())
@@ -59800,6 +59898,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59800
59898
  }), {
59801
59899
  kind: "mutation",
59802
59900
  auth: "admin"
59901
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59902
+ rows: number().int().nonnegative(),
59903
+ secrets: number().int().nonnegative()
59904
+ }), {
59905
+ kind: "mutation",
59906
+ auth: "admin"
59907
+ }), method(object({
59908
+ integrationId: string().min(1),
59909
+ patch: record(string(), unknown())
59910
+ }), object({ written: boolean() }), {
59911
+ kind: "mutation",
59912
+ auth: "admin"
59913
+ }), method(object({
59914
+ integrationId: string().min(1),
59915
+ keys: array(string().min(1))
59916
+ }), object({
59917
+ present: boolean(),
59918
+ removed: record(string(), unknown())
59919
+ }), {
59920
+ kind: "mutation",
59921
+ auth: "admin"
59803
59922
  });
59804
59923
  method(object({
59805
59924
  sourceUrl: string(),
@@ -86620,13 +86739,6 @@ Object.freeze({
86620
86739
  access: "create",
86621
86740
  role: "admin"
86622
86741
  },
86623
- "integrations.clearSettings": {
86624
- capName: "integrations",
86625
- capScope: "system",
86626
- addonId: null,
86627
- access: "delete",
86628
- role: "admin"
86629
- },
86630
86742
  "integrations.create": {
86631
86743
  capName: "integrations",
86632
86744
  capScope: "system",
@@ -86662,13 +86774,6 @@ Object.freeze({
86662
86774
  access: "view",
86663
86775
  role: "admin"
86664
86776
  },
86665
- "integrations.getSettings": {
86666
- capName: "integrations",
86667
- capScope: "system",
86668
- addonId: null,
86669
- access: "view",
86670
- role: "admin"
86671
- },
86672
86777
  "integrations.list": {
86673
86778
  capName: "integrations",
86674
86779
  capScope: "system",
@@ -86676,13 +86781,6 @@ Object.freeze({
86676
86781
  access: "view",
86677
86782
  role: "admin"
86678
86783
  },
86679
- "integrations.setSettings": {
86680
- capName: "integrations",
86681
- capScope: "system",
86682
- addonId: null,
86683
- access: "create",
86684
- role: "admin"
86685
- },
86686
86784
  "integrations.testConnection": {
86687
86785
  capName: "integrations",
86688
86786
  capScope: "system",
@@ -87677,6 +87775,13 @@ Object.freeze({
87677
87775
  access: "view",
87678
87776
  role: "admin"
87679
87777
  },
87778
+ "migrations.clearLegacyIntegrationRows": {
87779
+ capName: "migrations",
87780
+ capScope: "system",
87781
+ addonId: null,
87782
+ access: "delete",
87783
+ role: "admin"
87784
+ },
87680
87785
  "migrations.declareOwner": {
87681
87786
  capName: "migrations",
87682
87787
  capScope: "system",
@@ -87684,6 +87789,13 @@ Object.freeze({
87684
87789
  access: "create",
87685
87790
  role: "admin"
87686
87791
  },
87792
+ "migrations.deleteLegacyIntegrationRowKeys": {
87793
+ capName: "migrations",
87794
+ capScope: "system",
87795
+ addonId: null,
87796
+ access: "delete",
87797
+ role: "admin"
87798
+ },
87687
87799
  "migrations.dryRun": {
87688
87800
  capName: "migrations",
87689
87801
  capScope: "system",
@@ -87719,6 +87831,20 @@ Object.freeze({
87719
87831
  access: "create",
87720
87832
  role: "admin"
87721
87833
  },
87834
+ "migrations.patchLegacyIntegrationRows": {
87835
+ capName: "migrations",
87836
+ capScope: "system",
87837
+ addonId: null,
87838
+ access: "create",
87839
+ role: "admin"
87840
+ },
87841
+ "migrations.readLegacyIntegrationRows": {
87842
+ capName: "migrations",
87843
+ capScope: "system",
87844
+ addonId: null,
87845
+ access: "view",
87846
+ role: "admin"
87847
+ },
87722
87848
  "migrations.restorePreMigration": {
87723
87849
  capName: "migrations",
87724
87850
  capScope: "system",
@@ -98398,13 +98524,18 @@ var HA_BROKER_KIND = "home-assistant";
98398
98524
  async function listHomeAssistantBrokers(query) {
98399
98525
  return (await query({})).filter((broker) => broker.kind === HA_BROKER_KIND);
98400
98526
  }
98401
- function haInstanceSettingsPortsOver(api) {
98527
+ function refsOfKeys(stored, keys) {
98528
+ const picked = {};
98529
+ for (const key of keys) if (Object.prototype.hasOwnProperty.call(stored, key)) picked[key] = stored[key];
98530
+ return collectSecretMarkerRefs(picked);
98531
+ }
98532
+ function haInstanceSettingsPortsOver(api, secrets = null) {
98402
98533
  const listInstances = async () => {
98403
98534
  const all = await api.list.query();
98404
98535
  const rows = [];
98405
98536
  for (const integration of all) {
98406
98537
  if (integration.addonId !== "provider-homeassistant") continue;
98407
- const settings = await api.getSettings.query({ id: integration.id });
98538
+ const settings = await api.readLegacyIntegrationRows.query({ integrationId: integration.id });
98408
98539
  const brokerId = settings["brokerId"];
98409
98540
  rows.push({
98410
98541
  integrationId: integration.id,
@@ -98414,31 +98545,26 @@ function haInstanceSettingsPortsOver(api) {
98414
98545
  }
98415
98546
  return rows;
98416
98547
  };
98548
+ const owns = async (integrationId) => {
98549
+ return (await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant");
98550
+ };
98417
98551
  return {
98418
98552
  listInstances,
98419
98553
  patchInstanceSettings: async (integrationId, patch) => {
98420
- if (!(await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant")) return false;
98421
- const stored = await api.getSettings.query({ id: integrationId });
98422
- await api.setSettings.mutate({
98423
- id: integrationId,
98424
- settings: {
98425
- ...stored,
98426
- ...patch
98427
- },
98428
- skipRestart: true
98429
- });
98430
- return true;
98554
+ if (!await owns(integrationId)) return false;
98555
+ return (await api.patchLegacyIntegrationRows.mutate({
98556
+ integrationId,
98557
+ patch: { ...patch }
98558
+ })).written;
98431
98559
  },
98432
98560
  clearInstanceSettingsKeys: async (integrationId, keys) => {
98433
- if (!(await api.list.query()).some((i) => i.id === integrationId && i.addonId === "provider-homeassistant")) return false;
98434
- const stored = await api.getSettings.query({ id: integrationId });
98435
- const drop = new Set(keys);
98436
- await api.setSettings.mutate({
98437
- id: integrationId,
98438
- settings: Object.fromEntries(Object.entries(stored).filter(([key]) => !drop.has(key))),
98439
- skipRestart: true
98440
- });
98441
- return true;
98561
+ if (!await owns(integrationId)) return false;
98562
+ const refs = refsOfKeys(await api.readLegacyIntegrationRows.query({ integrationId }), keys);
98563
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
98564
+ return (await api.deleteLegacyIntegrationRowKeys.mutate({
98565
+ integrationId,
98566
+ keys
98567
+ })).present;
98442
98568
  }
98443
98569
  };
98444
98570
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.660",
3
+ "version": "1.2.662",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -137,7 +137,7 @@
137
137
  "description": "Device provider (import) + native entity export (push). Two addons, one bundle, separate processes."
138
138
  },
139
139
  "requires": {
140
- "server": ">=1.2.769"
140
+ "server": ">=1.2.777"
141
141
  }
142
142
  },
143
143
  "files": [