@camstack/addon-matter-broker 0.2.159 → 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 +8 -0
- package/dist/addon.js +912 -788
- package/dist/addon.mjs +912 -788
- package/package.json +3 -3
package/dist/addon.mjs
CHANGED
|
@@ -5993,7 +5993,7 @@ var ZodIssueCode = {
|
|
|
5993
5993
|
var ZodFirstPartyTypeKind;
|
|
5994
5994
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5995
5995
|
//#endregion
|
|
5996
|
-
//#region ../types/dist/sleep-
|
|
5996
|
+
//#region ../types/dist/sleep-BX_MqzUe.mjs
|
|
5997
5997
|
/**
|
|
5998
5998
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5999
5999
|
* window to float samples (D455).
|
|
@@ -7622,6 +7622,28 @@ var SECRET_RETRY_MAX_MS = 5 * 6e4;
|
|
|
7622
7622
|
/** Coalesces a burst of `keychain.changed` events into one re-resolve. */
|
|
7623
7623
|
var SECRET_CHANGE_DEBOUNCE_MS = 250;
|
|
7624
7624
|
/**
|
|
7625
|
+
* Pair a catalogue with the function that builds its ports FROM THE MIGRATION API. The one
|
|
7626
|
+
* place a catalogue is bound: a migration reaches nothing a ports binder cannot reach
|
|
7627
|
+
* through `api` (guard `check-migration-ports-surface`).
|
|
7628
|
+
*/
|
|
7629
|
+
function migrationSet(catalogue, bindPorts) {
|
|
7630
|
+
return { bind: (api) => defineAddonMigrations(catalogue, bindPorts(api)) };
|
|
7631
|
+
}
|
|
7632
|
+
/** A mixed array is a mistake: the other kind would be dropped without a word. */
|
|
7633
|
+
function refuseMixed(form, kept, dropped) {
|
|
7634
|
+
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`);
|
|
7635
|
+
}
|
|
7636
|
+
function catalogueOf(list) {
|
|
7637
|
+
const kept = list.filter((entry) => !("execute" in entry));
|
|
7638
|
+
refuseMixed("catalogue, (api) => ports", kept.length, list.length - kept.length);
|
|
7639
|
+
return kept;
|
|
7640
|
+
}
|
|
7641
|
+
function boundOf(list) {
|
|
7642
|
+
const kept = list.filter((entry) => "execute" in entry);
|
|
7643
|
+
refuseMixed("bound migrations", kept.length, list.length - kept.length);
|
|
7644
|
+
return kept;
|
|
7645
|
+
}
|
|
7646
|
+
/**
|
|
7625
7647
|
* Base class for CamStack addons. Eliminates settings boilerplate:
|
|
7626
7648
|
*
|
|
7627
7649
|
* - Typed `config` property with automatic resolution from store + defaults
|
|
@@ -7788,23 +7810,21 @@ var BaseAddon = class {
|
|
|
7788
7810
|
await this.loadScopedSchemas();
|
|
7789
7811
|
return normalized;
|
|
7790
7812
|
}
|
|
7813
|
+
async runMigrations(first, second, third) {
|
|
7814
|
+
if (typeof second === "function") return this.runMigrationSets([migrationSet(catalogueOf(first), second)], third);
|
|
7815
|
+
return this.runBound(boundOf(first), second);
|
|
7816
|
+
}
|
|
7791
7817
|
/**
|
|
7792
|
-
*
|
|
7793
|
-
*
|
|
7794
|
-
*
|
|
7795
|
-
* restored devices — with the catalogue bound to the addon's ports
|
|
7796
|
-
* (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
|
|
7797
|
-
*
|
|
7798
|
-
* An engine that cannot be reached or refuses the catalogue is logged and runs
|
|
7799
|
-
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7800
|
-
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7801
|
-
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7802
|
-
*
|
|
7803
|
-
* ALWAYS pass the whole catalogue; a phase that must run only some of it passes
|
|
7804
|
-
* `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
|
|
7805
|
-
* omitted applied ids "newer").
|
|
7818
|
+
* Several catalogues of ONE owner in one declaration (the engine replaces an owner's
|
|
7819
|
+
* catalogue on every declare, so two calls would each judge the other's ids "newer").
|
|
7820
|
+
* Build each entry with `migrationSet(catalogue, (api) => ports)`.
|
|
7806
7821
|
*/
|
|
7807
|
-
async
|
|
7822
|
+
async runMigrationSets(sets, options) {
|
|
7823
|
+
const api = this.ctx.migrationApi;
|
|
7824
|
+
if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
|
|
7825
|
+
return this.runBound(sets.flatMap((set) => set.bind(api)), options);
|
|
7826
|
+
}
|
|
7827
|
+
async runBound(migrations, options) {
|
|
7808
7828
|
const api = this.ctx.api.migrations;
|
|
7809
7829
|
const report = await runAddonMigrations({
|
|
7810
7830
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
@@ -13506,26 +13526,16 @@ var RecordIntegrationInstances = class {
|
|
|
13506
13526
|
...this.hooks.description,
|
|
13507
13527
|
mintsId: true
|
|
13508
13528
|
};
|
|
13509
|
-
|
|
13510
|
-
* The record an id names. A caller holding the `integrations` registry id (the Integrations
|
|
13511
|
-
* page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
|
|
13512
|
-
* record id answers as it is. An id that names nothing comes back unchanged.
|
|
13513
|
-
*/
|
|
13514
|
-
async resolveRecordId(id) {
|
|
13515
|
-
if (await this.handle.get(id) !== null) return id;
|
|
13516
|
-
const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
|
|
13517
|
-
return linked === void 0 ? id : linked.id;
|
|
13518
|
-
}
|
|
13519
|
-
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
|
|
13529
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13520
13530
|
listInstances = async () => {
|
|
13521
13531
|
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13522
13532
|
};
|
|
13523
13533
|
getInstance = async ({ id }) => {
|
|
13524
|
-
const record = await this.handle.get(
|
|
13534
|
+
const record = await this.handle.get(id);
|
|
13525
13535
|
return record === null ? null : this.viewOf(record);
|
|
13526
13536
|
};
|
|
13527
13537
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13528
|
-
createInstance = async ({ id, name, kind }) => {
|
|
13538
|
+
createInstance = async ({ id, name, kind, values }) => {
|
|
13529
13539
|
const minted = id ?? this.hooks.mintId?.({
|
|
13530
13540
|
name,
|
|
13531
13541
|
...kind !== void 0 ? { kind } : {}
|
|
@@ -13535,11 +13545,20 @@ var RecordIntegrationInstances = class {
|
|
|
13535
13545
|
id: minted,
|
|
13536
13546
|
data: this.hooks.initialBody(name, kind)
|
|
13537
13547
|
}, { ifRev: 0 });
|
|
13548
|
+
const patch = this.patchOf(values ?? {});
|
|
13549
|
+
if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
|
|
13538
13550
|
await this.hooks.onChanged?.(minted, "created");
|
|
13539
13551
|
return { id: minted };
|
|
13540
13552
|
};
|
|
13541
|
-
updateInstance = async ({ id
|
|
13542
|
-
const
|
|
13553
|
+
updateInstance = async ({ id, name, values }) => {
|
|
13554
|
+
const patch = this.patchOf(values);
|
|
13555
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13556
|
+
await this.handle.patch(id, patch);
|
|
13557
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13558
|
+
return { ok: true };
|
|
13559
|
+
};
|
|
13560
|
+
/** The record patch a form `values` object means: secrets kept, cleared or replaced. */
|
|
13561
|
+
patchOf(values) {
|
|
13543
13562
|
const patch = {};
|
|
13544
13563
|
for (const [key, value] of Object.entries(values)) {
|
|
13545
13564
|
if (value === void 0) continue;
|
|
@@ -13550,11 +13569,8 @@ var RecordIntegrationInstances = class {
|
|
|
13550
13569
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13551
13570
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13552
13571
|
}
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
await this.hooks.onChanged?.(id, "updated");
|
|
13556
|
-
return { ok: true };
|
|
13557
|
-
};
|
|
13572
|
+
return patch;
|
|
13573
|
+
}
|
|
13558
13574
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13559
13575
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13560
13576
|
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
@@ -13574,10 +13590,7 @@ var RecordIntegrationInstances = class {
|
|
|
13574
13590
|
status: "unsupported",
|
|
13575
13591
|
error: "This integration declares no connection test"
|
|
13576
13592
|
};
|
|
13577
|
-
return this.hooks.test(input
|
|
13578
|
-
...input,
|
|
13579
|
-
id: await this.resolveRecordId(input.id)
|
|
13580
|
-
});
|
|
13593
|
+
return this.hooks.test(input);
|
|
13581
13594
|
};
|
|
13582
13595
|
/** The flat provider object to hand to `registerProvider`. */
|
|
13583
13596
|
toProvider() {
|
|
@@ -13594,6 +13607,76 @@ var RecordIntegrationInstances = class {
|
|
|
13594
13607
|
};
|
|
13595
13608
|
}
|
|
13596
13609
|
};
|
|
13610
|
+
/** No part holds the id a call named. */
|
|
13611
|
+
var UnknownInstanceError = class extends Error {
|
|
13612
|
+
instanceId;
|
|
13613
|
+
constructor(instanceId) {
|
|
13614
|
+
super(`Unknown instance '${instanceId}'`);
|
|
13615
|
+
this.instanceId = instanceId;
|
|
13616
|
+
this.name = "UnknownInstanceError";
|
|
13617
|
+
}
|
|
13618
|
+
};
|
|
13619
|
+
/** A create or a form named no kind, or a kind no part serves. */
|
|
13620
|
+
var UnknownInstanceKindError = class extends Error {
|
|
13621
|
+
kindId;
|
|
13622
|
+
constructor(kindId) {
|
|
13623
|
+
super(kindId === void 0 ? "This provider has several kinds: name one" : `Unknown instance kind '${kindId}'`);
|
|
13624
|
+
this.kindId = kindId;
|
|
13625
|
+
this.name = "UnknownInstanceKindError";
|
|
13626
|
+
}
|
|
13627
|
+
};
|
|
13628
|
+
function composeInstanceKinds(parts, base) {
|
|
13629
|
+
const byKind = (kindId) => {
|
|
13630
|
+
if (kindId === void 0) {
|
|
13631
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
13632
|
+
if (only === void 0) throw new UnknownInstanceKindError(void 0);
|
|
13633
|
+
return only;
|
|
13634
|
+
}
|
|
13635
|
+
const part = parts.find((candidate) => candidate.kind.id === kindId);
|
|
13636
|
+
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13637
|
+
return part;
|
|
13638
|
+
};
|
|
13639
|
+
const holderOf = async (id) => {
|
|
13640
|
+
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13641
|
+
throw new UnknownInstanceError(id);
|
|
13642
|
+
};
|
|
13643
|
+
const tagged = (part, instance) => ({
|
|
13644
|
+
...instance,
|
|
13645
|
+
kind: part.kind.id
|
|
13646
|
+
});
|
|
13647
|
+
return {
|
|
13648
|
+
describe: async () => ({
|
|
13649
|
+
...base,
|
|
13650
|
+
kinds: parts.map((part) => part.kind)
|
|
13651
|
+
}),
|
|
13652
|
+
instanceFormSchema: async ({ id, kind }) => {
|
|
13653
|
+
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13654
|
+
},
|
|
13655
|
+
listInstances: async () => {
|
|
13656
|
+
return (await Promise.all(parts.map(async (part) => (await part.provider.listInstances()).map((instance) => tagged(part, instance))))).flat();
|
|
13657
|
+
},
|
|
13658
|
+
getInstance: async ({ id }) => {
|
|
13659
|
+
for (const part of parts) {
|
|
13660
|
+
const found = await part.provider.getInstance({ id });
|
|
13661
|
+
if (found !== null) return tagged(part, found);
|
|
13662
|
+
}
|
|
13663
|
+
return null;
|
|
13664
|
+
},
|
|
13665
|
+
createInstance: async (input) => byKind(input.kind).provider.createInstance(input),
|
|
13666
|
+
updateInstance: async (input) => (await holderOf(input.id)).provider.updateInstance(input),
|
|
13667
|
+
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13668
|
+
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13669
|
+
testInstance: async (input) => {
|
|
13670
|
+
const test = (input.id === null ? byKind(input.kind) : await holderOf(input.id)).provider.testInstance;
|
|
13671
|
+
if (test === void 0) return {
|
|
13672
|
+
success: false,
|
|
13673
|
+
status: "inconclusive",
|
|
13674
|
+
error: "This kind cannot be tested"
|
|
13675
|
+
};
|
|
13676
|
+
return test(input);
|
|
13677
|
+
}
|
|
13678
|
+
};
|
|
13679
|
+
}
|
|
13597
13680
|
var EncodeProfileSchema = object({
|
|
13598
13681
|
video: object({
|
|
13599
13682
|
codec: _enum([
|
|
@@ -15808,6 +15891,39 @@ var UnstampedEventMediaCountSchema = object({
|
|
|
15808
15891
|
/** Sum across both, or `null` when either lane could not be counted. */
|
|
15809
15892
|
total: number().int().nonnegative().nullable()
|
|
15810
15893
|
}).nullable();
|
|
15894
|
+
/**
|
|
15895
|
+
* Where the stored event media sits, per storage location: how many media rows
|
|
15896
|
+
* and how many bytes carry each location id, how many carry none (legacy,
|
|
15897
|
+
* resolved to the default `eventMedia` location), and how many name a location
|
|
15898
|
+
* that was not asked about (a deleted or never-listed one).
|
|
15899
|
+
*
|
|
15900
|
+
* One covering-index aggregate per asked location plus the NULL one, and one
|
|
15901
|
+
* unfiltered total that scans the covering index: O(rows), not a walk of the
|
|
15902
|
+
* rows themselves. `unlisted` is total minus the ASKED locations minus NULL, so
|
|
15903
|
+
* it is a deleted location only when every location id was asked.
|
|
15904
|
+
* `null` is "could not be measured", never zero.
|
|
15905
|
+
*/
|
|
15906
|
+
var MediaLocationCensusInputSchema = object({ locationIds: array(string$2().min(1)).max(50).readonly() });
|
|
15907
|
+
var MediaLocationCensusSchema = object({
|
|
15908
|
+
locations: array(object({
|
|
15909
|
+
locationId: string$2(),
|
|
15910
|
+
rows: number().int().nonnegative(),
|
|
15911
|
+
bytes: number().nonnegative()
|
|
15912
|
+
})).readonly(),
|
|
15913
|
+
unstamped: object({
|
|
15914
|
+
rows: number().int().nonnegative(),
|
|
15915
|
+
bytes: number().nonnegative()
|
|
15916
|
+
}),
|
|
15917
|
+
/** Rows stamped with a location id that was not in the asked list. */
|
|
15918
|
+
unlisted: object({
|
|
15919
|
+
rows: number().int().nonnegative(),
|
|
15920
|
+
bytes: number().nonnegative()
|
|
15921
|
+
}),
|
|
15922
|
+
total: object({
|
|
15923
|
+
rows: number().int().nonnegative(),
|
|
15924
|
+
bytes: number().nonnegative()
|
|
15925
|
+
})
|
|
15926
|
+
});
|
|
15811
15927
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string$2().min(1) });
|
|
15812
15928
|
/** The independently selectable logical storage classes — every class
|
|
15813
15929
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -46844,52 +46960,7 @@ onBrightnessChanged: { data: object({
|
|
|
46844
46960
|
minIntervalMs: 0
|
|
46845
46961
|
} } }
|
|
46846
46962
|
};
|
|
46847
|
-
|
|
46848
|
-
* `broker` — unified pub/sub broker registry, system-scoped collection.
|
|
46849
|
-
*
|
|
46850
|
-
* The cap models any kind-tagged message broker the user wants to
|
|
46851
|
-
* register and that other addons might consume. The first two kinds
|
|
46852
|
-
* are MQTT (mosquitto / aedes embedded / cloud bridge) and
|
|
46853
|
-
* Home Assistant (HA WebSocket — subscribe_entities + call_service).
|
|
46854
|
-
* Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
|
|
46855
|
-
* the same surface.
|
|
46856
|
-
*
|
|
46857
|
-
* Why one cap, not one cap per kind:
|
|
46858
|
-
* - The integrations page wants a single table of "every broker"
|
|
46859
|
-
* across kinds; a unified cap drives it without join logic.
|
|
46860
|
-
* - Generic admin operations (add / remove / test / status) are
|
|
46861
|
-
* identical across kinds — duplicating them per cap was busywork.
|
|
46862
|
-
* - The `network-access` cap precedent: same interface, many
|
|
46863
|
-
* `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
|
|
46864
|
-
*
|
|
46865
|
-
* What stays kind-specific:
|
|
46866
|
-
* - `getBrokerConfig` payload (kind decides whether it returns
|
|
46867
|
-
* mqtt URL+creds, HA baseUrl+token, …) — typed as
|
|
46868
|
-
* `Record<string, unknown>` at the wire; consumers narrow.
|
|
46869
|
-
* - `publish` / `subscribe` / `getState` arguments (target / filter /
|
|
46870
|
-
* key) — also typed as `Record<string, unknown>` so each kind can
|
|
46871
|
-
* evolve its surface (MQTT topic+qos vs HA entity_id+domain)
|
|
46872
|
-
* without rebreaking the cap signature.
|
|
46873
|
-
* - `add` `settings` payload (different fields per kind, validated by
|
|
46874
|
-
* the kind-specific provider).
|
|
46875
|
-
*
|
|
46876
|
-
* Bidirectionality:
|
|
46877
|
-
* - `publish` is RPC — synchronous result (provider-defined).
|
|
46878
|
-
* - `subscribe` returns a subscription handle; the actual message
|
|
46879
|
-
* stream flows over the typed event-bus as `broker.message` events
|
|
46880
|
-
* keyed by `(brokerId, subscriptionId)`. Consumers filter in their
|
|
46881
|
-
* event handler. Subscriptions persist across reconnects — the
|
|
46882
|
-
* provider re-subscribes upstream after a transport drop.
|
|
46883
|
-
*
|
|
46884
|
-
* Why event-bus push (not a subscription RPC stream):
|
|
46885
|
-
* - Matches the rest of CamStack's D8 contract — events for telemetry,
|
|
46886
|
-
* RPC for loss-is-a-bug. Broker messages are telemetry (a single
|
|
46887
|
-
* drop is recoverable via `getState({key})`).
|
|
46888
|
-
* - Frees `broker.subscribe` from holding a long-lived RPC channel.
|
|
46889
|
-
* - Cross-process: events already route through `$event-bus`; no
|
|
46890
|
-
* per-broker wire-shaping needed.
|
|
46891
|
-
*/
|
|
46892
|
-
var BrokerStatusEnum = _enum([
|
|
46963
|
+
_enum([
|
|
46893
46964
|
"connected",
|
|
46894
46965
|
"disconnected",
|
|
46895
46966
|
"connecting",
|
|
@@ -46897,141 +46968,12 @@ var BrokerStatusEnum = _enum([
|
|
|
46897
46968
|
"unreachable",
|
|
46898
46969
|
"error"
|
|
46899
46970
|
]);
|
|
46900
|
-
var BrokerInfoSchema = object({
|
|
46901
|
-
/** Stable broker id. Persisted; survives addon restarts. */
|
|
46902
|
-
id: string$2(),
|
|
46903
|
-
/** Addon id of the provider that OWNS this broker.
|
|
46904
|
-
*
|
|
46905
|
-
* The `broker` cap is a system-scoped collection: several addons
|
|
46906
|
-
* register a `broker` provider, each owning a DISJOINT set of
|
|
46907
|
-
* brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
|
|
46908
|
-
* `ha_*`). A broker therefore belongs to exactly one addon — like a
|
|
46909
|
-
* device belongs to one integration. The admin UI threads this id
|
|
46910
|
-
* back as the `{ addonId }` system-collection selector on every
|
|
46911
|
-
* id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
|
|
46912
|
-
* `testConnection`), so the call routes to the OWNING provider
|
|
46913
|
-
* instead of defaulting to the first-registered one. */
|
|
46914
|
-
addonId: string$2(),
|
|
46915
|
-
/** Human-readable name (operator-chosen at add-time). */
|
|
46916
|
-
name: string$2(),
|
|
46917
|
-
/** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
|
|
46918
|
-
kind: string$2(),
|
|
46919
|
-
status: BrokerStatusEnum,
|
|
46920
|
-
/** Free-form provider-specific info (HA version, MQTT broker
|
|
46921
|
-
* flavour, latency, mTLS active, …). */
|
|
46922
|
-
info: record(string$2(), unknown()),
|
|
46923
|
-
/** Ms epoch of the last connection probe / status update. */
|
|
46924
|
-
lastCheckedAt: number().nullable(),
|
|
46925
|
-
/** Last error message, when `status` indicates failure. */
|
|
46926
|
-
error: string$2().nullable()
|
|
46927
|
-
});
|
|
46928
46971
|
var RegistryStatusSchema = object({
|
|
46929
46972
|
brokerCount: number().int().nonnegative(),
|
|
46930
46973
|
connectedCount: number().int().nonnegative()
|
|
46931
46974
|
});
|
|
46932
|
-
/**
|
|
46933
|
-
* One entry per `broker` provider — which addon provides which broker
|
|
46934
|
-
* kind(s). The unified create picker reads this to know, for each
|
|
46935
|
-
* (addonId, kind) pair the operator can pick, where to route the
|
|
46936
|
-
* follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
|
|
46937
|
-
* `{ addonId }` selector). A provider only advertises a kind here when
|
|
46938
|
-
* it implements `getSettingsSchema` for that kind (so the picker never
|
|
46939
|
-
* offers a kind whose creation form can't be rendered).
|
|
46940
|
-
*/
|
|
46941
|
-
var BrokerProviderInfoSchema = object({
|
|
46942
|
-
/** Addon id of the `broker` provider this entry describes. */
|
|
46943
|
-
addonId: string$2(),
|
|
46944
|
-
/** Broker kinds this provider can create, with a display label. */
|
|
46945
|
-
kinds: array(object({
|
|
46946
|
-
kind: string$2(),
|
|
46947
|
-
label: string$2(),
|
|
46948
|
-
/**
|
|
46949
|
-
* Relative hub path to start an interactive account link (OAuth popup).
|
|
46950
|
-
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
46951
|
-
*/
|
|
46952
|
-
interactiveConnectPath: string$2().min(1).optional(),
|
|
46953
|
-
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
46954
|
-
interactiveDisconnectPath: string$2().min(1).optional(),
|
|
46955
|
-
/**
|
|
46956
|
-
* The kind signs in through a server-held interactive session instead of
|
|
46957
|
-
* (or before) typed credentials. `basePath` is a hub-relative route set
|
|
46958
|
-
* served by the owning addon (`/addon/<id>/...`) speaking the sign-in
|
|
46959
|
-
* contract: `POST {basePath}/start` (body: the form's settings) answers
|
|
46960
|
-
* `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
|
|
46961
|
-
* `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
|
|
46962
|
-
* 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
|
|
46963
|
-
* (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
|
|
46964
|
-
* existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
|
|
46965
|
-
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
46966
|
-
* offers it as the default path; the fields the session fills are
|
|
46967
|
-
* marked `filledBySignIn` in the kind's settings schema.
|
|
46968
|
-
*
|
|
46969
|
-
* `methods` lists the ways the session can sign in, recommended first:
|
|
46970
|
-
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
46971
|
-
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
46972
|
-
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
46973
|
-
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
46974
|
-
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
46975
|
-
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
46976
|
-
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
46977
|
-
* `['proxy']`.
|
|
46978
|
-
*/
|
|
46979
|
-
signIn: object({
|
|
46980
|
-
basePath: string$2().min(1),
|
|
46981
|
-
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
46982
|
-
}).optional()
|
|
46983
|
-
}))
|
|
46984
|
-
});
|
|
46985
|
-
var ListInputSchema = object({
|
|
46986
|
-
/** Optional kind filter — `list({kind:'home-assistant'})` returns
|
|
46987
|
-
* only HA brokers. Omit to list every broker. */
|
|
46988
|
-
kind: string$2().optional() });
|
|
46989
46975
|
var GetInputSchema = object({ id: string$2() });
|
|
46990
|
-
var AddInputSchema = object({
|
|
46991
|
-
kind: string$2().min(1),
|
|
46992
|
-
name: string$2().min(1),
|
|
46993
|
-
/**
|
|
46994
|
-
* ADOPT an existing id instead of minting a new one.
|
|
46995
|
-
*
|
|
46996
|
-
* Written the day this cost an outage. A broker's id is not a detail: HA
|
|
46997
|
-
* devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
|
|
46998
|
-
* lost from config and re-added as `ha_001` leaves every one of its devices
|
|
46999
|
-
* bound to a broker that no longer exists. Re-entering the password under the
|
|
47000
|
-
* ORIGINAL id turns a multi-step device migration back into re-entering a
|
|
47001
|
-
* password.
|
|
47002
|
-
*
|
|
47003
|
-
* A provider MUST refuse an id that is already in use — adopting a live
|
|
47004
|
-
* broker's id would silently take it over.
|
|
47005
|
-
*/
|
|
47006
|
-
id: string$2().min(1).optional(),
|
|
47007
|
-
/** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
|
|
47008
|
-
* `{baseUrl,accessToken}`). Validated by the kind-specific provider
|
|
47009
|
-
* branch on receipt — invalid shape rejects the add. */
|
|
47010
|
-
settings: record(string$2(), unknown())
|
|
47011
|
-
});
|
|
47012
|
-
var AddResultSchema = object({ id: string$2() });
|
|
47013
|
-
var RemoveInputSchema = object({ id: string$2() });
|
|
47014
|
-
var TestConnectionResultSchema = discriminatedUnion("ok", [object({
|
|
47015
|
-
ok: literal(true),
|
|
47016
|
-
latencyMs: number().nonnegative()
|
|
47017
|
-
}), object({
|
|
47018
|
-
ok: literal(false),
|
|
47019
|
-
error: string$2()
|
|
47020
|
-
})]);
|
|
47021
46976
|
var SettingsRecordSchema$1 = record(string$2(), unknown());
|
|
47022
|
-
var SettingsSchemaInputSchema = object({ kind: string$2() });
|
|
47023
|
-
var TestSettingsInputSchema = object({
|
|
47024
|
-
kind: string$2(),
|
|
47025
|
-
settings: SettingsRecordSchema$1
|
|
47026
|
-
});
|
|
47027
|
-
var TestSettingsResultSchema = discriminatedUnion("ok", [object({
|
|
47028
|
-
ok: literal(true),
|
|
47029
|
-
latencyMs: number().nonnegative().optional()
|
|
47030
|
-
}).strict(), object({
|
|
47031
|
-
ok: literal(false),
|
|
47032
|
-
error: string$2()
|
|
47033
|
-
})]);
|
|
47034
|
-
var SettingsSchemaResultSchema = unknown().nullable();
|
|
47035
46977
|
var PublishInputSchema = object({
|
|
47036
46978
|
brokerId: string$2(),
|
|
47037
46979
|
/** Kind-specific routing target.
|
|
@@ -47075,51 +47017,12 @@ var brokerCapability = {
|
|
|
47075
47017
|
kind: "poll"
|
|
47076
47018
|
},
|
|
47077
47019
|
methods: {
|
|
47078
|
-
list: method(ListInputSchema, array(BrokerInfoSchema)),
|
|
47079
|
-
get: method(GetInputSchema, BrokerInfoSchema.nullable()),
|
|
47080
|
-
/** Enumerate which addon provides which broker kind(s) for the
|
|
47081
|
-
* unified create picker. The auto-mount fans this array across
|
|
47082
|
-
* every registered `broker` provider (array-output method), so the
|
|
47083
|
-
* picker sees every kind from every provider in one call. */
|
|
47084
|
-
listProviders: method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }),
|
|
47085
|
-
add: method(AddInputSchema, AddResultSchema, {
|
|
47086
|
-
kind: "mutation",
|
|
47087
|
-
auth: "admin"
|
|
47088
|
-
}),
|
|
47089
|
-
remove: method(RemoveInputSchema, _void(), {
|
|
47090
|
-
kind: "mutation",
|
|
47091
|
-
auth: "admin"
|
|
47092
|
-
}),
|
|
47093
|
-
testConnection: method(GetInputSchema, TestConnectionResultSchema, {
|
|
47094
|
-
kind: "mutation",
|
|
47095
|
-
auth: "admin"
|
|
47096
|
-
}),
|
|
47097
|
-
/** Read the persisted settings record for a broker (kind-specific
|
|
47098
|
-
* shape). Admin-only — settings may contain secrets. Returns `null`
|
|
47099
|
-
* when the broker id is unknown to the provider (the collection
|
|
47100
|
-
* fallback may route a foreign id to the first provider). */
|
|
47101
|
-
getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47102
|
-
/** Overwrite the persisted settings record. The kind-specific
|
|
47103
|
-
* provider validates the shape and applies the change (reconnects
|
|
47104
|
-
* if credentials changed). */
|
|
47105
|
-
setSettings: method(object({
|
|
47106
|
-
id: string$2(),
|
|
47107
|
-
settings: SettingsRecordSchema$1
|
|
47108
|
-
}), _void(), {
|
|
47109
|
-
kind: "mutation",
|
|
47110
|
-
auth: "admin"
|
|
47111
|
-
}),
|
|
47112
47020
|
/** Returns the kind-specific connection config the consumer needs
|
|
47113
47021
|
* to open its own client (MQTT pattern: `{url, username, password,
|
|
47114
47022
|
* clientIdPrefix}`). HA providers MAY return the auth envelope
|
|
47115
47023
|
* but typical HA consumers use `publish` / `subscribe` instead.
|
|
47116
47024
|
* Returns `null` when the broker id is unknown to the provider. */
|
|
47117
47025
|
getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
47118
|
-
getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
|
|
47119
|
-
testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
47120
|
-
kind: "mutation",
|
|
47121
|
-
auth: "admin"
|
|
47122
|
-
}),
|
|
47123
47026
|
publish: method(PublishInputSchema, unknown(), {
|
|
47124
47027
|
kind: "mutation",
|
|
47125
47028
|
auth: "admin"
|
|
@@ -51966,7 +51869,14 @@ var DiscoveryCandidateSchema = object({
|
|
|
51966
51869
|
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
51967
51870
|
parentStableId: string$2().optional(),
|
|
51968
51871
|
/** `false` = shown for context but not pickable. Default true. */
|
|
51969
|
-
selectable: boolean().optional()
|
|
51872
|
+
selectable: boolean().optional(),
|
|
51873
|
+
/**
|
|
51874
|
+
* Keys of the provider's creation form (`getChildCreationSchema`) this candidate
|
|
51875
|
+
* cannot be adopted without — a key for an encrypted board, a pairing code. The
|
|
51876
|
+
* admin asks for exactly these, on the creation form prefilled with
|
|
51877
|
+
* `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
|
|
51878
|
+
*/
|
|
51879
|
+
needsParams: array(string$2()).optional()
|
|
51970
51880
|
});
|
|
51971
51881
|
/**
|
|
51972
51882
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -52093,8 +52003,14 @@ var deviceProviderCapability = {
|
|
|
52093
52003
|
* provider does not support manual creation. Lets the Add-Device dialog
|
|
52094
52004
|
* pick the right type instead of assuming Camera.
|
|
52095
52005
|
*/
|
|
52096
|
-
getManualCreationType: method(object({
|
|
52097
|
-
|
|
52006
|
+
getManualCreationType: method(object({
|
|
52007
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52008
|
+
integrationId: string$2().optional() }), object({ deviceType: _enum(DeviceType).nullable() })),
|
|
52009
|
+
adoptDiscoveredDevice: method(object({
|
|
52010
|
+
candidate: DiscoveryCandidateSchema,
|
|
52011
|
+
/** The integration the operator adopts into; a provider with several instances needs it. */
|
|
52012
|
+
integrationId: string$2().optional()
|
|
52013
|
+
}), DeviceSummarySchema, {
|
|
52098
52014
|
kind: "mutation",
|
|
52099
52015
|
auth: "admin"
|
|
52100
52016
|
}),
|
|
@@ -52105,10 +52021,16 @@ var deviceProviderCapability = {
|
|
|
52105
52021
|
* devices of that type. The output is a `ConfigUISchema` — the
|
|
52106
52022
|
* router type-asserts it at the boundary.
|
|
52107
52023
|
*/
|
|
52108
|
-
getChildCreationSchema: method(object({
|
|
52024
|
+
getChildCreationSchema: method(object({
|
|
52025
|
+
type: _enum(DeviceType),
|
|
52026
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
52027
|
+
integrationId: string$2().optional()
|
|
52028
|
+
}), CreationSchemaOutputSchema),
|
|
52109
52029
|
createDevice: method(object({
|
|
52110
52030
|
type: _enum(DeviceType),
|
|
52111
|
-
config: record(string$2(), unknown())
|
|
52031
|
+
config: record(string$2(), unknown()),
|
|
52032
|
+
/** The integration the operator adds the device to; a provider with several instances needs it. */
|
|
52033
|
+
integrationId: string$2().optional()
|
|
52112
52034
|
}), DeviceSummarySchema, {
|
|
52113
52035
|
kind: "mutation",
|
|
52114
52036
|
auth: "admin"
|
|
@@ -52833,7 +52755,10 @@ method(object({
|
|
|
52833
52755
|
auth: "admin"
|
|
52834
52756
|
}), method(object({
|
|
52835
52757
|
addonId: string$2(),
|
|
52836
|
-
type: _enum(DeviceType)
|
|
52758
|
+
type: _enum(DeviceType),
|
|
52759
|
+
/** The integration the form is for; a provider whose integrations differ (a hub
|
|
52760
|
+
* integration takes no manual add) answers per integration. Absent = the addon's own. */
|
|
52761
|
+
integrationId: string$2().optional()
|
|
52837
52762
|
}), unknown().nullable()), method(object({
|
|
52838
52763
|
addonId: string$2(),
|
|
52839
52764
|
type: _enum(DeviceType),
|
|
@@ -52917,7 +52842,11 @@ method(object({
|
|
|
52917
52842
|
kind: "mutation",
|
|
52918
52843
|
auth: "admin",
|
|
52919
52844
|
timeoutMs: 12e4
|
|
52920
|
-
}), method(object({
|
|
52845
|
+
}), method(object({
|
|
52846
|
+
addonId: string$2(),
|
|
52847
|
+
/** The integration the Add-Device dialog is open on; absent = the addon's own. */
|
|
52848
|
+
integrationId: string$2().optional()
|
|
52849
|
+
}), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string$2() }), unknown(), { auth: "admin" }), method(object({
|
|
52921
52850
|
deviceId: number(),
|
|
52922
52851
|
key: string$2(),
|
|
52923
52852
|
value: unknown()
|
|
@@ -56197,7 +56126,7 @@ var TestConnectionStatusEnum = _enum([
|
|
|
56197
56126
|
"inconclusive",
|
|
56198
56127
|
"unsupported"
|
|
56199
56128
|
]);
|
|
56200
|
-
var TestConnectionResultSchema
|
|
56129
|
+
var TestConnectionResultSchema = object({
|
|
56201
56130
|
/** True ONLY for `validated`. Never true for a test that did not run. */
|
|
56202
56131
|
success: boolean(),
|
|
56203
56132
|
error: string$2().optional(),
|
|
@@ -56242,7 +56171,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
56242
56171
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56243
56172
|
addonId: string$2(),
|
|
56244
56173
|
settings: record(string$2(), unknown())
|
|
56245
|
-
}), TestConnectionResultSchema
|
|
56174
|
+
}), TestConnectionResultSchema, {
|
|
56246
56175
|
kind: "mutation",
|
|
56247
56176
|
auth: "admin"
|
|
56248
56177
|
});
|
|
@@ -56269,6 +56198,14 @@ var ConfigSchemaPassthrough$3 = custom();
|
|
|
56269
56198
|
var IntegrationInstanceIdSchema = string$2().min(1);
|
|
56270
56199
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
56271
56200
|
/**
|
|
56201
|
+
* What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
|
|
56202
|
+
* instance is an integration that brings devices (the registry owns its on/off switch);
|
|
56203
|
+
* `services`: a connection configured from the addon's own function page, never listed on
|
|
56204
|
+
* Integrations.
|
|
56205
|
+
* It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
|
|
56206
|
+
*/
|
|
56207
|
+
var IntegrationInstanceRoleSchema = _enum(["devices", "services"]);
|
|
56208
|
+
/**
|
|
56272
56209
|
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
56273
56210
|
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
56274
56211
|
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
@@ -56279,6 +56216,22 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56279
56216
|
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
56280
56217
|
id: string$2().min(1),
|
|
56281
56218
|
label: string$2().min(1),
|
|
56219
|
+
/**
|
|
56220
|
+
* The function capability this kind is configured for (`broker`, `smtp-provider`,
|
|
56221
|
+
* `device-provider`, ...). A page section is the kinds whose `functionCap` it names (D1013); a
|
|
56222
|
+
* provider with more than one section must name it on every kind.
|
|
56223
|
+
*/
|
|
56224
|
+
functionCap: string$2().min(1),
|
|
56225
|
+
/**
|
|
56226
|
+
* For `functionCap: 'broker'`: the registry kind of the broker an instance of this kind is
|
|
56227
|
+
* (`matter`, `home-assistant`, ...). A caller that needs a broker of one kind (a link field)
|
|
56228
|
+
* finds the instances whose kind names it; absent takes the kind `id`.
|
|
56229
|
+
*/
|
|
56230
|
+
brokerKind: string$2().min(1).optional(),
|
|
56231
|
+
/** What instances of this kind are for the Integrations page; absent takes the provider's `role`. */
|
|
56232
|
+
role: IntegrationInstanceRoleSchema.optional(),
|
|
56233
|
+
/** `testInstance` really probes the remote for this kind; absent takes the provider's `canTest`. */
|
|
56234
|
+
canTest: boolean().optional(),
|
|
56282
56235
|
/** The kind signs in through a server-held interactive session. */
|
|
56283
56236
|
signIn: object({
|
|
56284
56237
|
basePath: string$2().min(1),
|
|
@@ -56291,11 +56244,7 @@ var IntegrationInstanceKindSchema = object({
|
|
|
56291
56244
|
});
|
|
56292
56245
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56293
56246
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56294
|
-
role:
|
|
56295
|
-
"devices",
|
|
56296
|
-
"services",
|
|
56297
|
-
"both"
|
|
56298
|
-
]),
|
|
56247
|
+
role: IntegrationInstanceRoleSchema,
|
|
56299
56248
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56300
56249
|
discovery: object({
|
|
56301
56250
|
mode: _enum([
|
|
@@ -56370,7 +56319,11 @@ var integrationInstancesCapability = {
|
|
|
56370
56319
|
methods: {
|
|
56371
56320
|
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
56372
56321
|
/** The form; `id: null` is the create form. */
|
|
56373
|
-
instanceFormSchema: method(object({
|
|
56322
|
+
instanceFormSchema: method(object({
|
|
56323
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
56324
|
+
/** The kind of the create form (`id: null`) of a provider with several. */
|
|
56325
|
+
kind: string$2().min(1).optional()
|
|
56326
|
+
}), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
56374
56327
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
56375
56328
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
56376
56329
|
/**
|
|
@@ -56380,7 +56333,9 @@ var integrationInstancesCapability = {
|
|
|
56380
56333
|
createInstance: method(object({
|
|
56381
56334
|
id: IntegrationInstanceIdSchema.optional(),
|
|
56382
56335
|
name: string$2().min(1),
|
|
56383
|
-
kind: string$2().min(1).optional()
|
|
56336
|
+
kind: string$2().min(1).optional(),
|
|
56337
|
+
/** Form values written with the record (a link picked in the create dialog). */
|
|
56338
|
+
values: record(string$2(), unknown()).optional()
|
|
56384
56339
|
}), object({ id: string$2() }), {
|
|
56385
56340
|
kind: "mutation",
|
|
56386
56341
|
auth: "admin"
|
|
@@ -56407,8 +56362,10 @@ var integrationInstancesCapability = {
|
|
|
56407
56362
|
}),
|
|
56408
56363
|
testInstance: optionalMethod(object({
|
|
56409
56364
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
56410
|
-
values: record(string$2(), unknown())
|
|
56411
|
-
|
|
56365
|
+
values: record(string$2(), unknown()),
|
|
56366
|
+
/** The kind to test when `id` is null and the provider has several. */
|
|
56367
|
+
kind: string$2().min(1).optional()
|
|
56368
|
+
}), TestConnectionResultSchema, {
|
|
56412
56369
|
kind: "mutation",
|
|
56413
56370
|
auth: "admin"
|
|
56414
56371
|
})
|
|
@@ -59165,6 +59122,380 @@ var SystemMetricsSchema = object({
|
|
|
59165
59122
|
gpuMemoryPercent: number().optional()
|
|
59166
59123
|
});
|
|
59167
59124
|
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);
|
|
59125
|
+
/**
|
|
59126
|
+
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
59127
|
+
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
59128
|
+
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
59129
|
+
*
|
|
59130
|
+
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
59131
|
+
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
59132
|
+
* that with the addon id closed over). It is deliberately NOT in any input
|
|
59133
|
+
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
59134
|
+
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
59135
|
+
* addons go through the owner's capability.
|
|
59136
|
+
*
|
|
59137
|
+
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
59138
|
+
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
59139
|
+
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
59140
|
+
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
59141
|
+
* row's own ref.
|
|
59142
|
+
*
|
|
59143
|
+
* Internal: addons reach it through `ctx.records`, never directly.
|
|
59144
|
+
*/
|
|
59145
|
+
var RecordIdSchema = string$2().min(1).max(256);
|
|
59146
|
+
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
59147
|
+
var RecordBodySchema = record(string$2(), unknown());
|
|
59148
|
+
var RecordInputSchema = object({
|
|
59149
|
+
id: RecordIdSchema,
|
|
59150
|
+
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
59151
|
+
deviceId: number().int().optional(),
|
|
59152
|
+
/** The domain clock, ms: observed / created / opened. */
|
|
59153
|
+
at: number().int().optional(),
|
|
59154
|
+
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
59155
|
+
expiresAt: number().int().optional(),
|
|
59156
|
+
data: RecordBodySchema
|
|
59157
|
+
});
|
|
59158
|
+
var RecordViewSchema = object({
|
|
59159
|
+
id: string$2(),
|
|
59160
|
+
deviceId: number().int().nullable(),
|
|
59161
|
+
at: number().nullable(),
|
|
59162
|
+
expiresAt: number().nullable(),
|
|
59163
|
+
rev: number().int(),
|
|
59164
|
+
updatedAt: number(),
|
|
59165
|
+
data: RecordBodySchema
|
|
59166
|
+
});
|
|
59167
|
+
/** Declared index fields only. Anything else does not compile, by design. */
|
|
59168
|
+
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
59169
|
+
string$2(),
|
|
59170
|
+
number(),
|
|
59171
|
+
boolean(),
|
|
59172
|
+
object({ in: array(union([
|
|
59173
|
+
string$2(),
|
|
59174
|
+
number(),
|
|
59175
|
+
boolean()
|
|
59176
|
+
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
59177
|
+
object({
|
|
59178
|
+
gt: union([string$2(), number()]).optional(),
|
|
59179
|
+
gte: union([string$2(), number()]).optional(),
|
|
59180
|
+
lt: union([string$2(), number()]).optional(),
|
|
59181
|
+
lte: union([string$2(), number()]).optional()
|
|
59182
|
+
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
59183
|
+
]));
|
|
59184
|
+
var KindShape = { kind: RecordKindNameSchema };
|
|
59185
|
+
var TimeWindowShape = {
|
|
59186
|
+
deviceId: number().int().optional(),
|
|
59187
|
+
atFrom: number().optional(),
|
|
59188
|
+
atTo: number().optional()
|
|
59189
|
+
};
|
|
59190
|
+
var RecordQueryInputSchema = object({
|
|
59191
|
+
...KindShape,
|
|
59192
|
+
...TimeWindowShape,
|
|
59193
|
+
where: RecordWhereSchema.optional(),
|
|
59194
|
+
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
59195
|
+
order: _enum(["at", "id"]).optional(),
|
|
59196
|
+
desc: boolean().optional(),
|
|
59197
|
+
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
59198
|
+
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
59199
|
+
cursor: string$2().min(1).optional()
|
|
59200
|
+
});
|
|
59201
|
+
var RecordQueryResultSchema = object({
|
|
59202
|
+
records: array(RecordViewSchema),
|
|
59203
|
+
nextCursor: string$2().nullable()
|
|
59204
|
+
});
|
|
59205
|
+
var RecordKindStateSchema = _enum([
|
|
59206
|
+
"created",
|
|
59207
|
+
"unchanged",
|
|
59208
|
+
"updated"
|
|
59209
|
+
]);
|
|
59210
|
+
var DeclareKindResultSchema = object({
|
|
59211
|
+
kindId: number().int(),
|
|
59212
|
+
state: RecordKindStateSchema,
|
|
59213
|
+
createdIndexes: array(string$2())
|
|
59214
|
+
});
|
|
59215
|
+
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
59216
|
+
var SecretRefsList = array(string$2());
|
|
59217
|
+
/**
|
|
59218
|
+
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
59219
|
+
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
59220
|
+
*/
|
|
59221
|
+
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
59222
|
+
object({
|
|
59223
|
+
op: literal("put"),
|
|
59224
|
+
kind: RecordKindNameSchema,
|
|
59225
|
+
record: RecordInputSchema,
|
|
59226
|
+
ifRev: number().int().min(0).optional()
|
|
59227
|
+
}),
|
|
59228
|
+
object({
|
|
59229
|
+
op: literal("patch"),
|
|
59230
|
+
kind: RecordKindNameSchema,
|
|
59231
|
+
id: RecordIdSchema,
|
|
59232
|
+
data: RecordBodySchema,
|
|
59233
|
+
at: number().int().nullable().optional(),
|
|
59234
|
+
expiresAt: number().int().nullable().optional(),
|
|
59235
|
+
ifRev: number().int().min(1).optional()
|
|
59236
|
+
}),
|
|
59237
|
+
object({
|
|
59238
|
+
op: literal("delete"),
|
|
59239
|
+
kind: RecordKindNameSchema,
|
|
59240
|
+
id: RecordIdSchema
|
|
59241
|
+
})
|
|
59242
|
+
]);
|
|
59243
|
+
var RecordSecretRefsSchema = record(string$2(), record(string$2(), string$2()));
|
|
59244
|
+
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string$2(), DeclareKindResultSchema) }), {
|
|
59245
|
+
kind: "mutation",
|
|
59246
|
+
auth: "admin"
|
|
59247
|
+
}), method(object({
|
|
59248
|
+
...KindShape,
|
|
59249
|
+
id: RecordIdSchema
|
|
59250
|
+
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
59251
|
+
...KindShape,
|
|
59252
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59253
|
+
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
59254
|
+
...KindShape,
|
|
59255
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59256
|
+
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
59257
|
+
...KindShape,
|
|
59258
|
+
record: RecordInputSchema,
|
|
59259
|
+
/** Compare-and-set. `0` means "the row must not exist". */
|
|
59260
|
+
ifRev: number().int().min(0).optional()
|
|
59261
|
+
}), object({
|
|
59262
|
+
rev: number().int(),
|
|
59263
|
+
releasedRefs: SecretRefsList
|
|
59264
|
+
}), {
|
|
59265
|
+
kind: "mutation",
|
|
59266
|
+
auth: "admin"
|
|
59267
|
+
}), method(object({
|
|
59268
|
+
...KindShape,
|
|
59269
|
+
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59270
|
+
}), object({
|
|
59271
|
+
count: number().int(),
|
|
59272
|
+
releasedRefs: SecretRefsList
|
|
59273
|
+
}), {
|
|
59274
|
+
kind: "mutation",
|
|
59275
|
+
auth: "admin"
|
|
59276
|
+
}), method(object({
|
|
59277
|
+
...KindShape,
|
|
59278
|
+
id: RecordIdSchema,
|
|
59279
|
+
data: RecordBodySchema,
|
|
59280
|
+
at: number().int().nullable().optional(),
|
|
59281
|
+
expiresAt: number().int().nullable().optional(),
|
|
59282
|
+
ifRev: number().int().min(1).optional()
|
|
59283
|
+
}), object({
|
|
59284
|
+
rev: number().int(),
|
|
59285
|
+
releasedRefs: SecretRefsList
|
|
59286
|
+
}), {
|
|
59287
|
+
kind: "mutation",
|
|
59288
|
+
auth: "admin"
|
|
59289
|
+
}), method(object({
|
|
59290
|
+
...KindShape,
|
|
59291
|
+
id: RecordIdSchema
|
|
59292
|
+
}), object({
|
|
59293
|
+
deleted: boolean(),
|
|
59294
|
+
secretRefs: SecretRefsList
|
|
59295
|
+
}), {
|
|
59296
|
+
kind: "mutation",
|
|
59297
|
+
auth: "admin"
|
|
59298
|
+
}), method(object({
|
|
59299
|
+
...KindShape,
|
|
59300
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
59301
|
+
}), object({
|
|
59302
|
+
deleted: number().int(),
|
|
59303
|
+
secretRefs: SecretRefsList
|
|
59304
|
+
}), {
|
|
59305
|
+
kind: "mutation",
|
|
59306
|
+
auth: "admin"
|
|
59307
|
+
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
59308
|
+
...KindShape,
|
|
59309
|
+
...TimeWindowShape,
|
|
59310
|
+
where: RecordWhereSchema.optional()
|
|
59311
|
+
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
59312
|
+
...KindShape,
|
|
59313
|
+
deviceId: number().int().optional(),
|
|
59314
|
+
from: number(),
|
|
59315
|
+
to: number(),
|
|
59316
|
+
bucketMs: number().int().positive()
|
|
59317
|
+
}), object({ buckets: array(object({
|
|
59318
|
+
bucketStart: number(),
|
|
59319
|
+
count: number().int()
|
|
59320
|
+
})) }), { auth: "admin" }), method(object({
|
|
59321
|
+
...KindShape,
|
|
59322
|
+
op: _enum([
|
|
59323
|
+
"sum",
|
|
59324
|
+
"min",
|
|
59325
|
+
"max",
|
|
59326
|
+
"count"
|
|
59327
|
+
]),
|
|
59328
|
+
field: RecordFieldPathSchema.optional(),
|
|
59329
|
+
groupBy: RecordFieldPathSchema.optional(),
|
|
59330
|
+
where: RecordWhereSchema.optional()
|
|
59331
|
+
}), object({ rows: array(object({
|
|
59332
|
+
group: union([
|
|
59333
|
+
string$2(),
|
|
59334
|
+
number(),
|
|
59335
|
+
_null()
|
|
59336
|
+
]),
|
|
59337
|
+
value: number().nullable()
|
|
59338
|
+
})) }), { auth: "admin" }), method(object({
|
|
59339
|
+
...KindShape,
|
|
59340
|
+
deviceId: number().int().optional(),
|
|
59341
|
+
atBefore: number().optional(),
|
|
59342
|
+
where: RecordWhereSchema.optional()
|
|
59343
|
+
}), object({
|
|
59344
|
+
deleted: number().int(),
|
|
59345
|
+
secretRefs: SecretRefsList
|
|
59346
|
+
}), {
|
|
59347
|
+
kind: "mutation",
|
|
59348
|
+
auth: "admin"
|
|
59349
|
+
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
59350
|
+
results: array(object({
|
|
59351
|
+
rev: number().int().nullable(),
|
|
59352
|
+
deleted: boolean()
|
|
59353
|
+
})),
|
|
59354
|
+
releasedRefs: SecretRefsList,
|
|
59355
|
+
secretRefs: SecretRefsList
|
|
59356
|
+
}), {
|
|
59357
|
+
kind: "mutation",
|
|
59358
|
+
auth: "admin"
|
|
59359
|
+
}), method(object({
|
|
59360
|
+
deviceId: number().int(),
|
|
59361
|
+
kinds: array(RecordKindNameSchema).optional()
|
|
59362
|
+
}), object({
|
|
59363
|
+
deleted: record(string$2(), number().int()),
|
|
59364
|
+
secretRefs: SecretRefsList
|
|
59365
|
+
}), {
|
|
59366
|
+
kind: "mutation",
|
|
59367
|
+
auth: "admin"
|
|
59368
|
+
}), method(object(KindShape), object({ id: number().int() }), {
|
|
59369
|
+
kind: "mutation",
|
|
59370
|
+
auth: "admin"
|
|
59371
|
+
});
|
|
59372
|
+
/**
|
|
59373
|
+
* The wire shapes of the migration API, version 1 (planning#447).
|
|
59374
|
+
*
|
|
59375
|
+
* A migration is code that outlives the server it was written against: an addon at
|
|
59376
|
+
* version 1.4 that is brought to 1.9 in one jump still runs its 1.4 catalogue, against
|
|
59377
|
+
* the 1.9 kernel. The only thing that code may call is {@link MigrationApiV1}; this file
|
|
59378
|
+
* is the contract of every method of it, one Zod pair per method.
|
|
59379
|
+
*
|
|
59380
|
+
* APPEND-ONLY. A released method is never removed and never changes meaning: an input
|
|
59381
|
+
* field is never removed or made required, an enum never narrows, an output field is
|
|
59382
|
+
* never removed. A change that cannot be expressed that way is `migration-api.v2`, next
|
|
59383
|
+
* to this file. `scripts/check-migration-api-append-only.ts` holds the line against
|
|
59384
|
+
* `scripts/migration-api-contract.json`, and every `behaviours` id below must have an
|
|
59385
|
+
* `it('<id>'` in `migration-api.contract.spec.ts`.
|
|
59386
|
+
*/
|
|
59387
|
+
var Rows = record(string$2(), unknown());
|
|
59388
|
+
var IntegrationId = object({ integrationId: string$2().min(1) });
|
|
59389
|
+
var RecordId = object({ id: string$2().min(1) });
|
|
59390
|
+
var RecordRowSchema = object({
|
|
59391
|
+
id: string$2(),
|
|
59392
|
+
deviceId: number().int().nullable(),
|
|
59393
|
+
at: number().nullable(),
|
|
59394
|
+
expiresAt: number().nullable(),
|
|
59395
|
+
rev: number().int(),
|
|
59396
|
+
updatedAt: number(),
|
|
59397
|
+
data: Rows
|
|
59398
|
+
});
|
|
59399
|
+
var RecordPutShape = {
|
|
59400
|
+
id: string$2().min(1),
|
|
59401
|
+
deviceId: number().int().optional(),
|
|
59402
|
+
at: number().int().optional(),
|
|
59403
|
+
expiresAt: number().int().optional(),
|
|
59404
|
+
data: Rows
|
|
59405
|
+
};
|
|
59406
|
+
var MigrationIntegrationSchema = object({
|
|
59407
|
+
id: string$2(),
|
|
59408
|
+
addonId: string$2(),
|
|
59409
|
+
name: string$2(),
|
|
59410
|
+
info: Rows,
|
|
59411
|
+
originStableId: string$2().optional()
|
|
59412
|
+
});
|
|
59413
|
+
var MigrationDeviceSchema = object({
|
|
59414
|
+
id: number().int(),
|
|
59415
|
+
integrationId: string$2().nullable().optional(),
|
|
59416
|
+
/** The owning device; null or absent for a root. */
|
|
59417
|
+
parentDeviceId: number().int().nullable().optional(),
|
|
59418
|
+
type: string$2().optional(),
|
|
59419
|
+
name: string$2().optional(),
|
|
59420
|
+
stableId: string$2().optional()
|
|
59421
|
+
});
|
|
59422
|
+
/** What the engine says about the migration API it serves: lets an addon notice an older server. */
|
|
59423
|
+
var MigrationApiInfoSchema = object({
|
|
59424
|
+
version: number().int(),
|
|
59425
|
+
methods: array(string$2())
|
|
59426
|
+
});
|
|
59427
|
+
object({
|
|
59428
|
+
message: string$2(),
|
|
59429
|
+
meta: Rows.optional(),
|
|
59430
|
+
/** Grouping tags of the line; a line about a device carries `{ deviceId }`. */
|
|
59431
|
+
tags: object({ deviceId: number().int().optional() }).optional()
|
|
59432
|
+
});
|
|
59433
|
+
object({
|
|
59434
|
+
owner: string$2().min(1),
|
|
59435
|
+
metas: array(MigrationMetaSchema)
|
|
59436
|
+
}), object({
|
|
59437
|
+
bootId: string$2(),
|
|
59438
|
+
snapshot: _enum([
|
|
59439
|
+
"none",
|
|
59440
|
+
"written",
|
|
59441
|
+
"refused"
|
|
59442
|
+
]),
|
|
59443
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59444
|
+
/** The migration API the engine serves; absent from a server that predates it. */
|
|
59445
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59446
|
+
}), object({ id: string$2().min(1) }), object({
|
|
59447
|
+
proceed: boolean(),
|
|
59448
|
+
state: union([literal("pending"), MigrationStatusSchema]),
|
|
59449
|
+
reason: string$2().nullable()
|
|
59450
|
+
}), object({
|
|
59451
|
+
id: string$2().min(1),
|
|
59452
|
+
result: MigrationOwnerResultSchema
|
|
59453
|
+
}), object({
|
|
59454
|
+
status: MigrationStatusSchema,
|
|
59455
|
+
error: string$2().nullable(),
|
|
59456
|
+
rowsChanged: number().int().nonnegative()
|
|
59457
|
+
}), object({
|
|
59458
|
+
rows: number().int().nonnegative(),
|
|
59459
|
+
secrets: number().int().nonnegative()
|
|
59460
|
+
}), IntegrationId.extend({ patch: Rows }), object({ written: boolean() }), IntegrationId.extend({ keys: array(string$2().min(1)) }), object({
|
|
59461
|
+
present: boolean(),
|
|
59462
|
+
removed: Rows
|
|
59463
|
+
}), RecordRowSchema.nullable(), RecordRowSchema.nullable(), object({
|
|
59464
|
+
...RecordPutShape,
|
|
59465
|
+
ifRev: number().int().optional()
|
|
59466
|
+
}), object({ rev: number().int() }), object({ records: array(object(RecordPutShape)) }), object({ count: number().int().nonnegative() }), RecordId.extend({
|
|
59467
|
+
partial: Rows,
|
|
59468
|
+
ifRev: number().int().optional()
|
|
59469
|
+
}), object({ rev: number().int() }), object({ deleted: boolean() }), object({
|
|
59470
|
+
deviceId: number().int().optional(),
|
|
59471
|
+
atFrom: number().optional(),
|
|
59472
|
+
atTo: number().optional(),
|
|
59473
|
+
where: RecordWhereSchema.optional(),
|
|
59474
|
+
limit: number().int().min(1).optional(),
|
|
59475
|
+
cursor: string$2().min(1).optional(),
|
|
59476
|
+
resolveSecrets: boolean().optional()
|
|
59477
|
+
}), object({
|
|
59478
|
+
records: array(RecordRowSchema),
|
|
59479
|
+
nextCursor: string$2().nullable()
|
|
59480
|
+
}), object({
|
|
59481
|
+
deviceId: number().int().optional(),
|
|
59482
|
+
atBefore: number().optional(),
|
|
59483
|
+
where: RecordWhereSchema.optional()
|
|
59484
|
+
}), 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({
|
|
59485
|
+
ref: string$2().min(1),
|
|
59486
|
+
value: string$2()
|
|
59487
|
+
}), 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({
|
|
59488
|
+
addonId: string$2().min(1),
|
|
59489
|
+
integrationId: string$2().min(1).optional()
|
|
59490
|
+
}), object({ devices: array(MigrationDeviceSchema) }), object({ deviceId: number().int() }), object({
|
|
59491
|
+
id: number().int(),
|
|
59492
|
+
integrationId: string$2().nullable(),
|
|
59493
|
+
/** Whether the device carries any stored config; its content is not offered. */
|
|
59494
|
+
hasConfig: boolean()
|
|
59495
|
+
}).nullable(), object({
|
|
59496
|
+
deviceId: number().int(),
|
|
59497
|
+
integrationId: string$2().min(1)
|
|
59498
|
+
}), object({}), object({}), object({ nodeId: string$2() }), object({}), object({});
|
|
59168
59499
|
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({
|
|
59169
59500
|
id: string$2().min(1),
|
|
59170
59501
|
error: string$2().min(1)
|
|
@@ -59194,7 +59525,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59194
59525
|
"written",
|
|
59195
59526
|
"refused"
|
|
59196
59527
|
]),
|
|
59197
|
-
steps: array(MigrationOwnerStepSchema)
|
|
59528
|
+
steps: array(MigrationOwnerStepSchema),
|
|
59529
|
+
/** The migration API this engine serves (additive: a server that predates it omits it). */
|
|
59530
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
59198
59531
|
}), {
|
|
59199
59532
|
kind: "mutation",
|
|
59200
59533
|
auth: "admin"
|
|
@@ -61861,7 +62194,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
61861
62194
|
}), method(RelocateMediaInputSchema, object({ jobId: string$2() }), {
|
|
61862
62195
|
kind: "mutation",
|
|
61863
62196
|
auth: "admin"
|
|
61864
|
-
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(
|
|
62197
|
+
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(MediaLocationCensusInputSchema, MediaLocationCensusSchema, {
|
|
62198
|
+
kind: "query",
|
|
62199
|
+
auth: "admin"
|
|
62200
|
+
}), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
|
|
61865
62201
|
kind: "query",
|
|
61866
62202
|
auth: "admin"
|
|
61867
62203
|
}), method(object({}), array(RelocateJobSchema).readonly(), {
|
|
@@ -64516,7 +64852,7 @@ var BrokerStatusSchema$1 = _enum([
|
|
|
64516
64852
|
"unreachable",
|
|
64517
64853
|
"tls-error"
|
|
64518
64854
|
]);
|
|
64519
|
-
var BrokerInfoSchema
|
|
64855
|
+
var BrokerInfoSchema = object({
|
|
64520
64856
|
id: string$2(),
|
|
64521
64857
|
name: string$2(),
|
|
64522
64858
|
url: string$2(),
|
|
@@ -64579,7 +64915,7 @@ var StatusSchema = object({
|
|
|
64579
64915
|
brokerCount: number(),
|
|
64580
64916
|
embeddedRunning: boolean()
|
|
64581
64917
|
});
|
|
64582
|
-
method(_void(), array(BrokerInfoSchema
|
|
64918
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
64583
64919
|
kind: "mutation",
|
|
64584
64920
|
auth: "admin"
|
|
64585
64921
|
}), method(IdInputSchema, _void(), {
|
|
@@ -71761,253 +72097,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
|
|
|
71761
72097
|
auth: "admin"
|
|
71762
72098
|
});
|
|
71763
72099
|
/**
|
|
71764
|
-
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
71765
|
-
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
71766
|
-
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
71767
|
-
*
|
|
71768
|
-
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
71769
|
-
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
71770
|
-
* that with the addon id closed over). It is deliberately NOT in any input
|
|
71771
|
-
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
71772
|
-
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
71773
|
-
* addons go through the owner's capability.
|
|
71774
|
-
*
|
|
71775
|
-
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
71776
|
-
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
71777
|
-
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
71778
|
-
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
71779
|
-
* row's own ref.
|
|
71780
|
-
*
|
|
71781
|
-
* Internal: addons reach it through `ctx.records`, never directly.
|
|
71782
|
-
*/
|
|
71783
|
-
var RecordIdSchema = string$2().min(1).max(256);
|
|
71784
|
-
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
71785
|
-
var RecordBodySchema = record(string$2(), unknown());
|
|
71786
|
-
var RecordInputSchema = object({
|
|
71787
|
-
id: RecordIdSchema,
|
|
71788
|
-
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
71789
|
-
deviceId: number().int().optional(),
|
|
71790
|
-
/** The domain clock, ms: observed / created / opened. */
|
|
71791
|
-
at: number().int().optional(),
|
|
71792
|
-
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
71793
|
-
expiresAt: number().int().optional(),
|
|
71794
|
-
data: RecordBodySchema
|
|
71795
|
-
});
|
|
71796
|
-
var RecordViewSchema = object({
|
|
71797
|
-
id: string$2(),
|
|
71798
|
-
deviceId: number().int().nullable(),
|
|
71799
|
-
at: number().nullable(),
|
|
71800
|
-
expiresAt: number().nullable(),
|
|
71801
|
-
rev: number().int(),
|
|
71802
|
-
updatedAt: number(),
|
|
71803
|
-
data: RecordBodySchema
|
|
71804
|
-
});
|
|
71805
|
-
/** Declared index fields only. Anything else does not compile, by design. */
|
|
71806
|
-
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
71807
|
-
string$2(),
|
|
71808
|
-
number(),
|
|
71809
|
-
boolean(),
|
|
71810
|
-
object({ in: array(union([
|
|
71811
|
-
string$2(),
|
|
71812
|
-
number(),
|
|
71813
|
-
boolean()
|
|
71814
|
-
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
71815
|
-
object({
|
|
71816
|
-
gt: union([string$2(), number()]).optional(),
|
|
71817
|
-
gte: union([string$2(), number()]).optional(),
|
|
71818
|
-
lt: union([string$2(), number()]).optional(),
|
|
71819
|
-
lte: union([string$2(), number()]).optional()
|
|
71820
|
-
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
71821
|
-
]));
|
|
71822
|
-
var KindShape = { kind: RecordKindNameSchema };
|
|
71823
|
-
var TimeWindowShape = {
|
|
71824
|
-
deviceId: number().int().optional(),
|
|
71825
|
-
atFrom: number().optional(),
|
|
71826
|
-
atTo: number().optional()
|
|
71827
|
-
};
|
|
71828
|
-
var RecordQueryInputSchema = object({
|
|
71829
|
-
...KindShape,
|
|
71830
|
-
...TimeWindowShape,
|
|
71831
|
-
where: RecordWhereSchema.optional(),
|
|
71832
|
-
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
71833
|
-
order: _enum(["at", "id"]).optional(),
|
|
71834
|
-
desc: boolean().optional(),
|
|
71835
|
-
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
71836
|
-
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
71837
|
-
cursor: string$2().min(1).optional()
|
|
71838
|
-
});
|
|
71839
|
-
var RecordQueryResultSchema = object({
|
|
71840
|
-
records: array(RecordViewSchema),
|
|
71841
|
-
nextCursor: string$2().nullable()
|
|
71842
|
-
});
|
|
71843
|
-
var RecordKindStateSchema = _enum([
|
|
71844
|
-
"created",
|
|
71845
|
-
"unchanged",
|
|
71846
|
-
"updated"
|
|
71847
|
-
]);
|
|
71848
|
-
var DeclareKindResultSchema = object({
|
|
71849
|
-
kindId: number().int(),
|
|
71850
|
-
state: RecordKindStateSchema,
|
|
71851
|
-
createdIndexes: array(string$2())
|
|
71852
|
-
});
|
|
71853
|
-
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
71854
|
-
var SecretRefsList = array(string$2());
|
|
71855
|
-
/**
|
|
71856
|
-
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
71857
|
-
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
71858
|
-
*/
|
|
71859
|
-
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
71860
|
-
object({
|
|
71861
|
-
op: literal("put"),
|
|
71862
|
-
kind: RecordKindNameSchema,
|
|
71863
|
-
record: RecordInputSchema,
|
|
71864
|
-
ifRev: number().int().min(0).optional()
|
|
71865
|
-
}),
|
|
71866
|
-
object({
|
|
71867
|
-
op: literal("patch"),
|
|
71868
|
-
kind: RecordKindNameSchema,
|
|
71869
|
-
id: RecordIdSchema,
|
|
71870
|
-
data: RecordBodySchema,
|
|
71871
|
-
at: number().int().nullable().optional(),
|
|
71872
|
-
expiresAt: number().int().nullable().optional(),
|
|
71873
|
-
ifRev: number().int().min(1).optional()
|
|
71874
|
-
}),
|
|
71875
|
-
object({
|
|
71876
|
-
op: literal("delete"),
|
|
71877
|
-
kind: RecordKindNameSchema,
|
|
71878
|
-
id: RecordIdSchema
|
|
71879
|
-
})
|
|
71880
|
-
]);
|
|
71881
|
-
var RecordSecretRefsSchema = record(string$2(), record(string$2(), string$2()));
|
|
71882
|
-
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string$2(), DeclareKindResultSchema) }), {
|
|
71883
|
-
kind: "mutation",
|
|
71884
|
-
auth: "admin"
|
|
71885
|
-
}), method(object({
|
|
71886
|
-
...KindShape,
|
|
71887
|
-
id: RecordIdSchema
|
|
71888
|
-
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
71889
|
-
...KindShape,
|
|
71890
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71891
|
-
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
71892
|
-
...KindShape,
|
|
71893
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71894
|
-
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
71895
|
-
...KindShape,
|
|
71896
|
-
record: RecordInputSchema,
|
|
71897
|
-
/** Compare-and-set. `0` means "the row must not exist". */
|
|
71898
|
-
ifRev: number().int().min(0).optional()
|
|
71899
|
-
}), object({
|
|
71900
|
-
rev: number().int(),
|
|
71901
|
-
releasedRefs: SecretRefsList
|
|
71902
|
-
}), {
|
|
71903
|
-
kind: "mutation",
|
|
71904
|
-
auth: "admin"
|
|
71905
|
-
}), method(object({
|
|
71906
|
-
...KindShape,
|
|
71907
|
-
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71908
|
-
}), object({
|
|
71909
|
-
count: number().int(),
|
|
71910
|
-
releasedRefs: SecretRefsList
|
|
71911
|
-
}), {
|
|
71912
|
-
kind: "mutation",
|
|
71913
|
-
auth: "admin"
|
|
71914
|
-
}), method(object({
|
|
71915
|
-
...KindShape,
|
|
71916
|
-
id: RecordIdSchema,
|
|
71917
|
-
data: RecordBodySchema,
|
|
71918
|
-
at: number().int().nullable().optional(),
|
|
71919
|
-
expiresAt: number().int().nullable().optional(),
|
|
71920
|
-
ifRev: number().int().min(1).optional()
|
|
71921
|
-
}), object({
|
|
71922
|
-
rev: number().int(),
|
|
71923
|
-
releasedRefs: SecretRefsList
|
|
71924
|
-
}), {
|
|
71925
|
-
kind: "mutation",
|
|
71926
|
-
auth: "admin"
|
|
71927
|
-
}), method(object({
|
|
71928
|
-
...KindShape,
|
|
71929
|
-
id: RecordIdSchema
|
|
71930
|
-
}), object({
|
|
71931
|
-
deleted: boolean(),
|
|
71932
|
-
secretRefs: SecretRefsList
|
|
71933
|
-
}), {
|
|
71934
|
-
kind: "mutation",
|
|
71935
|
-
auth: "admin"
|
|
71936
|
-
}), method(object({
|
|
71937
|
-
...KindShape,
|
|
71938
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
71939
|
-
}), object({
|
|
71940
|
-
deleted: number().int(),
|
|
71941
|
-
secretRefs: SecretRefsList
|
|
71942
|
-
}), {
|
|
71943
|
-
kind: "mutation",
|
|
71944
|
-
auth: "admin"
|
|
71945
|
-
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
71946
|
-
...KindShape,
|
|
71947
|
-
...TimeWindowShape,
|
|
71948
|
-
where: RecordWhereSchema.optional()
|
|
71949
|
-
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
71950
|
-
...KindShape,
|
|
71951
|
-
deviceId: number().int().optional(),
|
|
71952
|
-
from: number(),
|
|
71953
|
-
to: number(),
|
|
71954
|
-
bucketMs: number().int().positive()
|
|
71955
|
-
}), object({ buckets: array(object({
|
|
71956
|
-
bucketStart: number(),
|
|
71957
|
-
count: number().int()
|
|
71958
|
-
})) }), { auth: "admin" }), method(object({
|
|
71959
|
-
...KindShape,
|
|
71960
|
-
op: _enum([
|
|
71961
|
-
"sum",
|
|
71962
|
-
"min",
|
|
71963
|
-
"max",
|
|
71964
|
-
"count"
|
|
71965
|
-
]),
|
|
71966
|
-
field: RecordFieldPathSchema.optional(),
|
|
71967
|
-
groupBy: RecordFieldPathSchema.optional(),
|
|
71968
|
-
where: RecordWhereSchema.optional()
|
|
71969
|
-
}), object({ rows: array(object({
|
|
71970
|
-
group: union([
|
|
71971
|
-
string$2(),
|
|
71972
|
-
number(),
|
|
71973
|
-
_null()
|
|
71974
|
-
]),
|
|
71975
|
-
value: number().nullable()
|
|
71976
|
-
})) }), { auth: "admin" }), method(object({
|
|
71977
|
-
...KindShape,
|
|
71978
|
-
deviceId: number().int().optional(),
|
|
71979
|
-
atBefore: number().optional(),
|
|
71980
|
-
where: RecordWhereSchema.optional()
|
|
71981
|
-
}), object({
|
|
71982
|
-
deleted: number().int(),
|
|
71983
|
-
secretRefs: SecretRefsList
|
|
71984
|
-
}), {
|
|
71985
|
-
kind: "mutation",
|
|
71986
|
-
auth: "admin"
|
|
71987
|
-
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
71988
|
-
results: array(object({
|
|
71989
|
-
rev: number().int().nullable(),
|
|
71990
|
-
deleted: boolean()
|
|
71991
|
-
})),
|
|
71992
|
-
releasedRefs: SecretRefsList,
|
|
71993
|
-
secretRefs: SecretRefsList
|
|
71994
|
-
}), {
|
|
71995
|
-
kind: "mutation",
|
|
71996
|
-
auth: "admin"
|
|
71997
|
-
}), method(object({
|
|
71998
|
-
deviceId: number().int(),
|
|
71999
|
-
kinds: array(RecordKindNameSchema).optional()
|
|
72000
|
-
}), object({
|
|
72001
|
-
deleted: record(string$2(), number().int()),
|
|
72002
|
-
secretRefs: SecretRefsList
|
|
72003
|
-
}), {
|
|
72004
|
-
kind: "mutation",
|
|
72005
|
-
auth: "admin"
|
|
72006
|
-
}), method(object(KindShape), object({ id: number().int() }), {
|
|
72007
|
-
kind: "mutation",
|
|
72008
|
-
auth: "admin"
|
|
72009
|
-
});
|
|
72010
|
-
/**
|
|
72011
72100
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
72012
72101
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
72013
72102
|
* declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
|
|
@@ -81735,7 +81824,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
81735
81824
|
* `deviceClasses` map (first registered type). `null` when manual creation is
|
|
81736
81825
|
* unsupported. Lets the Add-Device dialog pick the right type per provider.
|
|
81737
81826
|
*/
|
|
81738
|
-
async getManualCreationType() {
|
|
81827
|
+
async getManualCreationType(_input) {
|
|
81739
81828
|
if (!await this.supportsManualCreation()) return { deviceType: null };
|
|
81740
81829
|
return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
|
|
81741
81830
|
}
|
|
@@ -81746,7 +81835,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
81746
81835
|
return true;
|
|
81747
81836
|
}
|
|
81748
81837
|
async getChildCreationSchema(input) {
|
|
81749
|
-
return this.onGetCreationSchema(input.type);
|
|
81838
|
+
return this.onGetCreationSchema(input.type, { integrationId: input.integrationId });
|
|
81750
81839
|
}
|
|
81751
81840
|
/**
|
|
81752
81841
|
* Default kernel-orchestrated `createDevice` implementation. The
|
|
@@ -81758,7 +81847,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
81758
81847
|
* `deviceClasses` instead.
|
|
81759
81848
|
*/
|
|
81760
81849
|
async createDevice(input) {
|
|
81761
|
-
const spec = await this.onCreateDevice(input.type, input.config);
|
|
81850
|
+
const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
|
|
81762
81851
|
const Class = this.deviceClasses[spec.meta.type];
|
|
81763
81852
|
if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
|
|
81764
81853
|
const stableId = this.generateStableId(spec.meta.type, spec.config);
|
|
@@ -83167,20 +83256,6 @@ Object.freeze({
|
|
|
83167
83256
|
access: "create",
|
|
83168
83257
|
role: "viewer"
|
|
83169
83258
|
},
|
|
83170
|
-
"broker.add": {
|
|
83171
|
-
capName: "broker",
|
|
83172
|
-
capScope: "system",
|
|
83173
|
-
addonId: null,
|
|
83174
|
-
access: "create",
|
|
83175
|
-
role: "admin"
|
|
83176
|
-
},
|
|
83177
|
-
"broker.get": {
|
|
83178
|
-
capName: "broker",
|
|
83179
|
-
capScope: "system",
|
|
83180
|
-
addonId: null,
|
|
83181
|
-
access: "view",
|
|
83182
|
-
role: "admin"
|
|
83183
|
-
},
|
|
83184
83259
|
"broker.getBrokerConfig": {
|
|
83185
83260
|
capName: "broker",
|
|
83186
83261
|
capScope: "system",
|
|
@@ -83188,20 +83263,6 @@ Object.freeze({
|
|
|
83188
83263
|
access: "view",
|
|
83189
83264
|
role: "admin"
|
|
83190
83265
|
},
|
|
83191
|
-
"broker.getSettings": {
|
|
83192
|
-
capName: "broker",
|
|
83193
|
-
capScope: "system",
|
|
83194
|
-
addonId: null,
|
|
83195
|
-
access: "view",
|
|
83196
|
-
role: "admin"
|
|
83197
|
-
},
|
|
83198
|
-
"broker.getSettingsSchema": {
|
|
83199
|
-
capName: "broker",
|
|
83200
|
-
capScope: "system",
|
|
83201
|
-
addonId: null,
|
|
83202
|
-
access: "view",
|
|
83203
|
-
role: "admin"
|
|
83204
|
-
},
|
|
83205
83266
|
"broker.getState": {
|
|
83206
83267
|
capName: "broker",
|
|
83207
83268
|
capScope: "system",
|
|
@@ -83216,20 +83277,6 @@ Object.freeze({
|
|
|
83216
83277
|
access: "view",
|
|
83217
83278
|
role: "admin"
|
|
83218
83279
|
},
|
|
83219
|
-
"broker.list": {
|
|
83220
|
-
capName: "broker",
|
|
83221
|
-
capScope: "system",
|
|
83222
|
-
addonId: null,
|
|
83223
|
-
access: "view",
|
|
83224
|
-
role: "admin"
|
|
83225
|
-
},
|
|
83226
|
-
"broker.listProviders": {
|
|
83227
|
-
capName: "broker",
|
|
83228
|
-
capScope: "system",
|
|
83229
|
-
addonId: null,
|
|
83230
|
-
access: "view",
|
|
83231
|
-
role: "admin"
|
|
83232
|
-
},
|
|
83233
83280
|
"broker.publish": {
|
|
83234
83281
|
capName: "broker",
|
|
83235
83282
|
capScope: "system",
|
|
@@ -83237,20 +83284,6 @@ Object.freeze({
|
|
|
83237
83284
|
access: "create",
|
|
83238
83285
|
role: "admin"
|
|
83239
83286
|
},
|
|
83240
|
-
"broker.remove": {
|
|
83241
|
-
capName: "broker",
|
|
83242
|
-
capScope: "system",
|
|
83243
|
-
addonId: null,
|
|
83244
|
-
access: "delete",
|
|
83245
|
-
role: "admin"
|
|
83246
|
-
},
|
|
83247
|
-
"broker.setSettings": {
|
|
83248
|
-
capName: "broker",
|
|
83249
|
-
capScope: "system",
|
|
83250
|
-
addonId: null,
|
|
83251
|
-
access: "create",
|
|
83252
|
-
role: "admin"
|
|
83253
|
-
},
|
|
83254
83287
|
"broker.subscribe": {
|
|
83255
83288
|
capName: "broker",
|
|
83256
83289
|
capScope: "system",
|
|
@@ -83258,20 +83291,6 @@ Object.freeze({
|
|
|
83258
83291
|
access: "create",
|
|
83259
83292
|
role: "admin"
|
|
83260
83293
|
},
|
|
83261
|
-
"broker.testConnection": {
|
|
83262
|
-
capName: "broker",
|
|
83263
|
-
capScope: "system",
|
|
83264
|
-
addonId: null,
|
|
83265
|
-
access: "create",
|
|
83266
|
-
role: "admin"
|
|
83267
|
-
},
|
|
83268
|
-
"broker.testSettings": {
|
|
83269
|
-
capName: "broker",
|
|
83270
|
-
capScope: "system",
|
|
83271
|
-
addonId: null,
|
|
83272
|
-
access: "create",
|
|
83273
|
-
role: "admin"
|
|
83274
|
-
},
|
|
83275
83294
|
"broker.unsubscribe": {
|
|
83276
83295
|
capName: "broker",
|
|
83277
83296
|
capScope: "system",
|
|
@@ -88235,6 +88254,13 @@ Object.freeze({
|
|
|
88235
88254
|
access: "view",
|
|
88236
88255
|
role: "viewer"
|
|
88237
88256
|
},
|
|
88257
|
+
"pipelineAnalytics.getMediaLocationCensus": {
|
|
88258
|
+
capName: "pipeline-analytics",
|
|
88259
|
+
capScope: "device",
|
|
88260
|
+
addonId: null,
|
|
88261
|
+
access: "view",
|
|
88262
|
+
role: "admin"
|
|
88263
|
+
},
|
|
88238
88264
|
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
88239
88265
|
capName: "pipeline-analytics",
|
|
88240
88266
|
capScope: "device",
|
|
@@ -97176,6 +97202,179 @@ object({
|
|
|
97176
97202
|
delay: number().nonnegative().optional(),
|
|
97177
97203
|
commands: record(string$2(), unknown())
|
|
97178
97204
|
});
|
|
97205
|
+
var BrokerInUseError = class extends Error {
|
|
97206
|
+
brokerId;
|
|
97207
|
+
users;
|
|
97208
|
+
name = "BrokerInUseError";
|
|
97209
|
+
constructor(brokerId, users) {
|
|
97210
|
+
super(`broker '${brokerId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
|
|
97211
|
+
this.brokerId = brokerId;
|
|
97212
|
+
this.users = users;
|
|
97213
|
+
}
|
|
97214
|
+
};
|
|
97215
|
+
/** Throw {@link BrokerInUseError} when any finder names a user of `brokerId`. */
|
|
97216
|
+
async function assertBrokerUnused(brokerId, finders) {
|
|
97217
|
+
const users = [];
|
|
97218
|
+
for (const finder of finders) users.push(...await finder.find(brokerId));
|
|
97219
|
+
if (users.length > 0) throw new BrokerInUseError(brokerId, users);
|
|
97220
|
+
}
|
|
97221
|
+
/**
|
|
97222
|
+
* An account provider whose `deleteInstance` is refused while anything still uses the account
|
|
97223
|
+
* (D1012). The account is an `integration-instances` kind (D1013), so the refusal lives on the
|
|
97224
|
+
* delete the instances page calls, not on a separate broker cap.
|
|
97225
|
+
*/
|
|
97226
|
+
function guardBrokerDelete(base, users) {
|
|
97227
|
+
return {
|
|
97228
|
+
...base,
|
|
97229
|
+
deleteInstance: async (input) => {
|
|
97230
|
+
await assertBrokerUnused(input.id, users());
|
|
97231
|
+
return base.deleteInstance(input);
|
|
97232
|
+
}
|
|
97233
|
+
};
|
|
97234
|
+
}
|
|
97235
|
+
/**
|
|
97236
|
+
* The generic binding of a service to a broker (D1012). A service (an integration today; a
|
|
97237
|
+
* notifier target, a calendar source or an exporter later) names the broker it runs through by id
|
|
97238
|
+
* and keeps only its OWN link settings: never a copy of the broker's host, sign-in or secrets,
|
|
97239
|
+
* which stay with the broker and are edited on the Brokers page.
|
|
97240
|
+
*
|
|
97241
|
+
* One primitive serves every consumer:
|
|
97242
|
+
* - {@link declareBrokerLinkKind}: the record kind, body `{ name, brokerId, ...linkFields }`;
|
|
97243
|
+
* - {@link buildBrokerLinkForm}: the form, a `broker-select` picker plus the service's link
|
|
97244
|
+
* sections, which may be DYNAMIC (a list read from the chosen broker, like HA calendars);
|
|
97245
|
+
* - {@link buildBrokerLinkInstances}: the `integration-instances` provider of an integration;
|
|
97246
|
+
* - {@link resolveBrokerId} / {@link brokerLinkUserFinder}: which broker a record names and which
|
|
97247
|
+
* records name a broker, so a delete of a broker still in use can name its users generically.
|
|
97248
|
+
*/
|
|
97249
|
+
/** The record kind an integration is stored under. */
|
|
97250
|
+
var BROKER_LINK_KIND_NAME = "connections";
|
|
97251
|
+
/** 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. */
|
|
97252
|
+
var BrokerLinkBodySchema = entryBody({ brokerId: string$2() });
|
|
97253
|
+
function declareBrokerLinkKind(spec) {
|
|
97254
|
+
return defineEntryKind(BROKER_LINK_KIND_NAME, {
|
|
97255
|
+
title: spec.title,
|
|
97256
|
+
schema: BrokerLinkBodySchema.extend(spec.linkFields ?? {}),
|
|
97257
|
+
writeTrigger: spec.writeTrigger
|
|
97258
|
+
});
|
|
97259
|
+
}
|
|
97260
|
+
/** The form: a picker over the brokers of `brokerKind`, then the service's link sections. */
|
|
97261
|
+
async function buildBrokerLinkForm(spec, brokerId) {
|
|
97262
|
+
const dynamic = brokerId !== void 0 && spec.dynamicSections !== void 0 ? await spec.dynamicSections(brokerId) : [];
|
|
97263
|
+
return { sections: [
|
|
97264
|
+
{
|
|
97265
|
+
id: "broker-link",
|
|
97266
|
+
title: spec.brokerLabel,
|
|
97267
|
+
description: `Each integration links to a ${spec.brokerLabel} connection (Connectivity → Brokers). The connection and its sign-in are edited there.`,
|
|
97268
|
+
columns: 1,
|
|
97269
|
+
fields: [{
|
|
97270
|
+
type: "broker-select",
|
|
97271
|
+
key: "brokerId",
|
|
97272
|
+
label: spec.brokerLabel,
|
|
97273
|
+
brokerKind: spec.brokerKind
|
|
97274
|
+
}]
|
|
97275
|
+
},
|
|
97276
|
+
...spec.linkSections ?? [],
|
|
97277
|
+
...dynamic
|
|
97278
|
+
] };
|
|
97279
|
+
}
|
|
97280
|
+
/** The broker a record names; undefined while it names none. */
|
|
97281
|
+
function resolveBrokerId(record) {
|
|
97282
|
+
return record.brokerId === void 0 || record.brokerId === "" ? void 0 : record.brokerId;
|
|
97283
|
+
}
|
|
97284
|
+
/** The broker an integration links to, from its record. */
|
|
97285
|
+
async function brokerIdOf(instances, integrationId) {
|
|
97286
|
+
const body = await instances.getResolved(integrationId);
|
|
97287
|
+
return body === null ? void 0 : resolveBrokerId(body);
|
|
97288
|
+
}
|
|
97289
|
+
/** Every record of the kind that names `brokerId`, as users of that broker. */
|
|
97290
|
+
function brokerLinkUserFinder(instances, userKind = "integration") {
|
|
97291
|
+
return { find: async (brokerId) => (await instances.listResolved()).filter((entry) => resolveBrokerId(entry.body) === brokerId).map((entry) => ({
|
|
97292
|
+
kind: userKind,
|
|
97293
|
+
id: entry.id,
|
|
97294
|
+
name: entry.body.name
|
|
97295
|
+
})) };
|
|
97296
|
+
}
|
|
97297
|
+
/** The `integration-instances` provider of an addon whose integrations link to a broker. */
|
|
97298
|
+
function buildBrokerLinkInstances(records, spec) {
|
|
97299
|
+
const instances = new RecordIntegrationInstances(records, spec.kind, {
|
|
97300
|
+
description: {
|
|
97301
|
+
role: "devices",
|
|
97302
|
+
instanceMode: "multiple",
|
|
97303
|
+
kind: "connection",
|
|
97304
|
+
canTest: false
|
|
97305
|
+
},
|
|
97306
|
+
formSchema: async (id) => buildBrokerLinkForm(spec, id === null ? void 0 : await brokerIdOf(instances, id)),
|
|
97307
|
+
initialBody: (name) => spec.kind.schema.parse({
|
|
97308
|
+
name,
|
|
97309
|
+
brokerId: ""
|
|
97310
|
+
}),
|
|
97311
|
+
configured: (body) => resolveBrokerId(body) !== void 0,
|
|
97312
|
+
...spec.onChanged !== void 0 ? { onChanged: spec.onChanged } : {}
|
|
97313
|
+
});
|
|
97314
|
+
return instances;
|
|
97315
|
+
}
|
|
97316
|
+
var BROKER_ROWS_MAX = 1e3;
|
|
97317
|
+
var STRAY_REGISTRY_ID = /^int_\d+$/;
|
|
97318
|
+
async function moveBrokerLinksToConnections(ports) {
|
|
97319
|
+
const brokers = await ports.listBrokers();
|
|
97320
|
+
const strays = brokers.filter((b) => STRAY_REGISTRY_ID.test(b.id));
|
|
97321
|
+
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(", ")}`);
|
|
97322
|
+
let moved = 0;
|
|
97323
|
+
for (const broker of brokers) {
|
|
97324
|
+
const integrationId = broker.integrationId;
|
|
97325
|
+
if (integrationId === void 0 || integrationId === "") continue;
|
|
97326
|
+
const existing = await ports.readConnection(integrationId);
|
|
97327
|
+
if (existing === null) await ports.putConnection(integrationId, {
|
|
97328
|
+
name: broker.name,
|
|
97329
|
+
brokerId: broker.id
|
|
97330
|
+
});
|
|
97331
|
+
else if (existing.brokerId !== broker.id) {
|
|
97332
|
+
ports.warn(`integration '${integrationId}' already names broker '${existing.brokerId ?? "none"}', not '${broker.id}'; the link stays on the broker`);
|
|
97333
|
+
continue;
|
|
97334
|
+
}
|
|
97335
|
+
if ((await ports.readConnection(integrationId))?.brokerId !== broker.id) throw new Error(`integration '${integrationId}': the link did not read back - broker '${broker.id}' keeps it`);
|
|
97336
|
+
await ports.clearBrokerLink(broker.id);
|
|
97337
|
+
moved += 1;
|
|
97338
|
+
}
|
|
97339
|
+
return {
|
|
97340
|
+
outcome: moved === 0 ? "already-done" : "done",
|
|
97341
|
+
summary: {
|
|
97342
|
+
brokers: brokers.length,
|
|
97343
|
+
moved,
|
|
97344
|
+
strayIntegrationRows: strays.length
|
|
97345
|
+
}
|
|
97346
|
+
};
|
|
97347
|
+
}
|
|
97348
|
+
/** The ports over the two record kinds of the addon. */
|
|
97349
|
+
function brokerLinkPortsOver(deps) {
|
|
97350
|
+
return {
|
|
97351
|
+
warn: deps.warn,
|
|
97352
|
+
listBrokers: async () => {
|
|
97353
|
+
return (await deps.brokers.query({ limit: BROKER_ROWS_MAX })).records.map((row) => {
|
|
97354
|
+
const name = row.data["name"];
|
|
97355
|
+
const link = row.data["integrationId"];
|
|
97356
|
+
return {
|
|
97357
|
+
id: row.id,
|
|
97358
|
+
name: typeof name === "string" && name.length > 0 ? name : row.id,
|
|
97359
|
+
integrationId: typeof link === "string" ? link : void 0
|
|
97360
|
+
};
|
|
97361
|
+
});
|
|
97362
|
+
},
|
|
97363
|
+
readConnection: async (id) => {
|
|
97364
|
+
const row = await deps.connections.get(id);
|
|
97365
|
+
return row === null ? null : { brokerId: row.data.brokerId };
|
|
97366
|
+
},
|
|
97367
|
+
putConnection: async (id, body) => {
|
|
97368
|
+
await deps.connections.put({
|
|
97369
|
+
id,
|
|
97370
|
+
data: body
|
|
97371
|
+
}, { ifRev: 0 });
|
|
97372
|
+
},
|
|
97373
|
+
clearBrokerLink: async (brokerId) => {
|
|
97374
|
+
await deps.brokers.patch(brokerId, { integrationId: null });
|
|
97375
|
+
}
|
|
97376
|
+
};
|
|
97377
|
+
}
|
|
97179
97378
|
//#endregion
|
|
97180
97379
|
//#region src/config.ts
|
|
97181
97380
|
/**
|
|
@@ -97202,7 +97401,6 @@ var MatterBrokerEntrySchema = object({
|
|
|
97202
97401
|
id: string$2().min(1),
|
|
97203
97402
|
name: string$2().min(1),
|
|
97204
97403
|
connection: matterConnectionSchema,
|
|
97205
|
-
integrationId: string$2().optional(),
|
|
97206
97404
|
/** Absent means on; the instances page switches a fabric off without deleting it. */
|
|
97207
97405
|
enabled: boolean().optional()
|
|
97208
97406
|
});
|
|
@@ -97229,14 +97427,6 @@ function publicMatterConnection(connection) {
|
|
|
97229
97427
|
threadDataset: ""
|
|
97230
97428
|
};
|
|
97231
97429
|
}
|
|
97232
|
-
/** Keep a stored secret when the form sent back the redacted empty string. */
|
|
97233
|
-
function mergeMatterSecrets(existing, incoming) {
|
|
97234
|
-
return {
|
|
97235
|
-
...incoming,
|
|
97236
|
-
wifiPassword: incoming.wifiPassword.length > 0 ? incoming.wifiPassword : existing.wifiPassword,
|
|
97237
|
-
threadDataset: incoming.threadDataset.length > 0 ? incoming.threadDataset : existing.threadDataset
|
|
97238
|
-
};
|
|
97239
|
-
}
|
|
97240
97430
|
/** Hand-written connection form for the broker/integration creation UI. */
|
|
97241
97431
|
function buildConnectionFormSchema() {
|
|
97242
97432
|
return { sections: [{
|
|
@@ -207620,7 +207810,6 @@ var MatterBrokerRegistry = class {
|
|
|
207620
207810
|
#onAttributeReport;
|
|
207621
207811
|
#makeManager;
|
|
207622
207812
|
#managers = /* @__PURE__ */ new Map();
|
|
207623
|
-
#integrationToBroker = /* @__PURE__ */ new Map();
|
|
207624
207813
|
#nextId = 1;
|
|
207625
207814
|
constructor(logger, deps) {
|
|
207626
207815
|
this.#logger = logger;
|
|
@@ -207669,7 +207858,6 @@ var MatterBrokerRegistry = class {
|
|
|
207669
207858
|
}
|
|
207670
207859
|
}));
|
|
207671
207860
|
this.#managers.clear();
|
|
207672
|
-
this.#integrationToBroker.clear();
|
|
207673
207861
|
}
|
|
207674
207862
|
setOnBrokerConnected(cb) {
|
|
207675
207863
|
this.#onBrokerConnected = cb;
|
|
@@ -207689,17 +207877,14 @@ var MatterBrokerRegistry = class {
|
|
|
207689
207877
|
const entry = {
|
|
207690
207878
|
id,
|
|
207691
207879
|
name,
|
|
207692
|
-
connection
|
|
207693
|
-
...opts.integrationId !== void 0 ? { integrationId: opts.integrationId } : {}
|
|
207880
|
+
connection
|
|
207694
207881
|
};
|
|
207695
207882
|
await this.#startManager(entry);
|
|
207696
|
-
if (opts.integrationId !== void 0) this.#integrationToBroker.set(opts.integrationId, id);
|
|
207697
207883
|
return entry;
|
|
207698
207884
|
}
|
|
207699
207885
|
async removeEntry(id) {
|
|
207700
207886
|
const mgr = this.#managers.get(id);
|
|
207701
207887
|
if (!mgr) return;
|
|
207702
|
-
for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
|
|
207703
207888
|
this.#managers.delete(id);
|
|
207704
207889
|
try {
|
|
207705
207890
|
await mgr.stop();
|
|
@@ -207740,9 +207925,6 @@ var MatterBrokerRegistry = class {
|
|
|
207740
207925
|
if (!mgr) throw new Error(`MatterBrokerRegistry: unknown broker id "${brokerId}"`);
|
|
207741
207926
|
await mgr.invokeCommand(invocation);
|
|
207742
207927
|
}
|
|
207743
|
-
linkIntegration(integrationId, brokerId) {
|
|
207744
|
-
this.#integrationToBroker.set(integrationId, brokerId);
|
|
207745
|
-
}
|
|
207746
207928
|
list() {
|
|
207747
207929
|
return Array.from(this.#managers.values()).map((m) => m.getInfo());
|
|
207748
207930
|
}
|
|
@@ -207761,10 +207943,7 @@ var MatterBrokerRegistry = class {
|
|
|
207761
207943
|
return count;
|
|
207762
207944
|
}
|
|
207763
207945
|
async #startManager(entry) {
|
|
207764
|
-
if (this.#managers.has(entry.id))
|
|
207765
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
207766
|
-
return;
|
|
207767
|
-
}
|
|
207946
|
+
if (this.#managers.has(entry.id)) return;
|
|
207768
207947
|
const mgr = this.#makeManager({
|
|
207769
207948
|
id: entry.id,
|
|
207770
207949
|
name: entry.name,
|
|
@@ -207777,7 +207956,6 @@ var MatterBrokerRegistry = class {
|
|
|
207777
207956
|
onAttributeReport: (id, report) => this.#onAttributeReport(id, report)
|
|
207778
207957
|
});
|
|
207779
207958
|
this.#managers.set(entry.id, mgr);
|
|
207780
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
207781
207959
|
await mgr.start();
|
|
207782
207960
|
}
|
|
207783
207961
|
/** The next free broker id, for a record minted before its manager exists. */
|
|
@@ -207827,7 +208005,7 @@ function parsePairingCode(raw) {
|
|
|
207827
208005
|
normalized: digits
|
|
207828
208006
|
};
|
|
207829
208007
|
}
|
|
207830
|
-
/** True when a code parses cleanly (no throw). Convenience for
|
|
208008
|
+
/** True when a code parses cleanly (no throw). Convenience for validating the pasted code. */
|
|
207831
208009
|
function isValidPairingCode(raw) {
|
|
207832
208010
|
try {
|
|
207833
208011
|
parsePairingCode(raw);
|
|
@@ -207838,113 +208016,22 @@ function isValidPairingCode(raw) {
|
|
|
207838
208016
|
}
|
|
207839
208017
|
//#endregion
|
|
207840
208018
|
//#region src/matter-broker-provider.ts
|
|
207841
|
-
var MATTER_KIND = "matter";
|
|
207842
208019
|
/**
|
|
207843
|
-
* Construct the `broker` cap provider for the Matter addon.
|
|
207844
|
-
*
|
|
207845
|
-
* commissioning
|
|
207846
|
-
* `target: { action: 'commissionNode' }` and `payload: { pairingCode }`.
|
|
207847
|
-
* `
|
|
207848
|
-
* preserved on an empty round-trip. Fabric keys stay in matter.js storage.
|
|
208020
|
+
* Construct the `broker` cap provider for the Matter addon: the bus only (D1013). The fabrics
|
|
208021
|
+
* themselves are the `account` kind of the addon's `integration-instances` provider. The defining
|
|
208022
|
+
* Matter operation, commissioning, is surfaced through `publish` with
|
|
208023
|
+
* `target: { action: 'commissionNode' }` and `payload: { pairingCode }`. `threadDataset` and
|
|
208024
|
+
* `wifiPassword` are redacted on config reads. Fabric keys stay in matter.js storage.
|
|
207849
208025
|
*/
|
|
207850
208026
|
function buildMatterBrokerProvider(deps) {
|
|
207851
|
-
const {
|
|
208027
|
+
const { registry, getBrokers } = deps;
|
|
207852
208028
|
const owns = (id) => getBrokers().some((b) => b.id === id);
|
|
207853
208029
|
const redact = (entry) => publicMatterConnection(entry.connection);
|
|
207854
208030
|
return {
|
|
207855
|
-
list: async ({ kind }) => {
|
|
207856
|
-
if (kind && kind !== MATTER_KIND) return [];
|
|
207857
|
-
return registry.list().map((info) => ({
|
|
207858
|
-
...info,
|
|
207859
|
-
addonId: ownerAddonId
|
|
207860
|
-
}));
|
|
207861
|
-
},
|
|
207862
|
-
get: async ({ id }) => {
|
|
207863
|
-
const info = registry.get(id);
|
|
207864
|
-
return info ? {
|
|
207865
|
-
...info,
|
|
207866
|
-
addonId: ownerAddonId
|
|
207867
|
-
} : null;
|
|
207868
|
-
},
|
|
207869
|
-
listProviders: async () => [{
|
|
207870
|
-
addonId: ownerAddonId,
|
|
207871
|
-
kinds: [{
|
|
207872
|
-
kind: MATTER_KIND,
|
|
207873
|
-
label: "Matter"
|
|
207874
|
-
}]
|
|
207875
|
-
}],
|
|
207876
|
-
add: async ({ kind, name, settings }) => {
|
|
207877
|
-
if (kind !== MATTER_KIND) throw new Error(`matter-broker: only kind '${MATTER_KIND}' is handled here (got '${kind}')`);
|
|
207878
|
-
const entry = await registry.createEntry(name, settingsToMatterConnection(settings));
|
|
207879
|
-
await persistBrokers([...getBrokers(), entry]);
|
|
207880
|
-
return { id: entry.id };
|
|
207881
|
-
},
|
|
207882
|
-
remove: async ({ id }) => {
|
|
207883
|
-
if (!owns(id)) return;
|
|
207884
|
-
await registry.removeEntry(id);
|
|
207885
|
-
await cascadeRemoveDevices(id).catch((err) => {
|
|
207886
|
-
logger.warn("matter: broker cascade-remove threw", {
|
|
207887
|
-
tags: { brokerId: id },
|
|
207888
|
-
meta: { error: errMsg(err) }
|
|
207889
|
-
});
|
|
207890
|
-
});
|
|
207891
|
-
await persistBrokers(getBrokers().filter((b) => b.id !== id));
|
|
207892
|
-
},
|
|
207893
|
-
testConnection: async ({ id }) => {
|
|
207894
|
-
if (!owns(id)) return {
|
|
207895
|
-
ok: false,
|
|
207896
|
-
error: "unknown broker"
|
|
207897
|
-
};
|
|
207898
|
-
const info = registry.get(id);
|
|
207899
|
-
if (!info) return {
|
|
207900
|
-
ok: false,
|
|
207901
|
-
error: "unknown broker"
|
|
207902
|
-
};
|
|
207903
|
-
if (info.status === "connected") return {
|
|
207904
|
-
ok: true,
|
|
207905
|
-
latencyMs: info.lastCheckedAt ? Math.max(0, Date.now() - info.lastCheckedAt) : 0
|
|
207906
|
-
};
|
|
207907
|
-
return {
|
|
207908
|
-
ok: false,
|
|
207909
|
-
error: info.error ?? `status: ${info.status}`
|
|
207910
|
-
};
|
|
207911
|
-
},
|
|
207912
|
-
getSettings: async ({ id }) => {
|
|
207913
|
-
const entry = getBrokers().find((b) => b.id === id);
|
|
207914
|
-
return entry ? redact(entry) : null;
|
|
207915
|
-
},
|
|
207916
|
-
setSettings: async ({ id, settings }) => {
|
|
207917
|
-
if (!owns(id)) return;
|
|
207918
|
-
const existing = getBrokers().find((b) => b.id === id);
|
|
207919
|
-
if (!existing) return;
|
|
207920
|
-
const parsed = settingsToMatterConnection(settings);
|
|
207921
|
-
const merged = mergeMatterSecrets(existing.connection, parsed);
|
|
207922
|
-
const updated = await registry.updateEntry(id, merged);
|
|
207923
|
-
await persistBrokers(getBrokers().map((b) => b.id === id ? updated : b));
|
|
207924
|
-
},
|
|
207925
208031
|
getBrokerConfig: async ({ id }) => {
|
|
207926
208032
|
const entry = getBrokers().find((b) => b.id === id);
|
|
207927
208033
|
return entry ? redact(entry) : null;
|
|
207928
208034
|
},
|
|
207929
|
-
getSettingsSchema: async ({ kind }) => {
|
|
207930
|
-
if (kind !== MATTER_KIND) return null;
|
|
207931
|
-
return buildConnectionFormSchema();
|
|
207932
|
-
},
|
|
207933
|
-
testSettings: async ({ kind, settings }) => {
|
|
207934
|
-
if (kind !== MATTER_KIND) return {
|
|
207935
|
-
ok: false,
|
|
207936
|
-
error: `unsupported kind: ${kind}`
|
|
207937
|
-
};
|
|
207938
|
-
try {
|
|
207939
|
-
settingsToMatterConnection(settings);
|
|
207940
|
-
return { ok: true };
|
|
207941
|
-
} catch (err) {
|
|
207942
|
-
return {
|
|
207943
|
-
ok: false,
|
|
207944
|
-
error: errMsg(err)
|
|
207945
|
-
};
|
|
207946
|
-
}
|
|
207947
|
-
},
|
|
207948
208035
|
/**
|
|
207949
208036
|
* `publish` is the commissioning + control action channel.
|
|
207950
208037
|
* `target: { action: 'commissionNode' }`, `payload: { pairingCode }` →
|
|
@@ -208164,8 +208251,8 @@ function buildMatterAdoptionProvider(deps) {
|
|
|
208164
208251
|
//#region src/matter-broker-device-cascade.ts
|
|
208165
208252
|
/**
|
|
208166
208253
|
* Remove every adopted Matter PARENT device (children cascade via the kernel)
|
|
208167
|
-
* whose persisted config carries `{ system: 'matter', brokerId }`. Used
|
|
208168
|
-
*
|
|
208254
|
+
* whose persisted config carries `{ system: 'matter', brokerId }`. Used when a
|
|
208255
|
+
* fabric instance is deleted and when an integration is deleted. Best-effort per device; returns the
|
|
208169
208256
|
* count removed. Mirrors the Ecowitt cascade helper.
|
|
208170
208257
|
*/
|
|
208171
208258
|
async function cascadeRemoveDevicesForBroker(input) {
|
|
@@ -208805,20 +208892,26 @@ var matterBrokersEntryKind = defineEntryKind("brokers", {
|
|
|
208805
208892
|
title: "Matter brokers",
|
|
208806
208893
|
schema: entryBody({
|
|
208807
208894
|
...matterConnectionSchema.shape,
|
|
208895
|
+
/** Retired (D1012): the link lives on the integration's `connections` record. Only the migration reads it. */
|
|
208808
208896
|
integrationId: string$2().optional(),
|
|
208809
208897
|
enabled: boolean().optional()
|
|
208810
208898
|
}),
|
|
208811
208899
|
writeTrigger: "an operator adds, edits or removes a Matter fabric broker",
|
|
208812
208900
|
retiredSecretPaths: ["connection.threadDataset", "connection.wifiPassword"]
|
|
208813
208901
|
});
|
|
208902
|
+
/** A Matter integration: the link to the fabric (broker) it imports devices from. */
|
|
208903
|
+
var matterConnectionsEntryKind = declareBrokerLinkKind({
|
|
208904
|
+
title: "Matter integrations",
|
|
208905
|
+
writeTrigger: "an operator links a Matter integration to a Matter fabric"
|
|
208906
|
+
});
|
|
208814
208907
|
//#endregion
|
|
208815
|
-
//#region src/matter-instances.ts
|
|
208908
|
+
//#region src/matter-account-instances.ts
|
|
208816
208909
|
/**
|
|
208817
|
-
* The Matter fabrics as `integration-instances` (planning#408 P3): one instance per `brokers`
|
|
208910
|
+
* 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`
|
|
208818
208911
|
* record, over the SAME records and ids (`matter_NNN`) the `broker` cap serves. The body is flat.
|
|
208819
208912
|
* A fabric needs no sign-in; commissioning a device stays on the adoption flow, behind the fabric.
|
|
208820
208913
|
*/
|
|
208821
|
-
var
|
|
208914
|
+
var MATTER_ACCOUNT_KIND = "account";
|
|
208822
208915
|
function statusOf(info) {
|
|
208823
208916
|
if (info === null) return {
|
|
208824
208917
|
state: "unknown",
|
|
@@ -208838,17 +208931,13 @@ function statusOf(info) {
|
|
|
208838
208931
|
...info.error !== null ? { detail: info.error } : {}
|
|
208839
208932
|
};
|
|
208840
208933
|
}
|
|
208841
|
-
function
|
|
208934
|
+
function buildMatterAccountInstances(records, deps) {
|
|
208842
208935
|
return new RecordIntegrationInstances(records, matterBrokersEntryKind, {
|
|
208843
208936
|
description: {
|
|
208844
|
-
role: "
|
|
208937
|
+
role: "services",
|
|
208845
208938
|
instanceMode: "multiple",
|
|
208846
208939
|
kind: "connection",
|
|
208847
208940
|
canTest: true,
|
|
208848
|
-
kinds: [{
|
|
208849
|
-
id: MATTER_INSTANCE_KIND,
|
|
208850
|
-
label: "Matter"
|
|
208851
|
-
}],
|
|
208852
208941
|
allowRestoreId: true
|
|
208853
208942
|
},
|
|
208854
208943
|
formSchema: () => buildConnectionFormSchema(),
|
|
@@ -208860,7 +208949,7 @@ function buildMatterInstances(records, deps) {
|
|
|
208860
208949
|
reconcileIntervalMs: 3e5
|
|
208861
208950
|
}),
|
|
208862
208951
|
mintId: () => deps.mintId(),
|
|
208863
|
-
kindOf: () =>
|
|
208952
|
+
kindOf: () => MATTER_ACCOUNT_KIND,
|
|
208864
208953
|
health: (_body, id) => {
|
|
208865
208954
|
const info = deps.infoOf(id);
|
|
208866
208955
|
return {
|
|
@@ -208889,6 +208978,22 @@ function buildMatterInstances(records, deps) {
|
|
|
208889
208978
|
});
|
|
208890
208979
|
}
|
|
208891
208980
|
//#endregion
|
|
208981
|
+
//#region src/matter-instances.ts
|
|
208982
|
+
/**
|
|
208983
|
+
* The Matter integrations as `integration-instances` (D1012): one `connections` record per
|
|
208984
|
+
* integration, keyed by the registry id, whose body is the link to a Matter fabric (a `brokers`
|
|
208985
|
+
* record, edited on the Brokers page).
|
|
208986
|
+
*/
|
|
208987
|
+
var MATTER_BROKER_KIND = "matter";
|
|
208988
|
+
function buildMatterInstances(records) {
|
|
208989
|
+
return buildBrokerLinkInstances(records, {
|
|
208990
|
+
kind: matterConnectionsEntryKind,
|
|
208991
|
+
brokerKind: MATTER_BROKER_KIND,
|
|
208992
|
+
brokerLabel: "Matter fabric",
|
|
208993
|
+
onChanged: () => void 0
|
|
208994
|
+
});
|
|
208995
|
+
}
|
|
208996
|
+
//#endregion
|
|
208892
208997
|
//#region src/migrations/flatten-broker-records.ts
|
|
208893
208998
|
/**
|
|
208894
208999
|
* The Matter broker records keep their fabric settings flat (planning#408 P3): the nested
|
|
@@ -208912,6 +209017,21 @@ async function flattenBrokerRecords(ports) {
|
|
|
208912
209017
|
});
|
|
208913
209018
|
}
|
|
208914
209019
|
//#endregion
|
|
209020
|
+
//#region src/migrations/broker-links-to-connections.ts
|
|
209021
|
+
var BROKER_LINKS_TO_CONNECTIONS_ID = "matter-broker/2026-11-05-broker-links-to-connections";
|
|
209022
|
+
/** The broker records and the integration (`connections`) records of this addon. */
|
|
209023
|
+
function matterBrokerLinkPorts(records, warn) {
|
|
209024
|
+
return brokerLinkPortsOver({
|
|
209025
|
+
brokers: records.kind({
|
|
209026
|
+
name: matterBrokersEntryKind.name,
|
|
209027
|
+
declaration: matterBrokersEntryKind.declaration,
|
|
209028
|
+
schema: record(string$2(), unknown())
|
|
209029
|
+
}),
|
|
209030
|
+
connections: records.kind(matterConnectionsEntryKind),
|
|
209031
|
+
warn
|
|
209032
|
+
});
|
|
209033
|
+
}
|
|
209034
|
+
//#endregion
|
|
208915
209035
|
//#region src/migrations/index.ts
|
|
208916
209036
|
/** The `matter-broker` catalogue (D987): offered to the engine when the addon initialises. */
|
|
208917
209037
|
var ADDON_MIGRATIONS = [{
|
|
@@ -208929,6 +209049,21 @@ var ADDON_MIGRATIONS = [{
|
|
|
208929
209049
|
fixtureMark: "the integration link and the Wi-Fi SSID of each fabric"
|
|
208930
209050
|
},
|
|
208931
209051
|
procedure: flattenBrokerRecords
|
|
209052
|
+
}, {
|
|
209053
|
+
meta: {
|
|
209054
|
+
id: BROKER_LINKS_TO_CONNECTIONS_ID,
|
|
209055
|
+
owner: "matter-broker",
|
|
209056
|
+
level: "addon-state",
|
|
209057
|
+
seq: 2,
|
|
209058
|
+
dependsOn: [FLATTEN_BROKER_RECORDS_ID],
|
|
209059
|
+
store: "settings",
|
|
209060
|
+
scope: "cluster",
|
|
209061
|
+
onFailure: "block-owner",
|
|
209062
|
+
bodyKind: "procedure",
|
|
209063
|
+
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",
|
|
209064
|
+
fixtureMark: "the name and the Wi-Fi SSID of the fabric that keeps its record"
|
|
209065
|
+
},
|
|
209066
|
+
procedure: moveBrokerLinksToConnections
|
|
208932
209067
|
}];
|
|
208933
209068
|
//#endregion
|
|
208934
209069
|
//#region src/matter-broker-store.ts
|
|
@@ -208942,7 +209077,6 @@ function rowToEntry(id, data) {
|
|
|
208942
209077
|
wifiPassword: data.wifiPassword,
|
|
208943
209078
|
reconcileIntervalMs: data.reconcileIntervalMs
|
|
208944
209079
|
},
|
|
208945
|
-
integrationId: data.integrationId,
|
|
208946
209080
|
enabled: data.enabled
|
|
208947
209081
|
});
|
|
208948
209082
|
return parsed.success ? parsed.data : null;
|
|
@@ -208979,11 +209113,10 @@ var MatterBrokerStore = class MatterBrokerStore {
|
|
|
208979
209113
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
208980
209114
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
208981
209115
|
for (const entry of entries) {
|
|
208982
|
-
const { id, name, connection,
|
|
209116
|
+
const { id, name, connection, enabled } = entry;
|
|
208983
209117
|
const body = {
|
|
208984
209118
|
name,
|
|
208985
209119
|
...connection,
|
|
208986
|
-
...integrationId === void 0 ? {} : { integrationId },
|
|
208987
209120
|
...enabled === void 0 ? {} : { enabled }
|
|
208988
209121
|
};
|
|
208989
209122
|
const parsed = matterBrokersEntryKind.schema.safeParse(body);
|
|
@@ -209021,6 +209154,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209021
209154
|
registry = null;
|
|
209022
209155
|
brokerStore = null;
|
|
209023
209156
|
instances = null;
|
|
209157
|
+
accountInstances = null;
|
|
209024
209158
|
activeBrokers = [];
|
|
209025
209159
|
constructor() {
|
|
209026
209160
|
super({ ...DEFAULTS });
|
|
@@ -209029,15 +209163,22 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209029
209163
|
const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
|
|
209030
209164
|
const records = this.ctx.records;
|
|
209031
209165
|
if (records === void 0) throw new Error("matter-broker: ctx.records is required (hub-only addon)");
|
|
209032
|
-
await records.declare({
|
|
209166
|
+
await records.declare({
|
|
209167
|
+
brokers: matterBrokersEntryKind,
|
|
209168
|
+
connections: matterConnectionsEntryKind
|
|
209169
|
+
});
|
|
209033
209170
|
this.brokerStore = MatterBrokerStore.of(records);
|
|
209034
|
-
this.instances = buildMatterInstances(records
|
|
209171
|
+
this.instances = buildMatterInstances(records);
|
|
209172
|
+
this.accountInstances = buildMatterAccountInstances(records, {
|
|
209035
209173
|
mintId: () => this.requireRegistry().allocateId(),
|
|
209036
209174
|
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
209037
209175
|
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
209038
209176
|
});
|
|
209039
209177
|
if (this.ctx.secrets === void 0) throw new Error("matter-broker: ctx.secrets is required");
|
|
209040
|
-
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS,
|
|
209178
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, {
|
|
209179
|
+
...flattenPortsOver(records.kind(matterBrokersEntryKind)),
|
|
209180
|
+
...matterBrokerLinkPorts(records, (message) => this.ctx.logger.warn(message))
|
|
209181
|
+
}));
|
|
209041
209182
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
209042
209183
|
this.registry = new MatterBrokerRegistry(this.ctx.logger, {
|
|
209043
209184
|
dataDir: this.ctx.dataDir,
|
|
@@ -209058,8 +209199,6 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209058
209199
|
matterCommands.setDispatcher((brokerId, invocation) => this.requireRegistry().invokeCommand(brokerId, invocation));
|
|
209059
209200
|
await this.registry.restore(this.activeBrokers);
|
|
209060
209201
|
this.ctx.logger.info("Matter: broker addon initialised", { meta: { brokerCount: this.activeBrokers.length } });
|
|
209061
|
-
await this.reconcileIntegrationsToBrokers();
|
|
209062
|
-
this.subscribeIntegrationLifecycle();
|
|
209063
209202
|
return [
|
|
209064
209203
|
...regs,
|
|
209065
209204
|
{
|
|
@@ -209068,7 +209207,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209068
209207
|
},
|
|
209069
209208
|
{
|
|
209070
209209
|
capability: integrationInstancesCapability,
|
|
209071
|
-
provider: this.
|
|
209210
|
+
provider: this.buildInstancesProvider()
|
|
209072
209211
|
},
|
|
209073
209212
|
{
|
|
209074
209213
|
capability: deviceAdoptionCapability,
|
|
@@ -209087,9 +209226,37 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209087
209226
|
if (this.instances === null) throw new Error("matter-broker: instances not ready");
|
|
209088
209227
|
return this.instances;
|
|
209089
209228
|
}
|
|
209229
|
+
/** One provider over the two record kinds: the fabric (broker) and the integrations using it. */
|
|
209230
|
+
buildInstancesProvider() {
|
|
209231
|
+
if (this.accountInstances === null) throw new Error("matter-broker: fabric instances not ready");
|
|
209232
|
+
return composeInstanceKinds([{
|
|
209233
|
+
kind: {
|
|
209234
|
+
id: "account",
|
|
209235
|
+
label: "Matter fabric",
|
|
209236
|
+
functionCap: "broker",
|
|
209237
|
+
brokerKind: "matter",
|
|
209238
|
+
role: "services"
|
|
209239
|
+
},
|
|
209240
|
+
provider: guardBrokerDelete(this.accountInstances.toProvider(), () => [brokerLinkUserFinder(this.requireInstances())])
|
|
209241
|
+
}, {
|
|
209242
|
+
kind: {
|
|
209243
|
+
id: "integration",
|
|
209244
|
+
label: "Matter",
|
|
209245
|
+
functionCap: "device-provider",
|
|
209246
|
+
role: "devices"
|
|
209247
|
+
},
|
|
209248
|
+
provider: this.requireInstances().toProvider()
|
|
209249
|
+
}], {
|
|
209250
|
+
role: "devices",
|
|
209251
|
+
instanceMode: "multiple",
|
|
209252
|
+
kind: "account",
|
|
209253
|
+
canTest: true,
|
|
209254
|
+
allowRestoreId: true
|
|
209255
|
+
});
|
|
209256
|
+
}
|
|
209090
209257
|
/**
|
|
209091
|
-
* Bring the runtime in line with one record after the instances page changed it: a
|
|
209092
|
-
* record stops its controller and cascades its devices, an off record only stops the
|
|
209258
|
+
* Bring the runtime in line with one fabric record after the instances page changed it: a
|
|
209259
|
+
* removed record stops its controller and cascades its devices, an off record only stops the
|
|
209093
209260
|
* controller, a live one re-applies its settings or starts under the record's own id.
|
|
209094
209261
|
*/
|
|
209095
209262
|
async syncBrokerRuntime(id) {
|
|
@@ -209107,10 +209274,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209107
209274
|
}
|
|
209108
209275
|
try {
|
|
209109
209276
|
if (reg.get(id) !== null) await reg.updateEntry(id, entry.connection);
|
|
209110
|
-
else await reg.createEntry(entry.name, entry.connection, {
|
|
209111
|
-
id,
|
|
209112
|
-
...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
|
|
209113
|
-
});
|
|
209277
|
+
else await reg.createEntry(entry.name, entry.connection, { id });
|
|
209114
209278
|
} catch (err) {
|
|
209115
209279
|
this.ctx.logger.warn("matter instance: could not start the fabric", { meta: {
|
|
209116
209280
|
brokerId: id,
|
|
@@ -209237,12 +209401,8 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209237
209401
|
}
|
|
209238
209402
|
buildBrokerProvider() {
|
|
209239
209403
|
return buildMatterBrokerProvider({
|
|
209240
|
-
ownerAddonId: this.ctx.id,
|
|
209241
209404
|
registry: this.requireRegistry(),
|
|
209242
|
-
getBrokers: () => this.activeBrokers
|
|
209243
|
-
persistBrokers: (brokers) => this.persistBrokerEntries(brokers),
|
|
209244
|
-
cascadeRemoveDevices: (brokerId) => this.cascadeRemoveDevicesForBroker(brokerId),
|
|
209245
|
-
logger: this.ctx.logger
|
|
209405
|
+
getBrokers: () => this.activeBrokers
|
|
209246
209406
|
});
|
|
209247
209407
|
}
|
|
209248
209408
|
async cascadeRemoveDevicesForBroker(brokerId) {
|
|
@@ -209257,42 +209417,6 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209257
209417
|
logger: this.ctx.logger
|
|
209258
209418
|
});
|
|
209259
209419
|
}
|
|
209260
|
-
async reconcileIntegrationsToBrokers() {
|
|
209261
|
-
try {
|
|
209262
|
-
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
209263
|
-
const surviving = new Set(mine.map((i) => i.id));
|
|
209264
|
-
for (const broker of this.activeBrokers) {
|
|
209265
|
-
const integrationId = broker.integrationId;
|
|
209266
|
-
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
209267
|
-
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
209268
|
-
}
|
|
209269
|
-
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
209270
|
-
if (toRemove.length === 0) return;
|
|
209271
|
-
for (const id of toRemove) try {
|
|
209272
|
-
await this.requireRegistry().removeEntry(id);
|
|
209273
|
-
await this.cascadeRemoveDevicesForBroker(id);
|
|
209274
|
-
} catch (err) {
|
|
209275
|
-
this.ctx.logger.warn("Matter integration→broker: broker cleanup failed", { meta: {
|
|
209276
|
-
brokerId: id,
|
|
209277
|
-
error: errMsg(err)
|
|
209278
|
-
} });
|
|
209279
|
-
}
|
|
209280
|
-
const nextBrokers = this.activeBrokers.filter((b) => !toRemove.includes(b.id));
|
|
209281
|
-
await this.persistBrokerEntries(nextBrokers);
|
|
209282
|
-
} catch (err) {
|
|
209283
|
-
this.ctx.logger.warn("Matter integration→broker reconcile failed", { meta: { error: errMsg(err) } });
|
|
209284
|
-
}
|
|
209285
|
-
}
|
|
209286
|
-
subscribeIntegrationLifecycle() {
|
|
209287
|
-
const handler = (event) => {
|
|
209288
|
-
const addonId = event.data["addonId"];
|
|
209289
|
-
if (typeof addonId === "string" && addonId !== this.ctx.id) return;
|
|
209290
|
-
this.reconcileIntegrationsToBrokers();
|
|
209291
|
-
};
|
|
209292
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationEnabled }, handler);
|
|
209293
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
|
|
209294
|
-
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
|
|
209295
|
-
}
|
|
209296
209420
|
buildAdoptionProvider() {
|
|
209297
209421
|
return buildMatterAdoptionProvider({
|
|
209298
209422
|
registry: {
|
|
@@ -209305,9 +209429,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
209305
209429
|
},
|
|
209306
209430
|
logger: this.ctx.logger,
|
|
209307
209431
|
getBrokerIdForIntegration: async (id) => {
|
|
209308
|
-
const
|
|
209309
|
-
if (
|
|
209310
|
-
return
|
|
209432
|
+
const brokerId = await brokerIdOf(this.requireInstances(), id);
|
|
209433
|
+
if (brokerId === void 0) throw new Error(`integration ${id} has no broker`);
|
|
209434
|
+
return brokerId;
|
|
209311
209435
|
},
|
|
209312
209436
|
listAdoptedMatter: async () => {
|
|
209313
209437
|
const reg = this.ctx.kernel.deviceRegistry;
|