@camstack/addon-matter-broker 0.2.158 → 0.2.160
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 +14 -0
- package/dist/addon.js +1007 -882
- package/dist/addon.mjs +1008 -883
- package/dist/index.mjs +1 -1
- package/i18n/en.json +0 -3
- package/i18n/it.json +0 -3
- package/package.json +3 -3
package/dist/addon.js
CHANGED
|
@@ -5995,7 +5995,7 @@ var ZodIssueCode = {
|
|
|
5995
5995
|
var ZodFirstPartyTypeKind;
|
|
5996
5996
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5997
5997
|
//#endregion
|
|
5998
|
-
//#region ../types/dist/sleep-
|
|
5998
|
+
//#region ../types/dist/sleep-BX_MqzUe.mjs
|
|
5999
5999
|
/**
|
|
6000
6000
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6001
6001
|
* window to float samples (D455).
|
|
@@ -7624,6 +7624,28 @@ var SECRET_RETRY_MAX_MS = 5 * 6e4;
|
|
|
7624
7624
|
/** Coalesces a burst of `keychain.changed` events into one re-resolve. */
|
|
7625
7625
|
var SECRET_CHANGE_DEBOUNCE_MS = 250;
|
|
7626
7626
|
/**
|
|
7627
|
+
* Pair a catalogue with the function that builds its ports FROM THE MIGRATION API. The one
|
|
7628
|
+
* place a catalogue is bound: a migration reaches nothing a ports binder cannot reach
|
|
7629
|
+
* through `api` (guard `check-migration-ports-surface`).
|
|
7630
|
+
*/
|
|
7631
|
+
function migrationSet(catalogue, bindPorts) {
|
|
7632
|
+
return { bind: (api) => defineAddonMigrations(catalogue, bindPorts(api)) };
|
|
7633
|
+
}
|
|
7634
|
+
/** A mixed array is a mistake: the other kind would be dropped without a word. */
|
|
7635
|
+
function refuseMixed(form, kept, dropped) {
|
|
7636
|
+
if (dropped > 0) throw new Error(`runMigrations(${form}): ${dropped} of ${kept + dropped} entries are of the other kind and would be dropped; pass one kind per call`);
|
|
7637
|
+
}
|
|
7638
|
+
function catalogueOf(list) {
|
|
7639
|
+
const kept = list.filter((entry) => !("execute" in entry));
|
|
7640
|
+
refuseMixed("catalogue, (api) => ports", kept.length, list.length - kept.length);
|
|
7641
|
+
return kept;
|
|
7642
|
+
}
|
|
7643
|
+
function boundOf(list) {
|
|
7644
|
+
const kept = list.filter((entry) => "execute" in entry);
|
|
7645
|
+
refuseMixed("bound migrations", kept.length, list.length - kept.length);
|
|
7646
|
+
return kept;
|
|
7647
|
+
}
|
|
7648
|
+
/**
|
|
7627
7649
|
* Base class for CamStack addons. Eliminates settings boilerplate:
|
|
7628
7650
|
*
|
|
7629
7651
|
* - Typed `config` property with automatic resolution from store + defaults
|
|
@@ -7790,23 +7812,21 @@ var BaseAddon = class {
|
|
|
7790
7812
|
await this.loadScopedSchemas();
|
|
7791
7813
|
return normalized;
|
|
7792
7814
|
}
|
|
7815
|
+
async runMigrations(first, second, third) {
|
|
7816
|
+
if (typeof second === "function") return this.runMigrationSets([migrationSet(catalogueOf(first), second)], third);
|
|
7817
|
+
return this.runBound(boundOf(first), second);
|
|
7818
|
+
}
|
|
7793
7819
|
/**
|
|
7794
|
-
*
|
|
7795
|
-
*
|
|
7796
|
-
*
|
|
7797
|
-
* restored devices — with the catalogue bound to the addon's ports
|
|
7798
|
-
* (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
|
|
7799
|
-
*
|
|
7800
|
-
* An engine that cannot be reached or refuses the catalogue is logged and runs
|
|
7801
|
-
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7802
|
-
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7803
|
-
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7804
|
-
*
|
|
7805
|
-
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7806
|
-
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7807
|
-
* omitted applied ids "newer").
|
|
7820
|
+
* Several catalogues of ONE owner in one declaration (the engine replaces an owner's
|
|
7821
|
+
* catalogue on every declare, so two calls would each judge the other's ids "newer").
|
|
7822
|
+
* Build each entry with `migrationSet(catalogue, (api) => ports)`.
|
|
7808
7823
|
*/
|
|
7809
|
-
async
|
|
7824
|
+
async runMigrationSets(sets, options) {
|
|
7825
|
+
const api = this.ctx.migrationApi;
|
|
7826
|
+
if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
|
|
7827
|
+
return this.runBound(sets.flatMap((set) => set.bind(api)), options);
|
|
7828
|
+
}
|
|
7829
|
+
async runBound(migrations, options) {
|
|
7810
7830
|
const api = this.ctx.api.migrations;
|
|
7811
7831
|
const report = await runAddonMigrations({
|
|
7812
7832
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
@@ -13517,7 +13537,7 @@ var RecordIntegrationInstances = class {
|
|
|
13517
13537
|
return record === null ? null : this.viewOf(record);
|
|
13518
13538
|
};
|
|
13519
13539
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13520
|
-
createInstance = async ({ id, name, kind }) => {
|
|
13540
|
+
createInstance = async ({ id, name, kind, values }) => {
|
|
13521
13541
|
const minted = id ?? this.hooks.mintId?.({
|
|
13522
13542
|
name,
|
|
13523
13543
|
...kind !== void 0 ? { kind } : {}
|
|
@@ -13527,10 +13547,20 @@ var RecordIntegrationInstances = class {
|
|
|
13527
13547
|
id: minted,
|
|
13528
13548
|
data: this.hooks.initialBody(name, kind)
|
|
13529
13549
|
}, { ifRev: 0 });
|
|
13550
|
+
const patch = this.patchOf(values ?? {});
|
|
13551
|
+
if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
|
|
13530
13552
|
await this.hooks.onChanged?.(minted, "created");
|
|
13531
13553
|
return { id: minted };
|
|
13532
13554
|
};
|
|
13533
13555
|
updateInstance = async ({ id, name, values }) => {
|
|
13556
|
+
const patch = this.patchOf(values);
|
|
13557
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13558
|
+
await this.handle.patch(id, patch);
|
|
13559
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13560
|
+
return { ok: true };
|
|
13561
|
+
};
|
|
13562
|
+
/** The record patch a form `values` object means: secrets kept, cleared or replaced. */
|
|
13563
|
+
patchOf(values) {
|
|
13534
13564
|
const patch = {};
|
|
13535
13565
|
for (const [key, value] of Object.entries(values)) {
|
|
13536
13566
|
if (value === void 0) continue;
|
|
@@ -13541,11 +13571,8 @@ var RecordIntegrationInstances = class {
|
|
|
13541
13571
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13542
13572
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13543
13573
|
}
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
await this.hooks.onChanged?.(id, "updated");
|
|
13547
|
-
return { ok: true };
|
|
13548
|
-
};
|
|
13574
|
+
return patch;
|
|
13575
|
+
}
|
|
13549
13576
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13550
13577
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13551
13578
|
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
@@ -13582,6 +13609,76 @@ var RecordIntegrationInstances = class {
|
|
|
13582
13609
|
};
|
|
13583
13610
|
}
|
|
13584
13611
|
};
|
|
13612
|
+
/** No part holds the id a call named. */
|
|
13613
|
+
var UnknownInstanceError = class extends Error {
|
|
13614
|
+
instanceId;
|
|
13615
|
+
constructor(instanceId) {
|
|
13616
|
+
super(`Unknown instance '${instanceId}'`);
|
|
13617
|
+
this.instanceId = instanceId;
|
|
13618
|
+
this.name = "UnknownInstanceError";
|
|
13619
|
+
}
|
|
13620
|
+
};
|
|
13621
|
+
/** A create or a form named no kind, or a kind no part serves. */
|
|
13622
|
+
var UnknownInstanceKindError = class extends Error {
|
|
13623
|
+
kindId;
|
|
13624
|
+
constructor(kindId) {
|
|
13625
|
+
super(kindId === void 0 ? "This provider has several kinds: name one" : `Unknown instance kind '${kindId}'`);
|
|
13626
|
+
this.kindId = kindId;
|
|
13627
|
+
this.name = "UnknownInstanceKindError";
|
|
13628
|
+
}
|
|
13629
|
+
};
|
|
13630
|
+
function composeInstanceKinds(parts, base) {
|
|
13631
|
+
const byKind = (kindId) => {
|
|
13632
|
+
if (kindId === void 0) {
|
|
13633
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
13634
|
+
if (only === void 0) throw new UnknownInstanceKindError(void 0);
|
|
13635
|
+
return only;
|
|
13636
|
+
}
|
|
13637
|
+
const part = parts.find((candidate) => candidate.kind.id === kindId);
|
|
13638
|
+
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13639
|
+
return part;
|
|
13640
|
+
};
|
|
13641
|
+
const holderOf = async (id) => {
|
|
13642
|
+
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13643
|
+
throw new UnknownInstanceError(id);
|
|
13644
|
+
};
|
|
13645
|
+
const tagged = (part, instance) => ({
|
|
13646
|
+
...instance,
|
|
13647
|
+
kind: part.kind.id
|
|
13648
|
+
});
|
|
13649
|
+
return {
|
|
13650
|
+
describe: async () => ({
|
|
13651
|
+
...base,
|
|
13652
|
+
kinds: parts.map((part) => part.kind)
|
|
13653
|
+
}),
|
|
13654
|
+
instanceFormSchema: async ({ id, kind }) => {
|
|
13655
|
+
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13656
|
+
},
|
|
13657
|
+
listInstances: async () => {
|
|
13658
|
+
return (await Promise.all(parts.map(async (part) => (await part.provider.listInstances()).map((instance) => tagged(part, instance))))).flat();
|
|
13659
|
+
},
|
|
13660
|
+
getInstance: async ({ id }) => {
|
|
13661
|
+
for (const part of parts) {
|
|
13662
|
+
const found = await part.provider.getInstance({ id });
|
|
13663
|
+
if (found !== null) return tagged(part, found);
|
|
13664
|
+
}
|
|
13665
|
+
return null;
|
|
13666
|
+
},
|
|
13667
|
+
createInstance: async (input) => byKind(input.kind).provider.createInstance(input),
|
|
13668
|
+
updateInstance: async (input) => (await holderOf(input.id)).provider.updateInstance(input),
|
|
13669
|
+
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13670
|
+
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13671
|
+
testInstance: async (input) => {
|
|
13672
|
+
const test = (input.id === null ? byKind(input.kind) : await holderOf(input.id)).provider.testInstance;
|
|
13673
|
+
if (test === void 0) return {
|
|
13674
|
+
success: false,
|
|
13675
|
+
status: "inconclusive",
|
|
13676
|
+
error: "This kind cannot be tested"
|
|
13677
|
+
};
|
|
13678
|
+
return test(input);
|
|
13679
|
+
}
|
|
13680
|
+
};
|
|
13681
|
+
}
|
|
13585
13682
|
var EncodeProfileSchema = object({
|
|
13586
13683
|
video: object({
|
|
13587
13684
|
codec: _enum([
|
|
@@ -15796,6 +15893,39 @@ var UnstampedEventMediaCountSchema = object({
|
|
|
15796
15893
|
/** Sum across both, or `null` when either lane could not be counted. */
|
|
15797
15894
|
total: number().int().nonnegative().nullable()
|
|
15798
15895
|
}).nullable();
|
|
15896
|
+
/**
|
|
15897
|
+
* Where the stored event media sits, per storage location: how many media rows
|
|
15898
|
+
* and how many bytes carry each location id, how many carry none (legacy,
|
|
15899
|
+
* resolved to the default `eventMedia` location), and how many name a location
|
|
15900
|
+
* that was not asked about (a deleted or never-listed one).
|
|
15901
|
+
*
|
|
15902
|
+
* One covering-index aggregate per asked location plus the NULL one, and one
|
|
15903
|
+
* unfiltered total that scans the covering index: O(rows), not a walk of the
|
|
15904
|
+
* rows themselves. `unlisted` is total minus the ASKED locations minus NULL, so
|
|
15905
|
+
* it is a deleted location only when every location id was asked.
|
|
15906
|
+
* `null` is "could not be measured", never zero.
|
|
15907
|
+
*/
|
|
15908
|
+
var MediaLocationCensusInputSchema = object({ locationIds: array(string$2().min(1)).max(50).readonly() });
|
|
15909
|
+
var MediaLocationCensusSchema = object({
|
|
15910
|
+
locations: array(object({
|
|
15911
|
+
locationId: string$2(),
|
|
15912
|
+
rows: number().int().nonnegative(),
|
|
15913
|
+
bytes: number().nonnegative()
|
|
15914
|
+
})).readonly(),
|
|
15915
|
+
unstamped: object({
|
|
15916
|
+
rows: number().int().nonnegative(),
|
|
15917
|
+
bytes: number().nonnegative()
|
|
15918
|
+
}),
|
|
15919
|
+
/** Rows stamped with a location id that was not in the asked list. */
|
|
15920
|
+
unlisted: object({
|
|
15921
|
+
rows: number().int().nonnegative(),
|
|
15922
|
+
bytes: number().nonnegative()
|
|
15923
|
+
}),
|
|
15924
|
+
total: object({
|
|
15925
|
+
rows: number().int().nonnegative(),
|
|
15926
|
+
bytes: number().nonnegative()
|
|
15927
|
+
})
|
|
15928
|
+
});
|
|
15799
15929
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string$2().min(1) });
|
|
15800
15930
|
/** The independently selectable logical storage classes — every class
|
|
15801
15931
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -46832,52 +46962,7 @@ onBrightnessChanged: { data: object({
|
|
|
46832
46962
|
minIntervalMs: 0
|
|
46833
46963
|
} } }
|
|
46834
46964
|
};
|
|
46835
|
-
|
|
46836
|
-
* `broker` — unified pub/sub broker registry, system-scoped collection.
|
|
46837
|
-
*
|
|
46838
|
-
* The cap models any kind-tagged message broker the user wants to
|
|
46839
|
-
* register and that other addons might consume. The first two kinds
|
|
46840
|
-
* are MQTT (mosquitto / aedes embedded / cloud bridge) and
|
|
46841
|
-
* Home Assistant (HA WebSocket — subscribe_entities + call_service).
|
|
46842
|
-
* Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
|
|
46843
|
-
* the same surface.
|
|
46844
|
-
*
|
|
46845
|
-
* Why one cap, not one cap per kind:
|
|
46846
|
-
* - The integrations page wants a single table of "every broker"
|
|
46847
|
-
* across kinds; a unified cap drives it without join logic.
|
|
46848
|
-
* - Generic admin operations (add / remove / test / status) are
|
|
46849
|
-
* identical across kinds — duplicating them per cap was busywork.
|
|
46850
|
-
* - The `network-access` cap precedent: same interface, many
|
|
46851
|
-
* `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
|
|
46852
|
-
*
|
|
46853
|
-
* What stays kind-specific:
|
|
46854
|
-
* - `getBrokerConfig` payload (kind decides whether it returns
|
|
46855
|
-
* mqtt URL+creds, HA baseUrl+token, …) — typed as
|
|
46856
|
-
* `Record<string, unknown>` at the wire; consumers narrow.
|
|
46857
|
-
* - `publish` / `subscribe` / `getState` arguments (target / filter /
|
|
46858
|
-
* key) — also typed as `Record<string, unknown>` so each kind can
|
|
46859
|
-
* evolve its surface (MQTT topic+qos vs HA entity_id+domain)
|
|
46860
|
-
* without rebreaking the cap signature.
|
|
46861
|
-
* - `add` `settings` payload (different fields per kind, validated by
|
|
46862
|
-
* the kind-specific provider).
|
|
46863
|
-
*
|
|
46864
|
-
* Bidirectionality:
|
|
46865
|
-
* - `publish` is RPC — synchronous result (provider-defined).
|
|
46866
|
-
* - `subscribe` returns a subscription handle; the actual message
|
|
46867
|
-
* stream flows over the typed event-bus as `broker.message` events
|
|
46868
|
-
* keyed by `(brokerId, subscriptionId)`. Consumers filter in their
|
|
46869
|
-
* event handler. Subscriptions persist across reconnects — the
|
|
46870
|
-
* provider re-subscribes upstream after a transport drop.
|
|
46871
|
-
*
|
|
46872
|
-
* Why event-bus push (not a subscription RPC stream):
|
|
46873
|
-
* - Matches the rest of CamStack's D8 contract — events for telemetry,
|
|
46874
|
-
* RPC for loss-is-a-bug. Broker messages are telemetry (a single
|
|
46875
|
-
* drop is recoverable via `getState({key})`).
|
|
46876
|
-
* - Frees `broker.subscribe` from holding a long-lived RPC channel.
|
|
46877
|
-
* - Cross-process: events already route through `$event-bus`; no
|
|
46878
|
-
* per-broker wire-shaping needed.
|
|
46879
|
-
*/
|
|
46880
|
-
var BrokerStatusEnum = _enum([
|
|
46965
|
+
_enum([
|
|
46881
46966
|
"connected",
|
|
46882
46967
|
"disconnected",
|
|
46883
46968
|
"connecting",
|
|
@@ -46885,141 +46970,12 @@ var BrokerStatusEnum = _enum([
|
|
|
46885
46970
|
"unreachable",
|
|
46886
46971
|
"error"
|
|
46887
46972
|
]);
|
|
46888
|
-
var BrokerInfoSchema = object({
|
|
46889
|
-
/** Stable broker id. Persisted; survives addon restarts. */
|
|
46890
|
-
id: string$2(),
|
|
46891
|
-
/** Addon id of the provider that OWNS this broker.
|
|
46892
|
-
*
|
|
46893
|
-
* The `broker` cap is a system-scoped collection: several addons
|
|
46894
|
-
* register a `broker` provider, each owning a DISJOINT set of
|
|
46895
|
-
* brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
|
|
46896
|
-
* `ha_*`). A broker therefore belongs to exactly one addon — like a
|
|
46897
|
-
* device belongs to one integration. The admin UI threads this id
|
|
46898
|
-
* back as the `{ addonId }` system-collection selector on every
|
|
46899
|
-
* id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
|
|
46900
|
-
* `testConnection`), so the call routes to the OWNING provider
|
|
46901
|
-
* instead of defaulting to the first-registered one. */
|
|
46902
|
-
addonId: string$2(),
|
|
46903
|
-
/** Human-readable name (operator-chosen at add-time). */
|
|
46904
|
-
name: string$2(),
|
|
46905
|
-
/** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
|
|
46906
|
-
kind: string$2(),
|
|
46907
|
-
status: BrokerStatusEnum,
|
|
46908
|
-
/** Free-form provider-specific info (HA version, MQTT broker
|
|
46909
|
-
* flavour, latency, mTLS active, …). */
|
|
46910
|
-
info: record(string$2(), unknown()),
|
|
46911
|
-
/** Ms epoch of the last connection probe / status update. */
|
|
46912
|
-
lastCheckedAt: number().nullable(),
|
|
46913
|
-
/** Last error message, when `status` indicates failure. */
|
|
46914
|
-
error: string$2().nullable()
|
|
46915
|
-
});
|
|
46916
46973
|
var RegistryStatusSchema = object({
|
|
46917
46974
|
brokerCount: number().int().nonnegative(),
|
|
46918
46975
|
connectedCount: number().int().nonnegative()
|
|
46919
46976
|
});
|
|
46920
|
-
/**
|
|
46921
|
-
* One entry per `broker` provider — which addon provides which broker
|
|
46922
|
-
* kind(s). The unified create picker reads this to know, for each
|
|
46923
|
-
* (addonId, kind) pair the operator can pick, where to route the
|
|
46924
|
-
* follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
|
|
46925
|
-
* `{ addonId }` selector). A provider only advertises a kind here when
|
|
46926
|
-
* it implements `getSettingsSchema` for that kind (so the picker never
|
|
46927
|
-
* offers a kind whose creation form can't be rendered).
|
|
46928
|
-
*/
|
|
46929
|
-
var BrokerProviderInfoSchema = object({
|
|
46930
|
-
/** Addon id of the `broker` provider this entry describes. */
|
|
46931
|
-
addonId: string$2(),
|
|
46932
|
-
/** Broker kinds this provider can create, with a display label. */
|
|
46933
|
-
kinds: array(object({
|
|
46934
|
-
kind: string$2(),
|
|
46935
|
-
label: string$2(),
|
|
46936
|
-
/**
|
|
46937
|
-
* Relative hub path to start an interactive account link (OAuth popup).
|
|
46938
|
-
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
46939
|
-
*/
|
|
46940
|
-
interactiveConnectPath: string$2().min(1).optional(),
|
|
46941
|
-
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
46942
|
-
interactiveDisconnectPath: string$2().min(1).optional(),
|
|
46943
|
-
/**
|
|
46944
|
-
* The kind signs in through a server-held interactive session instead of
|
|
46945
|
-
* (or before) typed credentials. `basePath` is a hub-relative route set
|
|
46946
|
-
* served by the owning addon (`/addon/<id>/...`) speaking the sign-in
|
|
46947
|
-
* contract: `POST {basePath}/start` (body: the form's settings) answers
|
|
46948
|
-
* `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
|
|
46949
|
-
* `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
|
|
46950
|
-
* 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
|
|
46951
|
-
* (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
|
|
46952
|
-
* existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
|
|
46953
|
-
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
46954
|
-
* offers it as the default path; the fields the session fills are
|
|
46955
|
-
* marked `filledBySignIn` in the kind's settings schema.
|
|
46956
|
-
*
|
|
46957
|
-
* `methods` lists the ways the session can sign in, recommended first:
|
|
46958
|
-
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
46959
|
-
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
46960
|
-
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
46961
|
-
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
46962
|
-
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
46963
|
-
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
46964
|
-
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
46965
|
-
* `['proxy']`.
|
|
46966
|
-
*/
|
|
46967
|
-
signIn: object({
|
|
46968
|
-
basePath: string$2().min(1),
|
|
46969
|
-
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
46970
|
-
}).optional()
|
|
46971
|
-
}))
|
|
46972
|
-
});
|
|
46973
|
-
var ListInputSchema = object({
|
|
46974
|
-
/** Optional kind filter — `list({kind:'home-assistant'})` returns
|
|
46975
|
-
* only HA brokers. Omit to list every broker. */
|
|
46976
|
-
kind: string$2().optional() });
|
|
46977
46977
|
var GetInputSchema = object({ id: string$2() });
|
|
46978
|
-
var AddInputSchema = object({
|
|
46979
|
-
kind: string$2().min(1),
|
|
46980
|
-
name: string$2().min(1),
|
|
46981
|
-
/**
|
|
46982
|
-
* ADOPT an existing id instead of minting a new one.
|
|
46983
|
-
*
|
|
46984
|
-
* Written the day this cost an outage. A broker's id is not a detail: HA
|
|
46985
|
-
* devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
|
|
46986
|
-
* lost from config and re-added as `ha_001` leaves every one of its devices
|
|
46987
|
-
* bound to a broker that no longer exists. Re-entering the password under the
|
|
46988
|
-
* ORIGINAL id turns a multi-step device migration back into re-entering a
|
|
46989
|
-
* password.
|
|
46990
|
-
*
|
|
46991
|
-
* A provider MUST refuse an id that is already in use — adopting a live
|
|
46992
|
-
* broker's id would silently take it over.
|
|
46993
|
-
*/
|
|
46994
|
-
id: string$2().min(1).optional(),
|
|
46995
|
-
/** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
|
|
46996
|
-
* `{baseUrl,accessToken}`). Validated by the kind-specific provider
|
|
46997
|
-
* branch on receipt — invalid shape rejects the add. */
|
|
46998
|
-
settings: record(string$2(), unknown())
|
|
46999
|
-
});
|
|
47000
|
-
var AddResultSchema = object({ id: string$2() });
|
|
47001
|
-
var RemoveInputSchema = object({ id: string$2() });
|
|
47002
|
-
var TestConnectionResultSchema = discriminatedUnion("ok", [object({
|
|
47003
|
-
ok: literal(true),
|
|
47004
|
-
latencyMs: number().nonnegative()
|
|
47005
|
-
}), object({
|
|
47006
|
-
ok: literal(false),
|
|
47007
|
-
error: string$2()
|
|
47008
|
-
})]);
|
|
47009
46978
|
var SettingsRecordSchema$1 = record(string$2(), unknown());
|
|
47010
|
-
var SettingsSchemaInputSchema = object({ kind: string$2() });
|
|
47011
|
-
var TestSettingsInputSchema = object({
|
|
47012
|
-
kind: string$2(),
|
|
47013
|
-
settings: SettingsRecordSchema$1
|
|
47014
|
-
});
|
|
47015
|
-
var TestSettingsResultSchema = discriminatedUnion("ok", [object({
|
|
47016
|
-
ok: literal(true),
|
|
47017
|
-
latencyMs: number().nonnegative().optional()
|
|
47018
|
-
}).strict(), object({
|
|
47019
|
-
ok: literal(false),
|
|
47020
|
-
error: string$2()
|
|
47021
|
-
})]);
|
|
47022
|
-
var SettingsSchemaResultSchema = unknown().nullable();
|
|
47023
46979
|
var PublishInputSchema = object({
|
|
47024
46980
|
brokerId: string$2(),
|
|
47025
46981
|
/** Kind-specific routing target.
|
|
@@ -47063,51 +47019,12 @@ var brokerCapability = {
|
|
|
47063
47019
|
kind: "poll"
|
|
47064
47020
|
},
|
|
47065
47021
|
methods: {
|
|
47066
|
-
list: method(ListInputSchema, array(BrokerInfoSchema)),
|
|
47067
|
-
get: method(GetInputSchema, BrokerInfoSchema.nullable()),
|
|
47068
|
-
/** Enumerate which addon provides which broker kind(s) for the
|
|
47069
|
-
* unified create picker. The auto-mount fans this array across
|
|
47070
|
-
* every registered `broker` provider (array-output method), so the
|
|
47071
|
-
* picker sees every kind from every provider in one call. */
|
|
47072
|
-
listProviders: method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }),
|
|
47073
|
-
add: method(AddInputSchema, AddResultSchema, {
|
|
47074
|
-
kind: "mutation",
|
|
47075
|
-
auth: "admin"
|
|
47076
|
-
}),
|
|
47077
|
-
remove: method(RemoveInputSchema, _void(), {
|
|
47078
|
-
kind: "mutation",
|
|
47079
|
-
auth: "admin"
|
|
47080
|
-
}),
|
|
47081
|
-
testConnection: method(GetInputSchema, TestConnectionResultSchema, {
|
|
47082
|
-
kind: "mutation",
|
|
47083
|
-
auth: "admin"
|
|
47084
|
-
}),
|
|
47085
|
-
/** Read the persisted settings record for a broker (kind-specific
|
|
47086
|
-
* shape). Admin-only — settings may contain secrets. Returns `null`
|
|
47087
|
-
* when the broker id is unknown to the provider (the collection
|
|
47088
|
-
* fallback may route a foreign id to the first provider). */
|
|
47089
|
-
getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47090
|
-
/** Overwrite the persisted settings record. The kind-specific
|
|
47091
|
-
* provider validates the shape and applies the change (reconnects
|
|
47092
|
-
* if credentials changed). */
|
|
47093
|
-
setSettings: method(object({
|
|
47094
|
-
id: string$2(),
|
|
47095
|
-
settings: SettingsRecordSchema$1
|
|
47096
|
-
}), _void(), {
|
|
47097
|
-
kind: "mutation",
|
|
47098
|
-
auth: "admin"
|
|
47099
|
-
}),
|
|
47100
47022
|
/** Returns the kind-specific connection config the consumer needs
|
|
47101
47023
|
* to open its own client (MQTT pattern: `{url, username, password,
|
|
47102
47024
|
* clientIdPrefix}`). HA providers MAY return the auth envelope
|
|
47103
47025
|
* but typical HA consumers use `publish` / `subscribe` instead.
|
|
47104
47026
|
* Returns `null` when the broker id is unknown to the provider. */
|
|
47105
47027
|
getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47106
|
-
getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
|
|
47107
|
-
testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
47108
|
-
kind: "mutation",
|
|
47109
|
-
auth: "admin"
|
|
47110
|
-
}),
|
|
47111
47028
|
publish: method(PublishInputSchema, unknown(), {
|
|
47112
47029
|
kind: "mutation",
|
|
47113
47030
|
auth: "admin"
|
|
@@ -51954,7 +51871,14 @@ var DiscoveryCandidateSchema = object({
|
|
|
51954
51871
|
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
51955
51872
|
parentStableId: string$2().optional(),
|
|
51956
51873
|
/** `false` = shown for context but not pickable. Default true. */
|
|
51957
|
-
selectable: boolean().optional()
|
|
51874
|
+
selectable: boolean().optional(),
|
|
51875
|
+
/**
|
|
51876
|
+
* Keys of the provider's creation form (`getChildCreationSchema`) this candidate
|
|
51877
|
+
* cannot be adopted without — a key for an encrypted board, a pairing code. The
|
|
51878
|
+
* admin asks for exactly these, on the creation form prefilled with
|
|
51879
|
+
* `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
|
|
51880
|
+
*/
|
|
51881
|
+
needsParams: array(string$2()).optional()
|
|
51958
51882
|
});
|
|
51959
51883
|
/**
|
|
51960
51884
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -52081,8 +52005,14 @@ var deviceProviderCapability = {
|
|
|
52081
52005
|
* provider does not support manual creation. Lets the Add-Device dialog
|
|
52082
52006
|
* pick the right type instead of assuming Camera.
|
|
52083
52007
|
*/
|
|
52084
|
-
getManualCreationType: method(object({
|
|
52085
|
-
|
|
52008
|
+
getManualCreationType: method(object({
|
|
52009
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52010
|
+
integrationId: string$2().optional() }), object({ deviceType: _enum(DeviceType).nullable() })),
|
|
52011
|
+
adoptDiscoveredDevice: method(object({
|
|
52012
|
+
candidate: DiscoveryCandidateSchema,
|
|
52013
|
+
/** The integration the operator adopts into; a provider with several instances needs it. */
|
|
52014
|
+
integrationId: string$2().optional()
|
|
52015
|
+
}), DeviceSummarySchema, {
|
|
52086
52016
|
kind: "mutation",
|
|
52087
52017
|
auth: "admin"
|
|
52088
52018
|
}),
|
|
@@ -52093,10 +52023,16 @@ var deviceProviderCapability = {
|
|
|
52093
52023
|
* devices of that type. The output is a `ConfigUISchema` — the
|
|
52094
52024
|
* router type-asserts it at the boundary.
|
|
52095
52025
|
*/
|
|
52096
|
-
getChildCreationSchema: method(object({
|
|
52026
|
+
getChildCreationSchema: method(object({
|
|
52027
|
+
type: _enum(DeviceType),
|
|
52028
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52029
|
+
integrationId: string$2().optional()
|
|
52030
|
+
}), CreationSchemaOutputSchema),
|
|
52097
52031
|
createDevice: method(object({
|
|
52098
52032
|
type: _enum(DeviceType),
|
|
52099
|
-
config: record(string$2(), unknown())
|
|
52033
|
+
config: record(string$2(), unknown()),
|
|
52034
|
+
/** The integration the operator adds the device to; a provider with several instances needs it. */
|
|
52035
|
+
integrationId: string$2().optional()
|
|
52100
52036
|
}), DeviceSummarySchema, {
|
|
52101
52037
|
kind: "mutation",
|
|
52102
52038
|
auth: "admin"
|
|
@@ -52821,7 +52757,10 @@ method(object({
|
|
|
52821
52757
|
auth: "admin"
|
|
52822
52758
|
}), method(object({
|
|
52823
52759
|
addonId: string$2(),
|
|
52824
|
-
type: _enum(DeviceType)
|
|
52760
|
+
type: _enum(DeviceType),
|
|
52761
|
+
/** The integration the form is for; a provider whose integrations differ (a hub
|
|
52762
|
+
* integration takes no manual add) answers per integration. Absent = the addon's own. */
|
|
52763
|
+
integrationId: string$2().optional()
|
|
52825
52764
|
}), unknown().nullable()), method(object({
|
|
52826
52765
|
addonId: string$2(),
|
|
52827
52766
|
type: _enum(DeviceType),
|
|
@@ -52905,7 +52844,11 @@ method(object({
|
|
|
52905
52844
|
kind: "mutation",
|
|
52906
52845
|
auth: "admin",
|
|
52907
52846
|
timeoutMs: 12e4
|
|
52908
|
-
}), method(object({
|
|
52847
|
+
}), method(object({
|
|
52848
|
+
addonId: string$2(),
|
|
52849
|
+
/** The integration the Add-Device dialog is open on; absent = the addon's own. */
|
|
52850
|
+
integrationId: string$2().optional()
|
|
52851
|
+
}), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string$2() }), unknown(), { auth: "admin" }), method(object({
|
|
52909
52852
|
deviceId: number(),
|
|
52910
52853
|
key: string$2(),
|
|
52911
52854
|
value: unknown()
|
|
@@ -56185,7 +56128,7 @@ var TestConnectionStatusEnum = _enum([
|
|
|
56185
56128
|
"inconclusive",
|
|
56186
56129
|
"unsupported"
|
|
56187
56130
|
]);
|
|
56188
|
-
var TestConnectionResultSchema
|
|
56131
|
+
var TestConnectionResultSchema = object({
|
|
56189
56132
|
/** True ONLY for `validated`. Never true for a test that did not run. */
|
|
56190
56133
|
success: boolean(),
|
|
56191
56134
|
error: string$2().optional(),
|
|
@@ -56230,7 +56173,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
56230
56173
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56231
56174
|
addonId: string$2(),
|
|
56232
56175
|
settings: record(string$2(), unknown())
|
|
56233
|
-
}), TestConnectionResultSchema
|
|
56176
|
+
}), TestConnectionResultSchema, {
|
|
56234
56177
|
kind: "mutation",
|
|
56235
56178
|
auth: "admin"
|
|
56236
56179
|
});
|
|
@@ -56257,6 +56200,14 @@ var ConfigSchemaPassthrough$3 = custom();
|
|
|
56257
56200
|
var IntegrationInstanceIdSchema = string$2().min(1);
|
|
56258
56201
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
56259
56202
|
/**
|
|
56203
|
+
* What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
|
|
56204
|
+
* instance is an integration that brings devices (the registry owns its on/off switch);
|
|
56205
|
+
* `services`: a connection configured from the addon's own function page, never listed on
|
|
56206
|
+
* Integrations.
|
|
56207
|
+
* It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
|
|
56208
|
+
*/
|
|
56209
|
+
var IntegrationInstanceRoleSchema = _enum(["devices", "services"]);
|
|
56210
|
+
/**
|
|
56260
56211
|
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
56261
56212
|
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
56262
56213
|
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
@@ -56267,6 +56218,22 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56267
56218
|
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
56268
56219
|
id: string$2().min(1),
|
|
56269
56220
|
label: string$2().min(1),
|
|
56221
|
+
/**
|
|
56222
|
+
* The function capability this kind is configured for (`broker`, `smtp-provider`,
|
|
56223
|
+
* `device-provider`, ...). A page section is the kinds whose `functionCap` it names (D1013); a
|
|
56224
|
+
* provider with more than one section must name it on every kind.
|
|
56225
|
+
*/
|
|
56226
|
+
functionCap: string$2().min(1),
|
|
56227
|
+
/**
|
|
56228
|
+
* For `functionCap: 'broker'`: the registry kind of the broker an instance of this kind is
|
|
56229
|
+
* (`matter`, `home-assistant`, ...). A caller that needs a broker of one kind (a link field)
|
|
56230
|
+
* finds the instances whose kind names it; absent takes the kind `id`.
|
|
56231
|
+
*/
|
|
56232
|
+
brokerKind: string$2().min(1).optional(),
|
|
56233
|
+
/** What instances of this kind are for the Integrations page; absent takes the provider's `role`. */
|
|
56234
|
+
role: IntegrationInstanceRoleSchema.optional(),
|
|
56235
|
+
/** `testInstance` really probes the remote for this kind; absent takes the provider's `canTest`. */
|
|
56236
|
+
canTest: boolean().optional(),
|
|
56270
56237
|
/** The kind signs in through a server-held interactive session. */
|
|
56271
56238
|
signIn: object({
|
|
56272
56239
|
basePath: string$2().min(1),
|
|
@@ -56279,11 +56246,7 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56279
56246
|
});
|
|
56280
56247
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56281
56248
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56282
|
-
role:
|
|
56283
|
-
"devices",
|
|
56284
|
-
"services",
|
|
56285
|
-
"both"
|
|
56286
|
-
]),
|
|
56249
|
+
role: IntegrationInstanceRoleSchema,
|
|
56287
56250
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56288
56251
|
discovery: object({
|
|
56289
56252
|
mode: _enum([
|
|
@@ -56358,7 +56321,11 @@ var integrationInstancesCapability = {
|
|
|
56358
56321
|
methods: {
|
|
56359
56322
|
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
56360
56323
|
/** The form; `id: null` is the create form. */
|
|
56361
|
-
instanceFormSchema: method(object({
|
|
56324
|
+
instanceFormSchema: method(object({
|
|
56325
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
56326
|
+
/** The kind of the create form (`id: null`) of a provider with several. */
|
|
56327
|
+
kind: string$2().min(1).optional()
|
|
56328
|
+
}), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
56362
56329
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
56363
56330
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
56364
56331
|
/**
|
|
@@ -56368,7 +56335,9 @@ var integrationInstancesCapability = {
|
|
|
56368
56335
|
createInstance: method(object({
|
|
56369
56336
|
id: IntegrationInstanceIdSchema.optional(),
|
|
56370
56337
|
name: string$2().min(1),
|
|
56371
|
-
kind: string$2().min(1).optional()
|
|
56338
|
+
kind: string$2().min(1).optional(),
|
|
56339
|
+
/** Form values written with the record (a link picked in the create dialog). */
|
|
56340
|
+
values: record(string$2(), unknown()).optional()
|
|
56372
56341
|
}), object({ id: string$2() }), {
|
|
56373
56342
|
kind: "mutation",
|
|
56374
56343
|
auth: "admin"
|
|
@@ -56395,8 +56364,10 @@ var integrationInstancesCapability = {
|
|
|
56395
56364
|
}),
|
|
56396
56365
|
testInstance: optionalMethod(object({
|
|
56397
56366
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
56398
|
-
values: record(string$2(), unknown())
|
|
56399
|
-
|
|
56367
|
+
values: record(string$2(), unknown()),
|
|
56368
|
+
/** The kind to test when `id` is null and the provider has several. */
|
|
56369
|
+
kind: string$2().min(1).optional()
|
|
56370
|
+
}), TestConnectionResultSchema, {
|
|
56400
56371
|
kind: "mutation",
|
|
56401
56372
|
auth: "admin"
|
|
56402
56373
|
})
|
|
@@ -59153,6 +59124,380 @@ var SystemMetricsSchema = object({
|
|
|
59153
59124
|
gpuMemoryPercent: number().optional()
|
|
59154
59125
|
});
|
|
59155
59126
|
method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string$2() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string$2() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
|
|
59127
|
+
/**
|
|
59128
|
+
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
59129
|
+
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
59130
|
+
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
59131
|
+
*
|
|
59132
|
+
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
59133
|
+
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
59134
|
+
* that with the addon id closed over). It is deliberately NOT in any input
|
|
59135
|
+
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
59136
|
+
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
59137
|
+
* addons go through the owner's capability.
|
|
59138
|
+
*
|
|
59139
|
+
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
59140
|
+
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
59141
|
+
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
59142
|
+
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
59143
|
+
* row's own ref.
|
|
59144
|
+
*
|
|
59145
|
+
* Internal: addons reach it through `ctx.records`, never directly.
|
|
59146
|
+
*/
|
|
59147
|
+
var RecordIdSchema = string$2().min(1).max(256);
|
|
59148
|
+
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
59149
|
+
var RecordBodySchema = record(string$2(), unknown());
|
|
59150
|
+
var RecordInputSchema = object({
|
|
59151
|
+
id: RecordIdSchema,
|
|
59152
|
+
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
59153
|
+
deviceId: number().int().optional(),
|
|
59154
|
+
/** The domain clock, ms: observed / created / opened. */
|
|
59155
|
+
at: number().int().optional(),
|
|
59156
|
+
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
59157
|
+
expiresAt: number().int().optional(),
|
|
59158
|
+
data: RecordBodySchema
|
|
59159
|
+
});
|
|
59160
|
+
var RecordViewSchema = object({
|
|
59161
|
+
id: string$2(),
|
|
59162
|
+
deviceId: number().int().nullable(),
|
|
59163
|
+
at: number().nullable(),
|
|
59164
|
+
expiresAt: number().nullable(),
|
|
59165
|
+
rev: number().int(),
|
|
59166
|
+
updatedAt: number(),
|
|
59167
|
+
data: RecordBodySchema
|
|
59168
|
+
});
|
|
59169
|
+
/** Declared index fields only. Anything else does not compile, by design. */
|
|
59170
|
+
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
59171
|
+
string$2(),
|
|
59172
|
+
number(),
|
|
59173
|
+
boolean(),
|
|
59174
|
+
object({ in: array(union([
|
|
59175
|
+
string$2(),
|
|
59176
|
+
number(),
|
|
59177
|
+
boolean()
|
|
59178
|
+
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
59179
|
+
object({
|
|
59180
|
+
gt: union([string$2(), number()]).optional(),
|
|
59181
|
+
gte: union([string$2(), number()]).optional(),
|
|
59182
|
+
lt: union([string$2(), number()]).optional(),
|
|
59183
|
+
lte: union([string$2(), number()]).optional()
|
|
59184
|
+
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
59185
|
+
]));
|
|
59186
|
+
var KindShape = { kind: RecordKindNameSchema };
|
|
59187
|
+
var TimeWindowShape = {
|
|
59188
|
+
deviceId: number().int().optional(),
|
|
59189
|
+
atFrom: number().optional(),
|
|
59190
|
+
atTo: number().optional()
|
|
59191
|
+
};
|
|
59192
|
+
var RecordQueryInputSchema = object({
|
|
59193
|
+
...KindShape,
|
|
59194
|
+
...TimeWindowShape,
|
|
59195
|
+
where: RecordWhereSchema.optional(),
|
|
59196
|
+
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
59197
|
+
order: _enum(["at", "id"]).optional(),
|
|
59198
|
+
desc: boolean().optional(),
|
|
59199
|
+
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
59200
|
+
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
59201
|
+
cursor: string$2().min(1).optional()
|
|
59202
|
+
});
|
|
59203
|
+
var RecordQueryResultSchema = object({
|
|
59204
|
+
records: array(RecordViewSchema),
|
|
59205
|
+
nextCursor: string$2().nullable()
|
|
59206
|
+
});
|
|
59207
|
+
var RecordKindStateSchema = _enum([
|
|
59208
|
+
"created",
|
|
59209
|
+
"unchanged",
|
|
59210
|
+
"updated"
|
|
59211
|
+
]);
|
|
59212
|
+
var DeclareKindResultSchema = object({
|
|
59213
|
+
kindId: number().int(),
|
|
59214
|
+
state: RecordKindStateSchema,
|
|
59215
|
+
createdIndexes: array(string$2())
|
|
59216
|
+
});
|
|
59217
|
+
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
59218
|
+
var SecretRefsList = array(string$2());
|
|
59219
|
+
/**
|
|
59220
|
+
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
59221
|
+
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
59222
|
+
*/
|
|
59223
|
+
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
59224
|
+
object({
|
|
59225
|
+
op: literal("put"),
|
|
59226
|
+
kind: RecordKindNameSchema,
|
|
59227
|
+
record: RecordInputSchema,
|
|
59228
|
+
ifRev: number().int().min(0).optional()
|
|
59229
|
+
}),
|
|
59230
|
+
object({
|
|
59231
|
+
op: literal("patch"),
|
|
59232
|
+
kind: RecordKindNameSchema,
|
|
59233
|
+
id: RecordIdSchema,
|
|
59234
|
+
data: RecordBodySchema,
|
|
59235
|
+
at: number().int().nullable().optional(),
|
|
59236
|
+
expiresAt: number().int().nullable().optional(),
|
|
59237
|
+
ifRev: number().int().min(1).optional()
|
|
59238
|
+
}),
|
|
59239
|
+
object({
|
|
59240
|
+
op: literal("delete"),
|
|
59241
|
+
kind: RecordKindNameSchema,
|
|
59242
|
+
id: RecordIdSchema
|
|
59243
|
+
})
|
|
59244
|
+
]);
|
|
59245
|
+
var RecordSecretRefsSchema = record(string$2(), record(string$2(), string$2()));
|
|
59246
|
+
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string$2(), DeclareKindResultSchema) }), {
|
|
59247
|
+
kind: "mutation",
|
|
59248
|
+
auth: "admin"
|
|
59249
|
+
}), method(object({
|
|
59250
|
+
...KindShape,
|
|
59251
|
+
id: RecordIdSchema
|
|
59252
|
+
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
59253
|
+
...KindShape,
|
|
59254
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59255
|
+
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
59256
|
+
...KindShape,
|
|
59257
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59258
|
+
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
59259
|
+
...KindShape,
|
|
59260
|
+
record: RecordInputSchema,
|
|
59261
|
+
/** Compare-and-set. `0` means "the row must not exist". */
|
|
59262
|
+
ifRev: number().int().min(0).optional()
|
|
59263
|
+
}), object({
|
|
59264
|
+
rev: number().int(),
|
|
59265
|
+
releasedRefs: SecretRefsList
|
|
59266
|
+
}), {
|
|
59267
|
+
kind: "mutation",
|
|
59268
|
+
auth: "admin"
|
|
59269
|
+
}), method(object({
|
|
59270
|
+
...KindShape,
|
|
59271
|
+
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59272
|
+
}), object({
|
|
59273
|
+
count: number().int(),
|
|
59274
|
+
releasedRefs: SecretRefsList
|
|
59275
|
+
}), {
|
|
59276
|
+
kind: "mutation",
|
|
59277
|
+
auth: "admin"
|
|
59278
|
+
}), method(object({
|
|
59279
|
+
...KindShape,
|
|
59280
|
+
id: RecordIdSchema,
|
|
59281
|
+
data: RecordBodySchema,
|
|
59282
|
+
at: number().int().nullable().optional(),
|
|
59283
|
+
expiresAt: number().int().nullable().optional(),
|
|
59284
|
+
ifRev: number().int().min(1).optional()
|
|
59285
|
+
}), object({
|
|
59286
|
+
rev: number().int(),
|
|
59287
|
+
releasedRefs: SecretRefsList
|
|
59288
|
+
}), {
|
|
59289
|
+
kind: "mutation",
|
|
59290
|
+
auth: "admin"
|
|
59291
|
+
}), method(object({
|
|
59292
|
+
...KindShape,
|
|
59293
|
+
id: RecordIdSchema
|
|
59294
|
+
}), object({
|
|
59295
|
+
deleted: boolean(),
|
|
59296
|
+
secretRefs: SecretRefsList
|
|
59297
|
+
}), {
|
|
59298
|
+
kind: "mutation",
|
|
59299
|
+
auth: "admin"
|
|
59300
|
+
}), method(object({
|
|
59301
|
+
...KindShape,
|
|
59302
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59303
|
+
}), object({
|
|
59304
|
+
deleted: number().int(),
|
|
59305
|
+
secretRefs: SecretRefsList
|
|
59306
|
+
}), {
|
|
59307
|
+
kind: "mutation",
|
|
59308
|
+
auth: "admin"
|
|
59309
|
+
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
59310
|
+
...KindShape,
|
|
59311
|
+
...TimeWindowShape,
|
|
59312
|
+
where: RecordWhereSchema.optional()
|
|
59313
|
+
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
59314
|
+
...KindShape,
|
|
59315
|
+
deviceId: number().int().optional(),
|
|
59316
|
+
from: number(),
|
|
59317
|
+
to: number(),
|
|
59318
|
+
bucketMs: number().int().positive()
|
|
59319
|
+
}), object({ buckets: array(object({
|
|
59320
|
+
bucketStart: number(),
|
|
59321
|
+
count: number().int()
|
|
59322
|
+
})) }), { auth: "admin" }), method(object({
|
|
59323
|
+
...KindShape,
|
|
59324
|
+
op: _enum([
|
|
59325
|
+
"sum",
|
|
59326
|
+
"min",
|
|
59327
|
+
"max",
|
|
59328
|
+
"count"
|
|
59329
|
+
]),
|
|
59330
|
+
field: RecordFieldPathSchema.optional(),
|
|
59331
|
+
groupBy: RecordFieldPathSchema.optional(),
|
|
59332
|
+
where: RecordWhereSchema.optional()
|
|
59333
|
+
}), object({ rows: array(object({
|
|
59334
|
+
group: union([
|
|
59335
|
+
string$2(),
|
|
59336
|
+
number(),
|
|
59337
|
+
_null()
|
|
59338
|
+
]),
|
|
59339
|
+
value: number().nullable()
|
|
59340
|
+
})) }), { auth: "admin" }), method(object({
|
|
59341
|
+
...KindShape,
|
|
59342
|
+
deviceId: number().int().optional(),
|
|
59343
|
+
atBefore: number().optional(),
|
|
59344
|
+
where: RecordWhereSchema.optional()
|
|
59345
|
+
}), object({
|
|
59346
|
+
deleted: number().int(),
|
|
59347
|
+
secretRefs: SecretRefsList
|
|
59348
|
+
}), {
|
|
59349
|
+
kind: "mutation",
|
|
59350
|
+
auth: "admin"
|
|
59351
|
+
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
59352
|
+
results: array(object({
|
|
59353
|
+
rev: number().int().nullable(),
|
|
59354
|
+
deleted: boolean()
|
|
59355
|
+
})),
|
|
59356
|
+
releasedRefs: SecretRefsList,
|
|
59357
|
+
secretRefs: SecretRefsList
|
|
59358
|
+
}), {
|
|
59359
|
+
kind: "mutation",
|
|
59360
|
+
auth: "admin"
|
|
59361
|
+
}), method(object({
|
|
59362
|
+
deviceId: number().int(),
|
|
59363
|
+
kinds: array(RecordKindNameSchema).optional()
|
|
59364
|
+
}), object({
|
|
59365
|
+
deleted: record(string$2(), number().int()),
|
|
59366
|
+
secretRefs: SecretRefsList
|
|
59367
|
+
}), {
|
|
59368
|
+
kind: "mutation",
|
|
59369
|
+
auth: "admin"
|
|
59370
|
+
}), method(object(KindShape), object({ id: number().int() }), {
|
|
59371
|
+
kind: "mutation",
|
|
59372
|
+
auth: "admin"
|
|
59373
|
+
});
|
|
59374
|
+
/**
|
|
59375
|
+
* The wire shapes of the migration API, version 1 (planning#447).
|
|
59376
|
+
*
|
|
59377
|
+
* A migration is code that outlives the server it was written against: an addon at
|
|
59378
|
+
* version 1.4 that is brought to 1.9 in one jump still runs its 1.4 catalogue, against
|
|
59379
|
+
* the 1.9 kernel. The only thing that code may call is {@link MigrationApiV1}; this file
|
|
59380
|
+
* is the contract of every method of it, one Zod pair per method.
|
|
59381
|
+
*
|
|
59382
|
+
* APPEND-ONLY. A released method is never removed and never changes meaning: an input
|
|
59383
|
+
* field is never removed or made required, an enum never narrows, an output field is
|
|
59384
|
+
* never removed. A change that cannot be expressed that way is `migration-api.v2`, next
|
|
59385
|
+
* to this file. `scripts/check-migration-api-append-only.ts` holds the line against
|
|
59386
|
+
* `scripts/migration-api-contract.json`, and every `behaviours` id below must have an
|
|
59387
|
+
* `it('<id>'` in `migration-api.contract.spec.ts`.
|
|
59388
|
+
*/
|
|
59389
|
+
var Rows = record(string$2(), unknown());
|
|
59390
|
+
var IntegrationId = object({ integrationId: string$2().min(1) });
|
|
59391
|
+
var RecordId = object({ id: string$2().min(1) });
|
|
59392
|
+
var RecordRowSchema = object({
|
|
59393
|
+
id: string$2(),
|
|
59394
|
+
deviceId: number().int().nullable(),
|
|
59395
|
+
at: number().nullable(),
|
|
59396
|
+
expiresAt: number().nullable(),
|
|
59397
|
+
rev: number().int(),
|
|
59398
|
+
updatedAt: number(),
|
|
59399
|
+
data: Rows
|
|
59400
|
+
});
|
|
59401
|
+
var RecordPutShape = {
|
|
59402
|
+
id: string$2().min(1),
|
|
59403
|
+
deviceId: number().int().optional(),
|
|
59404
|
+
at: number().int().optional(),
|
|
59405
|
+
expiresAt: number().int().optional(),
|
|
59406
|
+
data: Rows
|
|
59407
|
+
};
|
|
59408
|
+
var MigrationIntegrationSchema = object({
|
|
59409
|
+
id: string$2(),
|
|
59410
|
+
addonId: string$2(),
|
|
59411
|
+
name: string$2(),
|
|
59412
|
+
info: Rows,
|
|
59413
|
+
originStableId: string$2().optional()
|
|
59414
|
+
});
|
|
59415
|
+
var MigrationDeviceSchema = object({
|
|
59416
|
+
id: number().int(),
|
|
59417
|
+
integrationId: string$2().nullable().optional(),
|
|
59418
|
+
/** The owning device; null or absent for a root. */
|
|
59419
|
+
parentDeviceId: number().int().nullable().optional(),
|
|
59420
|
+
type: string$2().optional(),
|
|
59421
|
+
name: string$2().optional(),
|
|
59422
|
+
stableId: string$2().optional()
|
|
59423
|
+
});
|
|
59424
|
+
/** What the engine says about the migration API it serves: lets an addon notice an older server. */
|
|
59425
|
+
var MigrationApiInfoSchema = object({
|
|
59426
|
+
version: number().int(),
|
|
59427
|
+
methods: array(string$2())
|
|
59428
|
+
});
|
|
59429
|
+
object({
|
|
59430
|
+
message: string$2(),
|
|
59431
|
+
meta: Rows.optional(),
|
|
59432
|
+
/** Grouping tags of the line; a line about a device carries `{ deviceId }`. */
|
|
59433
|
+
tags: object({ deviceId: number().int().optional() }).optional()
|
|
59434
|
+
});
|
|
59435
|
+
object({
|
|
59436
|
+
owner: string$2().min(1),
|
|
59437
|
+
metas: array(MigrationMetaSchema)
|
|
59438
|
+
}), object({
|
|
59439
|
+
bootId: string$2(),
|
|
59440
|
+
snapshot: _enum([
|
|
59441
|
+
"none",
|
|
59442
|
+
"written",
|
|
59443
|
+
"refused"
|
|
59444
|
+
]),
|
|
59445
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59446
|
+
/** The migration API the engine serves; absent from a server that predates it. */
|
|
59447
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59448
|
+
}), object({ id: string$2().min(1) }), object({
|
|
59449
|
+
proceed: boolean(),
|
|
59450
|
+
state: union([literal("pending"), MigrationStatusSchema]),
|
|
59451
|
+
reason: string$2().nullable()
|
|
59452
|
+
}), object({
|
|
59453
|
+
id: string$2().min(1),
|
|
59454
|
+
result: MigrationOwnerResultSchema
|
|
59455
|
+
}), object({
|
|
59456
|
+
status: MigrationStatusSchema,
|
|
59457
|
+
error: string$2().nullable(),
|
|
59458
|
+
rowsChanged: number().int().nonnegative()
|
|
59459
|
+
}), object({
|
|
59460
|
+
rows: number().int().nonnegative(),
|
|
59461
|
+
secrets: number().int().nonnegative()
|
|
59462
|
+
}), IntegrationId.extend({ patch: Rows }), object({ written: boolean() }), IntegrationId.extend({ keys: array(string$2().min(1)) }), object({
|
|
59463
|
+
present: boolean(),
|
|
59464
|
+
removed: Rows
|
|
59465
|
+
}), RecordRowSchema.nullable(), RecordRowSchema.nullable(), object({
|
|
59466
|
+
...RecordPutShape,
|
|
59467
|
+
ifRev: number().int().optional()
|
|
59468
|
+
}), object({ rev: number().int() }), object({ records: array(object(RecordPutShape)) }), object({ count: number().int().nonnegative() }), RecordId.extend({
|
|
59469
|
+
partial: Rows,
|
|
59470
|
+
ifRev: number().int().optional()
|
|
59471
|
+
}), object({ rev: number().int() }), object({ deleted: boolean() }), object({
|
|
59472
|
+
deviceId: number().int().optional(),
|
|
59473
|
+
atFrom: number().optional(),
|
|
59474
|
+
atTo: number().optional(),
|
|
59475
|
+
where: RecordWhereSchema.optional(),
|
|
59476
|
+
limit: number().int().min(1).optional(),
|
|
59477
|
+
cursor: string$2().min(1).optional(),
|
|
59478
|
+
resolveSecrets: boolean().optional()
|
|
59479
|
+
}), object({
|
|
59480
|
+
records: array(RecordRowSchema),
|
|
59481
|
+
nextCursor: string$2().nullable()
|
|
59482
|
+
}), object({
|
|
59483
|
+
deviceId: number().int().optional(),
|
|
59484
|
+
atBefore: number().optional(),
|
|
59485
|
+
where: RecordWhereSchema.optional()
|
|
59486
|
+
}), object({ count: number().int().nonnegative() }), object({}), object({ store: Rows }), object({ patch: Rows }), object({}), object({ keys: array(string$2().min(1)) }), object({ removed: array(string$2()) }), object({ refs: array(string$2()) }), object({ values: record(string$2(), string$2().nullable()) }), object({
|
|
59487
|
+
ref: string$2().min(1),
|
|
59488
|
+
value: string$2()
|
|
59489
|
+
}), object({}), object({ refs: array(string$2()) }), object({ deleted: number().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string$2().min(1) }), MigrationIntegrationSchema.nullable(), object({
|
|
59490
|
+
addonId: string$2().min(1),
|
|
59491
|
+
integrationId: string$2().min(1).optional()
|
|
59492
|
+
}), object({ devices: array(MigrationDeviceSchema) }), object({ deviceId: number().int() }), object({
|
|
59493
|
+
id: number().int(),
|
|
59494
|
+
integrationId: string$2().nullable(),
|
|
59495
|
+
/** Whether the device carries any stored config; its content is not offered. */
|
|
59496
|
+
hasConfig: boolean()
|
|
59497
|
+
}).nullable(), object({
|
|
59498
|
+
deviceId: number().int(),
|
|
59499
|
+
integrationId: string$2().min(1)
|
|
59500
|
+
}), object({}), object({}), object({ nodeId: string$2() }), object({}), object({});
|
|
59156
59501
|
method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string$2().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string$2().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
|
|
59157
59502
|
id: string$2().min(1),
|
|
59158
59503
|
error: string$2().min(1)
|
|
@@ -59182,7 +59527,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59182
59527
|
"written",
|
|
59183
59528
|
"refused"
|
|
59184
59529
|
]),
|
|
59185
|
-
steps: array(MigrationOwnerStepSchema)
|
|
59530
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59531
|
+
/** The migration API this engine serves (additive: a server that predates it omits it). */
|
|
59532
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59186
59533
|
}), {
|
|
59187
59534
|
kind: "mutation",
|
|
59188
59535
|
auth: "admin"
|
|
@@ -61849,7 +62196,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
61849
62196
|
}), method(RelocateMediaInputSchema, object({ jobId: string$2() }), {
|
|
61850
62197
|
kind: "mutation",
|
|
61851
62198
|
auth: "admin"
|
|
61852
|
-
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(
|
|
62199
|
+
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(MediaLocationCensusInputSchema, MediaLocationCensusSchema, {
|
|
62200
|
+
kind: "query",
|
|
62201
|
+
auth: "admin"
|
|
62202
|
+
}), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
|
|
61853
62203
|
kind: "query",
|
|
61854
62204
|
auth: "admin"
|
|
61855
62205
|
}), method(object({}), array(RelocateJobSchema).readonly(), {
|
|
@@ -64504,7 +64854,7 @@ var BrokerStatusSchema$1 = _enum([
|
|
|
64504
64854
|
"unreachable",
|
|
64505
64855
|
"tls-error"
|
|
64506
64856
|
]);
|
|
64507
|
-
var BrokerInfoSchema
|
|
64857
|
+
var BrokerInfoSchema = object({
|
|
64508
64858
|
id: string$2(),
|
|
64509
64859
|
name: string$2(),
|
|
64510
64860
|
url: string$2(),
|
|
@@ -64567,7 +64917,7 @@ var StatusSchema = object({
|
|
|
64567
64917
|
brokerCount: number(),
|
|
64568
64918
|
embeddedRunning: boolean()
|
|
64569
64919
|
});
|
|
64570
|
-
method(_void(), array(BrokerInfoSchema
|
|
64920
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
64571
64921
|
kind: "mutation",
|
|
64572
64922
|
auth: "admin"
|
|
64573
64923
|
}), method(IdInputSchema, _void(), {
|
|
@@ -71749,253 +72099,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
|
|
|
71749
72099
|
auth: "admin"
|
|
71750
72100
|
});
|
|
71751
72101
|
/**
|
|
71752
|
-
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
71753
|
-
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
71754
|
-
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
71755
|
-
*
|
|
71756
|
-
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
71757
|
-
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
71758
|
-
* that with the addon id closed over). It is deliberately NOT in any input
|
|
71759
|
-
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
71760
|
-
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
71761
|
-
* addons go through the owner's capability.
|
|
71762
|
-
*
|
|
71763
|
-
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
71764
|
-
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
71765
|
-
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
71766
|
-
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
71767
|
-
* row's own ref.
|
|
71768
|
-
*
|
|
71769
|
-
* Internal: addons reach it through `ctx.records`, never directly.
|
|
71770
|
-
*/
|
|
71771
|
-
var RecordIdSchema = string$2().min(1).max(256);
|
|
71772
|
-
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
71773
|
-
var RecordBodySchema = record(string$2(), unknown());
|
|
71774
|
-
var RecordInputSchema = object({
|
|
71775
|
-
id: RecordIdSchema,
|
|
71776
|
-
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
71777
|
-
deviceId: number().int().optional(),
|
|
71778
|
-
/** The domain clock, ms: observed / created / opened. */
|
|
71779
|
-
at: number().int().optional(),
|
|
71780
|
-
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
71781
|
-
expiresAt: number().int().optional(),
|
|
71782
|
-
data: RecordBodySchema
|
|
71783
|
-
});
|
|
71784
|
-
var RecordViewSchema = object({
|
|
71785
|
-
id: string$2(),
|
|
71786
|
-
deviceId: number().int().nullable(),
|
|
71787
|
-
at: number().nullable(),
|
|
71788
|
-
expiresAt: number().nullable(),
|
|
71789
|
-
rev: number().int(),
|
|
71790
|
-
updatedAt: number(),
|
|
71791
|
-
data: RecordBodySchema
|
|
71792
|
-
});
|
|
71793
|
-
/** Declared index fields only. Anything else does not compile, by design. */
|
|
71794
|
-
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
71795
|
-
string$2(),
|
|
71796
|
-
number(),
|
|
71797
|
-
boolean(),
|
|
71798
|
-
object({ in: array(union([
|
|
71799
|
-
string$2(),
|
|
71800
|
-
number(),
|
|
71801
|
-
boolean()
|
|
71802
|
-
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
71803
|
-
object({
|
|
71804
|
-
gt: union([string$2(), number()]).optional(),
|
|
71805
|
-
gte: union([string$2(), number()]).optional(),
|
|
71806
|
-
lt: union([string$2(), number()]).optional(),
|
|
71807
|
-
lte: union([string$2(), number()]).optional()
|
|
71808
|
-
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
71809
|
-
]));
|
|
71810
|
-
var KindShape = { kind: RecordKindNameSchema };
|
|
71811
|
-
var TimeWindowShape = {
|
|
71812
|
-
deviceId: number().int().optional(),
|
|
71813
|
-
atFrom: number().optional(),
|
|
71814
|
-
atTo: number().optional()
|
|
71815
|
-
};
|
|
71816
|
-
var RecordQueryInputSchema = object({
|
|
71817
|
-
...KindShape,
|
|
71818
|
-
...TimeWindowShape,
|
|
71819
|
-
where: RecordWhereSchema.optional(),
|
|
71820
|
-
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
71821
|
-
order: _enum(["at", "id"]).optional(),
|
|
71822
|
-
desc: boolean().optional(),
|
|
71823
|
-
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
71824
|
-
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
71825
|
-
cursor: string$2().min(1).optional()
|
|
71826
|
-
});
|
|
71827
|
-
var RecordQueryResultSchema = object({
|
|
71828
|
-
records: array(RecordViewSchema),
|
|
71829
|
-
nextCursor: string$2().nullable()
|
|
71830
|
-
});
|
|
71831
|
-
var RecordKindStateSchema = _enum([
|
|
71832
|
-
"created",
|
|
71833
|
-
"unchanged",
|
|
71834
|
-
"updated"
|
|
71835
|
-
]);
|
|
71836
|
-
var DeclareKindResultSchema = object({
|
|
71837
|
-
kindId: number().int(),
|
|
71838
|
-
state: RecordKindStateSchema,
|
|
71839
|
-
createdIndexes: array(string$2())
|
|
71840
|
-
});
|
|
71841
|
-
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
71842
|
-
var SecretRefsList = array(string$2());
|
|
71843
|
-
/**
|
|
71844
|
-
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
71845
|
-
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
71846
|
-
*/
|
|
71847
|
-
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
71848
|
-
object({
|
|
71849
|
-
op: literal("put"),
|
|
71850
|
-
kind: RecordKindNameSchema,
|
|
71851
|
-
record: RecordInputSchema,
|
|
71852
|
-
ifRev: number().int().min(0).optional()
|
|
71853
|
-
}),
|
|
71854
|
-
object({
|
|
71855
|
-
op: literal("patch"),
|
|
71856
|
-
kind: RecordKindNameSchema,
|
|
71857
|
-
id: RecordIdSchema,
|
|
71858
|
-
data: RecordBodySchema,
|
|
71859
|
-
at: number().int().nullable().optional(),
|
|
71860
|
-
expiresAt: number().int().nullable().optional(),
|
|
71861
|
-
ifRev: number().int().min(1).optional()
|
|
71862
|
-
}),
|
|
71863
|
-
object({
|
|
71864
|
-
op: literal("delete"),
|
|
71865
|
-
kind: RecordKindNameSchema,
|
|
71866
|
-
id: RecordIdSchema
|
|
71867
|
-
})
|
|
71868
|
-
]);
|
|
71869
|
-
var RecordSecretRefsSchema = record(string$2(), record(string$2(), string$2()));
|
|
71870
|
-
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string$2(), DeclareKindResultSchema) }), {
|
|
71871
|
-
kind: "mutation",
|
|
71872
|
-
auth: "admin"
|
|
71873
|
-
}), method(object({
|
|
71874
|
-
...KindShape,
|
|
71875
|
-
id: RecordIdSchema
|
|
71876
|
-
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
71877
|
-
...KindShape,
|
|
71878
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71879
|
-
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
71880
|
-
...KindShape,
|
|
71881
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71882
|
-
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
71883
|
-
...KindShape,
|
|
71884
|
-
record: RecordInputSchema,
|
|
71885
|
-
/** Compare-and-set. `0` means "the row must not exist". */
|
|
71886
|
-
ifRev: number().int().min(0).optional()
|
|
71887
|
-
}), object({
|
|
71888
|
-
rev: number().int(),
|
|
71889
|
-
releasedRefs: SecretRefsList
|
|
71890
|
-
}), {
|
|
71891
|
-
kind: "mutation",
|
|
71892
|
-
auth: "admin"
|
|
71893
|
-
}), method(object({
|
|
71894
|
-
...KindShape,
|
|
71895
|
-
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71896
|
-
}), object({
|
|
71897
|
-
count: number().int(),
|
|
71898
|
-
releasedRefs: SecretRefsList
|
|
71899
|
-
}), {
|
|
71900
|
-
kind: "mutation",
|
|
71901
|
-
auth: "admin"
|
|
71902
|
-
}), method(object({
|
|
71903
|
-
...KindShape,
|
|
71904
|
-
id: RecordIdSchema,
|
|
71905
|
-
data: RecordBodySchema,
|
|
71906
|
-
at: number().int().nullable().optional(),
|
|
71907
|
-
expiresAt: number().int().nullable().optional(),
|
|
71908
|
-
ifRev: number().int().min(1).optional()
|
|
71909
|
-
}), object({
|
|
71910
|
-
rev: number().int(),
|
|
71911
|
-
releasedRefs: SecretRefsList
|
|
71912
|
-
}), {
|
|
71913
|
-
kind: "mutation",
|
|
71914
|
-
auth: "admin"
|
|
71915
|
-
}), method(object({
|
|
71916
|
-
...KindShape,
|
|
71917
|
-
id: RecordIdSchema
|
|
71918
|
-
}), object({
|
|
71919
|
-
deleted: boolean(),
|
|
71920
|
-
secretRefs: SecretRefsList
|
|
71921
|
-
}), {
|
|
71922
|
-
kind: "mutation",
|
|
71923
|
-
auth: "admin"
|
|
71924
|
-
}), method(object({
|
|
71925
|
-
...KindShape,
|
|
71926
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71927
|
-
}), object({
|
|
71928
|
-
deleted: number().int(),
|
|
71929
|
-
secretRefs: SecretRefsList
|
|
71930
|
-
}), {
|
|
71931
|
-
kind: "mutation",
|
|
71932
|
-
auth: "admin"
|
|
71933
|
-
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
71934
|
-
...KindShape,
|
|
71935
|
-
...TimeWindowShape,
|
|
71936
|
-
where: RecordWhereSchema.optional()
|
|
71937
|
-
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
71938
|
-
...KindShape,
|
|
71939
|
-
deviceId: number().int().optional(),
|
|
71940
|
-
from: number(),
|
|
71941
|
-
to: number(),
|
|
71942
|
-
bucketMs: number().int().positive()
|
|
71943
|
-
}), object({ buckets: array(object({
|
|
71944
|
-
bucketStart: number(),
|
|
71945
|
-
count: number().int()
|
|
71946
|
-
})) }), { auth: "admin" }), method(object({
|
|
71947
|
-
...KindShape,
|
|
71948
|
-
op: _enum([
|
|
71949
|
-
"sum",
|
|
71950
|
-
"min",
|
|
71951
|
-
"max",
|
|
71952
|
-
"count"
|
|
71953
|
-
]),
|
|
71954
|
-
field: RecordFieldPathSchema.optional(),
|
|
71955
|
-
groupBy: RecordFieldPathSchema.optional(),
|
|
71956
|
-
where: RecordWhereSchema.optional()
|
|
71957
|
-
}), object({ rows: array(object({
|
|
71958
|
-
group: union([
|
|
71959
|
-
string$2(),
|
|
71960
|
-
number(),
|
|
71961
|
-
_null()
|
|
71962
|
-
]),
|
|
71963
|
-
value: number().nullable()
|
|
71964
|
-
})) }), { auth: "admin" }), method(object({
|
|
71965
|
-
...KindShape,
|
|
71966
|
-
deviceId: number().int().optional(),
|
|
71967
|
-
atBefore: number().optional(),
|
|
71968
|
-
where: RecordWhereSchema.optional()
|
|
71969
|
-
}), object({
|
|
71970
|
-
deleted: number().int(),
|
|
71971
|
-
secretRefs: SecretRefsList
|
|
71972
|
-
}), {
|
|
71973
|
-
kind: "mutation",
|
|
71974
|
-
auth: "admin"
|
|
71975
|
-
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
71976
|
-
results: array(object({
|
|
71977
|
-
rev: number().int().nullable(),
|
|
71978
|
-
deleted: boolean()
|
|
71979
|
-
})),
|
|
71980
|
-
releasedRefs: SecretRefsList,
|
|
71981
|
-
secretRefs: SecretRefsList
|
|
71982
|
-
}), {
|
|
71983
|
-
kind: "mutation",
|
|
71984
|
-
auth: "admin"
|
|
71985
|
-
}), method(object({
|
|
71986
|
-
deviceId: number().int(),
|
|
71987
|
-
kinds: array(RecordKindNameSchema).optional()
|
|
71988
|
-
}), object({
|
|
71989
|
-
deleted: record(string$2(), number().int()),
|
|
71990
|
-
secretRefs: SecretRefsList
|
|
71991
|
-
}), {
|
|
71992
|
-
kind: "mutation",
|
|
71993
|
-
auth: "admin"
|
|
71994
|
-
}), method(object(KindShape), object({ id: number().int() }), {
|
|
71995
|
-
kind: "mutation",
|
|
71996
|
-
auth: "admin"
|
|
71997
|
-
});
|
|
71998
|
-
/**
|
|
71999
72102
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
72000
72103
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
72001
72104
|
* declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
|
|
@@ -81723,7 +81826,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
81723
81826
|
* `deviceClasses` map (first registered type). `null` when manual creation is
|
|
81724
81827
|
* unsupported. Lets the Add-Device dialog pick the right type per provider.
|
|
81725
81828
|
*/
|
|
81726
|
-
async getManualCreationType() {
|
|
81829
|
+
async getManualCreationType(_input) {
|
|
81727
81830
|
if (!await this.supportsManualCreation()) return { deviceType: null };
|
|
81728
81831
|
return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
|
|
81729
81832
|
}
|
|
@@ -81734,7 +81837,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
81734
81837
|
return true;
|
|
81735
81838
|
}
|
|
81736
81839
|
async getChildCreationSchema(input) {
|
|
81737
|
-
return this.onGetCreationSchema(input.type);
|
|
81840
|
+
return this.onGetCreationSchema(input.type, { integrationId: input.integrationId });
|
|
81738
81841
|
}
|
|
81739
81842
|
/**
|
|
81740
81843
|
* Default kernel-orchestrated `createDevice` implementation. The
|
|
@@ -81746,7 +81849,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
81746
81849
|
* `deviceClasses` instead.
|
|
81747
81850
|
*/
|
|
81748
81851
|
async createDevice(input) {
|
|
81749
|
-
const spec = await this.onCreateDevice(input.type, input.config);
|
|
81852
|
+
const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
|
|
81750
81853
|
const Class = this.deviceClasses[spec.meta.type];
|
|
81751
81854
|
if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
|
|
81752
81855
|
const stableId = this.generateStableId(spec.meta.type, spec.config);
|
|
@@ -83155,20 +83258,6 @@ Object.freeze({
|
|
|
83155
83258
|
access: "create",
|
|
83156
83259
|
role: "viewer"
|
|
83157
83260
|
},
|
|
83158
|
-
"broker.add": {
|
|
83159
|
-
capName: "broker",
|
|
83160
|
-
capScope: "system",
|
|
83161
|
-
addonId: null,
|
|
83162
|
-
access: "create",
|
|
83163
|
-
role: "admin"
|
|
83164
|
-
},
|
|
83165
|
-
"broker.get": {
|
|
83166
|
-
capName: "broker",
|
|
83167
|
-
capScope: "system",
|
|
83168
|
-
addonId: null,
|
|
83169
|
-
access: "view",
|
|
83170
|
-
role: "admin"
|
|
83171
|
-
},
|
|
83172
83261
|
"broker.getBrokerConfig": {
|
|
83173
83262
|
capName: "broker",
|
|
83174
83263
|
capScope: "system",
|
|
@@ -83176,20 +83265,6 @@ Object.freeze({
|
|
|
83176
83265
|
access: "view",
|
|
83177
83266
|
role: "admin"
|
|
83178
83267
|
},
|
|
83179
|
-
"broker.getSettings": {
|
|
83180
|
-
capName: "broker",
|
|
83181
|
-
capScope: "system",
|
|
83182
|
-
addonId: null,
|
|
83183
|
-
access: "view",
|
|
83184
|
-
role: "admin"
|
|
83185
|
-
},
|
|
83186
|
-
"broker.getSettingsSchema": {
|
|
83187
|
-
capName: "broker",
|
|
83188
|
-
capScope: "system",
|
|
83189
|
-
addonId: null,
|
|
83190
|
-
access: "view",
|
|
83191
|
-
role: "admin"
|
|
83192
|
-
},
|
|
83193
83268
|
"broker.getState": {
|
|
83194
83269
|
capName: "broker",
|
|
83195
83270
|
capScope: "system",
|
|
@@ -83204,20 +83279,6 @@ Object.freeze({
|
|
|
83204
83279
|
access: "view",
|
|
83205
83280
|
role: "admin"
|
|
83206
83281
|
},
|
|
83207
|
-
"broker.list": {
|
|
83208
|
-
capName: "broker",
|
|
83209
|
-
capScope: "system",
|
|
83210
|
-
addonId: null,
|
|
83211
|
-
access: "view",
|
|
83212
|
-
role: "admin"
|
|
83213
|
-
},
|
|
83214
|
-
"broker.listProviders": {
|
|
83215
|
-
capName: "broker",
|
|
83216
|
-
capScope: "system",
|
|
83217
|
-
addonId: null,
|
|
83218
|
-
access: "view",
|
|
83219
|
-
role: "admin"
|
|
83220
|
-
},
|
|
83221
83282
|
"broker.publish": {
|
|
83222
83283
|
capName: "broker",
|
|
83223
83284
|
capScope: "system",
|
|
@@ -83225,20 +83286,6 @@ Object.freeze({
|
|
|
83225
83286
|
access: "create",
|
|
83226
83287
|
role: "admin"
|
|
83227
83288
|
},
|
|
83228
|
-
"broker.remove": {
|
|
83229
|
-
capName: "broker",
|
|
83230
|
-
capScope: "system",
|
|
83231
|
-
addonId: null,
|
|
83232
|
-
access: "delete",
|
|
83233
|
-
role: "admin"
|
|
83234
|
-
},
|
|
83235
|
-
"broker.setSettings": {
|
|
83236
|
-
capName: "broker",
|
|
83237
|
-
capScope: "system",
|
|
83238
|
-
addonId: null,
|
|
83239
|
-
access: "create",
|
|
83240
|
-
role: "admin"
|
|
83241
|
-
},
|
|
83242
83289
|
"broker.subscribe": {
|
|
83243
83290
|
capName: "broker",
|
|
83244
83291
|
capScope: "system",
|
|
@@ -83246,20 +83293,6 @@ Object.freeze({
|
|
|
83246
83293
|
access: "create",
|
|
83247
83294
|
role: "admin"
|
|
83248
83295
|
},
|
|
83249
|
-
"broker.testConnection": {
|
|
83250
|
-
capName: "broker",
|
|
83251
|
-
capScope: "system",
|
|
83252
|
-
addonId: null,
|
|
83253
|
-
access: "create",
|
|
83254
|
-
role: "admin"
|
|
83255
|
-
},
|
|
83256
|
-
"broker.testSettings": {
|
|
83257
|
-
capName: "broker",
|
|
83258
|
-
capScope: "system",
|
|
83259
|
-
addonId: null,
|
|
83260
|
-
access: "create",
|
|
83261
|
-
role: "admin"
|
|
83262
|
-
},
|
|
83263
83296
|
"broker.unsubscribe": {
|
|
83264
83297
|
capName: "broker",
|
|
83265
83298
|
capScope: "system",
|
|
@@ -88223,6 +88256,13 @@ Object.freeze({
|
|
|
88223
88256
|
access: "view",
|
|
88224
88257
|
role: "viewer"
|
|
88225
88258
|
},
|
|
88259
|
+
"pipelineAnalytics.getMediaLocationCensus": {
|
|
88260
|
+
capName: "pipeline-analytics",
|
|
88261
|
+
capScope: "device",
|
|
88262
|
+
addonId: null,
|
|
88263
|
+
access: "view",
|
|
88264
|
+
role: "admin"
|
|
88265
|
+
},
|
|
88226
88266
|
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
88227
88267
|
capName: "pipeline-analytics",
|
|
88228
88268
|
capScope: "device",
|
|
@@ -97164,6 +97204,275 @@ object({
|
|
|
97164
97204
|
delay: number().nonnegative().optional(),
|
|
97165
97205
|
commands: record(string$2(), unknown())
|
|
97166
97206
|
});
|
|
97207
|
+
var BrokerInUseError = class extends Error {
|
|
97208
|
+
brokerId;
|
|
97209
|
+
users;
|
|
97210
|
+
name = "BrokerInUseError";
|
|
97211
|
+
constructor(brokerId, users) {
|
|
97212
|
+
super(`broker '${brokerId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
|
|
97213
|
+
this.brokerId = brokerId;
|
|
97214
|
+
this.users = users;
|
|
97215
|
+
}
|
|
97216
|
+
};
|
|
97217
|
+
/** Throw {@link BrokerInUseError} when any finder names a user of `brokerId`. */
|
|
97218
|
+
async function assertBrokerUnused(brokerId, finders) {
|
|
97219
|
+
const users = [];
|
|
97220
|
+
for (const finder of finders) users.push(...await finder.find(brokerId));
|
|
97221
|
+
if (users.length > 0) throw new BrokerInUseError(brokerId, users);
|
|
97222
|
+
}
|
|
97223
|
+
/**
|
|
97224
|
+
* An account provider whose `deleteInstance` is refused while anything still uses the account
|
|
97225
|
+
* (D1012). The account is an `integration-instances` kind (D1013), so the refusal lives on the
|
|
97226
|
+
* delete the instances page calls, not on a separate broker cap.
|
|
97227
|
+
*/
|
|
97228
|
+
function guardBrokerDelete(base, users) {
|
|
97229
|
+
return {
|
|
97230
|
+
...base,
|
|
97231
|
+
deleteInstance: async (input) => {
|
|
97232
|
+
await assertBrokerUnused(input.id, users());
|
|
97233
|
+
return base.deleteInstance(input);
|
|
97234
|
+
}
|
|
97235
|
+
};
|
|
97236
|
+
}
|
|
97237
|
+
/**
|
|
97238
|
+
* The generic binding of a service to a broker (D1012). A service (an integration today; a
|
|
97239
|
+
* notifier target, a calendar source or an exporter later) names the broker it runs through by id
|
|
97240
|
+
* and keeps only its OWN link settings: never a copy of the broker's host, sign-in or secrets,
|
|
97241
|
+
* which stay with the broker and are edited on the Brokers page.
|
|
97242
|
+
*
|
|
97243
|
+
* One primitive serves every consumer:
|
|
97244
|
+
* - {@link declareBrokerLinkKind}: the record kind, body `{ name, brokerId, ...linkFields }`;
|
|
97245
|
+
* - {@link buildBrokerLinkForm}: the form, a `broker-select` picker plus the service's link
|
|
97246
|
+
* sections, which may be DYNAMIC (a list read from the chosen broker, like HA calendars);
|
|
97247
|
+
* - {@link buildBrokerLinkInstances}: the `integration-instances` provider of an integration;
|
|
97248
|
+
* - {@link resolveBrokerId} / {@link brokerLinkUserFinder}: which broker a record names and which
|
|
97249
|
+
* records name a broker, so a delete of a broker still in use can name its users generically.
|
|
97250
|
+
*/
|
|
97251
|
+
/** The record kind an integration is stored under. */
|
|
97252
|
+
var BROKER_LINK_KIND_NAME = "connections";
|
|
97253
|
+
/** What every broker-linked record carries; `brokerId` is always present, `''` is a row whose broker is not picked yet; link fields of the service come on top. */
|
|
97254
|
+
var BrokerLinkBodySchema = entryBody({ brokerId: string$2() });
|
|
97255
|
+
function declareBrokerLinkKind(spec) {
|
|
97256
|
+
return defineEntryKind(BROKER_LINK_KIND_NAME, {
|
|
97257
|
+
title: spec.title,
|
|
97258
|
+
schema: BrokerLinkBodySchema.extend(spec.linkFields ?? {}),
|
|
97259
|
+
writeTrigger: spec.writeTrigger
|
|
97260
|
+
});
|
|
97261
|
+
}
|
|
97262
|
+
/** The form: a picker over the brokers of `brokerKind`, then the service's link sections. */
|
|
97263
|
+
async function buildBrokerLinkForm(spec, brokerId) {
|
|
97264
|
+
const dynamic = brokerId !== void 0 && spec.dynamicSections !== void 0 ? await spec.dynamicSections(brokerId) : [];
|
|
97265
|
+
return { sections: [
|
|
97266
|
+
{
|
|
97267
|
+
id: "broker-link",
|
|
97268
|
+
title: spec.brokerLabel,
|
|
97269
|
+
description: `Each integration links to a ${spec.brokerLabel} connection (Connectivity → Brokers). The connection and its sign-in are edited there.`,
|
|
97270
|
+
columns: 1,
|
|
97271
|
+
fields: [{
|
|
97272
|
+
type: "broker-select",
|
|
97273
|
+
key: "brokerId",
|
|
97274
|
+
label: spec.brokerLabel,
|
|
97275
|
+
brokerKind: spec.brokerKind
|
|
97276
|
+
}]
|
|
97277
|
+
},
|
|
97278
|
+
...spec.linkSections ?? [],
|
|
97279
|
+
...dynamic
|
|
97280
|
+
] };
|
|
97281
|
+
}
|
|
97282
|
+
/** The broker a record names; undefined while it names none. */
|
|
97283
|
+
function resolveBrokerId(record) {
|
|
97284
|
+
return record.brokerId === void 0 || record.brokerId === "" ? void 0 : record.brokerId;
|
|
97285
|
+
}
|
|
97286
|
+
/** The broker an integration links to, from its record. */
|
|
97287
|
+
async function brokerIdOf(instances, integrationId) {
|
|
97288
|
+
const body = await instances.getResolved(integrationId);
|
|
97289
|
+
return body === null ? void 0 : resolveBrokerId(body);
|
|
97290
|
+
}
|
|
97291
|
+
/** Every record of the kind that names `brokerId`, as users of that broker. */
|
|
97292
|
+
function brokerLinkUserFinder(instances, userKind = "integration") {
|
|
97293
|
+
return { find: async (brokerId) => (await instances.listResolved()).filter((entry) => resolveBrokerId(entry.body) === brokerId).map((entry) => ({
|
|
97294
|
+
kind: userKind,
|
|
97295
|
+
id: entry.id,
|
|
97296
|
+
name: entry.body.name
|
|
97297
|
+
})) };
|
|
97298
|
+
}
|
|
97299
|
+
/** The `integration-instances` provider of an addon whose integrations link to a broker. */
|
|
97300
|
+
function buildBrokerLinkInstances(records, spec) {
|
|
97301
|
+
const instances = new RecordIntegrationInstances(records, spec.kind, {
|
|
97302
|
+
description: {
|
|
97303
|
+
role: "devices",
|
|
97304
|
+
instanceMode: "multiple",
|
|
97305
|
+
kind: "connection",
|
|
97306
|
+
canTest: false
|
|
97307
|
+
},
|
|
97308
|
+
formSchema: async (id) => buildBrokerLinkForm(spec, id === null ? void 0 : await brokerIdOf(instances, id)),
|
|
97309
|
+
initialBody: (name) => spec.kind.schema.parse({
|
|
97310
|
+
name,
|
|
97311
|
+
brokerId: ""
|
|
97312
|
+
}),
|
|
97313
|
+
configured: (body) => resolveBrokerId(body) !== void 0,
|
|
97314
|
+
...spec.onChanged !== void 0 ? { onChanged: spec.onChanged } : {}
|
|
97315
|
+
});
|
|
97316
|
+
return instances;
|
|
97317
|
+
}
|
|
97318
|
+
var BROKER_ROWS_MAX = 1e3;
|
|
97319
|
+
var STRAY_REGISTRY_ID = /^int_\d+$/;
|
|
97320
|
+
async function moveBrokerLinksToConnections(ports) {
|
|
97321
|
+
const brokers = await ports.listBrokers();
|
|
97322
|
+
const strays = brokers.filter((b) => STRAY_REGISTRY_ID.test(b.id));
|
|
97323
|
+
if (strays.length > 0) ports.warn(`${strays.length} broker row(s) are keyed like a registry integration and are left as they are: ${strays.map((s) => s.id).join(", ")}`);
|
|
97324
|
+
let moved = 0;
|
|
97325
|
+
for (const broker of brokers) {
|
|
97326
|
+
const integrationId = broker.integrationId;
|
|
97327
|
+
if (integrationId === void 0 || integrationId === "") continue;
|
|
97328
|
+
const existing = await ports.readConnection(integrationId);
|
|
97329
|
+
if (existing === null) await ports.putConnection(integrationId, {
|
|
97330
|
+
name: broker.name,
|
|
97331
|
+
brokerId: broker.id
|
|
97332
|
+
});
|
|
97333
|
+
else if (existing.brokerId !== broker.id) {
|
|
97334
|
+
ports.warn(`integration '${integrationId}' already names broker '${existing.brokerId ?? "none"}', not '${broker.id}'; the link stays on the broker`);
|
|
97335
|
+
continue;
|
|
97336
|
+
}
|
|
97337
|
+
if ((await ports.readConnection(integrationId))?.brokerId !== broker.id) throw new Error(`integration '${integrationId}': the link did not read back - broker '${broker.id}' keeps it`);
|
|
97338
|
+
await ports.clearBrokerLink(broker.id);
|
|
97339
|
+
moved += 1;
|
|
97340
|
+
}
|
|
97341
|
+
return {
|
|
97342
|
+
outcome: moved === 0 ? "already-done" : "done",
|
|
97343
|
+
summary: {
|
|
97344
|
+
brokers: brokers.length,
|
|
97345
|
+
moved,
|
|
97346
|
+
strayIntegrationRows: strays.length
|
|
97347
|
+
}
|
|
97348
|
+
};
|
|
97349
|
+
}
|
|
97350
|
+
/** The ports over the two record kinds of the addon. */
|
|
97351
|
+
function brokerLinkPortsOver(deps) {
|
|
97352
|
+
return {
|
|
97353
|
+
warn: deps.warn,
|
|
97354
|
+
listBrokers: async () => {
|
|
97355
|
+
return (await deps.brokers.query({ limit: BROKER_ROWS_MAX })).records.map((row) => {
|
|
97356
|
+
const name = row.data["name"];
|
|
97357
|
+
const link = row.data["integrationId"];
|
|
97358
|
+
return {
|
|
97359
|
+
id: row.id,
|
|
97360
|
+
name: typeof name === "string" && name.length > 0 ? name : row.id,
|
|
97361
|
+
integrationId: typeof link === "string" ? link : void 0
|
|
97362
|
+
};
|
|
97363
|
+
});
|
|
97364
|
+
},
|
|
97365
|
+
readConnection: async (id) => {
|
|
97366
|
+
const row = await deps.connections.get(id);
|
|
97367
|
+
return row === null ? null : { brokerId: row.data.brokerId };
|
|
97368
|
+
},
|
|
97369
|
+
putConnection: async (id, body) => {
|
|
97370
|
+
await deps.connections.put({
|
|
97371
|
+
id,
|
|
97372
|
+
data: body
|
|
97373
|
+
}, { ifRev: 0 });
|
|
97374
|
+
},
|
|
97375
|
+
clearBrokerLink: async (brokerId) => {
|
|
97376
|
+
await deps.brokers.patch(brokerId, { integrationId: null });
|
|
97377
|
+
}
|
|
97378
|
+
};
|
|
97379
|
+
}
|
|
97380
|
+
//#endregion
|
|
97381
|
+
//#region src/config.ts
|
|
97382
|
+
/**
|
|
97383
|
+
* Matter broker connection settings for ONE fabric/controller (one broker entry).
|
|
97384
|
+
* v1 is intentionally minimal: optional Thread / Wi-Fi credentials supplied up
|
|
97385
|
+
* front for wireless commissioning. The pairing code itself is NOT stored here —
|
|
97386
|
+
* it is passed transiently to the commissioning action.
|
|
97387
|
+
*
|
|
97388
|
+
* The fabric credentials (root cert / NOC / node registry) live in the addon
|
|
97389
|
+
* `dataDir` via the controller's storage, NEVER in this typed KV.
|
|
97390
|
+
*/
|
|
97391
|
+
var matterConnectionSchema = object({
|
|
97392
|
+
/** Optional Thread operational dataset (hex) for Thread device onboarding. */
|
|
97393
|
+
threadDataset: string$2().default("").describe("Thread operational dataset (hex) — required only for Thread devices").meta({ secret: true }),
|
|
97394
|
+
/** Optional Wi-Fi SSID for Wi-Fi device onboarding over BLE. */
|
|
97395
|
+
wifiSsid: string$2().default("").describe("Wi-Fi SSID — required only for BLE Wi-Fi onboarding"),
|
|
97396
|
+
/** Optional Wi-Fi password for Wi-Fi device onboarding over BLE. */
|
|
97397
|
+
wifiPassword: string$2().default("").describe("Wi-Fi password (optional)").meta({ secret: true }),
|
|
97398
|
+
/** PULL-reconcile interval for the commissioned-node inventory. */
|
|
97399
|
+
reconcileIntervalMs: preprocess((v) => v === "" || v === null ? void 0 : v, number().int().min(1e4).max(864e5).default(3e5)).describe("Node-inventory reconcile interval (ms)")
|
|
97400
|
+
});
|
|
97401
|
+
/** A single registered Matter fabric ("broker"). */
|
|
97402
|
+
var MatterBrokerEntrySchema = object({
|
|
97403
|
+
id: string$2().min(1),
|
|
97404
|
+
name: string$2().min(1),
|
|
97405
|
+
connection: matterConnectionSchema,
|
|
97406
|
+
/** Absent means on; the instances page switches a fabric off without deleting it. */
|
|
97407
|
+
enabled: boolean().optional()
|
|
97408
|
+
});
|
|
97409
|
+
/** Top-level addon config — the fabrics are records, so nothing is left here. */
|
|
97410
|
+
var matterAddonConfigSchema = object({});
|
|
97411
|
+
/** Narrow an `unknown` to a shallow plain-object record (cast-free boundary). */
|
|
97412
|
+
function toRecord(value) {
|
|
97413
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.fromEntries(Object.entries(value));
|
|
97414
|
+
return {};
|
|
97415
|
+
}
|
|
97416
|
+
/** Coerce a loose settings blob through the connection schema, applying defaults. */
|
|
97417
|
+
function settingsToMatterConnection(settings) {
|
|
97418
|
+
return matterConnectionSchema.parse(toRecord(settings));
|
|
97419
|
+
}
|
|
97420
|
+
/**
|
|
97421
|
+
* Settings readers see an empty string for both secrets. The stored value
|
|
97422
|
+
* stays in the keychain (records `secret` meta). An empty round-trip must
|
|
97423
|
+
* not wipe it — see {@link mergeMatterSecrets}.
|
|
97424
|
+
*/
|
|
97425
|
+
function publicMatterConnection(connection) {
|
|
97426
|
+
return {
|
|
97427
|
+
...connection,
|
|
97428
|
+
wifiPassword: "",
|
|
97429
|
+
threadDataset: ""
|
|
97430
|
+
};
|
|
97431
|
+
}
|
|
97432
|
+
/** Hand-written connection form for the broker/integration creation UI. */
|
|
97433
|
+
function buildConnectionFormSchema() {
|
|
97434
|
+
return { sections: [{
|
|
97435
|
+
id: "matter-fabric",
|
|
97436
|
+
title: "Matter fabric",
|
|
97437
|
+
titleKey: "matter-broker:connection.s_matter-fabric.title",
|
|
97438
|
+
description: "CamStack owns a Matter fabric on this hub. Add wireless onboarding credentials only if you will commission Thread or BLE-Wi-Fi devices. On-network (Ethernet/Wi-Fi) Matter devices need none of these.",
|
|
97439
|
+
descriptionKey: "matter-broker:connection.s_matter-fabric.description",
|
|
97440
|
+
columns: 1,
|
|
97441
|
+
fields: [
|
|
97442
|
+
{
|
|
97443
|
+
type: "password",
|
|
97444
|
+
key: "threadDataset",
|
|
97445
|
+
secret: true,
|
|
97446
|
+
showToggle: true,
|
|
97447
|
+
label: "Thread operational dataset (hex, optional)",
|
|
97448
|
+
labelKey: "matter-broker:connection.s_matter-fabric.threadDataset.label",
|
|
97449
|
+
placeholder: "0e08000000000001…"
|
|
97450
|
+
},
|
|
97451
|
+
{
|
|
97452
|
+
type: "text",
|
|
97453
|
+
key: "wifiSsid",
|
|
97454
|
+
label: "Wi-Fi SSID (optional)",
|
|
97455
|
+
labelKey: "matter-broker:connection.s_matter-fabric.wifiSsid.label"
|
|
97456
|
+
},
|
|
97457
|
+
{
|
|
97458
|
+
type: "password",
|
|
97459
|
+
key: "wifiPassword",
|
|
97460
|
+
label: "Wi-Fi password (optional)",
|
|
97461
|
+
labelKey: "matter-broker:connection.s_matter-fabric.wifiPassword.label",
|
|
97462
|
+
showToggle: true
|
|
97463
|
+
},
|
|
97464
|
+
{
|
|
97465
|
+
type: "number",
|
|
97466
|
+
key: "reconcileIntervalMs",
|
|
97467
|
+
label: "Node reconcile interval (ms)",
|
|
97468
|
+
labelKey: "matter-broker:connection.s_matter-fabric.reconcileIntervalMs.label",
|
|
97469
|
+
min: 1e4,
|
|
97470
|
+
max: 864e5,
|
|
97471
|
+
default: 3e5
|
|
97472
|
+
}
|
|
97473
|
+
]
|
|
97474
|
+
}] };
|
|
97475
|
+
}
|
|
97167
97476
|
//#endregion
|
|
97168
97477
|
//#region ../../node_modules/@matter/general/dist/esm/net/tcp/TcpConnection.js
|
|
97169
97478
|
/**
|
|
@@ -207503,7 +207812,6 @@ var MatterBrokerRegistry = class {
|
|
|
207503
207812
|
#onAttributeReport;
|
|
207504
207813
|
#makeManager;
|
|
207505
207814
|
#managers = /* @__PURE__ */ new Map();
|
|
207506
|
-
#integrationToBroker = /* @__PURE__ */ new Map();
|
|
207507
207815
|
#nextId = 1;
|
|
207508
207816
|
constructor(logger, deps) {
|
|
207509
207817
|
this.#logger = logger;
|
|
@@ -207552,7 +207860,6 @@ var MatterBrokerRegistry = class {
|
|
|
207552
207860
|
}
|
|
207553
207861
|
}));
|
|
207554
207862
|
this.#managers.clear();
|
|
207555
|
-
this.#integrationToBroker.clear();
|
|
207556
207863
|
}
|
|
207557
207864
|
setOnBrokerConnected(cb) {
|
|
207558
207865
|
this.#onBrokerConnected = cb;
|
|
@@ -207572,17 +207879,14 @@ var MatterBrokerRegistry = class {
|
|
|
207572
207879
|
const entry = {
|
|
207573
207880
|
id,
|
|
207574
207881
|
name,
|
|
207575
|
-
connection
|
|
207576
|
-
...opts.integrationId !== void 0 ? { integrationId: opts.integrationId } : {}
|
|
207882
|
+
connection
|
|
207577
207883
|
};
|
|
207578
207884
|
await this.#startManager(entry);
|
|
207579
|
-
if (opts.integrationId !== void 0) this.#integrationToBroker.set(opts.integrationId, id);
|
|
207580
207885
|
return entry;
|
|
207581
207886
|
}
|
|
207582
207887
|
async removeEntry(id) {
|
|
207583
207888
|
const mgr = this.#managers.get(id);
|
|
207584
207889
|
if (!mgr) return;
|
|
207585
|
-
for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
|
|
207586
207890
|
this.#managers.delete(id);
|
|
207587
207891
|
try {
|
|
207588
207892
|
await mgr.stop();
|
|
@@ -207623,9 +207927,6 @@ var MatterBrokerRegistry = class {
|
|
|
207623
207927
|
if (!mgr) throw new Error(`MatterBrokerRegistry: unknown broker id "${brokerId}"`);
|
|
207624
207928
|
await mgr.invokeCommand(invocation);
|
|
207625
207929
|
}
|
|
207626
|
-
linkIntegration(integrationId, brokerId) {
|
|
207627
|
-
this.#integrationToBroker.set(integrationId, brokerId);
|
|
207628
|
-
}
|
|
207629
207930
|
list() {
|
|
207630
207931
|
return Array.from(this.#managers.values()).map((m) => m.getInfo());
|
|
207631
207932
|
}
|
|
@@ -207644,10 +207945,7 @@ var MatterBrokerRegistry = class {
|
|
|
207644
207945
|
return count;
|
|
207645
207946
|
}
|
|
207646
207947
|
async #startManager(entry) {
|
|
207647
|
-
if (this.#managers.has(entry.id))
|
|
207648
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
207649
|
-
return;
|
|
207650
|
-
}
|
|
207948
|
+
if (this.#managers.has(entry.id)) return;
|
|
207651
207949
|
const mgr = this.#makeManager({
|
|
207652
207950
|
id: entry.id,
|
|
207653
207951
|
name: entry.name,
|
|
@@ -207660,7 +207958,6 @@ var MatterBrokerRegistry = class {
|
|
|
207660
207958
|
onAttributeReport: (id, report) => this.#onAttributeReport(id, report)
|
|
207661
207959
|
});
|
|
207662
207960
|
this.#managers.set(entry.id, mgr);
|
|
207663
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
207664
207961
|
await mgr.start();
|
|
207665
207962
|
}
|
|
207666
207963
|
/** The next free broker id, for a record minted before its manager exists. */
|
|
@@ -207680,111 +207977,6 @@ var MatterBrokerRegistry = class {
|
|
|
207680
207977
|
}
|
|
207681
207978
|
};
|
|
207682
207979
|
//#endregion
|
|
207683
|
-
//#region src/config.ts
|
|
207684
|
-
/**
|
|
207685
|
-
* Matter broker connection settings for ONE fabric/controller (one broker entry).
|
|
207686
|
-
* v1 is intentionally minimal: optional Thread / Wi-Fi credentials supplied up
|
|
207687
|
-
* front for wireless commissioning. The pairing code itself is NOT stored here —
|
|
207688
|
-
* it is passed transiently to the commissioning action.
|
|
207689
|
-
*
|
|
207690
|
-
* The fabric credentials (root cert / NOC / node registry) live in the addon
|
|
207691
|
-
* `dataDir` via the controller's storage, NEVER in this typed KV.
|
|
207692
|
-
*/
|
|
207693
|
-
var matterConnectionSchema = object({
|
|
207694
|
-
/** Optional Thread operational dataset (hex) for Thread device onboarding. */
|
|
207695
|
-
threadDataset: string$2().default("").describe("Thread operational dataset (hex) — required only for Thread devices").meta({ secret: true }),
|
|
207696
|
-
/** Optional Wi-Fi SSID for Wi-Fi device onboarding over BLE. */
|
|
207697
|
-
wifiSsid: string$2().default("").describe("Wi-Fi SSID — required only for BLE Wi-Fi onboarding"),
|
|
207698
|
-
/** Optional Wi-Fi password for Wi-Fi device onboarding over BLE. */
|
|
207699
|
-
wifiPassword: string$2().default("").describe("Wi-Fi password (optional)").meta({ secret: true }),
|
|
207700
|
-
/** PULL-reconcile interval for the commissioned-node inventory. */
|
|
207701
|
-
reconcileIntervalMs: preprocess((v) => v === "" || v === null ? void 0 : v, number().int().min(1e4).max(864e5).default(3e5)).describe("Node-inventory reconcile interval (ms)")
|
|
207702
|
-
});
|
|
207703
|
-
/** A single registered Matter fabric ("broker"). */
|
|
207704
|
-
var MatterBrokerEntrySchema = object({
|
|
207705
|
-
id: string$2().min(1),
|
|
207706
|
-
name: string$2().min(1),
|
|
207707
|
-
connection: matterConnectionSchema,
|
|
207708
|
-
integrationId: string$2().optional(),
|
|
207709
|
-
/** Absent means on; the instances page switches a fabric off without deleting it. */
|
|
207710
|
-
enabled: boolean().optional()
|
|
207711
|
-
});
|
|
207712
|
-
/** Top-level addon config — the fabrics are records, so nothing is left here. */
|
|
207713
|
-
var matterAddonConfigSchema = object({});
|
|
207714
|
-
/** Narrow an `unknown` to a shallow plain-object record (cast-free boundary). */
|
|
207715
|
-
function toRecord(value) {
|
|
207716
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.fromEntries(Object.entries(value));
|
|
207717
|
-
return {};
|
|
207718
|
-
}
|
|
207719
|
-
/** Coerce a loose settings blob through the connection schema, applying defaults. */
|
|
207720
|
-
function settingsToMatterConnection(settings) {
|
|
207721
|
-
return matterConnectionSchema.parse(toRecord(settings));
|
|
207722
|
-
}
|
|
207723
|
-
/**
|
|
207724
|
-
* Settings readers see an empty string for both secrets. The stored value
|
|
207725
|
-
* stays in the keychain (records `secret` meta). An empty round-trip must
|
|
207726
|
-
* not wipe it — see {@link mergeMatterSecrets}.
|
|
207727
|
-
*/
|
|
207728
|
-
function publicMatterConnection(connection) {
|
|
207729
|
-
return {
|
|
207730
|
-
...connection,
|
|
207731
|
-
wifiPassword: "",
|
|
207732
|
-
threadDataset: ""
|
|
207733
|
-
};
|
|
207734
|
-
}
|
|
207735
|
-
/** Keep a stored secret when the form sent back the redacted empty string. */
|
|
207736
|
-
function mergeMatterSecrets(existing, incoming) {
|
|
207737
|
-
return {
|
|
207738
|
-
...incoming,
|
|
207739
|
-
wifiPassword: incoming.wifiPassword.length > 0 ? incoming.wifiPassword : existing.wifiPassword,
|
|
207740
|
-
threadDataset: incoming.threadDataset.length > 0 ? incoming.threadDataset : existing.threadDataset
|
|
207741
|
-
};
|
|
207742
|
-
}
|
|
207743
|
-
/** Hand-written connection form for the broker/integration creation UI. */
|
|
207744
|
-
function buildConnectionFormSchema() {
|
|
207745
|
-
return { sections: [{
|
|
207746
|
-
id: "matter-fabric",
|
|
207747
|
-
title: "Matter fabric",
|
|
207748
|
-
titleKey: "matter-broker:connection.s_matter-fabric.title",
|
|
207749
|
-
description: "CamStack owns a Matter fabric on this hub. Add wireless onboarding credentials only if you will commission Thread or BLE-Wi-Fi devices. On-network (Ethernet/Wi-Fi) Matter devices need none of these.",
|
|
207750
|
-
descriptionKey: "matter-broker:connection.s_matter-fabric.description",
|
|
207751
|
-
columns: 1,
|
|
207752
|
-
fields: [
|
|
207753
|
-
{
|
|
207754
|
-
type: "password",
|
|
207755
|
-
key: "threadDataset",
|
|
207756
|
-
secret: true,
|
|
207757
|
-
showToggle: true,
|
|
207758
|
-
label: "Thread operational dataset (hex, optional)",
|
|
207759
|
-
labelKey: "matter-broker:connection.s_matter-fabric.threadDataset.label",
|
|
207760
|
-
placeholder: "0e08000000000001…"
|
|
207761
|
-
},
|
|
207762
|
-
{
|
|
207763
|
-
type: "text",
|
|
207764
|
-
key: "wifiSsid",
|
|
207765
|
-
label: "Wi-Fi SSID (optional)",
|
|
207766
|
-
labelKey: "matter-broker:connection.s_matter-fabric.wifiSsid.label"
|
|
207767
|
-
},
|
|
207768
|
-
{
|
|
207769
|
-
type: "password",
|
|
207770
|
-
key: "wifiPassword",
|
|
207771
|
-
label: "Wi-Fi password (optional)",
|
|
207772
|
-
labelKey: "matter-broker:connection.s_matter-fabric.wifiPassword.label",
|
|
207773
|
-
showToggle: true
|
|
207774
|
-
},
|
|
207775
|
-
{
|
|
207776
|
-
type: "number",
|
|
207777
|
-
key: "reconcileIntervalMs",
|
|
207778
|
-
label: "Node reconcile interval (ms)",
|
|
207779
|
-
labelKey: "matter-broker:connection.s_matter-fabric.reconcileIntervalMs.label",
|
|
207780
|
-
min: 1e4,
|
|
207781
|
-
max: 864e5,
|
|
207782
|
-
default: 3e5
|
|
207783
|
-
}
|
|
207784
|
-
]
|
|
207785
|
-
}] };
|
|
207786
|
-
}
|
|
207787
|
-
//#endregion
|
|
207788
207980
|
//#region src/matter-commissioning.ts
|
|
207789
207981
|
/** A clear validation error for a malformed pairing code. */
|
|
207790
207982
|
var InvalidPairingCodeError = class extends Error {
|
|
@@ -207815,7 +208007,7 @@ function parsePairingCode(raw) {
|
|
|
207815
208007
|
normalized: digits
|
|
207816
208008
|
};
|
|
207817
208009
|
}
|
|
207818
|
-
/** True when a code parses cleanly (no throw). Convenience for
|
|
208010
|
+
/** True when a code parses cleanly (no throw). Convenience for validating the pasted code. */
|
|
207819
208011
|
function isValidPairingCode(raw) {
|
|
207820
208012
|
try {
|
|
207821
208013
|
parsePairingCode(raw);
|
|
@@ -207826,113 +208018,22 @@ function isValidPairingCode(raw) {
|
|
|
207826
208018
|
}
|
|
207827
208019
|
//#endregion
|
|
207828
208020
|
//#region src/matter-broker-provider.ts
|
|
207829
|
-
var MATTER_KIND = "matter";
|
|
207830
208021
|
/**
|
|
207831
|
-
* Construct the `broker` cap provider for the Matter addon.
|
|
207832
|
-
*
|
|
207833
|
-
* commissioning
|
|
207834
|
-
* `target: { action: 'commissionNode' }` and `payload: { pairingCode }`.
|
|
207835
|
-
* `
|
|
207836
|
-
* preserved on an empty round-trip. Fabric keys stay in matter.js storage.
|
|
208022
|
+
* Construct the `broker` cap provider for the Matter addon: the bus only (D1013). The fabrics
|
|
208023
|
+
* themselves are the `account` kind of the addon's `integration-instances` provider. The defining
|
|
208024
|
+
* Matter operation, commissioning, is surfaced through `publish` with
|
|
208025
|
+
* `target: { action: 'commissionNode' }` and `payload: { pairingCode }`. `threadDataset` and
|
|
208026
|
+
* `wifiPassword` are redacted on config reads. Fabric keys stay in matter.js storage.
|
|
207837
208027
|
*/
|
|
207838
208028
|
function buildMatterBrokerProvider(deps) {
|
|
207839
|
-
const {
|
|
208029
|
+
const { registry, getBrokers } = deps;
|
|
207840
208030
|
const owns = (id) => getBrokers().some((b) => b.id === id);
|
|
207841
208031
|
const redact = (entry) => publicMatterConnection(entry.connection);
|
|
207842
208032
|
return {
|
|
207843
|
-
list: async ({ kind }) => {
|
|
207844
|
-
if (kind && kind !== MATTER_KIND) return [];
|
|
207845
|
-
return registry.list().map((info) => ({
|
|
207846
|
-
...info,
|
|
207847
|
-
addonId: ownerAddonId
|
|
207848
|
-
}));
|
|
207849
|
-
},
|
|
207850
|
-
get: async ({ id }) => {
|
|
207851
|
-
const info = registry.get(id);
|
|
207852
|
-
return info ? {
|
|
207853
|
-
...info,
|
|
207854
|
-
addonId: ownerAddonId
|
|
207855
|
-
} : null;
|
|
207856
|
-
},
|
|
207857
|
-
listProviders: async () => [{
|
|
207858
|
-
addonId: ownerAddonId,
|
|
207859
|
-
kinds: [{
|
|
207860
|
-
kind: MATTER_KIND,
|
|
207861
|
-
label: "Matter"
|
|
207862
|
-
}]
|
|
207863
|
-
}],
|
|
207864
|
-
add: async ({ kind, name, settings }) => {
|
|
207865
|
-
if (kind !== MATTER_KIND) throw new Error(`matter-broker: only kind '${MATTER_KIND}' is handled here (got '${kind}')`);
|
|
207866
|
-
const entry = await registry.createEntry(name, settingsToMatterConnection(settings));
|
|
207867
|
-
await persistBrokers([...getBrokers(), entry]);
|
|
207868
|
-
return { id: entry.id };
|
|
207869
|
-
},
|
|
207870
|
-
remove: async ({ id }) => {
|
|
207871
|
-
if (!owns(id)) return;
|
|
207872
|
-
await registry.removeEntry(id);
|
|
207873
|
-
await cascadeRemoveDevices(id).catch((err) => {
|
|
207874
|
-
logger.warn("matter: broker cascade-remove threw", {
|
|
207875
|
-
tags: { brokerId: id },
|
|
207876
|
-
meta: { error: errMsg(err) }
|
|
207877
|
-
});
|
|
207878
|
-
});
|
|
207879
|
-
await persistBrokers(getBrokers().filter((b) => b.id !== id));
|
|
207880
|
-
},
|
|
207881
|
-
testConnection: async ({ id }) => {
|
|
207882
|
-
if (!owns(id)) return {
|
|
207883
|
-
ok: false,
|
|
207884
|
-
error: "unknown broker"
|
|
207885
|
-
};
|
|
207886
|
-
const info = registry.get(id);
|
|
207887
|
-
if (!info) return {
|
|
207888
|
-
ok: false,
|
|
207889
|
-
error: "unknown broker"
|
|
207890
|
-
};
|
|
207891
|
-
if (info.status === "connected") return {
|
|
207892
|
-
ok: true,
|
|
207893
|
-
latencyMs: info.lastCheckedAt ? Math.max(0, Date.now() - info.lastCheckedAt) : 0
|
|
207894
|
-
};
|
|
207895
|
-
return {
|
|
207896
|
-
ok: false,
|
|
207897
|
-
error: info.error ?? `status: ${info.status}`
|
|
207898
|
-
};
|
|
207899
|
-
},
|
|
207900
|
-
getSettings: async ({ id }) => {
|
|
207901
|
-
const entry = getBrokers().find((b) => b.id === id);
|
|
207902
|
-
return entry ? redact(entry) : null;
|
|
207903
|
-
},
|
|
207904
|
-
setSettings: async ({ id, settings }) => {
|
|
207905
|
-
if (!owns(id)) return;
|
|
207906
|
-
const existing = getBrokers().find((b) => b.id === id);
|
|
207907
|
-
if (!existing) return;
|
|
207908
|
-
const parsed = settingsToMatterConnection(settings);
|
|
207909
|
-
const merged = mergeMatterSecrets(existing.connection, parsed);
|
|
207910
|
-
const updated = await registry.updateEntry(id, merged);
|
|
207911
|
-
await persistBrokers(getBrokers().map((b) => b.id === id ? updated : b));
|
|
207912
|
-
},
|
|
207913
208033
|
getBrokerConfig: async ({ id }) => {
|
|
207914
208034
|
const entry = getBrokers().find((b) => b.id === id);
|
|
207915
208035
|
return entry ? redact(entry) : null;
|
|
207916
208036
|
},
|
|
207917
|
-
getSettingsSchema: async ({ kind }) => {
|
|
207918
|
-
if (kind !== MATTER_KIND) return null;
|
|
207919
|
-
return buildConnectionFormSchema();
|
|
207920
|
-
},
|
|
207921
|
-
testSettings: async ({ kind, settings }) => {
|
|
207922
|
-
if (kind !== MATTER_KIND) return {
|
|
207923
|
-
ok: false,
|
|
207924
|
-
error: `unsupported kind: ${kind}`
|
|
207925
|
-
};
|
|
207926
|
-
try {
|
|
207927
|
-
settingsToMatterConnection(settings);
|
|
207928
|
-
return { ok: true };
|
|
207929
|
-
} catch (err) {
|
|
207930
|
-
return {
|
|
207931
|
-
ok: false,
|
|
207932
|
-
error: errMsg(err)
|
|
207933
|
-
};
|
|
207934
|
-
}
|
|
207935
|
-
},
|
|
207936
208037
|
/**
|
|
207937
208038
|
* `publish` is the commissioning + control action channel.
|
|
207938
208039
|
* `target: { action: 'commissionNode' }`, `payload: { pairingCode }` →
|
|
@@ -208152,8 +208253,8 @@ function buildMatterAdoptionProvider(deps) {
|
|
|
208152
208253
|
//#region src/matter-broker-device-cascade.ts
|
|
208153
208254
|
/**
|
|
208154
208255
|
* Remove every adopted Matter PARENT device (children cascade via the kernel)
|
|
208155
|
-
* whose persisted config carries `{ system: 'matter', brokerId }`. Used
|
|
208156
|
-
*
|
|
208256
|
+
* whose persisted config carries `{ system: 'matter', brokerId }`. Used when a
|
|
208257
|
+
* fabric instance is deleted and when an integration is deleted. Best-effort per device; returns the
|
|
208157
208258
|
* count removed. Mirrors the Ecowitt cascade helper.
|
|
208158
208259
|
*/
|
|
208159
208260
|
async function cascadeRemoveDevicesForBroker(input) {
|
|
@@ -208793,20 +208894,26 @@ var matterBrokersEntryKind = defineEntryKind("brokers", {
|
|
|
208793
208894
|
title: "Matter brokers",
|
|
208794
208895
|
schema: entryBody({
|
|
208795
208896
|
...matterConnectionSchema.shape,
|
|
208897
|
+
/** Retired (D1012): the link lives on the integration's `connections` record. Only the migration reads it. */
|
|
208796
208898
|
integrationId: string$2().optional(),
|
|
208797
208899
|
enabled: boolean().optional()
|
|
208798
208900
|
}),
|
|
208799
208901
|
writeTrigger: "an operator adds, edits or removes a Matter fabric broker",
|
|
208800
208902
|
retiredSecretPaths: ["connection.threadDataset", "connection.wifiPassword"]
|
|
208801
208903
|
});
|
|
208904
|
+
/** A Matter integration: the link to the fabric (broker) it imports devices from. */
|
|
208905
|
+
var matterConnectionsEntryKind = declareBrokerLinkKind({
|
|
208906
|
+
title: "Matter integrations",
|
|
208907
|
+
writeTrigger: "an operator links a Matter integration to a Matter fabric"
|
|
208908
|
+
});
|
|
208802
208909
|
//#endregion
|
|
208803
|
-
//#region src/matter-instances.ts
|
|
208910
|
+
//#region src/matter-account-instances.ts
|
|
208804
208911
|
/**
|
|
208805
|
-
* The Matter fabrics as `integration-instances` (planning#408 P3): one instance per `brokers`
|
|
208912
|
+
* The Matter fabrics as `integration-instances` (planning#408 P3, D1013; the `account` kind of the composed provider, the integrations are `matter-instances.ts`): one instance per `brokers`
|
|
208806
208913
|
* record, over the SAME records and ids (`matter_NNN`) the `broker` cap serves. The body is flat.
|
|
208807
208914
|
* A fabric needs no sign-in; commissioning a device stays on the adoption flow, behind the fabric.
|
|
208808
208915
|
*/
|
|
208809
|
-
var
|
|
208916
|
+
var MATTER_ACCOUNT_KIND = "account";
|
|
208810
208917
|
function statusOf(info) {
|
|
208811
208918
|
if (info === null) return {
|
|
208812
208919
|
state: "unknown",
|
|
@@ -208826,17 +208933,13 @@ function statusOf(info) {
|
|
|
208826
208933
|
...info.error !== null ? { detail: info.error } : {}
|
|
208827
208934
|
};
|
|
208828
208935
|
}
|
|
208829
|
-
function
|
|
208936
|
+
function buildMatterAccountInstances(records, deps) {
|
|
208830
208937
|
return new RecordIntegrationInstances(records, matterBrokersEntryKind, {
|
|
208831
208938
|
description: {
|
|
208832
|
-
role: "
|
|
208939
|
+
role: "services",
|
|
208833
208940
|
instanceMode: "multiple",
|
|
208834
208941
|
kind: "connection",
|
|
208835
208942
|
canTest: true,
|
|
208836
|
-
kinds: [{
|
|
208837
|
-
id: MATTER_INSTANCE_KIND,
|
|
208838
|
-
label: "Matter"
|
|
208839
|
-
}],
|
|
208840
208943
|
allowRestoreId: true
|
|
208841
208944
|
},
|
|
208842
208945
|
formSchema: () => buildConnectionFormSchema(),
|
|
@@ -208848,7 +208951,7 @@ function buildMatterInstances(records, deps) {
|
|
|
208848
208951
|
reconcileIntervalMs: 3e5
|
|
208849
208952
|
}),
|
|
208850
208953
|
mintId: () => deps.mintId(),
|
|
208851
|
-
kindOf: () =>
|
|
208954
|
+
kindOf: () => MATTER_ACCOUNT_KIND,
|
|
208852
208955
|
health: (_body, id) => {
|
|
208853
208956
|
const info = deps.infoOf(id);
|
|
208854
208957
|
return {
|
|
@@ -208877,6 +208980,22 @@ function buildMatterInstances(records, deps) {
|
|
|
208877
208980
|
});
|
|
208878
208981
|
}
|
|
208879
208982
|
//#endregion
|
|
208983
|
+
//#region src/matter-instances.ts
|
|
208984
|
+
/**
|
|
208985
|
+
* The Matter integrations as `integration-instances` (D1012): one `connections` record per
|
|
208986
|
+
* integration, keyed by the registry id, whose body is the link to a Matter fabric (a `brokers`
|
|
208987
|
+
* record, edited on the Brokers page).
|
|
208988
|
+
*/
|
|
208989
|
+
var MATTER_BROKER_KIND = "matter";
|
|
208990
|
+
function buildMatterInstances(records) {
|
|
208991
|
+
return buildBrokerLinkInstances(records, {
|
|
208992
|
+
kind: matterConnectionsEntryKind,
|
|
208993
|
+
brokerKind: MATTER_BROKER_KIND,
|
|
208994
|
+
brokerLabel: "Matter fabric",
|
|
208995
|
+
onChanged: () => void 0
|
|
208996
|
+
});
|
|
208997
|
+
}
|
|
208998
|
+
//#endregion
|
|
208880
208999
|
//#region src/migrations/flatten-broker-records.ts
|
|
208881
209000
|
/**
|
|
208882
209001
|
* The Matter broker records keep their fabric settings flat (planning#408 P3): the nested
|
|
@@ -208900,6 +209019,21 @@ async function flattenBrokerRecords(ports) {
|
|
|
208900
209019
|
});
|
|
208901
209020
|
}
|
|
208902
209021
|
//#endregion
|
|
209022
|
+
//#region src/migrations/broker-links-to-connections.ts
|
|
209023
|
+
var BROKER_LINKS_TO_CONNECTIONS_ID = "matter-broker/2026-11-05-broker-links-to-connections";
|
|
209024
|
+
/** The broker records and the integration (`connections`) records of this addon. */
|
|
209025
|
+
function matterBrokerLinkPorts(records, warn) {
|
|
209026
|
+
return brokerLinkPortsOver({
|
|
209027
|
+
brokers: records.kind({
|
|
209028
|
+
name: matterBrokersEntryKind.name,
|
|
209029
|
+
declaration: matterBrokersEntryKind.declaration,
|
|
209030
|
+
schema: record(string$2(), unknown())
|
|
209031
|
+
}),
|
|
209032
|
+
connections: records.kind(matterConnectionsEntryKind),
|
|
209033
|
+
warn
|
|
209034
|
+
});
|
|
209035
|
+
}
|
|
209036
|
+
//#endregion
|
|
208903
209037
|
//#region src/migrations/index.ts
|
|
208904
209038
|
/** The `matter-broker` catalogue (D987): offered to the engine when the addon initialises. */
|
|
208905
209039
|
var ADDON_MIGRATIONS = [{
|
|
@@ -208917,6 +209051,21 @@ var ADDON_MIGRATIONS = [{
|
|
|
208917
209051
|
fixtureMark: "the integration link and the Wi-Fi SSID of each fabric"
|
|
208918
209052
|
},
|
|
208919
209053
|
procedure: flattenBrokerRecords
|
|
209054
|
+
}, {
|
|
209055
|
+
meta: {
|
|
209056
|
+
id: BROKER_LINKS_TO_CONNECTIONS_ID,
|
|
209057
|
+
owner: "matter-broker",
|
|
209058
|
+
level: "addon-state",
|
|
209059
|
+
seq: 2,
|
|
209060
|
+
dependsOn: [FLATTEN_BROKER_RECORDS_ID],
|
|
209061
|
+
store: "settings",
|
|
209062
|
+
scope: "cluster",
|
|
209063
|
+
onFailure: "block-owner",
|
|
209064
|
+
bodyKind: "procedure",
|
|
209065
|
+
description: "Write each integration a connections record naming its Matter fabric (keyed by the integration id), then clear the integration link on the fabric; stray registry-keyed fabric rows are counted and logged, never deleted",
|
|
209066
|
+
fixtureMark: "the name and the Wi-Fi SSID of the fabric that keeps its record"
|
|
209067
|
+
},
|
|
209068
|
+
procedure: moveBrokerLinksToConnections
|
|
208920
209069
|
}];
|
|
208921
209070
|
//#endregion
|
|
208922
209071
|
//#region src/matter-broker-store.ts
|
|
@@ -208930,7 +209079,6 @@ function rowToEntry(id, data) {
|
|
|
208930
209079
|
wifiPassword: data.wifiPassword,
|
|
208931
209080
|
reconcileIntervalMs: data.reconcileIntervalMs
|
|
208932
209081
|
},
|
|
208933
|
-
integrationId: data.integrationId,
|
|
208934
209082
|
enabled: data.enabled
|
|
208935
209083
|
});
|
|
208936
209084
|
return parsed.success ? parsed.data : null;
|
|
@@ -208967,11 +209115,10 @@ var MatterBrokerStore = class MatterBrokerStore {
|
|
|
208967
209115
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
208968
209116
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
208969
209117
|
for (const entry of entries) {
|
|
208970
|
-
const { id, name, connection,
|
|
209118
|
+
const { id, name, connection, enabled } = entry;
|
|
208971
209119
|
const body = {
|
|
208972
209120
|
name,
|
|
208973
209121
|
...connection,
|
|
208974
|
-
...integrationId === void 0 ? {} : { integrationId },
|
|
208975
209122
|
...enabled === void 0 ? {} : { enabled }
|
|
208976
209123
|
};
|
|
208977
209124
|
const parsed = matterBrokersEntryKind.schema.safeParse(body);
|
|
@@ -209009,6 +209156,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209009
209156
|
registry = null;
|
|
209010
209157
|
brokerStore = null;
|
|
209011
209158
|
instances = null;
|
|
209159
|
+
accountInstances = null;
|
|
209012
209160
|
activeBrokers = [];
|
|
209013
209161
|
constructor() {
|
|
209014
209162
|
super({ ...DEFAULTS });
|
|
@@ -209017,15 +209165,22 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209017
209165
|
const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
|
|
209018
209166
|
const records = this.ctx.records;
|
|
209019
209167
|
if (records === void 0) throw new Error("matter-broker: ctx.records is required (hub-only addon)");
|
|
209020
|
-
await records.declare({
|
|
209168
|
+
await records.declare({
|
|
209169
|
+
brokers: matterBrokersEntryKind,
|
|
209170
|
+
connections: matterConnectionsEntryKind
|
|
209171
|
+
});
|
|
209021
209172
|
this.brokerStore = MatterBrokerStore.of(records);
|
|
209022
|
-
this.instances = buildMatterInstances(records
|
|
209173
|
+
this.instances = buildMatterInstances(records);
|
|
209174
|
+
this.accountInstances = buildMatterAccountInstances(records, {
|
|
209023
209175
|
mintId: () => this.requireRegistry().allocateId(),
|
|
209024
209176
|
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
209025
209177
|
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
209026
209178
|
});
|
|
209027
209179
|
if (this.ctx.secrets === void 0) throw new Error("matter-broker: ctx.secrets is required");
|
|
209028
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS,
|
|
209180
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, {
|
|
209181
|
+
...flattenPortsOver(records.kind(matterBrokersEntryKind)),
|
|
209182
|
+
...matterBrokerLinkPorts(records, (message) => this.ctx.logger.warn(message))
|
|
209183
|
+
}));
|
|
209029
209184
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
209030
209185
|
this.registry = new MatterBrokerRegistry(this.ctx.logger, {
|
|
209031
209186
|
dataDir: this.ctx.dataDir,
|
|
@@ -209046,8 +209201,6 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209046
209201
|
matterCommands.setDispatcher((brokerId, invocation) => this.requireRegistry().invokeCommand(brokerId, invocation));
|
|
209047
209202
|
await this.registry.restore(this.activeBrokers);
|
|
209048
209203
|
this.ctx.logger.info("Matter: broker addon initialised", { meta: { brokerCount: this.activeBrokers.length } });
|
|
209049
|
-
await this.reconcileIntegrationsToBrokers();
|
|
209050
|
-
this.subscribeIntegrationLifecycle();
|
|
209051
209204
|
return [
|
|
209052
209205
|
...regs,
|
|
209053
209206
|
{
|
|
@@ -209056,7 +209209,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209056
209209
|
},
|
|
209057
209210
|
{
|
|
209058
209211
|
capability: integrationInstancesCapability,
|
|
209059
|
-
provider: this.
|
|
209212
|
+
provider: this.buildInstancesProvider()
|
|
209060
209213
|
},
|
|
209061
209214
|
{
|
|
209062
209215
|
capability: deviceAdoptionCapability,
|
|
@@ -209075,9 +209228,37 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209075
209228
|
if (this.instances === null) throw new Error("matter-broker: instances not ready");
|
|
209076
209229
|
return this.instances;
|
|
209077
209230
|
}
|
|
209231
|
+
/** One provider over the two record kinds: the fabric (broker) and the integrations using it. */
|
|
209232
|
+
buildInstancesProvider() {
|
|
209233
|
+
if (this.accountInstances === null) throw new Error("matter-broker: fabric instances not ready");
|
|
209234
|
+
return composeInstanceKinds([{
|
|
209235
|
+
kind: {
|
|
209236
|
+
id: "account",
|
|
209237
|
+
label: "Matter fabric",
|
|
209238
|
+
functionCap: "broker",
|
|
209239
|
+
brokerKind: "matter",
|
|
209240
|
+
role: "services"
|
|
209241
|
+
},
|
|
209242
|
+
provider: guardBrokerDelete(this.accountInstances.toProvider(), () => [brokerLinkUserFinder(this.requireInstances())])
|
|
209243
|
+
}, {
|
|
209244
|
+
kind: {
|
|
209245
|
+
id: "integration",
|
|
209246
|
+
label: "Matter",
|
|
209247
|
+
functionCap: "device-provider",
|
|
209248
|
+
role: "devices"
|
|
209249
|
+
},
|
|
209250
|
+
provider: this.requireInstances().toProvider()
|
|
209251
|
+
}], {
|
|
209252
|
+
role: "devices",
|
|
209253
|
+
instanceMode: "multiple",
|
|
209254
|
+
kind: "account",
|
|
209255
|
+
canTest: true,
|
|
209256
|
+
allowRestoreId: true
|
|
209257
|
+
});
|
|
209258
|
+
}
|
|
209078
209259
|
/**
|
|
209079
|
-
* Bring the runtime in line with one record after the instances page changed it: a
|
|
209080
|
-
* record stops its controller and cascades its devices, an off record only stops the
|
|
209260
|
+
* Bring the runtime in line with one fabric record after the instances page changed it: a
|
|
209261
|
+
* removed record stops its controller and cascades its devices, an off record only stops the
|
|
209081
209262
|
* controller, a live one re-applies its settings or starts under the record's own id.
|
|
209082
209263
|
*/
|
|
209083
209264
|
async syncBrokerRuntime(id) {
|
|
@@ -209095,10 +209276,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209095
209276
|
}
|
|
209096
209277
|
try {
|
|
209097
209278
|
if (reg.get(id) !== null) await reg.updateEntry(id, entry.connection);
|
|
209098
|
-
else await reg.createEntry(entry.name, entry.connection, {
|
|
209099
|
-
id,
|
|
209100
|
-
...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
|
|
209101
|
-
});
|
|
209279
|
+
else await reg.createEntry(entry.name, entry.connection, { id });
|
|
209102
209280
|
} catch (err) {
|
|
209103
209281
|
this.ctx.logger.warn("matter instance: could not start the fabric", { meta: {
|
|
209104
209282
|
brokerId: id,
|
|
@@ -209225,12 +209403,8 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209225
209403
|
}
|
|
209226
209404
|
buildBrokerProvider() {
|
|
209227
209405
|
return buildMatterBrokerProvider({
|
|
209228
|
-
ownerAddonId: this.ctx.id,
|
|
209229
209406
|
registry: this.requireRegistry(),
|
|
209230
|
-
getBrokers: () => this.activeBrokers
|
|
209231
|
-
persistBrokers: (brokers) => this.persistBrokerEntries(brokers),
|
|
209232
|
-
cascadeRemoveDevices: (brokerId) => this.cascadeRemoveDevicesForBroker(brokerId),
|
|
209233
|
-
logger: this.ctx.logger
|
|
209407
|
+
getBrokers: () => this.activeBrokers
|
|
209234
209408
|
});
|
|
209235
209409
|
}
|
|
209236
209410
|
async cascadeRemoveDevicesForBroker(brokerId) {
|
|
@@ -209245,42 +209419,6 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209245
209419
|
logger: this.ctx.logger
|
|
209246
209420
|
});
|
|
209247
209421
|
}
|
|
209248
|
-
async reconcileIntegrationsToBrokers() {
|
|
209249
|
-
try {
|
|
209250
|
-
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
209251
|
-
const surviving = new Set(mine.map((i) => i.id));
|
|
209252
|
-
for (const broker of this.activeBrokers) {
|
|
209253
|
-
const integrationId = broker.integrationId;
|
|
209254
|
-
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
209255
|
-
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
209256
|
-
}
|
|
209257
|
-
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
209258
|
-
if (toRemove.length === 0) return;
|
|
209259
|
-
for (const id of toRemove) try {
|
|
209260
|
-
await this.requireRegistry().removeEntry(id);
|
|
209261
|
-
await this.cascadeRemoveDevicesForBroker(id);
|
|
209262
|
-
} catch (err) {
|
|
209263
|
-
this.ctx.logger.warn("Matter integration→broker: broker cleanup failed", { meta: {
|
|
209264
|
-
brokerId: id,
|
|
209265
|
-
error: errMsg(err)
|
|
209266
|
-
} });
|
|
209267
|
-
}
|
|
209268
|
-
const nextBrokers = this.activeBrokers.filter((b) => !toRemove.includes(b.id));
|
|
209269
|
-
await this.persistBrokerEntries(nextBrokers);
|
|
209270
|
-
} catch (err) {
|
|
209271
|
-
this.ctx.logger.warn("Matter integration→broker reconcile failed", { meta: { error: errMsg(err) } });
|
|
209272
|
-
}
|
|
209273
|
-
}
|
|
209274
|
-
subscribeIntegrationLifecycle() {
|
|
209275
|
-
const handler = (event) => {
|
|
209276
|
-
const addonId = event.data["addonId"];
|
|
209277
|
-
if (typeof addonId === "string" && addonId !== this.ctx.id) return;
|
|
209278
|
-
this.reconcileIntegrationsToBrokers();
|
|
209279
|
-
};
|
|
209280
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationEnabled }, handler);
|
|
209281
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
|
|
209282
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
|
|
209283
|
-
}
|
|
209284
209422
|
buildAdoptionProvider() {
|
|
209285
209423
|
return buildMatterAdoptionProvider({
|
|
209286
209424
|
registry: {
|
|
@@ -209293,9 +209431,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209293
209431
|
},
|
|
209294
209432
|
logger: this.ctx.logger,
|
|
209295
209433
|
getBrokerIdForIntegration: async (id) => {
|
|
209296
|
-
const
|
|
209297
|
-
if (
|
|
209298
|
-
return
|
|
209434
|
+
const brokerId = await brokerIdOf(this.requireInstances(), id);
|
|
209435
|
+
if (brokerId === void 0) throw new Error(`integration ${id} has no broker`);
|
|
209436
|
+
return brokerId;
|
|
209299
209437
|
},
|
|
209300
209438
|
listAdoptedMatter: async () => {
|
|
209301
209439
|
const reg = this.ctx.kernel.deviceRegistry;
|
|
@@ -209346,22 +209484,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209346
209484
|
}
|
|
209347
209485
|
});
|
|
209348
209486
|
}
|
|
209349
|
-
|
|
209350
|
-
|
|
209351
|
-
|
|
209352
|
-
title: "Matter fabric",
|
|
209353
|
-
titleKey: "matter-broker:globalSettings.s_matter-fabric.title",
|
|
209354
|
-
description: "Matter fabrics are managed in the External systems → Brokers tab.",
|
|
209355
|
-
descriptionKey: "matter-broker:globalSettings.s_matter-fabric.description",
|
|
209356
|
-
columns: 1,
|
|
209357
|
-
fields: [{
|
|
209358
|
-
type: "info",
|
|
209359
|
-
key: "fabricHelp",
|
|
209360
|
-
label: "Fabrics are managed separately",
|
|
209361
|
-
labelKey: "matter-broker:globalSettings.s_matter-fabric.fabricHelp.label",
|
|
209362
|
-
content: "This integration links to a Matter fabric broker. Add, edit, or remove fabrics from External systems → Brokers, then commission devices into the fabric. The integration only stores a reference to its broker."
|
|
209363
|
-
}]
|
|
209364
|
-
}] });
|
|
209487
|
+
/** The connection form a record carries; the values live on the record, never in `addon-settings`. */
|
|
209488
|
+
async integrationFormSchema(_instance) {
|
|
209489
|
+
return buildConnectionFormSchema();
|
|
209365
209490
|
}
|
|
209366
209491
|
async supportsManualCreation() {
|
|
209367
209492
|
return false;
|