@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
|
@@ -6021,7 +6021,7 @@ var ZodIssueCode = {
|
|
|
6021
6021
|
var ZodFirstPartyTypeKind;
|
|
6022
6022
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6023
6023
|
//#endregion
|
|
6024
|
-
//#region ../types/dist/sleep-
|
|
6024
|
+
//#region ../types/dist/sleep-BaWhXZFm.mjs
|
|
6025
6025
|
/**
|
|
6026
6026
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6027
6027
|
* window to float samples (D455).
|
|
@@ -8158,6 +8158,10 @@ var BaseAddon = class {
|
|
|
8158
8158
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
8159
8159
|
const patch = {};
|
|
8160
8160
|
for (const key of keys) {
|
|
8161
|
+
if (tree.get(key)?.kind === "array") {
|
|
8162
|
+
patch[key] = [];
|
|
8163
|
+
continue;
|
|
8164
|
+
}
|
|
8161
8165
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8162
8166
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8163
8167
|
}
|
|
@@ -8853,6 +8857,10 @@ var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
|
8853
8857
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
8854
8858
|
* (`auth.accessToken`, `headers[].value`).
|
|
8855
8859
|
*
|
|
8860
|
+
* `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
|
|
8861
|
+
* names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
|
|
8862
|
+
* key releases exactly that key's secret.
|
|
8863
|
+
*
|
|
8856
8864
|
* Array elements are matched by a DECLARED id key (`secretArrayKeys`:
|
|
8857
8865
|
* `{ headers: 'name' }`), never by index, so reordering a list keeps every
|
|
8858
8866
|
* keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
|
|
@@ -8865,11 +8873,13 @@ function parseSecretPath(path) {
|
|
|
8865
8873
|
if (path.length === 0) throw new InvalidRecordError("an empty secret path");
|
|
8866
8874
|
return path.split(".").map((raw) => {
|
|
8867
8875
|
const each = raw.endsWith("[]");
|
|
8868
|
-
const
|
|
8876
|
+
const map = raw.endsWith("{}");
|
|
8877
|
+
const key = each || map ? raw.slice(0, -2) : raw;
|
|
8869
8878
|
if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
|
|
8870
8879
|
return {
|
|
8871
8880
|
key,
|
|
8872
|
-
each
|
|
8881
|
+
each,
|
|
8882
|
+
map
|
|
8873
8883
|
};
|
|
8874
8884
|
});
|
|
8875
8885
|
}
|
|
@@ -8921,6 +8931,22 @@ function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arra
|
|
|
8921
8931
|
[segment.key]: mapped
|
|
8922
8932
|
};
|
|
8923
8933
|
}
|
|
8934
|
+
if (segment.map) {
|
|
8935
|
+
if (!isRecord$4(child)) return node;
|
|
8936
|
+
const kept = Object.entries(child).map(([mapKey, value]) => {
|
|
8937
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
|
|
8938
|
+
if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
|
|
8939
|
+
return [mapKey, visit({
|
|
8940
|
+
declared,
|
|
8941
|
+
concrete,
|
|
8942
|
+
value
|
|
8943
|
+
})];
|
|
8944
|
+
}).filter(([, value]) => value !== REMOVE_LEAF);
|
|
8945
|
+
return {
|
|
8946
|
+
...node,
|
|
8947
|
+
[segment.key]: Object.fromEntries(kept)
|
|
8948
|
+
};
|
|
8949
|
+
}
|
|
8924
8950
|
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8925
8951
|
if (last) {
|
|
8926
8952
|
const replacement = visit({
|
|
@@ -9062,7 +9088,8 @@ function isZodSecret(schema) {
|
|
|
9062
9088
|
}
|
|
9063
9089
|
/**
|
|
9064
9090
|
* Every declared secret path in a config schema: dotted keys, `[]` for "each
|
|
9065
|
-
* element of this array" (`connection.password`, `rows[].token`)
|
|
9091
|
+
* element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
|
|
9092
|
+
* map" (`applianceSecrets{}`). A declared
|
|
9066
9093
|
* object or array is reported as one path and not descended into — everything
|
|
9067
9094
|
* under it is secret.
|
|
9068
9095
|
*/
|
|
@@ -9073,6 +9100,7 @@ function collectZodSecretPaths(schema, prefix = "") {
|
|
|
9073
9100
|
const def = defOf(layer);
|
|
9074
9101
|
if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
9075
9102
|
else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
|
|
9103
|
+
else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
|
|
9076
9104
|
else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
|
|
9077
9105
|
else if (def.type === "intersection") {
|
|
9078
9106
|
if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
|
|
@@ -9212,7 +9240,7 @@ function defineRecordKinds(specs) {
|
|
|
9212
9240
|
retention: spec.retention,
|
|
9213
9241
|
deviceRemoval: spec.deviceRemoval,
|
|
9214
9242
|
maxDataBytes: spec.maxDataBytes,
|
|
9215
|
-
secretPaths: [...secretPaths],
|
|
9243
|
+
secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
|
|
9216
9244
|
secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
|
|
9217
9245
|
transactFamily: spec.transactFamily,
|
|
9218
9246
|
entry: spec.entry === void 0 ? void 0 : {
|
|
@@ -9257,6 +9285,7 @@ function defineEntryKind(name, spec) {
|
|
|
9257
9285
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9258
9286
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9259
9287
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
9288
|
+
...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
|
|
9260
9289
|
entry: {
|
|
9261
9290
|
title: spec.title,
|
|
9262
9291
|
...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
|
|
@@ -11918,7 +11947,7 @@ object({
|
|
|
11918
11947
|
params: record(string(), union([string(), number()])).optional()
|
|
11919
11948
|
});
|
|
11920
11949
|
//#endregion
|
|
11921
|
-
//#region ../types/dist/composition-
|
|
11950
|
+
//#region ../types/dist/composition-CRNwcFYY.mjs
|
|
11922
11951
|
/**
|
|
11923
11952
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11924
11953
|
*
|
|
@@ -12681,38 +12710,6 @@ function validateExpressionSource(src) {
|
|
|
12681
12710
|
return null;
|
|
12682
12711
|
}
|
|
12683
12712
|
/**
|
|
12684
|
-
* One power reading becomes a washer, dryer or dishwasher (D944 helper).
|
|
12685
|
-
*
|
|
12686
|
-
* Standby of the control board is under 5 W; a motor or a heater is far
|
|
12687
|
-
* above that, so 5 W is the line. A door lock or a short pump spike is not
|
|
12688
|
-
* a cycle, so running starts only after 30 s above the line.
|
|
12689
|
-
*
|
|
12690
|
-
* A wash, dry or dishwasher pause (soak, heat-wait, heater off between
|
|
12691
|
-
* bursts) lasts several minutes. Ending the cycle on the first dip would
|
|
12692
|
-
* report "finished" in the middle of the program, so the stop dwell is
|
|
12693
|
-
* 10 minutes on every type. The number is stored per type so one machine
|
|
12694
|
-
* can move later without moving the others.
|
|
12695
|
-
*/
|
|
12696
|
-
var APPLIANCE_TEMPLATE_ID = "appliance";
|
|
12697
|
-
var APPLIANCE_KINDS = [
|
|
12698
|
-
"washer",
|
|
12699
|
-
"dryer",
|
|
12700
|
-
"dishwasher"
|
|
12701
|
-
];
|
|
12702
|
-
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
12703
|
-
/**
|
|
12704
|
-
* A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
|
|
12705
|
-
*
|
|
12706
|
-
* The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
|
|
12707
|
-
* (D967), so the heater switches exactly as the plug does. Running is the
|
|
12708
|
-
* plug being on; when a power reading is also named, running is the draw
|
|
12709
|
-
* staying above the line instead (a plug left on with the stove's own
|
|
12710
|
-
* thermostat open is not heating). 50 W is far above any standby and below
|
|
12711
|
-
* the smallest radiator.
|
|
12712
|
-
*/
|
|
12713
|
-
var HEATER_TEMPLATE_ID = "heater";
|
|
12714
|
-
DeviceType.Heater;
|
|
12715
|
-
/**
|
|
12716
12713
|
* Statistics and integrals over a history read (D944).
|
|
12717
12714
|
*
|
|
12718
12715
|
* Pure: the same points always give the same answer, so a restart recomputes
|
|
@@ -12856,34 +12853,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12856
12853
|
/** Actuator groups only. Readings leave this unset. */
|
|
12857
12854
|
active: ActiveCriterionSchema.optional()
|
|
12858
12855
|
}),
|
|
12859
|
-
object({
|
|
12860
|
-
kind: literal("appliance"),
|
|
12861
|
-
templateId: literal(APPLIANCE_TEMPLATE_ID),
|
|
12862
|
-
appliance: _enum(APPLIANCE_KINDS),
|
|
12863
|
-
thresholdWatts: number().positive(),
|
|
12864
|
-
startDwellMs: number().int().nonnegative(),
|
|
12865
|
-
stopDwellMs: number().int().nonnegative(),
|
|
12866
|
-
member: HelperMemberSchema,
|
|
12867
|
-
/**
|
|
12868
|
-
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12869
|
-
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12870
|
-
* that plug (power-source link of a NEW-target composition).
|
|
12871
|
-
*/
|
|
12872
|
-
powerSwitch: HelperMemberSchema.optional(),
|
|
12873
|
-
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12874
|
-
offSeconds: number().optional()
|
|
12875
|
-
}),
|
|
12876
|
-
object({
|
|
12877
|
-
kind: literal("heater"),
|
|
12878
|
-
templateId: literal(HEATER_TEMPLATE_ID),
|
|
12879
|
-
/** The plug, read and commanded at `switch.on`. */
|
|
12880
|
-
plug: HelperMemberSchema,
|
|
12881
|
-
/** The plug's power reading, when it has one: running is then the draw, not the switch. */
|
|
12882
|
-
meter: HelperMemberSchema.optional(),
|
|
12883
|
-
thresholdWatts: number().positive(),
|
|
12884
|
-
startDwellMs: number().int().nonnegative(),
|
|
12885
|
-
stopDwellMs: number().int().nonnegative()
|
|
12886
|
-
}),
|
|
12887
12856
|
object({
|
|
12888
12857
|
kind: literal("statistic"),
|
|
12889
12858
|
stat: _enum(STATISTIC_STATS),
|
|
@@ -13107,7 +13076,7 @@ var CompositionSchema = object({
|
|
|
13107
13076
|
kind: literal("existing"),
|
|
13108
13077
|
device: CompositionSourceRefSchema
|
|
13109
13078
|
})]),
|
|
13110
|
-
features: array(CompositionFeatureSchema).min(
|
|
13079
|
+
features: array(CompositionFeatureSchema).min(0).max(16),
|
|
13111
13080
|
/**
|
|
13112
13081
|
* Present when the block is a helper template. Absent on a free
|
|
13113
13082
|
* composition (the room example, device 4603). The editor reads this,
|
|
@@ -13325,11 +13294,49 @@ var IntegrationInstanceRefSchema = object({
|
|
|
13325
13294
|
]),
|
|
13326
13295
|
candidateStableId: string().optional()
|
|
13327
13296
|
});
|
|
13297
|
+
/**
|
|
13298
|
+
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13299
|
+
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13300
|
+
*
|
|
13301
|
+
* Every instance is one record of the addon's own kind, keyed by the
|
|
13302
|
+
* `integrations` registry id. The records door does the secret work: a field the
|
|
13303
|
+
* kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
|
|
13304
|
+
* deleting the row deletes the secret, and one row's value is never another's.
|
|
13305
|
+
*
|
|
13306
|
+
* Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
|
|
13307
|
+
* token or the view the form was given KEEP the stored secret;
|
|
13308
|
+
* `{ $secretClear: true }` clears it; any other string replaces it.
|
|
13309
|
+
*/
|
|
13328
13310
|
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13329
13311
|
var INSTANCES_LIST_MAX = 1e3;
|
|
13330
13312
|
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13331
13313
|
function topLevelSecretKeys(paths) {
|
|
13332
|
-
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
|
|
13314
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
|
|
13315
|
+
}
|
|
13316
|
+
/** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
|
|
13317
|
+
function secretMapKeys(paths) {
|
|
13318
|
+
return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
|
|
13319
|
+
}
|
|
13320
|
+
/**
|
|
13321
|
+
* A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
|
|
13322
|
+
* view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
|
|
13323
|
+
* or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
|
|
13324
|
+
*/
|
|
13325
|
+
function secretMapPatch(value) {
|
|
13326
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
13327
|
+
const out = {};
|
|
13328
|
+
for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
|
|
13329
|
+
else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
|
|
13330
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
13331
|
+
}
|
|
13332
|
+
/** Does the kind's body schema carry an `enabled` field? */
|
|
13333
|
+
function declaresEnabled(kind) {
|
|
13334
|
+
const json = toJSONSchema(kind.schema, {
|
|
13335
|
+
unrepresentable: "any",
|
|
13336
|
+
io: "input"
|
|
13337
|
+
});
|
|
13338
|
+
const properties = Reflect.get(json, "properties");
|
|
13339
|
+
return typeof properties === "object" && properties !== null && "enabled" in properties;
|
|
13333
13340
|
}
|
|
13334
13341
|
var RecordIntegrationInstances = class {
|
|
13335
13342
|
records;
|
|
@@ -13337,14 +13344,20 @@ var RecordIntegrationInstances = class {
|
|
|
13337
13344
|
hooks;
|
|
13338
13345
|
handle;
|
|
13339
13346
|
secretKeys;
|
|
13347
|
+
secretMaps;
|
|
13340
13348
|
nameField;
|
|
13349
|
+
/** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
|
|
13350
|
+
registryOwnsEnabled;
|
|
13341
13351
|
constructor(records, kind, hooks) {
|
|
13342
13352
|
this.records = records;
|
|
13343
13353
|
this.kind = kind;
|
|
13344
13354
|
this.hooks = hooks;
|
|
13345
13355
|
this.handle = records.kind(kind);
|
|
13346
13356
|
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13357
|
+
this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
|
|
13347
13358
|
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13359
|
+
this.registryOwnsEnabled = hooks.description.role === "devices";
|
|
13360
|
+
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"`);
|
|
13348
13361
|
}
|
|
13349
13362
|
/**
|
|
13350
13363
|
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
@@ -13389,15 +13402,23 @@ var RecordIntegrationInstances = class {
|
|
|
13389
13402
|
viewOf(record) {
|
|
13390
13403
|
const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
|
|
13391
13404
|
const rawName = record.data[this.nameField];
|
|
13405
|
+
const kind = this.hooks.kindOf?.(record.data);
|
|
13406
|
+
const health = this.hooks.health?.(record.data, record.id);
|
|
13392
13407
|
return {
|
|
13393
13408
|
id: record.id,
|
|
13394
13409
|
name: typeof rawName === "string" ? rawName : record.id,
|
|
13395
13410
|
configured: this.hooks.configured(record.data),
|
|
13396
|
-
enabled: record.data.enabled !== false,
|
|
13397
|
-
formValues: shown
|
|
13411
|
+
...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
|
|
13412
|
+
formValues: shown,
|
|
13413
|
+
...kind !== void 0 ? { kind } : {},
|
|
13414
|
+
...health?.status !== void 0 ? { status: health.status } : {},
|
|
13415
|
+
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13398
13416
|
};
|
|
13399
13417
|
}
|
|
13400
|
-
describe = async () => this.hooks.description
|
|
13418
|
+
describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
|
|
13419
|
+
...this.hooks.description,
|
|
13420
|
+
mintsId: true
|
|
13421
|
+
};
|
|
13401
13422
|
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13402
13423
|
listInstances = async () => {
|
|
13403
13424
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
@@ -13407,37 +13428,46 @@ var RecordIntegrationInstances = class {
|
|
|
13407
13428
|
return record === null ? null : this.viewOf(record);
|
|
13408
13429
|
};
|
|
13409
13430
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13410
|
-
createInstance = async ({ id, name }) => {
|
|
13431
|
+
createInstance = async ({ id, name, kind }) => {
|
|
13432
|
+
const minted = id ?? this.hooks.mintId?.({
|
|
13433
|
+
name,
|
|
13434
|
+
...kind !== void 0 ? { kind } : {}
|
|
13435
|
+
});
|
|
13436
|
+
if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
|
|
13411
13437
|
await this.handle.put({
|
|
13412
|
-
id,
|
|
13413
|
-
data: this.hooks.initialBody(name)
|
|
13438
|
+
id: minted,
|
|
13439
|
+
data: this.hooks.initialBody(name, kind)
|
|
13414
13440
|
}, { ifRev: 0 });
|
|
13415
|
-
await this.hooks.onChanged?.(
|
|
13416
|
-
return { id };
|
|
13441
|
+
await this.hooks.onChanged?.(minted, "created");
|
|
13442
|
+
return { id: minted };
|
|
13417
13443
|
};
|
|
13418
13444
|
updateInstance = async ({ id, name, values }) => {
|
|
13419
13445
|
const patch = {};
|
|
13420
13446
|
for (const [key, value] of Object.entries(values)) {
|
|
13421
13447
|
if (value === void 0) continue;
|
|
13422
|
-
if (
|
|
13448
|
+
if (this.secretMaps.has(key)) {
|
|
13449
|
+
const entries = secretMapPatch(value);
|
|
13450
|
+
if (entries !== null) patch[key] = entries;
|
|
13451
|
+
} else if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13423
13452
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13424
13453
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13425
13454
|
}
|
|
13426
13455
|
if (name !== void 0) patch[this.nameField] = name;
|
|
13427
13456
|
await this.handle.patch(id, patch);
|
|
13428
|
-
await this.hooks.onChanged?.(id);
|
|
13457
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13429
13458
|
return { ok: true };
|
|
13430
13459
|
};
|
|
13431
13460
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13432
13461
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13462
|
+
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
13433
13463
|
await this.handle.patch(id, { enabled });
|
|
13434
|
-
await this.hooks.onChanged?.(id);
|
|
13464
|
+
await this.hooks.onChanged?.(id, "enabled");
|
|
13435
13465
|
return { ok: true };
|
|
13436
13466
|
};
|
|
13437
13467
|
/** The row and its keychain refs go together (the records door). */
|
|
13438
13468
|
deleteInstance = async ({ id }) => {
|
|
13439
13469
|
await this.handle.delete(id);
|
|
13440
|
-
await this.hooks.onChanged?.(id);
|
|
13470
|
+
await this.hooks.onChanged?.(id, "deleted");
|
|
13441
13471
|
return { ok: true };
|
|
13442
13472
|
};
|
|
13443
13473
|
testInstance = async (input) => {
|
|
@@ -48887,49 +48917,6 @@ function actuator(input) {
|
|
|
48887
48917
|
};
|
|
48888
48918
|
}
|
|
48889
48919
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
48890
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
48891
|
-
"no-power-source",
|
|
48892
|
-
"switch-missing",
|
|
48893
|
-
"switch-offline",
|
|
48894
|
-
"switch-unknown",
|
|
48895
|
-
"restart-in-progress",
|
|
48896
|
-
"switch-call-failed"
|
|
48897
|
-
]);
|
|
48898
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
48899
|
-
accepted: literal(false),
|
|
48900
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
48901
|
-
/** One line for the operator, naming the switch. */
|
|
48902
|
-
message: string()
|
|
48903
|
-
})]);
|
|
48904
|
-
var PowerSourceInfoSchema = object({
|
|
48905
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
48906
|
-
linked: boolean(),
|
|
48907
|
-
switchDeviceId: number().nullable(),
|
|
48908
|
-
switchName: string().nullable(),
|
|
48909
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
48910
|
-
switchOn: boolean().nullable(),
|
|
48911
|
-
switchOnline: boolean().nullable(),
|
|
48912
|
-
restartInProgress: boolean(),
|
|
48913
|
-
offSeconds: number().int()
|
|
48914
|
-
});
|
|
48915
|
-
object({
|
|
48916
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
48917
|
-
switchOn: boolean().nullable(),
|
|
48918
|
-
offSeconds: number().int(),
|
|
48919
|
-
lastFetchedAt: number()
|
|
48920
|
-
});
|
|
48921
|
-
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;
|
|
48922
|
-
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
48923
|
-
systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48924
|
-
kind: "mutation",
|
|
48925
|
-
auth: "admin"
|
|
48926
|
-
}), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48927
|
-
kind: "mutation",
|
|
48928
|
-
auth: "admin"
|
|
48929
|
-
}), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48930
|
-
kind: "mutation",
|
|
48931
|
-
auth: "admin"
|
|
48932
|
-
});
|
|
48933
48920
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
48934
48921
|
DeviceType.Camera;
|
|
48935
48922
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -48957,6 +48944,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
|
|
|
48957
48944
|
}).optional()
|
|
48958
48945
|
})) });
|
|
48959
48946
|
/**
|
|
48947
|
+
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
48948
|
+
* `z.infer`, they do not restate the shape.
|
|
48949
|
+
*/
|
|
48950
|
+
var OverrideSourceSchema = object({
|
|
48951
|
+
addonId: string().min(1),
|
|
48952
|
+
stableId: string().min(1),
|
|
48953
|
+
cap: string().min(1),
|
|
48954
|
+
fieldPath: string().min(1)
|
|
48955
|
+
});
|
|
48956
|
+
var IdentityTransformDraftSchema = object({ kind: literal("identity") });
|
|
48957
|
+
var InvertTransformDraftSchema = object({ kind: literal("invert") });
|
|
48958
|
+
var ThresholdTransformDraftSchema = object({
|
|
48959
|
+
kind: literal("threshold"),
|
|
48960
|
+
comparison: _enum(["gte", "lte"]),
|
|
48961
|
+
threshold: number(),
|
|
48962
|
+
startDwellMs: number().nonnegative(),
|
|
48963
|
+
stopDwellMs: number().nonnegative()
|
|
48964
|
+
});
|
|
48965
|
+
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
48966
|
+
IdentityTransformDraftSchema,
|
|
48967
|
+
InvertTransformDraftSchema,
|
|
48968
|
+
ThresholdTransformDraftSchema,
|
|
48969
|
+
object({
|
|
48970
|
+
kind: literal("linear"),
|
|
48971
|
+
scale: number(),
|
|
48972
|
+
offset: number(),
|
|
48973
|
+
clampMin: number().optional(),
|
|
48974
|
+
clampMax: number().optional()
|
|
48975
|
+
}),
|
|
48976
|
+
object({
|
|
48977
|
+
kind: literal("enum-map"),
|
|
48978
|
+
mapping: record(string(), string().nullable())
|
|
48979
|
+
}),
|
|
48980
|
+
object({ kind: literal("number") })
|
|
48981
|
+
]);
|
|
48982
|
+
var ScalarOverrideDraftSchema = object({
|
|
48983
|
+
kind: literal("scalar"),
|
|
48984
|
+
cap: string().min(1),
|
|
48985
|
+
targetId: string().min(1),
|
|
48986
|
+
source: OverrideSourceSchema,
|
|
48987
|
+
transform: CustomizeTransformDraftSchema
|
|
48988
|
+
});
|
|
48989
|
+
var KeyedArrayOverrideDraftSchema = object({
|
|
48990
|
+
kind: literal("keyedArray"),
|
|
48991
|
+
cap: string().min(1),
|
|
48992
|
+
targetId: string().min(1),
|
|
48993
|
+
key: string().min(1),
|
|
48994
|
+
label: string().min(1),
|
|
48995
|
+
fieldPath: string().min(1),
|
|
48996
|
+
source: OverrideSourceSchema,
|
|
48997
|
+
transform: CustomizeTransformDraftSchema
|
|
48998
|
+
});
|
|
48999
|
+
var PulseOverrideDraftSchema = object({
|
|
49000
|
+
kind: literal("pulse"),
|
|
49001
|
+
cap: string().min(1),
|
|
49002
|
+
targetId: string().min(1),
|
|
49003
|
+
enabled: boolean(),
|
|
49004
|
+
debounceMs: number().nonnegative(),
|
|
49005
|
+
edges: array(_enum([
|
|
49006
|
+
"rising",
|
|
49007
|
+
"button",
|
|
49008
|
+
"native-event"
|
|
49009
|
+
])).min(1).optional(),
|
|
49010
|
+
sources: array(OverrideSourceSchema).min(1)
|
|
49011
|
+
});
|
|
49012
|
+
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
49013
|
+
"in-addition",
|
|
49014
|
+
"confirm",
|
|
49015
|
+
"replace"
|
|
49016
|
+
]).optional() });
|
|
49017
|
+
discriminatedUnion("kind", [
|
|
49018
|
+
ScalarOverrideDraftSchema,
|
|
49019
|
+
KeyedArrayOverrideDraftSchema,
|
|
49020
|
+
PulseOverrideDraftSchema,
|
|
49021
|
+
object({
|
|
49022
|
+
kind: literal("span"),
|
|
49023
|
+
cap: string().min(1),
|
|
49024
|
+
targetId: string().min(1),
|
|
49025
|
+
timeoutMs: number().nonnegative(),
|
|
49026
|
+
cooldownMs: number().nonnegative().optional(),
|
|
49027
|
+
sources: array(SpanSourceDraftSchema)
|
|
49028
|
+
})
|
|
49029
|
+
]);
|
|
49030
|
+
object({
|
|
49031
|
+
targetId: string().min(1),
|
|
49032
|
+
configured: boolean(),
|
|
49033
|
+
source: _enum(["native", "assigned"]),
|
|
49034
|
+
state: _enum([
|
|
49035
|
+
"active",
|
|
49036
|
+
"stale",
|
|
49037
|
+
"released",
|
|
49038
|
+
"unavailable"
|
|
49039
|
+
]),
|
|
49040
|
+
reason: string().nullable()
|
|
49041
|
+
});
|
|
49042
|
+
/**
|
|
49043
|
+
* What a running-threshold proposal says (planning#405). The schema is the
|
|
49044
|
+
* type: the algorithm, the capability method and the UI all read `z.infer`.
|
|
49045
|
+
*
|
|
49046
|
+
* A proposal is read-only advice: it carries the numbers it was derived from
|
|
49047
|
+
* (`evidence`) so the operator can judge it, and an `insufficient` answer names
|
|
49048
|
+
* WHY with a typed reason and the catalog defaults, never an invented number.
|
|
49049
|
+
*/
|
|
49050
|
+
var RunningProposalTierSchema = _enum(["raw", "5m"]);
|
|
49051
|
+
var RunningProposalConfidenceSchema = _enum([
|
|
49052
|
+
"high",
|
|
49053
|
+
"medium",
|
|
49054
|
+
"low"
|
|
49055
|
+
]);
|
|
49056
|
+
var RunningProposalInsufficientReasonSchema = _enum([
|
|
49057
|
+
"no-history",
|
|
49058
|
+
"span-too-short",
|
|
49059
|
+
"truncated",
|
|
49060
|
+
"no-idle-floor",
|
|
49061
|
+
"no-runs",
|
|
49062
|
+
"no-separation"
|
|
49063
|
+
]);
|
|
49064
|
+
var RunningProposalReplaySchema = object({
|
|
49065
|
+
/** Intervals the real dwell expression held true over the window. */
|
|
49066
|
+
intervals: number().int().nonnegative(),
|
|
49067
|
+
/** Intervals outside every run, or a second interval inside one. */
|
|
49068
|
+
extra: number().int().nonnegative(),
|
|
49069
|
+
/** Runs without an interval of their own (none, or merged into a neighbour). */
|
|
49070
|
+
missed: number().int().nonnegative()
|
|
49071
|
+
});
|
|
49072
|
+
var RunningProposalEvidenceSchema = object({
|
|
49073
|
+
tier: RunningProposalTierSchema,
|
|
49074
|
+
spanMs: number().nonnegative(),
|
|
49075
|
+
from: number(),
|
|
49076
|
+
to: number(),
|
|
49077
|
+
points: number().int().nonnegative(),
|
|
49078
|
+
runsFound: number().int().nonnegative(),
|
|
49079
|
+
pausesFound: number().int().nonnegative(),
|
|
49080
|
+
longestPauseMs: number().nonnegative(),
|
|
49081
|
+
idleFloorW: number(),
|
|
49082
|
+
noiseCeilingW: number(),
|
|
49083
|
+
plateauW: number(),
|
|
49084
|
+
replay: RunningProposalReplaySchema
|
|
49085
|
+
});
|
|
49086
|
+
var RunningProposalInsufficientEvidenceSchema = object({
|
|
49087
|
+
spanMs: number().nonnegative(),
|
|
49088
|
+
points: number().int().nonnegative(),
|
|
49089
|
+
runsFound: number().int().nonnegative()
|
|
49090
|
+
});
|
|
49091
|
+
var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
|
|
49092
|
+
state: literal("proposal"),
|
|
49093
|
+
transform: ThresholdTransformDraftSchema,
|
|
49094
|
+
evidence: RunningProposalEvidenceSchema,
|
|
49095
|
+
confidence: RunningProposalConfidenceSchema,
|
|
49096
|
+
/** Why the confidence is not higher; empty when nothing held it back. */
|
|
49097
|
+
notes: array(string())
|
|
49098
|
+
}), object({
|
|
49099
|
+
state: literal("insufficient"),
|
|
49100
|
+
reason: RunningProposalInsufficientReasonSchema,
|
|
49101
|
+
evidence: RunningProposalInsufficientEvidenceSchema,
|
|
49102
|
+
/** The catalog defaults, offered as what stands when nothing better is known. */
|
|
49103
|
+
fallback: ThresholdTransformDraftSchema
|
|
49104
|
+
})]);
|
|
49105
|
+
/**
|
|
48960
49106
|
* The report of a one-shot "move this owner's plaintext secrets into the
|
|
48961
49107
|
* keychain" run (D878). COUNTS only - never a value, never a ref.
|
|
48962
49108
|
*
|
|
@@ -49246,6 +49392,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
49246
49392
|
kind: "mutation",
|
|
49247
49393
|
auth: "admin",
|
|
49248
49394
|
access: "view"
|
|
49395
|
+
}), method(object({
|
|
49396
|
+
deviceId: number().int(),
|
|
49397
|
+
cap: string().min(1),
|
|
49398
|
+
fieldPath: string().min(1),
|
|
49399
|
+
deviceType: _enum(DeviceType).optional()
|
|
49400
|
+
}), RunningThresholdProposalSchema, {
|
|
49401
|
+
auth: "admin",
|
|
49402
|
+
access: "view"
|
|
49249
49403
|
}), method(object({
|
|
49250
49404
|
blockId: string().min(1),
|
|
49251
49405
|
trace: AutomationRunTraceSchema.optional(),
|
|
@@ -50054,7 +50208,27 @@ method(object({
|
|
|
50054
50208
|
* exposes only the COMMON surface: link state, supported device
|
|
50055
50209
|
* kinds (so the UI can filter the device picker), and per-device
|
|
50056
50210
|
* expose/unexpose.
|
|
50057
|
-
|
|
50211
|
+
*
|
|
50212
|
+
* INSTANCES. One export addon can serve several independent targets (a
|
|
50213
|
+
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
50214
|
+
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
50215
|
+
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
50216
|
+
* default instance's); present = that instance; an id the provider does not
|
|
50217
|
+
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
50218
|
+
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
50219
|
+
* change always names the target it changes. A single-instance provider
|
|
50220
|
+
* accepts an absent id on a read or its own id, and refuses any other.
|
|
50221
|
+
*/
|
|
50222
|
+
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
50223
|
+
var InstanceIdSchema$1 = string().min(1);
|
|
50224
|
+
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
50225
|
+
/** One independent export target of an addon. */
|
|
50226
|
+
var ExportInstanceSchema = object({
|
|
50227
|
+
instanceId: InstanceIdSchema$1,
|
|
50228
|
+
name: string(),
|
|
50229
|
+
enabled: boolean(),
|
|
50230
|
+
kind: string().optional()
|
|
50231
|
+
});
|
|
50058
50232
|
var LinkStateSchema = _enum([
|
|
50059
50233
|
"unlinked",
|
|
50060
50234
|
"linked",
|
|
@@ -50105,10 +50279,14 @@ var ExposedDeviceSchema = object({
|
|
|
50105
50279
|
capabilities: array(string()).optional()
|
|
50106
50280
|
});
|
|
50107
50281
|
var ExposeInputSchema = object({
|
|
50282
|
+
instanceId: InstanceIdSchema$1,
|
|
50108
50283
|
deviceId: string(),
|
|
50109
50284
|
capabilities: array(string()).optional()
|
|
50110
50285
|
});
|
|
50111
|
-
var UnexposeInputSchema = object({
|
|
50286
|
+
var UnexposeInputSchema = object({
|
|
50287
|
+
instanceId: InstanceIdSchema$1,
|
|
50288
|
+
deviceId: string()
|
|
50289
|
+
});
|
|
50112
50290
|
/**
|
|
50113
50291
|
* What an exporter may expose besides devices (D968). An action and a
|
|
50114
50292
|
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
@@ -50137,15 +50315,21 @@ var ExposedEntitySchema = object({
|
|
|
50137
50315
|
entity: ExportEntityRefSchema,
|
|
50138
50316
|
exposedAs: string().optional()
|
|
50139
50317
|
});
|
|
50140
|
-
var ExposeEntityInputSchema = object({
|
|
50141
|
-
|
|
50142
|
-
|
|
50318
|
+
var ExposeEntityInputSchema = object({
|
|
50319
|
+
instanceId: InstanceIdSchema$1,
|
|
50320
|
+
entity: ExportEntityRefSchema
|
|
50321
|
+
});
|
|
50322
|
+
var UnexposeEntityInputSchema = object({
|
|
50323
|
+
instanceId: InstanceIdSchema$1,
|
|
50324
|
+
entity: ExportEntityRefSchema
|
|
50325
|
+
});
|
|
50326
|
+
method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
50143
50327
|
kind: "mutation",
|
|
50144
50328
|
auth: "admin"
|
|
50145
50329
|
}), method(UnexposeInputSchema, _void(), {
|
|
50146
50330
|
kind: "mutation",
|
|
50147
50331
|
auth: "admin"
|
|
50148
|
-
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(
|
|
50332
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
50149
50333
|
kind: "mutation",
|
|
50150
50334
|
auth: "admin"
|
|
50151
50335
|
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
@@ -53346,15 +53530,15 @@ var FanPresetCaptureResultSchema = object({
|
|
|
53346
53530
|
items: array(FanCaptureItemSchema),
|
|
53347
53531
|
omitted: array(FanCaptureOmissionSchema)
|
|
53348
53532
|
});
|
|
53349
|
-
var deviceInput$
|
|
53533
|
+
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
53350
53534
|
DeviceType.Fan;
|
|
53351
|
-
method(deviceInput$
|
|
53535
|
+
method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
53352
53536
|
kind: "mutation",
|
|
53353
53537
|
auth: "admin"
|
|
53354
|
-
}), method(deviceInput$
|
|
53538
|
+
}), method(deviceInput$2.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
|
|
53355
53539
|
kind: "mutation",
|
|
53356
53540
|
auth: "protected"
|
|
53357
|
-
}), method(deviceInput$
|
|
53541
|
+
}), method(deviceInput$2.extend({
|
|
53358
53542
|
fields: array(FanCaptureFieldSchema).optional(),
|
|
53359
53543
|
steps: array(ActionStepSchema).max(20).default([])
|
|
53360
53544
|
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
@@ -53878,13 +54062,65 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
53878
54062
|
*/
|
|
53879
54063
|
var ConfigSchemaPassthrough$3 = custom();
|
|
53880
54064
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
54065
|
+
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
54066
|
+
/**
|
|
54067
|
+
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
54068
|
+
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
54069
|
+
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
54070
|
+
* interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
|
|
54071
|
+
* instance id.
|
|
54072
|
+
*/
|
|
54073
|
+
var IntegrationInstanceKindSchema = object({
|
|
54074
|
+
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
54075
|
+
id: string().min(1),
|
|
54076
|
+
label: string().min(1),
|
|
54077
|
+
/** The kind signs in through a server-held interactive session. */
|
|
54078
|
+
signIn: object({
|
|
54079
|
+
basePath: string().min(1),
|
|
54080
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
54081
|
+
}).optional(),
|
|
54082
|
+
/** Relative hub path to start an interactive account link of an instance of this kind. */
|
|
54083
|
+
connectPath: string().min(1).optional(),
|
|
54084
|
+
/** Relative hub path to disconnect that account. */
|
|
54085
|
+
disconnectPath: string().min(1).optional()
|
|
54086
|
+
});
|
|
53881
54087
|
var IntegrationInstanceDescriptionSchema = object({
|
|
54088
|
+
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
54089
|
+
role: _enum([
|
|
54090
|
+
"devices",
|
|
54091
|
+
"services",
|
|
54092
|
+
"both"
|
|
54093
|
+
]),
|
|
54094
|
+
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
54095
|
+
discovery: object({
|
|
54096
|
+
mode: _enum([
|
|
54097
|
+
"none",
|
|
54098
|
+
"lan",
|
|
54099
|
+
"account"
|
|
54100
|
+
]),
|
|
54101
|
+
scopedToInstance: boolean()
|
|
54102
|
+
}).optional(),
|
|
53882
54103
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
53883
|
-
instanceMode:
|
|
54104
|
+
instanceMode: IntegrationInstanceModeSchema,
|
|
53884
54105
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
53885
54106
|
kind: string(),
|
|
53886
54107
|
/** True when `testInstance` really probes the remote. */
|
|
53887
|
-
canTest: boolean()
|
|
54108
|
+
canTest: boolean(),
|
|
54109
|
+
/**
|
|
54110
|
+
* The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
|
|
54111
|
+
* Add flow asks which first and sends it as `createInstance.kind`.
|
|
54112
|
+
*/
|
|
54113
|
+
kinds: array(IntegrationInstanceKindSchema).optional(),
|
|
54114
|
+
/**
|
|
54115
|
+
* The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
|
|
54116
|
+
* devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
|
|
54117
|
+
*/
|
|
54118
|
+
allowRestoreId: boolean().optional(),
|
|
54119
|
+
/**
|
|
54120
|
+
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
54121
|
+
* send an id (the UI then mints an opaque one).
|
|
54122
|
+
*/
|
|
54123
|
+
mintsId: boolean().optional()
|
|
53888
54124
|
});
|
|
53889
54125
|
var IntegrationInstanceStatusSchema = object({
|
|
53890
54126
|
state: _enum([
|
|
@@ -53893,7 +54129,9 @@ var IntegrationInstanceStatusSchema = object({
|
|
|
53893
54129
|
"error",
|
|
53894
54130
|
"unknown"
|
|
53895
54131
|
]),
|
|
53896
|
-
detail: string().optional()
|
|
54132
|
+
detail: string().optional(),
|
|
54133
|
+
/** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
|
|
54134
|
+
label: string().optional()
|
|
53897
54135
|
});
|
|
53898
54136
|
var IntegrationInstanceSchema = object({
|
|
53899
54137
|
id: IntegrationInstanceIdSchema,
|
|
@@ -53901,13 +54139,18 @@ var IntegrationInstanceSchema = object({
|
|
|
53901
54139
|
/** The addon's own rule (an URL is present, a client id is present...). */
|
|
53902
54140
|
configured: boolean(),
|
|
53903
54141
|
/**
|
|
53904
|
-
* The instance's own on/off switch (D996)
|
|
53905
|
-
*
|
|
54142
|
+
* The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
|
|
54143
|
+
* so a row written before the field existed is on). Absent when the `integrations` registry
|
|
54144
|
+
* owns the switch (`describe().role` is `devices`): read it from the registry row.
|
|
53906
54145
|
*/
|
|
53907
|
-
enabled: boolean(),
|
|
54146
|
+
enabled: boolean().optional(),
|
|
54147
|
+
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
54148
|
+
kind: string().optional(),
|
|
53908
54149
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
53909
54150
|
formValues: record(string(), unknown()),
|
|
53910
|
-
status: IntegrationInstanceStatusSchema.optional()
|
|
54151
|
+
status: IntegrationInstanceStatusSchema.optional(),
|
|
54152
|
+
/** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
|
|
54153
|
+
lastCheckedAt: number().optional()
|
|
53911
54154
|
});
|
|
53912
54155
|
var IntegrationInstancePatchSchema = object({
|
|
53913
54156
|
id: IntegrationInstanceIdSchema,
|
|
@@ -53925,10 +54168,14 @@ var integrationInstancesCapability = {
|
|
|
53925
54168
|
instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
53926
54169
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
53927
54170
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
53928
|
-
/**
|
|
54171
|
+
/**
|
|
54172
|
+
* Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
|
|
54173
|
+
* without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
|
|
54174
|
+
*/
|
|
53929
54175
|
createInstance: method(object({
|
|
53930
|
-
id: IntegrationInstanceIdSchema,
|
|
53931
|
-
name: string().min(1)
|
|
54176
|
+
id: IntegrationInstanceIdSchema.optional(),
|
|
54177
|
+
name: string().min(1),
|
|
54178
|
+
kind: string().min(1).optional()
|
|
53932
54179
|
}), object({ id: string() }), {
|
|
53933
54180
|
kind: "mutation",
|
|
53934
54181
|
auth: "admin"
|
|
@@ -55760,6 +56007,11 @@ var LogEntrySchema = object({
|
|
|
55760
56007
|
* flight, and not asking for the next slice ends the scan.
|
|
55761
56008
|
*/
|
|
55762
56009
|
var LogReadQuerySchema = object({
|
|
56010
|
+
/**
|
|
56011
|
+
* Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
|
|
56012
|
+
* the provider's first readable one, so a caller written before instances existed is unchanged.
|
|
56013
|
+
*/
|
|
56014
|
+
instanceId: string().min(1).max(200).optional(),
|
|
55763
56015
|
/** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
|
|
55764
56016
|
requestId: string().min(1).max(64).optional(),
|
|
55765
56017
|
/** The device the line is about (`tags.deviceId`, numeric). */
|
|
@@ -55822,6 +56074,8 @@ var LogReadResultSchema = object({
|
|
|
55822
56074
|
/** True when `cancelRead` ended this slice early. */
|
|
55823
56075
|
cancelled: boolean().optional()
|
|
55824
56076
|
});
|
|
56077
|
+
/** Names one destination instance; absent ⇒ the provider's first readable one. */
|
|
56078
|
+
var LogInstanceSelectorSchema = object({ instanceId: string().min(1).max(200).optional() });
|
|
55825
56079
|
var LogReadCapabilitiesSchema = object({
|
|
55826
56080
|
/** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
|
|
55827
56081
|
readable: boolean(),
|
|
@@ -55840,7 +56094,7 @@ var LogReadCapabilitiesSchema = object({
|
|
|
55840
56094
|
/** How far back the store reaches, when the provider knows (days). */
|
|
55841
56095
|
retentionDays: number().nullable()
|
|
55842
56096
|
});
|
|
55843
|
-
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(
|
|
56097
|
+
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() }), {
|
|
55844
56098
|
kind: "mutation",
|
|
55845
56099
|
auth: "admin"
|
|
55846
56100
|
});
|
|
@@ -67431,6 +67685,49 @@ object({
|
|
|
67431
67685
|
DeviceType.PetFeeder,
|
|
67432
67686
|
DeviceType.Heater
|
|
67433
67687
|
].filter((type) => type !== DeviceType.Sensor);
|
|
67688
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
67689
|
+
"no-power-source",
|
|
67690
|
+
"switch-missing",
|
|
67691
|
+
"switch-offline",
|
|
67692
|
+
"switch-unknown",
|
|
67693
|
+
"restart-in-progress",
|
|
67694
|
+
"switch-call-failed"
|
|
67695
|
+
]);
|
|
67696
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
67697
|
+
accepted: literal(false),
|
|
67698
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
67699
|
+
/** One line for the operator, naming the switch. */
|
|
67700
|
+
message: string()
|
|
67701
|
+
})]);
|
|
67702
|
+
var PowerSourceInfoSchema = object({
|
|
67703
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
67704
|
+
linked: boolean(),
|
|
67705
|
+
switchDeviceId: number().nullable(),
|
|
67706
|
+
switchName: string().nullable(),
|
|
67707
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
67708
|
+
switchOn: boolean().nullable(),
|
|
67709
|
+
switchOnline: boolean().nullable(),
|
|
67710
|
+
restartInProgress: boolean(),
|
|
67711
|
+
offSeconds: number().int()
|
|
67712
|
+
});
|
|
67713
|
+
object({
|
|
67714
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
67715
|
+
switchOn: boolean().nullable(),
|
|
67716
|
+
offSeconds: number().int(),
|
|
67717
|
+
lastFetchedAt: number()
|
|
67718
|
+
});
|
|
67719
|
+
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;
|
|
67720
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
67721
|
+
systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67722
|
+
kind: "mutation",
|
|
67723
|
+
auth: "admin"
|
|
67724
|
+
}), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67725
|
+
kind: "mutation",
|
|
67726
|
+
auth: "admin"
|
|
67727
|
+
}), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67728
|
+
kind: "mutation",
|
|
67729
|
+
auth: "admin"
|
|
67730
|
+
});
|
|
67434
67731
|
/**
|
|
67435
67732
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
67436
67733
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -75484,6 +75781,25 @@ function buildDescriptor(capName, kind) {
|
|
|
75484
75781
|
* contributes one entry per bound cap present in this map.
|
|
75485
75782
|
*/
|
|
75486
75783
|
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
75784
|
+
/**
|
|
75785
|
+
* What a remote key is CALLED and what it LOOKS like, for every role (D963),
|
|
75786
|
+
* shared by the admin UI and the viewer. A role is data on the wire; each
|
|
75787
|
+
* client resolves its name from `label` (English and Italian side by side) and
|
|
75788
|
+
* its glyph from `icon`, a lucide id from the shared icon catalogue — the
|
|
75789
|
+
* server sends an id, a client maps it to its own icon set.
|
|
75790
|
+
*
|
|
75791
|
+
* `legend` is text printed on the key INSTEAD of a glyph (a digit has no
|
|
75792
|
+
* pictogram): a client that can draw text shows it, one that cannot falls back
|
|
75793
|
+
* to `icon`.
|
|
75794
|
+
*/
|
|
75795
|
+
function digit(n) {
|
|
75796
|
+
return {
|
|
75797
|
+
label: t(n, n),
|
|
75798
|
+
icon: "hash",
|
|
75799
|
+
legend: n
|
|
75800
|
+
};
|
|
75801
|
+
}
|
|
75802
|
+
digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
|
|
75487
75803
|
DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
|
|
75488
75804
|
var btn = (idSuffix, row, col, init) => ({
|
|
75489
75805
|
idSuffix,
|
|
@@ -75611,6 +75927,34 @@ discriminatedUnion("kind", [object({
|
|
|
75611
75927
|
kind: literal("pronto"),
|
|
75612
75928
|
code: string()
|
|
75613
75929
|
})]);
|
|
75930
|
+
/**
|
|
75931
|
+
* Propose a running threshold from a device's own power history (planning#405).
|
|
75932
|
+
*
|
|
75933
|
+
* Pure and deterministic: the same series gives the same answer. The series is
|
|
75934
|
+
* a HELD-VALUE step series — a point's value stands until the next point — which
|
|
75935
|
+
* is how device-history stores a gauge, so "time at a level" is exact, not a
|
|
75936
|
+
* count of samples.
|
|
75937
|
+
*
|
|
75938
|
+
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
75939
|
+
* near F. A device that is not near one level most of the time has no floor.
|
|
75940
|
+
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
75941
|
+
* 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
|
|
75942
|
+
* the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
|
|
75943
|
+
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
75944
|
+
* grid a person would type; stop dwell covers the longest pause; start dwell
|
|
75945
|
+
* covers the longest standby blip above T.
|
|
75946
|
+
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
75947
|
+
* run must hold exactly one true interval and nothing may flap outside.
|
|
75948
|
+
*/
|
|
75949
|
+
var SECOND_MS = 1e3;
|
|
75950
|
+
var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
75951
|
+
24 * (60 * MINUTE_MS$1);
|
|
75952
|
+
10 * MINUTE_MS$1;
|
|
75953
|
+
30 * SECOND_MS;
|
|
75954
|
+
60 * SECOND_MS;
|
|
75955
|
+
20 * MINUTE_MS$1;
|
|
75956
|
+
10 * SECOND_MS;
|
|
75957
|
+
10 * SECOND_MS;
|
|
75614
75958
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
75615
75959
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
75616
75960
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
@@ -77152,6 +77496,13 @@ Object.freeze({
|
|
|
77152
77496
|
access: "create",
|
|
77153
77497
|
role: "admin"
|
|
77154
77498
|
},
|
|
77499
|
+
"coreBlocks.proposeRunningThreshold": {
|
|
77500
|
+
capName: "core-blocks",
|
|
77501
|
+
capScope: "system",
|
|
77502
|
+
addonId: null,
|
|
77503
|
+
access: "view",
|
|
77504
|
+
role: "admin"
|
|
77505
|
+
},
|
|
77155
77506
|
"coreBlocks.reportAutomationRun": {
|
|
77156
77507
|
capName: "core-blocks",
|
|
77157
77508
|
capScope: "system",
|
|
@@ -77663,6 +78014,13 @@ Object.freeze({
|
|
|
77663
78014
|
access: "view",
|
|
77664
78015
|
role: "admin"
|
|
77665
78016
|
},
|
|
78017
|
+
"deviceExport.listInstances": {
|
|
78018
|
+
capName: "device-export",
|
|
78019
|
+
capScope: "system",
|
|
78020
|
+
addonId: null,
|
|
78021
|
+
access: "view",
|
|
78022
|
+
role: "admin"
|
|
78023
|
+
},
|
|
77666
78024
|
"deviceExport.listSupportedDeviceKinds": {
|
|
77667
78025
|
capName: "device-export",
|
|
77668
78026
|
capScope: "system",
|
|
@@ -86610,6 +86968,11 @@ Object.freeze({
|
|
|
86610
86968
|
form: "single",
|
|
86611
86969
|
optional: false
|
|
86612
86970
|
}],
|
|
86971
|
+
"coreBlocks.proposeRunningThreshold": [{
|
|
86972
|
+
name: "deviceId",
|
|
86973
|
+
form: "single",
|
|
86974
|
+
optional: false
|
|
86975
|
+
}],
|
|
86613
86976
|
"cover.close": [{
|
|
86614
86977
|
name: "deviceId",
|
|
86615
86978
|
form: "single",
|
|
@@ -89273,99 +89636,6 @@ Object.freeze({
|
|
|
89273
89636
|
"network-access": "ingress",
|
|
89274
89637
|
"smtp-provider": "email"
|
|
89275
89638
|
});
|
|
89276
|
-
/**
|
|
89277
|
-
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
89278
|
-
* `z.infer`, they do not restate the shape.
|
|
89279
|
-
*/
|
|
89280
|
-
var OverrideSourceSchema = object({
|
|
89281
|
-
addonId: string().min(1),
|
|
89282
|
-
stableId: string().min(1),
|
|
89283
|
-
cap: string().min(1),
|
|
89284
|
-
fieldPath: string().min(1)
|
|
89285
|
-
});
|
|
89286
|
-
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
89287
|
-
object({ kind: literal("identity") }),
|
|
89288
|
-
object({ kind: literal("invert") }),
|
|
89289
|
-
object({
|
|
89290
|
-
kind: literal("threshold"),
|
|
89291
|
-
comparison: _enum(["gte", "lte"]),
|
|
89292
|
-
threshold: number(),
|
|
89293
|
-
startDwellMs: number().nonnegative(),
|
|
89294
|
-
stopDwellMs: number().nonnegative()
|
|
89295
|
-
}),
|
|
89296
|
-
object({
|
|
89297
|
-
kind: literal("linear"),
|
|
89298
|
-
scale: number(),
|
|
89299
|
-
offset: number(),
|
|
89300
|
-
clampMin: number().optional(),
|
|
89301
|
-
clampMax: number().optional()
|
|
89302
|
-
}),
|
|
89303
|
-
object({
|
|
89304
|
-
kind: literal("enum-map"),
|
|
89305
|
-
mapping: record(string(), string().nullable())
|
|
89306
|
-
}),
|
|
89307
|
-
object({ kind: literal("number") })
|
|
89308
|
-
]);
|
|
89309
|
-
var ScalarOverrideDraftSchema = object({
|
|
89310
|
-
kind: literal("scalar"),
|
|
89311
|
-
cap: string().min(1),
|
|
89312
|
-
targetId: string().min(1),
|
|
89313
|
-
source: OverrideSourceSchema,
|
|
89314
|
-
transform: CustomizeTransformDraftSchema
|
|
89315
|
-
});
|
|
89316
|
-
var KeyedArrayOverrideDraftSchema = object({
|
|
89317
|
-
kind: literal("keyedArray"),
|
|
89318
|
-
cap: string().min(1),
|
|
89319
|
-
targetId: string().min(1),
|
|
89320
|
-
key: string().min(1),
|
|
89321
|
-
label: string().min(1),
|
|
89322
|
-
fieldPath: string().min(1),
|
|
89323
|
-
source: OverrideSourceSchema,
|
|
89324
|
-
transform: CustomizeTransformDraftSchema
|
|
89325
|
-
});
|
|
89326
|
-
var PulseOverrideDraftSchema = object({
|
|
89327
|
-
kind: literal("pulse"),
|
|
89328
|
-
cap: string().min(1),
|
|
89329
|
-
targetId: string().min(1),
|
|
89330
|
-
enabled: boolean(),
|
|
89331
|
-
debounceMs: number().nonnegative(),
|
|
89332
|
-
edges: array(_enum([
|
|
89333
|
-
"rising",
|
|
89334
|
-
"button",
|
|
89335
|
-
"native-event"
|
|
89336
|
-
])).min(1).optional(),
|
|
89337
|
-
sources: array(OverrideSourceSchema).min(1)
|
|
89338
|
-
});
|
|
89339
|
-
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
89340
|
-
"in-addition",
|
|
89341
|
-
"confirm",
|
|
89342
|
-
"replace"
|
|
89343
|
-
]).optional() });
|
|
89344
|
-
discriminatedUnion("kind", [
|
|
89345
|
-
ScalarOverrideDraftSchema,
|
|
89346
|
-
KeyedArrayOverrideDraftSchema,
|
|
89347
|
-
PulseOverrideDraftSchema,
|
|
89348
|
-
object({
|
|
89349
|
-
kind: literal("span"),
|
|
89350
|
-
cap: string().min(1),
|
|
89351
|
-
targetId: string().min(1),
|
|
89352
|
-
timeoutMs: number().nonnegative(),
|
|
89353
|
-
cooldownMs: number().nonnegative().optional(),
|
|
89354
|
-
sources: array(SpanSourceDraftSchema)
|
|
89355
|
-
})
|
|
89356
|
-
]);
|
|
89357
|
-
object({
|
|
89358
|
-
targetId: string().min(1),
|
|
89359
|
-
configured: boolean(),
|
|
89360
|
-
source: _enum(["native", "assigned"]),
|
|
89361
|
-
state: _enum([
|
|
89362
|
-
"active",
|
|
89363
|
-
"stale",
|
|
89364
|
-
"released",
|
|
89365
|
-
"unavailable"
|
|
89366
|
-
]),
|
|
89367
|
-
reason: string().nullable()
|
|
89368
|
-
});
|
|
89369
89639
|
var G711_SCALE_CORRECTION_DB = {
|
|
89370
89640
|
PCMU: 20 * Math.log10(4),
|
|
89371
89641
|
PCMA: 20 * Math.log10(8)
|
|
@@ -123377,6 +123647,7 @@ var MqttBrokerAddon = class extends BaseAddon {
|
|
|
123377
123647
|
this.aclStore = MqttAclStore.of(records);
|
|
123378
123648
|
this.instances = new RecordIntegrationInstances(records, mqttBrokersEntryKind, {
|
|
123379
123649
|
description: {
|
|
123650
|
+
role: "services",
|
|
123380
123651
|
instanceMode: "multiple",
|
|
123381
123652
|
kind: "brokers",
|
|
123382
123653
|
canTest: false
|