@camstack/addon-provider-enpal 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/addon.js +227 -78
- package/dist/addon.mjs +227 -78
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.10] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Internal: the connection form comment names the instance form; no behaviour change.
|
|
15
|
+
|
|
16
|
+
## [0.1.9] - 2026-11-05
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Copying an integration into its record reads the leftover rows through the migration-only door and resolves their secrets in this addon
|
|
21
|
+
|
|
10
22
|
## [0.1.8] - 2026-11-04
|
|
11
23
|
|
|
12
24
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
|
|
|
5981
5981
|
var ZodFirstPartyTypeKind;
|
|
5982
5982
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5983
5983
|
//#endregion
|
|
5984
|
-
//#region ../types/dist/sleep-
|
|
5984
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5985
5985
|
/**
|
|
5986
5986
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5987
5987
|
* window to float samples (D455).
|
|
@@ -6790,6 +6790,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6790
6790
|
}
|
|
6791
6791
|
return out;
|
|
6792
6792
|
}
|
|
6793
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6794
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6795
|
+
"cluster",
|
|
6796
|
+
"device-defaults",
|
|
6797
|
+
"node"
|
|
6798
|
+
];
|
|
6793
6799
|
/**
|
|
6794
6800
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6795
6801
|
*
|
|
@@ -7681,6 +7687,13 @@ var BaseAddon = class {
|
|
|
7681
7687
|
*/
|
|
7682
7688
|
secretStatePaths = [];
|
|
7683
7689
|
_addonSecretTree = null;
|
|
7690
|
+
/**
|
|
7691
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7692
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7693
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7694
|
+
*/
|
|
7695
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7696
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7684
7697
|
_deviceSecretTree = null;
|
|
7685
7698
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7686
7699
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7750,6 +7763,7 @@ var BaseAddon = class {
|
|
|
7750
7763
|
this._ctx = context;
|
|
7751
7764
|
this._secretSettings = null;
|
|
7752
7765
|
}
|
|
7766
|
+
await this.loadScopedSchemas();
|
|
7753
7767
|
await this.resolveConfig();
|
|
7754
7768
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7755
7769
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7759,6 +7773,7 @@ var BaseAddon = class {
|
|
|
7759
7773
|
const normalized = normalizeAddonInitResult(result);
|
|
7760
7774
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7761
7775
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7776
|
+
await this.loadScopedSchemas();
|
|
7762
7777
|
return normalized;
|
|
7763
7778
|
}
|
|
7764
7779
|
/**
|
|
@@ -7847,6 +7862,16 @@ var BaseAddon = class {
|
|
|
7847
7862
|
schema(schema) {
|
|
7848
7863
|
return schema;
|
|
7849
7864
|
}
|
|
7865
|
+
/**
|
|
7866
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7867
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7868
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7869
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7870
|
+
* once known.
|
|
7871
|
+
*/
|
|
7872
|
+
scopedSettingsCapabilities() {
|
|
7873
|
+
return [];
|
|
7874
|
+
}
|
|
7850
7875
|
/** Override to provide global-level settings UI schema. */
|
|
7851
7876
|
globalSettingsSchema(_cap) {
|
|
7852
7877
|
return null;
|
|
@@ -7890,7 +7915,8 @@ var BaseAddon = class {
|
|
|
7890
7915
|
return [];
|
|
7891
7916
|
}
|
|
7892
7917
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7893
|
-
|
|
7918
|
+
await this.loadScopedSchemas(cap);
|
|
7919
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7894
7920
|
if (!schema) return { sections: [] };
|
|
7895
7921
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7896
7922
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7925,10 +7951,9 @@ var BaseAddon = class {
|
|
|
7925
7951
|
*
|
|
7926
7952
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7927
7953
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7928
|
-
* SHAPE of the form only — the values live
|
|
7929
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7954
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7930
7955
|
*/
|
|
7931
|
-
async
|
|
7956
|
+
async integrationFormSchema(_instance) {
|
|
7932
7957
|
return null;
|
|
7933
7958
|
}
|
|
7934
7959
|
/**
|
|
@@ -7974,7 +7999,7 @@ var BaseAddon = class {
|
|
|
7974
7999
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7975
8000
|
*/
|
|
7976
8001
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7977
|
-
const scoped = await this.
|
|
8002
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7978
8003
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7979
8004
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7980
8005
|
}
|
|
@@ -8022,6 +8047,7 @@ var BaseAddon = class {
|
|
|
8022
8047
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8023
8048
|
*/
|
|
8024
8049
|
async resolveGlobalStore(nodeId, cap) {
|
|
8050
|
+
await this.loadScopedSchemas(cap);
|
|
8025
8051
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8026
8052
|
return this.projectForNode(raw, nodeId, cap);
|
|
8027
8053
|
}
|
|
@@ -8032,6 +8058,7 @@ var BaseAddon = class {
|
|
|
8032
8058
|
}
|
|
8033
8059
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8034
8060
|
async readRawProjectedStore(nodeId, cap) {
|
|
8061
|
+
await this.loadScopedSchemas(cap);
|
|
8035
8062
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8036
8063
|
return this.projectForNode(raw, nodeId, cap);
|
|
8037
8064
|
}
|
|
@@ -8134,6 +8161,7 @@ var BaseAddon = class {
|
|
|
8134
8161
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8135
8162
|
}
|
|
8136
8163
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8164
|
+
await this.loadScopedSchemas();
|
|
8137
8165
|
const keys = this.perNodeKeys();
|
|
8138
8166
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8139
8167
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8147,7 +8175,8 @@ var BaseAddon = class {
|
|
|
8147
8175
|
await this.resolveConfig();
|
|
8148
8176
|
await this.onConfigChanged();
|
|
8149
8177
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8150
|
-
|
|
8178
|
+
const legacy = this.globalSettingsSchema();
|
|
8179
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8151
8180
|
}
|
|
8152
8181
|
/**
|
|
8153
8182
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8155,14 +8184,67 @@ var BaseAddon = class {
|
|
|
8155
8184
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8156
8185
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8157
8186
|
*/
|
|
8158
|
-
|
|
8187
|
+
/**
|
|
8188
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8189
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8190
|
+
* derived from the previous set. Idempotent.
|
|
8191
|
+
*/
|
|
8192
|
+
async loadScopedSchemas(extraCap) {
|
|
8193
|
+
const caps = new Set([
|
|
8194
|
+
...this.scopedSettingsCapabilities(),
|
|
8195
|
+
...this._registeredCapNames,
|
|
8196
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8197
|
+
]);
|
|
8198
|
+
let added = false;
|
|
8199
|
+
for (const cap of caps) {
|
|
8200
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8201
|
+
const loaded = [];
|
|
8202
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8203
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8204
|
+
if (schema !== null) loaded.push({
|
|
8205
|
+
scope,
|
|
8206
|
+
schema
|
|
8207
|
+
});
|
|
8208
|
+
}
|
|
8209
|
+
this._scopedByCap.set(cap, loaded);
|
|
8210
|
+
added = true;
|
|
8211
|
+
}
|
|
8212
|
+
if (!added) return;
|
|
8213
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8214
|
+
this._addonSecretTree = null;
|
|
8215
|
+
}
|
|
8216
|
+
/**
|
|
8217
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8218
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8219
|
+
*/
|
|
8220
|
+
scopedFormSchema(cap) {
|
|
8221
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8222
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8223
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8224
|
+
}
|
|
8225
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8226
|
+
loadedScopedSchemas(cap) {
|
|
8227
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8228
|
+
return [...this._scopedByCap.values()].flat();
|
|
8229
|
+
}
|
|
8230
|
+
/**
|
|
8231
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8232
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8233
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8234
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8235
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8236
|
+
*/
|
|
8159
8237
|
perNodeKeys(cap) {
|
|
8160
8238
|
const cacheKey = cap ?? "";
|
|
8161
|
-
const cached = this.
|
|
8239
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8162
8240
|
if (cached) return cached;
|
|
8163
|
-
const
|
|
8164
|
-
const keys = new Set(
|
|
8165
|
-
this.
|
|
8241
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8242
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8243
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8244
|
+
if (scope !== "node") continue;
|
|
8245
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8246
|
+
}
|
|
8247
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8166
8248
|
return keys;
|
|
8167
8249
|
}
|
|
8168
8250
|
/**
|
|
@@ -8172,10 +8254,9 @@ var BaseAddon = class {
|
|
|
8172
8254
|
* time to return its response before the addon is torn down and
|
|
8173
8255
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8174
8256
|
*/
|
|
8175
|
-
maybeAutoRestart(patchKeys,
|
|
8176
|
-
if (!schema) return;
|
|
8257
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8177
8258
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8178
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8259
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8179
8260
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8180
8261
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8181
8262
|
}
|
|
@@ -8371,12 +8452,14 @@ var BaseAddon = class {
|
|
|
8371
8452
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8372
8453
|
*/
|
|
8373
8454
|
async getDeclaredSettingsKeys() {
|
|
8455
|
+
await this.loadScopedSchemas();
|
|
8374
8456
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8375
8457
|
}
|
|
8376
8458
|
declaredKeySet() {
|
|
8377
8459
|
const keys = new Set(Object.keys(this.defaults));
|
|
8378
8460
|
const schema = this.globalSettingsSchema();
|
|
8379
8461
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8462
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8380
8463
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8381
8464
|
for (const path of this.secretStatePaths) {
|
|
8382
8465
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8472,7 +8555,11 @@ var BaseAddon = class {
|
|
|
8472
8555
|
}
|
|
8473
8556
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8474
8557
|
addonSecretTree() {
|
|
8475
|
-
this._addonSecretTree
|
|
8558
|
+
if (this._addonSecretTree === null) {
|
|
8559
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8560
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8561
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8562
|
+
}
|
|
8476
8563
|
return this._addonSecretTree;
|
|
8477
8564
|
}
|
|
8478
8565
|
deviceSecretTree() {
|
|
@@ -8722,6 +8809,23 @@ function dropPerNodeFields(fields) {
|
|
|
8722
8809
|
}
|
|
8723
8810
|
return kept;
|
|
8724
8811
|
}
|
|
8812
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8813
|
+
function collectValueFieldKeys(fields) {
|
|
8814
|
+
const collected = [];
|
|
8815
|
+
for (const field of fields) {
|
|
8816
|
+
if (field.type === "group") {
|
|
8817
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8818
|
+
continue;
|
|
8819
|
+
}
|
|
8820
|
+
if (field.type === "sub-tabs") {
|
|
8821
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8822
|
+
continue;
|
|
8823
|
+
}
|
|
8824
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8825
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8826
|
+
}
|
|
8827
|
+
return collected;
|
|
8828
|
+
}
|
|
8725
8829
|
function collectFieldKeys(fields) {
|
|
8726
8830
|
const collected = [];
|
|
8727
8831
|
for (const field of fields) {
|
|
@@ -11934,7 +12038,7 @@ object({
|
|
|
11934
12038
|
params: record(string(), union([string(), number()])).optional()
|
|
11935
12039
|
});
|
|
11936
12040
|
//#endregion
|
|
11937
|
-
//#region ../types/dist/composition-
|
|
12041
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11938
12042
|
/**
|
|
11939
12043
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11940
12044
|
*
|
|
@@ -13406,12 +13510,22 @@ var RecordIntegrationInstances = class {
|
|
|
13406
13510
|
...this.hooks.description,
|
|
13407
13511
|
mintsId: true
|
|
13408
13512
|
};
|
|
13409
|
-
|
|
13513
|
+
/**
|
|
13514
|
+
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13515
|
+
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13516
|
+
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13517
|
+
*/
|
|
13518
|
+
async resolveRecordId(id) {
|
|
13519
|
+
if (await this.handle.get(id) !== null) return id;
|
|
13520
|
+
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13521
|
+
return linked === void 0 ? id : linked.id;
|
|
13522
|
+
}
|
|
13523
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13410
13524
|
listInstances = async () => {
|
|
13411
13525
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13412
13526
|
};
|
|
13413
13527
|
getInstance = async ({ id }) => {
|
|
13414
|
-
const record = await this.handle.get(id);
|
|
13528
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13415
13529
|
return record === null ? null : this.viewOf(record);
|
|
13416
13530
|
};
|
|
13417
13531
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13428,7 +13542,8 @@ var RecordIntegrationInstances = class {
|
|
|
13428
13542
|
await this.hooks.onChanged?.(minted, "created");
|
|
13429
13543
|
return { id: minted };
|
|
13430
13544
|
};
|
|
13431
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13545
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13546
|
+
const id = await this.resolveRecordId(askedId);
|
|
13432
13547
|
const patch = {};
|
|
13433
13548
|
for (const [key, value] of Object.entries(values)) {
|
|
13434
13549
|
if (value === void 0) continue;
|
|
@@ -13463,7 +13578,10 @@ var RecordIntegrationInstances = class {
|
|
|
13463
13578
|
status: "unsupported",
|
|
13464
13579
|
error: "This integration declares no connection test"
|
|
13465
13580
|
};
|
|
13466
|
-
return this.hooks.test(input
|
|
13581
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13582
|
+
...input,
|
|
13583
|
+
id: await this.resolveRecordId(input.id)
|
|
13584
|
+
});
|
|
13467
13585
|
};
|
|
13468
13586
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13469
13587
|
toProvider() {
|
|
@@ -14642,20 +14760,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
|
|
|
14642
14760
|
};
|
|
14643
14761
|
}
|
|
14644
14762
|
/**
|
|
14645
|
-
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416)
|
|
14646
|
-
* from its own process, so the
|
|
14647
|
-
* `
|
|
14648
|
-
*
|
|
14649
|
-
*
|
|
14650
|
-
|
|
14763
|
+
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
14764
|
+
* from its own process, so the leftover rows are reached through the migration-only door
|
|
14765
|
+
* (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
|
|
14766
|
+
* The door returns markers. This process is the owner, so it resolves and releases them with
|
|
14767
|
+
* `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
|
|
14768
|
+
* door to delete the rows. The addon's own record kind is its records handle.
|
|
14769
|
+
*/
|
|
14770
|
+
async function resolvedRows(stored, secrets) {
|
|
14771
|
+
if (secrets === null) return stored;
|
|
14772
|
+
const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
|
|
14773
|
+
if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
|
|
14774
|
+
return value;
|
|
14775
|
+
}
|
|
14651
14776
|
function integrationRowsPortsOverApi(deps) {
|
|
14777
|
+
const secrets = deps.secrets ?? null;
|
|
14652
14778
|
return {
|
|
14653
14779
|
warn: (message) => deps.log.warn(message),
|
|
14654
14780
|
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
14655
14781
|
id: i.id,
|
|
14656
14782
|
name: i.name
|
|
14657
14783
|
})),
|
|
14658
|
-
readRows: (integrationId) => deps.api.
|
|
14784
|
+
readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
|
|
14659
14785
|
putRecord: async (id, body) => {
|
|
14660
14786
|
if (await deps.records.get(id) !== null) return "exists";
|
|
14661
14787
|
try {
|
|
@@ -14674,7 +14800,9 @@ function integrationRowsPortsOverApi(deps) {
|
|
|
14674
14800
|
await deps.records.delete(id);
|
|
14675
14801
|
},
|
|
14676
14802
|
clearRows: async (integrationId) => {
|
|
14677
|
-
await deps.api.
|
|
14803
|
+
const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
|
|
14804
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
14805
|
+
await deps.api.clearLegacyIntegrationRows({ integrationId });
|
|
14678
14806
|
}
|
|
14679
14807
|
};
|
|
14680
14808
|
}
|
|
@@ -14742,6 +14870,7 @@ function integrationRowsMigration(spec) {
|
|
|
14742
14870
|
function integrationRowsPortsOver(deps) {
|
|
14743
14871
|
return integrationRowsPortsOverApi({
|
|
14744
14872
|
api: deps.api,
|
|
14873
|
+
...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
|
|
14745
14874
|
records: deps.records.kind({
|
|
14746
14875
|
name: deps.kind.name,
|
|
14747
14876
|
declaration: deps.kind.declaration,
|
|
@@ -14753,8 +14882,8 @@ function integrationRowsPortsOver(deps) {
|
|
|
14753
14882
|
function defaultIntegrationApiOver(api) {
|
|
14754
14883
|
return {
|
|
14755
14884
|
list: () => api.integrations.list.query(),
|
|
14756
|
-
|
|
14757
|
-
|
|
14885
|
+
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
14886
|
+
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
14758
14887
|
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
14759
14888
|
addonId,
|
|
14760
14889
|
projection: "slim"
|
|
@@ -50432,6 +50561,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50432
50561
|
runsFound: number().int().nonnegative(),
|
|
50433
50562
|
pausesFound: number().int().nonnegative(),
|
|
50434
50563
|
longestPauseMs: number().nonnegative(),
|
|
50564
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50565
|
+
pulsesFound: number().int().nonnegative(),
|
|
50566
|
+
longestPulseMs: number().nonnegative(),
|
|
50435
50567
|
idleFloorW: number(),
|
|
50436
50568
|
noiseCeilingW: number(),
|
|
50437
50569
|
plateauW: number(),
|
|
@@ -56298,11 +56430,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56298
56430
|
info: record(string(), unknown()).optional(),
|
|
56299
56431
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56300
56432
|
originStableId: string().optional(),
|
|
56301
|
-
settings: record(string(), unknown()).optional()
|
|
56302
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56303
|
-
* the keychain, not the table (D787). */
|
|
56304
|
-
secretKeys: array(string()).optional(),
|
|
56305
|
-
skipRestart: boolean().optional()
|
|
56433
|
+
settings: record(string(), unknown()).optional()
|
|
56306
56434
|
});
|
|
56307
56435
|
var UpdateIntegrationInputSchema = object({
|
|
56308
56436
|
id: string(),
|
|
@@ -56323,22 +56451,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56323
56451
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56324
56452
|
kind: "mutation",
|
|
56325
56453
|
auth: "admin"
|
|
56326
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56327
|
-
id: string(),
|
|
56328
|
-
settings: record(string(), unknown()),
|
|
56329
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56330
|
-
secretKeys: array(string()).optional(),
|
|
56331
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56332
|
-
skipRestart: boolean().optional()
|
|
56333
|
-
}), object({ success: boolean() }), {
|
|
56334
|
-
kind: "mutation",
|
|
56335
|
-
auth: "admin"
|
|
56336
|
-
}), method(object({ id: string() }), object({
|
|
56337
|
-
rows: number().int().nonnegative(),
|
|
56338
|
-
secrets: number().int().nonnegative()
|
|
56339
|
-
}), {
|
|
56340
|
-
kind: "mutation",
|
|
56341
|
-
auth: "admin"
|
|
56342
56454
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56343
56455
|
addonId: string(),
|
|
56344
56456
|
settings: record(string(), unknown())
|
|
@@ -59315,6 +59427,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59315
59427
|
}), {
|
|
59316
59428
|
kind: "mutation",
|
|
59317
59429
|
auth: "admin"
|
|
59430
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59431
|
+
rows: number().int().nonnegative(),
|
|
59432
|
+
secrets: number().int().nonnegative()
|
|
59433
|
+
}), {
|
|
59434
|
+
kind: "mutation",
|
|
59435
|
+
auth: "admin"
|
|
59436
|
+
}), method(object({
|
|
59437
|
+
integrationId: string().min(1),
|
|
59438
|
+
patch: record(string(), unknown())
|
|
59439
|
+
}), object({ written: boolean() }), {
|
|
59440
|
+
kind: "mutation",
|
|
59441
|
+
auth: "admin"
|
|
59442
|
+
}), method(object({
|
|
59443
|
+
integrationId: string().min(1),
|
|
59444
|
+
keys: array(string().min(1))
|
|
59445
|
+
}), object({
|
|
59446
|
+
present: boolean(),
|
|
59447
|
+
removed: record(string(), unknown())
|
|
59448
|
+
}), {
|
|
59449
|
+
kind: "mutation",
|
|
59450
|
+
auth: "admin"
|
|
59318
59451
|
});
|
|
59319
59452
|
method(object({
|
|
59320
59453
|
sourceUrl: string(),
|
|
@@ -80733,11 +80866,15 @@ discriminatedUnion("kind", [object({
|
|
|
80733
80866
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80734
80867
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80735
80868
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80736
|
-
* 3.
|
|
80737
|
-
*
|
|
80869
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80870
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80871
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80872
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80873
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80874
|
+
* is no run.
|
|
80738
80875
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80739
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80740
|
-
* covers the longest standby blip above T.
|
|
80876
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80877
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80741
80878
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80742
80879
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80743
80880
|
*/
|
|
@@ -80748,6 +80885,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80748
80885
|
30 * SECOND_MS;
|
|
80749
80886
|
60 * SECOND_MS;
|
|
80750
80887
|
20 * MINUTE_MS$1;
|
|
80888
|
+
90 * SECOND_MS;
|
|
80889
|
+
30 * MINUTE_MS$1;
|
|
80890
|
+
10 * MINUTE_MS$1;
|
|
80891
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80751
80892
|
10 * SECOND_MS;
|
|
80752
80893
|
10 * SECOND_MS;
|
|
80753
80894
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -86032,13 +86173,6 @@ Object.freeze({
|
|
|
86032
86173
|
access: "create",
|
|
86033
86174
|
role: "admin"
|
|
86034
86175
|
},
|
|
86035
|
-
"integrations.clearSettings": {
|
|
86036
|
-
capName: "integrations",
|
|
86037
|
-
capScope: "system",
|
|
86038
|
-
addonId: null,
|
|
86039
|
-
access: "delete",
|
|
86040
|
-
role: "admin"
|
|
86041
|
-
},
|
|
86042
86176
|
"integrations.create": {
|
|
86043
86177
|
capName: "integrations",
|
|
86044
86178
|
capScope: "system",
|
|
@@ -86074,13 +86208,6 @@ Object.freeze({
|
|
|
86074
86208
|
access: "view",
|
|
86075
86209
|
role: "admin"
|
|
86076
86210
|
},
|
|
86077
|
-
"integrations.getSettings": {
|
|
86078
|
-
capName: "integrations",
|
|
86079
|
-
capScope: "system",
|
|
86080
|
-
addonId: null,
|
|
86081
|
-
access: "view",
|
|
86082
|
-
role: "admin"
|
|
86083
|
-
},
|
|
86084
86211
|
"integrations.list": {
|
|
86085
86212
|
capName: "integrations",
|
|
86086
86213
|
capScope: "system",
|
|
@@ -86088,13 +86215,6 @@ Object.freeze({
|
|
|
86088
86215
|
access: "view",
|
|
86089
86216
|
role: "admin"
|
|
86090
86217
|
},
|
|
86091
|
-
"integrations.setSettings": {
|
|
86092
|
-
capName: "integrations",
|
|
86093
|
-
capScope: "system",
|
|
86094
|
-
addonId: null,
|
|
86095
|
-
access: "create",
|
|
86096
|
-
role: "admin"
|
|
86097
|
-
},
|
|
86098
86218
|
"integrations.testConnection": {
|
|
86099
86219
|
capName: "integrations",
|
|
86100
86220
|
capScope: "system",
|
|
@@ -87089,6 +87209,13 @@ Object.freeze({
|
|
|
87089
87209
|
access: "view",
|
|
87090
87210
|
role: "admin"
|
|
87091
87211
|
},
|
|
87212
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87213
|
+
capName: "migrations",
|
|
87214
|
+
capScope: "system",
|
|
87215
|
+
addonId: null,
|
|
87216
|
+
access: "delete",
|
|
87217
|
+
role: "admin"
|
|
87218
|
+
},
|
|
87092
87219
|
"migrations.declareOwner": {
|
|
87093
87220
|
capName: "migrations",
|
|
87094
87221
|
capScope: "system",
|
|
@@ -87096,6 +87223,13 @@ Object.freeze({
|
|
|
87096
87223
|
access: "create",
|
|
87097
87224
|
role: "admin"
|
|
87098
87225
|
},
|
|
87226
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87227
|
+
capName: "migrations",
|
|
87228
|
+
capScope: "system",
|
|
87229
|
+
addonId: null,
|
|
87230
|
+
access: "delete",
|
|
87231
|
+
role: "admin"
|
|
87232
|
+
},
|
|
87099
87233
|
"migrations.dryRun": {
|
|
87100
87234
|
capName: "migrations",
|
|
87101
87235
|
capScope: "system",
|
|
@@ -87131,6 +87265,20 @@ Object.freeze({
|
|
|
87131
87265
|
access: "create",
|
|
87132
87266
|
role: "admin"
|
|
87133
87267
|
},
|
|
87268
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87269
|
+
capName: "migrations",
|
|
87270
|
+
capScope: "system",
|
|
87271
|
+
addonId: null,
|
|
87272
|
+
access: "create",
|
|
87273
|
+
role: "admin"
|
|
87274
|
+
},
|
|
87275
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87276
|
+
capName: "migrations",
|
|
87277
|
+
capScope: "system",
|
|
87278
|
+
addonId: null,
|
|
87279
|
+
access: "view",
|
|
87280
|
+
role: "admin"
|
|
87281
|
+
},
|
|
87134
87282
|
"migrations.restorePreMigration": {
|
|
87135
87283
|
capName: "migrations",
|
|
87136
87284
|
capScope: "system",
|
|
@@ -97527,7 +97675,7 @@ function connectionFromSettings(settings, tokenLocked) {
|
|
|
97527
97675
|
function sameConnection(a, b) {
|
|
97528
97676
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
97529
97677
|
}
|
|
97530
|
-
/** The integration
|
|
97678
|
+
/** The integration instance form (the `formSchema` of its instances); the integration carries the name. */
|
|
97531
97679
|
function buildConnectionFormSchema() {
|
|
97532
97680
|
return { sections: [
|
|
97533
97681
|
{
|
|
@@ -99232,6 +99380,7 @@ var EnpalProviderAddon = class extends BaseDeviceProvider {
|
|
|
99232
99380
|
fixtureMark: "host"
|
|
99233
99381
|
})], integrationRowsPortsOver({
|
|
99234
99382
|
api: defaultIntegrationApiOver(this.ctx.api),
|
|
99383
|
+
secrets: this.ctx.secrets ?? null,
|
|
99235
99384
|
records,
|
|
99236
99385
|
kind: enpalConnectionsEntryKind,
|
|
99237
99386
|
warn: (message) => this.ctx.logger.warn(message)
|
package/dist/addon.mjs
CHANGED
|
@@ -5980,7 +5980,7 @@ var ZodIssueCode = {
|
|
|
5980
5980
|
var ZodFirstPartyTypeKind;
|
|
5981
5981
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5982
5982
|
//#endregion
|
|
5983
|
-
//#region ../types/dist/sleep-
|
|
5983
|
+
//#region ../types/dist/sleep-DEZaLbiu.mjs
|
|
5984
5984
|
/**
|
|
5985
5985
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5986
5986
|
* window to float samples (D455).
|
|
@@ -6789,6 +6789,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6789
6789
|
}
|
|
6790
6790
|
return out;
|
|
6791
6791
|
}
|
|
6792
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6793
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6794
|
+
"cluster",
|
|
6795
|
+
"device-defaults",
|
|
6796
|
+
"node"
|
|
6797
|
+
];
|
|
6792
6798
|
/**
|
|
6793
6799
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6794
6800
|
*
|
|
@@ -7680,6 +7686,13 @@ var BaseAddon = class {
|
|
|
7680
7686
|
*/
|
|
7681
7687
|
secretStatePaths = [];
|
|
7682
7688
|
_addonSecretTree = null;
|
|
7689
|
+
/**
|
|
7690
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7691
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7692
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7693
|
+
*/
|
|
7694
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7695
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7683
7696
|
_deviceSecretTree = null;
|
|
7684
7697
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7685
7698
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7749,6 +7762,7 @@ var BaseAddon = class {
|
|
|
7749
7762
|
this._ctx = context;
|
|
7750
7763
|
this._secretSettings = null;
|
|
7751
7764
|
}
|
|
7765
|
+
await this.loadScopedSchemas();
|
|
7752
7766
|
await this.resolveConfig();
|
|
7753
7767
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7754
7768
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7758,6 +7772,7 @@ var BaseAddon = class {
|
|
|
7758
7772
|
const normalized = normalizeAddonInitResult(result);
|
|
7759
7773
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7760
7774
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7775
|
+
await this.loadScopedSchemas();
|
|
7761
7776
|
return normalized;
|
|
7762
7777
|
}
|
|
7763
7778
|
/**
|
|
@@ -7846,6 +7861,16 @@ var BaseAddon = class {
|
|
|
7846
7861
|
schema(schema) {
|
|
7847
7862
|
return schema;
|
|
7848
7863
|
}
|
|
7864
|
+
/**
|
|
7865
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7866
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7867
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7868
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7869
|
+
* once known.
|
|
7870
|
+
*/
|
|
7871
|
+
scopedSettingsCapabilities() {
|
|
7872
|
+
return [];
|
|
7873
|
+
}
|
|
7849
7874
|
/** Override to provide global-level settings UI schema. */
|
|
7850
7875
|
globalSettingsSchema(_cap) {
|
|
7851
7876
|
return null;
|
|
@@ -7889,7 +7914,8 @@ var BaseAddon = class {
|
|
|
7889
7914
|
return [];
|
|
7890
7915
|
}
|
|
7891
7916
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7892
|
-
|
|
7917
|
+
await this.loadScopedSchemas(cap);
|
|
7918
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7893
7919
|
if (!schema) return { sections: [] };
|
|
7894
7920
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7895
7921
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7924,10 +7950,9 @@ var BaseAddon = class {
|
|
|
7924
7950
|
*
|
|
7925
7951
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7926
7952
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7927
|
-
* SHAPE of the form only — the values live
|
|
7928
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7953
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7929
7954
|
*/
|
|
7930
|
-
async
|
|
7955
|
+
async integrationFormSchema(_instance) {
|
|
7931
7956
|
return null;
|
|
7932
7957
|
}
|
|
7933
7958
|
/**
|
|
@@ -7973,7 +7998,7 @@ var BaseAddon = class {
|
|
|
7973
7998
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7974
7999
|
*/
|
|
7975
8000
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7976
|
-
const scoped = await this.
|
|
8001
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7977
8002
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7978
8003
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7979
8004
|
}
|
|
@@ -8021,6 +8046,7 @@ var BaseAddon = class {
|
|
|
8021
8046
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8022
8047
|
*/
|
|
8023
8048
|
async resolveGlobalStore(nodeId, cap) {
|
|
8049
|
+
await this.loadScopedSchemas(cap);
|
|
8024
8050
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8025
8051
|
return this.projectForNode(raw, nodeId, cap);
|
|
8026
8052
|
}
|
|
@@ -8031,6 +8057,7 @@ var BaseAddon = class {
|
|
|
8031
8057
|
}
|
|
8032
8058
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8033
8059
|
async readRawProjectedStore(nodeId, cap) {
|
|
8060
|
+
await this.loadScopedSchemas(cap);
|
|
8034
8061
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8035
8062
|
return this.projectForNode(raw, nodeId, cap);
|
|
8036
8063
|
}
|
|
@@ -8133,6 +8160,7 @@ var BaseAddon = class {
|
|
|
8133
8160
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8134
8161
|
}
|
|
8135
8162
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8163
|
+
await this.loadScopedSchemas();
|
|
8136
8164
|
const keys = this.perNodeKeys();
|
|
8137
8165
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8138
8166
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8146,7 +8174,8 @@ var BaseAddon = class {
|
|
|
8146
8174
|
await this.resolveConfig();
|
|
8147
8175
|
await this.onConfigChanged();
|
|
8148
8176
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8149
|
-
|
|
8177
|
+
const legacy = this.globalSettingsSchema();
|
|
8178
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8150
8179
|
}
|
|
8151
8180
|
/**
|
|
8152
8181
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8154,14 +8183,67 @@ var BaseAddon = class {
|
|
|
8154
8183
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8155
8184
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8156
8185
|
*/
|
|
8157
|
-
|
|
8186
|
+
/**
|
|
8187
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8188
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8189
|
+
* derived from the previous set. Idempotent.
|
|
8190
|
+
*/
|
|
8191
|
+
async loadScopedSchemas(extraCap) {
|
|
8192
|
+
const caps = new Set([
|
|
8193
|
+
...this.scopedSettingsCapabilities(),
|
|
8194
|
+
...this._registeredCapNames,
|
|
8195
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8196
|
+
]);
|
|
8197
|
+
let added = false;
|
|
8198
|
+
for (const cap of caps) {
|
|
8199
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8200
|
+
const loaded = [];
|
|
8201
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8202
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8203
|
+
if (schema !== null) loaded.push({
|
|
8204
|
+
scope,
|
|
8205
|
+
schema
|
|
8206
|
+
});
|
|
8207
|
+
}
|
|
8208
|
+
this._scopedByCap.set(cap, loaded);
|
|
8209
|
+
added = true;
|
|
8210
|
+
}
|
|
8211
|
+
if (!added) return;
|
|
8212
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8213
|
+
this._addonSecretTree = null;
|
|
8214
|
+
}
|
|
8215
|
+
/**
|
|
8216
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8217
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8218
|
+
*/
|
|
8219
|
+
scopedFormSchema(cap) {
|
|
8220
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8221
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8222
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8223
|
+
}
|
|
8224
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8225
|
+
loadedScopedSchemas(cap) {
|
|
8226
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8227
|
+
return [...this._scopedByCap.values()].flat();
|
|
8228
|
+
}
|
|
8229
|
+
/**
|
|
8230
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8231
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8232
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8233
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8234
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8235
|
+
*/
|
|
8158
8236
|
perNodeKeys(cap) {
|
|
8159
8237
|
const cacheKey = cap ?? "";
|
|
8160
|
-
const cached = this.
|
|
8238
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8161
8239
|
if (cached) return cached;
|
|
8162
|
-
const
|
|
8163
|
-
const keys = new Set(
|
|
8164
|
-
this.
|
|
8240
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8241
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8242
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8243
|
+
if (scope !== "node") continue;
|
|
8244
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8245
|
+
}
|
|
8246
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8165
8247
|
return keys;
|
|
8166
8248
|
}
|
|
8167
8249
|
/**
|
|
@@ -8171,10 +8253,9 @@ var BaseAddon = class {
|
|
|
8171
8253
|
* time to return its response before the addon is torn down and
|
|
8172
8254
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8173
8255
|
*/
|
|
8174
|
-
maybeAutoRestart(patchKeys,
|
|
8175
|
-
if (!schema) return;
|
|
8256
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8176
8257
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8177
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8258
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8178
8259
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8179
8260
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8180
8261
|
}
|
|
@@ -8370,12 +8451,14 @@ var BaseAddon = class {
|
|
|
8370
8451
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8371
8452
|
*/
|
|
8372
8453
|
async getDeclaredSettingsKeys() {
|
|
8454
|
+
await this.loadScopedSchemas();
|
|
8373
8455
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8374
8456
|
}
|
|
8375
8457
|
declaredKeySet() {
|
|
8376
8458
|
const keys = new Set(Object.keys(this.defaults));
|
|
8377
8459
|
const schema = this.globalSettingsSchema();
|
|
8378
8460
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8461
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8379
8462
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8380
8463
|
for (const path of this.secretStatePaths) {
|
|
8381
8464
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8471,7 +8554,11 @@ var BaseAddon = class {
|
|
|
8471
8554
|
}
|
|
8472
8555
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8473
8556
|
addonSecretTree() {
|
|
8474
|
-
this._addonSecretTree
|
|
8557
|
+
if (this._addonSecretTree === null) {
|
|
8558
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8559
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8560
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8561
|
+
}
|
|
8475
8562
|
return this._addonSecretTree;
|
|
8476
8563
|
}
|
|
8477
8564
|
deviceSecretTree() {
|
|
@@ -8721,6 +8808,23 @@ function dropPerNodeFields(fields) {
|
|
|
8721
8808
|
}
|
|
8722
8809
|
return kept;
|
|
8723
8810
|
}
|
|
8811
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8812
|
+
function collectValueFieldKeys(fields) {
|
|
8813
|
+
const collected = [];
|
|
8814
|
+
for (const field of fields) {
|
|
8815
|
+
if (field.type === "group") {
|
|
8816
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8817
|
+
continue;
|
|
8818
|
+
}
|
|
8819
|
+
if (field.type === "sub-tabs") {
|
|
8820
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8821
|
+
continue;
|
|
8822
|
+
}
|
|
8823
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8824
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8825
|
+
}
|
|
8826
|
+
return collected;
|
|
8827
|
+
}
|
|
8724
8828
|
function collectFieldKeys(fields) {
|
|
8725
8829
|
const collected = [];
|
|
8726
8830
|
for (const field of fields) {
|
|
@@ -11933,7 +12037,7 @@ object({
|
|
|
11933
12037
|
params: record(string(), union([string(), number()])).optional()
|
|
11934
12038
|
});
|
|
11935
12039
|
//#endregion
|
|
11936
|
-
//#region ../types/dist/composition-
|
|
12040
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11937
12041
|
/**
|
|
11938
12042
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11939
12043
|
*
|
|
@@ -13405,12 +13509,22 @@ var RecordIntegrationInstances = class {
|
|
|
13405
13509
|
...this.hooks.description,
|
|
13406
13510
|
mintsId: true
|
|
13407
13511
|
};
|
|
13408
|
-
|
|
13512
|
+
/**
|
|
13513
|
+
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13514
|
+
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13515
|
+
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13516
|
+
*/
|
|
13517
|
+
async resolveRecordId(id) {
|
|
13518
|
+
if (await this.handle.get(id) !== null) return id;
|
|
13519
|
+
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13520
|
+
return linked === void 0 ? id : linked.id;
|
|
13521
|
+
}
|
|
13522
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13409
13523
|
listInstances = async () => {
|
|
13410
13524
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13411
13525
|
};
|
|
13412
13526
|
getInstance = async ({ id }) => {
|
|
13413
|
-
const record = await this.handle.get(id);
|
|
13527
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13414
13528
|
return record === null ? null : this.viewOf(record);
|
|
13415
13529
|
};
|
|
13416
13530
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13427,7 +13541,8 @@ var RecordIntegrationInstances = class {
|
|
|
13427
13541
|
await this.hooks.onChanged?.(minted, "created");
|
|
13428
13542
|
return { id: minted };
|
|
13429
13543
|
};
|
|
13430
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13544
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13545
|
+
const id = await this.resolveRecordId(askedId);
|
|
13431
13546
|
const patch = {};
|
|
13432
13547
|
for (const [key, value] of Object.entries(values)) {
|
|
13433
13548
|
if (value === void 0) continue;
|
|
@@ -13462,7 +13577,10 @@ var RecordIntegrationInstances = class {
|
|
|
13462
13577
|
status: "unsupported",
|
|
13463
13578
|
error: "This integration declares no connection test"
|
|
13464
13579
|
};
|
|
13465
|
-
return this.hooks.test(input
|
|
13580
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13581
|
+
...input,
|
|
13582
|
+
id: await this.resolveRecordId(input.id)
|
|
13583
|
+
});
|
|
13466
13584
|
};
|
|
13467
13585
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13468
13586
|
toProvider() {
|
|
@@ -14641,20 +14759,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
|
|
|
14641
14759
|
};
|
|
14642
14760
|
}
|
|
14643
14761
|
/**
|
|
14644
|
-
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416)
|
|
14645
|
-
* from its own process, so the
|
|
14646
|
-
* `
|
|
14647
|
-
*
|
|
14648
|
-
*
|
|
14649
|
-
|
|
14762
|
+
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
14763
|
+
* from its own process, so the leftover rows are reached through the migration-only door
|
|
14764
|
+
* (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
|
|
14765
|
+
* The door returns markers. This process is the owner, so it resolves and releases them with
|
|
14766
|
+
* `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
|
|
14767
|
+
* door to delete the rows. The addon's own record kind is its records handle.
|
|
14768
|
+
*/
|
|
14769
|
+
async function resolvedRows(stored, secrets) {
|
|
14770
|
+
if (secrets === null) return stored;
|
|
14771
|
+
const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
|
|
14772
|
+
if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
|
|
14773
|
+
return value;
|
|
14774
|
+
}
|
|
14650
14775
|
function integrationRowsPortsOverApi(deps) {
|
|
14776
|
+
const secrets = deps.secrets ?? null;
|
|
14651
14777
|
return {
|
|
14652
14778
|
warn: (message) => deps.log.warn(message),
|
|
14653
14779
|
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
14654
14780
|
id: i.id,
|
|
14655
14781
|
name: i.name
|
|
14656
14782
|
})),
|
|
14657
|
-
readRows: (integrationId) => deps.api.
|
|
14783
|
+
readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
|
|
14658
14784
|
putRecord: async (id, body) => {
|
|
14659
14785
|
if (await deps.records.get(id) !== null) return "exists";
|
|
14660
14786
|
try {
|
|
@@ -14673,7 +14799,9 @@ function integrationRowsPortsOverApi(deps) {
|
|
|
14673
14799
|
await deps.records.delete(id);
|
|
14674
14800
|
},
|
|
14675
14801
|
clearRows: async (integrationId) => {
|
|
14676
|
-
await deps.api.
|
|
14802
|
+
const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
|
|
14803
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
14804
|
+
await deps.api.clearLegacyIntegrationRows({ integrationId });
|
|
14677
14805
|
}
|
|
14678
14806
|
};
|
|
14679
14807
|
}
|
|
@@ -14741,6 +14869,7 @@ function integrationRowsMigration(spec) {
|
|
|
14741
14869
|
function integrationRowsPortsOver(deps) {
|
|
14742
14870
|
return integrationRowsPortsOverApi({
|
|
14743
14871
|
api: deps.api,
|
|
14872
|
+
...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
|
|
14744
14873
|
records: deps.records.kind({
|
|
14745
14874
|
name: deps.kind.name,
|
|
14746
14875
|
declaration: deps.kind.declaration,
|
|
@@ -14752,8 +14881,8 @@ function integrationRowsPortsOver(deps) {
|
|
|
14752
14881
|
function defaultIntegrationApiOver(api) {
|
|
14753
14882
|
return {
|
|
14754
14883
|
list: () => api.integrations.list.query(),
|
|
14755
|
-
|
|
14756
|
-
|
|
14884
|
+
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
14885
|
+
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
14757
14886
|
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
14758
14887
|
addonId,
|
|
14759
14888
|
projection: "slim"
|
|
@@ -50431,6 +50560,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50431
50560
|
runsFound: number().int().nonnegative(),
|
|
50432
50561
|
pausesFound: number().int().nonnegative(),
|
|
50433
50562
|
longestPauseMs: number().nonnegative(),
|
|
50563
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50564
|
+
pulsesFound: number().int().nonnegative(),
|
|
50565
|
+
longestPulseMs: number().nonnegative(),
|
|
50434
50566
|
idleFloorW: number(),
|
|
50435
50567
|
noiseCeilingW: number(),
|
|
50436
50568
|
plateauW: number(),
|
|
@@ -56297,11 +56429,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56297
56429
|
info: record(string(), unknown()).optional(),
|
|
56298
56430
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56299
56431
|
originStableId: string().optional(),
|
|
56300
|
-
settings: record(string(), unknown()).optional()
|
|
56301
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56302
|
-
* the keychain, not the table (D787). */
|
|
56303
|
-
secretKeys: array(string()).optional(),
|
|
56304
|
-
skipRestart: boolean().optional()
|
|
56432
|
+
settings: record(string(), unknown()).optional()
|
|
56305
56433
|
});
|
|
56306
56434
|
var UpdateIntegrationInputSchema = object({
|
|
56307
56435
|
id: string(),
|
|
@@ -56322,22 +56450,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56322
56450
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56323
56451
|
kind: "mutation",
|
|
56324
56452
|
auth: "admin"
|
|
56325
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56326
|
-
id: string(),
|
|
56327
|
-
settings: record(string(), unknown()),
|
|
56328
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56329
|
-
secretKeys: array(string()).optional(),
|
|
56330
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56331
|
-
skipRestart: boolean().optional()
|
|
56332
|
-
}), object({ success: boolean() }), {
|
|
56333
|
-
kind: "mutation",
|
|
56334
|
-
auth: "admin"
|
|
56335
|
-
}), method(object({ id: string() }), object({
|
|
56336
|
-
rows: number().int().nonnegative(),
|
|
56337
|
-
secrets: number().int().nonnegative()
|
|
56338
|
-
}), {
|
|
56339
|
-
kind: "mutation",
|
|
56340
|
-
auth: "admin"
|
|
56341
56453
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56342
56454
|
addonId: string(),
|
|
56343
56455
|
settings: record(string(), unknown())
|
|
@@ -59314,6 +59426,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59314
59426
|
}), {
|
|
59315
59427
|
kind: "mutation",
|
|
59316
59428
|
auth: "admin"
|
|
59429
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59430
|
+
rows: number().int().nonnegative(),
|
|
59431
|
+
secrets: number().int().nonnegative()
|
|
59432
|
+
}), {
|
|
59433
|
+
kind: "mutation",
|
|
59434
|
+
auth: "admin"
|
|
59435
|
+
}), method(object({
|
|
59436
|
+
integrationId: string().min(1),
|
|
59437
|
+
patch: record(string(), unknown())
|
|
59438
|
+
}), object({ written: boolean() }), {
|
|
59439
|
+
kind: "mutation",
|
|
59440
|
+
auth: "admin"
|
|
59441
|
+
}), method(object({
|
|
59442
|
+
integrationId: string().min(1),
|
|
59443
|
+
keys: array(string().min(1))
|
|
59444
|
+
}), object({
|
|
59445
|
+
present: boolean(),
|
|
59446
|
+
removed: record(string(), unknown())
|
|
59447
|
+
}), {
|
|
59448
|
+
kind: "mutation",
|
|
59449
|
+
auth: "admin"
|
|
59317
59450
|
});
|
|
59318
59451
|
method(object({
|
|
59319
59452
|
sourceUrl: string(),
|
|
@@ -80732,11 +80865,15 @@ discriminatedUnion("kind", [object({
|
|
|
80732
80865
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80733
80866
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80734
80867
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80735
|
-
* 3.
|
|
80736
|
-
*
|
|
80868
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80869
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80870
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80871
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80872
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80873
|
+
* is no run.
|
|
80737
80874
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80738
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80739
|
-
* covers the longest standby blip above T.
|
|
80875
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80876
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80740
80877
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80741
80878
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80742
80879
|
*/
|
|
@@ -80747,6 +80884,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80747
80884
|
30 * SECOND_MS;
|
|
80748
80885
|
60 * SECOND_MS;
|
|
80749
80886
|
20 * MINUTE_MS$1;
|
|
80887
|
+
90 * SECOND_MS;
|
|
80888
|
+
30 * MINUTE_MS$1;
|
|
80889
|
+
10 * MINUTE_MS$1;
|
|
80890
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80750
80891
|
10 * SECOND_MS;
|
|
80751
80892
|
10 * SECOND_MS;
|
|
80752
80893
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -86031,13 +86172,6 @@ Object.freeze({
|
|
|
86031
86172
|
access: "create",
|
|
86032
86173
|
role: "admin"
|
|
86033
86174
|
},
|
|
86034
|
-
"integrations.clearSettings": {
|
|
86035
|
-
capName: "integrations",
|
|
86036
|
-
capScope: "system",
|
|
86037
|
-
addonId: null,
|
|
86038
|
-
access: "delete",
|
|
86039
|
-
role: "admin"
|
|
86040
|
-
},
|
|
86041
86175
|
"integrations.create": {
|
|
86042
86176
|
capName: "integrations",
|
|
86043
86177
|
capScope: "system",
|
|
@@ -86073,13 +86207,6 @@ Object.freeze({
|
|
|
86073
86207
|
access: "view",
|
|
86074
86208
|
role: "admin"
|
|
86075
86209
|
},
|
|
86076
|
-
"integrations.getSettings": {
|
|
86077
|
-
capName: "integrations",
|
|
86078
|
-
capScope: "system",
|
|
86079
|
-
addonId: null,
|
|
86080
|
-
access: "view",
|
|
86081
|
-
role: "admin"
|
|
86082
|
-
},
|
|
86083
86210
|
"integrations.list": {
|
|
86084
86211
|
capName: "integrations",
|
|
86085
86212
|
capScope: "system",
|
|
@@ -86087,13 +86214,6 @@ Object.freeze({
|
|
|
86087
86214
|
access: "view",
|
|
86088
86215
|
role: "admin"
|
|
86089
86216
|
},
|
|
86090
|
-
"integrations.setSettings": {
|
|
86091
|
-
capName: "integrations",
|
|
86092
|
-
capScope: "system",
|
|
86093
|
-
addonId: null,
|
|
86094
|
-
access: "create",
|
|
86095
|
-
role: "admin"
|
|
86096
|
-
},
|
|
86097
86217
|
"integrations.testConnection": {
|
|
86098
86218
|
capName: "integrations",
|
|
86099
86219
|
capScope: "system",
|
|
@@ -87088,6 +87208,13 @@ Object.freeze({
|
|
|
87088
87208
|
access: "view",
|
|
87089
87209
|
role: "admin"
|
|
87090
87210
|
},
|
|
87211
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87212
|
+
capName: "migrations",
|
|
87213
|
+
capScope: "system",
|
|
87214
|
+
addonId: null,
|
|
87215
|
+
access: "delete",
|
|
87216
|
+
role: "admin"
|
|
87217
|
+
},
|
|
87091
87218
|
"migrations.declareOwner": {
|
|
87092
87219
|
capName: "migrations",
|
|
87093
87220
|
capScope: "system",
|
|
@@ -87095,6 +87222,13 @@ Object.freeze({
|
|
|
87095
87222
|
access: "create",
|
|
87096
87223
|
role: "admin"
|
|
87097
87224
|
},
|
|
87225
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87226
|
+
capName: "migrations",
|
|
87227
|
+
capScope: "system",
|
|
87228
|
+
addonId: null,
|
|
87229
|
+
access: "delete",
|
|
87230
|
+
role: "admin"
|
|
87231
|
+
},
|
|
87098
87232
|
"migrations.dryRun": {
|
|
87099
87233
|
capName: "migrations",
|
|
87100
87234
|
capScope: "system",
|
|
@@ -87130,6 +87264,20 @@ Object.freeze({
|
|
|
87130
87264
|
access: "create",
|
|
87131
87265
|
role: "admin"
|
|
87132
87266
|
},
|
|
87267
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87268
|
+
capName: "migrations",
|
|
87269
|
+
capScope: "system",
|
|
87270
|
+
addonId: null,
|
|
87271
|
+
access: "create",
|
|
87272
|
+
role: "admin"
|
|
87273
|
+
},
|
|
87274
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87275
|
+
capName: "migrations",
|
|
87276
|
+
capScope: "system",
|
|
87277
|
+
addonId: null,
|
|
87278
|
+
access: "view",
|
|
87279
|
+
role: "admin"
|
|
87280
|
+
},
|
|
87133
87281
|
"migrations.restorePreMigration": {
|
|
87134
87282
|
capName: "migrations",
|
|
87135
87283
|
capScope: "system",
|
|
@@ -97526,7 +97674,7 @@ function connectionFromSettings(settings, tokenLocked) {
|
|
|
97526
97674
|
function sameConnection(a, b) {
|
|
97527
97675
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
97528
97676
|
}
|
|
97529
|
-
/** The integration
|
|
97677
|
+
/** The integration instance form (the `formSchema` of its instances); the integration carries the name. */
|
|
97530
97678
|
function buildConnectionFormSchema() {
|
|
97531
97679
|
return { sections: [
|
|
97532
97680
|
{
|
|
@@ -99231,6 +99379,7 @@ var EnpalProviderAddon = class extends BaseDeviceProvider {
|
|
|
99231
99379
|
fixtureMark: "host"
|
|
99232
99380
|
})], integrationRowsPortsOver({
|
|
99233
99381
|
api: defaultIntegrationApiOver(this.ctx.api),
|
|
99382
|
+
secrets: this.ctx.secrets ?? null,
|
|
99234
99383
|
records,
|
|
99235
99384
|
kind: enpalConnectionsEntryKind,
|
|
99236
99385
|
warn: (message) => this.ctx.logger.warn(message)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-enpal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Enpal solar-box device-provider addon for CamStack — PV, battery, grid, house and heat-pump readings from the box's local InfluxDB (device page as fallback)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
}
|
|
64
64
|
],
|
|
65
65
|
"requires": {
|
|
66
|
-
"server": ">=1.2.
|
|
66
|
+
"server": ">=1.2.777"
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
"files": [
|