@camstack/addon-mqtt-broker 1.2.149 → 1.2.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/mqtt-broker.addon.js +508 -237
- package/dist/mqtt-broker.addon.mjs +508 -237
- package/package.json +2 -2
|
@@ -6028,7 +6028,7 @@ var ZodIssueCode = {
|
|
|
6028
6028
|
var ZodFirstPartyTypeKind;
|
|
6029
6029
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6030
6030
|
//#endregion
|
|
6031
|
-
//#region ../types/dist/sleep-
|
|
6031
|
+
//#region ../types/dist/sleep-BaWhXZFm.mjs
|
|
6032
6032
|
/**
|
|
6033
6033
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6034
6034
|
* window to float samples (D455).
|
|
@@ -8165,6 +8165,10 @@ var BaseAddon = class {
|
|
|
8165
8165
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
8166
8166
|
const patch = {};
|
|
8167
8167
|
for (const key of keys) {
|
|
8168
|
+
if (tree.get(key)?.kind === "array") {
|
|
8169
|
+
patch[key] = [];
|
|
8170
|
+
continue;
|
|
8171
|
+
}
|
|
8168
8172
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8169
8173
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8170
8174
|
}
|
|
@@ -8860,6 +8864,10 @@ var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
|
8860
8864
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
8861
8865
|
* (`auth.accessToken`, `headers[].value`).
|
|
8862
8866
|
*
|
|
8867
|
+
* `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
|
|
8868
|
+
* names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
|
|
8869
|
+
* key releases exactly that key's secret.
|
|
8870
|
+
*
|
|
8863
8871
|
* Array elements are matched by a DECLARED id key (`secretArrayKeys`:
|
|
8864
8872
|
* `{ headers: 'name' }`), never by index, so reordering a list keeps every
|
|
8865
8873
|
* keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
|
|
@@ -8872,11 +8880,13 @@ function parseSecretPath(path) {
|
|
|
8872
8880
|
if (path.length === 0) throw new InvalidRecordError("an empty secret path");
|
|
8873
8881
|
return path.split(".").map((raw) => {
|
|
8874
8882
|
const each = raw.endsWith("[]");
|
|
8875
|
-
const
|
|
8883
|
+
const map = raw.endsWith("{}");
|
|
8884
|
+
const key = each || map ? raw.slice(0, -2) : raw;
|
|
8876
8885
|
if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
|
|
8877
8886
|
return {
|
|
8878
8887
|
key,
|
|
8879
|
-
each
|
|
8888
|
+
each,
|
|
8889
|
+
map
|
|
8880
8890
|
};
|
|
8881
8891
|
});
|
|
8882
8892
|
}
|
|
@@ -8928,6 +8938,22 @@ function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arra
|
|
|
8928
8938
|
[segment.key]: mapped
|
|
8929
8939
|
};
|
|
8930
8940
|
}
|
|
8941
|
+
if (segment.map) {
|
|
8942
|
+
if (!isRecord$4(child)) return node;
|
|
8943
|
+
const kept = Object.entries(child).map(([mapKey, value]) => {
|
|
8944
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
|
|
8945
|
+
if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
|
|
8946
|
+
return [mapKey, visit({
|
|
8947
|
+
declared,
|
|
8948
|
+
concrete,
|
|
8949
|
+
value
|
|
8950
|
+
})];
|
|
8951
|
+
}).filter(([, value]) => value !== REMOVE_LEAF);
|
|
8952
|
+
return {
|
|
8953
|
+
...node,
|
|
8954
|
+
[segment.key]: Object.fromEntries(kept)
|
|
8955
|
+
};
|
|
8956
|
+
}
|
|
8931
8957
|
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8932
8958
|
if (last) {
|
|
8933
8959
|
const replacement = visit({
|
|
@@ -9069,7 +9095,8 @@ function isZodSecret(schema) {
|
|
|
9069
9095
|
}
|
|
9070
9096
|
/**
|
|
9071
9097
|
* Every declared secret path in a config schema: dotted keys, `[]` for "each
|
|
9072
|
-
* element of this array" (`connection.password`, `rows[].token`)
|
|
9098
|
+
* element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
|
|
9099
|
+
* map" (`applianceSecrets{}`). A declared
|
|
9073
9100
|
* object or array is reported as one path and not descended into — everything
|
|
9074
9101
|
* under it is secret.
|
|
9075
9102
|
*/
|
|
@@ -9080,6 +9107,7 @@ function collectZodSecretPaths(schema, prefix = "") {
|
|
|
9080
9107
|
const def = defOf(layer);
|
|
9081
9108
|
if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
9082
9109
|
else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
|
|
9110
|
+
else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
|
|
9083
9111
|
else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
|
|
9084
9112
|
else if (def.type === "intersection") {
|
|
9085
9113
|
if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
|
|
@@ -9219,7 +9247,7 @@ function defineRecordKinds(specs) {
|
|
|
9219
9247
|
retention: spec.retention,
|
|
9220
9248
|
deviceRemoval: spec.deviceRemoval,
|
|
9221
9249
|
maxDataBytes: spec.maxDataBytes,
|
|
9222
|
-
secretPaths: [...secretPaths],
|
|
9250
|
+
secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
|
|
9223
9251
|
secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
|
|
9224
9252
|
transactFamily: spec.transactFamily,
|
|
9225
9253
|
entry: spec.entry === void 0 ? void 0 : {
|
|
@@ -9264,6 +9292,7 @@ function defineEntryKind(name, spec) {
|
|
|
9264
9292
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9265
9293
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9266
9294
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
9295
|
+
...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
|
|
9267
9296
|
entry: {
|
|
9268
9297
|
title: spec.title,
|
|
9269
9298
|
...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
|
|
@@ -11925,7 +11954,7 @@ object({
|
|
|
11925
11954
|
params: record(string(), union([string(), number()])).optional()
|
|
11926
11955
|
});
|
|
11927
11956
|
//#endregion
|
|
11928
|
-
//#region ../types/dist/composition-
|
|
11957
|
+
//#region ../types/dist/composition-CRNwcFYY.mjs
|
|
11929
11958
|
/**
|
|
11930
11959
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11931
11960
|
*
|
|
@@ -12688,38 +12717,6 @@ function validateExpressionSource(src) {
|
|
|
12688
12717
|
return null;
|
|
12689
12718
|
}
|
|
12690
12719
|
/**
|
|
12691
|
-
* One power reading becomes a washer, dryer or dishwasher (D944 helper).
|
|
12692
|
-
*
|
|
12693
|
-
* Standby of the control board is under 5 W; a motor or a heater is far
|
|
12694
|
-
* above that, so 5 W is the line. A door lock or a short pump spike is not
|
|
12695
|
-
* a cycle, so running starts only after 30 s above the line.
|
|
12696
|
-
*
|
|
12697
|
-
* A wash, dry or dishwasher pause (soak, heat-wait, heater off between
|
|
12698
|
-
* bursts) lasts several minutes. Ending the cycle on the first dip would
|
|
12699
|
-
* report "finished" in the middle of the program, so the stop dwell is
|
|
12700
|
-
* 10 minutes on every type. The number is stored per type so one machine
|
|
12701
|
-
* can move later without moving the others.
|
|
12702
|
-
*/
|
|
12703
|
-
var APPLIANCE_TEMPLATE_ID = "appliance";
|
|
12704
|
-
var APPLIANCE_KINDS = [
|
|
12705
|
-
"washer",
|
|
12706
|
-
"dryer",
|
|
12707
|
-
"dishwasher"
|
|
12708
|
-
];
|
|
12709
|
-
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
12710
|
-
/**
|
|
12711
|
-
* A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
|
|
12712
|
-
*
|
|
12713
|
-
* The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
|
|
12714
|
-
* (D967), so the heater switches exactly as the plug does. Running is the
|
|
12715
|
-
* plug being on; when a power reading is also named, running is the draw
|
|
12716
|
-
* staying above the line instead (a plug left on with the stove's own
|
|
12717
|
-
* thermostat open is not heating). 50 W is far above any standby and below
|
|
12718
|
-
* the smallest radiator.
|
|
12719
|
-
*/
|
|
12720
|
-
var HEATER_TEMPLATE_ID = "heater";
|
|
12721
|
-
DeviceType.Heater;
|
|
12722
|
-
/**
|
|
12723
12720
|
* Statistics and integrals over a history read (D944).
|
|
12724
12721
|
*
|
|
12725
12722
|
* Pure: the same points always give the same answer, so a restart recomputes
|
|
@@ -12863,34 +12860,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12863
12860
|
/** Actuator groups only. Readings leave this unset. */
|
|
12864
12861
|
active: ActiveCriterionSchema.optional()
|
|
12865
12862
|
}),
|
|
12866
|
-
object({
|
|
12867
|
-
kind: literal("appliance"),
|
|
12868
|
-
templateId: literal(APPLIANCE_TEMPLATE_ID),
|
|
12869
|
-
appliance: _enum(APPLIANCE_KINDS),
|
|
12870
|
-
thresholdWatts: number().positive(),
|
|
12871
|
-
startDwellMs: number().int().nonnegative(),
|
|
12872
|
-
stopDwellMs: number().int().nonnegative(),
|
|
12873
|
-
member: HelperMemberSchema,
|
|
12874
|
-
/**
|
|
12875
|
-
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12876
|
-
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12877
|
-
* that plug (power-source link of a NEW-target composition).
|
|
12878
|
-
*/
|
|
12879
|
-
powerSwitch: HelperMemberSchema.optional(),
|
|
12880
|
-
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12881
|
-
offSeconds: number().optional()
|
|
12882
|
-
}),
|
|
12883
|
-
object({
|
|
12884
|
-
kind: literal("heater"),
|
|
12885
|
-
templateId: literal(HEATER_TEMPLATE_ID),
|
|
12886
|
-
/** The plug, read and commanded at `switch.on`. */
|
|
12887
|
-
plug: HelperMemberSchema,
|
|
12888
|
-
/** The plug's power reading, when it has one: running is then the draw, not the switch. */
|
|
12889
|
-
meter: HelperMemberSchema.optional(),
|
|
12890
|
-
thresholdWatts: number().positive(),
|
|
12891
|
-
startDwellMs: number().int().nonnegative(),
|
|
12892
|
-
stopDwellMs: number().int().nonnegative()
|
|
12893
|
-
}),
|
|
12894
12863
|
object({
|
|
12895
12864
|
kind: literal("statistic"),
|
|
12896
12865
|
stat: _enum(STATISTIC_STATS),
|
|
@@ -13114,7 +13083,7 @@ var CompositionSchema = object({
|
|
|
13114
13083
|
kind: literal("existing"),
|
|
13115
13084
|
device: CompositionSourceRefSchema
|
|
13116
13085
|
})]),
|
|
13117
|
-
features: array(CompositionFeatureSchema).min(
|
|
13086
|
+
features: array(CompositionFeatureSchema).min(0).max(16),
|
|
13118
13087
|
/**
|
|
13119
13088
|
* Present when the block is a helper template. Absent on a free
|
|
13120
13089
|
* composition (the room example, device 4603). The editor reads this,
|
|
@@ -13332,11 +13301,49 @@ var IntegrationInstanceRefSchema = object({
|
|
|
13332
13301
|
]),
|
|
13333
13302
|
candidateStableId: string().optional()
|
|
13334
13303
|
});
|
|
13304
|
+
/**
|
|
13305
|
+
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13306
|
+
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13307
|
+
*
|
|
13308
|
+
* Every instance is one record of the addon's own kind, keyed by the
|
|
13309
|
+
* `integrations` registry id. The records door does the secret work: a field the
|
|
13310
|
+
* kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
|
|
13311
|
+
* deleting the row deletes the secret, and one row's value is never another's.
|
|
13312
|
+
*
|
|
13313
|
+
* Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
|
|
13314
|
+
* token or the view the form was given KEEP the stored secret;
|
|
13315
|
+
* `{ $secretClear: true }` clears it; any other string replaces it.
|
|
13316
|
+
*/
|
|
13335
13317
|
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13336
13318
|
var INSTANCES_LIST_MAX = 1e3;
|
|
13337
13319
|
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13338
13320
|
function topLevelSecretKeys(paths) {
|
|
13339
|
-
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
|
|
13321
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
|
|
13322
|
+
}
|
|
13323
|
+
/** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
|
|
13324
|
+
function secretMapKeys(paths) {
|
|
13325
|
+
return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
|
|
13326
|
+
}
|
|
13327
|
+
/**
|
|
13328
|
+
* A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
|
|
13329
|
+
* view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
|
|
13330
|
+
* or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
|
|
13331
|
+
*/
|
|
13332
|
+
function secretMapPatch(value) {
|
|
13333
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
13334
|
+
const out = {};
|
|
13335
|
+
for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
|
|
13336
|
+
else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
|
|
13337
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
13338
|
+
}
|
|
13339
|
+
/** Does the kind's body schema carry an `enabled` field? */
|
|
13340
|
+
function declaresEnabled(kind) {
|
|
13341
|
+
const json = toJSONSchema(kind.schema, {
|
|
13342
|
+
unrepresentable: "any",
|
|
13343
|
+
io: "input"
|
|
13344
|
+
});
|
|
13345
|
+
const properties = Reflect.get(json, "properties");
|
|
13346
|
+
return typeof properties === "object" && properties !== null && "enabled" in properties;
|
|
13340
13347
|
}
|
|
13341
13348
|
var RecordIntegrationInstances = class {
|
|
13342
13349
|
records;
|
|
@@ -13344,14 +13351,20 @@ var RecordIntegrationInstances = class {
|
|
|
13344
13351
|
hooks;
|
|
13345
13352
|
handle;
|
|
13346
13353
|
secretKeys;
|
|
13354
|
+
secretMaps;
|
|
13347
13355
|
nameField;
|
|
13356
|
+
/** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
|
|
13357
|
+
registryOwnsEnabled;
|
|
13348
13358
|
constructor(records, kind, hooks) {
|
|
13349
13359
|
this.records = records;
|
|
13350
13360
|
this.kind = kind;
|
|
13351
13361
|
this.hooks = hooks;
|
|
13352
13362
|
this.handle = records.kind(kind);
|
|
13353
13363
|
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13364
|
+
this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
|
|
13354
13365
|
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13366
|
+
this.registryOwnsEnabled = hooks.description.role === "devices";
|
|
13367
|
+
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"`);
|
|
13355
13368
|
}
|
|
13356
13369
|
/**
|
|
13357
13370
|
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
@@ -13396,15 +13409,23 @@ var RecordIntegrationInstances = class {
|
|
|
13396
13409
|
viewOf(record) {
|
|
13397
13410
|
const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
|
|
13398
13411
|
const rawName = record.data[this.nameField];
|
|
13412
|
+
const kind = this.hooks.kindOf?.(record.data);
|
|
13413
|
+
const health = this.hooks.health?.(record.data, record.id);
|
|
13399
13414
|
return {
|
|
13400
13415
|
id: record.id,
|
|
13401
13416
|
name: typeof rawName === "string" ? rawName : record.id,
|
|
13402
13417
|
configured: this.hooks.configured(record.data),
|
|
13403
|
-
enabled: record.data.enabled !== false,
|
|
13404
|
-
formValues: shown
|
|
13418
|
+
...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
|
|
13419
|
+
formValues: shown,
|
|
13420
|
+
...kind !== void 0 ? { kind } : {},
|
|
13421
|
+
...health?.status !== void 0 ? { status: health.status } : {},
|
|
13422
|
+
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13405
13423
|
};
|
|
13406
13424
|
}
|
|
13407
|
-
describe = async () => this.hooks.description
|
|
13425
|
+
describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
|
|
13426
|
+
...this.hooks.description,
|
|
13427
|
+
mintsId: true
|
|
13428
|
+
};
|
|
13408
13429
|
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13409
13430
|
listInstances = async () => {
|
|
13410
13431
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
@@ -13414,37 +13435,46 @@ var RecordIntegrationInstances = class {
|
|
|
13414
13435
|
return record === null ? null : this.viewOf(record);
|
|
13415
13436
|
};
|
|
13416
13437
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13417
|
-
createInstance = async ({ id, name }) => {
|
|
13438
|
+
createInstance = async ({ id, name, kind }) => {
|
|
13439
|
+
const minted = id ?? this.hooks.mintId?.({
|
|
13440
|
+
name,
|
|
13441
|
+
...kind !== void 0 ? { kind } : {}
|
|
13442
|
+
});
|
|
13443
|
+
if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
|
|
13418
13444
|
await this.handle.put({
|
|
13419
|
-
id,
|
|
13420
|
-
data: this.hooks.initialBody(name)
|
|
13445
|
+
id: minted,
|
|
13446
|
+
data: this.hooks.initialBody(name, kind)
|
|
13421
13447
|
}, { ifRev: 0 });
|
|
13422
|
-
await this.hooks.onChanged?.(
|
|
13423
|
-
return { id };
|
|
13448
|
+
await this.hooks.onChanged?.(minted, "created");
|
|
13449
|
+
return { id: minted };
|
|
13424
13450
|
};
|
|
13425
13451
|
updateInstance = async ({ id, name, values }) => {
|
|
13426
13452
|
const patch = {};
|
|
13427
13453
|
for (const [key, value] of Object.entries(values)) {
|
|
13428
13454
|
if (value === void 0) continue;
|
|
13429
|
-
if (
|
|
13455
|
+
if (this.secretMaps.has(key)) {
|
|
13456
|
+
const entries = secretMapPatch(value);
|
|
13457
|
+
if (entries !== null) patch[key] = entries;
|
|
13458
|
+
} else if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13430
13459
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13431
13460
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13432
13461
|
}
|
|
13433
13462
|
if (name !== void 0) patch[this.nameField] = name;
|
|
13434
13463
|
await this.handle.patch(id, patch);
|
|
13435
|
-
await this.hooks.onChanged?.(id);
|
|
13464
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13436
13465
|
return { ok: true };
|
|
13437
13466
|
};
|
|
13438
13467
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13439
13468
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13469
|
+
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
13440
13470
|
await this.handle.patch(id, { enabled });
|
|
13441
|
-
await this.hooks.onChanged?.(id);
|
|
13471
|
+
await this.hooks.onChanged?.(id, "enabled");
|
|
13442
13472
|
return { ok: true };
|
|
13443
13473
|
};
|
|
13444
13474
|
/** The row and its keychain refs go together (the records door). */
|
|
13445
13475
|
deleteInstance = async ({ id }) => {
|
|
13446
13476
|
await this.handle.delete(id);
|
|
13447
|
-
await this.hooks.onChanged?.(id);
|
|
13477
|
+
await this.hooks.onChanged?.(id, "deleted");
|
|
13448
13478
|
return { ok: true };
|
|
13449
13479
|
};
|
|
13450
13480
|
testInstance = async (input) => {
|
|
@@ -48894,49 +48924,6 @@ function actuator(input) {
|
|
|
48894
48924
|
};
|
|
48895
48925
|
}
|
|
48896
48926
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
48897
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
48898
|
-
"no-power-source",
|
|
48899
|
-
"switch-missing",
|
|
48900
|
-
"switch-offline",
|
|
48901
|
-
"switch-unknown",
|
|
48902
|
-
"restart-in-progress",
|
|
48903
|
-
"switch-call-failed"
|
|
48904
|
-
]);
|
|
48905
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
48906
|
-
accepted: literal(false),
|
|
48907
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
48908
|
-
/** One line for the operator, naming the switch. */
|
|
48909
|
-
message: string()
|
|
48910
|
-
})]);
|
|
48911
|
-
var PowerSourceInfoSchema = object({
|
|
48912
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
48913
|
-
linked: boolean(),
|
|
48914
|
-
switchDeviceId: number().nullable(),
|
|
48915
|
-
switchName: string().nullable(),
|
|
48916
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
48917
|
-
switchOn: boolean().nullable(),
|
|
48918
|
-
switchOnline: boolean().nullable(),
|
|
48919
|
-
restartInProgress: boolean(),
|
|
48920
|
-
offSeconds: number().int()
|
|
48921
|
-
});
|
|
48922
|
-
object({
|
|
48923
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
48924
|
-
switchOn: boolean().nullable(),
|
|
48925
|
-
offSeconds: number().int(),
|
|
48926
|
-
lastFetchedAt: number()
|
|
48927
|
-
});
|
|
48928
|
-
DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater;
|
|
48929
|
-
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
48930
|
-
systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48931
|
-
kind: "mutation",
|
|
48932
|
-
auth: "admin"
|
|
48933
|
-
}), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48934
|
-
kind: "mutation",
|
|
48935
|
-
auth: "admin"
|
|
48936
|
-
}), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48937
|
-
kind: "mutation",
|
|
48938
|
-
auth: "admin"
|
|
48939
|
-
});
|
|
48940
48927
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
48941
48928
|
DeviceType.Camera;
|
|
48942
48929
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -48964,6 +48951,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
|
|
|
48964
48951
|
}).optional()
|
|
48965
48952
|
})) });
|
|
48966
48953
|
/**
|
|
48954
|
+
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
48955
|
+
* `z.infer`, they do not restate the shape.
|
|
48956
|
+
*/
|
|
48957
|
+
var OverrideSourceSchema = object({
|
|
48958
|
+
addonId: string().min(1),
|
|
48959
|
+
stableId: string().min(1),
|
|
48960
|
+
cap: string().min(1),
|
|
48961
|
+
fieldPath: string().min(1)
|
|
48962
|
+
});
|
|
48963
|
+
var IdentityTransformDraftSchema = object({ kind: literal("identity") });
|
|
48964
|
+
var InvertTransformDraftSchema = object({ kind: literal("invert") });
|
|
48965
|
+
var ThresholdTransformDraftSchema = object({
|
|
48966
|
+
kind: literal("threshold"),
|
|
48967
|
+
comparison: _enum(["gte", "lte"]),
|
|
48968
|
+
threshold: number(),
|
|
48969
|
+
startDwellMs: number().nonnegative(),
|
|
48970
|
+
stopDwellMs: number().nonnegative()
|
|
48971
|
+
});
|
|
48972
|
+
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
48973
|
+
IdentityTransformDraftSchema,
|
|
48974
|
+
InvertTransformDraftSchema,
|
|
48975
|
+
ThresholdTransformDraftSchema,
|
|
48976
|
+
object({
|
|
48977
|
+
kind: literal("linear"),
|
|
48978
|
+
scale: number(),
|
|
48979
|
+
offset: number(),
|
|
48980
|
+
clampMin: number().optional(),
|
|
48981
|
+
clampMax: number().optional()
|
|
48982
|
+
}),
|
|
48983
|
+
object({
|
|
48984
|
+
kind: literal("enum-map"),
|
|
48985
|
+
mapping: record(string(), string().nullable())
|
|
48986
|
+
}),
|
|
48987
|
+
object({ kind: literal("number") })
|
|
48988
|
+
]);
|
|
48989
|
+
var ScalarOverrideDraftSchema = object({
|
|
48990
|
+
kind: literal("scalar"),
|
|
48991
|
+
cap: string().min(1),
|
|
48992
|
+
targetId: string().min(1),
|
|
48993
|
+
source: OverrideSourceSchema,
|
|
48994
|
+
transform: CustomizeTransformDraftSchema
|
|
48995
|
+
});
|
|
48996
|
+
var KeyedArrayOverrideDraftSchema = object({
|
|
48997
|
+
kind: literal("keyedArray"),
|
|
48998
|
+
cap: string().min(1),
|
|
48999
|
+
targetId: string().min(1),
|
|
49000
|
+
key: string().min(1),
|
|
49001
|
+
label: string().min(1),
|
|
49002
|
+
fieldPath: string().min(1),
|
|
49003
|
+
source: OverrideSourceSchema,
|
|
49004
|
+
transform: CustomizeTransformDraftSchema
|
|
49005
|
+
});
|
|
49006
|
+
var PulseOverrideDraftSchema = object({
|
|
49007
|
+
kind: literal("pulse"),
|
|
49008
|
+
cap: string().min(1),
|
|
49009
|
+
targetId: string().min(1),
|
|
49010
|
+
enabled: boolean(),
|
|
49011
|
+
debounceMs: number().nonnegative(),
|
|
49012
|
+
edges: array(_enum([
|
|
49013
|
+
"rising",
|
|
49014
|
+
"button",
|
|
49015
|
+
"native-event"
|
|
49016
|
+
])).min(1).optional(),
|
|
49017
|
+
sources: array(OverrideSourceSchema).min(1)
|
|
49018
|
+
});
|
|
49019
|
+
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
49020
|
+
"in-addition",
|
|
49021
|
+
"confirm",
|
|
49022
|
+
"replace"
|
|
49023
|
+
]).optional() });
|
|
49024
|
+
discriminatedUnion("kind", [
|
|
49025
|
+
ScalarOverrideDraftSchema,
|
|
49026
|
+
KeyedArrayOverrideDraftSchema,
|
|
49027
|
+
PulseOverrideDraftSchema,
|
|
49028
|
+
object({
|
|
49029
|
+
kind: literal("span"),
|
|
49030
|
+
cap: string().min(1),
|
|
49031
|
+
targetId: string().min(1),
|
|
49032
|
+
timeoutMs: number().nonnegative(),
|
|
49033
|
+
cooldownMs: number().nonnegative().optional(),
|
|
49034
|
+
sources: array(SpanSourceDraftSchema)
|
|
49035
|
+
})
|
|
49036
|
+
]);
|
|
49037
|
+
object({
|
|
49038
|
+
targetId: string().min(1),
|
|
49039
|
+
configured: boolean(),
|
|
49040
|
+
source: _enum(["native", "assigned"]),
|
|
49041
|
+
state: _enum([
|
|
49042
|
+
"active",
|
|
49043
|
+
"stale",
|
|
49044
|
+
"released",
|
|
49045
|
+
"unavailable"
|
|
49046
|
+
]),
|
|
49047
|
+
reason: string().nullable()
|
|
49048
|
+
});
|
|
49049
|
+
/**
|
|
49050
|
+
* What a running-threshold proposal says (planning#405). The schema is the
|
|
49051
|
+
* type: the algorithm, the capability method and the UI all read `z.infer`.
|
|
49052
|
+
*
|
|
49053
|
+
* A proposal is read-only advice: it carries the numbers it was derived from
|
|
49054
|
+
* (`evidence`) so the operator can judge it, and an `insufficient` answer names
|
|
49055
|
+
* WHY with a typed reason and the catalog defaults, never an invented number.
|
|
49056
|
+
*/
|
|
49057
|
+
var RunningProposalTierSchema = _enum(["raw", "5m"]);
|
|
49058
|
+
var RunningProposalConfidenceSchema = _enum([
|
|
49059
|
+
"high",
|
|
49060
|
+
"medium",
|
|
49061
|
+
"low"
|
|
49062
|
+
]);
|
|
49063
|
+
var RunningProposalInsufficientReasonSchema = _enum([
|
|
49064
|
+
"no-history",
|
|
49065
|
+
"span-too-short",
|
|
49066
|
+
"truncated",
|
|
49067
|
+
"no-idle-floor",
|
|
49068
|
+
"no-runs",
|
|
49069
|
+
"no-separation"
|
|
49070
|
+
]);
|
|
49071
|
+
var RunningProposalReplaySchema = object({
|
|
49072
|
+
/** Intervals the real dwell expression held true over the window. */
|
|
49073
|
+
intervals: number().int().nonnegative(),
|
|
49074
|
+
/** Intervals outside every run, or a second interval inside one. */
|
|
49075
|
+
extra: number().int().nonnegative(),
|
|
49076
|
+
/** Runs without an interval of their own (none, or merged into a neighbour). */
|
|
49077
|
+
missed: number().int().nonnegative()
|
|
49078
|
+
});
|
|
49079
|
+
var RunningProposalEvidenceSchema = object({
|
|
49080
|
+
tier: RunningProposalTierSchema,
|
|
49081
|
+
spanMs: number().nonnegative(),
|
|
49082
|
+
from: number(),
|
|
49083
|
+
to: number(),
|
|
49084
|
+
points: number().int().nonnegative(),
|
|
49085
|
+
runsFound: number().int().nonnegative(),
|
|
49086
|
+
pausesFound: number().int().nonnegative(),
|
|
49087
|
+
longestPauseMs: number().nonnegative(),
|
|
49088
|
+
idleFloorW: number(),
|
|
49089
|
+
noiseCeilingW: number(),
|
|
49090
|
+
plateauW: number(),
|
|
49091
|
+
replay: RunningProposalReplaySchema
|
|
49092
|
+
});
|
|
49093
|
+
var RunningProposalInsufficientEvidenceSchema = object({
|
|
49094
|
+
spanMs: number().nonnegative(),
|
|
49095
|
+
points: number().int().nonnegative(),
|
|
49096
|
+
runsFound: number().int().nonnegative()
|
|
49097
|
+
});
|
|
49098
|
+
var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
|
|
49099
|
+
state: literal("proposal"),
|
|
49100
|
+
transform: ThresholdTransformDraftSchema,
|
|
49101
|
+
evidence: RunningProposalEvidenceSchema,
|
|
49102
|
+
confidence: RunningProposalConfidenceSchema,
|
|
49103
|
+
/** Why the confidence is not higher; empty when nothing held it back. */
|
|
49104
|
+
notes: array(string())
|
|
49105
|
+
}), object({
|
|
49106
|
+
state: literal("insufficient"),
|
|
49107
|
+
reason: RunningProposalInsufficientReasonSchema,
|
|
49108
|
+
evidence: RunningProposalInsufficientEvidenceSchema,
|
|
49109
|
+
/** The catalog defaults, offered as what stands when nothing better is known. */
|
|
49110
|
+
fallback: ThresholdTransformDraftSchema
|
|
49111
|
+
})]);
|
|
49112
|
+
/**
|
|
48967
49113
|
* The report of a one-shot "move this owner's plaintext secrets into the
|
|
48968
49114
|
* keychain" run (D878). COUNTS only - never a value, never a ref.
|
|
48969
49115
|
*
|
|
@@ -49253,6 +49399,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
49253
49399
|
kind: "mutation",
|
|
49254
49400
|
auth: "admin",
|
|
49255
49401
|
access: "view"
|
|
49402
|
+
}), method(object({
|
|
49403
|
+
deviceId: number().int(),
|
|
49404
|
+
cap: string().min(1),
|
|
49405
|
+
fieldPath: string().min(1),
|
|
49406
|
+
deviceType: _enum(DeviceType).optional()
|
|
49407
|
+
}), RunningThresholdProposalSchema, {
|
|
49408
|
+
auth: "admin",
|
|
49409
|
+
access: "view"
|
|
49256
49410
|
}), method(object({
|
|
49257
49411
|
blockId: string().min(1),
|
|
49258
49412
|
trace: AutomationRunTraceSchema.optional(),
|
|
@@ -50061,7 +50215,27 @@ method(object({
|
|
|
50061
50215
|
* exposes only the COMMON surface: link state, supported device
|
|
50062
50216
|
* kinds (so the UI can filter the device picker), and per-device
|
|
50063
50217
|
* expose/unexpose.
|
|
50064
|
-
|
|
50218
|
+
*
|
|
50219
|
+
* INSTANCES. One export addon can serve several independent targets (a
|
|
50220
|
+
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
50221
|
+
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
50222
|
+
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
50223
|
+
* default instance's); present = that instance; an id the provider does not
|
|
50224
|
+
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
50225
|
+
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
50226
|
+
* change always names the target it changes. A single-instance provider
|
|
50227
|
+
* accepts an absent id on a read or its own id, and refuses any other.
|
|
50228
|
+
*/
|
|
50229
|
+
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
50230
|
+
var InstanceIdSchema$1 = string().min(1);
|
|
50231
|
+
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
50232
|
+
/** One independent export target of an addon. */
|
|
50233
|
+
var ExportInstanceSchema = object({
|
|
50234
|
+
instanceId: InstanceIdSchema$1,
|
|
50235
|
+
name: string(),
|
|
50236
|
+
enabled: boolean(),
|
|
50237
|
+
kind: string().optional()
|
|
50238
|
+
});
|
|
50065
50239
|
var LinkStateSchema = _enum([
|
|
50066
50240
|
"unlinked",
|
|
50067
50241
|
"linked",
|
|
@@ -50112,10 +50286,14 @@ var ExposedDeviceSchema = object({
|
|
|
50112
50286
|
capabilities: array(string()).optional()
|
|
50113
50287
|
});
|
|
50114
50288
|
var ExposeInputSchema = object({
|
|
50289
|
+
instanceId: InstanceIdSchema$1,
|
|
50115
50290
|
deviceId: string(),
|
|
50116
50291
|
capabilities: array(string()).optional()
|
|
50117
50292
|
});
|
|
50118
|
-
var UnexposeInputSchema = object({
|
|
50293
|
+
var UnexposeInputSchema = object({
|
|
50294
|
+
instanceId: InstanceIdSchema$1,
|
|
50295
|
+
deviceId: string()
|
|
50296
|
+
});
|
|
50119
50297
|
/**
|
|
50120
50298
|
* What an exporter may expose besides devices (D968). An action and a
|
|
50121
50299
|
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
@@ -50144,15 +50322,21 @@ var ExposedEntitySchema = object({
|
|
|
50144
50322
|
entity: ExportEntityRefSchema,
|
|
50145
50323
|
exposedAs: string().optional()
|
|
50146
50324
|
});
|
|
50147
|
-
var ExposeEntityInputSchema = object({
|
|
50148
|
-
|
|
50149
|
-
|
|
50325
|
+
var ExposeEntityInputSchema = object({
|
|
50326
|
+
instanceId: InstanceIdSchema$1,
|
|
50327
|
+
entity: ExportEntityRefSchema
|
|
50328
|
+
});
|
|
50329
|
+
var UnexposeEntityInputSchema = object({
|
|
50330
|
+
instanceId: InstanceIdSchema$1,
|
|
50331
|
+
entity: ExportEntityRefSchema
|
|
50332
|
+
});
|
|
50333
|
+
method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
50150
50334
|
kind: "mutation",
|
|
50151
50335
|
auth: "admin"
|
|
50152
50336
|
}), method(UnexposeInputSchema, _void(), {
|
|
50153
50337
|
kind: "mutation",
|
|
50154
50338
|
auth: "admin"
|
|
50155
|
-
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(
|
|
50339
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
50156
50340
|
kind: "mutation",
|
|
50157
50341
|
auth: "admin"
|
|
50158
50342
|
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
@@ -53353,15 +53537,15 @@ var FanPresetCaptureResultSchema = object({
|
|
|
53353
53537
|
items: array(FanCaptureItemSchema),
|
|
53354
53538
|
omitted: array(FanCaptureOmissionSchema)
|
|
53355
53539
|
});
|
|
53356
|
-
var deviceInput$
|
|
53540
|
+
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
53357
53541
|
DeviceType.Fan;
|
|
53358
|
-
method(deviceInput$
|
|
53542
|
+
method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
53359
53543
|
kind: "mutation",
|
|
53360
53544
|
auth: "admin"
|
|
53361
|
-
}), method(deviceInput$
|
|
53545
|
+
}), method(deviceInput$2.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
|
|
53362
53546
|
kind: "mutation",
|
|
53363
53547
|
auth: "protected"
|
|
53364
|
-
}), method(deviceInput$
|
|
53548
|
+
}), method(deviceInput$2.extend({
|
|
53365
53549
|
fields: array(FanCaptureFieldSchema).optional(),
|
|
53366
53550
|
steps: array(ActionStepSchema).max(20).default([])
|
|
53367
53551
|
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
@@ -53885,13 +54069,65 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
53885
54069
|
*/
|
|
53886
54070
|
var ConfigSchemaPassthrough$3 = custom();
|
|
53887
54071
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
54072
|
+
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
54073
|
+
/**
|
|
54074
|
+
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
54075
|
+
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
54076
|
+
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
54077
|
+
* interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
|
|
54078
|
+
* instance id.
|
|
54079
|
+
*/
|
|
54080
|
+
var IntegrationInstanceKindSchema = object({
|
|
54081
|
+
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
54082
|
+
id: string().min(1),
|
|
54083
|
+
label: string().min(1),
|
|
54084
|
+
/** The kind signs in through a server-held interactive session. */
|
|
54085
|
+
signIn: object({
|
|
54086
|
+
basePath: string().min(1),
|
|
54087
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
54088
|
+
}).optional(),
|
|
54089
|
+
/** Relative hub path to start an interactive account link of an instance of this kind. */
|
|
54090
|
+
connectPath: string().min(1).optional(),
|
|
54091
|
+
/** Relative hub path to disconnect that account. */
|
|
54092
|
+
disconnectPath: string().min(1).optional()
|
|
54093
|
+
});
|
|
53888
54094
|
var IntegrationInstanceDescriptionSchema = object({
|
|
54095
|
+
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
54096
|
+
role: _enum([
|
|
54097
|
+
"devices",
|
|
54098
|
+
"services",
|
|
54099
|
+
"both"
|
|
54100
|
+
]),
|
|
54101
|
+
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
54102
|
+
discovery: object({
|
|
54103
|
+
mode: _enum([
|
|
54104
|
+
"none",
|
|
54105
|
+
"lan",
|
|
54106
|
+
"account"
|
|
54107
|
+
]),
|
|
54108
|
+
scopedToInstance: boolean()
|
|
54109
|
+
}).optional(),
|
|
53889
54110
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
53890
|
-
instanceMode:
|
|
54111
|
+
instanceMode: IntegrationInstanceModeSchema,
|
|
53891
54112
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
53892
54113
|
kind: string(),
|
|
53893
54114
|
/** True when `testInstance` really probes the remote. */
|
|
53894
|
-
canTest: boolean()
|
|
54115
|
+
canTest: boolean(),
|
|
54116
|
+
/**
|
|
54117
|
+
* The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
|
|
54118
|
+
* Add flow asks which first and sends it as `createInstance.kind`.
|
|
54119
|
+
*/
|
|
54120
|
+
kinds: array(IntegrationInstanceKindSchema).optional(),
|
|
54121
|
+
/**
|
|
54122
|
+
* The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
|
|
54123
|
+
* devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
|
|
54124
|
+
*/
|
|
54125
|
+
allowRestoreId: boolean().optional(),
|
|
54126
|
+
/**
|
|
54127
|
+
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
54128
|
+
* send an id (the UI then mints an opaque one).
|
|
54129
|
+
*/
|
|
54130
|
+
mintsId: boolean().optional()
|
|
53895
54131
|
});
|
|
53896
54132
|
var IntegrationInstanceStatusSchema = object({
|
|
53897
54133
|
state: _enum([
|
|
@@ -53900,7 +54136,9 @@ var IntegrationInstanceStatusSchema = object({
|
|
|
53900
54136
|
"error",
|
|
53901
54137
|
"unknown"
|
|
53902
54138
|
]),
|
|
53903
|
-
detail: string().optional()
|
|
54139
|
+
detail: string().optional(),
|
|
54140
|
+
/** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
|
|
54141
|
+
label: string().optional()
|
|
53904
54142
|
});
|
|
53905
54143
|
var IntegrationInstanceSchema = object({
|
|
53906
54144
|
id: IntegrationInstanceIdSchema,
|
|
@@ -53908,13 +54146,18 @@ var IntegrationInstanceSchema = object({
|
|
|
53908
54146
|
/** The addon's own rule (an URL is present, a client id is present...). */
|
|
53909
54147
|
configured: boolean(),
|
|
53910
54148
|
/**
|
|
53911
|
-
* The instance's own on/off switch (D996)
|
|
53912
|
-
*
|
|
54149
|
+
* The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
|
|
54150
|
+
* so a row written before the field existed is on). Absent when the `integrations` registry
|
|
54151
|
+
* owns the switch (`describe().role` is `devices`): read it from the registry row.
|
|
53913
54152
|
*/
|
|
53914
|
-
enabled: boolean(),
|
|
54153
|
+
enabled: boolean().optional(),
|
|
54154
|
+
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
54155
|
+
kind: string().optional(),
|
|
53915
54156
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
53916
54157
|
formValues: record(string(), unknown()),
|
|
53917
|
-
status: IntegrationInstanceStatusSchema.optional()
|
|
54158
|
+
status: IntegrationInstanceStatusSchema.optional(),
|
|
54159
|
+
/** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
|
|
54160
|
+
lastCheckedAt: number().optional()
|
|
53918
54161
|
});
|
|
53919
54162
|
var IntegrationInstancePatchSchema = object({
|
|
53920
54163
|
id: IntegrationInstanceIdSchema,
|
|
@@ -53932,10 +54175,14 @@ var integrationInstancesCapability = {
|
|
|
53932
54175
|
instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
53933
54176
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
53934
54177
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
53935
|
-
/**
|
|
54178
|
+
/**
|
|
54179
|
+
* Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
|
|
54180
|
+
* without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
|
|
54181
|
+
*/
|
|
53936
54182
|
createInstance: method(object({
|
|
53937
|
-
id: IntegrationInstanceIdSchema,
|
|
53938
|
-
name: string().min(1)
|
|
54183
|
+
id: IntegrationInstanceIdSchema.optional(),
|
|
54184
|
+
name: string().min(1),
|
|
54185
|
+
kind: string().min(1).optional()
|
|
53939
54186
|
}), object({ id: string() }), {
|
|
53940
54187
|
kind: "mutation",
|
|
53941
54188
|
auth: "admin"
|
|
@@ -55767,6 +56014,11 @@ var LogEntrySchema = object({
|
|
|
55767
56014
|
* flight, and not asking for the next slice ends the scan.
|
|
55768
56015
|
*/
|
|
55769
56016
|
var LogReadQuerySchema = object({
|
|
56017
|
+
/**
|
|
56018
|
+
* Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
|
|
56019
|
+
* the provider's first readable one, so a caller written before instances existed is unchanged.
|
|
56020
|
+
*/
|
|
56021
|
+
instanceId: string().min(1).max(200).optional(),
|
|
55770
56022
|
/** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
|
|
55771
56023
|
requestId: string().min(1).max(64).optional(),
|
|
55772
56024
|
/** The device the line is about (`tags.deviceId`, numeric). */
|
|
@@ -55829,6 +56081,8 @@ var LogReadResultSchema = object({
|
|
|
55829
56081
|
/** True when `cancelRead` ended this slice early. */
|
|
55830
56082
|
cancelled: boolean().optional()
|
|
55831
56083
|
});
|
|
56084
|
+
/** Names one destination instance; absent ⇒ the provider's first readable one. */
|
|
56085
|
+
var LogInstanceSelectorSchema = object({ instanceId: string().min(1).max(200).optional() });
|
|
55832
56086
|
var LogReadCapabilitiesSchema = object({
|
|
55833
56087
|
/** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
|
|
55834
56088
|
readable: boolean(),
|
|
@@ -55847,7 +56101,7 @@ var LogReadCapabilitiesSchema = object({
|
|
|
55847
56101
|
/** How far back the store reaches, when the provider knows (days). */
|
|
55848
56102
|
retentionDays: number().nullable()
|
|
55849
56103
|
});
|
|
55850
|
-
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(
|
|
56104
|
+
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(LogInstanceSelectorSchema, LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string().min(1).max(64) }), object({ cancelled: boolean() }), {
|
|
55851
56105
|
kind: "mutation",
|
|
55852
56106
|
auth: "admin"
|
|
55853
56107
|
});
|
|
@@ -67438,6 +67692,49 @@ object({
|
|
|
67438
67692
|
DeviceType.PetFeeder,
|
|
67439
67693
|
DeviceType.Heater
|
|
67440
67694
|
].filter((type) => type !== DeviceType.Sensor);
|
|
67695
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
67696
|
+
"no-power-source",
|
|
67697
|
+
"switch-missing",
|
|
67698
|
+
"switch-offline",
|
|
67699
|
+
"switch-unknown",
|
|
67700
|
+
"restart-in-progress",
|
|
67701
|
+
"switch-call-failed"
|
|
67702
|
+
]);
|
|
67703
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
67704
|
+
accepted: literal(false),
|
|
67705
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
67706
|
+
/** One line for the operator, naming the switch. */
|
|
67707
|
+
message: string()
|
|
67708
|
+
})]);
|
|
67709
|
+
var PowerSourceInfoSchema = object({
|
|
67710
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
67711
|
+
linked: boolean(),
|
|
67712
|
+
switchDeviceId: number().nullable(),
|
|
67713
|
+
switchName: string().nullable(),
|
|
67714
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
67715
|
+
switchOn: boolean().nullable(),
|
|
67716
|
+
switchOnline: boolean().nullable(),
|
|
67717
|
+
restartInProgress: boolean(),
|
|
67718
|
+
offSeconds: number().int()
|
|
67719
|
+
});
|
|
67720
|
+
object({
|
|
67721
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
67722
|
+
switchOn: boolean().nullable(),
|
|
67723
|
+
offSeconds: number().int(),
|
|
67724
|
+
lastFetchedAt: number()
|
|
67725
|
+
});
|
|
67726
|
+
DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater;
|
|
67727
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
67728
|
+
systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67729
|
+
kind: "mutation",
|
|
67730
|
+
auth: "admin"
|
|
67731
|
+
}), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67732
|
+
kind: "mutation",
|
|
67733
|
+
auth: "admin"
|
|
67734
|
+
}), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67735
|
+
kind: "mutation",
|
|
67736
|
+
auth: "admin"
|
|
67737
|
+
});
|
|
67441
67738
|
/**
|
|
67442
67739
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
67443
67740
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -75491,6 +75788,25 @@ function buildDescriptor(capName, kind) {
|
|
|
75491
75788
|
* contributes one entry per bound cap present in this map.
|
|
75492
75789
|
*/
|
|
75493
75790
|
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
75791
|
+
/**
|
|
75792
|
+
* What a remote key is CALLED and what it LOOKS like, for every role (D963),
|
|
75793
|
+
* shared by the admin UI and the viewer. A role is data on the wire; each
|
|
75794
|
+
* client resolves its name from `label` (English and Italian side by side) and
|
|
75795
|
+
* its glyph from `icon`, a lucide id from the shared icon catalogue — the
|
|
75796
|
+
* server sends an id, a client maps it to its own icon set.
|
|
75797
|
+
*
|
|
75798
|
+
* `legend` is text printed on the key INSTEAD of a glyph (a digit has no
|
|
75799
|
+
* pictogram): a client that can draw text shows it, one that cannot falls back
|
|
75800
|
+
* to `icon`.
|
|
75801
|
+
*/
|
|
75802
|
+
function digit(n) {
|
|
75803
|
+
return {
|
|
75804
|
+
label: t(n, n),
|
|
75805
|
+
icon: "hash",
|
|
75806
|
+
legend: n
|
|
75807
|
+
};
|
|
75808
|
+
}
|
|
75809
|
+
digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
|
|
75494
75810
|
DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
|
|
75495
75811
|
var btn = (idSuffix, row, col, init) => ({
|
|
75496
75812
|
idSuffix,
|
|
@@ -75618,6 +75934,34 @@ discriminatedUnion("kind", [object({
|
|
|
75618
75934
|
kind: literal("pronto"),
|
|
75619
75935
|
code: string()
|
|
75620
75936
|
})]);
|
|
75937
|
+
/**
|
|
75938
|
+
* Propose a running threshold from a device's own power history (planning#405).
|
|
75939
|
+
*
|
|
75940
|
+
* Pure and deterministic: the same series gives the same answer. The series is
|
|
75941
|
+
* a HELD-VALUE step series — a point's value stands until the next point — which
|
|
75942
|
+
* is how device-history stores a gauge, so "time at a level" is exact, not a
|
|
75943
|
+
* count of samples.
|
|
75944
|
+
*
|
|
75945
|
+
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
75946
|
+
* near F. A device that is not near one level most of the time has no floor.
|
|
75947
|
+
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
75948
|
+
* 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
|
|
75949
|
+
* the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
|
|
75950
|
+
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
75951
|
+
* grid a person would type; stop dwell covers the longest pause; start dwell
|
|
75952
|
+
* covers the longest standby blip above T.
|
|
75953
|
+
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
75954
|
+
* run must hold exactly one true interval and nothing may flap outside.
|
|
75955
|
+
*/
|
|
75956
|
+
var SECOND_MS = 1e3;
|
|
75957
|
+
var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
75958
|
+
24 * (60 * MINUTE_MS$1);
|
|
75959
|
+
10 * MINUTE_MS$1;
|
|
75960
|
+
30 * SECOND_MS;
|
|
75961
|
+
60 * SECOND_MS;
|
|
75962
|
+
20 * MINUTE_MS$1;
|
|
75963
|
+
10 * SECOND_MS;
|
|
75964
|
+
10 * SECOND_MS;
|
|
75621
75965
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
75622
75966
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
75623
75967
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
@@ -77159,6 +77503,13 @@ Object.freeze({
|
|
|
77159
77503
|
access: "create",
|
|
77160
77504
|
role: "admin"
|
|
77161
77505
|
},
|
|
77506
|
+
"coreBlocks.proposeRunningThreshold": {
|
|
77507
|
+
capName: "core-blocks",
|
|
77508
|
+
capScope: "system",
|
|
77509
|
+
addonId: null,
|
|
77510
|
+
access: "view",
|
|
77511
|
+
role: "admin"
|
|
77512
|
+
},
|
|
77162
77513
|
"coreBlocks.reportAutomationRun": {
|
|
77163
77514
|
capName: "core-blocks",
|
|
77164
77515
|
capScope: "system",
|
|
@@ -77670,6 +78021,13 @@ Object.freeze({
|
|
|
77670
78021
|
access: "view",
|
|
77671
78022
|
role: "admin"
|
|
77672
78023
|
},
|
|
78024
|
+
"deviceExport.listInstances": {
|
|
78025
|
+
capName: "device-export",
|
|
78026
|
+
capScope: "system",
|
|
78027
|
+
addonId: null,
|
|
78028
|
+
access: "view",
|
|
78029
|
+
role: "admin"
|
|
78030
|
+
},
|
|
77673
78031
|
"deviceExport.listSupportedDeviceKinds": {
|
|
77674
78032
|
capName: "device-export",
|
|
77675
78033
|
capScope: "system",
|
|
@@ -86617,6 +86975,11 @@ Object.freeze({
|
|
|
86617
86975
|
form: "single",
|
|
86618
86976
|
optional: false
|
|
86619
86977
|
}],
|
|
86978
|
+
"coreBlocks.proposeRunningThreshold": [{
|
|
86979
|
+
name: "deviceId",
|
|
86980
|
+
form: "single",
|
|
86981
|
+
optional: false
|
|
86982
|
+
}],
|
|
86620
86983
|
"cover.close": [{
|
|
86621
86984
|
name: "deviceId",
|
|
86622
86985
|
form: "single",
|
|
@@ -89280,99 +89643,6 @@ Object.freeze({
|
|
|
89280
89643
|
"network-access": "ingress",
|
|
89281
89644
|
"smtp-provider": "email"
|
|
89282
89645
|
});
|
|
89283
|
-
/**
|
|
89284
|
-
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
89285
|
-
* `z.infer`, they do not restate the shape.
|
|
89286
|
-
*/
|
|
89287
|
-
var OverrideSourceSchema = object({
|
|
89288
|
-
addonId: string().min(1),
|
|
89289
|
-
stableId: string().min(1),
|
|
89290
|
-
cap: string().min(1),
|
|
89291
|
-
fieldPath: string().min(1)
|
|
89292
|
-
});
|
|
89293
|
-
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
89294
|
-
object({ kind: literal("identity") }),
|
|
89295
|
-
object({ kind: literal("invert") }),
|
|
89296
|
-
object({
|
|
89297
|
-
kind: literal("threshold"),
|
|
89298
|
-
comparison: _enum(["gte", "lte"]),
|
|
89299
|
-
threshold: number(),
|
|
89300
|
-
startDwellMs: number().nonnegative(),
|
|
89301
|
-
stopDwellMs: number().nonnegative()
|
|
89302
|
-
}),
|
|
89303
|
-
object({
|
|
89304
|
-
kind: literal("linear"),
|
|
89305
|
-
scale: number(),
|
|
89306
|
-
offset: number(),
|
|
89307
|
-
clampMin: number().optional(),
|
|
89308
|
-
clampMax: number().optional()
|
|
89309
|
-
}),
|
|
89310
|
-
object({
|
|
89311
|
-
kind: literal("enum-map"),
|
|
89312
|
-
mapping: record(string(), string().nullable())
|
|
89313
|
-
}),
|
|
89314
|
-
object({ kind: literal("number") })
|
|
89315
|
-
]);
|
|
89316
|
-
var ScalarOverrideDraftSchema = object({
|
|
89317
|
-
kind: literal("scalar"),
|
|
89318
|
-
cap: string().min(1),
|
|
89319
|
-
targetId: string().min(1),
|
|
89320
|
-
source: OverrideSourceSchema,
|
|
89321
|
-
transform: CustomizeTransformDraftSchema
|
|
89322
|
-
});
|
|
89323
|
-
var KeyedArrayOverrideDraftSchema = object({
|
|
89324
|
-
kind: literal("keyedArray"),
|
|
89325
|
-
cap: string().min(1),
|
|
89326
|
-
targetId: string().min(1),
|
|
89327
|
-
key: string().min(1),
|
|
89328
|
-
label: string().min(1),
|
|
89329
|
-
fieldPath: string().min(1),
|
|
89330
|
-
source: OverrideSourceSchema,
|
|
89331
|
-
transform: CustomizeTransformDraftSchema
|
|
89332
|
-
});
|
|
89333
|
-
var PulseOverrideDraftSchema = object({
|
|
89334
|
-
kind: literal("pulse"),
|
|
89335
|
-
cap: string().min(1),
|
|
89336
|
-
targetId: string().min(1),
|
|
89337
|
-
enabled: boolean(),
|
|
89338
|
-
debounceMs: number().nonnegative(),
|
|
89339
|
-
edges: array(_enum([
|
|
89340
|
-
"rising",
|
|
89341
|
-
"button",
|
|
89342
|
-
"native-event"
|
|
89343
|
-
])).min(1).optional(),
|
|
89344
|
-
sources: array(OverrideSourceSchema).min(1)
|
|
89345
|
-
});
|
|
89346
|
-
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
89347
|
-
"in-addition",
|
|
89348
|
-
"confirm",
|
|
89349
|
-
"replace"
|
|
89350
|
-
]).optional() });
|
|
89351
|
-
discriminatedUnion("kind", [
|
|
89352
|
-
ScalarOverrideDraftSchema,
|
|
89353
|
-
KeyedArrayOverrideDraftSchema,
|
|
89354
|
-
PulseOverrideDraftSchema,
|
|
89355
|
-
object({
|
|
89356
|
-
kind: literal("span"),
|
|
89357
|
-
cap: string().min(1),
|
|
89358
|
-
targetId: string().min(1),
|
|
89359
|
-
timeoutMs: number().nonnegative(),
|
|
89360
|
-
cooldownMs: number().nonnegative().optional(),
|
|
89361
|
-
sources: array(SpanSourceDraftSchema)
|
|
89362
|
-
})
|
|
89363
|
-
]);
|
|
89364
|
-
object({
|
|
89365
|
-
targetId: string().min(1),
|
|
89366
|
-
configured: boolean(),
|
|
89367
|
-
source: _enum(["native", "assigned"]),
|
|
89368
|
-
state: _enum([
|
|
89369
|
-
"active",
|
|
89370
|
-
"stale",
|
|
89371
|
-
"released",
|
|
89372
|
-
"unavailable"
|
|
89373
|
-
]),
|
|
89374
|
-
reason: string().nullable()
|
|
89375
|
-
});
|
|
89376
89646
|
var G711_SCALE_CORRECTION_DB = {
|
|
89377
89647
|
PCMU: 20 * Math.log10(4),
|
|
89378
89648
|
PCMA: 20 * Math.log10(8)
|
|
@@ -123384,6 +123654,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
123384
123654
|
this.aclStore = MqttAclStore.of(records);
|
|
123385
123655
|
this.instances = new RecordIntegrationInstances(records, mqttBrokersEntryKind, {
|
|
123386
123656
|
description: {
|
|
123657
|
+
role: "services",
|
|
123387
123658
|
instanceMode: "multiple",
|
|
123388
123659
|
kind: "brokers",
|
|
123389
123660
|
canTest: false
|