@camstack/addon-provider-enpal 0.1.9 → 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 +6 -0
- package/dist/addon.js +136 -17
- package/dist/addon.mjs +136 -17
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ 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
|
+
|
|
10
16
|
## [0.1.9] - 2026-11-05
|
|
11
17
|
|
|
12
18
|
### 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 });
|
|
@@ -8021,6 +8047,7 @@ var BaseAddon = class {
|
|
|
8021
8047
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8022
8048
|
*/
|
|
8023
8049
|
async resolveGlobalStore(nodeId, cap) {
|
|
8050
|
+
await this.loadScopedSchemas(cap);
|
|
8024
8051
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8025
8052
|
return this.projectForNode(raw, nodeId, cap);
|
|
8026
8053
|
}
|
|
@@ -8031,6 +8058,7 @@ var BaseAddon = class {
|
|
|
8031
8058
|
}
|
|
8032
8059
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8033
8060
|
async readRawProjectedStore(nodeId, cap) {
|
|
8061
|
+
await this.loadScopedSchemas(cap);
|
|
8034
8062
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8035
8063
|
return this.projectForNode(raw, nodeId, cap);
|
|
8036
8064
|
}
|
|
@@ -8133,6 +8161,7 @@ var BaseAddon = class {
|
|
|
8133
8161
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8134
8162
|
}
|
|
8135
8163
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8164
|
+
await this.loadScopedSchemas();
|
|
8136
8165
|
const keys = this.perNodeKeys();
|
|
8137
8166
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8138
8167
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8146,7 +8175,8 @@ var BaseAddon = class {
|
|
|
8146
8175
|
await this.resolveConfig();
|
|
8147
8176
|
await this.onConfigChanged();
|
|
8148
8177
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8149
|
-
|
|
8178
|
+
const legacy = this.globalSettingsSchema();
|
|
8179
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8150
8180
|
}
|
|
8151
8181
|
/**
|
|
8152
8182
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8154,14 +8184,67 @@ var BaseAddon = class {
|
|
|
8154
8184
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8155
8185
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8156
8186
|
*/
|
|
8157
|
-
|
|
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
|
+
*/
|
|
8158
8237
|
perNodeKeys(cap) {
|
|
8159
8238
|
const cacheKey = cap ?? "";
|
|
8160
|
-
const cached = this.
|
|
8239
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8161
8240
|
if (cached) return cached;
|
|
8162
|
-
const
|
|
8163
|
-
const keys = new Set(
|
|
8164
|
-
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);
|
|
8165
8248
|
return keys;
|
|
8166
8249
|
}
|
|
8167
8250
|
/**
|
|
@@ -8171,10 +8254,9 @@ var BaseAddon = class {
|
|
|
8171
8254
|
* time to return its response before the addon is torn down and
|
|
8172
8255
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8173
8256
|
*/
|
|
8174
|
-
maybeAutoRestart(patchKeys,
|
|
8175
|
-
if (!schema) return;
|
|
8257
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8176
8258
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8177
|
-
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) {
|
|
8178
8260
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8179
8261
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8180
8262
|
}
|
|
@@ -8370,12 +8452,14 @@ var BaseAddon = class {
|
|
|
8370
8452
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8371
8453
|
*/
|
|
8372
8454
|
async getDeclaredSettingsKeys() {
|
|
8455
|
+
await this.loadScopedSchemas();
|
|
8373
8456
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8374
8457
|
}
|
|
8375
8458
|
declaredKeySet() {
|
|
8376
8459
|
const keys = new Set(Object.keys(this.defaults));
|
|
8377
8460
|
const schema = this.globalSettingsSchema();
|
|
8378
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);
|
|
8379
8463
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8380
8464
|
for (const path of this.secretStatePaths) {
|
|
8381
8465
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8471,7 +8555,11 @@ var BaseAddon = class {
|
|
|
8471
8555
|
}
|
|
8472
8556
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8473
8557
|
addonSecretTree() {
|
|
8474
|
-
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
|
+
}
|
|
8475
8563
|
return this._addonSecretTree;
|
|
8476
8564
|
}
|
|
8477
8565
|
deviceSecretTree() {
|
|
@@ -8721,6 +8809,23 @@ function dropPerNodeFields(fields) {
|
|
|
8721
8809
|
}
|
|
8722
8810
|
return kept;
|
|
8723
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
|
+
}
|
|
8724
8829
|
function collectFieldKeys(fields) {
|
|
8725
8830
|
const collected = [];
|
|
8726
8831
|
for (const field of fields) {
|
|
@@ -13405,12 +13510,22 @@ var RecordIntegrationInstances = class {
|
|
|
13405
13510
|
...this.hooks.description,
|
|
13406
13511
|
mintsId: true
|
|
13407
13512
|
};
|
|
13408
|
-
|
|
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));
|
|
13409
13524
|
listInstances = async () => {
|
|
13410
13525
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13411
13526
|
};
|
|
13412
13527
|
getInstance = async ({ id }) => {
|
|
13413
|
-
const record = await this.handle.get(id);
|
|
13528
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13414
13529
|
return record === null ? null : this.viewOf(record);
|
|
13415
13530
|
};
|
|
13416
13531
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13427,7 +13542,8 @@ var RecordIntegrationInstances = class {
|
|
|
13427
13542
|
await this.hooks.onChanged?.(minted, "created");
|
|
13428
13543
|
return { id: minted };
|
|
13429
13544
|
};
|
|
13430
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13545
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13546
|
+
const id = await this.resolveRecordId(askedId);
|
|
13431
13547
|
const patch = {};
|
|
13432
13548
|
for (const [key, value] of Object.entries(values)) {
|
|
13433
13549
|
if (value === void 0) continue;
|
|
@@ -13462,7 +13578,10 @@ var RecordIntegrationInstances = class {
|
|
|
13462
13578
|
status: "unsupported",
|
|
13463
13579
|
error: "This integration declares no connection test"
|
|
13464
13580
|
};
|
|
13465
|
-
return this.hooks.test(input
|
|
13581
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13582
|
+
...input,
|
|
13583
|
+
id: await this.resolveRecordId(input.id)
|
|
13584
|
+
});
|
|
13466
13585
|
};
|
|
13467
13586
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13468
13587
|
toProvider() {
|
|
@@ -97556,7 +97675,7 @@ function connectionFromSettings(settings, tokenLocked) {
|
|
|
97556
97675
|
function sameConnection(a, b) {
|
|
97557
97676
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
97558
97677
|
}
|
|
97559
|
-
/** The integration
|
|
97678
|
+
/** The integration instance form (the `formSchema` of its instances); the integration carries the name. */
|
|
97560
97679
|
function buildConnectionFormSchema() {
|
|
97561
97680
|
return { sections: [
|
|
97562
97681
|
{
|
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 });
|
|
@@ -8020,6 +8046,7 @@ var BaseAddon = class {
|
|
|
8020
8046
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8021
8047
|
*/
|
|
8022
8048
|
async resolveGlobalStore(nodeId, cap) {
|
|
8049
|
+
await this.loadScopedSchemas(cap);
|
|
8023
8050
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8024
8051
|
return this.projectForNode(raw, nodeId, cap);
|
|
8025
8052
|
}
|
|
@@ -8030,6 +8057,7 @@ var BaseAddon = class {
|
|
|
8030
8057
|
}
|
|
8031
8058
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8032
8059
|
async readRawProjectedStore(nodeId, cap) {
|
|
8060
|
+
await this.loadScopedSchemas(cap);
|
|
8033
8061
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8034
8062
|
return this.projectForNode(raw, nodeId, cap);
|
|
8035
8063
|
}
|
|
@@ -8132,6 +8160,7 @@ var BaseAddon = class {
|
|
|
8132
8160
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8133
8161
|
}
|
|
8134
8162
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8163
|
+
await this.loadScopedSchemas();
|
|
8135
8164
|
const keys = this.perNodeKeys();
|
|
8136
8165
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8137
8166
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8145,7 +8174,8 @@ var BaseAddon = class {
|
|
|
8145
8174
|
await this.resolveConfig();
|
|
8146
8175
|
await this.onConfigChanged();
|
|
8147
8176
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8148
|
-
|
|
8177
|
+
const legacy = this.globalSettingsSchema();
|
|
8178
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8149
8179
|
}
|
|
8150
8180
|
/**
|
|
8151
8181
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8153,14 +8183,67 @@ var BaseAddon = class {
|
|
|
8153
8183
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8154
8184
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8155
8185
|
*/
|
|
8156
|
-
|
|
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
|
+
*/
|
|
8157
8236
|
perNodeKeys(cap) {
|
|
8158
8237
|
const cacheKey = cap ?? "";
|
|
8159
|
-
const cached = this.
|
|
8238
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8160
8239
|
if (cached) return cached;
|
|
8161
|
-
const
|
|
8162
|
-
const keys = new Set(
|
|
8163
|
-
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);
|
|
8164
8247
|
return keys;
|
|
8165
8248
|
}
|
|
8166
8249
|
/**
|
|
@@ -8170,10 +8253,9 @@ var BaseAddon = class {
|
|
|
8170
8253
|
* time to return its response before the addon is torn down and
|
|
8171
8254
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8172
8255
|
*/
|
|
8173
|
-
maybeAutoRestart(patchKeys,
|
|
8174
|
-
if (!schema) return;
|
|
8256
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8175
8257
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8176
|
-
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) {
|
|
8177
8259
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8178
8260
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8179
8261
|
}
|
|
@@ -8369,12 +8451,14 @@ var BaseAddon = class {
|
|
|
8369
8451
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8370
8452
|
*/
|
|
8371
8453
|
async getDeclaredSettingsKeys() {
|
|
8454
|
+
await this.loadScopedSchemas();
|
|
8372
8455
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8373
8456
|
}
|
|
8374
8457
|
declaredKeySet() {
|
|
8375
8458
|
const keys = new Set(Object.keys(this.defaults));
|
|
8376
8459
|
const schema = this.globalSettingsSchema();
|
|
8377
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);
|
|
8378
8462
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8379
8463
|
for (const path of this.secretStatePaths) {
|
|
8380
8464
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8470,7 +8554,11 @@ var BaseAddon = class {
|
|
|
8470
8554
|
}
|
|
8471
8555
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8472
8556
|
addonSecretTree() {
|
|
8473
|
-
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
|
+
}
|
|
8474
8562
|
return this._addonSecretTree;
|
|
8475
8563
|
}
|
|
8476
8564
|
deviceSecretTree() {
|
|
@@ -8720,6 +8808,23 @@ function dropPerNodeFields(fields) {
|
|
|
8720
8808
|
}
|
|
8721
8809
|
return kept;
|
|
8722
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
|
+
}
|
|
8723
8828
|
function collectFieldKeys(fields) {
|
|
8724
8829
|
const collected = [];
|
|
8725
8830
|
for (const field of fields) {
|
|
@@ -13404,12 +13509,22 @@ var RecordIntegrationInstances = class {
|
|
|
13404
13509
|
...this.hooks.description,
|
|
13405
13510
|
mintsId: true
|
|
13406
13511
|
};
|
|
13407
|
-
|
|
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));
|
|
13408
13523
|
listInstances = async () => {
|
|
13409
13524
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13410
13525
|
};
|
|
13411
13526
|
getInstance = async ({ id }) => {
|
|
13412
|
-
const record = await this.handle.get(id);
|
|
13527
|
+
const record = await this.handle.get(await this.resolveRecordId(id));
|
|
13413
13528
|
return record === null ? null : this.viewOf(record);
|
|
13414
13529
|
};
|
|
13415
13530
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
@@ -13426,7 +13541,8 @@ var RecordIntegrationInstances = class {
|
|
|
13426
13541
|
await this.hooks.onChanged?.(minted, "created");
|
|
13427
13542
|
return { id: minted };
|
|
13428
13543
|
};
|
|
13429
|
-
updateInstance = async ({ id, name, values }) => {
|
|
13544
|
+
updateInstance = async ({ id: askedId, name, values }) => {
|
|
13545
|
+
const id = await this.resolveRecordId(askedId);
|
|
13430
13546
|
const patch = {};
|
|
13431
13547
|
for (const [key, value] of Object.entries(values)) {
|
|
13432
13548
|
if (value === void 0) continue;
|
|
@@ -13461,7 +13577,10 @@ var RecordIntegrationInstances = class {
|
|
|
13461
13577
|
status: "unsupported",
|
|
13462
13578
|
error: "This integration declares no connection test"
|
|
13463
13579
|
};
|
|
13464
|
-
return this.hooks.test(input
|
|
13580
|
+
return this.hooks.test(input.id === null ? input : {
|
|
13581
|
+
...input,
|
|
13582
|
+
id: await this.resolveRecordId(input.id)
|
|
13583
|
+
});
|
|
13465
13584
|
};
|
|
13466
13585
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13467
13586
|
toProvider() {
|
|
@@ -97555,7 +97674,7 @@ function connectionFromSettings(settings, tokenLocked) {
|
|
|
97555
97674
|
function sameConnection(a, b) {
|
|
97556
97675
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
97557
97676
|
}
|
|
97558
|
-
/** The integration
|
|
97677
|
+
/** The integration instance form (the `formSchema` of its instances); the integration carries the name. */
|
|
97559
97678
|
function buildConnectionFormSchema() {
|
|
97560
97679
|
return { sections: [
|
|
97561
97680
|
{
|
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": [
|