@camstack/addon-smtp-nodemailer 1.2.147 → 1.2.149
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 +15 -0
- package/dist/smtp.addon.js +1017 -777
- package/dist/smtp.addon.mjs +1017 -777
- package/package.json +2 -2
package/dist/smtp.addon.mjs
CHANGED
|
@@ -6018,7 +6018,7 @@ var ZodIssueCode = {
|
|
|
6018
6018
|
var ZodFirstPartyTypeKind;
|
|
6019
6019
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6020
6020
|
//#endregion
|
|
6021
|
-
//#region ../types/dist/sleep-
|
|
6021
|
+
//#region ../types/dist/sleep-BX_MqzUe.mjs
|
|
6022
6022
|
/**
|
|
6023
6023
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6024
6024
|
* window to float samples (D455).
|
|
@@ -6618,7 +6618,7 @@ var NOT_RUN = {
|
|
|
6618
6618
|
* throws: an unreachable or refusing engine is logged and reported `declared: false`,
|
|
6619
6619
|
* because the hub (and this addon's boot) must not hang on a migration.
|
|
6620
6620
|
*/
|
|
6621
|
-
async function runAddonMigrations(host, migrations, logger) {
|
|
6621
|
+
async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
6622
6622
|
const first = migrations[0];
|
|
6623
6623
|
if (first === void 0) return NOT_RUN;
|
|
6624
6624
|
const owner = first.meta.owner;
|
|
@@ -6647,7 +6647,9 @@ async function runAddonMigrations(host, migrations, logger) {
|
|
|
6647
6647
|
else if (status === "blocked") blocked.push(id);
|
|
6648
6648
|
else skipped.push(id);
|
|
6649
6649
|
};
|
|
6650
|
+
const selected = options.only === void 0 ? null : new Set(options.only);
|
|
6650
6651
|
for (const step of declaration.steps) {
|
|
6652
|
+
if (selected !== null && !selected.has(step.id)) continue;
|
|
6651
6653
|
const bound = byId.get(step.id);
|
|
6652
6654
|
if (bound === void 0 || step.action === "skip") {
|
|
6653
6655
|
skipped.push(step.id);
|
|
@@ -6825,6 +6827,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6825
6827
|
}
|
|
6826
6828
|
return out;
|
|
6827
6829
|
}
|
|
6830
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6831
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6832
|
+
"cluster",
|
|
6833
|
+
"device-defaults",
|
|
6834
|
+
"node"
|
|
6835
|
+
];
|
|
6828
6836
|
/**
|
|
6829
6837
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6830
6838
|
*
|
|
@@ -7639,6 +7647,28 @@ var SECRET_RETRY_MAX_MS = 5 * 6e4;
|
|
|
7639
7647
|
/** Coalesces a burst of `keychain.changed` events into one re-resolve. */
|
|
7640
7648
|
var SECRET_CHANGE_DEBOUNCE_MS = 250;
|
|
7641
7649
|
/**
|
|
7650
|
+
* Pair a catalogue with the function that builds its ports FROM THE MIGRATION API. The one
|
|
7651
|
+
* place a catalogue is bound: a migration reaches nothing a ports binder cannot reach
|
|
7652
|
+
* through `api` (guard `check-migration-ports-surface`).
|
|
7653
|
+
*/
|
|
7654
|
+
function migrationSet(catalogue, bindPorts) {
|
|
7655
|
+
return { bind: (api) => defineAddonMigrations(catalogue, bindPorts(api)) };
|
|
7656
|
+
}
|
|
7657
|
+
/** A mixed array is a mistake: the other kind would be dropped without a word. */
|
|
7658
|
+
function refuseMixed(form, kept, dropped) {
|
|
7659
|
+
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`);
|
|
7660
|
+
}
|
|
7661
|
+
function catalogueOf(list) {
|
|
7662
|
+
const kept = list.filter((entry) => !("execute" in entry));
|
|
7663
|
+
refuseMixed("catalogue, (api) => ports", kept.length, list.length - kept.length);
|
|
7664
|
+
return kept;
|
|
7665
|
+
}
|
|
7666
|
+
function boundOf(list) {
|
|
7667
|
+
const kept = list.filter((entry) => "execute" in entry);
|
|
7668
|
+
refuseMixed("bound migrations", kept.length, list.length - kept.length);
|
|
7669
|
+
return kept;
|
|
7670
|
+
}
|
|
7671
|
+
/**
|
|
7642
7672
|
* Base class for CamStack addons. Eliminates settings boilerplate:
|
|
7643
7673
|
*
|
|
7644
7674
|
* - Typed `config` property with automatic resolution from store + defaults
|
|
@@ -7716,6 +7746,13 @@ var BaseAddon = class {
|
|
|
7716
7746
|
*/
|
|
7717
7747
|
secretStatePaths = [];
|
|
7718
7748
|
_addonSecretTree = null;
|
|
7749
|
+
/**
|
|
7750
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7751
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7752
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7753
|
+
*/
|
|
7754
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7755
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7719
7756
|
_deviceSecretTree = null;
|
|
7720
7757
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7721
7758
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7785,6 +7822,7 @@ var BaseAddon = class {
|
|
|
7785
7822
|
this._ctx = context;
|
|
7786
7823
|
this._secretSettings = null;
|
|
7787
7824
|
}
|
|
7825
|
+
await this.loadScopedSchemas();
|
|
7788
7826
|
await this.resolveConfig();
|
|
7789
7827
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7790
7828
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7794,27 +7832,30 @@ var BaseAddon = class {
|
|
|
7794
7832
|
const normalized = normalizeAddonInitResult(result);
|
|
7795
7833
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7796
7834
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7835
|
+
await this.loadScopedSchemas();
|
|
7797
7836
|
return normalized;
|
|
7798
7837
|
}
|
|
7838
|
+
async runMigrations(first, second, third) {
|
|
7839
|
+
if (typeof second === "function") return this.runMigrationSets([migrationSet(catalogueOf(first), second)], third);
|
|
7840
|
+
return this.runBound(boundOf(first), second);
|
|
7841
|
+
}
|
|
7799
7842
|
/**
|
|
7800
|
-
*
|
|
7801
|
-
*
|
|
7802
|
-
*
|
|
7803
|
-
* restored devices — with the catalogue bound to the addon's ports
|
|
7804
|
-
* (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
|
|
7805
|
-
*
|
|
7806
|
-
* An engine that cannot be reached or refuses the catalogue is logged and runs
|
|
7807
|
-
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7808
|
-
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7809
|
-
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7843
|
+
* Several catalogues of ONE owner in one declaration (the engine replaces an owner's
|
|
7844
|
+
* catalogue on every declare, so two calls would each judge the other's ids "newer").
|
|
7845
|
+
* Build each entry with `migrationSet(catalogue, (api) => ports)`.
|
|
7810
7846
|
*/
|
|
7811
|
-
async
|
|
7847
|
+
async runMigrationSets(sets, options) {
|
|
7848
|
+
const api = this.ctx.migrationApi;
|
|
7849
|
+
if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
|
|
7850
|
+
return this.runBound(sets.flatMap((set) => set.bind(api)), options);
|
|
7851
|
+
}
|
|
7852
|
+
async runBound(migrations, options) {
|
|
7812
7853
|
const api = this.ctx.api.migrations;
|
|
7813
7854
|
const report = await runAddonMigrations({
|
|
7814
7855
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7815
7856
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7816
7857
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7817
|
-
}, migrations, this.ctx.logger);
|
|
7858
|
+
}, migrations, this.ctx.logger, options);
|
|
7818
7859
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7819
7860
|
return report;
|
|
7820
7861
|
}
|
|
@@ -7878,6 +7919,16 @@ var BaseAddon = class {
|
|
|
7878
7919
|
schema(schema) {
|
|
7879
7920
|
return schema;
|
|
7880
7921
|
}
|
|
7922
|
+
/**
|
|
7923
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7924
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7925
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7926
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7927
|
+
* once known.
|
|
7928
|
+
*/
|
|
7929
|
+
scopedSettingsCapabilities() {
|
|
7930
|
+
return [];
|
|
7931
|
+
}
|
|
7881
7932
|
/** Override to provide global-level settings UI schema. */
|
|
7882
7933
|
globalSettingsSchema(_cap) {
|
|
7883
7934
|
return null;
|
|
@@ -7921,7 +7972,8 @@ var BaseAddon = class {
|
|
|
7921
7972
|
return [];
|
|
7922
7973
|
}
|
|
7923
7974
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7924
|
-
|
|
7975
|
+
await this.loadScopedSchemas(cap);
|
|
7976
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7925
7977
|
if (!schema) return { sections: [] };
|
|
7926
7978
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7927
7979
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7956,10 +8008,9 @@ var BaseAddon = class {
|
|
|
7956
8008
|
*
|
|
7957
8009
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7958
8010
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7959
|
-
* SHAPE of the form only — the values live
|
|
7960
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
8011
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7961
8012
|
*/
|
|
7962
|
-
async
|
|
8013
|
+
async integrationFormSchema(_instance) {
|
|
7963
8014
|
return null;
|
|
7964
8015
|
}
|
|
7965
8016
|
/**
|
|
@@ -8005,7 +8056,7 @@ var BaseAddon = class {
|
|
|
8005
8056
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
8006
8057
|
*/
|
|
8007
8058
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
8008
|
-
const scoped = await this.
|
|
8059
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
8009
8060
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
8010
8061
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
8011
8062
|
}
|
|
@@ -8053,6 +8104,7 @@ var BaseAddon = class {
|
|
|
8053
8104
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8054
8105
|
*/
|
|
8055
8106
|
async resolveGlobalStore(nodeId, cap) {
|
|
8107
|
+
await this.loadScopedSchemas(cap);
|
|
8056
8108
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8057
8109
|
return this.projectForNode(raw, nodeId, cap);
|
|
8058
8110
|
}
|
|
@@ -8063,6 +8115,7 @@ var BaseAddon = class {
|
|
|
8063
8115
|
}
|
|
8064
8116
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8065
8117
|
async readRawProjectedStore(nodeId, cap) {
|
|
8118
|
+
await this.loadScopedSchemas(cap);
|
|
8066
8119
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8067
8120
|
return this.projectForNode(raw, nodeId, cap);
|
|
8068
8121
|
}
|
|
@@ -8165,6 +8218,7 @@ var BaseAddon = class {
|
|
|
8165
8218
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8166
8219
|
}
|
|
8167
8220
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8221
|
+
await this.loadScopedSchemas();
|
|
8168
8222
|
const keys = this.perNodeKeys();
|
|
8169
8223
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8170
8224
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8178,7 +8232,8 @@ var BaseAddon = class {
|
|
|
8178
8232
|
await this.resolveConfig();
|
|
8179
8233
|
await this.onConfigChanged();
|
|
8180
8234
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8181
|
-
|
|
8235
|
+
const legacy = this.globalSettingsSchema();
|
|
8236
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8182
8237
|
}
|
|
8183
8238
|
/**
|
|
8184
8239
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8186,14 +8241,67 @@ var BaseAddon = class {
|
|
|
8186
8241
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8187
8242
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8188
8243
|
*/
|
|
8189
|
-
|
|
8244
|
+
/**
|
|
8245
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8246
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8247
|
+
* derived from the previous set. Idempotent.
|
|
8248
|
+
*/
|
|
8249
|
+
async loadScopedSchemas(extraCap) {
|
|
8250
|
+
const caps = new Set([
|
|
8251
|
+
...this.scopedSettingsCapabilities(),
|
|
8252
|
+
...this._registeredCapNames,
|
|
8253
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8254
|
+
]);
|
|
8255
|
+
let added = false;
|
|
8256
|
+
for (const cap of caps) {
|
|
8257
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8258
|
+
const loaded = [];
|
|
8259
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8260
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8261
|
+
if (schema !== null) loaded.push({
|
|
8262
|
+
scope,
|
|
8263
|
+
schema
|
|
8264
|
+
});
|
|
8265
|
+
}
|
|
8266
|
+
this._scopedByCap.set(cap, loaded);
|
|
8267
|
+
added = true;
|
|
8268
|
+
}
|
|
8269
|
+
if (!added) return;
|
|
8270
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8271
|
+
this._addonSecretTree = null;
|
|
8272
|
+
}
|
|
8273
|
+
/**
|
|
8274
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8275
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8276
|
+
*/
|
|
8277
|
+
scopedFormSchema(cap) {
|
|
8278
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8279
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8280
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8281
|
+
}
|
|
8282
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8283
|
+
loadedScopedSchemas(cap) {
|
|
8284
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8285
|
+
return [...this._scopedByCap.values()].flat();
|
|
8286
|
+
}
|
|
8287
|
+
/**
|
|
8288
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8289
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8290
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8291
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8292
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8293
|
+
*/
|
|
8190
8294
|
perNodeKeys(cap) {
|
|
8191
8295
|
const cacheKey = cap ?? "";
|
|
8192
|
-
const cached = this.
|
|
8296
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8193
8297
|
if (cached) return cached;
|
|
8194
|
-
const
|
|
8195
|
-
const keys = new Set(
|
|
8196
|
-
this.
|
|
8298
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8299
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8300
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8301
|
+
if (scope !== "node") continue;
|
|
8302
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8303
|
+
}
|
|
8304
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8197
8305
|
return keys;
|
|
8198
8306
|
}
|
|
8199
8307
|
/**
|
|
@@ -8203,10 +8311,9 @@ var BaseAddon = class {
|
|
|
8203
8311
|
* time to return its response before the addon is torn down and
|
|
8204
8312
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8205
8313
|
*/
|
|
8206
|
-
maybeAutoRestart(patchKeys,
|
|
8207
|
-
if (!schema) return;
|
|
8314
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8208
8315
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8209
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8316
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8210
8317
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8211
8318
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8212
8319
|
}
|
|
@@ -8402,12 +8509,14 @@ var BaseAddon = class {
|
|
|
8402
8509
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8403
8510
|
*/
|
|
8404
8511
|
async getDeclaredSettingsKeys() {
|
|
8512
|
+
await this.loadScopedSchemas();
|
|
8405
8513
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8406
8514
|
}
|
|
8407
8515
|
declaredKeySet() {
|
|
8408
8516
|
const keys = new Set(Object.keys(this.defaults));
|
|
8409
8517
|
const schema = this.globalSettingsSchema();
|
|
8410
8518
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8519
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8411
8520
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8412
8521
|
for (const path of this.secretStatePaths) {
|
|
8413
8522
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8503,7 +8612,11 @@ var BaseAddon = class {
|
|
|
8503
8612
|
}
|
|
8504
8613
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8505
8614
|
addonSecretTree() {
|
|
8506
|
-
this._addonSecretTree
|
|
8615
|
+
if (this._addonSecretTree === null) {
|
|
8616
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8617
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8618
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8619
|
+
}
|
|
8507
8620
|
return this._addonSecretTree;
|
|
8508
8621
|
}
|
|
8509
8622
|
deviceSecretTree() {
|
|
@@ -8753,6 +8866,23 @@ function dropPerNodeFields(fields) {
|
|
|
8753
8866
|
}
|
|
8754
8867
|
return kept;
|
|
8755
8868
|
}
|
|
8869
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8870
|
+
function collectValueFieldKeys(fields) {
|
|
8871
|
+
const collected = [];
|
|
8872
|
+
for (const field of fields) {
|
|
8873
|
+
if (field.type === "group") {
|
|
8874
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8875
|
+
continue;
|
|
8876
|
+
}
|
|
8877
|
+
if (field.type === "sub-tabs") {
|
|
8878
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8879
|
+
continue;
|
|
8880
|
+
}
|
|
8881
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8882
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8883
|
+
}
|
|
8884
|
+
return collected;
|
|
8885
|
+
}
|
|
8756
8886
|
function collectFieldKeys(fields) {
|
|
8757
8887
|
const collected = [];
|
|
8758
8888
|
for (const field of fields) {
|
|
@@ -9286,6 +9416,7 @@ function defineEntryKind(name, spec) {
|
|
|
9286
9416
|
class: "config",
|
|
9287
9417
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9288
9418
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9419
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9289
9420
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9290
9421
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9291
9422
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11965,7 +12096,7 @@ function userError(code, params, englishFallback) {
|
|
|
11965
12096
|
return new CamstackError(code, params, englishFallback);
|
|
11966
12097
|
}
|
|
11967
12098
|
//#endregion
|
|
11968
|
-
//#region ../types/dist/composition-
|
|
12099
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11969
12100
|
/**
|
|
11970
12101
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11971
12102
|
*
|
|
@@ -13463,7 +13594,7 @@ var RecordIntegrationInstances = class {
|
|
|
13463
13594
|
return record === null ? null : this.viewOf(record);
|
|
13464
13595
|
};
|
|
13465
13596
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13466
|
-
createInstance = async ({ id, name, kind }) => {
|
|
13597
|
+
createInstance = async ({ id, name, kind, values }) => {
|
|
13467
13598
|
const minted = id ?? this.hooks.mintId?.({
|
|
13468
13599
|
name,
|
|
13469
13600
|
...kind !== void 0 ? { kind } : {}
|
|
@@ -13473,10 +13604,20 @@ var RecordIntegrationInstances = class {
|
|
|
13473
13604
|
id: minted,
|
|
13474
13605
|
data: this.hooks.initialBody(name, kind)
|
|
13475
13606
|
}, { ifRev: 0 });
|
|
13607
|
+
const patch = this.patchOf(values ?? {});
|
|
13608
|
+
if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
|
|
13476
13609
|
await this.hooks.onChanged?.(minted, "created");
|
|
13477
13610
|
return { id: minted };
|
|
13478
13611
|
};
|
|
13479
13612
|
updateInstance = async ({ id, name, values }) => {
|
|
13613
|
+
const patch = this.patchOf(values);
|
|
13614
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13615
|
+
await this.handle.patch(id, patch);
|
|
13616
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13617
|
+
return { ok: true };
|
|
13618
|
+
};
|
|
13619
|
+
/** The record patch a form `values` object means: secrets kept, cleared or replaced. */
|
|
13620
|
+
patchOf(values) {
|
|
13480
13621
|
const patch = {};
|
|
13481
13622
|
for (const [key, value] of Object.entries(values)) {
|
|
13482
13623
|
if (value === void 0) continue;
|
|
@@ -13487,11 +13628,8 @@ var RecordIntegrationInstances = class {
|
|
|
13487
13628
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13488
13629
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13489
13630
|
}
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
await this.hooks.onChanged?.(id, "updated");
|
|
13493
|
-
return { ok: true };
|
|
13494
|
-
};
|
|
13631
|
+
return patch;
|
|
13632
|
+
}
|
|
13495
13633
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13496
13634
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13497
13635
|
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
@@ -13528,6 +13666,76 @@ var RecordIntegrationInstances = class {
|
|
|
13528
13666
|
};
|
|
13529
13667
|
}
|
|
13530
13668
|
};
|
|
13669
|
+
/** No part holds the id a call named. */
|
|
13670
|
+
var UnknownInstanceError = class extends Error {
|
|
13671
|
+
instanceId;
|
|
13672
|
+
constructor(instanceId) {
|
|
13673
|
+
super(`Unknown instance '${instanceId}'`);
|
|
13674
|
+
this.instanceId = instanceId;
|
|
13675
|
+
this.name = "UnknownInstanceError";
|
|
13676
|
+
}
|
|
13677
|
+
};
|
|
13678
|
+
/** A create or a form named no kind, or a kind no part serves. */
|
|
13679
|
+
var UnknownInstanceKindError = class extends Error {
|
|
13680
|
+
kindId;
|
|
13681
|
+
constructor(kindId) {
|
|
13682
|
+
super(kindId === void 0 ? "This provider has several kinds: name one" : `Unknown instance kind '${kindId}'`);
|
|
13683
|
+
this.kindId = kindId;
|
|
13684
|
+
this.name = "UnknownInstanceKindError";
|
|
13685
|
+
}
|
|
13686
|
+
};
|
|
13687
|
+
function composeInstanceKinds(parts, base) {
|
|
13688
|
+
const byKind = (kindId) => {
|
|
13689
|
+
if (kindId === void 0) {
|
|
13690
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
13691
|
+
if (only === void 0) throw new UnknownInstanceKindError(void 0);
|
|
13692
|
+
return only;
|
|
13693
|
+
}
|
|
13694
|
+
const part = parts.find((candidate) => candidate.kind.id === kindId);
|
|
13695
|
+
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13696
|
+
return part;
|
|
13697
|
+
};
|
|
13698
|
+
const holderOf = async (id) => {
|
|
13699
|
+
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13700
|
+
throw new UnknownInstanceError(id);
|
|
13701
|
+
};
|
|
13702
|
+
const tagged = (part, instance) => ({
|
|
13703
|
+
...instance,
|
|
13704
|
+
kind: part.kind.id
|
|
13705
|
+
});
|
|
13706
|
+
return {
|
|
13707
|
+
describe: async () => ({
|
|
13708
|
+
...base,
|
|
13709
|
+
kinds: parts.map((part) => part.kind)
|
|
13710
|
+
}),
|
|
13711
|
+
instanceFormSchema: async ({ id, kind }) => {
|
|
13712
|
+
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13713
|
+
},
|
|
13714
|
+
listInstances: async () => {
|
|
13715
|
+
return (await Promise.all(parts.map(async (part) => (await part.provider.listInstances()).map((instance) => tagged(part, instance))))).flat();
|
|
13716
|
+
},
|
|
13717
|
+
getInstance: async ({ id }) => {
|
|
13718
|
+
for (const part of parts) {
|
|
13719
|
+
const found = await part.provider.getInstance({ id });
|
|
13720
|
+
if (found !== null) return tagged(part, found);
|
|
13721
|
+
}
|
|
13722
|
+
return null;
|
|
13723
|
+
},
|
|
13724
|
+
createInstance: async (input) => byKind(input.kind).provider.createInstance(input),
|
|
13725
|
+
updateInstance: async (input) => (await holderOf(input.id)).provider.updateInstance(input),
|
|
13726
|
+
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13727
|
+
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13728
|
+
testInstance: async (input) => {
|
|
13729
|
+
const test = (input.id === null ? byKind(input.kind) : await holderOf(input.id)).provider.testInstance;
|
|
13730
|
+
if (test === void 0) return {
|
|
13731
|
+
success: false,
|
|
13732
|
+
status: "inconclusive",
|
|
13733
|
+
error: "This kind cannot be tested"
|
|
13734
|
+
};
|
|
13735
|
+
return test(input);
|
|
13736
|
+
}
|
|
13737
|
+
};
|
|
13738
|
+
}
|
|
13531
13739
|
var EncodeProfileSchema = object({
|
|
13532
13740
|
video: object({
|
|
13533
13741
|
codec: _enum([
|
|
@@ -14573,7 +14781,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14573
14781
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14574
14782
|
* catalogue lists the id (a newer database).
|
|
14575
14783
|
*/
|
|
14576
|
-
meta: MigrationMetaSchema.optional()
|
|
14784
|
+
meta: MigrationMetaSchema.optional(),
|
|
14785
|
+
/**
|
|
14786
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14787
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14788
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14789
|
+
*/
|
|
14790
|
+
ownerNotLoaded: boolean().optional()
|
|
14577
14791
|
});
|
|
14578
14792
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14579
14793
|
var MigrationPendingEntrySchema = object({
|
|
@@ -15694,6 +15908,39 @@ var UnstampedEventMediaCountSchema = object({
|
|
|
15694
15908
|
/** Sum across both, or `null` when either lane could not be counted. */
|
|
15695
15909
|
total: number().int().nonnegative().nullable()
|
|
15696
15910
|
}).nullable();
|
|
15911
|
+
/**
|
|
15912
|
+
* Where the stored event media sits, per storage location: how many media rows
|
|
15913
|
+
* and how many bytes carry each location id, how many carry none (legacy,
|
|
15914
|
+
* resolved to the default `eventMedia` location), and how many name a location
|
|
15915
|
+
* that was not asked about (a deleted or never-listed one).
|
|
15916
|
+
*
|
|
15917
|
+
* One covering-index aggregate per asked location plus the NULL one, and one
|
|
15918
|
+
* unfiltered total that scans the covering index: O(rows), not a walk of the
|
|
15919
|
+
* rows themselves. `unlisted` is total minus the ASKED locations minus NULL, so
|
|
15920
|
+
* it is a deleted location only when every location id was asked.
|
|
15921
|
+
* `null` is "could not be measured", never zero.
|
|
15922
|
+
*/
|
|
15923
|
+
var MediaLocationCensusInputSchema = object({ locationIds: array(string().min(1)).max(50).readonly() });
|
|
15924
|
+
var MediaLocationCensusSchema = object({
|
|
15925
|
+
locations: array(object({
|
|
15926
|
+
locationId: string(),
|
|
15927
|
+
rows: number().int().nonnegative(),
|
|
15928
|
+
bytes: number().nonnegative()
|
|
15929
|
+
})).readonly(),
|
|
15930
|
+
unstamped: object({
|
|
15931
|
+
rows: number().int().nonnegative(),
|
|
15932
|
+
bytes: number().nonnegative()
|
|
15933
|
+
}),
|
|
15934
|
+
/** Rows stamped with a location id that was not in the asked list. */
|
|
15935
|
+
unlisted: object({
|
|
15936
|
+
rows: number().int().nonnegative(),
|
|
15937
|
+
bytes: number().nonnegative()
|
|
15938
|
+
}),
|
|
15939
|
+
total: object({
|
|
15940
|
+
rows: number().int().nonnegative(),
|
|
15941
|
+
bytes: number().nonnegative()
|
|
15942
|
+
})
|
|
15943
|
+
});
|
|
15697
15944
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
|
|
15698
15945
|
/** The independently selectable logical storage classes — every class
|
|
15699
15946
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -46214,52 +46461,7 @@ DeviceType.Light, method(object({
|
|
|
46214
46461
|
level: number().min(0).max(100),
|
|
46215
46462
|
lastChangedAt: number()
|
|
46216
46463
|
});
|
|
46217
|
-
|
|
46218
|
-
* `broker` — unified pub/sub broker registry, system-scoped collection.
|
|
46219
|
-
*
|
|
46220
|
-
* The cap models any kind-tagged message broker the user wants to
|
|
46221
|
-
* register and that other addons might consume. The first two kinds
|
|
46222
|
-
* are MQTT (mosquitto / aedes embedded / cloud bridge) and
|
|
46223
|
-
* Home Assistant (HA WebSocket — subscribe_entities + call_service).
|
|
46224
|
-
* Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
|
|
46225
|
-
* the same surface.
|
|
46226
|
-
*
|
|
46227
|
-
* Why one cap, not one cap per kind:
|
|
46228
|
-
* - The integrations page wants a single table of "every broker"
|
|
46229
|
-
* across kinds; a unified cap drives it without join logic.
|
|
46230
|
-
* - Generic admin operations (add / remove / test / status) are
|
|
46231
|
-
* identical across kinds — duplicating them per cap was busywork.
|
|
46232
|
-
* - The `network-access` cap precedent: same interface, many
|
|
46233
|
-
* `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
|
|
46234
|
-
*
|
|
46235
|
-
* What stays kind-specific:
|
|
46236
|
-
* - `getBrokerConfig` payload (kind decides whether it returns
|
|
46237
|
-
* mqtt URL+creds, HA baseUrl+token, …) — typed as
|
|
46238
|
-
* `Record<string, unknown>` at the wire; consumers narrow.
|
|
46239
|
-
* - `publish` / `subscribe` / `getState` arguments (target / filter /
|
|
46240
|
-
* key) — also typed as `Record<string, unknown>` so each kind can
|
|
46241
|
-
* evolve its surface (MQTT topic+qos vs HA entity_id+domain)
|
|
46242
|
-
* without rebreaking the cap signature.
|
|
46243
|
-
* - `add` `settings` payload (different fields per kind, validated by
|
|
46244
|
-
* the kind-specific provider).
|
|
46245
|
-
*
|
|
46246
|
-
* Bidirectionality:
|
|
46247
|
-
* - `publish` is RPC — synchronous result (provider-defined).
|
|
46248
|
-
* - `subscribe` returns a subscription handle; the actual message
|
|
46249
|
-
* stream flows over the typed event-bus as `broker.message` events
|
|
46250
|
-
* keyed by `(brokerId, subscriptionId)`. Consumers filter in their
|
|
46251
|
-
* event handler. Subscriptions persist across reconnects — the
|
|
46252
|
-
* provider re-subscribes upstream after a transport drop.
|
|
46253
|
-
*
|
|
46254
|
-
* Why event-bus push (not a subscription RPC stream):
|
|
46255
|
-
* - Matches the rest of CamStack's D8 contract — events for telemetry,
|
|
46256
|
-
* RPC for loss-is-a-bug. Broker messages are telemetry (a single
|
|
46257
|
-
* drop is recoverable via `getState({key})`).
|
|
46258
|
-
* - Frees `broker.subscribe` from holding a long-lived RPC channel.
|
|
46259
|
-
* - Cross-process: events already route through `$event-bus`; no
|
|
46260
|
-
* per-broker wire-shaping needed.
|
|
46261
|
-
*/
|
|
46262
|
-
var BrokerStatusEnum = _enum([
|
|
46464
|
+
_enum([
|
|
46263
46465
|
"connected",
|
|
46264
46466
|
"disconnected",
|
|
46265
46467
|
"connecting",
|
|
@@ -46267,141 +46469,12 @@ var BrokerStatusEnum = _enum([
|
|
|
46267
46469
|
"unreachable",
|
|
46268
46470
|
"error"
|
|
46269
46471
|
]);
|
|
46270
|
-
var BrokerInfoSchema = object({
|
|
46271
|
-
/** Stable broker id. Persisted; survives addon restarts. */
|
|
46272
|
-
id: string(),
|
|
46273
|
-
/** Addon id of the provider that OWNS this broker.
|
|
46274
|
-
*
|
|
46275
|
-
* The `broker` cap is a system-scoped collection: several addons
|
|
46276
|
-
* register a `broker` provider, each owning a DISJOINT set of
|
|
46277
|
-
* brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
|
|
46278
|
-
* `ha_*`). A broker therefore belongs to exactly one addon — like a
|
|
46279
|
-
* device belongs to one integration. The admin UI threads this id
|
|
46280
|
-
* back as the `{ addonId }` system-collection selector on every
|
|
46281
|
-
* id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
|
|
46282
|
-
* `testConnection`), so the call routes to the OWNING provider
|
|
46283
|
-
* instead of defaulting to the first-registered one. */
|
|
46284
|
-
addonId: string(),
|
|
46285
|
-
/** Human-readable name (operator-chosen at add-time). */
|
|
46286
|
-
name: string(),
|
|
46287
|
-
/** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
|
|
46288
|
-
kind: string(),
|
|
46289
|
-
status: BrokerStatusEnum,
|
|
46290
|
-
/** Free-form provider-specific info (HA version, MQTT broker
|
|
46291
|
-
* flavour, latency, mTLS active, …). */
|
|
46292
|
-
info: record(string(), unknown()),
|
|
46293
|
-
/** Ms epoch of the last connection probe / status update. */
|
|
46294
|
-
lastCheckedAt: number().nullable(),
|
|
46295
|
-
/** Last error message, when `status` indicates failure. */
|
|
46296
|
-
error: string().nullable()
|
|
46297
|
-
});
|
|
46298
46472
|
var RegistryStatusSchema = object({
|
|
46299
46473
|
brokerCount: number().int().nonnegative(),
|
|
46300
46474
|
connectedCount: number().int().nonnegative()
|
|
46301
46475
|
});
|
|
46302
|
-
/**
|
|
46303
|
-
* One entry per `broker` provider — which addon provides which broker
|
|
46304
|
-
* kind(s). The unified create picker reads this to know, for each
|
|
46305
|
-
* (addonId, kind) pair the operator can pick, where to route the
|
|
46306
|
-
* follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
|
|
46307
|
-
* `{ addonId }` selector). A provider only advertises a kind here when
|
|
46308
|
-
* it implements `getSettingsSchema` for that kind (so the picker never
|
|
46309
|
-
* offers a kind whose creation form can't be rendered).
|
|
46310
|
-
*/
|
|
46311
|
-
var BrokerProviderInfoSchema = object({
|
|
46312
|
-
/** Addon id of the `broker` provider this entry describes. */
|
|
46313
|
-
addonId: string(),
|
|
46314
|
-
/** Broker kinds this provider can create, with a display label. */
|
|
46315
|
-
kinds: array(object({
|
|
46316
|
-
kind: string(),
|
|
46317
|
-
label: string(),
|
|
46318
|
-
/**
|
|
46319
|
-
* Relative hub path to start an interactive account link (OAuth popup).
|
|
46320
|
-
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
46321
|
-
*/
|
|
46322
|
-
interactiveConnectPath: string().min(1).optional(),
|
|
46323
|
-
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
46324
|
-
interactiveDisconnectPath: string().min(1).optional(),
|
|
46325
|
-
/**
|
|
46326
|
-
* The kind signs in through a server-held interactive session instead of
|
|
46327
|
-
* (or before) typed credentials. `basePath` is a hub-relative route set
|
|
46328
|
-
* served by the owning addon (`/addon/<id>/...`) speaking the sign-in
|
|
46329
|
-
* contract: `POST {basePath}/start` (body: the form's settings) answers
|
|
46330
|
-
* `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
|
|
46331
|
-
* `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
|
|
46332
|
-
* 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
|
|
46333
|
-
* (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
|
|
46334
|
-
* existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
|
|
46335
|
-
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
46336
|
-
* offers it as the default path; the fields the session fills are
|
|
46337
|
-
* marked `filledBySignIn` in the kind's settings schema.
|
|
46338
|
-
*
|
|
46339
|
-
* `methods` lists the ways the session can sign in, recommended first:
|
|
46340
|
-
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
46341
|
-
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
46342
|
-
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
46343
|
-
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
46344
|
-
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
46345
|
-
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
46346
|
-
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
46347
|
-
* `['proxy']`.
|
|
46348
|
-
*/
|
|
46349
|
-
signIn: object({
|
|
46350
|
-
basePath: string().min(1),
|
|
46351
|
-
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
46352
|
-
}).optional()
|
|
46353
|
-
}))
|
|
46354
|
-
});
|
|
46355
|
-
var ListInputSchema = object({
|
|
46356
|
-
/** Optional kind filter — `list({kind:'home-assistant'})` returns
|
|
46357
|
-
* only HA brokers. Omit to list every broker. */
|
|
46358
|
-
kind: string().optional() });
|
|
46359
46476
|
var GetInputSchema = object({ id: string() });
|
|
46360
|
-
var AddInputSchema = object({
|
|
46361
|
-
kind: string().min(1),
|
|
46362
|
-
name: string().min(1),
|
|
46363
|
-
/**
|
|
46364
|
-
* ADOPT an existing id instead of minting a new one.
|
|
46365
|
-
*
|
|
46366
|
-
* Written the day this cost an outage. A broker's id is not a detail: HA
|
|
46367
|
-
* devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
|
|
46368
|
-
* lost from config and re-added as `ha_001` leaves every one of its devices
|
|
46369
|
-
* bound to a broker that no longer exists. Re-entering the password under the
|
|
46370
|
-
* ORIGINAL id turns a multi-step device migration back into re-entering a
|
|
46371
|
-
* password.
|
|
46372
|
-
*
|
|
46373
|
-
* A provider MUST refuse an id that is already in use — adopting a live
|
|
46374
|
-
* broker's id would silently take it over.
|
|
46375
|
-
*/
|
|
46376
|
-
id: string().min(1).optional(),
|
|
46377
|
-
/** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
|
|
46378
|
-
* `{baseUrl,accessToken}`). Validated by the kind-specific provider
|
|
46379
|
-
* branch on receipt — invalid shape rejects the add. */
|
|
46380
|
-
settings: record(string(), unknown())
|
|
46381
|
-
});
|
|
46382
|
-
var AddResultSchema = object({ id: string() });
|
|
46383
|
-
var RemoveInputSchema = object({ id: string() });
|
|
46384
|
-
var TestConnectionResultSchema = discriminatedUnion("ok", [object({
|
|
46385
|
-
ok: literal(true),
|
|
46386
|
-
latencyMs: number().nonnegative()
|
|
46387
|
-
}), object({
|
|
46388
|
-
ok: literal(false),
|
|
46389
|
-
error: string()
|
|
46390
|
-
})]);
|
|
46391
46477
|
var SettingsRecordSchema$1 = record(string(), unknown());
|
|
46392
|
-
var SettingsSchemaInputSchema = object({ kind: string() });
|
|
46393
|
-
var TestSettingsInputSchema = object({
|
|
46394
|
-
kind: string(),
|
|
46395
|
-
settings: SettingsRecordSchema$1
|
|
46396
|
-
});
|
|
46397
|
-
var TestSettingsResultSchema = discriminatedUnion("ok", [object({
|
|
46398
|
-
ok: literal(true),
|
|
46399
|
-
latencyMs: number().nonnegative().optional()
|
|
46400
|
-
}).strict(), object({
|
|
46401
|
-
ok: literal(false),
|
|
46402
|
-
error: string()
|
|
46403
|
-
})]);
|
|
46404
|
-
var SettingsSchemaResultSchema = unknown().nullable();
|
|
46405
46478
|
var PublishInputSchema = object({
|
|
46406
46479
|
brokerId: string(),
|
|
46407
46480
|
/** Kind-specific routing target.
|
|
@@ -46445,51 +46518,12 @@ var brokerCapability = {
|
|
|
46445
46518
|
kind: "poll"
|
|
46446
46519
|
},
|
|
46447
46520
|
methods: {
|
|
46448
|
-
list: method(ListInputSchema, array(BrokerInfoSchema)),
|
|
46449
|
-
get: method(GetInputSchema, BrokerInfoSchema.nullable()),
|
|
46450
|
-
/** Enumerate which addon provides which broker kind(s) for the
|
|
46451
|
-
* unified create picker. The auto-mount fans this array across
|
|
46452
|
-
* every registered `broker` provider (array-output method), so the
|
|
46453
|
-
* picker sees every kind from every provider in one call. */
|
|
46454
|
-
listProviders: method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }),
|
|
46455
|
-
add: method(AddInputSchema, AddResultSchema, {
|
|
46456
|
-
kind: "mutation",
|
|
46457
|
-
auth: "admin"
|
|
46458
|
-
}),
|
|
46459
|
-
remove: method(RemoveInputSchema, _void(), {
|
|
46460
|
-
kind: "mutation",
|
|
46461
|
-
auth: "admin"
|
|
46462
|
-
}),
|
|
46463
|
-
testConnection: method(GetInputSchema, TestConnectionResultSchema, {
|
|
46464
|
-
kind: "mutation",
|
|
46465
|
-
auth: "admin"
|
|
46466
|
-
}),
|
|
46467
|
-
/** Read the persisted settings record for a broker (kind-specific
|
|
46468
|
-
* shape). Admin-only — settings may contain secrets. Returns `null`
|
|
46469
|
-
* when the broker id is unknown to the provider (the collection
|
|
46470
|
-
* fallback may route a foreign id to the first provider). */
|
|
46471
|
-
getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
46472
|
-
/** Overwrite the persisted settings record. The kind-specific
|
|
46473
|
-
* provider validates the shape and applies the change (reconnects
|
|
46474
|
-
* if credentials changed). */
|
|
46475
|
-
setSettings: method(object({
|
|
46476
|
-
id: string(),
|
|
46477
|
-
settings: SettingsRecordSchema$1
|
|
46478
|
-
}), _void(), {
|
|
46479
|
-
kind: "mutation",
|
|
46480
|
-
auth: "admin"
|
|
46481
|
-
}),
|
|
46482
46521
|
/** Returns the kind-specific connection config the consumer needs
|
|
46483
46522
|
* to open its own client (MQTT pattern: `{url, username, password,
|
|
46484
46523
|
* clientIdPrefix}`). HA providers MAY return the auth envelope
|
|
46485
46524
|
* but typical HA consumers use `publish` / `subscribe` instead.
|
|
46486
46525
|
* Returns `null` when the broker id is unknown to the provider. */
|
|
46487
46526
|
getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
46488
|
-
getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
|
|
46489
|
-
testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
46490
|
-
kind: "mutation",
|
|
46491
|
-
auth: "admin"
|
|
46492
|
-
}),
|
|
46493
46527
|
publish: method(PublishInputSchema, unknown(), {
|
|
46494
46528
|
kind: "mutation",
|
|
46495
46529
|
auth: "admin"
|
|
@@ -49113,6 +49147,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
49113
49147
|
runsFound: number().int().nonnegative(),
|
|
49114
49148
|
pausesFound: number().int().nonnegative(),
|
|
49115
49149
|
longestPauseMs: number().nonnegative(),
|
|
49150
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
49151
|
+
pulsesFound: number().int().nonnegative(),
|
|
49152
|
+
longestPulseMs: number().nonnegative(),
|
|
49116
49153
|
idleFloorW: number(),
|
|
49117
49154
|
noiseCeilingW: number(),
|
|
49118
49155
|
plateauW: number(),
|
|
@@ -50559,7 +50596,14 @@ var DiscoveryCandidateSchema = object({
|
|
|
50559
50596
|
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
50560
50597
|
parentStableId: string().optional(),
|
|
50561
50598
|
/** `false` = shown for context but not pickable. Default true. */
|
|
50562
|
-
selectable: boolean().optional()
|
|
50599
|
+
selectable: boolean().optional(),
|
|
50600
|
+
/**
|
|
50601
|
+
* Keys of the provider's creation form (`getChildCreationSchema`) this candidate
|
|
50602
|
+
* cannot be adopted without — a key for an encrypted board, a pairing code. The
|
|
50603
|
+
* admin asks for exactly these, on the creation form prefilled with
|
|
50604
|
+
* `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
|
|
50605
|
+
*/
|
|
50606
|
+
needsParams: array(string()).optional()
|
|
50563
50607
|
});
|
|
50564
50608
|
/**
|
|
50565
50609
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -50618,12 +50662,24 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
50618
50662
|
}), array(DiscoveryCandidateSchema), {
|
|
50619
50663
|
kind: "mutation",
|
|
50620
50664
|
auth: "admin"
|
|
50621
|
-
}), method(object({}), CreationSchemaOutputSchema), method(object({
|
|
50665
|
+
}), method(object({}), CreationSchemaOutputSchema), method(object({
|
|
50666
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
50667
|
+
integrationId: string().optional() }), object({ deviceType: _enum(DeviceType).nullable() })), method(object({
|
|
50668
|
+
candidate: DiscoveryCandidateSchema,
|
|
50669
|
+
/** The integration the operator adopts into; a provider with several instances needs it. */
|
|
50670
|
+
integrationId: string().optional()
|
|
50671
|
+
}), DeviceSummarySchema, {
|
|
50622
50672
|
kind: "mutation",
|
|
50623
50673
|
auth: "admin"
|
|
50624
|
-
}), method(object({}), boolean()), method(object({
|
|
50674
|
+
}), method(object({}), boolean()), method(object({
|
|
50625
50675
|
type: _enum(DeviceType),
|
|
50626
|
-
|
|
50676
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
50677
|
+
integrationId: string().optional()
|
|
50678
|
+
}), CreationSchemaOutputSchema), method(object({
|
|
50679
|
+
type: _enum(DeviceType),
|
|
50680
|
+
config: record(string(), unknown()),
|
|
50681
|
+
/** The integration the operator adds the device to; a provider with several instances needs it. */
|
|
50682
|
+
integrationId: string().optional()
|
|
50627
50683
|
}), DeviceSummarySchema, {
|
|
50628
50684
|
kind: "mutation",
|
|
50629
50685
|
auth: "admin"
|
|
@@ -51333,7 +51389,10 @@ method(object({
|
|
|
51333
51389
|
auth: "admin"
|
|
51334
51390
|
}), method(object({
|
|
51335
51391
|
addonId: string(),
|
|
51336
|
-
type: _enum(DeviceType)
|
|
51392
|
+
type: _enum(DeviceType),
|
|
51393
|
+
/** The integration the form is for; a provider whose integrations differ (a hub
|
|
51394
|
+
* integration takes no manual add) answers per integration. Absent = the addon's own. */
|
|
51395
|
+
integrationId: string().optional()
|
|
51337
51396
|
}), unknown().nullable()), method(object({
|
|
51338
51397
|
addonId: string(),
|
|
51339
51398
|
type: _enum(DeviceType),
|
|
@@ -51417,7 +51476,11 @@ method(object({
|
|
|
51417
51476
|
kind: "mutation",
|
|
51418
51477
|
auth: "admin",
|
|
51419
51478
|
timeoutMs: 12e4
|
|
51420
|
-
}), method(object({
|
|
51479
|
+
}), method(object({
|
|
51480
|
+
addonId: string(),
|
|
51481
|
+
/** The integration the Add-Device dialog is open on; absent = the addon's own. */
|
|
51482
|
+
integrationId: string().optional()
|
|
51483
|
+
}), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
51421
51484
|
deviceId: number(),
|
|
51422
51485
|
key: string(),
|
|
51423
51486
|
value: unknown()
|
|
@@ -54013,7 +54076,7 @@ var TestConnectionStatusEnum = _enum([
|
|
|
54013
54076
|
"inconclusive",
|
|
54014
54077
|
"unsupported"
|
|
54015
54078
|
]);
|
|
54016
|
-
var TestConnectionResultSchema
|
|
54079
|
+
var TestConnectionResultSchema = object({
|
|
54017
54080
|
/** True ONLY for `validated`. Never true for a test that did not run. */
|
|
54018
54081
|
success: boolean(),
|
|
54019
54082
|
error: string().optional(),
|
|
@@ -54034,11 +54097,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
54034
54097
|
info: record(string(), unknown()).optional(),
|
|
54035
54098
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
54036
54099
|
originStableId: string().optional(),
|
|
54037
|
-
settings: record(string(), unknown()).optional()
|
|
54038
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
54039
|
-
* the keychain, not the table (D787). */
|
|
54040
|
-
secretKeys: array(string()).optional(),
|
|
54041
|
-
skipRestart: boolean().optional()
|
|
54100
|
+
settings: record(string(), unknown()).optional()
|
|
54042
54101
|
});
|
|
54043
54102
|
var UpdateIntegrationInputSchema = object({
|
|
54044
54103
|
id: string(),
|
|
@@ -54059,20 +54118,10 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
54059
54118
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
54060
54119
|
kind: "mutation",
|
|
54061
54120
|
auth: "admin"
|
|
54062
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
54063
|
-
id: string(),
|
|
54064
|
-
settings: record(string(), unknown()),
|
|
54065
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
54066
|
-
secretKeys: array(string()).optional(),
|
|
54067
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
54068
|
-
skipRestart: boolean().optional()
|
|
54069
|
-
}), object({ success: boolean() }), {
|
|
54070
|
-
kind: "mutation",
|
|
54071
|
-
auth: "admin"
|
|
54072
54121
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
54073
54122
|
addonId: string(),
|
|
54074
54123
|
settings: record(string(), unknown())
|
|
54075
|
-
}), TestConnectionResultSchema
|
|
54124
|
+
}), TestConnectionResultSchema, {
|
|
54076
54125
|
kind: "mutation",
|
|
54077
54126
|
auth: "admin"
|
|
54078
54127
|
});
|
|
@@ -54099,6 +54148,14 @@ var ConfigSchemaPassthrough$3 = custom();
|
|
|
54099
54148
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
54100
54149
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
54101
54150
|
/**
|
|
54151
|
+
* What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
|
|
54152
|
+
* instance is an integration that brings devices (the registry owns its on/off switch);
|
|
54153
|
+
* `services`: a connection configured from the addon's own function page, never listed on
|
|
54154
|
+
* Integrations.
|
|
54155
|
+
* It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
|
|
54156
|
+
*/
|
|
54157
|
+
var IntegrationInstanceRoleSchema = _enum(["devices", "services"]);
|
|
54158
|
+
/**
|
|
54102
54159
|
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
54103
54160
|
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
54104
54161
|
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
@@ -54109,6 +54166,22 @@ var IntegrationInstanceKindSchema = object({
|
|
|
54109
54166
|
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
54110
54167
|
id: string().min(1),
|
|
54111
54168
|
label: string().min(1),
|
|
54169
|
+
/**
|
|
54170
|
+
* The function capability this kind is configured for (`broker`, `smtp-provider`,
|
|
54171
|
+
* `device-provider`, ...). A page section is the kinds whose `functionCap` it names (D1013); a
|
|
54172
|
+
* provider with more than one section must name it on every kind.
|
|
54173
|
+
*/
|
|
54174
|
+
functionCap: string().min(1),
|
|
54175
|
+
/**
|
|
54176
|
+
* For `functionCap: 'broker'`: the registry kind of the broker an instance of this kind is
|
|
54177
|
+
* (`matter`, `home-assistant`, ...). A caller that needs a broker of one kind (a link field)
|
|
54178
|
+
* finds the instances whose kind names it; absent takes the kind `id`.
|
|
54179
|
+
*/
|
|
54180
|
+
brokerKind: string().min(1).optional(),
|
|
54181
|
+
/** What instances of this kind are for the Integrations page; absent takes the provider's `role`. */
|
|
54182
|
+
role: IntegrationInstanceRoleSchema.optional(),
|
|
54183
|
+
/** `testInstance` really probes the remote for this kind; absent takes the provider's `canTest`. */
|
|
54184
|
+
canTest: boolean().optional(),
|
|
54112
54185
|
/** The kind signs in through a server-held interactive session. */
|
|
54113
54186
|
signIn: object({
|
|
54114
54187
|
basePath: string().min(1),
|
|
@@ -54121,11 +54194,7 @@ var IntegrationInstanceKindSchema = object({
|
|
|
54121
54194
|
});
|
|
54122
54195
|
var IntegrationInstanceDescriptionSchema = object({
|
|
54123
54196
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
54124
|
-
role:
|
|
54125
|
-
"devices",
|
|
54126
|
-
"services",
|
|
54127
|
-
"both"
|
|
54128
|
-
]),
|
|
54197
|
+
role: IntegrationInstanceRoleSchema,
|
|
54129
54198
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
54130
54199
|
discovery: object({
|
|
54131
54200
|
mode: _enum([
|
|
@@ -54200,7 +54269,11 @@ var integrationInstancesCapability = {
|
|
|
54200
54269
|
methods: {
|
|
54201
54270
|
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
54202
54271
|
/** The form; `id: null` is the create form. */
|
|
54203
|
-
instanceFormSchema: method(object({
|
|
54272
|
+
instanceFormSchema: method(object({
|
|
54273
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
54274
|
+
/** The kind of the create form (`id: null`) of a provider with several. */
|
|
54275
|
+
kind: string().min(1).optional()
|
|
54276
|
+
}), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
54204
54277
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
54205
54278
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
54206
54279
|
/**
|
|
@@ -54210,7 +54283,9 @@ var integrationInstancesCapability = {
|
|
|
54210
54283
|
createInstance: method(object({
|
|
54211
54284
|
id: IntegrationInstanceIdSchema.optional(),
|
|
54212
54285
|
name: string().min(1),
|
|
54213
|
-
kind: string().min(1).optional()
|
|
54286
|
+
kind: string().min(1).optional(),
|
|
54287
|
+
/** Form values written with the record (a link picked in the create dialog). */
|
|
54288
|
+
values: record(string(), unknown()).optional()
|
|
54214
54289
|
}), object({ id: string() }), {
|
|
54215
54290
|
kind: "mutation",
|
|
54216
54291
|
auth: "admin"
|
|
@@ -54237,8 +54312,10 @@ var integrationInstancesCapability = {
|
|
|
54237
54312
|
}),
|
|
54238
54313
|
testInstance: optionalMethod(object({
|
|
54239
54314
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
54240
|
-
values: record(string(), unknown())
|
|
54241
|
-
|
|
54315
|
+
values: record(string(), unknown()),
|
|
54316
|
+
/** The kind to test when `id` is null and the provider has several. */
|
|
54317
|
+
kind: string().min(1).optional()
|
|
54318
|
+
}), TestConnectionResultSchema, {
|
|
54242
54319
|
kind: "mutation",
|
|
54243
54320
|
auth: "admin"
|
|
54244
54321
|
})
|
|
@@ -56743,6 +56820,380 @@ var SystemMetricsSchema = object({
|
|
|
56743
56820
|
gpuMemoryPercent: number().optional()
|
|
56744
56821
|
});
|
|
56745
56822
|
method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), 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() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
|
|
56823
|
+
/**
|
|
56824
|
+
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
56825
|
+
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
56826
|
+
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
56827
|
+
*
|
|
56828
|
+
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
56829
|
+
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
56830
|
+
* that with the addon id closed over). It is deliberately NOT in any input
|
|
56831
|
+
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
56832
|
+
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
56833
|
+
* addons go through the owner's capability.
|
|
56834
|
+
*
|
|
56835
|
+
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
56836
|
+
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
56837
|
+
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
56838
|
+
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
56839
|
+
* row's own ref.
|
|
56840
|
+
*
|
|
56841
|
+
* Internal: addons reach it through `ctx.records`, never directly.
|
|
56842
|
+
*/
|
|
56843
|
+
var RecordIdSchema = string().min(1).max(256);
|
|
56844
|
+
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
56845
|
+
var RecordBodySchema = record(string(), unknown());
|
|
56846
|
+
var RecordInputSchema = object({
|
|
56847
|
+
id: RecordIdSchema,
|
|
56848
|
+
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
56849
|
+
deviceId: number().int().optional(),
|
|
56850
|
+
/** The domain clock, ms: observed / created / opened. */
|
|
56851
|
+
at: number().int().optional(),
|
|
56852
|
+
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
56853
|
+
expiresAt: number().int().optional(),
|
|
56854
|
+
data: RecordBodySchema
|
|
56855
|
+
});
|
|
56856
|
+
var RecordViewSchema = object({
|
|
56857
|
+
id: string(),
|
|
56858
|
+
deviceId: number().int().nullable(),
|
|
56859
|
+
at: number().nullable(),
|
|
56860
|
+
expiresAt: number().nullable(),
|
|
56861
|
+
rev: number().int(),
|
|
56862
|
+
updatedAt: number(),
|
|
56863
|
+
data: RecordBodySchema
|
|
56864
|
+
});
|
|
56865
|
+
/** Declared index fields only. Anything else does not compile, by design. */
|
|
56866
|
+
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
56867
|
+
string(),
|
|
56868
|
+
number(),
|
|
56869
|
+
boolean(),
|
|
56870
|
+
object({ in: array(union([
|
|
56871
|
+
string(),
|
|
56872
|
+
number(),
|
|
56873
|
+
boolean()
|
|
56874
|
+
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
56875
|
+
object({
|
|
56876
|
+
gt: union([string(), number()]).optional(),
|
|
56877
|
+
gte: union([string(), number()]).optional(),
|
|
56878
|
+
lt: union([string(), number()]).optional(),
|
|
56879
|
+
lte: union([string(), number()]).optional()
|
|
56880
|
+
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
56881
|
+
]));
|
|
56882
|
+
var KindShape = { kind: RecordKindNameSchema };
|
|
56883
|
+
var TimeWindowShape = {
|
|
56884
|
+
deviceId: number().int().optional(),
|
|
56885
|
+
atFrom: number().optional(),
|
|
56886
|
+
atTo: number().optional()
|
|
56887
|
+
};
|
|
56888
|
+
var RecordQueryInputSchema = object({
|
|
56889
|
+
...KindShape,
|
|
56890
|
+
...TimeWindowShape,
|
|
56891
|
+
where: RecordWhereSchema.optional(),
|
|
56892
|
+
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
56893
|
+
order: _enum(["at", "id"]).optional(),
|
|
56894
|
+
desc: boolean().optional(),
|
|
56895
|
+
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
56896
|
+
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
56897
|
+
cursor: string().min(1).optional()
|
|
56898
|
+
});
|
|
56899
|
+
var RecordQueryResultSchema = object({
|
|
56900
|
+
records: array(RecordViewSchema),
|
|
56901
|
+
nextCursor: string().nullable()
|
|
56902
|
+
});
|
|
56903
|
+
var RecordKindStateSchema = _enum([
|
|
56904
|
+
"created",
|
|
56905
|
+
"unchanged",
|
|
56906
|
+
"updated"
|
|
56907
|
+
]);
|
|
56908
|
+
var DeclareKindResultSchema = object({
|
|
56909
|
+
kindId: number().int(),
|
|
56910
|
+
state: RecordKindStateSchema,
|
|
56911
|
+
createdIndexes: array(string())
|
|
56912
|
+
});
|
|
56913
|
+
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
56914
|
+
var SecretRefsList = array(string());
|
|
56915
|
+
/**
|
|
56916
|
+
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
56917
|
+
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
56918
|
+
*/
|
|
56919
|
+
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
56920
|
+
object({
|
|
56921
|
+
op: literal("put"),
|
|
56922
|
+
kind: RecordKindNameSchema,
|
|
56923
|
+
record: RecordInputSchema,
|
|
56924
|
+
ifRev: number().int().min(0).optional()
|
|
56925
|
+
}),
|
|
56926
|
+
object({
|
|
56927
|
+
op: literal("patch"),
|
|
56928
|
+
kind: RecordKindNameSchema,
|
|
56929
|
+
id: RecordIdSchema,
|
|
56930
|
+
data: RecordBodySchema,
|
|
56931
|
+
at: number().int().nullable().optional(),
|
|
56932
|
+
expiresAt: number().int().nullable().optional(),
|
|
56933
|
+
ifRev: number().int().min(1).optional()
|
|
56934
|
+
}),
|
|
56935
|
+
object({
|
|
56936
|
+
op: literal("delete"),
|
|
56937
|
+
kind: RecordKindNameSchema,
|
|
56938
|
+
id: RecordIdSchema
|
|
56939
|
+
})
|
|
56940
|
+
]);
|
|
56941
|
+
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
56942
|
+
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
56943
|
+
kind: "mutation",
|
|
56944
|
+
auth: "admin"
|
|
56945
|
+
}), method(object({
|
|
56946
|
+
...KindShape,
|
|
56947
|
+
id: RecordIdSchema
|
|
56948
|
+
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
56949
|
+
...KindShape,
|
|
56950
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
56951
|
+
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
56952
|
+
...KindShape,
|
|
56953
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
56954
|
+
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
56955
|
+
...KindShape,
|
|
56956
|
+
record: RecordInputSchema,
|
|
56957
|
+
/** Compare-and-set. `0` means "the row must not exist". */
|
|
56958
|
+
ifRev: number().int().min(0).optional()
|
|
56959
|
+
}), object({
|
|
56960
|
+
rev: number().int(),
|
|
56961
|
+
releasedRefs: SecretRefsList
|
|
56962
|
+
}), {
|
|
56963
|
+
kind: "mutation",
|
|
56964
|
+
auth: "admin"
|
|
56965
|
+
}), method(object({
|
|
56966
|
+
...KindShape,
|
|
56967
|
+
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
56968
|
+
}), object({
|
|
56969
|
+
count: number().int(),
|
|
56970
|
+
releasedRefs: SecretRefsList
|
|
56971
|
+
}), {
|
|
56972
|
+
kind: "mutation",
|
|
56973
|
+
auth: "admin"
|
|
56974
|
+
}), method(object({
|
|
56975
|
+
...KindShape,
|
|
56976
|
+
id: RecordIdSchema,
|
|
56977
|
+
data: RecordBodySchema,
|
|
56978
|
+
at: number().int().nullable().optional(),
|
|
56979
|
+
expiresAt: number().int().nullable().optional(),
|
|
56980
|
+
ifRev: number().int().min(1).optional()
|
|
56981
|
+
}), object({
|
|
56982
|
+
rev: number().int(),
|
|
56983
|
+
releasedRefs: SecretRefsList
|
|
56984
|
+
}), {
|
|
56985
|
+
kind: "mutation",
|
|
56986
|
+
auth: "admin"
|
|
56987
|
+
}), method(object({
|
|
56988
|
+
...KindShape,
|
|
56989
|
+
id: RecordIdSchema
|
|
56990
|
+
}), object({
|
|
56991
|
+
deleted: boolean(),
|
|
56992
|
+
secretRefs: SecretRefsList
|
|
56993
|
+
}), {
|
|
56994
|
+
kind: "mutation",
|
|
56995
|
+
auth: "admin"
|
|
56996
|
+
}), method(object({
|
|
56997
|
+
...KindShape,
|
|
56998
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
56999
|
+
}), object({
|
|
57000
|
+
deleted: number().int(),
|
|
57001
|
+
secretRefs: SecretRefsList
|
|
57002
|
+
}), {
|
|
57003
|
+
kind: "mutation",
|
|
57004
|
+
auth: "admin"
|
|
57005
|
+
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
57006
|
+
...KindShape,
|
|
57007
|
+
...TimeWindowShape,
|
|
57008
|
+
where: RecordWhereSchema.optional()
|
|
57009
|
+
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
57010
|
+
...KindShape,
|
|
57011
|
+
deviceId: number().int().optional(),
|
|
57012
|
+
from: number(),
|
|
57013
|
+
to: number(),
|
|
57014
|
+
bucketMs: number().int().positive()
|
|
57015
|
+
}), object({ buckets: array(object({
|
|
57016
|
+
bucketStart: number(),
|
|
57017
|
+
count: number().int()
|
|
57018
|
+
})) }), { auth: "admin" }), method(object({
|
|
57019
|
+
...KindShape,
|
|
57020
|
+
op: _enum([
|
|
57021
|
+
"sum",
|
|
57022
|
+
"min",
|
|
57023
|
+
"max",
|
|
57024
|
+
"count"
|
|
57025
|
+
]),
|
|
57026
|
+
field: RecordFieldPathSchema.optional(),
|
|
57027
|
+
groupBy: RecordFieldPathSchema.optional(),
|
|
57028
|
+
where: RecordWhereSchema.optional()
|
|
57029
|
+
}), object({ rows: array(object({
|
|
57030
|
+
group: union([
|
|
57031
|
+
string(),
|
|
57032
|
+
number(),
|
|
57033
|
+
_null()
|
|
57034
|
+
]),
|
|
57035
|
+
value: number().nullable()
|
|
57036
|
+
})) }), { auth: "admin" }), method(object({
|
|
57037
|
+
...KindShape,
|
|
57038
|
+
deviceId: number().int().optional(),
|
|
57039
|
+
atBefore: number().optional(),
|
|
57040
|
+
where: RecordWhereSchema.optional()
|
|
57041
|
+
}), object({
|
|
57042
|
+
deleted: number().int(),
|
|
57043
|
+
secretRefs: SecretRefsList
|
|
57044
|
+
}), {
|
|
57045
|
+
kind: "mutation",
|
|
57046
|
+
auth: "admin"
|
|
57047
|
+
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
57048
|
+
results: array(object({
|
|
57049
|
+
rev: number().int().nullable(),
|
|
57050
|
+
deleted: boolean()
|
|
57051
|
+
})),
|
|
57052
|
+
releasedRefs: SecretRefsList,
|
|
57053
|
+
secretRefs: SecretRefsList
|
|
57054
|
+
}), {
|
|
57055
|
+
kind: "mutation",
|
|
57056
|
+
auth: "admin"
|
|
57057
|
+
}), method(object({
|
|
57058
|
+
deviceId: number().int(),
|
|
57059
|
+
kinds: array(RecordKindNameSchema).optional()
|
|
57060
|
+
}), object({
|
|
57061
|
+
deleted: record(string(), number().int()),
|
|
57062
|
+
secretRefs: SecretRefsList
|
|
57063
|
+
}), {
|
|
57064
|
+
kind: "mutation",
|
|
57065
|
+
auth: "admin"
|
|
57066
|
+
}), method(object(KindShape), object({ id: number().int() }), {
|
|
57067
|
+
kind: "mutation",
|
|
57068
|
+
auth: "admin"
|
|
57069
|
+
});
|
|
57070
|
+
/**
|
|
57071
|
+
* The wire shapes of the migration API, version 1 (planning#447).
|
|
57072
|
+
*
|
|
57073
|
+
* A migration is code that outlives the server it was written against: an addon at
|
|
57074
|
+
* version 1.4 that is brought to 1.9 in one jump still runs its 1.4 catalogue, against
|
|
57075
|
+
* the 1.9 kernel. The only thing that code may call is {@link MigrationApiV1}; this file
|
|
57076
|
+
* is the contract of every method of it, one Zod pair per method.
|
|
57077
|
+
*
|
|
57078
|
+
* APPEND-ONLY. A released method is never removed and never changes meaning: an input
|
|
57079
|
+
* field is never removed or made required, an enum never narrows, an output field is
|
|
57080
|
+
* never removed. A change that cannot be expressed that way is `migration-api.v2`, next
|
|
57081
|
+
* to this file. `scripts/check-migration-api-append-only.ts` holds the line against
|
|
57082
|
+
* `scripts/migration-api-contract.json`, and every `behaviours` id below must have an
|
|
57083
|
+
* `it('<id>'` in `migration-api.contract.spec.ts`.
|
|
57084
|
+
*/
|
|
57085
|
+
var Rows = record(string(), unknown());
|
|
57086
|
+
var IntegrationId = object({ integrationId: string().min(1) });
|
|
57087
|
+
var RecordId = object({ id: string().min(1) });
|
|
57088
|
+
var RecordRowSchema = object({
|
|
57089
|
+
id: string(),
|
|
57090
|
+
deviceId: number().int().nullable(),
|
|
57091
|
+
at: number().nullable(),
|
|
57092
|
+
expiresAt: number().nullable(),
|
|
57093
|
+
rev: number().int(),
|
|
57094
|
+
updatedAt: number(),
|
|
57095
|
+
data: Rows
|
|
57096
|
+
});
|
|
57097
|
+
var RecordPutShape = {
|
|
57098
|
+
id: string().min(1),
|
|
57099
|
+
deviceId: number().int().optional(),
|
|
57100
|
+
at: number().int().optional(),
|
|
57101
|
+
expiresAt: number().int().optional(),
|
|
57102
|
+
data: Rows
|
|
57103
|
+
};
|
|
57104
|
+
var MigrationIntegrationSchema = object({
|
|
57105
|
+
id: string(),
|
|
57106
|
+
addonId: string(),
|
|
57107
|
+
name: string(),
|
|
57108
|
+
info: Rows,
|
|
57109
|
+
originStableId: string().optional()
|
|
57110
|
+
});
|
|
57111
|
+
var MigrationDeviceSchema = object({
|
|
57112
|
+
id: number().int(),
|
|
57113
|
+
integrationId: string().nullable().optional(),
|
|
57114
|
+
/** The owning device; null or absent for a root. */
|
|
57115
|
+
parentDeviceId: number().int().nullable().optional(),
|
|
57116
|
+
type: string().optional(),
|
|
57117
|
+
name: string().optional(),
|
|
57118
|
+
stableId: string().optional()
|
|
57119
|
+
});
|
|
57120
|
+
/** What the engine says about the migration API it serves: lets an addon notice an older server. */
|
|
57121
|
+
var MigrationApiInfoSchema = object({
|
|
57122
|
+
version: number().int(),
|
|
57123
|
+
methods: array(string())
|
|
57124
|
+
});
|
|
57125
|
+
object({
|
|
57126
|
+
message: string(),
|
|
57127
|
+
meta: Rows.optional(),
|
|
57128
|
+
/** Grouping tags of the line; a line about a device carries `{ deviceId }`. */
|
|
57129
|
+
tags: object({ deviceId: number().int().optional() }).optional()
|
|
57130
|
+
});
|
|
57131
|
+
object({
|
|
57132
|
+
owner: string().min(1),
|
|
57133
|
+
metas: array(MigrationMetaSchema)
|
|
57134
|
+
}), object({
|
|
57135
|
+
bootId: string(),
|
|
57136
|
+
snapshot: _enum([
|
|
57137
|
+
"none",
|
|
57138
|
+
"written",
|
|
57139
|
+
"refused"
|
|
57140
|
+
]),
|
|
57141
|
+
steps: array(MigrationOwnerStepSchema),
|
|
57142
|
+
/** The migration API the engine serves; absent from a server that predates it. */
|
|
57143
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
57144
|
+
}), object({ id: string().min(1) }), object({
|
|
57145
|
+
proceed: boolean(),
|
|
57146
|
+
state: union([literal("pending"), MigrationStatusSchema]),
|
|
57147
|
+
reason: string().nullable()
|
|
57148
|
+
}), object({
|
|
57149
|
+
id: string().min(1),
|
|
57150
|
+
result: MigrationOwnerResultSchema
|
|
57151
|
+
}), object({
|
|
57152
|
+
status: MigrationStatusSchema,
|
|
57153
|
+
error: string().nullable(),
|
|
57154
|
+
rowsChanged: number().int().nonnegative()
|
|
57155
|
+
}), object({
|
|
57156
|
+
rows: number().int().nonnegative(),
|
|
57157
|
+
secrets: number().int().nonnegative()
|
|
57158
|
+
}), IntegrationId.extend({ patch: Rows }), object({ written: boolean() }), IntegrationId.extend({ keys: array(string().min(1)) }), object({
|
|
57159
|
+
present: boolean(),
|
|
57160
|
+
removed: Rows
|
|
57161
|
+
}), RecordRowSchema.nullable(), RecordRowSchema.nullable(), object({
|
|
57162
|
+
...RecordPutShape,
|
|
57163
|
+
ifRev: number().int().optional()
|
|
57164
|
+
}), object({ rev: number().int() }), object({ records: array(object(RecordPutShape)) }), object({ count: number().int().nonnegative() }), RecordId.extend({
|
|
57165
|
+
partial: Rows,
|
|
57166
|
+
ifRev: number().int().optional()
|
|
57167
|
+
}), object({ rev: number().int() }), object({ deleted: boolean() }), object({
|
|
57168
|
+
deviceId: number().int().optional(),
|
|
57169
|
+
atFrom: number().optional(),
|
|
57170
|
+
atTo: number().optional(),
|
|
57171
|
+
where: RecordWhereSchema.optional(),
|
|
57172
|
+
limit: number().int().min(1).optional(),
|
|
57173
|
+
cursor: string().min(1).optional(),
|
|
57174
|
+
resolveSecrets: boolean().optional()
|
|
57175
|
+
}), object({
|
|
57176
|
+
records: array(RecordRowSchema),
|
|
57177
|
+
nextCursor: string().nullable()
|
|
57178
|
+
}), object({
|
|
57179
|
+
deviceId: number().int().optional(),
|
|
57180
|
+
atBefore: number().optional(),
|
|
57181
|
+
where: RecordWhereSchema.optional()
|
|
57182
|
+
}), object({ count: number().int().nonnegative() }), object({}), object({ store: Rows }), object({ patch: Rows }), object({}), object({ keys: array(string().min(1)) }), object({ removed: array(string()) }), object({ refs: array(string()) }), object({ values: record(string(), string().nullable()) }), object({
|
|
57183
|
+
ref: string().min(1),
|
|
57184
|
+
value: string()
|
|
57185
|
+
}), object({}), object({ refs: array(string()) }), object({ deleted: number().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string().min(1) }), MigrationIntegrationSchema.nullable(), object({
|
|
57186
|
+
addonId: string().min(1),
|
|
57187
|
+
integrationId: string().min(1).optional()
|
|
57188
|
+
}), object({ devices: array(MigrationDeviceSchema) }), object({ deviceId: number().int() }), object({
|
|
57189
|
+
id: number().int(),
|
|
57190
|
+
integrationId: string().nullable(),
|
|
57191
|
+
/** Whether the device carries any stored config; its content is not offered. */
|
|
57192
|
+
hasConfig: boolean()
|
|
57193
|
+
}).nullable(), object({
|
|
57194
|
+
deviceId: number().int(),
|
|
57195
|
+
integrationId: string().min(1)
|
|
57196
|
+
}), object({}), object({}), object({ nodeId: string() }), object({}), object({});
|
|
56746
57197
|
method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
|
|
56747
57198
|
id: string().min(1),
|
|
56748
57199
|
error: string().min(1)
|
|
@@ -56772,7 +57223,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
56772
57223
|
"written",
|
|
56773
57224
|
"refused"
|
|
56774
57225
|
]),
|
|
56775
|
-
steps: array(MigrationOwnerStepSchema)
|
|
57226
|
+
steps: array(MigrationOwnerStepSchema),
|
|
57227
|
+
/** The migration API this engine serves (additive: a server that predates it omits it). */
|
|
57228
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
56776
57229
|
}), {
|
|
56777
57230
|
kind: "mutation",
|
|
56778
57231
|
auth: "admin"
|
|
@@ -56793,6 +57246,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
56793
57246
|
}), {
|
|
56794
57247
|
kind: "mutation",
|
|
56795
57248
|
auth: "admin"
|
|
57249
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
57250
|
+
rows: number().int().nonnegative(),
|
|
57251
|
+
secrets: number().int().nonnegative()
|
|
57252
|
+
}), {
|
|
57253
|
+
kind: "mutation",
|
|
57254
|
+
auth: "admin"
|
|
57255
|
+
}), method(object({
|
|
57256
|
+
integrationId: string().min(1),
|
|
57257
|
+
patch: record(string(), unknown())
|
|
57258
|
+
}), object({ written: boolean() }), {
|
|
57259
|
+
kind: "mutation",
|
|
57260
|
+
auth: "admin"
|
|
57261
|
+
}), method(object({
|
|
57262
|
+
integrationId: string().min(1),
|
|
57263
|
+
keys: array(string().min(1))
|
|
57264
|
+
}), object({
|
|
57265
|
+
present: boolean(),
|
|
57266
|
+
removed: record(string(), unknown())
|
|
57267
|
+
}), {
|
|
57268
|
+
kind: "mutation",
|
|
57269
|
+
auth: "admin"
|
|
56796
57270
|
});
|
|
56797
57271
|
method(object({
|
|
56798
57272
|
sourceUrl: string(),
|
|
@@ -59418,7 +59892,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
59418
59892
|
}), method(RelocateMediaInputSchema, object({ jobId: string() }), {
|
|
59419
59893
|
kind: "mutation",
|
|
59420
59894
|
auth: "admin"
|
|
59421
|
-
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(
|
|
59895
|
+
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(MediaLocationCensusInputSchema, MediaLocationCensusSchema, {
|
|
59896
|
+
kind: "query",
|
|
59897
|
+
auth: "admin"
|
|
59898
|
+
}), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
|
|
59422
59899
|
kind: "query",
|
|
59423
59900
|
auth: "admin"
|
|
59424
59901
|
}), method(object({}), array(RelocateJobSchema).readonly(), {
|
|
@@ -61825,7 +62302,7 @@ var BrokerStatusSchema$1 = _enum([
|
|
|
61825
62302
|
"unreachable",
|
|
61826
62303
|
"tls-error"
|
|
61827
62304
|
]);
|
|
61828
|
-
var BrokerInfoSchema
|
|
62305
|
+
var BrokerInfoSchema = object({
|
|
61829
62306
|
id: string(),
|
|
61830
62307
|
name: string(),
|
|
61831
62308
|
url: string(),
|
|
@@ -61888,7 +62365,7 @@ var StatusSchema = object({
|
|
|
61888
62365
|
brokerCount: number(),
|
|
61889
62366
|
embeddedRunning: boolean()
|
|
61890
62367
|
});
|
|
61891
|
-
method(_void(), array(BrokerInfoSchema
|
|
62368
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
61892
62369
|
kind: "mutation",
|
|
61893
62370
|
auth: "admin"
|
|
61894
62371
|
}), method(IdInputSchema, _void(), {
|
|
@@ -68206,253 +68683,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
|
|
|
68206
68683
|
auth: "admin"
|
|
68207
68684
|
});
|
|
68208
68685
|
/**
|
|
68209
|
-
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
68210
|
-
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
68211
|
-
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
68212
|
-
*
|
|
68213
|
-
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
68214
|
-
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
68215
|
-
* that with the addon id closed over). It is deliberately NOT in any input
|
|
68216
|
-
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
68217
|
-
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
68218
|
-
* addons go through the owner's capability.
|
|
68219
|
-
*
|
|
68220
|
-
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
68221
|
-
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
68222
|
-
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
68223
|
-
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
68224
|
-
* row's own ref.
|
|
68225
|
-
*
|
|
68226
|
-
* Internal: addons reach it through `ctx.records`, never directly.
|
|
68227
|
-
*/
|
|
68228
|
-
var RecordIdSchema = string().min(1).max(256);
|
|
68229
|
-
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
68230
|
-
var RecordBodySchema = record(string(), unknown());
|
|
68231
|
-
var RecordInputSchema = object({
|
|
68232
|
-
id: RecordIdSchema,
|
|
68233
|
-
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
68234
|
-
deviceId: number().int().optional(),
|
|
68235
|
-
/** The domain clock, ms: observed / created / opened. */
|
|
68236
|
-
at: number().int().optional(),
|
|
68237
|
-
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
68238
|
-
expiresAt: number().int().optional(),
|
|
68239
|
-
data: RecordBodySchema
|
|
68240
|
-
});
|
|
68241
|
-
var RecordViewSchema = object({
|
|
68242
|
-
id: string(),
|
|
68243
|
-
deviceId: number().int().nullable(),
|
|
68244
|
-
at: number().nullable(),
|
|
68245
|
-
expiresAt: number().nullable(),
|
|
68246
|
-
rev: number().int(),
|
|
68247
|
-
updatedAt: number(),
|
|
68248
|
-
data: RecordBodySchema
|
|
68249
|
-
});
|
|
68250
|
-
/** Declared index fields only. Anything else does not compile, by design. */
|
|
68251
|
-
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
68252
|
-
string(),
|
|
68253
|
-
number(),
|
|
68254
|
-
boolean(),
|
|
68255
|
-
object({ in: array(union([
|
|
68256
|
-
string(),
|
|
68257
|
-
number(),
|
|
68258
|
-
boolean()
|
|
68259
|
-
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
68260
|
-
object({
|
|
68261
|
-
gt: union([string(), number()]).optional(),
|
|
68262
|
-
gte: union([string(), number()]).optional(),
|
|
68263
|
-
lt: union([string(), number()]).optional(),
|
|
68264
|
-
lte: union([string(), number()]).optional()
|
|
68265
|
-
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
68266
|
-
]));
|
|
68267
|
-
var KindShape = { kind: RecordKindNameSchema };
|
|
68268
|
-
var TimeWindowShape = {
|
|
68269
|
-
deviceId: number().int().optional(),
|
|
68270
|
-
atFrom: number().optional(),
|
|
68271
|
-
atTo: number().optional()
|
|
68272
|
-
};
|
|
68273
|
-
var RecordQueryInputSchema = object({
|
|
68274
|
-
...KindShape,
|
|
68275
|
-
...TimeWindowShape,
|
|
68276
|
-
where: RecordWhereSchema.optional(),
|
|
68277
|
-
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
68278
|
-
order: _enum(["at", "id"]).optional(),
|
|
68279
|
-
desc: boolean().optional(),
|
|
68280
|
-
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
68281
|
-
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
68282
|
-
cursor: string().min(1).optional()
|
|
68283
|
-
});
|
|
68284
|
-
var RecordQueryResultSchema = object({
|
|
68285
|
-
records: array(RecordViewSchema),
|
|
68286
|
-
nextCursor: string().nullable()
|
|
68287
|
-
});
|
|
68288
|
-
var RecordKindStateSchema = _enum([
|
|
68289
|
-
"created",
|
|
68290
|
-
"unchanged",
|
|
68291
|
-
"updated"
|
|
68292
|
-
]);
|
|
68293
|
-
var DeclareKindResultSchema = object({
|
|
68294
|
-
kindId: number().int(),
|
|
68295
|
-
state: RecordKindStateSchema,
|
|
68296
|
-
createdIndexes: array(string())
|
|
68297
|
-
});
|
|
68298
|
-
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
68299
|
-
var SecretRefsList = array(string());
|
|
68300
|
-
/**
|
|
68301
|
-
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
68302
|
-
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
68303
|
-
*/
|
|
68304
|
-
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
68305
|
-
object({
|
|
68306
|
-
op: literal("put"),
|
|
68307
|
-
kind: RecordKindNameSchema,
|
|
68308
|
-
record: RecordInputSchema,
|
|
68309
|
-
ifRev: number().int().min(0).optional()
|
|
68310
|
-
}),
|
|
68311
|
-
object({
|
|
68312
|
-
op: literal("patch"),
|
|
68313
|
-
kind: RecordKindNameSchema,
|
|
68314
|
-
id: RecordIdSchema,
|
|
68315
|
-
data: RecordBodySchema,
|
|
68316
|
-
at: number().int().nullable().optional(),
|
|
68317
|
-
expiresAt: number().int().nullable().optional(),
|
|
68318
|
-
ifRev: number().int().min(1).optional()
|
|
68319
|
-
}),
|
|
68320
|
-
object({
|
|
68321
|
-
op: literal("delete"),
|
|
68322
|
-
kind: RecordKindNameSchema,
|
|
68323
|
-
id: RecordIdSchema
|
|
68324
|
-
})
|
|
68325
|
-
]);
|
|
68326
|
-
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
68327
|
-
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
68328
|
-
kind: "mutation",
|
|
68329
|
-
auth: "admin"
|
|
68330
|
-
}), method(object({
|
|
68331
|
-
...KindShape,
|
|
68332
|
-
id: RecordIdSchema
|
|
68333
|
-
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
68334
|
-
...KindShape,
|
|
68335
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68336
|
-
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
68337
|
-
...KindShape,
|
|
68338
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68339
|
-
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
68340
|
-
...KindShape,
|
|
68341
|
-
record: RecordInputSchema,
|
|
68342
|
-
/** Compare-and-set. `0` means "the row must not exist". */
|
|
68343
|
-
ifRev: number().int().min(0).optional()
|
|
68344
|
-
}), object({
|
|
68345
|
-
rev: number().int(),
|
|
68346
|
-
releasedRefs: SecretRefsList
|
|
68347
|
-
}), {
|
|
68348
|
-
kind: "mutation",
|
|
68349
|
-
auth: "admin"
|
|
68350
|
-
}), method(object({
|
|
68351
|
-
...KindShape,
|
|
68352
|
-
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68353
|
-
}), object({
|
|
68354
|
-
count: number().int(),
|
|
68355
|
-
releasedRefs: SecretRefsList
|
|
68356
|
-
}), {
|
|
68357
|
-
kind: "mutation",
|
|
68358
|
-
auth: "admin"
|
|
68359
|
-
}), method(object({
|
|
68360
|
-
...KindShape,
|
|
68361
|
-
id: RecordIdSchema,
|
|
68362
|
-
data: RecordBodySchema,
|
|
68363
|
-
at: number().int().nullable().optional(),
|
|
68364
|
-
expiresAt: number().int().nullable().optional(),
|
|
68365
|
-
ifRev: number().int().min(1).optional()
|
|
68366
|
-
}), object({
|
|
68367
|
-
rev: number().int(),
|
|
68368
|
-
releasedRefs: SecretRefsList
|
|
68369
|
-
}), {
|
|
68370
|
-
kind: "mutation",
|
|
68371
|
-
auth: "admin"
|
|
68372
|
-
}), method(object({
|
|
68373
|
-
...KindShape,
|
|
68374
|
-
id: RecordIdSchema
|
|
68375
|
-
}), object({
|
|
68376
|
-
deleted: boolean(),
|
|
68377
|
-
secretRefs: SecretRefsList
|
|
68378
|
-
}), {
|
|
68379
|
-
kind: "mutation",
|
|
68380
|
-
auth: "admin"
|
|
68381
|
-
}), method(object({
|
|
68382
|
-
...KindShape,
|
|
68383
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68384
|
-
}), object({
|
|
68385
|
-
deleted: number().int(),
|
|
68386
|
-
secretRefs: SecretRefsList
|
|
68387
|
-
}), {
|
|
68388
|
-
kind: "mutation",
|
|
68389
|
-
auth: "admin"
|
|
68390
|
-
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
68391
|
-
...KindShape,
|
|
68392
|
-
...TimeWindowShape,
|
|
68393
|
-
where: RecordWhereSchema.optional()
|
|
68394
|
-
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
68395
|
-
...KindShape,
|
|
68396
|
-
deviceId: number().int().optional(),
|
|
68397
|
-
from: number(),
|
|
68398
|
-
to: number(),
|
|
68399
|
-
bucketMs: number().int().positive()
|
|
68400
|
-
}), object({ buckets: array(object({
|
|
68401
|
-
bucketStart: number(),
|
|
68402
|
-
count: number().int()
|
|
68403
|
-
})) }), { auth: "admin" }), method(object({
|
|
68404
|
-
...KindShape,
|
|
68405
|
-
op: _enum([
|
|
68406
|
-
"sum",
|
|
68407
|
-
"min",
|
|
68408
|
-
"max",
|
|
68409
|
-
"count"
|
|
68410
|
-
]),
|
|
68411
|
-
field: RecordFieldPathSchema.optional(),
|
|
68412
|
-
groupBy: RecordFieldPathSchema.optional(),
|
|
68413
|
-
where: RecordWhereSchema.optional()
|
|
68414
|
-
}), object({ rows: array(object({
|
|
68415
|
-
group: union([
|
|
68416
|
-
string(),
|
|
68417
|
-
number(),
|
|
68418
|
-
_null()
|
|
68419
|
-
]),
|
|
68420
|
-
value: number().nullable()
|
|
68421
|
-
})) }), { auth: "admin" }), method(object({
|
|
68422
|
-
...KindShape,
|
|
68423
|
-
deviceId: number().int().optional(),
|
|
68424
|
-
atBefore: number().optional(),
|
|
68425
|
-
where: RecordWhereSchema.optional()
|
|
68426
|
-
}), object({
|
|
68427
|
-
deleted: number().int(),
|
|
68428
|
-
secretRefs: SecretRefsList
|
|
68429
|
-
}), {
|
|
68430
|
-
kind: "mutation",
|
|
68431
|
-
auth: "admin"
|
|
68432
|
-
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
68433
|
-
results: array(object({
|
|
68434
|
-
rev: number().int().nullable(),
|
|
68435
|
-
deleted: boolean()
|
|
68436
|
-
})),
|
|
68437
|
-
releasedRefs: SecretRefsList,
|
|
68438
|
-
secretRefs: SecretRefsList
|
|
68439
|
-
}), {
|
|
68440
|
-
kind: "mutation",
|
|
68441
|
-
auth: "admin"
|
|
68442
|
-
}), method(object({
|
|
68443
|
-
deviceId: number().int(),
|
|
68444
|
-
kinds: array(RecordKindNameSchema).optional()
|
|
68445
|
-
}), object({
|
|
68446
|
-
deleted: record(string(), number().int()),
|
|
68447
|
-
secretRefs: SecretRefsList
|
|
68448
|
-
}), {
|
|
68449
|
-
kind: "mutation",
|
|
68450
|
-
auth: "admin"
|
|
68451
|
-
}), method(object(KindShape), object({ id: number().int() }), {
|
|
68452
|
-
kind: "mutation",
|
|
68453
|
-
auth: "admin"
|
|
68454
|
-
});
|
|
68455
|
-
/**
|
|
68456
68686
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
68457
68687
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
68458
68688
|
* declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
|
|
@@ -75967,11 +76197,15 @@ discriminatedUnion("kind", [object({
|
|
|
75967
76197
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
75968
76198
|
* near F. A device that is not near one level most of the time has no floor.
|
|
75969
76199
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
75970
|
-
* 3.
|
|
75971
|
-
*
|
|
76200
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
76201
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
76202
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
76203
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
76204
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
76205
|
+
* is no run.
|
|
75972
76206
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
75973
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
75974
|
-
* covers the longest standby blip above T.
|
|
76207
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
76208
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
75975
76209
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
75976
76210
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
75977
76211
|
*/
|
|
@@ -75982,6 +76216,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
75982
76216
|
30 * SECOND_MS;
|
|
75983
76217
|
60 * SECOND_MS;
|
|
75984
76218
|
20 * MINUTE_MS$1;
|
|
76219
|
+
90 * SECOND_MS;
|
|
76220
|
+
30 * MINUTE_MS$1;
|
|
76221
|
+
10 * MINUTE_MS$1;
|
|
76222
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
75985
76223
|
10 * SECOND_MS;
|
|
75986
76224
|
10 * SECOND_MS;
|
|
75987
76225
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -77000,20 +77238,6 @@ Object.freeze({
|
|
|
77000
77238
|
access: "create",
|
|
77001
77239
|
role: "viewer"
|
|
77002
77240
|
},
|
|
77003
|
-
"broker.add": {
|
|
77004
|
-
capName: "broker",
|
|
77005
|
-
capScope: "system",
|
|
77006
|
-
addonId: null,
|
|
77007
|
-
access: "create",
|
|
77008
|
-
role: "admin"
|
|
77009
|
-
},
|
|
77010
|
-
"broker.get": {
|
|
77011
|
-
capName: "broker",
|
|
77012
|
-
capScope: "system",
|
|
77013
|
-
addonId: null,
|
|
77014
|
-
access: "view",
|
|
77015
|
-
role: "admin"
|
|
77016
|
-
},
|
|
77017
77241
|
"broker.getBrokerConfig": {
|
|
77018
77242
|
capName: "broker",
|
|
77019
77243
|
capScope: "system",
|
|
@@ -77021,20 +77245,6 @@ Object.freeze({
|
|
|
77021
77245
|
access: "view",
|
|
77022
77246
|
role: "admin"
|
|
77023
77247
|
},
|
|
77024
|
-
"broker.getSettings": {
|
|
77025
|
-
capName: "broker",
|
|
77026
|
-
capScope: "system",
|
|
77027
|
-
addonId: null,
|
|
77028
|
-
access: "view",
|
|
77029
|
-
role: "admin"
|
|
77030
|
-
},
|
|
77031
|
-
"broker.getSettingsSchema": {
|
|
77032
|
-
capName: "broker",
|
|
77033
|
-
capScope: "system",
|
|
77034
|
-
addonId: null,
|
|
77035
|
-
access: "view",
|
|
77036
|
-
role: "admin"
|
|
77037
|
-
},
|
|
77038
77248
|
"broker.getState": {
|
|
77039
77249
|
capName: "broker",
|
|
77040
77250
|
capScope: "system",
|
|
@@ -77049,20 +77259,6 @@ Object.freeze({
|
|
|
77049
77259
|
access: "view",
|
|
77050
77260
|
role: "admin"
|
|
77051
77261
|
},
|
|
77052
|
-
"broker.list": {
|
|
77053
|
-
capName: "broker",
|
|
77054
|
-
capScope: "system",
|
|
77055
|
-
addonId: null,
|
|
77056
|
-
access: "view",
|
|
77057
|
-
role: "admin"
|
|
77058
|
-
},
|
|
77059
|
-
"broker.listProviders": {
|
|
77060
|
-
capName: "broker",
|
|
77061
|
-
capScope: "system",
|
|
77062
|
-
addonId: null,
|
|
77063
|
-
access: "view",
|
|
77064
|
-
role: "admin"
|
|
77065
|
-
},
|
|
77066
77262
|
"broker.publish": {
|
|
77067
77263
|
capName: "broker",
|
|
77068
77264
|
capScope: "system",
|
|
@@ -77070,20 +77266,6 @@ Object.freeze({
|
|
|
77070
77266
|
access: "create",
|
|
77071
77267
|
role: "admin"
|
|
77072
77268
|
},
|
|
77073
|
-
"broker.remove": {
|
|
77074
|
-
capName: "broker",
|
|
77075
|
-
capScope: "system",
|
|
77076
|
-
addonId: null,
|
|
77077
|
-
access: "delete",
|
|
77078
|
-
role: "admin"
|
|
77079
|
-
},
|
|
77080
|
-
"broker.setSettings": {
|
|
77081
|
-
capName: "broker",
|
|
77082
|
-
capScope: "system",
|
|
77083
|
-
addonId: null,
|
|
77084
|
-
access: "create",
|
|
77085
|
-
role: "admin"
|
|
77086
|
-
},
|
|
77087
77269
|
"broker.subscribe": {
|
|
77088
77270
|
capName: "broker",
|
|
77089
77271
|
capScope: "system",
|
|
@@ -77091,20 +77273,6 @@ Object.freeze({
|
|
|
77091
77273
|
access: "create",
|
|
77092
77274
|
role: "admin"
|
|
77093
77275
|
},
|
|
77094
|
-
"broker.testConnection": {
|
|
77095
|
-
capName: "broker",
|
|
77096
|
-
capScope: "system",
|
|
77097
|
-
addonId: null,
|
|
77098
|
-
access: "create",
|
|
77099
|
-
role: "admin"
|
|
77100
|
-
},
|
|
77101
|
-
"broker.testSettings": {
|
|
77102
|
-
capName: "broker",
|
|
77103
|
-
capScope: "system",
|
|
77104
|
-
addonId: null,
|
|
77105
|
-
access: "create",
|
|
77106
|
-
role: "admin"
|
|
77107
|
-
},
|
|
77108
77276
|
"broker.unsubscribe": {
|
|
77109
77277
|
capName: "broker",
|
|
77110
77278
|
capScope: "system",
|
|
@@ -79737,13 +79905,6 @@ Object.freeze({
|
|
|
79737
79905
|
access: "view",
|
|
79738
79906
|
role: "admin"
|
|
79739
79907
|
},
|
|
79740
|
-
"integrations.getSettings": {
|
|
79741
|
-
capName: "integrations",
|
|
79742
|
-
capScope: "system",
|
|
79743
|
-
addonId: null,
|
|
79744
|
-
access: "view",
|
|
79745
|
-
role: "admin"
|
|
79746
|
-
},
|
|
79747
79908
|
"integrations.list": {
|
|
79748
79909
|
capName: "integrations",
|
|
79749
79910
|
capScope: "system",
|
|
@@ -79751,13 +79912,6 @@ Object.freeze({
|
|
|
79751
79912
|
access: "view",
|
|
79752
79913
|
role: "admin"
|
|
79753
79914
|
},
|
|
79754
|
-
"integrations.setSettings": {
|
|
79755
|
-
capName: "integrations",
|
|
79756
|
-
capScope: "system",
|
|
79757
|
-
addonId: null,
|
|
79758
|
-
access: "create",
|
|
79759
|
-
role: "admin"
|
|
79760
|
-
},
|
|
79761
79915
|
"integrations.testConnection": {
|
|
79762
79916
|
capName: "integrations",
|
|
79763
79917
|
capScope: "system",
|
|
@@ -80752,6 +80906,13 @@ Object.freeze({
|
|
|
80752
80906
|
access: "view",
|
|
80753
80907
|
role: "admin"
|
|
80754
80908
|
},
|
|
80909
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
80910
|
+
capName: "migrations",
|
|
80911
|
+
capScope: "system",
|
|
80912
|
+
addonId: null,
|
|
80913
|
+
access: "delete",
|
|
80914
|
+
role: "admin"
|
|
80915
|
+
},
|
|
80755
80916
|
"migrations.declareOwner": {
|
|
80756
80917
|
capName: "migrations",
|
|
80757
80918
|
capScope: "system",
|
|
@@ -80759,6 +80920,13 @@ Object.freeze({
|
|
|
80759
80920
|
access: "create",
|
|
80760
80921
|
role: "admin"
|
|
80761
80922
|
},
|
|
80923
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
80924
|
+
capName: "migrations",
|
|
80925
|
+
capScope: "system",
|
|
80926
|
+
addonId: null,
|
|
80927
|
+
access: "delete",
|
|
80928
|
+
role: "admin"
|
|
80929
|
+
},
|
|
80762
80930
|
"migrations.dryRun": {
|
|
80763
80931
|
capName: "migrations",
|
|
80764
80932
|
capScope: "system",
|
|
@@ -80794,6 +80962,20 @@ Object.freeze({
|
|
|
80794
80962
|
access: "create",
|
|
80795
80963
|
role: "admin"
|
|
80796
80964
|
},
|
|
80965
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
80966
|
+
capName: "migrations",
|
|
80967
|
+
capScope: "system",
|
|
80968
|
+
addonId: null,
|
|
80969
|
+
access: "create",
|
|
80970
|
+
role: "admin"
|
|
80971
|
+
},
|
|
80972
|
+
"migrations.readLegacyIntegrationRows": {
|
|
80973
|
+
capName: "migrations",
|
|
80974
|
+
capScope: "system",
|
|
80975
|
+
addonId: null,
|
|
80976
|
+
access: "view",
|
|
80977
|
+
role: "admin"
|
|
80978
|
+
},
|
|
80797
80979
|
"migrations.restorePreMigration": {
|
|
80798
80980
|
capName: "migrations",
|
|
80799
80981
|
capScope: "system",
|
|
@@ -82054,6 +82236,13 @@ Object.freeze({
|
|
|
82054
82236
|
access: "view",
|
|
82055
82237
|
role: "viewer"
|
|
82056
82238
|
},
|
|
82239
|
+
"pipelineAnalytics.getMediaLocationCensus": {
|
|
82240
|
+
capName: "pipeline-analytics",
|
|
82241
|
+
capScope: "device",
|
|
82242
|
+
addonId: null,
|
|
82243
|
+
access: "view",
|
|
82244
|
+
role: "admin"
|
|
82245
|
+
},
|
|
82057
82246
|
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
82058
82247
|
capName: "pipeline-analytics",
|
|
82059
82248
|
capScope: "device",
|
|
@@ -90995,6 +91184,23 @@ object({
|
|
|
90995
91184
|
delay: number().nonnegative().optional(),
|
|
90996
91185
|
commands: record(string(), unknown())
|
|
90997
91186
|
});
|
|
91187
|
+
var BrokerInUseError = class extends Error {
|
|
91188
|
+
brokerId;
|
|
91189
|
+
users;
|
|
91190
|
+
name = "BrokerInUseError";
|
|
91191
|
+
constructor(brokerId, users) {
|
|
91192
|
+
super(`broker '${brokerId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
|
|
91193
|
+
this.brokerId = brokerId;
|
|
91194
|
+
this.users = users;
|
|
91195
|
+
}
|
|
91196
|
+
};
|
|
91197
|
+
/** Throw {@link BrokerInUseError} when any finder names a user of `brokerId`. */
|
|
91198
|
+
async function assertBrokerUnused(brokerId, finders) {
|
|
91199
|
+
const users = [];
|
|
91200
|
+
for (const finder of finders) users.push(...await finder.find(brokerId));
|
|
91201
|
+
if (users.length > 0) throw new BrokerInUseError(brokerId, users);
|
|
91202
|
+
}
|
|
91203
|
+
entryBody({ brokerId: string() });
|
|
90998
91204
|
//#endregion
|
|
90999
91205
|
//#region src/relay-record-kinds.ts
|
|
91000
91206
|
/**
|
|
@@ -91244,29 +91450,6 @@ var MessageRing = class {
|
|
|
91244
91450
|
}
|
|
91245
91451
|
};
|
|
91246
91452
|
//#endregion
|
|
91247
|
-
//#region src/mailbox-record-kinds.ts
|
|
91248
|
-
/**
|
|
91249
|
-
* The `smtp-inbound` mailbox as a record (D999): one `mailboxes` row per mailbox, owned by
|
|
91250
|
-
* `smtp-nodemailer`. The row id is the broker id. The SMTP AUTH password is a keychain-bound secret
|
|
91251
|
-
* field; nothing else holds a copy.
|
|
91252
|
-
*/
|
|
91253
|
-
var MailboxBodySchema = entryBody({
|
|
91254
|
-
port: number().int().min(1).max(65535),
|
|
91255
|
-
bindHost: string().min(1),
|
|
91256
|
-
domain: string().min(1),
|
|
91257
|
-
requireAuth: boolean(),
|
|
91258
|
-
username: string(),
|
|
91259
|
-
password: string().optional().meta({ secret: true }),
|
|
91260
|
-
starttls: boolean(),
|
|
91261
|
-
maxMessageBytes: number().int().positive()
|
|
91262
|
-
});
|
|
91263
|
-
var mailboxesKind = defineEntryKind("mailboxes", {
|
|
91264
|
-
title: "Inbound mailboxes",
|
|
91265
|
-
nameField: "name",
|
|
91266
|
-
schema: MailboxBodySchema,
|
|
91267
|
-
writeTrigger: "an operator (or a consumer addon, once) adds or edits an inbound mailbox"
|
|
91268
|
-
});
|
|
91269
|
-
//#endregion
|
|
91270
91453
|
//#region src/inbound-broker.ts
|
|
91271
91454
|
/**
|
|
91272
91455
|
* The `smtp-inbound` broker kind (D999): a mailbox a device can send alarm mail to.
|
|
@@ -91280,7 +91463,6 @@ var mailboxesKind = defineEntryKind("mailboxes", {
|
|
|
91280
91463
|
var REBIND_DELAY_MS = 5e3;
|
|
91281
91464
|
var MAX_REBIND_ATTEMPTS = 6;
|
|
91282
91465
|
var NO_INTERNAL_HOST_REASON = "The hub has no internal address. Flag one as \"Internal\" under System → Network; cameras need it to reach this mailbox.";
|
|
91283
|
-
var KIND_LABEL = "Inbound mailbox (SMTP)";
|
|
91284
91466
|
var PAGE = 500;
|
|
91285
91467
|
/** The hostname of a role's resolved base URL (`http://192.0.2.10:4480` → `192.0.2.10`). */
|
|
91286
91468
|
function hostFromRole(resolved) {
|
|
@@ -91353,6 +91535,20 @@ var SmtpInboundBroker = class {
|
|
|
91353
91535
|
this.rebindTimers.clear();
|
|
91354
91536
|
await Promise.all([...this.runtimes.keys()].map((id) => this.unbind(id)));
|
|
91355
91537
|
}
|
|
91538
|
+
/** Bring the listener in line with the record after the instances page changed it. */
|
|
91539
|
+
reconcileMailbox(id) {
|
|
91540
|
+
return this.reconcile(id);
|
|
91541
|
+
}
|
|
91542
|
+
/** The runtime's view of one mailbox; null while it has no listener. */
|
|
91543
|
+
runtimeOf(id) {
|
|
91544
|
+
const runtime = this.runtimes.get(id);
|
|
91545
|
+
if (runtime === void 0) return null;
|
|
91546
|
+
return {
|
|
91547
|
+
status: runtime.status,
|
|
91548
|
+
error: runtime.error,
|
|
91549
|
+
checkedAt: runtime.checkedAt
|
|
91550
|
+
};
|
|
91551
|
+
}
|
|
91356
91552
|
reconcile(id) {
|
|
91357
91553
|
const next = (this.chains.get(id) ?? Promise.resolve()).then(() => this.applyRecord(id));
|
|
91358
91554
|
this.chains.set(id, next.catch(() => void 0));
|
|
@@ -91470,28 +91666,6 @@ var SmtpInboundBroker = class {
|
|
|
91470
91666
|
payload: message
|
|
91471
91667
|
});
|
|
91472
91668
|
}
|
|
91473
|
-
async exists(id) {
|
|
91474
|
-
return await this.deps.handle.get(id) !== null;
|
|
91475
|
-
}
|
|
91476
|
-
async info(id) {
|
|
91477
|
-
const row = await this.deps.handle.get(id);
|
|
91478
|
-
if (row === null) return null;
|
|
91479
|
-
const runtime = this.runtimes.get(id);
|
|
91480
|
-
return {
|
|
91481
|
-
id,
|
|
91482
|
-
addonId: this.deps.addonId,
|
|
91483
|
-
name: row.data.name,
|
|
91484
|
-
kind: SMTP_INBOUND_KIND,
|
|
91485
|
-
status: runtime?.status ?? "disconnected",
|
|
91486
|
-
info: {
|
|
91487
|
-
port: row.data.port,
|
|
91488
|
-
domain: row.data.domain,
|
|
91489
|
-
recipients: runtime === void 0 ? 0 : [...runtime.subscriptions.values()].reduce((n, s) => n + s.recipients.size, 0)
|
|
91490
|
-
},
|
|
91491
|
-
lastCheckedAt: runtime?.checkedAt ?? null,
|
|
91492
|
-
error: runtime?.error ?? null
|
|
91493
|
-
};
|
|
91494
|
-
}
|
|
91495
91669
|
async subscribe(brokerId, filter) {
|
|
91496
91670
|
const parsed = SmtpInboundFilterSchema.parse(filter);
|
|
91497
91671
|
const runtime = this.runtimes.get(brokerId);
|
|
@@ -91511,85 +91685,19 @@ var SmtpInboundBroker = class {
|
|
|
91511
91685
|
runtime.subscriptions = next;
|
|
91512
91686
|
return { subscriptionId: id };
|
|
91513
91687
|
}
|
|
91688
|
+
/** The subscribers (camera email push) that still read one mailbox. */
|
|
91689
|
+
subscriberUsers() {
|
|
91690
|
+
return { find: async (brokerId) => [...this.runtimes.get(brokerId)?.subscriptions.values() ?? []].map((sub) => ({
|
|
91691
|
+
kind: "subscriber",
|
|
91692
|
+
id: sub.subscriberId
|
|
91693
|
+
})) };
|
|
91694
|
+
}
|
|
91695
|
+
/**
|
|
91696
|
+
* The `broker` cap is the bus only (D1013): the mailboxes themselves are the `mailboxes` kind of
|
|
91697
|
+
* the addon's `integration-instances` provider (`mailbox-instances.ts`).
|
|
91698
|
+
*/
|
|
91514
91699
|
provider() {
|
|
91515
|
-
const owns = (id) => this.exists(id);
|
|
91516
91700
|
return {
|
|
91517
|
-
list: async ({ kind }) => {
|
|
91518
|
-
if (kind !== void 0 && kind !== "smtp-inbound") return [];
|
|
91519
|
-
const page = await this.deps.handle.query({ limit: PAGE });
|
|
91520
|
-
return (await Promise.all(page.records.map((r) => this.info(r.id)))).flatMap((i) => i === null ? [] : [i]);
|
|
91521
|
-
},
|
|
91522
|
-
get: async ({ id }) => this.info(id),
|
|
91523
|
-
listProviders: async () => [{
|
|
91524
|
-
addonId: this.deps.addonId,
|
|
91525
|
-
kinds: [{
|
|
91526
|
-
kind: SMTP_INBOUND_KIND,
|
|
91527
|
-
label: KIND_LABEL
|
|
91528
|
-
}]
|
|
91529
|
-
}],
|
|
91530
|
-
add: async ({ kind, name, id, settings }) => {
|
|
91531
|
-
if (kind !== "smtp-inbound") throw new Error(`smtp-nodemailer: only kind '${SMTP_INBOUND_KIND}' is handled here (got '${kind}')`);
|
|
91532
|
-
const parsed = SmtpInboundSettingsSchema.parse(settings);
|
|
91533
|
-
const brokerId = id ?? `smtp-inbound-${randomUUID().slice(0, 8)}`;
|
|
91534
|
-
if (await this.exists(brokerId)) throw new Error(`broker id '${brokerId}' is already in use`);
|
|
91535
|
-
const body = MailboxBodySchema.parse({
|
|
91536
|
-
name,
|
|
91537
|
-
port: parsed.port,
|
|
91538
|
-
bindHost: parsed.bindHost,
|
|
91539
|
-
domain: parsed.domain,
|
|
91540
|
-
requireAuth: parsed.requireAuth,
|
|
91541
|
-
username: parsed.username.length > 0 ? parsed.username : `camstack-${randomBytes(4).toString("hex")}`,
|
|
91542
|
-
password: parsed.password.length > 0 ? parsed.password : randomBytes(18).toString("base64url"),
|
|
91543
|
-
starttls: parsed.starttls,
|
|
91544
|
-
maxMessageBytes: parsed.maxMessageBytes
|
|
91545
|
-
});
|
|
91546
|
-
await this.deps.handle.put({
|
|
91547
|
-
id: brokerId,
|
|
91548
|
-
data: body
|
|
91549
|
-
}, { ifRev: 0 });
|
|
91550
|
-
await this.reconcile(brokerId);
|
|
91551
|
-
return { id: brokerId };
|
|
91552
|
-
},
|
|
91553
|
-
remove: async ({ id }) => {
|
|
91554
|
-
if (!await owns(id)) return;
|
|
91555
|
-
await this.deps.handle.delete(id);
|
|
91556
|
-
await this.reconcile(id);
|
|
91557
|
-
},
|
|
91558
|
-
testConnection: async ({ id }) => {
|
|
91559
|
-
if (!await owns(id)) return {
|
|
91560
|
-
ok: false,
|
|
91561
|
-
error: "unknown broker"
|
|
91562
|
-
};
|
|
91563
|
-
const runtime = this.runtimes.get(id);
|
|
91564
|
-
if (runtime?.status === "connected") return {
|
|
91565
|
-
ok: true,
|
|
91566
|
-
latencyMs: 0
|
|
91567
|
-
};
|
|
91568
|
-
return {
|
|
91569
|
-
ok: false,
|
|
91570
|
-
error: runtime?.error ?? "The mailbox is not listening"
|
|
91571
|
-
};
|
|
91572
|
-
},
|
|
91573
|
-
getSettings: async ({ id }) => {
|
|
91574
|
-
const row = await this.deps.handle.get(id);
|
|
91575
|
-
if (row === null) return null;
|
|
91576
|
-
const { name: _name, password: _password, ...settings } = row.data;
|
|
91577
|
-
return settings;
|
|
91578
|
-
},
|
|
91579
|
-
setSettings: async ({ id, settings }) => {
|
|
91580
|
-
const row = await this.deps.handle.getResolved(id);
|
|
91581
|
-
if (row === null) return;
|
|
91582
|
-
const parsed = SmtpInboundSettingsSchema.partial().parse(settings);
|
|
91583
|
-
const next = MailboxBodySchema.parse({
|
|
91584
|
-
...row.data,
|
|
91585
|
-
...Object.fromEntries(Object.entries(parsed).filter(([, v]) => v !== void 0 && v !== ""))
|
|
91586
|
-
});
|
|
91587
|
-
await this.deps.handle.put({
|
|
91588
|
-
id,
|
|
91589
|
-
data: next
|
|
91590
|
-
});
|
|
91591
|
-
await this.reconcile(id);
|
|
91592
|
-
},
|
|
91593
91701
|
getBrokerConfig: async ({ id }) => {
|
|
91594
91702
|
let row;
|
|
91595
91703
|
try {
|
|
@@ -91611,19 +91719,6 @@ var SmtpInboundBroker = class {
|
|
|
91611
91719
|
starttls: row.data.starttls
|
|
91612
91720
|
};
|
|
91613
91721
|
},
|
|
91614
|
-
getSettingsSchema: async ({ kind }) => kind === "smtp-inbound" ? mailboxSettingsSchema() : null,
|
|
91615
|
-
testSettings: async ({ kind, settings }) => {
|
|
91616
|
-
if (kind !== "smtp-inbound") return {
|
|
91617
|
-
ok: false,
|
|
91618
|
-
error: `unsupported kind: ${kind}`
|
|
91619
|
-
};
|
|
91620
|
-
const parsed = SmtpInboundSettingsSchema.safeParse(settings);
|
|
91621
|
-
if (!parsed.success) return {
|
|
91622
|
-
ok: false,
|
|
91623
|
-
error: parsed.error.message
|
|
91624
|
-
};
|
|
91625
|
-
return probeBind(parsed.data.port, parsed.data.bindHost);
|
|
91626
|
-
},
|
|
91627
91722
|
publish: async () => {
|
|
91628
91723
|
throw new Error("smtp-inbound: a mailbox only receives — publish is not supported");
|
|
91629
91724
|
},
|
|
@@ -91651,23 +91746,6 @@ var SmtpInboundBroker = class {
|
|
|
91651
91746
|
};
|
|
91652
91747
|
}
|
|
91653
91748
|
};
|
|
91654
|
-
/** A bind probe: can this port be taken on this host right now? */
|
|
91655
|
-
function probeBind(port, host) {
|
|
91656
|
-
const started = Date.now();
|
|
91657
|
-
return new Promise((resolve) => {
|
|
91658
|
-
const probe = createServer();
|
|
91659
|
-
probe.once("error", (err) => resolve({
|
|
91660
|
-
ok: false,
|
|
91661
|
-
error: err.message
|
|
91662
|
-
}));
|
|
91663
|
-
probe.listen(port, host, () => {
|
|
91664
|
-
probe.close(() => resolve({
|
|
91665
|
-
ok: true,
|
|
91666
|
-
latencyMs: Date.now() - started
|
|
91667
|
-
}));
|
|
91668
|
-
});
|
|
91669
|
-
});
|
|
91670
|
-
}
|
|
91671
91749
|
function mailboxSettingsSchema() {
|
|
91672
91750
|
return { sections: [{
|
|
91673
91751
|
id: "mailbox",
|
|
@@ -135957,6 +136035,145 @@ var SmtpServerListener = class {
|
|
|
135957
136035
|
}
|
|
135958
136036
|
};
|
|
135959
136037
|
//#endregion
|
|
136038
|
+
//#region src/bind-probe.ts
|
|
136039
|
+
/** A bind probe: can this port be taken on this host right now? */
|
|
136040
|
+
function probeBind(port, host) {
|
|
136041
|
+
const started = Date.now();
|
|
136042
|
+
return new Promise((resolve) => {
|
|
136043
|
+
const probe = createServer();
|
|
136044
|
+
probe.once("error", (err) => resolve({
|
|
136045
|
+
ok: false,
|
|
136046
|
+
error: err.message
|
|
136047
|
+
}));
|
|
136048
|
+
probe.listen(port, host, () => {
|
|
136049
|
+
probe.close(() => resolve({
|
|
136050
|
+
ok: true,
|
|
136051
|
+
latencyMs: Date.now() - started
|
|
136052
|
+
}));
|
|
136053
|
+
});
|
|
136054
|
+
});
|
|
136055
|
+
}
|
|
136056
|
+
//#endregion
|
|
136057
|
+
//#region src/mailbox-record-kinds.ts
|
|
136058
|
+
/**
|
|
136059
|
+
* The `smtp-inbound` mailbox as a record (D999): one `mailboxes` row per mailbox, owned by
|
|
136060
|
+
* `smtp-nodemailer`. The row id is the broker id. The SMTP AUTH password is a keychain-bound secret
|
|
136061
|
+
* field; nothing else holds a copy.
|
|
136062
|
+
*/
|
|
136063
|
+
var MailboxBodySchema = entryBody({
|
|
136064
|
+
port: number().int().min(1).max(65535),
|
|
136065
|
+
bindHost: string().min(1),
|
|
136066
|
+
domain: string().min(1),
|
|
136067
|
+
requireAuth: boolean(),
|
|
136068
|
+
username: string(),
|
|
136069
|
+
password: string().optional().meta({ secret: true }),
|
|
136070
|
+
starttls: boolean(),
|
|
136071
|
+
maxMessageBytes: number().int().positive()
|
|
136072
|
+
});
|
|
136073
|
+
var mailboxesKind = defineEntryKind("mailboxes", {
|
|
136074
|
+
title: "Inbound mailboxes",
|
|
136075
|
+
nameField: "name",
|
|
136076
|
+
schema: MailboxBodySchema,
|
|
136077
|
+
writeTrigger: "an operator (or a consumer addon, once) adds or edits an inbound mailbox"
|
|
136078
|
+
});
|
|
136079
|
+
//#endregion
|
|
136080
|
+
//#region src/mailbox-instances.ts
|
|
136081
|
+
/**
|
|
136082
|
+
* The inbound mailboxes as `integration-instances` (D1013): one instance per `mailboxes` record,
|
|
136083
|
+
* over the SAME records and ids the `smtp-inbound` broker serves; the `mailboxes` kind of the
|
|
136084
|
+
* composed provider (the relays are the `relays` kind). A change re-applies the listener; removing
|
|
136085
|
+
* a mailbox a subscriber (camera email push) still reads is refused.
|
|
136086
|
+
*/
|
|
136087
|
+
var MAILBOX_INSTANCE_KIND = "mailboxes";
|
|
136088
|
+
function statusOf(runtime) {
|
|
136089
|
+
if (runtime === null) return {
|
|
136090
|
+
state: "unknown",
|
|
136091
|
+
label: "Not listening"
|
|
136092
|
+
};
|
|
136093
|
+
if (runtime.status === "connected") return {
|
|
136094
|
+
state: "ok",
|
|
136095
|
+
label: "Listening"
|
|
136096
|
+
};
|
|
136097
|
+
return {
|
|
136098
|
+
state: "error",
|
|
136099
|
+
label: runtime.status,
|
|
136100
|
+
...runtime.error !== null ? { detail: runtime.error } : {}
|
|
136101
|
+
};
|
|
136102
|
+
}
|
|
136103
|
+
function newMailboxBody(name) {
|
|
136104
|
+
const defaults = SmtpInboundSettingsSchema.parse({});
|
|
136105
|
+
return MailboxBodySchema.parse({
|
|
136106
|
+
name,
|
|
136107
|
+
port: defaults.port,
|
|
136108
|
+
bindHost: defaults.bindHost,
|
|
136109
|
+
domain: defaults.domain,
|
|
136110
|
+
requireAuth: defaults.requireAuth,
|
|
136111
|
+
username: `camstack-${randomBytes(4).toString("hex")}`,
|
|
136112
|
+
password: randomBytes(18).toString("base64url"),
|
|
136113
|
+
starttls: defaults.starttls,
|
|
136114
|
+
maxMessageBytes: defaults.maxMessageBytes
|
|
136115
|
+
});
|
|
136116
|
+
}
|
|
136117
|
+
function buildMailboxInstances(records, deps) {
|
|
136118
|
+
const users = deps.inbound.subscriberUsers();
|
|
136119
|
+
const base = new RecordIntegrationInstances(records, mailboxesKind, {
|
|
136120
|
+
description: {
|
|
136121
|
+
role: "services",
|
|
136122
|
+
instanceMode: "multiple",
|
|
136123
|
+
kind: "mailbox",
|
|
136124
|
+
canTest: true
|
|
136125
|
+
},
|
|
136126
|
+
formSchema: () => mailboxSettingsSchema(),
|
|
136127
|
+
initialBody: (name) => newMailboxBody(name),
|
|
136128
|
+
mintId: () => `smtp-inbound-${randomUUID().slice(0, 8)}`,
|
|
136129
|
+
configured: () => true,
|
|
136130
|
+
health: (_body, id) => {
|
|
136131
|
+
const runtime = deps.inbound.runtimeOf(id);
|
|
136132
|
+
return {
|
|
136133
|
+
status: statusOf(runtime),
|
|
136134
|
+
...runtime !== null ? { lastCheckedAt: runtime.checkedAt } : {}
|
|
136135
|
+
};
|
|
136136
|
+
},
|
|
136137
|
+
onChanged: (id) => deps.inbound.reconcileMailbox(id),
|
|
136138
|
+
test: async ({ id, values }) => {
|
|
136139
|
+
if (id === null) {
|
|
136140
|
+
const parsed = SmtpInboundSettingsSchema.safeParse(values);
|
|
136141
|
+
if (!parsed.success) return {
|
|
136142
|
+
success: false,
|
|
136143
|
+
status: "rejected",
|
|
136144
|
+
error: parsed.error.message
|
|
136145
|
+
};
|
|
136146
|
+
const bound = await probeBind(parsed.data.port, parsed.data.bindHost);
|
|
136147
|
+
return bound.ok ? {
|
|
136148
|
+
success: true,
|
|
136149
|
+
status: "validated",
|
|
136150
|
+
latencyMs: bound.latencyMs
|
|
136151
|
+
} : {
|
|
136152
|
+
success: false,
|
|
136153
|
+
status: "rejected",
|
|
136154
|
+
error: bound.error
|
|
136155
|
+
};
|
|
136156
|
+
}
|
|
136157
|
+
const runtime = deps.inbound.runtimeOf(id);
|
|
136158
|
+
return runtime?.status === "connected" ? {
|
|
136159
|
+
success: true,
|
|
136160
|
+
status: "validated"
|
|
136161
|
+
} : {
|
|
136162
|
+
success: false,
|
|
136163
|
+
status: "rejected",
|
|
136164
|
+
error: runtime?.error ?? "The mailbox is not listening"
|
|
136165
|
+
};
|
|
136166
|
+
}
|
|
136167
|
+
}).toProvider();
|
|
136168
|
+
return {
|
|
136169
|
+
...base,
|
|
136170
|
+
deleteInstance: async (input) => {
|
|
136171
|
+
await assertBrokerUnused(input.id, [users]);
|
|
136172
|
+
return base.deleteInstance(input);
|
|
136173
|
+
}
|
|
136174
|
+
};
|
|
136175
|
+
}
|
|
136176
|
+
//#endregion
|
|
135960
136177
|
//#region ../../node_modules/nodemailer/lib/mime-funcs/mime-types.js
|
|
135961
136178
|
var require_mime_types = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
135962
136179
|
var path$1 = __require("path");
|
|
@@ -145690,7 +145907,29 @@ var SmtpNodemailerAddon = class extends BaseAddon {
|
|
|
145690
145907
|
},
|
|
145691
145908
|
{
|
|
145692
145909
|
capability: integrationInstancesCapability,
|
|
145693
|
-
provider:
|
|
145910
|
+
provider: composeInstanceKinds([{
|
|
145911
|
+
kind: {
|
|
145912
|
+
id: "relays",
|
|
145913
|
+
label: "SMTP relay",
|
|
145914
|
+
functionCap: "smtp-provider",
|
|
145915
|
+
role: "services"
|
|
145916
|
+
},
|
|
145917
|
+
provider: this.instances.toProvider()
|
|
145918
|
+
}, {
|
|
145919
|
+
kind: {
|
|
145920
|
+
id: MAILBOX_INSTANCE_KIND,
|
|
145921
|
+
label: "Inbound mailbox (SMTP)",
|
|
145922
|
+
functionCap: "broker",
|
|
145923
|
+
brokerKind: SMTP_INBOUND_KIND,
|
|
145924
|
+
role: "services"
|
|
145925
|
+
},
|
|
145926
|
+
provider: buildMailboxInstances(records, { inbound })
|
|
145927
|
+
}], {
|
|
145928
|
+
role: "services",
|
|
145929
|
+
instanceMode: "multiple",
|
|
145930
|
+
kind: "relays",
|
|
145931
|
+
canTest: true
|
|
145932
|
+
})
|
|
145694
145933
|
},
|
|
145695
145934
|
{
|
|
145696
145935
|
capability: brokerCapability,
|
|
@@ -145844,7 +146083,8 @@ var SmtpNodemailerAddon = class extends BaseAddon {
|
|
|
145844
146083
|
const rows = (await records.kind(smtpRelaysKind).query({ limit: RELAY_LIST_MAX })).records;
|
|
145845
146084
|
return (rows.find((row) => row.data.host.length > 0) ?? rows[0])?.id;
|
|
145846
146085
|
}
|
|
145847
|
-
|
|
146086
|
+
/** The relay form. Its values live on the relay record, never in `addon-settings`. */
|
|
146087
|
+
async integrationFormSchema(_instance) {
|
|
145848
146088
|
return this.relayForm();
|
|
145849
146089
|
}
|
|
145850
146090
|
async getGlobalSettings() {
|