@camstack/addon-matter-broker 0.2.157 → 0.2.158
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 +6 -0
- package/dist/addon.js +119 -14
- package/dist/addon.mjs +119 -14
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.158] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The Matter manifest declares multiple instances, as the integration already allowed.
|
|
15
|
+
|
|
10
16
|
## [0.2.157] - 2026-11-05
|
|
11
17
|
|
|
12
18
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -5995,7 +5995,7 @@ var ZodIssueCode = {
|
|
|
5995
5995
|
var ZodFirstPartyTypeKind;
|
|
5996
5996
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5997
5997
|
//#endregion
|
|
5998
|
-
//#region ../types/dist/sleep-
|
|
5998
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5999
5999
|
/**
|
|
6000
6000
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6001
6001
|
* window to float samples (D455).
|
|
@@ -6804,6 +6804,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6804
6804
|
}
|
|
6805
6805
|
return out;
|
|
6806
6806
|
}
|
|
6807
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6808
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6809
|
+
"cluster",
|
|
6810
|
+
"device-defaults",
|
|
6811
|
+
"node"
|
|
6812
|
+
];
|
|
6807
6813
|
/**
|
|
6808
6814
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6809
6815
|
*
|
|
@@ -7695,6 +7701,13 @@ var BaseAddon = class {
|
|
|
7695
7701
|
*/
|
|
7696
7702
|
secretStatePaths = [];
|
|
7697
7703
|
_addonSecretTree = null;
|
|
7704
|
+
/**
|
|
7705
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7706
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7707
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7708
|
+
*/
|
|
7709
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7710
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7698
7711
|
_deviceSecretTree = null;
|
|
7699
7712
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7700
7713
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7764,6 +7777,7 @@ var BaseAddon = class {
|
|
|
7764
7777
|
this._ctx = context;
|
|
7765
7778
|
this._secretSettings = null;
|
|
7766
7779
|
}
|
|
7780
|
+
await this.loadScopedSchemas();
|
|
7767
7781
|
await this.resolveConfig();
|
|
7768
7782
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7769
7783
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7773,6 +7787,7 @@ var BaseAddon = class {
|
|
|
7773
7787
|
const normalized = normalizeAddonInitResult(result);
|
|
7774
7788
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7775
7789
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7790
|
+
await this.loadScopedSchemas();
|
|
7776
7791
|
return normalized;
|
|
7777
7792
|
}
|
|
7778
7793
|
/**
|
|
@@ -7861,6 +7876,16 @@ var BaseAddon = class {
|
|
|
7861
7876
|
schema(schema) {
|
|
7862
7877
|
return schema;
|
|
7863
7878
|
}
|
|
7879
|
+
/**
|
|
7880
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7881
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7882
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7883
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7884
|
+
* once known.
|
|
7885
|
+
*/
|
|
7886
|
+
scopedSettingsCapabilities() {
|
|
7887
|
+
return [];
|
|
7888
|
+
}
|
|
7864
7889
|
/** Override to provide global-level settings UI schema. */
|
|
7865
7890
|
globalSettingsSchema(_cap) {
|
|
7866
7891
|
return null;
|
|
@@ -7904,7 +7929,8 @@ var BaseAddon = class {
|
|
|
7904
7929
|
return [];
|
|
7905
7930
|
}
|
|
7906
7931
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7907
|
-
|
|
7932
|
+
await this.loadScopedSchemas(cap);
|
|
7933
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7908
7934
|
if (!schema) return { sections: [] };
|
|
7909
7935
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7910
7936
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -8035,6 +8061,7 @@ var BaseAddon = class {
|
|
|
8035
8061
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8036
8062
|
*/
|
|
8037
8063
|
async resolveGlobalStore(nodeId, cap) {
|
|
8064
|
+
await this.loadScopedSchemas(cap);
|
|
8038
8065
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8039
8066
|
return this.projectForNode(raw, nodeId, cap);
|
|
8040
8067
|
}
|
|
@@ -8045,6 +8072,7 @@ var BaseAddon = class {
|
|
|
8045
8072
|
}
|
|
8046
8073
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8047
8074
|
async readRawProjectedStore(nodeId, cap) {
|
|
8075
|
+
await this.loadScopedSchemas(cap);
|
|
8048
8076
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8049
8077
|
return this.projectForNode(raw, nodeId, cap);
|
|
8050
8078
|
}
|
|
@@ -8147,6 +8175,7 @@ var BaseAddon = class {
|
|
|
8147
8175
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8148
8176
|
}
|
|
8149
8177
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8178
|
+
await this.loadScopedSchemas();
|
|
8150
8179
|
const keys = this.perNodeKeys();
|
|
8151
8180
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8152
8181
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8160,7 +8189,8 @@ var BaseAddon = class {
|
|
|
8160
8189
|
await this.resolveConfig();
|
|
8161
8190
|
await this.onConfigChanged();
|
|
8162
8191
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8163
|
-
|
|
8192
|
+
const legacy = this.globalSettingsSchema();
|
|
8193
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8164
8194
|
}
|
|
8165
8195
|
/**
|
|
8166
8196
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8168,14 +8198,67 @@ var BaseAddon = class {
|
|
|
8168
8198
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8169
8199
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8170
8200
|
*/
|
|
8171
|
-
|
|
8201
|
+
/**
|
|
8202
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8203
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8204
|
+
* derived from the previous set. Idempotent.
|
|
8205
|
+
*/
|
|
8206
|
+
async loadScopedSchemas(extraCap) {
|
|
8207
|
+
const caps = new Set([
|
|
8208
|
+
...this.scopedSettingsCapabilities(),
|
|
8209
|
+
...this._registeredCapNames,
|
|
8210
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8211
|
+
]);
|
|
8212
|
+
let added = false;
|
|
8213
|
+
for (const cap of caps) {
|
|
8214
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8215
|
+
const loaded = [];
|
|
8216
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8217
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8218
|
+
if (schema !== null) loaded.push({
|
|
8219
|
+
scope,
|
|
8220
|
+
schema
|
|
8221
|
+
});
|
|
8222
|
+
}
|
|
8223
|
+
this._scopedByCap.set(cap, loaded);
|
|
8224
|
+
added = true;
|
|
8225
|
+
}
|
|
8226
|
+
if (!added) return;
|
|
8227
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8228
|
+
this._addonSecretTree = null;
|
|
8229
|
+
}
|
|
8230
|
+
/**
|
|
8231
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8232
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8233
|
+
*/
|
|
8234
|
+
scopedFormSchema(cap) {
|
|
8235
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8236
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8237
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8238
|
+
}
|
|
8239
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8240
|
+
loadedScopedSchemas(cap) {
|
|
8241
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8242
|
+
return [...this._scopedByCap.values()].flat();
|
|
8243
|
+
}
|
|
8244
|
+
/**
|
|
8245
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8246
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8247
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8248
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8249
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8250
|
+
*/
|
|
8172
8251
|
perNodeKeys(cap) {
|
|
8173
8252
|
const cacheKey = cap ?? "";
|
|
8174
|
-
const cached = this.
|
|
8253
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8175
8254
|
if (cached) return cached;
|
|
8176
|
-
const
|
|
8177
|
-
const keys = new Set(
|
|
8178
|
-
this.
|
|
8255
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8256
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8257
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8258
|
+
if (scope !== "node") continue;
|
|
8259
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8260
|
+
}
|
|
8261
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8179
8262
|
return keys;
|
|
8180
8263
|
}
|
|
8181
8264
|
/**
|
|
@@ -8185,10 +8268,9 @@ var BaseAddon = class {
|
|
|
8185
8268
|
* time to return its response before the addon is torn down and
|
|
8186
8269
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8187
8270
|
*/
|
|
8188
|
-
maybeAutoRestart(patchKeys,
|
|
8189
|
-
if (!schema) return;
|
|
8271
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8190
8272
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8191
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8273
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8192
8274
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8193
8275
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8194
8276
|
}
|
|
@@ -8384,12 +8466,14 @@ var BaseAddon = class {
|
|
|
8384
8466
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8385
8467
|
*/
|
|
8386
8468
|
async getDeclaredSettingsKeys() {
|
|
8469
|
+
await this.loadScopedSchemas();
|
|
8387
8470
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8388
8471
|
}
|
|
8389
8472
|
declaredKeySet() {
|
|
8390
8473
|
const keys = new Set(Object.keys(this.defaults));
|
|
8391
8474
|
const schema = this.globalSettingsSchema();
|
|
8392
8475
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8476
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8393
8477
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8394
8478
|
for (const path of this.secretStatePaths) {
|
|
8395
8479
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8485,7 +8569,11 @@ var BaseAddon = class {
|
|
|
8485
8569
|
}
|
|
8486
8570
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8487
8571
|
addonSecretTree() {
|
|
8488
|
-
this._addonSecretTree
|
|
8572
|
+
if (this._addonSecretTree === null) {
|
|
8573
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8574
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8575
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8576
|
+
}
|
|
8489
8577
|
return this._addonSecretTree;
|
|
8490
8578
|
}
|
|
8491
8579
|
deviceSecretTree() {
|
|
@@ -8735,6 +8823,23 @@ function dropPerNodeFields(fields) {
|
|
|
8735
8823
|
}
|
|
8736
8824
|
return kept;
|
|
8737
8825
|
}
|
|
8826
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8827
|
+
function collectValueFieldKeys(fields) {
|
|
8828
|
+
const collected = [];
|
|
8829
|
+
for (const field of fields) {
|
|
8830
|
+
if (field.type === "group") {
|
|
8831
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8832
|
+
continue;
|
|
8833
|
+
}
|
|
8834
|
+
if (field.type === "sub-tabs") {
|
|
8835
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8836
|
+
continue;
|
|
8837
|
+
}
|
|
8838
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8839
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8840
|
+
}
|
|
8841
|
+
return collected;
|
|
8842
|
+
}
|
|
8738
8843
|
function collectFieldKeys(fields) {
|
|
8739
8844
|
const collected = [];
|
|
8740
8845
|
for (const field of fields) {
|
|
@@ -207386,8 +207491,8 @@ function sameConnection(a, b) {
|
|
|
207386
207491
|
* Manages N live Matter fabric connections ("brokers"), each backed by a
|
|
207387
207492
|
* {@link MatterIntegrationManager}. Allocates stable ids (`matter_001`, …), CRUD,
|
|
207388
207493
|
* an integration FK index, lifecycle helpers. Mirrors the Ecowitt / Dreame
|
|
207389
|
-
* registry.
|
|
207390
|
-
* fabric,
|
|
207494
|
+
* registry. The integration is `instanceMode: multiple`: the common case is one
|
|
207495
|
+
* fabric, and an operator may add more.
|
|
207391
207496
|
*/
|
|
207392
207497
|
var MatterBrokerRegistry = class {
|
|
207393
207498
|
#logger;
|
package/dist/addon.mjs
CHANGED
|
@@ -5993,7 +5993,7 @@ var ZodIssueCode = {
|
|
|
5993
5993
|
var ZodFirstPartyTypeKind;
|
|
5994
5994
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5995
5995
|
//#endregion
|
|
5996
|
-
//#region ../types/dist/sleep-
|
|
5996
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5997
5997
|
/**
|
|
5998
5998
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5999
5999
|
* window to float samples (D455).
|
|
@@ -6802,6 +6802,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6802
6802
|
}
|
|
6803
6803
|
return out;
|
|
6804
6804
|
}
|
|
6805
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6806
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6807
|
+
"cluster",
|
|
6808
|
+
"device-defaults",
|
|
6809
|
+
"node"
|
|
6810
|
+
];
|
|
6805
6811
|
/**
|
|
6806
6812
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6807
6813
|
*
|
|
@@ -7693,6 +7699,13 @@ var BaseAddon = class {
|
|
|
7693
7699
|
*/
|
|
7694
7700
|
secretStatePaths = [];
|
|
7695
7701
|
_addonSecretTree = null;
|
|
7702
|
+
/**
|
|
7703
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7704
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7705
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7706
|
+
*/
|
|
7707
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7708
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7696
7709
|
_deviceSecretTree = null;
|
|
7697
7710
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7698
7711
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7762,6 +7775,7 @@ var BaseAddon = class {
|
|
|
7762
7775
|
this._ctx = context;
|
|
7763
7776
|
this._secretSettings = null;
|
|
7764
7777
|
}
|
|
7778
|
+
await this.loadScopedSchemas();
|
|
7765
7779
|
await this.resolveConfig();
|
|
7766
7780
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7767
7781
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7771,6 +7785,7 @@ var BaseAddon = class {
|
|
|
7771
7785
|
const normalized = normalizeAddonInitResult(result);
|
|
7772
7786
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7773
7787
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7788
|
+
await this.loadScopedSchemas();
|
|
7774
7789
|
return normalized;
|
|
7775
7790
|
}
|
|
7776
7791
|
/**
|
|
@@ -7859,6 +7874,16 @@ var BaseAddon = class {
|
|
|
7859
7874
|
schema(schema) {
|
|
7860
7875
|
return schema;
|
|
7861
7876
|
}
|
|
7877
|
+
/**
|
|
7878
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7879
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7880
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7881
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7882
|
+
* once known.
|
|
7883
|
+
*/
|
|
7884
|
+
scopedSettingsCapabilities() {
|
|
7885
|
+
return [];
|
|
7886
|
+
}
|
|
7862
7887
|
/** Override to provide global-level settings UI schema. */
|
|
7863
7888
|
globalSettingsSchema(_cap) {
|
|
7864
7889
|
return null;
|
|
@@ -7902,7 +7927,8 @@ var BaseAddon = class {
|
|
|
7902
7927
|
return [];
|
|
7903
7928
|
}
|
|
7904
7929
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7905
|
-
|
|
7930
|
+
await this.loadScopedSchemas(cap);
|
|
7931
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7906
7932
|
if (!schema) return { sections: [] };
|
|
7907
7933
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7908
7934
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -8033,6 +8059,7 @@ var BaseAddon = class {
|
|
|
8033
8059
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8034
8060
|
*/
|
|
8035
8061
|
async resolveGlobalStore(nodeId, cap) {
|
|
8062
|
+
await this.loadScopedSchemas(cap);
|
|
8036
8063
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8037
8064
|
return this.projectForNode(raw, nodeId, cap);
|
|
8038
8065
|
}
|
|
@@ -8043,6 +8070,7 @@ var BaseAddon = class {
|
|
|
8043
8070
|
}
|
|
8044
8071
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8045
8072
|
async readRawProjectedStore(nodeId, cap) {
|
|
8073
|
+
await this.loadScopedSchemas(cap);
|
|
8046
8074
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8047
8075
|
return this.projectForNode(raw, nodeId, cap);
|
|
8048
8076
|
}
|
|
@@ -8145,6 +8173,7 @@ var BaseAddon = class {
|
|
|
8145
8173
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8146
8174
|
}
|
|
8147
8175
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8176
|
+
await this.loadScopedSchemas();
|
|
8148
8177
|
const keys = this.perNodeKeys();
|
|
8149
8178
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8150
8179
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8158,7 +8187,8 @@ var BaseAddon = class {
|
|
|
8158
8187
|
await this.resolveConfig();
|
|
8159
8188
|
await this.onConfigChanged();
|
|
8160
8189
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8161
|
-
|
|
8190
|
+
const legacy = this.globalSettingsSchema();
|
|
8191
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8162
8192
|
}
|
|
8163
8193
|
/**
|
|
8164
8194
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8166,14 +8196,67 @@ var BaseAddon = class {
|
|
|
8166
8196
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8167
8197
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8168
8198
|
*/
|
|
8169
|
-
|
|
8199
|
+
/**
|
|
8200
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8201
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8202
|
+
* derived from the previous set. Idempotent.
|
|
8203
|
+
*/
|
|
8204
|
+
async loadScopedSchemas(extraCap) {
|
|
8205
|
+
const caps = new Set([
|
|
8206
|
+
...this.scopedSettingsCapabilities(),
|
|
8207
|
+
...this._registeredCapNames,
|
|
8208
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8209
|
+
]);
|
|
8210
|
+
let added = false;
|
|
8211
|
+
for (const cap of caps) {
|
|
8212
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8213
|
+
const loaded = [];
|
|
8214
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8215
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8216
|
+
if (schema !== null) loaded.push({
|
|
8217
|
+
scope,
|
|
8218
|
+
schema
|
|
8219
|
+
});
|
|
8220
|
+
}
|
|
8221
|
+
this._scopedByCap.set(cap, loaded);
|
|
8222
|
+
added = true;
|
|
8223
|
+
}
|
|
8224
|
+
if (!added) return;
|
|
8225
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8226
|
+
this._addonSecretTree = null;
|
|
8227
|
+
}
|
|
8228
|
+
/**
|
|
8229
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8230
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8231
|
+
*/
|
|
8232
|
+
scopedFormSchema(cap) {
|
|
8233
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8234
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8235
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8236
|
+
}
|
|
8237
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8238
|
+
loadedScopedSchemas(cap) {
|
|
8239
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8240
|
+
return [...this._scopedByCap.values()].flat();
|
|
8241
|
+
}
|
|
8242
|
+
/**
|
|
8243
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8244
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8245
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8246
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8247
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8248
|
+
*/
|
|
8170
8249
|
perNodeKeys(cap) {
|
|
8171
8250
|
const cacheKey = cap ?? "";
|
|
8172
|
-
const cached = this.
|
|
8251
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8173
8252
|
if (cached) return cached;
|
|
8174
|
-
const
|
|
8175
|
-
const keys = new Set(
|
|
8176
|
-
this.
|
|
8253
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8254
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8255
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8256
|
+
if (scope !== "node") continue;
|
|
8257
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8258
|
+
}
|
|
8259
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8177
8260
|
return keys;
|
|
8178
8261
|
}
|
|
8179
8262
|
/**
|
|
@@ -8183,10 +8266,9 @@ var BaseAddon = class {
|
|
|
8183
8266
|
* time to return its response before the addon is torn down and
|
|
8184
8267
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8185
8268
|
*/
|
|
8186
|
-
maybeAutoRestart(patchKeys,
|
|
8187
|
-
if (!schema) return;
|
|
8269
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8188
8270
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8189
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8271
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8190
8272
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8191
8273
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8192
8274
|
}
|
|
@@ -8382,12 +8464,14 @@ var BaseAddon = class {
|
|
|
8382
8464
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8383
8465
|
*/
|
|
8384
8466
|
async getDeclaredSettingsKeys() {
|
|
8467
|
+
await this.loadScopedSchemas();
|
|
8385
8468
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8386
8469
|
}
|
|
8387
8470
|
declaredKeySet() {
|
|
8388
8471
|
const keys = new Set(Object.keys(this.defaults));
|
|
8389
8472
|
const schema = this.globalSettingsSchema();
|
|
8390
8473
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8474
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8391
8475
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8392
8476
|
for (const path of this.secretStatePaths) {
|
|
8393
8477
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8483,7 +8567,11 @@ var BaseAddon = class {
|
|
|
8483
8567
|
}
|
|
8484
8568
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8485
8569
|
addonSecretTree() {
|
|
8486
|
-
this._addonSecretTree
|
|
8570
|
+
if (this._addonSecretTree === null) {
|
|
8571
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8572
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8573
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8574
|
+
}
|
|
8487
8575
|
return this._addonSecretTree;
|
|
8488
8576
|
}
|
|
8489
8577
|
deviceSecretTree() {
|
|
@@ -8733,6 +8821,23 @@ function dropPerNodeFields(fields) {
|
|
|
8733
8821
|
}
|
|
8734
8822
|
return kept;
|
|
8735
8823
|
}
|
|
8824
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8825
|
+
function collectValueFieldKeys(fields) {
|
|
8826
|
+
const collected = [];
|
|
8827
|
+
for (const field of fields) {
|
|
8828
|
+
if (field.type === "group") {
|
|
8829
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8830
|
+
continue;
|
|
8831
|
+
}
|
|
8832
|
+
if (field.type === "sub-tabs") {
|
|
8833
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8834
|
+
continue;
|
|
8835
|
+
}
|
|
8836
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8837
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8838
|
+
}
|
|
8839
|
+
return collected;
|
|
8840
|
+
}
|
|
8736
8841
|
function collectFieldKeys(fields) {
|
|
8737
8842
|
const collected = [];
|
|
8738
8843
|
for (const field of fields) {
|
|
@@ -207384,8 +207489,8 @@ function sameConnection(a, b) {
|
|
|
207384
207489
|
* Manages N live Matter fabric connections ("brokers"), each backed by a
|
|
207385
207490
|
* {@link MatterIntegrationManager}. Allocates stable ids (`matter_001`, …), CRUD,
|
|
207386
207491
|
* an integration FK index, lifecycle helpers. Mirrors the Ecowitt / Dreame
|
|
207387
|
-
* registry.
|
|
207388
|
-
* fabric,
|
|
207492
|
+
* registry. The integration is `instanceMode: multiple`: the common case is one
|
|
207493
|
+
* fabric, and an operator may add more.
|
|
207389
207494
|
*/
|
|
207390
207495
|
var MatterBrokerRegistry = class {
|
|
207391
207496
|
#logger;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-matter-broker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.158",
|
|
4
4
|
"description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"entry": "./dist/addon.js",
|
|
44
44
|
"icon": "assets/icon.svg",
|
|
45
45
|
"color": "#3b6ea5",
|
|
46
|
-
"instanceMode": "
|
|
46
|
+
"instanceMode": "multiple",
|
|
47
47
|
"brokerKind": "matter",
|
|
48
48
|
"execution": {
|
|
49
49
|
"placement": "hub-only"
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
],
|
|
67
67
|
"requires": {
|
|
68
|
-
"server": ">=1.2.
|
|
68
|
+
"server": ">=1.2.776"
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"files": [
|