@camstack/addon-matter-broker 0.2.155 → 0.2.157
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 +196 -71
- package/dist/addon.mjs +196 -71
- package/package.json +2 -2
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.157] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- A Matter integration links to its fabric through the fabric record
|
|
15
|
+
|
|
16
|
+
## [0.2.156] - 2026-11-04
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- The integration states its role (devices, services or both) to the Integrations page
|
|
21
|
+
|
|
10
22
|
## [0.2.155] - 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-9TIHFj1w.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);
|
|
@@ -7784,14 +7786,18 @@ var BaseAddon = class {
|
|
|
7784
7786
|
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7785
7787
|
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7786
7788
|
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7789
|
+
*
|
|
7790
|
+
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7791
|
+
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7792
|
+
* omitted applied ids "newer").
|
|
7787
7793
|
*/
|
|
7788
|
-
async runMigrations(migrations) {
|
|
7794
|
+
async runMigrations(migrations, options) {
|
|
7789
7795
|
const api = this.ctx.api.migrations;
|
|
7790
7796
|
const report = await runAddonMigrations({
|
|
7791
7797
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7792
7798
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7793
7799
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7794
|
-
}, migrations, this.ctx.logger);
|
|
7800
|
+
}, migrations, this.ctx.logger, options);
|
|
7795
7801
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7796
7802
|
return report;
|
|
7797
7803
|
}
|
|
@@ -7933,10 +7939,9 @@ var BaseAddon = class {
|
|
|
7933
7939
|
*
|
|
7934
7940
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7935
7941
|
* 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).
|
|
7942
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7938
7943
|
*/
|
|
7939
|
-
async
|
|
7944
|
+
async integrationFormSchema(_instance) {
|
|
7940
7945
|
return null;
|
|
7941
7946
|
}
|
|
7942
7947
|
/**
|
|
@@ -7982,7 +7987,7 @@ var BaseAddon = class {
|
|
|
7982
7987
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7983
7988
|
*/
|
|
7984
7989
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7985
|
-
const scoped = await this.
|
|
7990
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7986
7991
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7987
7992
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7988
7993
|
}
|
|
@@ -8831,6 +8836,10 @@ var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
|
8831
8836
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
8832
8837
|
* (`auth.accessToken`, `headers[].value`).
|
|
8833
8838
|
*
|
|
8839
|
+
* `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
|
|
8840
|
+
* names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
|
|
8841
|
+
* key releases exactly that key's secret.
|
|
8842
|
+
*
|
|
8834
8843
|
* Array elements are matched by a DECLARED id key (`secretArrayKeys`:
|
|
8835
8844
|
* `{ headers: 'name' }`), never by index, so reordering a list keeps every
|
|
8836
8845
|
* keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
|
|
@@ -8843,11 +8852,13 @@ function parseSecretPath(path) {
|
|
|
8843
8852
|
if (path.length === 0) throw new InvalidRecordError("an empty secret path");
|
|
8844
8853
|
return path.split(".").map((raw) => {
|
|
8845
8854
|
const each = raw.endsWith("[]");
|
|
8846
|
-
const
|
|
8855
|
+
const map = raw.endsWith("{}");
|
|
8856
|
+
const key = each || map ? raw.slice(0, -2) : raw;
|
|
8847
8857
|
if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
|
|
8848
8858
|
return {
|
|
8849
8859
|
key,
|
|
8850
|
-
each
|
|
8860
|
+
each,
|
|
8861
|
+
map
|
|
8851
8862
|
};
|
|
8852
8863
|
});
|
|
8853
8864
|
}
|
|
@@ -8899,6 +8910,22 @@ function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arra
|
|
|
8899
8910
|
[segment.key]: mapped
|
|
8900
8911
|
};
|
|
8901
8912
|
}
|
|
8913
|
+
if (segment.map) {
|
|
8914
|
+
if (!isRecord$4$1(child)) return node;
|
|
8915
|
+
const kept = Object.entries(child).map(([mapKey, value]) => {
|
|
8916
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
|
|
8917
|
+
if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
|
|
8918
|
+
return [mapKey, visit({
|
|
8919
|
+
declared,
|
|
8920
|
+
concrete,
|
|
8921
|
+
value
|
|
8922
|
+
})];
|
|
8923
|
+
}).filter(([, value]) => value !== REMOVE_LEAF);
|
|
8924
|
+
return {
|
|
8925
|
+
...node,
|
|
8926
|
+
[segment.key]: Object.fromEntries(kept)
|
|
8927
|
+
};
|
|
8928
|
+
}
|
|
8902
8929
|
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8903
8930
|
if (last) {
|
|
8904
8931
|
const replacement = visit({
|
|
@@ -9040,7 +9067,8 @@ function isZodSecret(schema) {
|
|
|
9040
9067
|
}
|
|
9041
9068
|
/**
|
|
9042
9069
|
* Every declared secret path in a config schema: dotted keys, `[]` for "each
|
|
9043
|
-
* element of this array" (`connection.password`, `rows[].token`)
|
|
9070
|
+
* element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
|
|
9071
|
+
* map" (`applianceSecrets{}`). A declared
|
|
9044
9072
|
* object or array is reported as one path and not descended into — everything
|
|
9045
9073
|
* under it is secret.
|
|
9046
9074
|
*/
|
|
@@ -9051,6 +9079,7 @@ function collectZodSecretPaths(schema, prefix = "") {
|
|
|
9051
9079
|
const def = defOf(layer);
|
|
9052
9080
|
if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
9053
9081
|
else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
|
|
9082
|
+
else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
|
|
9054
9083
|
else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
|
|
9055
9084
|
else if (def.type === "intersection") {
|
|
9056
9085
|
if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
|
|
@@ -9232,6 +9261,7 @@ function defineEntryKind(name, spec) {
|
|
|
9232
9261
|
class: "config",
|
|
9233
9262
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9234
9263
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9264
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9235
9265
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9236
9266
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9237
9267
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11901,7 +11931,7 @@ object({
|
|
|
11901
11931
|
params: record(string$2(), union([string$2(), number()])).optional()
|
|
11902
11932
|
});
|
|
11903
11933
|
//#endregion
|
|
11904
|
-
//#region ../types/dist/composition-
|
|
11934
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11905
11935
|
/**
|
|
11906
11936
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11907
11937
|
*
|
|
@@ -13248,11 +13278,49 @@ var IntegrationInstanceRefSchema = object({
|
|
|
13248
13278
|
]),
|
|
13249
13279
|
candidateStableId: string$2().optional()
|
|
13250
13280
|
});
|
|
13281
|
+
/**
|
|
13282
|
+
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13283
|
+
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13284
|
+
*
|
|
13285
|
+
* Every instance is one record of the addon's own kind, keyed by the
|
|
13286
|
+
* `integrations` registry id. The records door does the secret work: a field the
|
|
13287
|
+
* kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
|
|
13288
|
+
* deleting the row deletes the secret, and one row's value is never another's.
|
|
13289
|
+
*
|
|
13290
|
+
* Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
|
|
13291
|
+
* token or the view the form was given KEEP the stored secret;
|
|
13292
|
+
* `{ $secretClear: true }` clears it; any other string replaces it.
|
|
13293
|
+
*/
|
|
13251
13294
|
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13252
13295
|
var INSTANCES_LIST_MAX = 1e3;
|
|
13253
13296
|
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13254
13297
|
function topLevelSecretKeys(paths) {
|
|
13255
|
-
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
|
|
13298
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
|
|
13299
|
+
}
|
|
13300
|
+
/** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
|
|
13301
|
+
function secretMapKeys(paths) {
|
|
13302
|
+
return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
|
|
13303
|
+
}
|
|
13304
|
+
/**
|
|
13305
|
+
* A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
|
|
13306
|
+
* view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
|
|
13307
|
+
* or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
|
|
13308
|
+
*/
|
|
13309
|
+
function secretMapPatch(value) {
|
|
13310
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
13311
|
+
const out = {};
|
|
13312
|
+
for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
|
|
13313
|
+
else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
|
|
13314
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
13315
|
+
}
|
|
13316
|
+
/** Does the kind's body schema carry an `enabled` field? */
|
|
13317
|
+
function declaresEnabled(kind) {
|
|
13318
|
+
const json = toJSONSchema(kind.schema, {
|
|
13319
|
+
unrepresentable: "any",
|
|
13320
|
+
io: "input"
|
|
13321
|
+
});
|
|
13322
|
+
const properties = Reflect.get(json, "properties");
|
|
13323
|
+
return typeof properties === "object" && properties !== null && "enabled" in properties;
|
|
13256
13324
|
}
|
|
13257
13325
|
var RecordIntegrationInstances = class {
|
|
13258
13326
|
records;
|
|
@@ -13260,14 +13328,20 @@ var RecordIntegrationInstances = class {
|
|
|
13260
13328
|
hooks;
|
|
13261
13329
|
handle;
|
|
13262
13330
|
secretKeys;
|
|
13331
|
+
secretMaps;
|
|
13263
13332
|
nameField;
|
|
13333
|
+
/** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
|
|
13334
|
+
registryOwnsEnabled;
|
|
13264
13335
|
constructor(records, kind, hooks) {
|
|
13265
13336
|
this.records = records;
|
|
13266
13337
|
this.kind = kind;
|
|
13267
13338
|
this.hooks = hooks;
|
|
13268
13339
|
this.handle = records.kind(kind);
|
|
13269
13340
|
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13341
|
+
this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
|
|
13270
13342
|
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13343
|
+
this.registryOwnsEnabled = hooks.description.role === "devices";
|
|
13344
|
+
if (this.registryOwnsEnabled && declaresEnabled(kind)) throw new Error(`kind "${kind.name}": the integrations registry owns the on/off switch of a role "devices" addon, so the body must not declare "enabled"`);
|
|
13271
13345
|
}
|
|
13272
13346
|
/**
|
|
13273
13347
|
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
@@ -13318,7 +13392,7 @@ var RecordIntegrationInstances = class {
|
|
|
13318
13392
|
id: record.id,
|
|
13319
13393
|
name: typeof rawName === "string" ? rawName : record.id,
|
|
13320
13394
|
configured: this.hooks.configured(record.data),
|
|
13321
|
-
enabled: record.data.enabled !== false,
|
|
13395
|
+
...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
|
|
13322
13396
|
formValues: shown,
|
|
13323
13397
|
...kind !== void 0 ? { kind } : {},
|
|
13324
13398
|
...health?.status !== void 0 ? { status: health.status } : {},
|
|
@@ -13348,32 +13422,36 @@ var RecordIntegrationInstances = class {
|
|
|
13348
13422
|
id: minted,
|
|
13349
13423
|
data: this.hooks.initialBody(name, kind)
|
|
13350
13424
|
}, { ifRev: 0 });
|
|
13351
|
-
await this.hooks.onChanged?.(minted);
|
|
13425
|
+
await this.hooks.onChanged?.(minted, "created");
|
|
13352
13426
|
return { id: minted };
|
|
13353
13427
|
};
|
|
13354
13428
|
updateInstance = async ({ id, name, values }) => {
|
|
13355
13429
|
const patch = {};
|
|
13356
13430
|
for (const [key, value] of Object.entries(values)) {
|
|
13357
13431
|
if (value === void 0) continue;
|
|
13358
|
-
if (
|
|
13432
|
+
if (this.secretMaps.has(key)) {
|
|
13433
|
+
const entries = secretMapPatch(value);
|
|
13434
|
+
if (entries !== null) patch[key] = entries;
|
|
13435
|
+
} else if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13359
13436
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13360
13437
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13361
13438
|
}
|
|
13362
13439
|
if (name !== void 0) patch[this.nameField] = name;
|
|
13363
13440
|
await this.handle.patch(id, patch);
|
|
13364
|
-
await this.hooks.onChanged?.(id);
|
|
13441
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13365
13442
|
return { ok: true };
|
|
13366
13443
|
};
|
|
13367
13444
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13368
13445
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13446
|
+
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
13369
13447
|
await this.handle.patch(id, { enabled });
|
|
13370
|
-
await this.hooks.onChanged?.(id);
|
|
13448
|
+
await this.hooks.onChanged?.(id, "enabled");
|
|
13371
13449
|
return { ok: true };
|
|
13372
13450
|
};
|
|
13373
13451
|
/** The row and its keychain refs go together (the records door). */
|
|
13374
13452
|
deleteInstance = async ({ id }) => {
|
|
13375
13453
|
await this.handle.delete(id);
|
|
13376
|
-
await this.hooks.onChanged?.(id);
|
|
13454
|
+
await this.hooks.onChanged?.(id, "deleted");
|
|
13377
13455
|
return { ok: true };
|
|
13378
13456
|
};
|
|
13379
13457
|
testInstance = async (input) => {
|
|
@@ -14434,7 +14512,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14434
14512
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14435
14513
|
* catalogue lists the id (a newer database).
|
|
14436
14514
|
*/
|
|
14437
|
-
meta: MigrationMetaSchema.optional()
|
|
14515
|
+
meta: MigrationMetaSchema.optional(),
|
|
14516
|
+
/**
|
|
14517
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14518
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14519
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14520
|
+
*/
|
|
14521
|
+
ownerNotLoaded: boolean().optional()
|
|
14438
14522
|
});
|
|
14439
14523
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14440
14524
|
var MigrationPendingEntrySchema = object({
|
|
@@ -50169,6 +50253,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50169
50253
|
runsFound: number().int().nonnegative(),
|
|
50170
50254
|
pausesFound: number().int().nonnegative(),
|
|
50171
50255
|
longestPauseMs: number().nonnegative(),
|
|
50256
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50257
|
+
pulsesFound: number().int().nonnegative(),
|
|
50258
|
+
longestPulseMs: number().nonnegative(),
|
|
50172
50259
|
idleFloorW: number(),
|
|
50173
50260
|
noiseCeilingW: number(),
|
|
50174
50261
|
plateauW: number(),
|
|
@@ -56014,11 +56101,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56014
56101
|
info: record(string$2(), unknown()).optional(),
|
|
56015
56102
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56016
56103
|
originStableId: string$2().optional(),
|
|
56017
|
-
settings: record(string$2(), unknown()).optional()
|
|
56018
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56019
|
-
* the keychain, not the table (D787). */
|
|
56020
|
-
secretKeys: array(string$2()).optional(),
|
|
56021
|
-
skipRestart: boolean().optional()
|
|
56104
|
+
settings: record(string$2(), unknown()).optional()
|
|
56022
56105
|
});
|
|
56023
56106
|
var UpdateIntegrationInputSchema = object({
|
|
56024
56107
|
id: string$2(),
|
|
@@ -56039,16 +56122,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
56039
56122
|
}), method(object({ id: string$2() }), DeleteIntegrationResultSchema, {
|
|
56040
56123
|
kind: "mutation",
|
|
56041
56124
|
auth: "admin"
|
|
56042
|
-
}), method(object({ id: string$2() }), record(string$2(), unknown()), { auth: "admin" }), method(object({
|
|
56043
|
-
id: string$2(),
|
|
56044
|
-
settings: record(string$2(), unknown()),
|
|
56045
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56046
|
-
secretKeys: array(string$2()).optional(),
|
|
56047
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56048
|
-
skipRestart: boolean().optional()
|
|
56049
|
-
}), object({ success: boolean() }), {
|
|
56050
|
-
kind: "mutation",
|
|
56051
|
-
auth: "admin"
|
|
56052
56125
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56053
56126
|
addonId: string$2(),
|
|
56054
56127
|
settings: record(string$2(), unknown())
|
|
@@ -56100,6 +56173,21 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56100
56173
|
disconnectPath: string$2().min(1).optional()
|
|
56101
56174
|
});
|
|
56102
56175
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56176
|
+
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56177
|
+
role: _enum([
|
|
56178
|
+
"devices",
|
|
56179
|
+
"services",
|
|
56180
|
+
"both"
|
|
56181
|
+
]),
|
|
56182
|
+
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56183
|
+
discovery: object({
|
|
56184
|
+
mode: _enum([
|
|
56185
|
+
"none",
|
|
56186
|
+
"lan",
|
|
56187
|
+
"account"
|
|
56188
|
+
]),
|
|
56189
|
+
scopedToInstance: boolean()
|
|
56190
|
+
}).optional(),
|
|
56103
56191
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
56104
56192
|
instanceMode: IntegrationInstanceModeSchema,
|
|
56105
56193
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
@@ -56139,10 +56227,11 @@ var IntegrationInstanceSchema = object({
|
|
|
56139
56227
|
/** The addon's own rule (an URL is present, a client id is present...). */
|
|
56140
56228
|
configured: boolean(),
|
|
56141
56229
|
/**
|
|
56142
|
-
* The instance's own on/off switch (D996)
|
|
56143
|
-
*
|
|
56230
|
+
* The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
|
|
56231
|
+
* so a row written before the field existed is on). Absent when the `integrations` registry
|
|
56232
|
+
* owns the switch (`describe().role` is `devices`): read it from the registry row.
|
|
56144
56233
|
*/
|
|
56145
|
-
enabled: boolean(),
|
|
56234
|
+
enabled: boolean().optional(),
|
|
56146
56235
|
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
56147
56236
|
kind: string$2().optional(),
|
|
56148
56237
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
@@ -59009,6 +59098,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59009
59098
|
}), {
|
|
59010
59099
|
kind: "mutation",
|
|
59011
59100
|
auth: "admin"
|
|
59101
|
+
}), method(object({ integrationId: string$2().min(1) }), record(string$2(), unknown()), { auth: "admin" }), method(object({ integrationId: string$2().min(1) }), object({
|
|
59102
|
+
rows: number().int().nonnegative(),
|
|
59103
|
+
secrets: number().int().nonnegative()
|
|
59104
|
+
}), {
|
|
59105
|
+
kind: "mutation",
|
|
59106
|
+
auth: "admin"
|
|
59107
|
+
}), method(object({
|
|
59108
|
+
integrationId: string$2().min(1),
|
|
59109
|
+
patch: record(string$2(), unknown())
|
|
59110
|
+
}), object({ written: boolean() }), {
|
|
59111
|
+
kind: "mutation",
|
|
59112
|
+
auth: "admin"
|
|
59113
|
+
}), method(object({
|
|
59114
|
+
integrationId: string$2().min(1),
|
|
59115
|
+
keys: array(string$2().min(1))
|
|
59116
|
+
}), object({
|
|
59117
|
+
present: boolean(),
|
|
59118
|
+
removed: record(string$2(), unknown())
|
|
59119
|
+
}), {
|
|
59120
|
+
kind: "mutation",
|
|
59121
|
+
auth: "admin"
|
|
59012
59122
|
});
|
|
59013
59123
|
method(object({
|
|
59014
59124
|
sourceUrl: string$2(),
|
|
@@ -80335,11 +80445,15 @@ discriminatedUnion("kind", [object({
|
|
|
80335
80445
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80336
80446
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80337
80447
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80338
|
-
* 3.
|
|
80339
|
-
*
|
|
80448
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80449
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80450
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80451
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80452
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80453
|
+
* is no run.
|
|
80340
80454
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80341
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80342
|
-
* covers the longest standby blip above T.
|
|
80455
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80456
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80343
80457
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80344
80458
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80345
80459
|
*/
|
|
@@ -80350,6 +80464,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80350
80464
|
30 * SECOND_MS;
|
|
80351
80465
|
60 * SECOND_MS;
|
|
80352
80466
|
20 * MINUTE_MS$1;
|
|
80467
|
+
90 * SECOND_MS;
|
|
80468
|
+
30 * MINUTE_MS$1;
|
|
80469
|
+
10 * MINUTE_MS$1;
|
|
80470
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80353
80471
|
10 * SECOND_MS;
|
|
80354
80472
|
10 * SECOND_MS;
|
|
80355
80473
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85669,13 +85787,6 @@ Object.freeze({
|
|
|
85669
85787
|
access: "view",
|
|
85670
85788
|
role: "admin"
|
|
85671
85789
|
},
|
|
85672
|
-
"integrations.getSettings": {
|
|
85673
|
-
capName: "integrations",
|
|
85674
|
-
capScope: "system",
|
|
85675
|
-
addonId: null,
|
|
85676
|
-
access: "view",
|
|
85677
|
-
role: "admin"
|
|
85678
|
-
},
|
|
85679
85790
|
"integrations.list": {
|
|
85680
85791
|
capName: "integrations",
|
|
85681
85792
|
capScope: "system",
|
|
@@ -85683,13 +85794,6 @@ Object.freeze({
|
|
|
85683
85794
|
access: "view",
|
|
85684
85795
|
role: "admin"
|
|
85685
85796
|
},
|
|
85686
|
-
"integrations.setSettings": {
|
|
85687
|
-
capName: "integrations",
|
|
85688
|
-
capScope: "system",
|
|
85689
|
-
addonId: null,
|
|
85690
|
-
access: "create",
|
|
85691
|
-
role: "admin"
|
|
85692
|
-
},
|
|
85693
85797
|
"integrations.testConnection": {
|
|
85694
85798
|
capName: "integrations",
|
|
85695
85799
|
capScope: "system",
|
|
@@ -86684,6 +86788,13 @@ Object.freeze({
|
|
|
86684
86788
|
access: "view",
|
|
86685
86789
|
role: "admin"
|
|
86686
86790
|
},
|
|
86791
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
86792
|
+
capName: "migrations",
|
|
86793
|
+
capScope: "system",
|
|
86794
|
+
addonId: null,
|
|
86795
|
+
access: "delete",
|
|
86796
|
+
role: "admin"
|
|
86797
|
+
},
|
|
86687
86798
|
"migrations.declareOwner": {
|
|
86688
86799
|
capName: "migrations",
|
|
86689
86800
|
capScope: "system",
|
|
@@ -86691,6 +86802,13 @@ Object.freeze({
|
|
|
86691
86802
|
access: "create",
|
|
86692
86803
|
role: "admin"
|
|
86693
86804
|
},
|
|
86805
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
86806
|
+
capName: "migrations",
|
|
86807
|
+
capScope: "system",
|
|
86808
|
+
addonId: null,
|
|
86809
|
+
access: "delete",
|
|
86810
|
+
role: "admin"
|
|
86811
|
+
},
|
|
86694
86812
|
"migrations.dryRun": {
|
|
86695
86813
|
capName: "migrations",
|
|
86696
86814
|
capScope: "system",
|
|
@@ -86726,6 +86844,20 @@ Object.freeze({
|
|
|
86726
86844
|
access: "create",
|
|
86727
86845
|
role: "admin"
|
|
86728
86846
|
},
|
|
86847
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
86848
|
+
capName: "migrations",
|
|
86849
|
+
capScope: "system",
|
|
86850
|
+
addonId: null,
|
|
86851
|
+
access: "create",
|
|
86852
|
+
role: "admin"
|
|
86853
|
+
},
|
|
86854
|
+
"migrations.readLegacyIntegrationRows": {
|
|
86855
|
+
capName: "migrations",
|
|
86856
|
+
capScope: "system",
|
|
86857
|
+
addonId: null,
|
|
86858
|
+
access: "view",
|
|
86859
|
+
role: "admin"
|
|
86860
|
+
},
|
|
86729
86861
|
"migrations.restorePreMigration": {
|
|
86730
86862
|
capName: "migrations",
|
|
86731
86863
|
capScope: "system",
|
|
@@ -208592,6 +208724,7 @@ function statusOf(info) {
|
|
|
208592
208724
|
function buildMatterInstances(records, deps) {
|
|
208593
208725
|
return new RecordIntegrationInstances(records, matterBrokersEntryKind, {
|
|
208594
208726
|
description: {
|
|
208727
|
+
role: "both",
|
|
208595
208728
|
instanceMode: "multiple",
|
|
208596
208729
|
kind: "connection",
|
|
208597
208730
|
canTest: true,
|
|
@@ -209011,18 +209144,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209011
209144
|
try {
|
|
209012
209145
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
209013
209146
|
const surviving = new Set(mine.map((i) => i.id));
|
|
209014
|
-
for (const
|
|
209015
|
-
const
|
|
209016
|
-
|
|
209017
|
-
|
|
209018
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
209019
|
-
this.ctx.logger.warn("Matter integration→broker: linked broker not found", { meta: {
|
|
209020
|
-
integrationId: integration.id,
|
|
209021
|
-
brokerId
|
|
209022
|
-
} });
|
|
209023
|
-
continue;
|
|
209024
|
-
}
|
|
209025
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
209147
|
+
for (const broker of this.activeBrokers) {
|
|
209148
|
+
const integrationId = broker.integrationId;
|
|
209149
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
209150
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
209026
209151
|
}
|
|
209027
209152
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
209028
209153
|
if (toRemove.length === 0) return;
|
|
@@ -209063,9 +209188,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209063
209188
|
},
|
|
209064
209189
|
logger: this.ctx.logger,
|
|
209065
209190
|
getBrokerIdForIntegration: async (id) => {
|
|
209066
|
-
const
|
|
209067
|
-
if (
|
|
209068
|
-
return
|
|
209191
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
209192
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
209193
|
+
return broker.id;
|
|
209069
209194
|
},
|
|
209070
209195
|
listAdoptedMatter: async () => {
|
|
209071
209196
|
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-9TIHFj1w.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);
|
|
@@ -7782,14 +7784,18 @@ var BaseAddon = class {
|
|
|
7782
7784
|
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7783
7785
|
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7784
7786
|
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7787
|
+
*
|
|
7788
|
+
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7789
|
+
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7790
|
+
* omitted applied ids "newer").
|
|
7785
7791
|
*/
|
|
7786
|
-
async runMigrations(migrations) {
|
|
7792
|
+
async runMigrations(migrations, options) {
|
|
7787
7793
|
const api = this.ctx.api.migrations;
|
|
7788
7794
|
const report = await runAddonMigrations({
|
|
7789
7795
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7790
7796
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7791
7797
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7792
|
-
}, migrations, this.ctx.logger);
|
|
7798
|
+
}, migrations, this.ctx.logger, options);
|
|
7793
7799
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7794
7800
|
return report;
|
|
7795
7801
|
}
|
|
@@ -7931,10 +7937,9 @@ var BaseAddon = class {
|
|
|
7931
7937
|
*
|
|
7932
7938
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7933
7939
|
* 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).
|
|
7940
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7936
7941
|
*/
|
|
7937
|
-
async
|
|
7942
|
+
async integrationFormSchema(_instance) {
|
|
7938
7943
|
return null;
|
|
7939
7944
|
}
|
|
7940
7945
|
/**
|
|
@@ -7980,7 +7985,7 @@ var BaseAddon = class {
|
|
|
7980
7985
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7981
7986
|
*/
|
|
7982
7987
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7983
|
-
const scoped = await this.
|
|
7988
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7984
7989
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7985
7990
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7986
7991
|
}
|
|
@@ -8829,6 +8834,10 @@ var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
|
8829
8834
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
8830
8835
|
* (`auth.accessToken`, `headers[].value`).
|
|
8831
8836
|
*
|
|
8837
|
+
* `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
|
|
8838
|
+
* names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
|
|
8839
|
+
* key releases exactly that key's secret.
|
|
8840
|
+
*
|
|
8832
8841
|
* Array elements are matched by a DECLARED id key (`secretArrayKeys`:
|
|
8833
8842
|
* `{ headers: 'name' }`), never by index, so reordering a list keeps every
|
|
8834
8843
|
* keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
|
|
@@ -8841,11 +8850,13 @@ function parseSecretPath(path) {
|
|
|
8841
8850
|
if (path.length === 0) throw new InvalidRecordError("an empty secret path");
|
|
8842
8851
|
return path.split(".").map((raw) => {
|
|
8843
8852
|
const each = raw.endsWith("[]");
|
|
8844
|
-
const
|
|
8853
|
+
const map = raw.endsWith("{}");
|
|
8854
|
+
const key = each || map ? raw.slice(0, -2) : raw;
|
|
8845
8855
|
if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
|
|
8846
8856
|
return {
|
|
8847
8857
|
key,
|
|
8848
|
-
each
|
|
8858
|
+
each,
|
|
8859
|
+
map
|
|
8849
8860
|
};
|
|
8850
8861
|
});
|
|
8851
8862
|
}
|
|
@@ -8897,6 +8908,22 @@ function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arra
|
|
|
8897
8908
|
[segment.key]: mapped
|
|
8898
8909
|
};
|
|
8899
8910
|
}
|
|
8911
|
+
if (segment.map) {
|
|
8912
|
+
if (!isRecord$4$1(child)) return node;
|
|
8913
|
+
const kept = Object.entries(child).map(([mapKey, value]) => {
|
|
8914
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
|
|
8915
|
+
if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
|
|
8916
|
+
return [mapKey, visit({
|
|
8917
|
+
declared,
|
|
8918
|
+
concrete,
|
|
8919
|
+
value
|
|
8920
|
+
})];
|
|
8921
|
+
}).filter(([, value]) => value !== REMOVE_LEAF);
|
|
8922
|
+
return {
|
|
8923
|
+
...node,
|
|
8924
|
+
[segment.key]: Object.fromEntries(kept)
|
|
8925
|
+
};
|
|
8926
|
+
}
|
|
8900
8927
|
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8901
8928
|
if (last) {
|
|
8902
8929
|
const replacement = visit({
|
|
@@ -9038,7 +9065,8 @@ function isZodSecret(schema) {
|
|
|
9038
9065
|
}
|
|
9039
9066
|
/**
|
|
9040
9067
|
* Every declared secret path in a config schema: dotted keys, `[]` for "each
|
|
9041
|
-
* element of this array" (`connection.password`, `rows[].token`)
|
|
9068
|
+
* element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
|
|
9069
|
+
* map" (`applianceSecrets{}`). A declared
|
|
9042
9070
|
* object or array is reported as one path and not descended into — everything
|
|
9043
9071
|
* under it is secret.
|
|
9044
9072
|
*/
|
|
@@ -9049,6 +9077,7 @@ function collectZodSecretPaths(schema, prefix = "") {
|
|
|
9049
9077
|
const def = defOf(layer);
|
|
9050
9078
|
if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
9051
9079
|
else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
|
|
9080
|
+
else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
|
|
9052
9081
|
else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
|
|
9053
9082
|
else if (def.type === "intersection") {
|
|
9054
9083
|
if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
|
|
@@ -9230,6 +9259,7 @@ function defineEntryKind(name, spec) {
|
|
|
9230
9259
|
class: "config",
|
|
9231
9260
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9232
9261
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9262
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9233
9263
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9234
9264
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9235
9265
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11899,7 +11929,7 @@ object({
|
|
|
11899
11929
|
params: record(string$2(), union([string$2(), number()])).optional()
|
|
11900
11930
|
});
|
|
11901
11931
|
//#endregion
|
|
11902
|
-
//#region ../types/dist/composition-
|
|
11932
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11903
11933
|
/**
|
|
11904
11934
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11905
11935
|
*
|
|
@@ -13246,11 +13276,49 @@ var IntegrationInstanceRefSchema = object({
|
|
|
13246
13276
|
]),
|
|
13247
13277
|
candidateStableId: string$2().optional()
|
|
13248
13278
|
});
|
|
13279
|
+
/**
|
|
13280
|
+
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13281
|
+
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13282
|
+
*
|
|
13283
|
+
* Every instance is one record of the addon's own kind, keyed by the
|
|
13284
|
+
* `integrations` registry id. The records door does the secret work: a field the
|
|
13285
|
+
* kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
|
|
13286
|
+
* deleting the row deletes the secret, and one row's value is never another's.
|
|
13287
|
+
*
|
|
13288
|
+
* Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
|
|
13289
|
+
* token or the view the form was given KEEP the stored secret;
|
|
13290
|
+
* `{ $secretClear: true }` clears it; any other string replaces it.
|
|
13291
|
+
*/
|
|
13249
13292
|
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13250
13293
|
var INSTANCES_LIST_MAX = 1e3;
|
|
13251
13294
|
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13252
13295
|
function topLevelSecretKeys(paths) {
|
|
13253
|
-
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
|
|
13296
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
|
|
13297
|
+
}
|
|
13298
|
+
/** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
|
|
13299
|
+
function secretMapKeys(paths) {
|
|
13300
|
+
return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
|
|
13301
|
+
}
|
|
13302
|
+
/**
|
|
13303
|
+
* A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
|
|
13304
|
+
* view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
|
|
13305
|
+
* or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
|
|
13306
|
+
*/
|
|
13307
|
+
function secretMapPatch(value) {
|
|
13308
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
13309
|
+
const out = {};
|
|
13310
|
+
for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
|
|
13311
|
+
else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
|
|
13312
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
13313
|
+
}
|
|
13314
|
+
/** Does the kind's body schema carry an `enabled` field? */
|
|
13315
|
+
function declaresEnabled(kind) {
|
|
13316
|
+
const json = toJSONSchema(kind.schema, {
|
|
13317
|
+
unrepresentable: "any",
|
|
13318
|
+
io: "input"
|
|
13319
|
+
});
|
|
13320
|
+
const properties = Reflect.get(json, "properties");
|
|
13321
|
+
return typeof properties === "object" && properties !== null && "enabled" in properties;
|
|
13254
13322
|
}
|
|
13255
13323
|
var RecordIntegrationInstances = class {
|
|
13256
13324
|
records;
|
|
@@ -13258,14 +13326,20 @@ var RecordIntegrationInstances = class {
|
|
|
13258
13326
|
hooks;
|
|
13259
13327
|
handle;
|
|
13260
13328
|
secretKeys;
|
|
13329
|
+
secretMaps;
|
|
13261
13330
|
nameField;
|
|
13331
|
+
/** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
|
|
13332
|
+
registryOwnsEnabled;
|
|
13262
13333
|
constructor(records, kind, hooks) {
|
|
13263
13334
|
this.records = records;
|
|
13264
13335
|
this.kind = kind;
|
|
13265
13336
|
this.hooks = hooks;
|
|
13266
13337
|
this.handle = records.kind(kind);
|
|
13267
13338
|
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13339
|
+
this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
|
|
13268
13340
|
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13341
|
+
this.registryOwnsEnabled = hooks.description.role === "devices";
|
|
13342
|
+
if (this.registryOwnsEnabled && declaresEnabled(kind)) throw new Error(`kind "${kind.name}": the integrations registry owns the on/off switch of a role "devices" addon, so the body must not declare "enabled"`);
|
|
13269
13343
|
}
|
|
13270
13344
|
/**
|
|
13271
13345
|
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
@@ -13316,7 +13390,7 @@ var RecordIntegrationInstances = class {
|
|
|
13316
13390
|
id: record.id,
|
|
13317
13391
|
name: typeof rawName === "string" ? rawName : record.id,
|
|
13318
13392
|
configured: this.hooks.configured(record.data),
|
|
13319
|
-
enabled: record.data.enabled !== false,
|
|
13393
|
+
...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
|
|
13320
13394
|
formValues: shown,
|
|
13321
13395
|
...kind !== void 0 ? { kind } : {},
|
|
13322
13396
|
...health?.status !== void 0 ? { status: health.status } : {},
|
|
@@ -13346,32 +13420,36 @@ var RecordIntegrationInstances = class {
|
|
|
13346
13420
|
id: minted,
|
|
13347
13421
|
data: this.hooks.initialBody(name, kind)
|
|
13348
13422
|
}, { ifRev: 0 });
|
|
13349
|
-
await this.hooks.onChanged?.(minted);
|
|
13423
|
+
await this.hooks.onChanged?.(minted, "created");
|
|
13350
13424
|
return { id: minted };
|
|
13351
13425
|
};
|
|
13352
13426
|
updateInstance = async ({ id, name, values }) => {
|
|
13353
13427
|
const patch = {};
|
|
13354
13428
|
for (const [key, value] of Object.entries(values)) {
|
|
13355
13429
|
if (value === void 0) continue;
|
|
13356
|
-
if (
|
|
13430
|
+
if (this.secretMaps.has(key)) {
|
|
13431
|
+
const entries = secretMapPatch(value);
|
|
13432
|
+
if (entries !== null) patch[key] = entries;
|
|
13433
|
+
} else if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13357
13434
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13358
13435
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13359
13436
|
}
|
|
13360
13437
|
if (name !== void 0) patch[this.nameField] = name;
|
|
13361
13438
|
await this.handle.patch(id, patch);
|
|
13362
|
-
await this.hooks.onChanged?.(id);
|
|
13439
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13363
13440
|
return { ok: true };
|
|
13364
13441
|
};
|
|
13365
13442
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13366
13443
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13444
|
+
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
13367
13445
|
await this.handle.patch(id, { enabled });
|
|
13368
|
-
await this.hooks.onChanged?.(id);
|
|
13446
|
+
await this.hooks.onChanged?.(id, "enabled");
|
|
13369
13447
|
return { ok: true };
|
|
13370
13448
|
};
|
|
13371
13449
|
/** The row and its keychain refs go together (the records door). */
|
|
13372
13450
|
deleteInstance = async ({ id }) => {
|
|
13373
13451
|
await this.handle.delete(id);
|
|
13374
|
-
await this.hooks.onChanged?.(id);
|
|
13452
|
+
await this.hooks.onChanged?.(id, "deleted");
|
|
13375
13453
|
return { ok: true };
|
|
13376
13454
|
};
|
|
13377
13455
|
testInstance = async (input) => {
|
|
@@ -14432,7 +14510,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14432
14510
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14433
14511
|
* catalogue lists the id (a newer database).
|
|
14434
14512
|
*/
|
|
14435
|
-
meta: MigrationMetaSchema.optional()
|
|
14513
|
+
meta: MigrationMetaSchema.optional(),
|
|
14514
|
+
/**
|
|
14515
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14516
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14517
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14518
|
+
*/
|
|
14519
|
+
ownerNotLoaded: boolean().optional()
|
|
14436
14520
|
});
|
|
14437
14521
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14438
14522
|
var MigrationPendingEntrySchema = object({
|
|
@@ -50167,6 +50251,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50167
50251
|
runsFound: number().int().nonnegative(),
|
|
50168
50252
|
pausesFound: number().int().nonnegative(),
|
|
50169
50253
|
longestPauseMs: number().nonnegative(),
|
|
50254
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50255
|
+
pulsesFound: number().int().nonnegative(),
|
|
50256
|
+
longestPulseMs: number().nonnegative(),
|
|
50170
50257
|
idleFloorW: number(),
|
|
50171
50258
|
noiseCeilingW: number(),
|
|
50172
50259
|
plateauW: number(),
|
|
@@ -56012,11 +56099,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56012
56099
|
info: record(string$2(), unknown()).optional(),
|
|
56013
56100
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56014
56101
|
originStableId: string$2().optional(),
|
|
56015
|
-
settings: record(string$2(), unknown()).optional()
|
|
56016
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56017
|
-
* the keychain, not the table (D787). */
|
|
56018
|
-
secretKeys: array(string$2()).optional(),
|
|
56019
|
-
skipRestart: boolean().optional()
|
|
56102
|
+
settings: record(string$2(), unknown()).optional()
|
|
56020
56103
|
});
|
|
56021
56104
|
var UpdateIntegrationInputSchema = object({
|
|
56022
56105
|
id: string$2(),
|
|
@@ -56037,16 +56120,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
56037
56120
|
}), method(object({ id: string$2() }), DeleteIntegrationResultSchema, {
|
|
56038
56121
|
kind: "mutation",
|
|
56039
56122
|
auth: "admin"
|
|
56040
|
-
}), method(object({ id: string$2() }), record(string$2(), unknown()), { auth: "admin" }), method(object({
|
|
56041
|
-
id: string$2(),
|
|
56042
|
-
settings: record(string$2(), unknown()),
|
|
56043
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56044
|
-
secretKeys: array(string$2()).optional(),
|
|
56045
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56046
|
-
skipRestart: boolean().optional()
|
|
56047
|
-
}), object({ success: boolean() }), {
|
|
56048
|
-
kind: "mutation",
|
|
56049
|
-
auth: "admin"
|
|
56050
56123
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56051
56124
|
addonId: string$2(),
|
|
56052
56125
|
settings: record(string$2(), unknown())
|
|
@@ -56098,6 +56171,21 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56098
56171
|
disconnectPath: string$2().min(1).optional()
|
|
56099
56172
|
});
|
|
56100
56173
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56174
|
+
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56175
|
+
role: _enum([
|
|
56176
|
+
"devices",
|
|
56177
|
+
"services",
|
|
56178
|
+
"both"
|
|
56179
|
+
]),
|
|
56180
|
+
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56181
|
+
discovery: object({
|
|
56182
|
+
mode: _enum([
|
|
56183
|
+
"none",
|
|
56184
|
+
"lan",
|
|
56185
|
+
"account"
|
|
56186
|
+
]),
|
|
56187
|
+
scopedToInstance: boolean()
|
|
56188
|
+
}).optional(),
|
|
56101
56189
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
56102
56190
|
instanceMode: IntegrationInstanceModeSchema,
|
|
56103
56191
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
@@ -56137,10 +56225,11 @@ var IntegrationInstanceSchema = object({
|
|
|
56137
56225
|
/** The addon's own rule (an URL is present, a client id is present...). */
|
|
56138
56226
|
configured: boolean(),
|
|
56139
56227
|
/**
|
|
56140
|
-
* The instance's own on/off switch (D996)
|
|
56141
|
-
*
|
|
56228
|
+
* The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
|
|
56229
|
+
* so a row written before the field existed is on). Absent when the `integrations` registry
|
|
56230
|
+
* owns the switch (`describe().role` is `devices`): read it from the registry row.
|
|
56142
56231
|
*/
|
|
56143
|
-
enabled: boolean(),
|
|
56232
|
+
enabled: boolean().optional(),
|
|
56144
56233
|
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
56145
56234
|
kind: string$2().optional(),
|
|
56146
56235
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
@@ -59007,6 +59096,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59007
59096
|
}), {
|
|
59008
59097
|
kind: "mutation",
|
|
59009
59098
|
auth: "admin"
|
|
59099
|
+
}), method(object({ integrationId: string$2().min(1) }), record(string$2(), unknown()), { auth: "admin" }), method(object({ integrationId: string$2().min(1) }), object({
|
|
59100
|
+
rows: number().int().nonnegative(),
|
|
59101
|
+
secrets: number().int().nonnegative()
|
|
59102
|
+
}), {
|
|
59103
|
+
kind: "mutation",
|
|
59104
|
+
auth: "admin"
|
|
59105
|
+
}), method(object({
|
|
59106
|
+
integrationId: string$2().min(1),
|
|
59107
|
+
patch: record(string$2(), unknown())
|
|
59108
|
+
}), object({ written: boolean() }), {
|
|
59109
|
+
kind: "mutation",
|
|
59110
|
+
auth: "admin"
|
|
59111
|
+
}), method(object({
|
|
59112
|
+
integrationId: string$2().min(1),
|
|
59113
|
+
keys: array(string$2().min(1))
|
|
59114
|
+
}), object({
|
|
59115
|
+
present: boolean(),
|
|
59116
|
+
removed: record(string$2(), unknown())
|
|
59117
|
+
}), {
|
|
59118
|
+
kind: "mutation",
|
|
59119
|
+
auth: "admin"
|
|
59010
59120
|
});
|
|
59011
59121
|
method(object({
|
|
59012
59122
|
sourceUrl: string$2(),
|
|
@@ -80333,11 +80443,15 @@ discriminatedUnion("kind", [object({
|
|
|
80333
80443
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80334
80444
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80335
80445
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80336
|
-
* 3.
|
|
80337
|
-
*
|
|
80446
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80447
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80448
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80449
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80450
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80451
|
+
* is no run.
|
|
80338
80452
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80339
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80340
|
-
* covers the longest standby blip above T.
|
|
80453
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80454
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80341
80455
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80342
80456
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80343
80457
|
*/
|
|
@@ -80348,6 +80462,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80348
80462
|
30 * SECOND_MS;
|
|
80349
80463
|
60 * SECOND_MS;
|
|
80350
80464
|
20 * MINUTE_MS$1;
|
|
80465
|
+
90 * SECOND_MS;
|
|
80466
|
+
30 * MINUTE_MS$1;
|
|
80467
|
+
10 * MINUTE_MS$1;
|
|
80468
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80351
80469
|
10 * SECOND_MS;
|
|
80352
80470
|
10 * SECOND_MS;
|
|
80353
80471
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85667,13 +85785,6 @@ Object.freeze({
|
|
|
85667
85785
|
access: "view",
|
|
85668
85786
|
role: "admin"
|
|
85669
85787
|
},
|
|
85670
|
-
"integrations.getSettings": {
|
|
85671
|
-
capName: "integrations",
|
|
85672
|
-
capScope: "system",
|
|
85673
|
-
addonId: null,
|
|
85674
|
-
access: "view",
|
|
85675
|
-
role: "admin"
|
|
85676
|
-
},
|
|
85677
85788
|
"integrations.list": {
|
|
85678
85789
|
capName: "integrations",
|
|
85679
85790
|
capScope: "system",
|
|
@@ -85681,13 +85792,6 @@ Object.freeze({
|
|
|
85681
85792
|
access: "view",
|
|
85682
85793
|
role: "admin"
|
|
85683
85794
|
},
|
|
85684
|
-
"integrations.setSettings": {
|
|
85685
|
-
capName: "integrations",
|
|
85686
|
-
capScope: "system",
|
|
85687
|
-
addonId: null,
|
|
85688
|
-
access: "create",
|
|
85689
|
-
role: "admin"
|
|
85690
|
-
},
|
|
85691
85795
|
"integrations.testConnection": {
|
|
85692
85796
|
capName: "integrations",
|
|
85693
85797
|
capScope: "system",
|
|
@@ -86682,6 +86786,13 @@ Object.freeze({
|
|
|
86682
86786
|
access: "view",
|
|
86683
86787
|
role: "admin"
|
|
86684
86788
|
},
|
|
86789
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
86790
|
+
capName: "migrations",
|
|
86791
|
+
capScope: "system",
|
|
86792
|
+
addonId: null,
|
|
86793
|
+
access: "delete",
|
|
86794
|
+
role: "admin"
|
|
86795
|
+
},
|
|
86685
86796
|
"migrations.declareOwner": {
|
|
86686
86797
|
capName: "migrations",
|
|
86687
86798
|
capScope: "system",
|
|
@@ -86689,6 +86800,13 @@ Object.freeze({
|
|
|
86689
86800
|
access: "create",
|
|
86690
86801
|
role: "admin"
|
|
86691
86802
|
},
|
|
86803
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
86804
|
+
capName: "migrations",
|
|
86805
|
+
capScope: "system",
|
|
86806
|
+
addonId: null,
|
|
86807
|
+
access: "delete",
|
|
86808
|
+
role: "admin"
|
|
86809
|
+
},
|
|
86692
86810
|
"migrations.dryRun": {
|
|
86693
86811
|
capName: "migrations",
|
|
86694
86812
|
capScope: "system",
|
|
@@ -86724,6 +86842,20 @@ Object.freeze({
|
|
|
86724
86842
|
access: "create",
|
|
86725
86843
|
role: "admin"
|
|
86726
86844
|
},
|
|
86845
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
86846
|
+
capName: "migrations",
|
|
86847
|
+
capScope: "system",
|
|
86848
|
+
addonId: null,
|
|
86849
|
+
access: "create",
|
|
86850
|
+
role: "admin"
|
|
86851
|
+
},
|
|
86852
|
+
"migrations.readLegacyIntegrationRows": {
|
|
86853
|
+
capName: "migrations",
|
|
86854
|
+
capScope: "system",
|
|
86855
|
+
addonId: null,
|
|
86856
|
+
access: "view",
|
|
86857
|
+
role: "admin"
|
|
86858
|
+
},
|
|
86727
86859
|
"migrations.restorePreMigration": {
|
|
86728
86860
|
capName: "migrations",
|
|
86729
86861
|
capScope: "system",
|
|
@@ -208590,6 +208722,7 @@ function statusOf(info) {
|
|
|
208590
208722
|
function buildMatterInstances(records, deps) {
|
|
208591
208723
|
return new RecordIntegrationInstances(records, matterBrokersEntryKind, {
|
|
208592
208724
|
description: {
|
|
208725
|
+
role: "both",
|
|
208593
208726
|
instanceMode: "multiple",
|
|
208594
208727
|
kind: "connection",
|
|
208595
208728
|
canTest: true,
|
|
@@ -209009,18 +209142,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209009
209142
|
try {
|
|
209010
209143
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
209011
209144
|
const surviving = new Set(mine.map((i) => i.id));
|
|
209012
|
-
for (const
|
|
209013
|
-
const
|
|
209014
|
-
|
|
209015
|
-
|
|
209016
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
209017
|
-
this.ctx.logger.warn("Matter integration→broker: linked broker not found", { meta: {
|
|
209018
|
-
integrationId: integration.id,
|
|
209019
|
-
brokerId
|
|
209020
|
-
} });
|
|
209021
|
-
continue;
|
|
209022
|
-
}
|
|
209023
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
209145
|
+
for (const broker of this.activeBrokers) {
|
|
209146
|
+
const integrationId = broker.integrationId;
|
|
209147
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
209148
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
209024
209149
|
}
|
|
209025
209150
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
209026
209151
|
if (toRemove.length === 0) return;
|
|
@@ -209061,9 +209186,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209061
209186
|
},
|
|
209062
209187
|
logger: this.ctx.logger,
|
|
209063
209188
|
getBrokerIdForIntegration: async (id) => {
|
|
209064
|
-
const
|
|
209065
|
-
if (
|
|
209066
|
-
return
|
|
209189
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
209190
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
209191
|
+
return broker.id;
|
|
209067
209192
|
},
|
|
209068
209193
|
listAdoptedMatter: async () => {
|
|
209069
209194
|
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.157",
|
|
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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
],
|
|
67
67
|
"requires": {
|
|
68
|
-
"server": ">=1.2.
|
|
68
|
+
"server": ">=1.2.770"
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"files": [
|