@camstack/system 1.2.468 → 1.2.470
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 +56 -25
- package/dist/addon-runner.mjs +56 -25
- package/dist/{agent-https-DJ8OzQOr.js → agent-https-D_oo3PNv.js} +1 -1
- package/dist/{agent-https-DR1wmhYY.mjs → agent-https-DmNQyAl7.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/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.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.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- 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 +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-history/capture/capture-engine.d.ts +24 -4
- package/dist/builtins/device-history/device-history.addon.js +319 -44
- package/dist/builtins/device-history/device-history.addon.mjs +319 -44
- package/dist/builtins/device-history/query/history-query.d.ts +19 -2
- package/dist/builtins/device-history/retention/orphans.d.ts +50 -0
- package/dist/builtins/device-history/retention/retention.d.ts +9 -2
- package/dist/builtins/device-history/runtime/history-runtime.d.ts +10 -2
- package/dist/builtins/device-history/store/history-store.d.ts +5 -0
- 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/device-manager/device-meta-types.d.ts +2 -2
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +3 -3
- package/dist/builtins/keychain/index.mjs +3 -3
- 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 +2 -2
- package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- 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/accelerator-reprobe.d.ts +23 -0
- package/dist/builtins/platform-probe/coral-accelerators.d.ts +37 -8
- package/dist/builtins/platform-probe/index.d.ts +17 -1
- package/dist/builtins/platform-probe/index.js +192 -11
- package/dist/builtins/platform-probe/index.mjs +192 -12
- package/dist/builtins/platform-probe/platform-scorer.d.ts +14 -0
- package/dist/builtins/record-entries/index.js +1 -1
- package/dist/builtins/record-entries/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/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- 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 +3 -3
- package/dist/builtins/system-config/system-config.addon.mjs +3 -3
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-Qto-S6MA.mjs → composition-sources-B0tqvf0t.mjs} +1 -1
- package/dist/{composition-sources-5rpWQhA1.js → composition-sources-CRJffEiB.js} +1 -1
- package/dist/{constants-BWli8Cju.mjs → constants--A505iuO.mjs} +1 -1
- package/dist/{constants-CuO3rcw4.js → constants-WF1AqoKF.js} +1 -1
- package/dist/{coral-accelerators-oUXmmbc2.js → coral-accelerators-Cri32b7q.js} +61 -24
- package/dist/{coral-accelerators-D_S1Fo5h.mjs → coral-accelerators-Dl-GnMHn.mjs} +56 -25
- package/dist/{device-ca-C5cLlah1.mjs → device-ca-DYmToMp8.mjs} +1 -1
- package/dist/{device-ca-CGcaSheI.js → device-ca-R4MZZtc0.js} +1 -1
- package/dist/{dist-l6zKmHEb.js → dist-DRL3rEfN.js} +335 -120
- package/dist/{dist-BpUtAgqR.mjs → dist-X1r44BdF.mjs} +324 -121
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-0KWDyIMD.mjs → hub-mdns-ciao-CZV5Hion.mjs} +2 -2
- package/dist/{hub-mdns-ciao-CIno_SNn.js → hub-mdns-ciao-DUoTS6jN.js} +2 -2
- package/dist/index.js +8 -8
- package/dist/index.mjs +8 -8
- package/dist/{restore-archive-B71JI-sP.js → restore-archive-Df6PsOHP.js} +1 -1
- package/dist/{restore-archive-DKxAZ9pI.mjs → restore-archive-X0ciGMmO.mjs} +1 -1
- package/dist/{wal-maintenance-BXYwFecW.mjs → wal-maintenance-D6J_MmZ8.mjs} +0 -0
- package/dist/{wal-maintenance-B6LSQ_OZ.js → wal-maintenance-DYvzJiuK.js} +0 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as HISTORY_ITEM_CATALOG, Ct as armRemovalReconcile, D as DEVICE_HISTORY_ENERGY_KEY, Da as EventCategory, Ji as emitReadiness, Qr as DeviceType, Zi as historyItemFieldPath, ei as BaseAddon, en as deviceHistoryCapability, li as HISTORY_DEFAULT_MIN_INTERVAL_MS, nn as deviceRemovalHookCapability, z as HISTORY_CATALOG } from "../../dist-X1r44BdF.mjs";
|
|
2
2
|
//#region src/builtins/device-history/time/bucket-time.ts
|
|
3
3
|
/**
|
|
4
4
|
* Bucket arithmetic for the three rollup tiers (D850, plan §4/§5.5).
|
|
@@ -185,13 +185,25 @@ var RetentionJob = class {
|
|
|
185
185
|
}
|
|
186
186
|
return deleted;
|
|
187
187
|
}
|
|
188
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* Tier by tier in slices, then the series row (and whatever the slices missed).
|
|
190
|
+
* Answers the rows the slices deleted per tier. The caller removes the series
|
|
191
|
+
* from the registry, which releases its per-series state.
|
|
192
|
+
*/
|
|
189
193
|
async purgeSeries(series, now) {
|
|
194
|
+
const deleted = {
|
|
195
|
+
raw: 0,
|
|
196
|
+
"5m": 0,
|
|
197
|
+
"1h": 0,
|
|
198
|
+
"1d": 0
|
|
199
|
+
};
|
|
190
200
|
for (const tier of TIERS) {
|
|
191
201
|
const lo = tier === "raw" || tier === "5m" ? Math.max(series.firstTs - BUCKET_LEAD_MS, now - SHORT_TIER_LOOKBACK_MS) : series.firstTs - BUCKET_LEAD_MS;
|
|
192
|
-
await this.deleteSliced(tier, series.id, Math.max(0, lo), now + DAY_MS);
|
|
202
|
+
deleted[tier] = await this.deleteSliced(tier, series.id, Math.max(0, lo), now + DAY_MS);
|
|
203
|
+
this.deps.counters.addDeleted(tier, deleted[tier]);
|
|
193
204
|
}
|
|
194
205
|
await this.deps.store.deleteSeries(series.id);
|
|
206
|
+
return deleted;
|
|
195
207
|
}
|
|
196
208
|
/**
|
|
197
209
|
* Delete every series of a DELETED device now (D865). Driven by a tombstone
|
|
@@ -558,14 +570,14 @@ var DeviceDirectory = class {
|
|
|
558
570
|
* `runtimeState` key or a path into a FIXED object (`dustBin.level`); the
|
|
559
571
|
* catalog guard keeps it out of arrays and records, so a plain walk is enough.
|
|
560
572
|
*/
|
|
561
|
-
function isRecord$
|
|
573
|
+
function isRecord$2(value) {
|
|
562
574
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
563
575
|
}
|
|
564
576
|
/** The value at `path`, or `undefined` when any step is missing or not an object. */
|
|
565
577
|
function readPath(slice, path) {
|
|
566
578
|
let current = slice;
|
|
567
579
|
for (const step of path.split(".")) {
|
|
568
|
-
if (!isRecord$
|
|
580
|
+
if (!isRecord$2(current)) return void 0;
|
|
569
581
|
current = current[step];
|
|
570
582
|
}
|
|
571
583
|
return current;
|
|
@@ -591,18 +603,41 @@ function readUnit(slice, unitFrom) {
|
|
|
591
603
|
}
|
|
592
604
|
//#endregion
|
|
593
605
|
//#region src/builtins/device-history/capture/capture-engine.ts
|
|
606
|
+
/**
|
|
607
|
+
* Capture of capability fields (D850, plan §5.3), driven by the GENERATED
|
|
608
|
+
* history catalog — a field is recorded because its `*.cap.ts` says so, never
|
|
609
|
+
* because someone listed it here.
|
|
610
|
+
*
|
|
611
|
+
* For a `DeviceStateChanged { deviceId, capName, slice }` the engine:
|
|
612
|
+
* 1. ignores a cap with no catalog entry (the common case — cheap, uncounted);
|
|
613
|
+
* 2. skips an opted-out device (counted), a device whose type is not yet
|
|
614
|
+
* known (parked, then replayed once the hub answers), and a `hub` /
|
|
615
|
+
* `container` (counted: not a real device);
|
|
616
|
+
* 3. per field: skips an excluded device type, an absent or non-numeric value
|
|
617
|
+
* (null is UNKNOWN, never 0), then runs {@link decideCapture} against the
|
|
618
|
+
* last RECORDED value — change-only, deadband, capture floor;
|
|
619
|
+
* 4. buffers the point for the batched write.
|
|
620
|
+
*
|
|
621
|
+
* A change inside the capture floor is kept PENDING and emitted when the floor
|
|
622
|
+
* has passed ({@link CaptureEngine.tickPending}), so the end of a burst is never
|
|
623
|
+
* lost. Reconcile ({@link CaptureEngine.reconcile}) restores what a dropped
|
|
624
|
+
* event missed from the state mirror's snapshot.
|
|
625
|
+
*/
|
|
594
626
|
var CAP_KEY_PREFIX = "cap:";
|
|
595
627
|
function capSeriesKey(capName, fieldPath) {
|
|
596
628
|
return `${CAP_KEY_PREFIX}${capName}.${fieldPath}`;
|
|
597
629
|
}
|
|
630
|
+
/** The series key of an item field: `cap:<capName>.<path>[].<field>`. */
|
|
631
|
+
function capItemSeriesKey(capName, items, field) {
|
|
632
|
+
return capSeriesKey(capName, historyItemFieldPath(items.path, field));
|
|
633
|
+
}
|
|
598
634
|
function humanize(capName, fieldPath) {
|
|
599
635
|
const field = fieldPath.replace(/([A-Z])/g, " $1").replace(/\./g, " ").toLowerCase();
|
|
600
636
|
return `${capName.replace(/-/g, " ")} ${field}`;
|
|
601
637
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
key: capSeriesKey(capName, path),
|
|
638
|
+
function metricOf(key, capName, path, spec, now) {
|
|
639
|
+
return {
|
|
640
|
+
key,
|
|
606
641
|
source: "cap",
|
|
607
642
|
ownerAddon: "device-history",
|
|
608
643
|
subjectKind: "device",
|
|
@@ -612,7 +647,16 @@ function catalogMetrics(catalog, now) {
|
|
|
612
647
|
ingest: "points",
|
|
613
648
|
declaredAt: now,
|
|
614
649
|
values: spec.kind === "enum" ? spec.values ?? [] : null
|
|
615
|
-
}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/** The metric rows of every catalog field and keyed-item field (owner `device-history`). */
|
|
653
|
+
function catalogMetrics(catalog, itemCatalog, now) {
|
|
654
|
+
const fields = Object.entries(catalog).flatMap(([capName, specs]) => Object.entries(specs).map(([path, spec]) => metricOf(capSeriesKey(capName, path), capName, path, spec, now)));
|
|
655
|
+
const items = Object.entries(itemCatalog).flatMap(([capName, block]) => Object.entries(block.fields).map(([field, spec]) => metricOf(capItemSeriesKey(capName, block, field), capName, field, spec, now)));
|
|
656
|
+
return [...fields, ...items];
|
|
657
|
+
}
|
|
658
|
+
function isRecord$1(value) {
|
|
659
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
616
660
|
}
|
|
617
661
|
/** Devices waiting on a type lookup; beyond this the oldest is dropped (and counted). */
|
|
618
662
|
var MAX_PARKED_DEVICES = 5e3;
|
|
@@ -639,7 +683,8 @@ var CaptureEngine = class {
|
|
|
639
683
|
/** One `DeviceStateChanged`. Synchronous: the bus must never wait on us. */
|
|
640
684
|
handleState(deviceId, capName, slice, at) {
|
|
641
685
|
const fields = this.deps.catalog[capName];
|
|
642
|
-
|
|
686
|
+
const items = this.deps.itemCatalog[capName];
|
|
687
|
+
if (fields === void 0 && items === void 0) return;
|
|
643
688
|
const { counters, directory } = this.deps;
|
|
644
689
|
counters.eventsSeen += 1;
|
|
645
690
|
if (this.deps.optedOut().has(deviceId)) {
|
|
@@ -655,14 +700,46 @@ var CaptureEngine = class {
|
|
|
655
700
|
counters.skip("not-a-real-device");
|
|
656
701
|
return;
|
|
657
702
|
}
|
|
658
|
-
|
|
703
|
+
this.captureSlice(deviceId, type, capName, slice, at, false);
|
|
704
|
+
}
|
|
705
|
+
/** Every catalogued field and keyed item of one cap slice, for a real device. */
|
|
706
|
+
captureSlice(deviceId, type, capName, slice, at, reconciling) {
|
|
707
|
+
for (const [path, spec] of Object.entries(this.deps.catalog[capName] ?? {})) {
|
|
659
708
|
if (spec.excludeDeviceTypes?.includes(type) === true) {
|
|
660
|
-
counters.skip("excluded-device-type");
|
|
709
|
+
if (!reconciling) this.deps.counters.skip("excluded-device-type");
|
|
661
710
|
continue;
|
|
662
711
|
}
|
|
663
|
-
const
|
|
712
|
+
const key = capSeriesKey(capName, path);
|
|
713
|
+
const value = this.readValue(slice, key, path, spec);
|
|
664
714
|
if (value === null) continue;
|
|
665
|
-
this.consider(deviceId,
|
|
715
|
+
this.consider(deviceId, key, "", spec, value, at, reconciling);
|
|
716
|
+
}
|
|
717
|
+
const items = this.deps.itemCatalog[capName];
|
|
718
|
+
if (items !== void 0) this.captureItems(deviceId, type, capName, items, slice, at, reconciling);
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* A keyed array (D917): each item with a usable key contributes its fields
|
|
722
|
+
* under that key as the series scope. A value that is not an array is no
|
|
723
|
+
* reading; an item without a string key cannot be followed and is counted.
|
|
724
|
+
*/
|
|
725
|
+
captureItems(deviceId, type, capName, items, slice, at, reconciling) {
|
|
726
|
+
const list = readPath(slice, items.path);
|
|
727
|
+
if (!Array.isArray(list)) return;
|
|
728
|
+
const { counters } = this.deps;
|
|
729
|
+
if (list.length > 64) counters.skip("item-overflow", list.length - 64);
|
|
730
|
+
for (const entry of list.slice(0, 64)) {
|
|
731
|
+
const scope = isRecord$1(entry) ? entry[items.key] : void 0;
|
|
732
|
+
if (!isRecord$1(entry) || typeof scope !== "string" || scope.length === 0 || scope.length > 128) {
|
|
733
|
+
counters.skip("item-key-invalid");
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
for (const [field, spec] of Object.entries(items.fields)) {
|
|
737
|
+
if (spec.excludeDeviceTypes?.includes(type) === true) continue;
|
|
738
|
+
const key = capItemSeriesKey(capName, items, field);
|
|
739
|
+
const value = this.readValue(entry, key, field, spec);
|
|
740
|
+
if (value === null) continue;
|
|
741
|
+
this.consider(deviceId, key, scope, spec, value, at, reconciling);
|
|
742
|
+
}
|
|
666
743
|
}
|
|
667
744
|
}
|
|
668
745
|
/**
|
|
@@ -670,7 +747,7 @@ var CaptureEngine = class {
|
|
|
670
747
|
* `enum` state becomes the ordinal of its value in the metric's list; a value
|
|
671
748
|
* the full list cannot take is refused and counted (`enum-overflow`).
|
|
672
749
|
*/
|
|
673
|
-
readValue(slice,
|
|
750
|
+
readValue(slice, key, path, spec) {
|
|
674
751
|
if (spec.skipWhenSet !== void 0) {
|
|
675
752
|
const gate = readPath(slice, spec.skipWhenSet);
|
|
676
753
|
if (gate !== void 0 && gate !== null) return null;
|
|
@@ -678,20 +755,19 @@ var CaptureEngine = class {
|
|
|
678
755
|
if (spec.kind !== "enum") return readNumeric(slice, path, spec.kind);
|
|
679
756
|
const raw = readPath(slice, path);
|
|
680
757
|
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
681
|
-
const ordinal = this.deps.registry.ordinalOf(
|
|
758
|
+
const ordinal = this.deps.registry.ordinalOf(key, raw);
|
|
682
759
|
if (ordinal === null) this.deps.counters.skip("enum-overflow");
|
|
683
760
|
return ordinal;
|
|
684
761
|
}
|
|
685
|
-
consider(deviceId,
|
|
686
|
-
const key = capSeriesKey(capName, path);
|
|
762
|
+
consider(deviceId, key, scope, spec, value, at, reconciling) {
|
|
687
763
|
const subject = {
|
|
688
764
|
kind: "device",
|
|
689
765
|
deviceId
|
|
690
766
|
};
|
|
691
|
-
const identity = identityOf(key, `d:${deviceId}`,
|
|
767
|
+
const identity = identityOf(key, `d:${deviceId}`, scope);
|
|
692
768
|
const decision = decideCapture(spec, this.recorded.get(identity) ?? null, value, at, { ignoreFloor: reconciling });
|
|
693
769
|
if (decision.action === "emit") {
|
|
694
|
-
this.emit(identity, key, subject, value, at);
|
|
770
|
+
this.emit(identity, key, subject, scope, value, at);
|
|
695
771
|
if (reconciling) this.deps.counters.reconciledPoints += 1;
|
|
696
772
|
return;
|
|
697
773
|
}
|
|
@@ -699,6 +775,7 @@ var CaptureEngine = class {
|
|
|
699
775
|
this.pending.set(identity, {
|
|
700
776
|
key,
|
|
701
777
|
subject,
|
|
778
|
+
scope,
|
|
702
779
|
spec,
|
|
703
780
|
value
|
|
704
781
|
});
|
|
@@ -707,7 +784,7 @@ var CaptureEngine = class {
|
|
|
707
784
|
this.pending.delete(identity);
|
|
708
785
|
this.deps.counters.skip(decision.reason);
|
|
709
786
|
}
|
|
710
|
-
emit(identity, key, subject, value, ts) {
|
|
787
|
+
emit(identity, key, subject, scope, value, ts) {
|
|
711
788
|
this.recorded.set(identity, {
|
|
712
789
|
value,
|
|
713
790
|
ts
|
|
@@ -717,7 +794,7 @@ var CaptureEngine = class {
|
|
|
717
794
|
this.deps.writer.enqueue({
|
|
718
795
|
key,
|
|
719
796
|
subject,
|
|
720
|
-
scope
|
|
797
|
+
scope,
|
|
721
798
|
ts,
|
|
722
799
|
value
|
|
723
800
|
});
|
|
@@ -734,7 +811,7 @@ var CaptureEngine = class {
|
|
|
734
811
|
}
|
|
735
812
|
const notBefore = (recorded?.ts ?? now) + minIntervalOf(p.spec);
|
|
736
813
|
if (now < notBefore) continue;
|
|
737
|
-
this.emit(identity, p.key, p.subject, p.value, notBefore);
|
|
814
|
+
this.emit(identity, p.key, p.subject, p.scope, p.value, notBefore);
|
|
738
815
|
this.deps.counters.trailingEmitted += 1;
|
|
739
816
|
}
|
|
740
817
|
}
|
|
@@ -782,16 +859,7 @@ var CaptureEngine = class {
|
|
|
782
859
|
if (!Number.isInteger(deviceId) || optedOut.has(deviceId)) continue;
|
|
783
860
|
const type = this.deps.directory.typeOf(deviceId);
|
|
784
861
|
if (type === void 0 || !isRealDeviceType(type)) continue;
|
|
785
|
-
for (const [capName,
|
|
786
|
-
const slice = caps[capName];
|
|
787
|
-
if (slice === void 0) continue;
|
|
788
|
-
for (const [path, spec] of Object.entries(fields)) {
|
|
789
|
-
if (spec.excludeDeviceTypes?.includes(type) === true) continue;
|
|
790
|
-
const value = this.readValue(slice, capName, path, spec);
|
|
791
|
-
if (value === null) continue;
|
|
792
|
-
this.consider(deviceId, capName, path, spec, value, now, true);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
862
|
+
for (const [capName, slice] of Object.entries(caps)) this.captureSlice(deviceId, type, capName, slice, now, true);
|
|
795
863
|
}
|
|
796
864
|
}
|
|
797
865
|
/**
|
|
@@ -817,8 +885,8 @@ var CaptureEngine = class {
|
|
|
817
885
|
return this.pending.size;
|
|
818
886
|
}
|
|
819
887
|
/** The last recorded state of a series, for specs. */
|
|
820
|
-
lastRecorded(key, subjectId) {
|
|
821
|
-
return this.recorded.get(identityOf(key, subjectId,
|
|
888
|
+
lastRecorded(key, subjectId, scope = "") {
|
|
889
|
+
return this.recorded.get(identityOf(key, subjectId, scope));
|
|
822
890
|
}
|
|
823
891
|
};
|
|
824
892
|
//#endregion
|
|
@@ -1287,8 +1355,10 @@ function errorText(err) {
|
|
|
1287
1355
|
}
|
|
1288
1356
|
var HistoryQuery = class {
|
|
1289
1357
|
deps;
|
|
1358
|
+
itemFields;
|
|
1290
1359
|
constructor(deps) {
|
|
1291
1360
|
this.deps = deps;
|
|
1361
|
+
this.itemFields = itemFieldsByKey(deps.itemCatalog);
|
|
1292
1362
|
}
|
|
1293
1363
|
async query(input) {
|
|
1294
1364
|
if (input.key === "derived:energy.kwh") return this.queryEnergy(input);
|
|
@@ -1476,12 +1546,14 @@ var HistoryQuery = class {
|
|
|
1476
1546
|
for (const s of registry.forSubject(subjectIdOf(subject))) {
|
|
1477
1547
|
const metric = registry.metric(s.key);
|
|
1478
1548
|
if (metric === void 0) continue;
|
|
1549
|
+
const item = this.itemFields.get(s.key);
|
|
1550
|
+
const named = item === void 0 || subject.kind !== "device" ? null : await this.itemNaming(subject.deviceId, item, s.scope, metric.unit, slices);
|
|
1479
1551
|
infos.push({
|
|
1480
1552
|
key: s.key,
|
|
1481
1553
|
scope: s.scope,
|
|
1482
1554
|
kind: metric.kind,
|
|
1483
|
-
unit: await this.unitOf(subject, s, metric.unit, slices),
|
|
1484
|
-
displayName: metric.displayName,
|
|
1555
|
+
unit: named !== null ? named.unit : await this.unitOf(subject, s, metric.unit, slices),
|
|
1556
|
+
displayName: named !== null ? `${named.label} · ${metric.displayName}` : metric.displayName,
|
|
1485
1557
|
source: metric.source,
|
|
1486
1558
|
values: metric.kind === "enum" ? metric.values ?? [] : null,
|
|
1487
1559
|
firstTs: s.firstTs,
|
|
@@ -1502,15 +1574,38 @@ var HistoryQuery = class {
|
|
|
1502
1574
|
});
|
|
1503
1575
|
return infos.toSorted((a, b) => a.key.localeCompare(b.key) || a.scope.localeCompare(b.scope));
|
|
1504
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* An item series (D917) is named after its item and takes its `unitFrom`
|
|
1579
|
+
* from it, both read from the device's CURRENT slice: the item's `label`,
|
|
1580
|
+
* else its key (the scope) when the item is gone or the slice unreadable.
|
|
1581
|
+
*/
|
|
1582
|
+
async itemNaming(deviceId, item, scope, fixed, slices) {
|
|
1583
|
+
const slice = await this.sliceOf(deviceId, item.capName, slices);
|
|
1584
|
+
const list = slice === null ? void 0 : readPath(slice, item.items.path);
|
|
1585
|
+
const entry = Array.isArray(list) ? list.find((e) => isObject(e) && e[item.items.key] === scope) : void 0;
|
|
1586
|
+
const rawLabel = entry !== void 0 && item.items.label !== void 0 ? entry[item.items.label] : void 0;
|
|
1587
|
+
const label = typeof rawLabel === "string" && rawLabel.length > 0 ? rawLabel : scope;
|
|
1588
|
+
if (fixed !== null || item.spec.unitFrom === void 0) return {
|
|
1589
|
+
label,
|
|
1590
|
+
unit: fixed
|
|
1591
|
+
};
|
|
1592
|
+
return {
|
|
1593
|
+
label,
|
|
1594
|
+
unit: entry === void 0 ? null : readUnit(entry, item.spec.unitFrom)
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
sliceOf(deviceId, capName, slices) {
|
|
1598
|
+
const pending = slices.get(capName) ?? this.deps.readCapSlice(deviceId, capName).catch(() => null);
|
|
1599
|
+
slices.set(capName, pending);
|
|
1600
|
+
return pending;
|
|
1601
|
+
}
|
|
1505
1602
|
/** The metric's fixed unit, else the device's own (a `unitFrom` field), else unknown. */
|
|
1506
1603
|
async unitOf(subject, series, fixed, slices) {
|
|
1507
1604
|
if (fixed !== null || subject.kind !== "device" || !series.key.startsWith("cap:")) return fixed;
|
|
1508
1605
|
const spec = this.specOf(series.key);
|
|
1509
1606
|
if (spec?.unitFrom === void 0) return null;
|
|
1510
1607
|
const capName = series.key.slice(CAP_KEY_PREFIX.length, series.key.indexOf("."));
|
|
1511
|
-
const
|
|
1512
|
-
slices.set(capName, pending);
|
|
1513
|
-
const slice = await pending;
|
|
1608
|
+
const slice = await this.sliceOf(subject.deviceId, capName, slices);
|
|
1514
1609
|
return slice === null ? null : readUnit(slice, spec.unitFrom);
|
|
1515
1610
|
}
|
|
1516
1611
|
specOf(key) {
|
|
@@ -1520,12 +1615,158 @@ var HistoryQuery = class {
|
|
|
1520
1615
|
return this.deps.catalog[body.slice(0, dot)]?.[body.slice(dot + 1)];
|
|
1521
1616
|
}
|
|
1522
1617
|
};
|
|
1618
|
+
function isObject(value) {
|
|
1619
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1620
|
+
}
|
|
1621
|
+
/** Series key → keyed-item field, for every item field of the catalog. */
|
|
1622
|
+
function itemFieldsByKey(itemCatalog) {
|
|
1623
|
+
return new Map(Object.entries(itemCatalog).flatMap(([capName, items]) => Object.entries(items.fields).map(([field, spec]) => [capItemSeriesKey(capName, items, field), {
|
|
1624
|
+
capName,
|
|
1625
|
+
items,
|
|
1626
|
+
spec
|
|
1627
|
+
}])));
|
|
1628
|
+
}
|
|
1523
1629
|
/** Every covering tier but raw; the coarsest stays when it is the only one. */
|
|
1524
1630
|
function withoutRaw(tiers) {
|
|
1525
1631
|
const rollups = tiers.filter((t) => t !== "raw");
|
|
1526
1632
|
return rollups.length > 0 ? rollups : ["5m"];
|
|
1527
1633
|
}
|
|
1528
1634
|
//#endregion
|
|
1635
|
+
//#region src/builtins/device-history/retention/orphans.ts
|
|
1636
|
+
/**
|
|
1637
|
+
* The orphan verdict of one series, or `null` when it is linked. PURE. A node
|
|
1638
|
+
* series is never orphan here (its subject is a node, not a device).
|
|
1639
|
+
*/
|
|
1640
|
+
function judgeOrphan(series, declaredCapKeys, missingSince) {
|
|
1641
|
+
if (series.deviceId === null) return null;
|
|
1642
|
+
if (missingSince !== void 0) return {
|
|
1643
|
+
reason: "device-gone",
|
|
1644
|
+
missingSince
|
|
1645
|
+
};
|
|
1646
|
+
if (series.key.startsWith("cap:") && !declaredCapKeys.has(series.key)) return {
|
|
1647
|
+
reason: "undeclared",
|
|
1648
|
+
missingSince: null
|
|
1649
|
+
};
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
var OrphanSeries = class {
|
|
1653
|
+
deps;
|
|
1654
|
+
constructor(deps) {
|
|
1655
|
+
this.deps = deps;
|
|
1656
|
+
}
|
|
1657
|
+
verdictOf(series) {
|
|
1658
|
+
const { departures, declaredCapKeys } = this.deps;
|
|
1659
|
+
return judgeOrphan(series, declaredCapKeys, series.deviceId === null ? void 0 : departures.missingSince(series.deviceId));
|
|
1660
|
+
}
|
|
1661
|
+
/** Every orphan, ordered by device, key and scope, the first {@link DEVICE_HISTORY_ORPHAN_LIST_MAX} counted. */
|
|
1662
|
+
async list() {
|
|
1663
|
+
const found = this.deps.registry.all().flatMap((series) => {
|
|
1664
|
+
const verdict = this.verdictOf(series);
|
|
1665
|
+
return verdict === null || series.deviceId === null ? [] : [{
|
|
1666
|
+
series,
|
|
1667
|
+
deviceId: series.deviceId,
|
|
1668
|
+
verdict
|
|
1669
|
+
}];
|
|
1670
|
+
}).toSorted((a, b) => a.deviceId - b.deviceId || a.series.key.localeCompare(b.series.key) || a.series.scope.localeCompare(b.series.scope));
|
|
1671
|
+
const out = [];
|
|
1672
|
+
for (const { series, deviceId, verdict } of found.slice(0, 200)) out.push({
|
|
1673
|
+
seriesId: series.id,
|
|
1674
|
+
deviceId,
|
|
1675
|
+
key: series.key,
|
|
1676
|
+
scope: series.scope,
|
|
1677
|
+
displayName: this.deps.registry.metric(series.key)?.displayName ?? null,
|
|
1678
|
+
reason: verdict.reason,
|
|
1679
|
+
missingSince: verdict.missingSince,
|
|
1680
|
+
firstTs: series.firstTs,
|
|
1681
|
+
lastTs: series.lastTs,
|
|
1682
|
+
rows: await this.rowsOf(series.id)
|
|
1683
|
+
});
|
|
1684
|
+
return {
|
|
1685
|
+
series: out,
|
|
1686
|
+
truncated: found.length > 200
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
async rowsOf(seriesId) {
|
|
1690
|
+
const { store } = this.deps;
|
|
1691
|
+
return {
|
|
1692
|
+
raw: await store.countSeriesRows("raw", seriesId),
|
|
1693
|
+
"5m": await store.countSeriesRows("5m", seriesId),
|
|
1694
|
+
"1h": await store.countSeriesRows("1h", seriesId),
|
|
1695
|
+
"1d": await store.countSeriesRows("1d", seriesId)
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
/** Delete orphans by id, one at a time, each re-judged now. One entry per id, in order. */
|
|
1699
|
+
async delete(seriesIds) {
|
|
1700
|
+
const out = [];
|
|
1701
|
+
for (const seriesId of seriesIds) out.push(await this.deleteOne(seriesId));
|
|
1702
|
+
return out;
|
|
1703
|
+
}
|
|
1704
|
+
async deleteOne(seriesId) {
|
|
1705
|
+
const { registry, retention, logger, clock } = this.deps;
|
|
1706
|
+
const series = registry.all().find((s) => s.id === seriesId);
|
|
1707
|
+
if (series === void 0) return {
|
|
1708
|
+
seriesId,
|
|
1709
|
+
ok: false,
|
|
1710
|
+
reason: "not-found"
|
|
1711
|
+
};
|
|
1712
|
+
const verdict = this.verdictOf(series);
|
|
1713
|
+
if (verdict === null || series.deviceId === null) {
|
|
1714
|
+
logger.info("device-history: refused to delete a series that is not orphan", {
|
|
1715
|
+
...series.deviceId === null ? {} : { tags: { deviceId: series.deviceId } },
|
|
1716
|
+
meta: {
|
|
1717
|
+
seriesId,
|
|
1718
|
+
key: series.key,
|
|
1719
|
+
scope: series.scope
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
return {
|
|
1723
|
+
seriesId,
|
|
1724
|
+
ok: false,
|
|
1725
|
+
reason: "not-orphan"
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
try {
|
|
1729
|
+
const deleted = await retention.purgeSeries(series, clock());
|
|
1730
|
+
registry.remove(series.id);
|
|
1731
|
+
const rows = {
|
|
1732
|
+
raw: deleted.raw,
|
|
1733
|
+
"5m": deleted["5m"],
|
|
1734
|
+
"1h": deleted["1h"],
|
|
1735
|
+
"1d": deleted["1d"]
|
|
1736
|
+
};
|
|
1737
|
+
logger.info("device-history: deleted an orphan series", {
|
|
1738
|
+
tags: { deviceId: series.deviceId },
|
|
1739
|
+
meta: {
|
|
1740
|
+
seriesId,
|
|
1741
|
+
key: series.key,
|
|
1742
|
+
scope: series.scope,
|
|
1743
|
+
reason: verdict.reason,
|
|
1744
|
+
rows
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
return {
|
|
1748
|
+
seriesId,
|
|
1749
|
+
ok: true,
|
|
1750
|
+
deleted: rows
|
|
1751
|
+
};
|
|
1752
|
+
} catch (err) {
|
|
1753
|
+
logger.warn("device-history: deleting an orphan series failed — it stays listed, retry the delete", {
|
|
1754
|
+
tags: { deviceId: series.deviceId },
|
|
1755
|
+
meta: {
|
|
1756
|
+
seriesId,
|
|
1757
|
+
key: series.key,
|
|
1758
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1761
|
+
return {
|
|
1762
|
+
seriesId,
|
|
1763
|
+
ok: false,
|
|
1764
|
+
reason: "failed"
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
//#endregion
|
|
1529
1770
|
//#region src/builtins/device-history/store/history-schema.ts
|
|
1530
1771
|
var HISTORY_NAMESPACE = "device-history";
|
|
1531
1772
|
var COLLECTIONS = {
|
|
@@ -2136,6 +2377,17 @@ var HistoryStore = class {
|
|
|
2136
2377
|
key: String(seriesId)
|
|
2137
2378
|
});
|
|
2138
2379
|
}
|
|
2380
|
+
/**
|
|
2381
|
+
* One series' rows of a tier: a range of the primary key (`series_id = ?`),
|
|
2382
|
+
* never a scan of the tier (D447, asserted in `history-query-plans.spec.ts`).
|
|
2383
|
+
*/
|
|
2384
|
+
async countSeriesRows(tier, seriesId) {
|
|
2385
|
+
return this.door.count.query({
|
|
2386
|
+
namespace: HISTORY_NAMESPACE,
|
|
2387
|
+
collection: collectionOf(tier),
|
|
2388
|
+
filter: { where: { series_id: seriesId } }
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2139
2391
|
async countRows(tier) {
|
|
2140
2392
|
return this.door.count.query({
|
|
2141
2393
|
namespace: HISTORY_NAMESPACE,
|
|
@@ -2696,6 +2948,7 @@ var HistoryRuntime = class {
|
|
|
2696
2948
|
query;
|
|
2697
2949
|
retentionJob;
|
|
2698
2950
|
departures;
|
|
2951
|
+
orphans;
|
|
2699
2952
|
optedOutDevices = /* @__PURE__ */ new Set();
|
|
2700
2953
|
tierCounts = /* @__PURE__ */ new Map();
|
|
2701
2954
|
startedAt;
|
|
@@ -2704,7 +2957,7 @@ var HistoryRuntime = class {
|
|
|
2704
2957
|
ready = false;
|
|
2705
2958
|
constructor(deps) {
|
|
2706
2959
|
this.deps = deps;
|
|
2707
|
-
const { logger, clock, catalog } = deps;
|
|
2960
|
+
const { logger, clock, catalog, itemCatalog } = deps;
|
|
2708
2961
|
this.startedAt = clock();
|
|
2709
2962
|
this.store = new HistoryStore(deps.door);
|
|
2710
2963
|
this.registry = new SeriesRegistry(this.store);
|
|
@@ -2728,6 +2981,7 @@ var HistoryRuntime = class {
|
|
|
2728
2981
|
const optedOut = () => this.optedOutDevices;
|
|
2729
2982
|
this.capture = new CaptureEngine({
|
|
2730
2983
|
catalog,
|
|
2984
|
+
itemCatalog,
|
|
2731
2985
|
registry: this.registry,
|
|
2732
2986
|
store: this.store,
|
|
2733
2987
|
directory: this.directory,
|
|
@@ -2761,6 +3015,7 @@ var HistoryRuntime = class {
|
|
|
2761
3015
|
clock,
|
|
2762
3016
|
retention: deps.retention,
|
|
2763
3017
|
catalog,
|
|
3018
|
+
itemCatalog,
|
|
2764
3019
|
readCapSlice: deps.readCapSlice
|
|
2765
3020
|
});
|
|
2766
3021
|
this.departures = new DepartureTracker(this.store, this.registry, logger);
|
|
@@ -2777,11 +3032,20 @@ var HistoryRuntime = class {
|
|
|
2777
3032
|
counters: this.counters,
|
|
2778
3033
|
logger
|
|
2779
3034
|
});
|
|
3035
|
+
this.orphans = new OrphanSeries({
|
|
3036
|
+
store: this.store,
|
|
3037
|
+
registry: this.registry,
|
|
3038
|
+
departures: this.departures,
|
|
3039
|
+
retention: this.retentionJob,
|
|
3040
|
+
declaredCapKeys: new Set(catalogMetrics(catalog, itemCatalog, 0).map((m) => m.key)),
|
|
3041
|
+
logger,
|
|
3042
|
+
clock
|
|
3043
|
+
});
|
|
2780
3044
|
}
|
|
2781
3045
|
async prepare() {
|
|
2782
3046
|
await this.store.declare();
|
|
2783
3047
|
await this.registry.load();
|
|
2784
|
-
for (const metric of catalogMetrics(this.deps.catalog, this.deps.clock())) await this.registry.declareMetric(metric);
|
|
3048
|
+
for (const metric of catalogMetrics(this.deps.catalog, this.deps.itemCatalog, this.deps.clock())) await this.registry.declareMetric(metric);
|
|
2785
3049
|
this.optedOutDevices = await this.store.loadOptOuts();
|
|
2786
3050
|
await this.departures.load();
|
|
2787
3051
|
await this.directory.refresh();
|
|
@@ -2900,6 +3164,14 @@ var HistoryRuntime = class {
|
|
|
2900
3164
|
this.optedOutDevices = new Set([...this.optedOutDevices].filter((id) => id !== deviceId));
|
|
2901
3165
|
return { series };
|
|
2902
3166
|
}
|
|
3167
|
+
/** Device series no longer linked to a device — reported, never purged here. */
|
|
3168
|
+
listOrphans() {
|
|
3169
|
+
return this.orphans.list();
|
|
3170
|
+
}
|
|
3171
|
+
/** Delete orphan series by id; a series that is not orphan NOW is refused. */
|
|
3172
|
+
deleteOrphans(seriesIds) {
|
|
3173
|
+
return this.orphans.delete(seriesIds);
|
|
3174
|
+
}
|
|
2903
3175
|
/** Series held per device, for the admin clean-up listing. */
|
|
2904
3176
|
seriesByDevice() {
|
|
2905
3177
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3095,7 +3367,9 @@ var DeviceHistoryAddon = class extends BaseAddon {
|
|
|
3095
3367
|
recordBuckets: async (input) => this.requireRuntime().ingest.recordBuckets(callerOf(input), input.buckets),
|
|
3096
3368
|
getDeviceEnabled: async (input) => ({ enabled: this.requireRuntime().isEnabled(input.deviceId) }),
|
|
3097
3369
|
setDeviceEnabled: async (input) => ({ enabled: await this.requireRuntime().setEnabled(input.deviceId, input.enabled) }),
|
|
3098
|
-
getStatus: async () => this.requireRuntime().status()
|
|
3370
|
+
getStatus: async () => this.requireRuntime().status(),
|
|
3371
|
+
listOrphanSeries: async () => this.requireRuntime().listOrphans(),
|
|
3372
|
+
deleteOrphanSeries: async (input) => ({ results: await this.requireRuntime().deleteOrphans(input.seriesIds) })
|
|
3099
3373
|
};
|
|
3100
3374
|
this.emitStoreReadiness("starting");
|
|
3101
3375
|
this.ctx.logger.info("Initialized — device-history provider registered (store opens on hub link)");
|
|
@@ -3131,6 +3405,7 @@ var DeviceHistoryAddon = class extends BaseAddon {
|
|
|
3131
3405
|
const runtime = new HistoryRuntime({
|
|
3132
3406
|
door: api.settingsStore,
|
|
3133
3407
|
catalog: HISTORY_CATALOG,
|
|
3408
|
+
itemCatalog: HISTORY_ITEM_CATALOG,
|
|
3134
3409
|
deviceSource: {
|
|
3135
3410
|
listAll: async () => {
|
|
3136
3411
|
return (await api.deviceManager.listAll.query({ projection: "slim" })).map((d) => ({
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
* One place makes that choice, so two charts of one device cannot disagree, and
|
|
20
20
|
* it covers every hour of history recorded before the key existed.
|
|
21
21
|
*/
|
|
22
|
-
import { type DeviceHistoryQueryManyEntry, type DeviceHistoryQueryManyItem, type DeviceHistoryQueryPoint, type DeviceHistoryResolution, type DeviceHistorySeriesInfo, type DeviceHistorySubject } from "@camstack/types";
|
|
22
|
+
import { type CapabilityHistoryField, type CapabilityHistoryItems, type DeviceHistoryQueryManyEntry, type DeviceHistoryQueryManyItem, type DeviceHistoryQueryPoint, type DeviceHistoryResolution, type DeviceHistorySeriesInfo, type DeviceHistorySubject } from "@camstack/types";
|
|
23
23
|
import { type RetentionPolicy } from "../retention/retention.js";
|
|
24
24
|
import type { HistoryStore } from "../store/history-store.js";
|
|
25
25
|
import { type SeriesRegistry } from "../store/series-registry.js";
|
|
26
|
-
import { type HistoryCatalog } from "../capture/capture-engine.js";
|
|
26
|
+
import { type HistoryCatalog, type HistoryItemCatalog } from "../capture/capture-engine.js";
|
|
27
27
|
type Tier = DeviceHistoryResolution;
|
|
28
28
|
/** The power-meter series the energy key is derived from, meter first. */
|
|
29
29
|
export declare const ENERGY_METER_KEY: string;
|
|
@@ -66,11 +66,13 @@ export interface QueryDeps {
|
|
|
66
66
|
readonly clock: () => number;
|
|
67
67
|
readonly retention: () => RetentionPolicy;
|
|
68
68
|
readonly catalog: HistoryCatalog;
|
|
69
|
+
readonly itemCatalog: HistoryItemCatalog;
|
|
69
70
|
/** The device's current slice of a cap, for a `unitFrom` series; `null` when unreadable. */
|
|
70
71
|
readonly readCapSlice: (deviceId: number, capName: string) => Promise<Readonly<Record<string, unknown>> | null>;
|
|
71
72
|
}
|
|
72
73
|
export declare class HistoryQuery {
|
|
73
74
|
private readonly deps;
|
|
75
|
+
private readonly itemFields;
|
|
74
76
|
constructor(deps: QueryDeps);
|
|
75
77
|
query(input: QueryInput): Promise<QueryOutput>;
|
|
76
78
|
/** Several reads over one window; one failing item answers its error, not the batch's. */
|
|
@@ -88,8 +90,23 @@ export declare class HistoryQuery {
|
|
|
88
90
|
private energySource;
|
|
89
91
|
private queryEnergy;
|
|
90
92
|
listSeries(subject: DeviceHistorySubject): Promise<readonly DeviceHistorySeriesInfo[]>;
|
|
93
|
+
/**
|
|
94
|
+
* An item series (D917) is named after its item and takes its `unitFrom`
|
|
95
|
+
* from it, both read from the device's CURRENT slice: the item's `label`,
|
|
96
|
+
* else its key (the scope) when the item is gone or the slice unreadable.
|
|
97
|
+
*/
|
|
98
|
+
private itemNaming;
|
|
99
|
+
private sliceOf;
|
|
91
100
|
/** The metric's fixed unit, else the device's own (a `unitFrom` field), else unknown. */
|
|
92
101
|
private unitOf;
|
|
93
102
|
private specOf;
|
|
94
103
|
}
|
|
104
|
+
/** One keyed-item field (D917), by its series key. */
|
|
105
|
+
interface ItemField {
|
|
106
|
+
readonly capName: string;
|
|
107
|
+
readonly items: CapabilityHistoryItems;
|
|
108
|
+
readonly spec: CapabilityHistoryField;
|
|
109
|
+
}
|
|
110
|
+
/** Series key → keyed-item field, for every item field of the catalog. */
|
|
111
|
+
export declare function itemFieldsByKey(itemCatalog: HistoryItemCatalog): ReadonlyMap<string, ItemField>;
|
|
95
112
|
export {};
|