@camstack/addon-matter-broker 0.2.156 → 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 +12 -0
- package/dist/addon.js +212 -71
- package/dist/addon.mjs +212 -71
- package/package.json +3 -3
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
|
+
## [0.2.158] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The Matter manifest declares multiple instances, as the integration already allowed.
|
|
15
|
+
|
|
16
|
+
## [0.2.157] - 2026-11-05
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- A Matter integration links to its fabric through the fabric record
|
|
21
|
+
|
|
10
22
|
## [0.2.156] - 2026-11-04
|
|
11
23
|
|
|
12
24
|
### 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).
|
|
@@ -6595,7 +6595,7 @@ var NOT_RUN = {
|
|
|
6595
6595
|
* throws: an unreachable or refusing engine is logged and reported `declared: false`,
|
|
6596
6596
|
* because the hub (and this addon's boot) must not hang on a migration.
|
|
6597
6597
|
*/
|
|
6598
|
-
async function runAddonMigrations(host, migrations, logger) {
|
|
6598
|
+
async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
6599
6599
|
const first = migrations[0];
|
|
6600
6600
|
if (first === void 0) return NOT_RUN;
|
|
6601
6601
|
const owner = first.meta.owner;
|
|
@@ -6624,7 +6624,9 @@ async function runAddonMigrations(host, migrations, logger) {
|
|
|
6624
6624
|
else if (status === "blocked") blocked.push(id);
|
|
6625
6625
|
else skipped.push(id);
|
|
6626
6626
|
};
|
|
6627
|
+
const selected = options.only === void 0 ? null : new Set(options.only);
|
|
6627
6628
|
for (const step of declaration.steps) {
|
|
6629
|
+
if (selected !== null && !selected.has(step.id)) continue;
|
|
6628
6630
|
const bound = byId.get(step.id);
|
|
6629
6631
|
if (bound === void 0 || step.action === "skip") {
|
|
6630
6632
|
skipped.push(step.id);
|
|
@@ -6802,6 +6804,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6802
6804
|
}
|
|
6803
6805
|
return out;
|
|
6804
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
|
+
];
|
|
6805
6813
|
/**
|
|
6806
6814
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6807
6815
|
*
|
|
@@ -7693,6 +7701,13 @@ var BaseAddon = class {
|
|
|
7693
7701
|
*/
|
|
7694
7702
|
secretStatePaths = [];
|
|
7695
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();
|
|
7696
7711
|
_deviceSecretTree = null;
|
|
7697
7712
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7698
7713
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7762,6 +7777,7 @@ var BaseAddon = class {
|
|
|
7762
7777
|
this._ctx = context;
|
|
7763
7778
|
this._secretSettings = null;
|
|
7764
7779
|
}
|
|
7780
|
+
await this.loadScopedSchemas();
|
|
7765
7781
|
await this.resolveConfig();
|
|
7766
7782
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7767
7783
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7771,6 +7787,7 @@ var BaseAddon = class {
|
|
|
7771
7787
|
const normalized = normalizeAddonInitResult(result);
|
|
7772
7788
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7773
7789
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7790
|
+
await this.loadScopedSchemas();
|
|
7774
7791
|
return normalized;
|
|
7775
7792
|
}
|
|
7776
7793
|
/**
|
|
@@ -7784,14 +7801,18 @@ var BaseAddon = class {
|
|
|
7784
7801
|
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7785
7802
|
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7786
7803
|
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7804
|
+
*
|
|
7805
|
+
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7806
|
+
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7807
|
+
* omitted applied ids "newer").
|
|
7787
7808
|
*/
|
|
7788
|
-
async runMigrations(migrations) {
|
|
7809
|
+
async runMigrations(migrations, options) {
|
|
7789
7810
|
const api = this.ctx.api.migrations;
|
|
7790
7811
|
const report = await runAddonMigrations({
|
|
7791
7812
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7792
7813
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7793
7814
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7794
|
-
}, migrations, this.ctx.logger);
|
|
7815
|
+
}, migrations, this.ctx.logger, options);
|
|
7795
7816
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7796
7817
|
return report;
|
|
7797
7818
|
}
|
|
@@ -7855,6 +7876,16 @@ var BaseAddon = class {
|
|
|
7855
7876
|
schema(schema) {
|
|
7856
7877
|
return schema;
|
|
7857
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
|
+
}
|
|
7858
7889
|
/** Override to provide global-level settings UI schema. */
|
|
7859
7890
|
globalSettingsSchema(_cap) {
|
|
7860
7891
|
return null;
|
|
@@ -7898,7 +7929,8 @@ var BaseAddon = class {
|
|
|
7898
7929
|
return [];
|
|
7899
7930
|
}
|
|
7900
7931
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7901
|
-
|
|
7932
|
+
await this.loadScopedSchemas(cap);
|
|
7933
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7902
7934
|
if (!schema) return { sections: [] };
|
|
7903
7935
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7904
7936
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7933,10 +7965,9 @@ var BaseAddon = class {
|
|
|
7933
7965
|
*
|
|
7934
7966
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7935
7967
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7936
|
-
* SHAPE of the form only — the values live
|
|
7937
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7968
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7938
7969
|
*/
|
|
7939
|
-
async
|
|
7970
|
+
async integrationFormSchema(_instance) {
|
|
7940
7971
|
return null;
|
|
7941
7972
|
}
|
|
7942
7973
|
/**
|
|
@@ -7982,7 +8013,7 @@ var BaseAddon = class {
|
|
|
7982
8013
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7983
8014
|
*/
|
|
7984
8015
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7985
|
-
const scoped = await this.
|
|
8016
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7986
8017
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7987
8018
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7988
8019
|
}
|
|
@@ -8030,6 +8061,7 @@ var BaseAddon = class {
|
|
|
8030
8061
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8031
8062
|
*/
|
|
8032
8063
|
async resolveGlobalStore(nodeId, cap) {
|
|
8064
|
+
await this.loadScopedSchemas(cap);
|
|
8033
8065
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8034
8066
|
return this.projectForNode(raw, nodeId, cap);
|
|
8035
8067
|
}
|
|
@@ -8040,6 +8072,7 @@ var BaseAddon = class {
|
|
|
8040
8072
|
}
|
|
8041
8073
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8042
8074
|
async readRawProjectedStore(nodeId, cap) {
|
|
8075
|
+
await this.loadScopedSchemas(cap);
|
|
8043
8076
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8044
8077
|
return this.projectForNode(raw, nodeId, cap);
|
|
8045
8078
|
}
|
|
@@ -8142,6 +8175,7 @@ var BaseAddon = class {
|
|
|
8142
8175
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8143
8176
|
}
|
|
8144
8177
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8178
|
+
await this.loadScopedSchemas();
|
|
8145
8179
|
const keys = this.perNodeKeys();
|
|
8146
8180
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8147
8181
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8155,7 +8189,8 @@ var BaseAddon = class {
|
|
|
8155
8189
|
await this.resolveConfig();
|
|
8156
8190
|
await this.onConfigChanged();
|
|
8157
8191
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8158
|
-
|
|
8192
|
+
const legacy = this.globalSettingsSchema();
|
|
8193
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8159
8194
|
}
|
|
8160
8195
|
/**
|
|
8161
8196
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8163,14 +8198,67 @@ var BaseAddon = class {
|
|
|
8163
8198
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8164
8199
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8165
8200
|
*/
|
|
8166
|
-
|
|
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
|
+
*/
|
|
8167
8251
|
perNodeKeys(cap) {
|
|
8168
8252
|
const cacheKey = cap ?? "";
|
|
8169
|
-
const cached = this.
|
|
8253
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8170
8254
|
if (cached) return cached;
|
|
8171
|
-
const
|
|
8172
|
-
const keys = new Set(
|
|
8173
|
-
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);
|
|
8174
8262
|
return keys;
|
|
8175
8263
|
}
|
|
8176
8264
|
/**
|
|
@@ -8180,10 +8268,9 @@ var BaseAddon = class {
|
|
|
8180
8268
|
* time to return its response before the addon is torn down and
|
|
8181
8269
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8182
8270
|
*/
|
|
8183
|
-
maybeAutoRestart(patchKeys,
|
|
8184
|
-
if (!schema) return;
|
|
8271
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8185
8272
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8186
|
-
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) {
|
|
8187
8274
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8188
8275
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8189
8276
|
}
|
|
@@ -8379,12 +8466,14 @@ var BaseAddon = class {
|
|
|
8379
8466
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8380
8467
|
*/
|
|
8381
8468
|
async getDeclaredSettingsKeys() {
|
|
8469
|
+
await this.loadScopedSchemas();
|
|
8382
8470
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8383
8471
|
}
|
|
8384
8472
|
declaredKeySet() {
|
|
8385
8473
|
const keys = new Set(Object.keys(this.defaults));
|
|
8386
8474
|
const schema = this.globalSettingsSchema();
|
|
8387
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);
|
|
8388
8477
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8389
8478
|
for (const path of this.secretStatePaths) {
|
|
8390
8479
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8480,7 +8569,11 @@ var BaseAddon = class {
|
|
|
8480
8569
|
}
|
|
8481
8570
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8482
8571
|
addonSecretTree() {
|
|
8483
|
-
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
|
+
}
|
|
8484
8577
|
return this._addonSecretTree;
|
|
8485
8578
|
}
|
|
8486
8579
|
deviceSecretTree() {
|
|
@@ -8730,6 +8823,23 @@ function dropPerNodeFields(fields) {
|
|
|
8730
8823
|
}
|
|
8731
8824
|
return kept;
|
|
8732
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
|
+
}
|
|
8733
8843
|
function collectFieldKeys(fields) {
|
|
8734
8844
|
const collected = [];
|
|
8735
8845
|
for (const field of fields) {
|
|
@@ -9256,6 +9366,7 @@ function defineEntryKind(name, spec) {
|
|
|
9256
9366
|
class: "config",
|
|
9257
9367
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9258
9368
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9369
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9259
9370
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9260
9371
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9261
9372
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11925,7 +12036,7 @@ object({
|
|
|
11925
12036
|
params: record(string$2(), union([string$2(), number()])).optional()
|
|
11926
12037
|
});
|
|
11927
12038
|
//#endregion
|
|
11928
|
-
//#region ../types/dist/composition-
|
|
12039
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11929
12040
|
/**
|
|
11930
12041
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11931
12042
|
*
|
|
@@ -14506,7 +14617,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14506
14617
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14507
14618
|
* catalogue lists the id (a newer database).
|
|
14508
14619
|
*/
|
|
14509
|
-
meta: MigrationMetaSchema.optional()
|
|
14620
|
+
meta: MigrationMetaSchema.optional(),
|
|
14621
|
+
/**
|
|
14622
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14623
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14624
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14625
|
+
*/
|
|
14626
|
+
ownerNotLoaded: boolean().optional()
|
|
14510
14627
|
});
|
|
14511
14628
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14512
14629
|
var MigrationPendingEntrySchema = object({
|
|
@@ -50241,6 +50358,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50241
50358
|
runsFound: number().int().nonnegative(),
|
|
50242
50359
|
pausesFound: number().int().nonnegative(),
|
|
50243
50360
|
longestPauseMs: number().nonnegative(),
|
|
50361
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50362
|
+
pulsesFound: number().int().nonnegative(),
|
|
50363
|
+
longestPulseMs: number().nonnegative(),
|
|
50244
50364
|
idleFloorW: number(),
|
|
50245
50365
|
noiseCeilingW: number(),
|
|
50246
50366
|
plateauW: number(),
|
|
@@ -56086,11 +56206,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56086
56206
|
info: record(string$2(), unknown()).optional(),
|
|
56087
56207
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56088
56208
|
originStableId: string$2().optional(),
|
|
56089
|
-
settings: record(string$2(), unknown()).optional()
|
|
56090
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56091
|
-
* the keychain, not the table (D787). */
|
|
56092
|
-
secretKeys: array(string$2()).optional(),
|
|
56093
|
-
skipRestart: boolean().optional()
|
|
56209
|
+
settings: record(string$2(), unknown()).optional()
|
|
56094
56210
|
});
|
|
56095
56211
|
var UpdateIntegrationInputSchema = object({
|
|
56096
56212
|
id: string$2(),
|
|
@@ -56111,16 +56227,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
56111
56227
|
}), method(object({ id: string$2() }), DeleteIntegrationResultSchema, {
|
|
56112
56228
|
kind: "mutation",
|
|
56113
56229
|
auth: "admin"
|
|
56114
|
-
}), method(object({ id: string$2() }), record(string$2(), unknown()), { auth: "admin" }), method(object({
|
|
56115
|
-
id: string$2(),
|
|
56116
|
-
settings: record(string$2(), unknown()),
|
|
56117
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56118
|
-
secretKeys: array(string$2()).optional(),
|
|
56119
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56120
|
-
skipRestart: boolean().optional()
|
|
56121
|
-
}), object({ success: boolean() }), {
|
|
56122
|
-
kind: "mutation",
|
|
56123
|
-
auth: "admin"
|
|
56124
56230
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56125
56231
|
addonId: string$2(),
|
|
56126
56232
|
settings: record(string$2(), unknown())
|
|
@@ -59097,6 +59203,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59097
59203
|
}), {
|
|
59098
59204
|
kind: "mutation",
|
|
59099
59205
|
auth: "admin"
|
|
59206
|
+
}), method(object({ integrationId: string$2().min(1) }), record(string$2(), unknown()), { auth: "admin" }), method(object({ integrationId: string$2().min(1) }), object({
|
|
59207
|
+
rows: number().int().nonnegative(),
|
|
59208
|
+
secrets: number().int().nonnegative()
|
|
59209
|
+
}), {
|
|
59210
|
+
kind: "mutation",
|
|
59211
|
+
auth: "admin"
|
|
59212
|
+
}), method(object({
|
|
59213
|
+
integrationId: string$2().min(1),
|
|
59214
|
+
patch: record(string$2(), unknown())
|
|
59215
|
+
}), object({ written: boolean() }), {
|
|
59216
|
+
kind: "mutation",
|
|
59217
|
+
auth: "admin"
|
|
59218
|
+
}), method(object({
|
|
59219
|
+
integrationId: string$2().min(1),
|
|
59220
|
+
keys: array(string$2().min(1))
|
|
59221
|
+
}), object({
|
|
59222
|
+
present: boolean(),
|
|
59223
|
+
removed: record(string$2(), unknown())
|
|
59224
|
+
}), {
|
|
59225
|
+
kind: "mutation",
|
|
59226
|
+
auth: "admin"
|
|
59100
59227
|
});
|
|
59101
59228
|
method(object({
|
|
59102
59229
|
sourceUrl: string$2(),
|
|
@@ -80423,11 +80550,15 @@ discriminatedUnion("kind", [object({
|
|
|
80423
80550
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80424
80551
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80425
80552
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80426
|
-
* 3.
|
|
80427
|
-
*
|
|
80553
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80554
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80555
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80556
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80557
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80558
|
+
* is no run.
|
|
80428
80559
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80429
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80430
|
-
* covers the longest standby blip above T.
|
|
80560
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80561
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80431
80562
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80432
80563
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80433
80564
|
*/
|
|
@@ -80438,6 +80569,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80438
80569
|
30 * SECOND_MS;
|
|
80439
80570
|
60 * SECOND_MS;
|
|
80440
80571
|
20 * MINUTE_MS$1;
|
|
80572
|
+
90 * SECOND_MS;
|
|
80573
|
+
30 * MINUTE_MS$1;
|
|
80574
|
+
10 * MINUTE_MS$1;
|
|
80575
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80441
80576
|
10 * SECOND_MS;
|
|
80442
80577
|
10 * SECOND_MS;
|
|
80443
80578
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85757,13 +85892,6 @@ Object.freeze({
|
|
|
85757
85892
|
access: "view",
|
|
85758
85893
|
role: "admin"
|
|
85759
85894
|
},
|
|
85760
|
-
"integrations.getSettings": {
|
|
85761
|
-
capName: "integrations",
|
|
85762
|
-
capScope: "system",
|
|
85763
|
-
addonId: null,
|
|
85764
|
-
access: "view",
|
|
85765
|
-
role: "admin"
|
|
85766
|
-
},
|
|
85767
85895
|
"integrations.list": {
|
|
85768
85896
|
capName: "integrations",
|
|
85769
85897
|
capScope: "system",
|
|
@@ -85771,13 +85899,6 @@ Object.freeze({
|
|
|
85771
85899
|
access: "view",
|
|
85772
85900
|
role: "admin"
|
|
85773
85901
|
},
|
|
85774
|
-
"integrations.setSettings": {
|
|
85775
|
-
capName: "integrations",
|
|
85776
|
-
capScope: "system",
|
|
85777
|
-
addonId: null,
|
|
85778
|
-
access: "create",
|
|
85779
|
-
role: "admin"
|
|
85780
|
-
},
|
|
85781
85902
|
"integrations.testConnection": {
|
|
85782
85903
|
capName: "integrations",
|
|
85783
85904
|
capScope: "system",
|
|
@@ -86772,6 +86893,13 @@ Object.freeze({
|
|
|
86772
86893
|
access: "view",
|
|
86773
86894
|
role: "admin"
|
|
86774
86895
|
},
|
|
86896
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
86897
|
+
capName: "migrations",
|
|
86898
|
+
capScope: "system",
|
|
86899
|
+
addonId: null,
|
|
86900
|
+
access: "delete",
|
|
86901
|
+
role: "admin"
|
|
86902
|
+
},
|
|
86775
86903
|
"migrations.declareOwner": {
|
|
86776
86904
|
capName: "migrations",
|
|
86777
86905
|
capScope: "system",
|
|
@@ -86779,6 +86907,13 @@ Object.freeze({
|
|
|
86779
86907
|
access: "create",
|
|
86780
86908
|
role: "admin"
|
|
86781
86909
|
},
|
|
86910
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
86911
|
+
capName: "migrations",
|
|
86912
|
+
capScope: "system",
|
|
86913
|
+
addonId: null,
|
|
86914
|
+
access: "delete",
|
|
86915
|
+
role: "admin"
|
|
86916
|
+
},
|
|
86782
86917
|
"migrations.dryRun": {
|
|
86783
86918
|
capName: "migrations",
|
|
86784
86919
|
capScope: "system",
|
|
@@ -86814,6 +86949,20 @@ Object.freeze({
|
|
|
86814
86949
|
access: "create",
|
|
86815
86950
|
role: "admin"
|
|
86816
86951
|
},
|
|
86952
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
86953
|
+
capName: "migrations",
|
|
86954
|
+
capScope: "system",
|
|
86955
|
+
addonId: null,
|
|
86956
|
+
access: "create",
|
|
86957
|
+
role: "admin"
|
|
86958
|
+
},
|
|
86959
|
+
"migrations.readLegacyIntegrationRows": {
|
|
86960
|
+
capName: "migrations",
|
|
86961
|
+
capScope: "system",
|
|
86962
|
+
addonId: null,
|
|
86963
|
+
access: "view",
|
|
86964
|
+
role: "admin"
|
|
86965
|
+
},
|
|
86817
86966
|
"migrations.restorePreMigration": {
|
|
86818
86967
|
capName: "migrations",
|
|
86819
86968
|
capScope: "system",
|
|
@@ -207342,8 +207491,8 @@ function sameConnection(a, b) {
|
|
|
207342
207491
|
* Manages N live Matter fabric connections ("brokers"), each backed by a
|
|
207343
207492
|
* {@link MatterIntegrationManager}. Allocates stable ids (`matter_001`, …), CRUD,
|
|
207344
207493
|
* an integration FK index, lifecycle helpers. Mirrors the Ecowitt / Dreame
|
|
207345
|
-
* registry.
|
|
207346
|
-
* fabric,
|
|
207494
|
+
* registry. The integration is `instanceMode: multiple`: the common case is one
|
|
207495
|
+
* fabric, and an operator may add more.
|
|
207347
207496
|
*/
|
|
207348
207497
|
var MatterBrokerRegistry = class {
|
|
207349
207498
|
#logger;
|
|
@@ -209100,18 +209249,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209100
209249
|
try {
|
|
209101
209250
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
209102
209251
|
const surviving = new Set(mine.map((i) => i.id));
|
|
209103
|
-
for (const
|
|
209104
|
-
const
|
|
209105
|
-
|
|
209106
|
-
|
|
209107
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
209108
|
-
this.ctx.logger.warn("Matter integration→broker: linked broker not found", { meta: {
|
|
209109
|
-
integrationId: integration.id,
|
|
209110
|
-
brokerId
|
|
209111
|
-
} });
|
|
209112
|
-
continue;
|
|
209113
|
-
}
|
|
209114
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
209252
|
+
for (const broker of this.activeBrokers) {
|
|
209253
|
+
const integrationId = broker.integrationId;
|
|
209254
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
209255
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
209115
209256
|
}
|
|
209116
209257
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
209117
209258
|
if (toRemove.length === 0) return;
|
|
@@ -209152,9 +209293,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209152
209293
|
},
|
|
209153
209294
|
logger: this.ctx.logger,
|
|
209154
209295
|
getBrokerIdForIntegration: async (id) => {
|
|
209155
|
-
const
|
|
209156
|
-
if (
|
|
209157
|
-
return
|
|
209296
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
209297
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
209298
|
+
return broker.id;
|
|
209158
209299
|
},
|
|
209159
209300
|
listAdoptedMatter: async () => {
|
|
209160
209301
|
const reg = this.ctx.kernel.deviceRegistry;
|
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).
|
|
@@ -6593,7 +6593,7 @@ var NOT_RUN = {
|
|
|
6593
6593
|
* throws: an unreachable or refusing engine is logged and reported `declared: false`,
|
|
6594
6594
|
* because the hub (and this addon's boot) must not hang on a migration.
|
|
6595
6595
|
*/
|
|
6596
|
-
async function runAddonMigrations(host, migrations, logger) {
|
|
6596
|
+
async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
6597
6597
|
const first = migrations[0];
|
|
6598
6598
|
if (first === void 0) return NOT_RUN;
|
|
6599
6599
|
const owner = first.meta.owner;
|
|
@@ -6622,7 +6622,9 @@ async function runAddonMigrations(host, migrations, logger) {
|
|
|
6622
6622
|
else if (status === "blocked") blocked.push(id);
|
|
6623
6623
|
else skipped.push(id);
|
|
6624
6624
|
};
|
|
6625
|
+
const selected = options.only === void 0 ? null : new Set(options.only);
|
|
6625
6626
|
for (const step of declaration.steps) {
|
|
6627
|
+
if (selected !== null && !selected.has(step.id)) continue;
|
|
6626
6628
|
const bound = byId.get(step.id);
|
|
6627
6629
|
if (bound === void 0 || step.action === "skip") {
|
|
6628
6630
|
skipped.push(step.id);
|
|
@@ -6800,6 +6802,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6800
6802
|
}
|
|
6801
6803
|
return out;
|
|
6802
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
|
+
];
|
|
6803
6811
|
/**
|
|
6804
6812
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6805
6813
|
*
|
|
@@ -7691,6 +7699,13 @@ var BaseAddon = class {
|
|
|
7691
7699
|
*/
|
|
7692
7700
|
secretStatePaths = [];
|
|
7693
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();
|
|
7694
7709
|
_deviceSecretTree = null;
|
|
7695
7710
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7696
7711
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7760,6 +7775,7 @@ var BaseAddon = class {
|
|
|
7760
7775
|
this._ctx = context;
|
|
7761
7776
|
this._secretSettings = null;
|
|
7762
7777
|
}
|
|
7778
|
+
await this.loadScopedSchemas();
|
|
7763
7779
|
await this.resolveConfig();
|
|
7764
7780
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7765
7781
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7769,6 +7785,7 @@ var BaseAddon = class {
|
|
|
7769
7785
|
const normalized = normalizeAddonInitResult(result);
|
|
7770
7786
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7771
7787
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7788
|
+
await this.loadScopedSchemas();
|
|
7772
7789
|
return normalized;
|
|
7773
7790
|
}
|
|
7774
7791
|
/**
|
|
@@ -7782,14 +7799,18 @@ var BaseAddon = class {
|
|
|
7782
7799
|
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7783
7800
|
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7784
7801
|
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7802
|
+
*
|
|
7803
|
+
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7804
|
+
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7805
|
+
* omitted applied ids "newer").
|
|
7785
7806
|
*/
|
|
7786
|
-
async runMigrations(migrations) {
|
|
7807
|
+
async runMigrations(migrations, options) {
|
|
7787
7808
|
const api = this.ctx.api.migrations;
|
|
7788
7809
|
const report = await runAddonMigrations({
|
|
7789
7810
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7790
7811
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7791
7812
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7792
|
-
}, migrations, this.ctx.logger);
|
|
7813
|
+
}, migrations, this.ctx.logger, options);
|
|
7793
7814
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7794
7815
|
return report;
|
|
7795
7816
|
}
|
|
@@ -7853,6 +7874,16 @@ var BaseAddon = class {
|
|
|
7853
7874
|
schema(schema) {
|
|
7854
7875
|
return schema;
|
|
7855
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
|
+
}
|
|
7856
7887
|
/** Override to provide global-level settings UI schema. */
|
|
7857
7888
|
globalSettingsSchema(_cap) {
|
|
7858
7889
|
return null;
|
|
@@ -7896,7 +7927,8 @@ var BaseAddon = class {
|
|
|
7896
7927
|
return [];
|
|
7897
7928
|
}
|
|
7898
7929
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7899
|
-
|
|
7930
|
+
await this.loadScopedSchemas(cap);
|
|
7931
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7900
7932
|
if (!schema) return { sections: [] };
|
|
7901
7933
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7902
7934
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7931,10 +7963,9 @@ var BaseAddon = class {
|
|
|
7931
7963
|
*
|
|
7932
7964
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7933
7965
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7934
|
-
* SHAPE of the form only — the values live
|
|
7935
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7966
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7936
7967
|
*/
|
|
7937
|
-
async
|
|
7968
|
+
async integrationFormSchema(_instance) {
|
|
7938
7969
|
return null;
|
|
7939
7970
|
}
|
|
7940
7971
|
/**
|
|
@@ -7980,7 +8011,7 @@ var BaseAddon = class {
|
|
|
7980
8011
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7981
8012
|
*/
|
|
7982
8013
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7983
|
-
const scoped = await this.
|
|
8014
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7984
8015
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7985
8016
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7986
8017
|
}
|
|
@@ -8028,6 +8059,7 @@ var BaseAddon = class {
|
|
|
8028
8059
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8029
8060
|
*/
|
|
8030
8061
|
async resolveGlobalStore(nodeId, cap) {
|
|
8062
|
+
await this.loadScopedSchemas(cap);
|
|
8031
8063
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8032
8064
|
return this.projectForNode(raw, nodeId, cap);
|
|
8033
8065
|
}
|
|
@@ -8038,6 +8070,7 @@ var BaseAddon = class {
|
|
|
8038
8070
|
}
|
|
8039
8071
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8040
8072
|
async readRawProjectedStore(nodeId, cap) {
|
|
8073
|
+
await this.loadScopedSchemas(cap);
|
|
8041
8074
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8042
8075
|
return this.projectForNode(raw, nodeId, cap);
|
|
8043
8076
|
}
|
|
@@ -8140,6 +8173,7 @@ var BaseAddon = class {
|
|
|
8140
8173
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8141
8174
|
}
|
|
8142
8175
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8176
|
+
await this.loadScopedSchemas();
|
|
8143
8177
|
const keys = this.perNodeKeys();
|
|
8144
8178
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8145
8179
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8153,7 +8187,8 @@ var BaseAddon = class {
|
|
|
8153
8187
|
await this.resolveConfig();
|
|
8154
8188
|
await this.onConfigChanged();
|
|
8155
8189
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8156
|
-
|
|
8190
|
+
const legacy = this.globalSettingsSchema();
|
|
8191
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8157
8192
|
}
|
|
8158
8193
|
/**
|
|
8159
8194
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8161,14 +8196,67 @@ var BaseAddon = class {
|
|
|
8161
8196
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8162
8197
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8163
8198
|
*/
|
|
8164
|
-
|
|
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
|
+
*/
|
|
8165
8249
|
perNodeKeys(cap) {
|
|
8166
8250
|
const cacheKey = cap ?? "";
|
|
8167
|
-
const cached = this.
|
|
8251
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8168
8252
|
if (cached) return cached;
|
|
8169
|
-
const
|
|
8170
|
-
const keys = new Set(
|
|
8171
|
-
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);
|
|
8172
8260
|
return keys;
|
|
8173
8261
|
}
|
|
8174
8262
|
/**
|
|
@@ -8178,10 +8266,9 @@ var BaseAddon = class {
|
|
|
8178
8266
|
* time to return its response before the addon is torn down and
|
|
8179
8267
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8180
8268
|
*/
|
|
8181
|
-
maybeAutoRestart(patchKeys,
|
|
8182
|
-
if (!schema) return;
|
|
8269
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8183
8270
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8184
|
-
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) {
|
|
8185
8272
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8186
8273
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8187
8274
|
}
|
|
@@ -8377,12 +8464,14 @@ var BaseAddon = class {
|
|
|
8377
8464
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8378
8465
|
*/
|
|
8379
8466
|
async getDeclaredSettingsKeys() {
|
|
8467
|
+
await this.loadScopedSchemas();
|
|
8380
8468
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8381
8469
|
}
|
|
8382
8470
|
declaredKeySet() {
|
|
8383
8471
|
const keys = new Set(Object.keys(this.defaults));
|
|
8384
8472
|
const schema = this.globalSettingsSchema();
|
|
8385
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);
|
|
8386
8475
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8387
8476
|
for (const path of this.secretStatePaths) {
|
|
8388
8477
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8478,7 +8567,11 @@ var BaseAddon = class {
|
|
|
8478
8567
|
}
|
|
8479
8568
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8480
8569
|
addonSecretTree() {
|
|
8481
|
-
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
|
+
}
|
|
8482
8575
|
return this._addonSecretTree;
|
|
8483
8576
|
}
|
|
8484
8577
|
deviceSecretTree() {
|
|
@@ -8728,6 +8821,23 @@ function dropPerNodeFields(fields) {
|
|
|
8728
8821
|
}
|
|
8729
8822
|
return kept;
|
|
8730
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
|
+
}
|
|
8731
8841
|
function collectFieldKeys(fields) {
|
|
8732
8842
|
const collected = [];
|
|
8733
8843
|
for (const field of fields) {
|
|
@@ -9254,6 +9364,7 @@ function defineEntryKind(name, spec) {
|
|
|
9254
9364
|
class: "config",
|
|
9255
9365
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9256
9366
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9367
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9257
9368
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9258
9369
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9259
9370
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11923,7 +12034,7 @@ object({
|
|
|
11923
12034
|
params: record(string$2(), union([string$2(), number()])).optional()
|
|
11924
12035
|
});
|
|
11925
12036
|
//#endregion
|
|
11926
|
-
//#region ../types/dist/composition-
|
|
12037
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11927
12038
|
/**
|
|
11928
12039
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11929
12040
|
*
|
|
@@ -14504,7 +14615,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14504
14615
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14505
14616
|
* catalogue lists the id (a newer database).
|
|
14506
14617
|
*/
|
|
14507
|
-
meta: MigrationMetaSchema.optional()
|
|
14618
|
+
meta: MigrationMetaSchema.optional(),
|
|
14619
|
+
/**
|
|
14620
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14621
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14622
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14623
|
+
*/
|
|
14624
|
+
ownerNotLoaded: boolean().optional()
|
|
14508
14625
|
});
|
|
14509
14626
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14510
14627
|
var MigrationPendingEntrySchema = object({
|
|
@@ -50239,6 +50356,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50239
50356
|
runsFound: number().int().nonnegative(),
|
|
50240
50357
|
pausesFound: number().int().nonnegative(),
|
|
50241
50358
|
longestPauseMs: number().nonnegative(),
|
|
50359
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50360
|
+
pulsesFound: number().int().nonnegative(),
|
|
50361
|
+
longestPulseMs: number().nonnegative(),
|
|
50242
50362
|
idleFloorW: number(),
|
|
50243
50363
|
noiseCeilingW: number(),
|
|
50244
50364
|
plateauW: number(),
|
|
@@ -56084,11 +56204,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56084
56204
|
info: record(string$2(), unknown()).optional(),
|
|
56085
56205
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56086
56206
|
originStableId: string$2().optional(),
|
|
56087
|
-
settings: record(string$2(), unknown()).optional()
|
|
56088
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56089
|
-
* the keychain, not the table (D787). */
|
|
56090
|
-
secretKeys: array(string$2()).optional(),
|
|
56091
|
-
skipRestart: boolean().optional()
|
|
56207
|
+
settings: record(string$2(), unknown()).optional()
|
|
56092
56208
|
});
|
|
56093
56209
|
var UpdateIntegrationInputSchema = object({
|
|
56094
56210
|
id: string$2(),
|
|
@@ -56109,16 +56225,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
56109
56225
|
}), method(object({ id: string$2() }), DeleteIntegrationResultSchema, {
|
|
56110
56226
|
kind: "mutation",
|
|
56111
56227
|
auth: "admin"
|
|
56112
|
-
}), method(object({ id: string$2() }), record(string$2(), unknown()), { auth: "admin" }), method(object({
|
|
56113
|
-
id: string$2(),
|
|
56114
|
-
settings: record(string$2(), unknown()),
|
|
56115
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56116
|
-
secretKeys: array(string$2()).optional(),
|
|
56117
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56118
|
-
skipRestart: boolean().optional()
|
|
56119
|
-
}), object({ success: boolean() }), {
|
|
56120
|
-
kind: "mutation",
|
|
56121
|
-
auth: "admin"
|
|
56122
56228
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56123
56229
|
addonId: string$2(),
|
|
56124
56230
|
settings: record(string$2(), unknown())
|
|
@@ -59095,6 +59201,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59095
59201
|
}), {
|
|
59096
59202
|
kind: "mutation",
|
|
59097
59203
|
auth: "admin"
|
|
59204
|
+
}), method(object({ integrationId: string$2().min(1) }), record(string$2(), unknown()), { auth: "admin" }), method(object({ integrationId: string$2().min(1) }), object({
|
|
59205
|
+
rows: number().int().nonnegative(),
|
|
59206
|
+
secrets: number().int().nonnegative()
|
|
59207
|
+
}), {
|
|
59208
|
+
kind: "mutation",
|
|
59209
|
+
auth: "admin"
|
|
59210
|
+
}), method(object({
|
|
59211
|
+
integrationId: string$2().min(1),
|
|
59212
|
+
patch: record(string$2(), unknown())
|
|
59213
|
+
}), object({ written: boolean() }), {
|
|
59214
|
+
kind: "mutation",
|
|
59215
|
+
auth: "admin"
|
|
59216
|
+
}), method(object({
|
|
59217
|
+
integrationId: string$2().min(1),
|
|
59218
|
+
keys: array(string$2().min(1))
|
|
59219
|
+
}), object({
|
|
59220
|
+
present: boolean(),
|
|
59221
|
+
removed: record(string$2(), unknown())
|
|
59222
|
+
}), {
|
|
59223
|
+
kind: "mutation",
|
|
59224
|
+
auth: "admin"
|
|
59098
59225
|
});
|
|
59099
59226
|
method(object({
|
|
59100
59227
|
sourceUrl: string$2(),
|
|
@@ -80421,11 +80548,15 @@ discriminatedUnion("kind", [object({
|
|
|
80421
80548
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80422
80549
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80423
80550
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80424
|
-
* 3.
|
|
80425
|
-
*
|
|
80551
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80552
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80553
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80554
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80555
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80556
|
+
* is no run.
|
|
80426
80557
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80427
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80428
|
-
* covers the longest standby blip above T.
|
|
80558
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80559
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80429
80560
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80430
80561
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80431
80562
|
*/
|
|
@@ -80436,6 +80567,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80436
80567
|
30 * SECOND_MS;
|
|
80437
80568
|
60 * SECOND_MS;
|
|
80438
80569
|
20 * MINUTE_MS$1;
|
|
80570
|
+
90 * SECOND_MS;
|
|
80571
|
+
30 * MINUTE_MS$1;
|
|
80572
|
+
10 * MINUTE_MS$1;
|
|
80573
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80439
80574
|
10 * SECOND_MS;
|
|
80440
80575
|
10 * SECOND_MS;
|
|
80441
80576
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85755,13 +85890,6 @@ Object.freeze({
|
|
|
85755
85890
|
access: "view",
|
|
85756
85891
|
role: "admin"
|
|
85757
85892
|
},
|
|
85758
|
-
"integrations.getSettings": {
|
|
85759
|
-
capName: "integrations",
|
|
85760
|
-
capScope: "system",
|
|
85761
|
-
addonId: null,
|
|
85762
|
-
access: "view",
|
|
85763
|
-
role: "admin"
|
|
85764
|
-
},
|
|
85765
85893
|
"integrations.list": {
|
|
85766
85894
|
capName: "integrations",
|
|
85767
85895
|
capScope: "system",
|
|
@@ -85769,13 +85897,6 @@ Object.freeze({
|
|
|
85769
85897
|
access: "view",
|
|
85770
85898
|
role: "admin"
|
|
85771
85899
|
},
|
|
85772
|
-
"integrations.setSettings": {
|
|
85773
|
-
capName: "integrations",
|
|
85774
|
-
capScope: "system",
|
|
85775
|
-
addonId: null,
|
|
85776
|
-
access: "create",
|
|
85777
|
-
role: "admin"
|
|
85778
|
-
},
|
|
85779
85900
|
"integrations.testConnection": {
|
|
85780
85901
|
capName: "integrations",
|
|
85781
85902
|
capScope: "system",
|
|
@@ -86770,6 +86891,13 @@ Object.freeze({
|
|
|
86770
86891
|
access: "view",
|
|
86771
86892
|
role: "admin"
|
|
86772
86893
|
},
|
|
86894
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
86895
|
+
capName: "migrations",
|
|
86896
|
+
capScope: "system",
|
|
86897
|
+
addonId: null,
|
|
86898
|
+
access: "delete",
|
|
86899
|
+
role: "admin"
|
|
86900
|
+
},
|
|
86773
86901
|
"migrations.declareOwner": {
|
|
86774
86902
|
capName: "migrations",
|
|
86775
86903
|
capScope: "system",
|
|
@@ -86777,6 +86905,13 @@ Object.freeze({
|
|
|
86777
86905
|
access: "create",
|
|
86778
86906
|
role: "admin"
|
|
86779
86907
|
},
|
|
86908
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
86909
|
+
capName: "migrations",
|
|
86910
|
+
capScope: "system",
|
|
86911
|
+
addonId: null,
|
|
86912
|
+
access: "delete",
|
|
86913
|
+
role: "admin"
|
|
86914
|
+
},
|
|
86780
86915
|
"migrations.dryRun": {
|
|
86781
86916
|
capName: "migrations",
|
|
86782
86917
|
capScope: "system",
|
|
@@ -86812,6 +86947,20 @@ Object.freeze({
|
|
|
86812
86947
|
access: "create",
|
|
86813
86948
|
role: "admin"
|
|
86814
86949
|
},
|
|
86950
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
86951
|
+
capName: "migrations",
|
|
86952
|
+
capScope: "system",
|
|
86953
|
+
addonId: null,
|
|
86954
|
+
access: "create",
|
|
86955
|
+
role: "admin"
|
|
86956
|
+
},
|
|
86957
|
+
"migrations.readLegacyIntegrationRows": {
|
|
86958
|
+
capName: "migrations",
|
|
86959
|
+
capScope: "system",
|
|
86960
|
+
addonId: null,
|
|
86961
|
+
access: "view",
|
|
86962
|
+
role: "admin"
|
|
86963
|
+
},
|
|
86815
86964
|
"migrations.restorePreMigration": {
|
|
86816
86965
|
capName: "migrations",
|
|
86817
86966
|
capScope: "system",
|
|
@@ -207340,8 +207489,8 @@ function sameConnection(a, b) {
|
|
|
207340
207489
|
* Manages N live Matter fabric connections ("brokers"), each backed by a
|
|
207341
207490
|
* {@link MatterIntegrationManager}. Allocates stable ids (`matter_001`, …), CRUD,
|
|
207342
207491
|
* an integration FK index, lifecycle helpers. Mirrors the Ecowitt / Dreame
|
|
207343
|
-
* registry.
|
|
207344
|
-
* fabric,
|
|
207492
|
+
* registry. The integration is `instanceMode: multiple`: the common case is one
|
|
207493
|
+
* fabric, and an operator may add more.
|
|
207345
207494
|
*/
|
|
207346
207495
|
var MatterBrokerRegistry = class {
|
|
207347
207496
|
#logger;
|
|
@@ -209098,18 +209247,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209098
209247
|
try {
|
|
209099
209248
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
209100
209249
|
const surviving = new Set(mine.map((i) => i.id));
|
|
209101
|
-
for (const
|
|
209102
|
-
const
|
|
209103
|
-
|
|
209104
|
-
|
|
209105
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
209106
|
-
this.ctx.logger.warn("Matter integration→broker: linked broker not found", { meta: {
|
|
209107
|
-
integrationId: integration.id,
|
|
209108
|
-
brokerId
|
|
209109
|
-
} });
|
|
209110
|
-
continue;
|
|
209111
|
-
}
|
|
209112
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
209250
|
+
for (const broker of this.activeBrokers) {
|
|
209251
|
+
const integrationId = broker.integrationId;
|
|
209252
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
209253
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
209113
209254
|
}
|
|
209114
209255
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
209115
209256
|
if (toRemove.length === 0) return;
|
|
@@ -209150,9 +209291,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209150
209291
|
},
|
|
209151
209292
|
logger: this.ctx.logger,
|
|
209152
209293
|
getBrokerIdForIntegration: async (id) => {
|
|
209153
|
-
const
|
|
209154
|
-
if (
|
|
209155
|
-
return
|
|
209294
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
209295
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
209296
|
+
return broker.id;
|
|
209156
209297
|
},
|
|
209157
209298
|
listAdoptedMatter: async () => {
|
|
209158
209299
|
const reg = this.ctx.kernel.deviceRegistry;
|
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": [
|