@camstack/system 1.2.314 → 1.2.315
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/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/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.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/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer-plan.d.ts +8 -1
- package/dist/builtins/composer/composer.addon.js +191 -29
- package/dist/builtins/composer/composer.addon.mjs +195 -33
- package/dist/builtins/composer/composer.d.ts +5 -1
- package/dist/builtins/composer/composition-runtime.d.ts +10 -1
- package/dist/builtins/composer/existing-target.d.ts +9 -0
- package/dist/builtins/composer/field-record.d.ts +12 -3
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/composition-sources.d.ts +3 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +3 -103
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -102
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/binding-mirror.d.ts +1 -0
- package/dist/builtins/doorbell/doorbell-composition-migration.d.ts +59 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +3 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +315 -8
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +315 -8
- 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/composition-sources-DYqQLRzF.js +122 -0
- package/dist/composition-sources-yonsRneP.mjs +111 -0
- package/dist/{dist-BtNkk3Qz.js → dist-DVKv5i-e.js} +611 -299
- package/dist/{dist-cH4t1ZEm.mjs → dist-MzYJVCLE.mjs} +582 -300
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-DQYHfZ0S.mjs → retired-settings-keys-8yY2sTc-.mjs} +1 -1
- package/dist/{retired-settings-keys-B7imb-qH.js → retired-settings-keys-iY5I0nKM.js} +1 -1
- package/package.json +1 -1
|
@@ -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-DVKv5i-e.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 { Gn as BaseAddon, R as addonPagesCapability, Wn as errMsg, hr as EventCategory } from "../../dist-MzYJVCLE.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-DVKv5i-e.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 { B as addonWidgetsCapability, Gn as BaseAddon, Wn as errMsg, hr as EventCategory } from "../../dist-MzYJVCLE.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-DVKv5i-e.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 { Gn as BaseAddon, V as alertsCapability, Wn as errMsg, hr as EventCategory, n as AlertSchema, tr as createEvent } from "../../dist-MzYJVCLE.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
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DVKv5i-e.js");
|
|
7
7
|
//#region src/builtins/autotrack/autotrack-config.ts
|
|
8
8
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
9
9
|
deadZoneFraction: .5,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $t as ptzAutotrackCapability, Gn as BaseAddon, W as bareAddonId, Wn as errMsg, _ as FIRST_LEVEL_MACRO_CLASSES, hr as EventCategory, sr as isEvent } from "../../dist-MzYJVCLE.mjs";
|
|
2
2
|
//#region src/builtins/autotrack/autotrack-config.ts
|
|
3
3
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
4
4
|
deadZoneFraction: .5,
|
|
@@ -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-DVKv5i-e.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 { Gn as BaseAddon, Ht as mayWriteToLocation, U as backupCapability, hr as EventCategory, mn as storageOccupancyCapability } from "../../dist-MzYJVCLE.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-DVKv5i-e.js");
|
|
7
7
|
let zod = require("zod");
|
|
8
8
|
let _camstack_types_node = require("@camstack/types/node");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Gn as BaseAddon, Gt as motionCapability, J as cameraGridLayoutCapability, T as MOTION_CLOSE_AFTER_MS, Un as DeviceType, Vn as DeviceFeature, Wn as errMsg, ar as hydrateSchema, dn as snapshotCapability, gn as streamCatalogCapability, h as DeclaredDevices, hr as EventCategory, i as BaseDevice, kn as buildFfmpegArgs, m as DERIVED_CAMERA_SILENCED_CAP_NAMES, ot as declarationOwnerNodeId, tr as createEvent } from "../../dist-MzYJVCLE.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { FfmpegProcess } from "@camstack/types/node";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CapabilityLookup, ComposerBlock, ComposerUnreadableBlock, CompositionExistingTarget, CompositionFeaturePlan, CompositionFieldPlan, CompositionNewTarget, CompositionSourceRef, DeviceDeclaration, DeviceType, IScopedLogger, LogTags } from '@camstack/types';
|
|
1
|
+
import { CapabilityLookup, ComposerBlock, ComposerUnreadableBlock, CompositionExistingTarget, CompositionEventPlan, CompositionFeaturePlan, CompositionFieldPlan, CompositionNewTarget, CompositionSourceRef, DeviceDeclaration, DeviceType, IScopedLogger, LogTags } from '@camstack/types';
|
|
2
2
|
import { TargetDescriptions } from './existing-target.js';
|
|
3
3
|
import { SourceResolution } from './source-tracker.js';
|
|
4
4
|
/** What the apply knows of a block besides its plan. `name` is null for an unreadable row whose name is unreadable too. */
|
|
@@ -36,6 +36,7 @@ export type PlannedTarget = PlannedNewTarget | PlannedUnreadableTarget | Planned
|
|
|
36
36
|
export interface PlannedBlock {
|
|
37
37
|
readonly block: PlannedBlockMeta;
|
|
38
38
|
readonly fields: readonly CompositionFieldPlan[];
|
|
39
|
+
readonly events: readonly CompositionEventPlan[];
|
|
39
40
|
readonly problems: readonly string[];
|
|
40
41
|
readonly declaration: DeviceDeclaration | null;
|
|
41
42
|
/** What forces a rebuild of the live device; null when nothing is declared. */
|
|
@@ -71,6 +72,12 @@ export interface ExistingPlanInput {
|
|
|
71
72
|
readonly lookupCap: CapabilityLookup;
|
|
72
73
|
}
|
|
73
74
|
export declare function planExisting(input: ExistingPlanInput): Promise<PlannedBlock>;
|
|
75
|
+
/**
|
|
76
|
+
* Runtime preparation can still fail after a complete existing-target plan.
|
|
77
|
+
* Preserve its fields and description for retry, but mark it waiting so claim
|
|
78
|
+
* reconciliation cannot treat the unprepared candidate as live.
|
|
79
|
+
*/
|
|
80
|
+
export declare function withExistingTargetWait(p: PlannedBlock, waiting: string): PlannedBlock;
|
|
74
81
|
/** A target the previous plan held live on `deviceId`, that this plan finds gone. */
|
|
75
82
|
export interface LostTarget {
|
|
76
83
|
readonly target: PlannedExistingTarget;
|
|
@@ -3,8 +3,9 @@ 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-DVKv5i-e.js");
|
|
7
7
|
const require_device_state_changed = require("../../device-state-changed-Bk7cvTnz.js");
|
|
8
|
+
const require_composition_sources = require("../../composition-sources-DYqQLRzF.js");
|
|
8
9
|
//#region src/builtins/composer/composed-device.ts
|
|
9
10
|
/**
|
|
10
11
|
* A composed device: an ordinary `BaseDevice` whose caps are served by the
|
|
@@ -423,6 +424,7 @@ var WithdrawalGate = class {
|
|
|
423
424
|
var PLACEHOLDER_SHAPE = "placeholder";
|
|
424
425
|
var NO_PLAN = {
|
|
425
426
|
fields: [],
|
|
427
|
+
events: [],
|
|
426
428
|
problems: [],
|
|
427
429
|
declaration: null,
|
|
428
430
|
shape: null,
|
|
@@ -469,6 +471,7 @@ function planNew(block, target, lookupCap, rows) {
|
|
|
469
471
|
};
|
|
470
472
|
}
|
|
471
473
|
const caps = block.composition.features.flatMap((f) => {
|
|
474
|
+
if (!require_dist.compositionFeatureEnabled(f)) return [];
|
|
472
475
|
const def = lookupCap(f.cap);
|
|
473
476
|
return def === null ? [] : [def];
|
|
474
477
|
});
|
|
@@ -481,6 +484,7 @@ function planNew(block, target, lookupCap, rows) {
|
|
|
481
484
|
return {
|
|
482
485
|
block,
|
|
483
486
|
fields: plan.fields,
|
|
487
|
+
events: plan.events,
|
|
484
488
|
problems: [],
|
|
485
489
|
declaration: declarationFor(declarationInput),
|
|
486
490
|
shape: shapeOf(declarationInput),
|
|
@@ -528,6 +532,7 @@ async function planExisting(input) {
|
|
|
528
532
|
...NO_PLAN,
|
|
529
533
|
block,
|
|
530
534
|
fields: plan.problems.length > 0 ? [] : plan.fields,
|
|
535
|
+
events: plan.problems.length > 0 ? [] : plan.events,
|
|
531
536
|
problems: plan.problems.map((p) => `${p.path}: ${p.message}`),
|
|
532
537
|
target: {
|
|
533
538
|
...existing,
|
|
@@ -551,6 +556,20 @@ function waitingPlan(block, ref, deviceId, waiting) {
|
|
|
551
556
|
}
|
|
552
557
|
};
|
|
553
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* Runtime preparation can still fail after a complete existing-target plan.
|
|
561
|
+
* Preserve its fields and description for retry, but mark it waiting so claim
|
|
562
|
+
* reconciliation cannot treat the unprepared candidate as live.
|
|
563
|
+
*/
|
|
564
|
+
function withExistingTargetWait(p, waiting) {
|
|
565
|
+
return p.target.kind === "existing" ? {
|
|
566
|
+
...p,
|
|
567
|
+
target: {
|
|
568
|
+
...p.target,
|
|
569
|
+
waiting
|
|
570
|
+
}
|
|
571
|
+
} : p;
|
|
572
|
+
}
|
|
554
573
|
/** What `logPlanTransition` compares: problems, and for an existing target its wait or its loss. */
|
|
555
574
|
function planSummary(p) {
|
|
556
575
|
const target = p.target;
|
|
@@ -628,7 +647,7 @@ function logPlanTransition(logger, previous, p, liveTags) {
|
|
|
628
647
|
* is thrown by name, and the runtime's write path turns a throw into a named,
|
|
629
648
|
* retried failure — never a half-built array.
|
|
630
649
|
*/
|
|
631
|
-
function isRecord(value) {
|
|
650
|
+
function isRecord$1(value) {
|
|
632
651
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
633
652
|
}
|
|
634
653
|
/** `setPath(item, ['remaining', 'value'], 0)` → `{ ...item, remaining: { ...item.remaining, value: 0 } }`. */
|
|
@@ -642,7 +661,7 @@ function setPath(record, segments, value) {
|
|
|
642
661
|
const current = record[head];
|
|
643
662
|
return {
|
|
644
663
|
...record,
|
|
645
|
-
[head]: setPath(isRecord(current) ? current : {}, rest, value)
|
|
664
|
+
[head]: setPath(isRecord$1(current) ? current : {}, rest, value)
|
|
646
665
|
};
|
|
647
666
|
}
|
|
648
667
|
function itemArrayOf(group, lookupCap) {
|
|
@@ -705,6 +724,38 @@ var NO_FIELDS = /* @__PURE__ */ new Set();
|
|
|
705
724
|
function fieldKey(plan) {
|
|
706
725
|
return require_dist.compositionFieldKey(plan);
|
|
707
726
|
}
|
|
727
|
+
function isRecord(value) {
|
|
728
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
729
|
+
}
|
|
730
|
+
function valueAtPath$1(value, path) {
|
|
731
|
+
return path.split(".").reduce((node, segment) => isRecord(node) ? node[segment] : void 0, value);
|
|
732
|
+
}
|
|
733
|
+
function initialValue(plan, byCap, lookupCap) {
|
|
734
|
+
const slice = byCap?.get(plan.cap);
|
|
735
|
+
if (slice === void 0 || slice === null) return void 0;
|
|
736
|
+
const item = plan.item;
|
|
737
|
+
if (item === void 0) return valueAtPath$1(slice, plan.path);
|
|
738
|
+
const itemArray = lookupCap(plan.cap)?.status?.itemArray;
|
|
739
|
+
if (itemArray === void 0 || itemArray.path !== item.arrayPath) return void 0;
|
|
740
|
+
const entries = valueAtPath$1(slice, item.arrayPath);
|
|
741
|
+
if (!Array.isArray(entries)) return void 0;
|
|
742
|
+
return valueAtPath$1(entries.find((entry) => isRecord(entry) && entry[itemArray.keyField] === item.key), plan.path);
|
|
743
|
+
}
|
|
744
|
+
function createFieldRecord(plan, byCap, lookupCap) {
|
|
745
|
+
const initial = initialValue(plan, byCap, lookupCap);
|
|
746
|
+
return {
|
|
747
|
+
plan,
|
|
748
|
+
outcome: null,
|
|
749
|
+
initial,
|
|
750
|
+
hasWritten: false,
|
|
751
|
+
written: void 0,
|
|
752
|
+
expressionMemory: require_dist.createStatefulExpressionMemory(),
|
|
753
|
+
eventHighWater: typeof initial === "number" && Number.isFinite(initial) ? initial : null
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function currentOutput(record) {
|
|
757
|
+
return record.hasWritten ? record.written : record.initial;
|
|
758
|
+
}
|
|
708
759
|
/** The top-level field a record writes: an item leaf writes its whole array. */
|
|
709
760
|
function topLevelField(plan) {
|
|
710
761
|
return plan.item === void 0 ? plan.path : plan.item.arrayPath;
|
|
@@ -816,6 +867,11 @@ function pendingReason(cap, pending) {
|
|
|
816
867
|
* - A target whose writes are asynchronous (claims on the hub) reports what did
|
|
817
868
|
* not land through `onAsyncFailure`: the cap is marked write-pending by name
|
|
818
869
|
* and re-written whole on the next pass — no retry timer (D3).
|
|
870
|
+
*
|
|
871
|
+
* Slice 2b (D670): stateful expression memory commits only when the whole
|
|
872
|
+
* expression succeeds. A declared state-derived event advances its seeded
|
|
873
|
+
* high-water and is emitted before the corresponding target write; neither an
|
|
874
|
+
* event-bus failure nor a refused write can replay the event.
|
|
819
875
|
*/
|
|
820
876
|
/**
|
|
821
877
|
* A refusal nothing on this side can retry away: another owner holds the
|
|
@@ -828,9 +884,13 @@ var TERMINAL_REFUSALS = new Set([
|
|
|
828
884
|
"invalid-slice",
|
|
829
885
|
"unknown-device"
|
|
830
886
|
]);
|
|
887
|
+
function strictFiniteAdvance(previous, next) {
|
|
888
|
+
return typeof next === "number" && Number.isFinite(next) && (previous === null || next > previous);
|
|
889
|
+
}
|
|
831
890
|
var CompositionRuntime = class {
|
|
832
891
|
deps;
|
|
833
892
|
records;
|
|
893
|
+
startedAtMs;
|
|
834
894
|
seeded = /* @__PURE__ */ new Set();
|
|
835
895
|
available = null;
|
|
836
896
|
failure = null;
|
|
@@ -852,11 +912,8 @@ var CompositionRuntime = class {
|
|
|
852
912
|
terminal = /* @__PURE__ */ new Map();
|
|
853
913
|
constructor(deps) {
|
|
854
914
|
this.deps = deps;
|
|
855
|
-
this.
|
|
856
|
-
|
|
857
|
-
outcome: null,
|
|
858
|
-
written: void 0
|
|
859
|
-
}]));
|
|
915
|
+
this.startedAtMs = deps.now();
|
|
916
|
+
this.records = new Map(deps.fields.map((plan) => [fieldKey(plan), createFieldRecord(plan, deps.initialByCap, deps.lookupCap)]));
|
|
860
917
|
deps.target.onAsyncFailure?.((cap, failure) => this.onTargetWriteFailed(cap, failure));
|
|
861
918
|
}
|
|
862
919
|
start() {
|
|
@@ -1015,15 +1072,18 @@ var CompositionRuntime = class {
|
|
|
1015
1072
|
pass(keys, trigger) {
|
|
1016
1073
|
const initial = trigger === "start";
|
|
1017
1074
|
const now = this.deps.now();
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1075
|
+
const calculated = this.evaluate(keys.filter((k) => {
|
|
1076
|
+
const role = this.records.get(k)?.plan.role;
|
|
1077
|
+
return role === "derived" || role === "accumulator";
|
|
1078
|
+
}), now);
|
|
1079
|
+
const followers = [...this.records.values()].filter((r) => r.plan.role === "clock" ? initial || calculated.valueCaps.has(r.plan.cap) : r.plan.role === "constant" && initial).map((r) => fieldKey(r.plan));
|
|
1020
1080
|
const extra = this.evaluate(followers, now);
|
|
1021
1081
|
const evaluatedCaps = new Set([...keys, ...followers].flatMap((k) => {
|
|
1022
1082
|
const cap = this.records.get(k)?.plan.cap;
|
|
1023
1083
|
return cap === void 0 ? [] : [cap];
|
|
1024
1084
|
}));
|
|
1025
1085
|
const retry = new Set([...this.pending.keys()].filter((cap) => trigger === "deadline" || evaluatedCaps.has(cap)));
|
|
1026
|
-
this.writeCaps(new Set([...
|
|
1086
|
+
this.writeCaps(new Set([...calculated.keys, ...extra.keys]), retry);
|
|
1027
1087
|
this.updateFailure();
|
|
1028
1088
|
this.updateAvailability();
|
|
1029
1089
|
}
|
|
@@ -1035,15 +1095,35 @@ var CompositionRuntime = class {
|
|
|
1035
1095
|
for (const key of keys) {
|
|
1036
1096
|
const record = next.get(key);
|
|
1037
1097
|
if (record === void 0) continue;
|
|
1098
|
+
const candidateMemory = require_dist.cloneStatefulExpressionMemory(record.expressionMemory);
|
|
1038
1099
|
const outcome = require_dist.evaluateCompositionField({
|
|
1039
1100
|
source: record.plan.source,
|
|
1040
1101
|
read: this.deps.read,
|
|
1041
|
-
now
|
|
1102
|
+
now,
|
|
1103
|
+
stateful: {
|
|
1104
|
+
currentOutput: currentOutput(record),
|
|
1105
|
+
expressionMemory: candidateMemory,
|
|
1106
|
+
startedAtMs: this.startedAtMs,
|
|
1107
|
+
callPathPrefix: fieldKey(record.plan)
|
|
1108
|
+
}
|
|
1042
1109
|
});
|
|
1043
|
-
|
|
1110
|
+
const expressionSucceeded = outcome.state !== "unavailable";
|
|
1111
|
+
let evaluated = {
|
|
1044
1112
|
...record,
|
|
1045
|
-
outcome
|
|
1046
|
-
|
|
1113
|
+
outcome,
|
|
1114
|
+
...expressionSucceeded ? { expressionMemory: candidateMemory } : {}
|
|
1115
|
+
};
|
|
1116
|
+
const eventPlans = this.deps.eventPlans.filter((eventPlan) => eventPlan.fieldKey === fieldKey(record.plan));
|
|
1117
|
+
if (expressionSucceeded && eventPlans.length > 0 && strictFiniteAdvance(record.eventHighWater, outcome.value)) {
|
|
1118
|
+
const timestamp = outcome.value;
|
|
1119
|
+
evaluated = {
|
|
1120
|
+
...evaluated,
|
|
1121
|
+
eventHighWater: timestamp
|
|
1122
|
+
};
|
|
1123
|
+
next.set(key, evaluated);
|
|
1124
|
+
this.records = next;
|
|
1125
|
+
for (const eventPlan of eventPlans) this.emitStateEvent(eventPlan, timestamp);
|
|
1126
|
+
} else next.set(key, evaluated);
|
|
1047
1127
|
if (sameOutcome(record.outcome, outcome)) continue;
|
|
1048
1128
|
if (record.outcome?.state !== outcome.state) this.logTransition(record, outcome);
|
|
1049
1129
|
if (valueOf(record.outcome) !== valueOf(outcome)) valueCaps.add(record.plan.cap);
|
|
@@ -1057,6 +1137,39 @@ var CompositionRuntime = class {
|
|
|
1057
1137
|
valueCaps
|
|
1058
1138
|
};
|
|
1059
1139
|
}
|
|
1140
|
+
emitStateEvent(plan, timestamp) {
|
|
1141
|
+
const extras = {
|
|
1142
|
+
tags: { deviceId: this.deps.target.id },
|
|
1143
|
+
meta: {
|
|
1144
|
+
blockId: this.deps.blockId,
|
|
1145
|
+
cap: plan.cap,
|
|
1146
|
+
eventName: plan.eventName,
|
|
1147
|
+
category: plan.category,
|
|
1148
|
+
timestamp
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
try {
|
|
1152
|
+
if (plan.category !== require_dist.EventCategory.DoorbellOnPressed) throw new Error(`unsupported state-derived event category \`${plan.category}\``);
|
|
1153
|
+
const event = require_dist.createEvent(require_dist.EventCategory.DoorbellOnPressed, {
|
|
1154
|
+
type: "device",
|
|
1155
|
+
id: this.deps.target.id,
|
|
1156
|
+
deviceId: this.deps.target.id
|
|
1157
|
+
}, {
|
|
1158
|
+
deviceId: this.deps.target.id,
|
|
1159
|
+
timestamp
|
|
1160
|
+
});
|
|
1161
|
+
this.deps.events.emit(event);
|
|
1162
|
+
this.deps.logger.debug("composition: state-derived event emitted", extras);
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
this.deps.logger.warn("composition: state-derived event delivery failed", {
|
|
1165
|
+
...extras,
|
|
1166
|
+
meta: {
|
|
1167
|
+
...extras.meta,
|
|
1168
|
+
error: errorText(error)
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1060
1173
|
recordsOf(cap) {
|
|
1061
1174
|
return [...this.records.values()].filter((r) => r.plan.cap === cap);
|
|
1062
1175
|
}
|
|
@@ -1085,8 +1198,9 @@ var CompositionRuntime = class {
|
|
|
1085
1198
|
const records = this.recordsOf(cap);
|
|
1086
1199
|
this.logReleaseTransitions(cap, records);
|
|
1087
1200
|
const releasedRecords = records.filter(isReleased);
|
|
1088
|
-
this.records = new Map([...this.records].map(([k, r]) => releasedRecords.includes(r) && r.
|
|
1201
|
+
this.records = new Map([...this.records].map(([k, r]) => releasedRecords.includes(r) && r.hasWritten ? [k, {
|
|
1089
1202
|
...r,
|
|
1203
|
+
hasWritten: false,
|
|
1090
1204
|
written: void 0
|
|
1091
1205
|
}] : [k, r]));
|
|
1092
1206
|
const withhold = this.deps.target.withhold;
|
|
@@ -1148,7 +1262,7 @@ var CompositionRuntime = class {
|
|
|
1148
1262
|
this.markWritten(cap, entries);
|
|
1149
1263
|
}
|
|
1150
1264
|
patchCap(cap, touched) {
|
|
1151
|
-
const entries = this.entriesFor(cap, this.recordsOf(cap).filter((r) => touched.has(fieldKey(r.plan)) && writable(r) !== r.written));
|
|
1265
|
+
const entries = this.entriesFor(cap, this.recordsOf(cap).filter((r) => touched.has(fieldKey(r.plan)) && (!r.hasWritten || writable(r) !== r.written)));
|
|
1152
1266
|
if (entries.length === 0) return;
|
|
1153
1267
|
if (this.write(cap, "patch", entries)) this.markWritten(cap, entries);
|
|
1154
1268
|
}
|
|
@@ -1280,6 +1394,7 @@ var CompositionRuntime = class {
|
|
|
1280
1394
|
const written = new Map(entries.map((e) => [fieldKey(e.plan), e.value]));
|
|
1281
1395
|
this.records = new Map([...this.records].map(([k, r]) => r.plan.cap === cap && written.has(k) ? [k, {
|
|
1282
1396
|
...r,
|
|
1397
|
+
hasWritten: true,
|
|
1283
1398
|
written: written.get(k)
|
|
1284
1399
|
}] : [k, r]));
|
|
1285
1400
|
}
|
|
@@ -1417,6 +1532,16 @@ var ConfirmedSeed = class {
|
|
|
1417
1532
|
* is NOT decided here from the plan: it follows whether the claim has landed
|
|
1418
1533
|
* (`HeldClaims`, C-1).
|
|
1419
1534
|
*/
|
|
1535
|
+
/**
|
|
1536
|
+
* Read every affected effective capability exactly once, before the composer
|
|
1537
|
+
* grafts or claims any of them. A null slice is a successful empty seed; a
|
|
1538
|
+
* rejection is deliberately left to the caller so ownership cannot begin.
|
|
1539
|
+
*/
|
|
1540
|
+
async function readExistingTargetSeeds(targetDeviceId, caps, read) {
|
|
1541
|
+
const byCap = /* @__PURE__ */ new Map();
|
|
1542
|
+
for (const cap of new Set(caps)) byCap.set(cap, await read(targetDeviceId, cap));
|
|
1543
|
+
return { byCap };
|
|
1544
|
+
}
|
|
1420
1545
|
/** The reason every ungraft of a gone target carries (Task 9 rule 5 / Task 10 rule 2). */
|
|
1421
1546
|
var TARGET_GONE_UNGRAFT_REASON = "target device gone";
|
|
1422
1547
|
/**
|
|
@@ -1750,14 +1875,17 @@ function verdictOf(blockId, entry, unsynced, grafts = null) {
|
|
|
1750
1875
|
reason: "the composed device has not been constructed yet"
|
|
1751
1876
|
};
|
|
1752
1877
|
const runtime = entry.runtime;
|
|
1753
|
-
if (runtime === null)
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1878
|
+
if (runtime === null) {
|
|
1879
|
+
if (target.kind !== "existing" || target.features.length === 0) return {
|
|
1880
|
+
blockId,
|
|
1881
|
+
status: "running"
|
|
1882
|
+
};
|
|
1883
|
+
return {
|
|
1884
|
+
blockId,
|
|
1885
|
+
status: "starting",
|
|
1886
|
+
reason: NOT_STARTED
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1761
1889
|
if (unsynced) return {
|
|
1762
1890
|
blockId,
|
|
1763
1891
|
status: "starting",
|
|
@@ -2365,8 +2493,30 @@ var Composer = class {
|
|
|
2365
2493
|
});
|
|
2366
2494
|
continue;
|
|
2367
2495
|
}
|
|
2496
|
+
let targetSeed;
|
|
2497
|
+
if (p.target.kind === "existing" && p.target.deviceId !== null && p.target.waiting === null && p.target.gone === null && p.block.enabled && p.fields.length > 0) try {
|
|
2498
|
+
targetSeed = await readExistingTargetSeeds(p.target.deviceId, p.fields.map((field) => field.cap), this.deps.readExistingTargetCap);
|
|
2499
|
+
} catch (err) {
|
|
2500
|
+
const reason = `the target device state could not be read before ownership (${require_dist.errMsg(err)}) — nothing is grafted or claimed until it is; retried on the next apply`;
|
|
2501
|
+
this.deps.logger.warn(previous?.runtime === null || previous === void 0 ? "customization target state could not be read — ownership is held" : "customization edit target state could not be read — the previous runtime stays installed", {
|
|
2502
|
+
tags: { deviceId: p.target.deviceId },
|
|
2503
|
+
meta: {
|
|
2504
|
+
blockId: p.block.id,
|
|
2505
|
+
error: require_dist.errMsg(err)
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
if (previous !== void 0) next.set(p.block.id, previous);
|
|
2509
|
+
else next.set(p.block.id, {
|
|
2510
|
+
planned: withExistingTargetWait(p, reason),
|
|
2511
|
+
device: null,
|
|
2512
|
+
runtime: null,
|
|
2513
|
+
appliedName: null,
|
|
2514
|
+
rebuildError: null,
|
|
2515
|
+
heldFields: []
|
|
2516
|
+
});
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2368
2519
|
const heldFields = this.heldFieldsFor(p, previous);
|
|
2369
|
-
previous?.runtime?.stop();
|
|
2370
2520
|
const operatorAction = previous === void 0 || restart.has(p.block.id) || previous.planned.block.updatedAt !== p.block.updatedAt;
|
|
2371
2521
|
const targetKey = p.target.kind === "existing" ? require_dist.compositionSourceKey(p.target.ref) : null;
|
|
2372
2522
|
if (operatorAction) for (const f of p.fields) for (const r of f.reads) {
|
|
@@ -2374,6 +2524,7 @@ var Composer = class {
|
|
|
2374
2524
|
if (key !== targetKey) reset.add(key);
|
|
2375
2525
|
}
|
|
2376
2526
|
const writer = this.writerFor(p, device);
|
|
2527
|
+
const runtime = writer === null || p.fields.length === 0 ? null : this.newRuntime(p, writer, targetSeed);
|
|
2377
2528
|
if (writer !== null && p.target.kind === "existing" && p.target.deviceId !== null) this.grafts.plan({
|
|
2378
2529
|
deviceId: p.target.deviceId,
|
|
2379
2530
|
ref: p.target.ref,
|
|
@@ -2382,7 +2533,7 @@ var Composer = class {
|
|
|
2382
2533
|
enabled: p.block.enabled,
|
|
2383
2534
|
settled: p.problems.length === 0
|
|
2384
2535
|
});
|
|
2385
|
-
|
|
2536
|
+
previous?.runtime?.stop();
|
|
2386
2537
|
if (runtime !== null) fresh.add(runtime);
|
|
2387
2538
|
next.set(p.block.id, {
|
|
2388
2539
|
planned: p,
|
|
@@ -2453,15 +2604,18 @@ var Composer = class {
|
|
|
2453
2604
|
if (previous.runtime === null) return previous.heldFields;
|
|
2454
2605
|
return heldFieldsOf(previous.runtime.state().fields, `the composition no longer plans — the field keeps its last value on the device: ${p.problems.join("; ")}`);
|
|
2455
2606
|
}
|
|
2456
|
-
newRuntime(p, target) {
|
|
2607
|
+
newRuntime(p, target, seed) {
|
|
2457
2608
|
const self = p.target.kind === "existing" ? selfReadCaps(p.fields, p.target.ref, p.target.features) : null;
|
|
2458
2609
|
const targetKey = p.target.kind === "existing" ? require_dist.compositionSourceKey(p.target.ref) : null;
|
|
2459
2610
|
const nativeCaps = new Set(self?.native ?? []);
|
|
2460
2611
|
return new CompositionRuntime({
|
|
2461
2612
|
blockId: p.block.id,
|
|
2462
2613
|
fields: p.fields,
|
|
2614
|
+
eventPlans: p.events,
|
|
2615
|
+
events: this.deps.events,
|
|
2463
2616
|
target,
|
|
2464
2617
|
lookupCap: this.deps.lookupCap,
|
|
2618
|
+
...seed === void 0 ? {} : { initialByCap: seed.byCap },
|
|
2465
2619
|
read: (read) => this.deps.tracker.read(read, require_dist.compositionSourceKey(read.source) === targetKey && nativeCaps.has(read.cap) ? "native" : "effective"),
|
|
2466
2620
|
now: this.deps.now,
|
|
2467
2621
|
timer: this.deps.newTimer(),
|
|
@@ -3585,7 +3739,10 @@ var ComposerAddon = class extends require_dist.BaseAddon {
|
|
|
3585
3739
|
const bindings = await api.deviceManager.getBindings.query({ deviceId });
|
|
3586
3740
|
return {
|
|
3587
3741
|
kind: "caps",
|
|
3588
|
-
caps: new Map(bindings.entries.
|
|
3742
|
+
caps: new Map(bindings.entries.flatMap((entry) => {
|
|
3743
|
+
const owner = require_composition_sources.normalizedNativeOwner(entry.nativeAddonId);
|
|
3744
|
+
return owner === null ? [] : [[entry.capName, owner]];
|
|
3745
|
+
}))
|
|
3589
3746
|
};
|
|
3590
3747
|
} catch (err) {
|
|
3591
3748
|
return {
|
|
@@ -3697,6 +3854,11 @@ var ComposerAddon = class extends require_dist.BaseAddon {
|
|
|
3697
3854
|
releaseClaim: (input) => api.deviceState.releaseClaim.mutate(input),
|
|
3698
3855
|
listClaims: (input) => api.deviceState.listClaims.query(input)
|
|
3699
3856
|
},
|
|
3857
|
+
events: this.ctx.eventBus,
|
|
3858
|
+
readExistingTargetCap: async (deviceId, capName) => await api.deviceState.getCapSlice.query({
|
|
3859
|
+
deviceId,
|
|
3860
|
+
capName
|
|
3861
|
+
}) ?? null,
|
|
3700
3862
|
describeTarget: readBoundCaps,
|
|
3701
3863
|
grafts,
|
|
3702
3864
|
now: () => Date.now(),
|