@camstack/system 1.2.172 → 1.2.174
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/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +200 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +200 -2
- package/dist/builtins/device-manager/device-meta-actions.d.ts +13 -0
- package/dist/builtins/device-manager/device-row-store.d.ts +41 -0
- package/dist/builtins/device-manager/migrate-device.d.ts +44 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-1DEQFmTq.mjs → dist-B25PDvaS.mjs} +85 -2
- package/dist/{dist-DsoVfJSR.js → dist-BzPNEPHo.js} +90 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{manifest-system-deps-ir_XTNpe.js → manifest-system-deps-B0mzJPmb.js} +1 -1
- package/dist/{manifest-system-deps-Da6sme9C.mjs → manifest-system-deps-Pw8iaY5X.mjs} +1 -1
- package/dist/{retired-settings-keys-DTE5k4lU.js → retired-settings-keys-DZmeJ8ja.js} +1 -1
- package/dist/{retired-settings-keys-ChS1Q2Kv.mjs → retired-settings-keys-tWdiQpwa.mjs} +1 -1
- package/package.json +1 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_system_deps = require("./manifest-system-deps-
|
|
2
|
+
const require_manifest_system_deps = require("./manifest-system-deps-B0mzJPmb.js");
|
|
3
3
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_chunk.__toESM(node_path);
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $t as emitHeapDiagnosticReport, A as createUdsLoggerWithControl, Ft as RUNNER_RSS_BUDGET_ENV, Jt as startRunnerHeapWatch, a as createUdsAddonContext, bt as resolveAddonClass, it as setWorkerNativeCapsChangeListener, j as LocalChildClient, n as installManifestPythonDeps, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestSystemDeps, tt as getWorkerNativeCapProvider, yt as installManifestNativeDeps } from "./manifest-system-deps-
|
|
1
|
+
import { $t as emitHeapDiagnosticReport, A as createUdsLoggerWithControl, Ft as RUNNER_RSS_BUDGET_ENV, Jt as startRunnerHeapWatch, a as createUdsAddonContext, bt as resolveAddonClass, it as setWorkerNativeCapsChangeListener, j as LocalChildClient, n as installManifestPythonDeps, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestSystemDeps, tt as getWorkerNativeCapProvider, yt as installManifestNativeDeps } from "./manifest-system-deps-Pw8iaY5X.mjs";
|
|
2
2
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
3
3
|
import { register } from "node:module";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BzPNEPHo.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as addonPagesCapability, Nt as errMsg, Pt as BaseAddon, on as EventCategory } from "../../dist-B25PDvaS.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BzPNEPHo.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Nt as errMsg, O as addonWidgetsCapability, Pt as BaseAddon, on as EventCategory } from "../../dist-B25PDvaS.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BzPNEPHo.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Nt as errMsg, Pt as BaseAddon, k as alertsCapability, n as AlertSchema, on as EventCategory, qt as createEvent } from "../../dist-B25PDvaS.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BzPNEPHo.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let buffer = require("buffer");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Pt as BaseAddon, j as backupCapability, on as EventCategory } from "../../dist-B25PDvaS.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { Buffer as Buffer$1 } from "buffer";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BzPNEPHo.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Pt as BaseAddon, ct as logDestinationCapability } from "../../dist-B25PDvaS.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-BzPNEPHo.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as coreBlockAddonId, L as coreBlockIdFromAddonId, Nt as errMsg, Pt as BaseAddon, R as coreBlocksCapability, c as CORE_BLOCK_ADDON_PREFIX, d as DeclaredDevices, l as CoreBlockSchema, s as CORE_BLOCKS_ADDON_ID } from "../../dist-B25PDvaS.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-BzPNEPHo.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-DZmeJ8ja.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
10
10
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -3271,6 +3271,75 @@ var DeviceEventPropagator = class {
|
|
|
3271
3271
|
}
|
|
3272
3272
|
};
|
|
3273
3273
|
//#endregion
|
|
3274
|
+
//#region src/builtins/device-manager/migrate-device.ts
|
|
3275
|
+
/** A refusal that names a switch the camera does not offer, rather than one it
|
|
3276
|
+
* could not be asked about. The cap documents rejecting the former; anything
|
|
3277
|
+
* else is treated as the camera being out of reach, which is the safe reading:
|
|
3278
|
+
* it keeps the switch in `sourceStillLive` instead of quietly clearing it. */
|
|
3279
|
+
function classify(err) {
|
|
3280
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3281
|
+
return {
|
|
3282
|
+
outcome: /does not offer|not available|unsupported/i.test(detail) ? "not-offered" : "unreachable",
|
|
3283
|
+
detail
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
async function applyAll(deps, deviceId, enabled) {
|
|
3287
|
+
const out = [];
|
|
3288
|
+
for (const switchId of require_dist.CAMERA_SWITCH_ORDER) try {
|
|
3289
|
+
await deps.setSwitch(deviceId, switchId, enabled);
|
|
3290
|
+
out.push({
|
|
3291
|
+
deviceId,
|
|
3292
|
+
switchId,
|
|
3293
|
+
outcome: enabled ? "on" : "off"
|
|
3294
|
+
});
|
|
3295
|
+
} catch (err) {
|
|
3296
|
+
const { outcome, detail } = classify(err);
|
|
3297
|
+
out.push({
|
|
3298
|
+
deviceId,
|
|
3299
|
+
switchId,
|
|
3300
|
+
outcome,
|
|
3301
|
+
detail
|
|
3302
|
+
});
|
|
3303
|
+
deps.log("camera switch refused", {
|
|
3304
|
+
deviceId,
|
|
3305
|
+
switchId,
|
|
3306
|
+
enabled,
|
|
3307
|
+
outcome,
|
|
3308
|
+
detail
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
return out;
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Put both cameras to sleep, exchange their identities, wake the target.
|
|
3315
|
+
*
|
|
3316
|
+
* Throws only if the swap itself fails — a switch that could not be written is
|
|
3317
|
+
* reported, not raised, because refusing to migrate a camera whose old hardware
|
|
3318
|
+
* is unreachable would refuse the exact case this exists for.
|
|
3319
|
+
*/
|
|
3320
|
+
async function migrateDevice$1(deps, input) {
|
|
3321
|
+
const { sourceId, targetId } = input;
|
|
3322
|
+
if (sourceId === targetId) throw new Error(`[device-manager] migrateDevice: ${sourceId} is both source and target`);
|
|
3323
|
+
const down = [...await applyAll(deps, sourceId, false), ...await applyAll(deps, targetId, false)];
|
|
3324
|
+
const sourceStillLive = down.filter((r) => r.deviceId === sourceId && r.outcome === "unreachable").map((r) => r.switchId);
|
|
3325
|
+
await deps.swapIds(sourceId, targetId);
|
|
3326
|
+
const up = await applyAll(deps, sourceId, true);
|
|
3327
|
+
await deps.respawnRunners([sourceId, targetId]);
|
|
3328
|
+
deps.log("camera migrated", {
|
|
3329
|
+
sourceId,
|
|
3330
|
+
targetId,
|
|
3331
|
+
sourceStillLive,
|
|
3332
|
+
refusals: [...down, ...up].filter((r) => r.outcome === "unreachable").length
|
|
3333
|
+
});
|
|
3334
|
+
return {
|
|
3335
|
+
sourceId,
|
|
3336
|
+
targetId,
|
|
3337
|
+
switches: [...down, ...up],
|
|
3338
|
+
sourceStillLive,
|
|
3339
|
+
swapped: true
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
//#endregion
|
|
3274
3343
|
//#region src/builtins/device-manager/device-meta-actions.ts
|
|
3275
3344
|
/**
|
|
3276
3345
|
* Device meta-mutation + persistence actions for the device-manager addon —
|
|
@@ -3344,6 +3413,54 @@ async function allocateDeviceId(pctx, input) {
|
|
|
3344
3413
|
return { id };
|
|
3345
3414
|
});
|
|
3346
3415
|
}
|
|
3416
|
+
/**
|
|
3417
|
+
* Replace a camera: the new hardware inherits the number the system knows.
|
|
3418
|
+
*
|
|
3419
|
+
* The composition lives in `migrate-device.ts`, deliberately free of this
|
|
3420
|
+
* file's context so the decisions it carries — above all "never report a switch
|
|
3421
|
+
* as off when it could not be written" — are testable without a hub. This is
|
|
3422
|
+
* the wiring: the four real dependencies, and the write lock.
|
|
3423
|
+
*
|
|
3424
|
+
* The temp id comes from `allocateNextDeviceId`, the monotonic counter, so the
|
|
3425
|
+
* value a crash could park a row on was never used and will never be reused.
|
|
3426
|
+
* `swapIds` therefore runs INSIDE the same lock the id was allocated under.
|
|
3427
|
+
*/
|
|
3428
|
+
async function migrateDevice(pctx, input) {
|
|
3429
|
+
const { sourceId, targetId } = input;
|
|
3430
|
+
const ctx = pctx.host.ctx;
|
|
3431
|
+
return await pctx.metaStore.withMetaWriteLock(async () => {
|
|
3432
|
+
const tempId = await pctx.metaStore.allocateNextDeviceId();
|
|
3433
|
+
return await migrateDevice$1({
|
|
3434
|
+
setSwitch: async (deviceId, switchId, enabled) => {
|
|
3435
|
+
await ctx.api.pipelineOrchestrator.setCameraSwitch.mutate({
|
|
3436
|
+
deviceId,
|
|
3437
|
+
switchId,
|
|
3438
|
+
enabled
|
|
3439
|
+
});
|
|
3440
|
+
},
|
|
3441
|
+
swapIds: async (a, b) => {
|
|
3442
|
+
await pctx.metaStore.rows.swapIds(a, b, tempId);
|
|
3443
|
+
},
|
|
3444
|
+
respawnRunners: async (deviceIds) => {
|
|
3445
|
+
const owners = /* @__PURE__ */ new Set();
|
|
3446
|
+
for (const deviceId of deviceIds) {
|
|
3447
|
+
const addonId = pctx.metaStore.idToAddonId.get(deviceId);
|
|
3448
|
+
if (addonId !== void 0) owners.add(addonId);
|
|
3449
|
+
}
|
|
3450
|
+
for (const addonId of owners) await ctx.api.addons.restartAddon.mutate({ addonId });
|
|
3451
|
+
},
|
|
3452
|
+
log: (message, meta) => {
|
|
3453
|
+
ctx.logger.info(message, {
|
|
3454
|
+
tags: { deviceId: sourceId },
|
|
3455
|
+
meta
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3458
|
+
}, {
|
|
3459
|
+
sourceId,
|
|
3460
|
+
targetId
|
|
3461
|
+
});
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3347
3464
|
async function registerDevice(pctx, input) {
|
|
3348
3465
|
const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
|
|
3349
3466
|
const featuresArr = Array.isArray(features) ? [...features] : [];
|
|
@@ -5151,6 +5268,86 @@ var DeviceRowStore = class {
|
|
|
5151
5268
|
});
|
|
5152
5269
|
}
|
|
5153
5270
|
/** Drop the device's row. Idempotent. */
|
|
5271
|
+
/**
|
|
5272
|
+
* Exchange the ids of two devices, carrying their children with them.
|
|
5273
|
+
*
|
|
5274
|
+
* ## Why this exists as a primitive
|
|
5275
|
+
*
|
|
5276
|
+
* Replacing a camera means the new hardware has to become the device the rest
|
|
5277
|
+
* of the system already knows. Everything general about a camera is keyed on
|
|
5278
|
+
* the NUMERIC id and on nothing else — the recording path on disk is
|
|
5279
|
+
* `<deviceId>/profile/…`, the hour ledger, the media store and zone ownership
|
|
5280
|
+
* carry `deviceId` and not one `stableId` between them, and all four
|
|
5281
|
+
* ecosystem exports (Home Assistant, Alexa, HomeKit, Google) key on it too.
|
|
5282
|
+
* Moving that work is the expensive, lossy operation. Moving the NUMBER is
|
|
5283
|
+
* two rows.
|
|
5284
|
+
*
|
|
5285
|
+
* ## Children are re-pointed, not renumbered
|
|
5286
|
+
*
|
|
5287
|
+
* A child keeps its own id and follows its parent: every row whose
|
|
5288
|
+
* `parentDeviceId` was `a` now names `b`, and the reverse. Renumbering the
|
|
5289
|
+
* children as well would move THEIR general work for no reason — the
|
|
5290
|
+
* accessory's own recordings and rules are keyed on the child id, which is
|
|
5291
|
+
* not the id being inherited.
|
|
5292
|
+
*
|
|
5293
|
+
* ## There are no transactions here, so the ORDER is the safety
|
|
5294
|
+
*
|
|
5295
|
+
* The backing store is a collection API (`set` / `delete` / `updateWhere`);
|
|
5296
|
+
* there is no rollback to lean on. So the sequence is chosen to make every
|
|
5297
|
+
* crash point LOUD rather than plausible: `a` first moves to a freshly
|
|
5298
|
+
* allocated id, which the monotonic counter guarantees was never used and
|
|
5299
|
+
* will never be reused. A crash before the end therefore leaves a device
|
|
5300
|
+
* missing from its number and a stray at an unfamiliar one — visible in the
|
|
5301
|
+
* device list at a glance. What it can never leave is two rows on one number,
|
|
5302
|
+
* or a row that looks correct while its children point elsewhere.
|
|
5303
|
+
*
|
|
5304
|
+
* The caller holds {@link DeviceMetaStore.withMetaWriteLock}. This method does
|
|
5305
|
+
* not take it: taking it here would deadlock the migration that has to disable
|
|
5306
|
+
* functions on both devices in the same critical section.
|
|
5307
|
+
*/
|
|
5308
|
+
async swapIds(a, b, tempId) {
|
|
5309
|
+
if (a === b) throw new Error(`[device-manager] swapIds: ${a} and ${b} are the same device`);
|
|
5310
|
+
await this.declare();
|
|
5311
|
+
const rowA = await this.get(a);
|
|
5312
|
+
const rowB = await this.get(b);
|
|
5313
|
+
if (rowA === null) throw new Error(`[device-manager] swapIds: device ${a} does not exist`);
|
|
5314
|
+
if (rowB === null) throw new Error(`[device-manager] swapIds: device ${b} does not exist`);
|
|
5315
|
+
if (await this.get(tempId) !== null) throw new Error(`[device-manager] swapIds: temp id ${tempId} is taken`);
|
|
5316
|
+
const childrenOfA = (await this.listByParent(a)).map((r) => r.meta.id);
|
|
5317
|
+
const childrenOfB = (await this.listByParent(b)).map((r) => r.meta.id);
|
|
5318
|
+
await this.moveRow(rowA, tempId);
|
|
5319
|
+
await this.moveRow(rowB, a);
|
|
5320
|
+
await this.moveRow({
|
|
5321
|
+
...rowA,
|
|
5322
|
+
meta: {
|
|
5323
|
+
...rowA.meta,
|
|
5324
|
+
id: tempId
|
|
5325
|
+
}
|
|
5326
|
+
}, b);
|
|
5327
|
+
for (const childId of childrenOfA) await this.patch(childId, { parentDeviceId: b });
|
|
5328
|
+
for (const childId of childrenOfB) await this.patch(childId, { parentDeviceId: a });
|
|
5329
|
+
this.logger.info("device ids swapped", {
|
|
5330
|
+
tags: { deviceId: a },
|
|
5331
|
+
meta: {
|
|
5332
|
+
a,
|
|
5333
|
+
b,
|
|
5334
|
+
childrenOfA,
|
|
5335
|
+
childrenOfB
|
|
5336
|
+
}
|
|
5337
|
+
});
|
|
5338
|
+
}
|
|
5339
|
+
/** Rewrite a row under a new id and drop the old key. Not exported: the only
|
|
5340
|
+
* legitimate reason to move a row is {@link swapIds}. */
|
|
5341
|
+
async moveRow(row, toId) {
|
|
5342
|
+
await this.upsert({
|
|
5343
|
+
...row.meta,
|
|
5344
|
+
id: toId
|
|
5345
|
+
}, {
|
|
5346
|
+
registered: row.registered,
|
|
5347
|
+
metadata: row.metadata
|
|
5348
|
+
});
|
|
5349
|
+
await this.remove(row.meta.id);
|
|
5350
|
+
}
|
|
5154
5351
|
async remove(deviceId) {
|
|
5155
5352
|
await this.declare();
|
|
5156
5353
|
await this.backend.delete({
|
|
@@ -5871,6 +6068,7 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
5871
6068
|
resolveDeviceOwnerSync: (deviceId) => resolveDeviceOwnerSync(pctx, deviceId),
|
|
5872
6069
|
resolveNativeCapOwnerSync: (capName, deviceId) => resolveNativeCapOwnerSync(pctx, capName, deviceId),
|
|
5873
6070
|
allocateDeviceId: (input) => allocateDeviceId(pctx, input),
|
|
6071
|
+
migrateDevice: (input) => migrateDevice(pctx, input),
|
|
5874
6072
|
registerDevice: (input) => registerDevice(pctx, input),
|
|
5875
6073
|
removeDevice: (input) => removeDevice(pctx, input),
|
|
5876
6074
|
persistConfig: (input) => persistConfig(pctx, input),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-
|
|
1
|
+
import { Bt as DeviceType, H as deviceManagerCapability, J as enumerateSchemaFields, Nt as errMsg, P as buildStreamParamsConfigSchema, Pt as BaseAddon, Qt as isDeviceConfigCap, Rt as DeviceFeature, U as deviceStateCapability, Ut as WELL_KNOWN_TAB_MAP, W as deviceStatusCapability, a as CAMERA_SWITCH_ORDER, an as sleep, b as STREAM_PROFILE_META, bt as runtimeStatePolicyFor, f as DeviceStatusSchema, ft as normalizeUnit, mt as parseStreamParamsFormPatch, o as CAP_NAMES_WITH_STATUS, on as EventCategory, q as enumerateItemArrayFields, t as ALL_CAPABILITY_DEFINITIONS, zt as DeviceRole } from "../../dist-B25PDvaS.mjs";
|
|
2
|
+
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-tWdiQpwa.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
5
5
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -3266,6 +3266,75 @@ var DeviceEventPropagator = class {
|
|
|
3266
3266
|
}
|
|
3267
3267
|
};
|
|
3268
3268
|
//#endregion
|
|
3269
|
+
//#region src/builtins/device-manager/migrate-device.ts
|
|
3270
|
+
/** A refusal that names a switch the camera does not offer, rather than one it
|
|
3271
|
+
* could not be asked about. The cap documents rejecting the former; anything
|
|
3272
|
+
* else is treated as the camera being out of reach, which is the safe reading:
|
|
3273
|
+
* it keeps the switch in `sourceStillLive` instead of quietly clearing it. */
|
|
3274
|
+
function classify(err) {
|
|
3275
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3276
|
+
return {
|
|
3277
|
+
outcome: /does not offer|not available|unsupported/i.test(detail) ? "not-offered" : "unreachable",
|
|
3278
|
+
detail
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
async function applyAll(deps, deviceId, enabled) {
|
|
3282
|
+
const out = [];
|
|
3283
|
+
for (const switchId of CAMERA_SWITCH_ORDER) try {
|
|
3284
|
+
await deps.setSwitch(deviceId, switchId, enabled);
|
|
3285
|
+
out.push({
|
|
3286
|
+
deviceId,
|
|
3287
|
+
switchId,
|
|
3288
|
+
outcome: enabled ? "on" : "off"
|
|
3289
|
+
});
|
|
3290
|
+
} catch (err) {
|
|
3291
|
+
const { outcome, detail } = classify(err);
|
|
3292
|
+
out.push({
|
|
3293
|
+
deviceId,
|
|
3294
|
+
switchId,
|
|
3295
|
+
outcome,
|
|
3296
|
+
detail
|
|
3297
|
+
});
|
|
3298
|
+
deps.log("camera switch refused", {
|
|
3299
|
+
deviceId,
|
|
3300
|
+
switchId,
|
|
3301
|
+
enabled,
|
|
3302
|
+
outcome,
|
|
3303
|
+
detail
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
return out;
|
|
3307
|
+
}
|
|
3308
|
+
/**
|
|
3309
|
+
* Put both cameras to sleep, exchange their identities, wake the target.
|
|
3310
|
+
*
|
|
3311
|
+
* Throws only if the swap itself fails — a switch that could not be written is
|
|
3312
|
+
* reported, not raised, because refusing to migrate a camera whose old hardware
|
|
3313
|
+
* is unreachable would refuse the exact case this exists for.
|
|
3314
|
+
*/
|
|
3315
|
+
async function migrateDevice$1(deps, input) {
|
|
3316
|
+
const { sourceId, targetId } = input;
|
|
3317
|
+
if (sourceId === targetId) throw new Error(`[device-manager] migrateDevice: ${sourceId} is both source and target`);
|
|
3318
|
+
const down = [...await applyAll(deps, sourceId, false), ...await applyAll(deps, targetId, false)];
|
|
3319
|
+
const sourceStillLive = down.filter((r) => r.deviceId === sourceId && r.outcome === "unreachable").map((r) => r.switchId);
|
|
3320
|
+
await deps.swapIds(sourceId, targetId);
|
|
3321
|
+
const up = await applyAll(deps, sourceId, true);
|
|
3322
|
+
await deps.respawnRunners([sourceId, targetId]);
|
|
3323
|
+
deps.log("camera migrated", {
|
|
3324
|
+
sourceId,
|
|
3325
|
+
targetId,
|
|
3326
|
+
sourceStillLive,
|
|
3327
|
+
refusals: [...down, ...up].filter((r) => r.outcome === "unreachable").length
|
|
3328
|
+
});
|
|
3329
|
+
return {
|
|
3330
|
+
sourceId,
|
|
3331
|
+
targetId,
|
|
3332
|
+
switches: [...down, ...up],
|
|
3333
|
+
sourceStillLive,
|
|
3334
|
+
swapped: true
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
//#endregion
|
|
3269
3338
|
//#region src/builtins/device-manager/device-meta-actions.ts
|
|
3270
3339
|
/**
|
|
3271
3340
|
* Device meta-mutation + persistence actions for the device-manager addon —
|
|
@@ -3339,6 +3408,54 @@ async function allocateDeviceId(pctx, input) {
|
|
|
3339
3408
|
return { id };
|
|
3340
3409
|
});
|
|
3341
3410
|
}
|
|
3411
|
+
/**
|
|
3412
|
+
* Replace a camera: the new hardware inherits the number the system knows.
|
|
3413
|
+
*
|
|
3414
|
+
* The composition lives in `migrate-device.ts`, deliberately free of this
|
|
3415
|
+
* file's context so the decisions it carries — above all "never report a switch
|
|
3416
|
+
* as off when it could not be written" — are testable without a hub. This is
|
|
3417
|
+
* the wiring: the four real dependencies, and the write lock.
|
|
3418
|
+
*
|
|
3419
|
+
* The temp id comes from `allocateNextDeviceId`, the monotonic counter, so the
|
|
3420
|
+
* value a crash could park a row on was never used and will never be reused.
|
|
3421
|
+
* `swapIds` therefore runs INSIDE the same lock the id was allocated under.
|
|
3422
|
+
*/
|
|
3423
|
+
async function migrateDevice(pctx, input) {
|
|
3424
|
+
const { sourceId, targetId } = input;
|
|
3425
|
+
const ctx = pctx.host.ctx;
|
|
3426
|
+
return await pctx.metaStore.withMetaWriteLock(async () => {
|
|
3427
|
+
const tempId = await pctx.metaStore.allocateNextDeviceId();
|
|
3428
|
+
return await migrateDevice$1({
|
|
3429
|
+
setSwitch: async (deviceId, switchId, enabled) => {
|
|
3430
|
+
await ctx.api.pipelineOrchestrator.setCameraSwitch.mutate({
|
|
3431
|
+
deviceId,
|
|
3432
|
+
switchId,
|
|
3433
|
+
enabled
|
|
3434
|
+
});
|
|
3435
|
+
},
|
|
3436
|
+
swapIds: async (a, b) => {
|
|
3437
|
+
await pctx.metaStore.rows.swapIds(a, b, tempId);
|
|
3438
|
+
},
|
|
3439
|
+
respawnRunners: async (deviceIds) => {
|
|
3440
|
+
const owners = /* @__PURE__ */ new Set();
|
|
3441
|
+
for (const deviceId of deviceIds) {
|
|
3442
|
+
const addonId = pctx.metaStore.idToAddonId.get(deviceId);
|
|
3443
|
+
if (addonId !== void 0) owners.add(addonId);
|
|
3444
|
+
}
|
|
3445
|
+
for (const addonId of owners) await ctx.api.addons.restartAddon.mutate({ addonId });
|
|
3446
|
+
},
|
|
3447
|
+
log: (message, meta) => {
|
|
3448
|
+
ctx.logger.info(message, {
|
|
3449
|
+
tags: { deviceId: sourceId },
|
|
3450
|
+
meta
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
}, {
|
|
3454
|
+
sourceId,
|
|
3455
|
+
targetId
|
|
3456
|
+
});
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3342
3459
|
async function registerDevice(pctx, input) {
|
|
3343
3460
|
const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
|
|
3344
3461
|
const featuresArr = Array.isArray(features) ? [...features] : [];
|
|
@@ -5146,6 +5263,86 @@ var DeviceRowStore = class {
|
|
|
5146
5263
|
});
|
|
5147
5264
|
}
|
|
5148
5265
|
/** Drop the device's row. Idempotent. */
|
|
5266
|
+
/**
|
|
5267
|
+
* Exchange the ids of two devices, carrying their children with them.
|
|
5268
|
+
*
|
|
5269
|
+
* ## Why this exists as a primitive
|
|
5270
|
+
*
|
|
5271
|
+
* Replacing a camera means the new hardware has to become the device the rest
|
|
5272
|
+
* of the system already knows. Everything general about a camera is keyed on
|
|
5273
|
+
* the NUMERIC id and on nothing else — the recording path on disk is
|
|
5274
|
+
* `<deviceId>/profile/…`, the hour ledger, the media store and zone ownership
|
|
5275
|
+
* carry `deviceId` and not one `stableId` between them, and all four
|
|
5276
|
+
* ecosystem exports (Home Assistant, Alexa, HomeKit, Google) key on it too.
|
|
5277
|
+
* Moving that work is the expensive, lossy operation. Moving the NUMBER is
|
|
5278
|
+
* two rows.
|
|
5279
|
+
*
|
|
5280
|
+
* ## Children are re-pointed, not renumbered
|
|
5281
|
+
*
|
|
5282
|
+
* A child keeps its own id and follows its parent: every row whose
|
|
5283
|
+
* `parentDeviceId` was `a` now names `b`, and the reverse. Renumbering the
|
|
5284
|
+
* children as well would move THEIR general work for no reason — the
|
|
5285
|
+
* accessory's own recordings and rules are keyed on the child id, which is
|
|
5286
|
+
* not the id being inherited.
|
|
5287
|
+
*
|
|
5288
|
+
* ## There are no transactions here, so the ORDER is the safety
|
|
5289
|
+
*
|
|
5290
|
+
* The backing store is a collection API (`set` / `delete` / `updateWhere`);
|
|
5291
|
+
* there is no rollback to lean on. So the sequence is chosen to make every
|
|
5292
|
+
* crash point LOUD rather than plausible: `a` first moves to a freshly
|
|
5293
|
+
* allocated id, which the monotonic counter guarantees was never used and
|
|
5294
|
+
* will never be reused. A crash before the end therefore leaves a device
|
|
5295
|
+
* missing from its number and a stray at an unfamiliar one — visible in the
|
|
5296
|
+
* device list at a glance. What it can never leave is two rows on one number,
|
|
5297
|
+
* or a row that looks correct while its children point elsewhere.
|
|
5298
|
+
*
|
|
5299
|
+
* The caller holds {@link DeviceMetaStore.withMetaWriteLock}. This method does
|
|
5300
|
+
* not take it: taking it here would deadlock the migration that has to disable
|
|
5301
|
+
* functions on both devices in the same critical section.
|
|
5302
|
+
*/
|
|
5303
|
+
async swapIds(a, b, tempId) {
|
|
5304
|
+
if (a === b) throw new Error(`[device-manager] swapIds: ${a} and ${b} are the same device`);
|
|
5305
|
+
await this.declare();
|
|
5306
|
+
const rowA = await this.get(a);
|
|
5307
|
+
const rowB = await this.get(b);
|
|
5308
|
+
if (rowA === null) throw new Error(`[device-manager] swapIds: device ${a} does not exist`);
|
|
5309
|
+
if (rowB === null) throw new Error(`[device-manager] swapIds: device ${b} does not exist`);
|
|
5310
|
+
if (await this.get(tempId) !== null) throw new Error(`[device-manager] swapIds: temp id ${tempId} is taken`);
|
|
5311
|
+
const childrenOfA = (await this.listByParent(a)).map((r) => r.meta.id);
|
|
5312
|
+
const childrenOfB = (await this.listByParent(b)).map((r) => r.meta.id);
|
|
5313
|
+
await this.moveRow(rowA, tempId);
|
|
5314
|
+
await this.moveRow(rowB, a);
|
|
5315
|
+
await this.moveRow({
|
|
5316
|
+
...rowA,
|
|
5317
|
+
meta: {
|
|
5318
|
+
...rowA.meta,
|
|
5319
|
+
id: tempId
|
|
5320
|
+
}
|
|
5321
|
+
}, b);
|
|
5322
|
+
for (const childId of childrenOfA) await this.patch(childId, { parentDeviceId: b });
|
|
5323
|
+
for (const childId of childrenOfB) await this.patch(childId, { parentDeviceId: a });
|
|
5324
|
+
this.logger.info("device ids swapped", {
|
|
5325
|
+
tags: { deviceId: a },
|
|
5326
|
+
meta: {
|
|
5327
|
+
a,
|
|
5328
|
+
b,
|
|
5329
|
+
childrenOfA,
|
|
5330
|
+
childrenOfB
|
|
5331
|
+
}
|
|
5332
|
+
});
|
|
5333
|
+
}
|
|
5334
|
+
/** Rewrite a row under a new id and drop the old key. Not exported: the only
|
|
5335
|
+
* legitimate reason to move a row is {@link swapIds}. */
|
|
5336
|
+
async moveRow(row, toId) {
|
|
5337
|
+
await this.upsert({
|
|
5338
|
+
...row.meta,
|
|
5339
|
+
id: toId
|
|
5340
|
+
}, {
|
|
5341
|
+
registered: row.registered,
|
|
5342
|
+
metadata: row.metadata
|
|
5343
|
+
});
|
|
5344
|
+
await this.remove(row.meta.id);
|
|
5345
|
+
}
|
|
5149
5346
|
async remove(deviceId) {
|
|
5150
5347
|
await this.declare();
|
|
5151
5348
|
await this.backend.delete({
|
|
@@ -5866,6 +6063,7 @@ var DeviceManagerAddon = class extends BaseAddon {
|
|
|
5866
6063
|
resolveDeviceOwnerSync: (deviceId) => resolveDeviceOwnerSync(pctx, deviceId),
|
|
5867
6064
|
resolveNativeCapOwnerSync: (capName, deviceId) => resolveNativeCapOwnerSync(pctx, capName, deviceId),
|
|
5868
6065
|
allocateDeviceId: (input) => allocateDeviceId(pctx, input),
|
|
6066
|
+
migrateDevice: (input) => migrateDevice(pctx, input),
|
|
5869
6067
|
registerDevice: (input) => registerDevice(pctx, input),
|
|
5870
6068
|
removeDevice: (input) => removeDevice(pctx, input),
|
|
5871
6069
|
persistConfig: (input) => persistConfig(pctx, input),
|
|
@@ -13,6 +13,19 @@ type IDeviceManagerProvider = InferProvider<typeof deviceManagerCapability>;
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function stampIntegrationId(metaStore: DeviceMetaStore, ctx: AddonContext, deviceId: number, integrationId: string): Promise<void>;
|
|
15
15
|
export declare function allocateDeviceId(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['allocateDeviceId']>[0]): ReturnType<IDeviceManagerProvider['allocateDeviceId']>;
|
|
16
|
+
/**
|
|
17
|
+
* Replace a camera: the new hardware inherits the number the system knows.
|
|
18
|
+
*
|
|
19
|
+
* The composition lives in `migrate-device.ts`, deliberately free of this
|
|
20
|
+
* file's context so the decisions it carries — above all "never report a switch
|
|
21
|
+
* as off when it could not be written" — are testable without a hub. This is
|
|
22
|
+
* the wiring: the four real dependencies, and the write lock.
|
|
23
|
+
*
|
|
24
|
+
* The temp id comes from `allocateNextDeviceId`, the monotonic counter, so the
|
|
25
|
+
* value a crash could park a row on was never used and will never be reused.
|
|
26
|
+
* `swapIds` therefore runs INSIDE the same lock the id was allocated under.
|
|
27
|
+
*/
|
|
28
|
+
export declare function migrateDevice(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['migrateDevice']>[0]): ReturnType<IDeviceManagerProvider['migrateDevice']>;
|
|
16
29
|
export declare function registerDevice(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['registerDevice']>[0]): ReturnType<IDeviceManagerProvider['registerDevice']>;
|
|
17
30
|
export declare function removeDevice(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['removeDevice']>[0]): ReturnType<IDeviceManagerProvider['removeDevice']>;
|
|
18
31
|
export declare function persistConfig(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['persistConfig']>[0]): ReturnType<IDeviceManagerProvider['persistConfig']>;
|
|
@@ -340,6 +340,47 @@ export declare class DeviceRowStore {
|
|
|
340
340
|
*/
|
|
341
341
|
upsertRegistration(row: DeviceRegistrationRow): Promise<void>;
|
|
342
342
|
/** Drop the device's row. Idempotent. */
|
|
343
|
+
/**
|
|
344
|
+
* Exchange the ids of two devices, carrying their children with them.
|
|
345
|
+
*
|
|
346
|
+
* ## Why this exists as a primitive
|
|
347
|
+
*
|
|
348
|
+
* Replacing a camera means the new hardware has to become the device the rest
|
|
349
|
+
* of the system already knows. Everything general about a camera is keyed on
|
|
350
|
+
* the NUMERIC id and on nothing else — the recording path on disk is
|
|
351
|
+
* `<deviceId>/profile/…`, the hour ledger, the media store and zone ownership
|
|
352
|
+
* carry `deviceId` and not one `stableId` between them, and all four
|
|
353
|
+
* ecosystem exports (Home Assistant, Alexa, HomeKit, Google) key on it too.
|
|
354
|
+
* Moving that work is the expensive, lossy operation. Moving the NUMBER is
|
|
355
|
+
* two rows.
|
|
356
|
+
*
|
|
357
|
+
* ## Children are re-pointed, not renumbered
|
|
358
|
+
*
|
|
359
|
+
* A child keeps its own id and follows its parent: every row whose
|
|
360
|
+
* `parentDeviceId` was `a` now names `b`, and the reverse. Renumbering the
|
|
361
|
+
* children as well would move THEIR general work for no reason — the
|
|
362
|
+
* accessory's own recordings and rules are keyed on the child id, which is
|
|
363
|
+
* not the id being inherited.
|
|
364
|
+
*
|
|
365
|
+
* ## There are no transactions here, so the ORDER is the safety
|
|
366
|
+
*
|
|
367
|
+
* The backing store is a collection API (`set` / `delete` / `updateWhere`);
|
|
368
|
+
* there is no rollback to lean on. So the sequence is chosen to make every
|
|
369
|
+
* crash point LOUD rather than plausible: `a` first moves to a freshly
|
|
370
|
+
* allocated id, which the monotonic counter guarantees was never used and
|
|
371
|
+
* will never be reused. A crash before the end therefore leaves a device
|
|
372
|
+
* missing from its number and a stray at an unfamiliar one — visible in the
|
|
373
|
+
* device list at a glance. What it can never leave is two rows on one number,
|
|
374
|
+
* or a row that looks correct while its children point elsewhere.
|
|
375
|
+
*
|
|
376
|
+
* The caller holds {@link DeviceMetaStore.withMetaWriteLock}. This method does
|
|
377
|
+
* not take it: taking it here would deadlock the migration that has to disable
|
|
378
|
+
* functions on both devices in the same critical section.
|
|
379
|
+
*/
|
|
380
|
+
swapIds(a: number, b: number, tempId: number): Promise<void>;
|
|
381
|
+
/** Rewrite a row under a new id and drop the old key. Not exported: the only
|
|
382
|
+
* legitimate reason to move a row is {@link swapIds}. */
|
|
383
|
+
private moveRow;
|
|
343
384
|
remove(deviceId: number): Promise<void>;
|
|
344
385
|
/**
|
|
345
386
|
* The retirement door for the three blobs this collection replaced.
|