@camstack/types 1.2.33 → 1.2.35
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.js +3 -2
- package/dist/addon.mjs +3 -2
- package/dist/capabilities/index.d.ts +14 -11
- package/dist/capabilities/notification-rules.cap.d.ts +135 -0
- package/dist/capabilities/vector-store.cap.d.ts +223 -0
- package/dist/device/device-control-resolution.d.ts +1 -1
- package/dist/generated/addon-api.d.ts +64 -0
- package/dist/generated/cap-input-defaults.d.ts +1 -1
- package/dist/generated/capability-router-map.d.ts +5 -2
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/index.d.ts +166 -165
- package/dist/index.js +25160 -24801
- package/dist/index.mjs +25138 -24802
- package/dist/{sleep-C1ssEDf2.mjs → sleep-BeLSE-Rr.mjs} +520 -518
- package/dist/{sleep-C84-hqFR.js → sleep-C6W2qcHX.js} +519 -517
- package/dist/utils/vector-codec.d.ts +28 -0
- package/package.json +1 -1
|
@@ -1,70 +1,5 @@
|
|
|
1
1
|
const require_event_category = require("./event-category-BE4PDZ_3.js");
|
|
2
2
|
let zod = require("zod");
|
|
3
|
-
//#region src/disposer-chain.ts
|
|
4
|
-
var DisposerChain = class {
|
|
5
|
-
disposers = [];
|
|
6
|
-
disposed = false;
|
|
7
|
-
onError;
|
|
8
|
-
constructor(opts = {}) {
|
|
9
|
-
this.onError = opts.onError ?? ((err, index) => {
|
|
10
|
-
console.error(`[DisposerChain] disposer #${index} threw`, err);
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Register a teardown callback. Returns an unregister function so
|
|
15
|
-
* callers can drop a single disposer without disposing the whole
|
|
16
|
-
* chain.
|
|
17
|
-
*
|
|
18
|
-
* If the chain has already been disposed, the callback runs immediately
|
|
19
|
-
* (sync) — this matches the “register-after-shutdown” edge case where
|
|
20
|
-
* an addon's late initialization races with kernel restart.
|
|
21
|
-
*/
|
|
22
|
-
add(fn) {
|
|
23
|
-
if (this.disposed) {
|
|
24
|
-
try {
|
|
25
|
-
const result = fn();
|
|
26
|
-
if (result && typeof result.then === "function") result.catch((err) => this.onError(err, -1));
|
|
27
|
-
} catch (err) {
|
|
28
|
-
this.onError(err, -1);
|
|
29
|
-
}
|
|
30
|
-
return () => void 0;
|
|
31
|
-
}
|
|
32
|
-
this.disposers.push(fn);
|
|
33
|
-
return () => {
|
|
34
|
-
const idx = this.disposers.indexOf(fn);
|
|
35
|
-
if (idx >= 0) this.disposers.splice(idx, 1);
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
/** True after `dispose()` has been called at least once. */
|
|
39
|
-
get isDisposed() {
|
|
40
|
-
return this.disposed;
|
|
41
|
-
}
|
|
42
|
-
/** Number of disposers currently registered. */
|
|
43
|
-
get size() {
|
|
44
|
-
return this.disposers.length;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Run every registered disposer in LIFO order. Idempotent: subsequent
|
|
48
|
-
* calls do nothing. Awaits async disposers so callers can sequence
|
|
49
|
-
* shutdown → restart correctly.
|
|
50
|
-
*/
|
|
51
|
-
async dispose() {
|
|
52
|
-
if (this.disposed) return;
|
|
53
|
-
this.disposed = true;
|
|
54
|
-
const drain = this.disposers.slice().toReversed();
|
|
55
|
-
this.disposers = [];
|
|
56
|
-
for (let i = 0; i < drain.length; i++) {
|
|
57
|
-
const fn = drain[i];
|
|
58
|
-
try {
|
|
59
|
-
const result = fn();
|
|
60
|
-
if (result && typeof result.then === "function") await result;
|
|
61
|
-
} catch (err) {
|
|
62
|
-
this.onError(err, drain.length - 1 - i);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
//#endregion
|
|
68
3
|
//#region src/interfaces/config-ui.ts
|
|
69
4
|
/** Predefined tabs with standard label, icon, and sort order.
|
|
70
5
|
*
|
|
@@ -1084,6 +1019,80 @@ function normalizeAddonInitResult(result) {
|
|
|
1084
1019
|
return result;
|
|
1085
1020
|
}
|
|
1086
1021
|
//#endregion
|
|
1022
|
+
//#region src/disposer-chain.ts
|
|
1023
|
+
var DisposerChain = class {
|
|
1024
|
+
disposers = [];
|
|
1025
|
+
disposed = false;
|
|
1026
|
+
onError;
|
|
1027
|
+
constructor(opts = {}) {
|
|
1028
|
+
this.onError = opts.onError ?? ((err, index) => {
|
|
1029
|
+
console.error(`[DisposerChain] disposer #${index} threw`, err);
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Register a teardown callback. Returns an unregister function so
|
|
1034
|
+
* callers can drop a single disposer without disposing the whole
|
|
1035
|
+
* chain.
|
|
1036
|
+
*
|
|
1037
|
+
* If the chain has already been disposed, the callback runs immediately
|
|
1038
|
+
* (sync) — this matches the “register-after-shutdown” edge case where
|
|
1039
|
+
* an addon's late initialization races with kernel restart.
|
|
1040
|
+
*/
|
|
1041
|
+
add(fn) {
|
|
1042
|
+
if (this.disposed) {
|
|
1043
|
+
try {
|
|
1044
|
+
const result = fn();
|
|
1045
|
+
if (result && typeof result.then === "function") result.catch((err) => this.onError(err, -1));
|
|
1046
|
+
} catch (err) {
|
|
1047
|
+
this.onError(err, -1);
|
|
1048
|
+
}
|
|
1049
|
+
return () => void 0;
|
|
1050
|
+
}
|
|
1051
|
+
this.disposers.push(fn);
|
|
1052
|
+
return () => {
|
|
1053
|
+
const idx = this.disposers.indexOf(fn);
|
|
1054
|
+
if (idx >= 0) this.disposers.splice(idx, 1);
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
/** True after `dispose()` has been called at least once. */
|
|
1058
|
+
get isDisposed() {
|
|
1059
|
+
return this.disposed;
|
|
1060
|
+
}
|
|
1061
|
+
/** Number of disposers currently registered. */
|
|
1062
|
+
get size() {
|
|
1063
|
+
return this.disposers.length;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Run every registered disposer in LIFO order. Idempotent: subsequent
|
|
1067
|
+
* calls do nothing. Awaits async disposers so callers can sequence
|
|
1068
|
+
* shutdown → restart correctly.
|
|
1069
|
+
*/
|
|
1070
|
+
async dispose() {
|
|
1071
|
+
if (this.disposed) return;
|
|
1072
|
+
this.disposed = true;
|
|
1073
|
+
const drain = this.disposers.slice().toReversed();
|
|
1074
|
+
this.disposers = [];
|
|
1075
|
+
for (let i = 0; i < drain.length; i++) {
|
|
1076
|
+
const fn = drain[i];
|
|
1077
|
+
try {
|
|
1078
|
+
const result = fn();
|
|
1079
|
+
if (result && typeof result.then === "function") await result;
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
this.onError(err, drain.length - 1 - i);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
//#endregion
|
|
1087
|
+
//#region src/interfaces/addon-data-plane.ts
|
|
1088
|
+
/**
|
|
1089
|
+
* Per-listener shared-secret header the hub injects on every reverse-proxied
|
|
1090
|
+
* data-plane request and the addon's framework wrapper validates — so only the
|
|
1091
|
+
* hub can reach the addon's `127.0.0.1` listener. Defined here (shared by the
|
|
1092
|
+
* core proxy and the kernel listener) to keep both off a kernel↔core import.
|
|
1093
|
+
*/
|
|
1094
|
+
var DATAPLANE_SECRET_HEADER = "x-camstack-dataplane-secret";
|
|
1095
|
+
//#endregion
|
|
1087
1096
|
//#region src/capabilities/schemas/streaming-shared.ts
|
|
1088
1097
|
/** Shared Zod schemas used across streaming capabilities. */
|
|
1089
1098
|
var CamProfileSchema = zod.z.enum([
|
|
@@ -1898,15 +1907,6 @@ function emitDownForOwnedCaps(registry, owned) {
|
|
|
1898
1907
|
for (const { capName, scope } of owned) registry.emitDown(capName, scope);
|
|
1899
1908
|
}
|
|
1900
1909
|
//#endregion
|
|
1901
|
-
//#region src/interfaces/addon-data-plane.ts
|
|
1902
|
-
/**
|
|
1903
|
-
* Per-listener shared-secret header the hub injects on every reverse-proxied
|
|
1904
|
-
* data-plane request and the addon's framework wrapper validates — so only the
|
|
1905
|
-
* hub can reach the addon's `127.0.0.1` listener. Defined here (shared by the
|
|
1906
|
-
* core proxy and the kernel listener) to keep both off a kernel↔core import.
|
|
1907
|
-
*/
|
|
1908
|
-
var DATAPLANE_SECRET_HEADER = "x-camstack-dataplane-secret";
|
|
1909
|
-
//#endregion
|
|
1910
1910
|
//#region src/cap-call-context.ts
|
|
1911
1911
|
/**
|
|
1912
1912
|
* Per-call node pinning for `ctx.api` capability calls.
|
|
@@ -1948,171 +1948,180 @@ function readNodePin(context) {
|
|
|
1948
1948
|
return typeof value === "string" ? value : void 0;
|
|
1949
1949
|
}
|
|
1950
1950
|
//#endregion
|
|
1951
|
-
//#region src/
|
|
1951
|
+
//#region src/capabilities/capability-definition.ts
|
|
1952
1952
|
/**
|
|
1953
|
-
*
|
|
1953
|
+
* Generic types for capability definitions.
|
|
1954
1954
|
*
|
|
1955
|
-
*
|
|
1956
|
-
*
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1955
|
+
* A capability is defined with Zod schemas for methods, events, and settings.
|
|
1956
|
+
* TypeScript types are inferred via z.infer<> — zero duplication.
|
|
1957
|
+
*
|
|
1958
|
+
* Pattern:
|
|
1959
|
+
* 1. Define Zod schemas for data, methods, settings
|
|
1960
|
+
* 2. Export const capabilityDef = { ... } satisfies CapabilityDefinition
|
|
1961
|
+
* 3. Export type IProvider = InferProvider<typeof capabilityDef>
|
|
1962
|
+
* 4. Addon implements IProvider
|
|
1963
|
+
* 5. Registry auto-mounts tRPC router from definition.methods
|
|
1959
1964
|
*/
|
|
1960
1965
|
/**
|
|
1961
|
-
*
|
|
1962
|
-
*
|
|
1963
|
-
*
|
|
1964
|
-
*
|
|
1965
|
-
* known protocol schema). This is a **type-level bridge only** — no runtime
|
|
1966
|
-
* validation is performed. Callers that need runtime validation should parse
|
|
1967
|
-
* as `unknown` and narrow via Zod or structural guards.
|
|
1966
|
+
* Resolve the EFFECTIVE mount hint for a cap — the explicit `mount` when
|
|
1967
|
+
* present, else the scope/mode/deviceNative-derived default. Single source
|
|
1968
|
+
* of truth shared by the runtime builder and any codegen that needs the
|
|
1969
|
+
* same classification, so the two can never disagree.
|
|
1968
1970
|
*/
|
|
1969
|
-
function
|
|
1970
|
-
return
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1975
|
-
return { ...value };
|
|
1976
|
-
}
|
|
1977
|
-
/** Narrow an unknown value to a `readonly unknown[]` or return an empty array. */
|
|
1978
|
-
function asJsonArray(value) {
|
|
1979
|
-
return Array.isArray(value) ? value : [];
|
|
1980
|
-
}
|
|
1981
|
-
/** Safe string extraction from an unknown record field. */
|
|
1982
|
-
function asString(value, fallback = "") {
|
|
1983
|
-
return typeof value === "string" ? value : fallback;
|
|
1984
|
-
}
|
|
1985
|
-
/** Safe number extraction from an unknown record field. */
|
|
1986
|
-
function asNumber(value, fallback = 0) {
|
|
1987
|
-
return typeof value === "number" ? value : fallback;
|
|
1988
|
-
}
|
|
1989
|
-
/** Safe boolean extraction from an unknown record field. */
|
|
1990
|
-
function asBoolean(value, fallback = false) {
|
|
1991
|
-
return typeof value === "boolean" ? value : fallback;
|
|
1992
|
-
}
|
|
1993
|
-
/** Parse JSON + narrow to object in one step. */
|
|
1994
|
-
function parseJsonObject(text) {
|
|
1995
|
-
try {
|
|
1996
|
-
return asJsonObject(parseJsonUnknown(text));
|
|
1997
|
-
} catch {
|
|
1998
|
-
return null;
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
/** Parse JSON + narrow to array in one step. */
|
|
2002
|
-
function parseJsonArray(text) {
|
|
2003
|
-
try {
|
|
2004
|
-
const parsed = parseJsonUnknown(text);
|
|
2005
|
-
return Array.isArray(parsed) ? parsed : null;
|
|
2006
|
-
} catch {
|
|
2007
|
-
return null;
|
|
2008
|
-
}
|
|
1971
|
+
function resolveCapMount(def) {
|
|
1972
|
+
if (def.mount) return def.mount;
|
|
1973
|
+
if (def.deviceNative === true) return { kind: "device-native" };
|
|
1974
|
+
if (def.mode === "collection") return { kind: "collection" };
|
|
1975
|
+
return { kind: "singleton" };
|
|
2009
1976
|
}
|
|
2010
|
-
//#endregion
|
|
2011
|
-
//#region src/generated/device-scoped-caps.ts
|
|
2012
1977
|
/**
|
|
2013
|
-
*
|
|
1978
|
+
* Output schema shared by the contribution + live methods.
|
|
2014
1979
|
*
|
|
2015
|
-
*
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
1980
|
+
* Mirrors the `ConfigUISchemaWithValues` shape (sections[] + optional
|
|
1981
|
+
* tabs[]) without importing from `../interfaces/config-ui.js` — a
|
|
1982
|
+
* concrete-but-lenient Zod object keeps tRPC output inference happy
|
|
1983
|
+
* (using `z.unknown()` here collapses unrelated router branches to
|
|
1984
|
+
* `unknown` when the generator re-inlines the huge AppRouter type).
|
|
2018
1985
|
*
|
|
2019
|
-
*
|
|
1986
|
+
* `.passthrough()` on sections/fields accepts whatever FormBuilder
|
|
1987
|
+
* extensions the caller adds (showWhen, displayScale, …) without
|
|
1988
|
+
* rebuilding every time a new field kind is introduced.
|
|
2020
1989
|
*/
|
|
2021
|
-
var
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
"
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
"
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
"numeric-sensor",
|
|
2072
|
-
"osd",
|
|
2073
|
-
"pet-feeder",
|
|
2074
|
-
"pipeline-analytics",
|
|
2075
|
-
"power-meter",
|
|
2076
|
-
"presence",
|
|
2077
|
-
"pressure-sensor",
|
|
2078
|
-
"privacy-mask",
|
|
2079
|
-
"ptz",
|
|
2080
|
-
"ptz-autotrack",
|
|
2081
|
-
"reboot",
|
|
2082
|
-
"scene-monitor",
|
|
2083
|
-
"script-runner",
|
|
2084
|
-
"smoke",
|
|
2085
|
-
"snapshot",
|
|
2086
|
-
"stream-catalog",
|
|
2087
|
-
"stream-params",
|
|
2088
|
-
"switch",
|
|
2089
|
-
"tamper",
|
|
2090
|
-
"temperature-sensor",
|
|
2091
|
-
"update",
|
|
2092
|
-
"vacuum-control",
|
|
2093
|
-
"valve",
|
|
2094
|
-
"vibration",
|
|
2095
|
-
"videoclips",
|
|
2096
|
-
"water-heater",
|
|
2097
|
-
"weather",
|
|
2098
|
-
"webrtc-session",
|
|
2099
|
-
"zone-analytics",
|
|
2100
|
-
"zone-rules",
|
|
2101
|
-
"zones"
|
|
2102
|
-
]);
|
|
1990
|
+
var ContributionSectionSchema = zod.z.object({
|
|
1991
|
+
id: zod.z.string(),
|
|
1992
|
+
title: zod.z.string(),
|
|
1993
|
+
description: zod.z.string().optional(),
|
|
1994
|
+
style: zod.z.enum(["card", "accordion"]).optional(),
|
|
1995
|
+
defaultCollapsed: zod.z.boolean().optional(),
|
|
1996
|
+
columns: zod.z.union([
|
|
1997
|
+
zod.z.literal(1),
|
|
1998
|
+
zod.z.literal(2),
|
|
1999
|
+
zod.z.literal(3),
|
|
2000
|
+
zod.z.literal(4)
|
|
2001
|
+
]).optional(),
|
|
2002
|
+
tab: zod.z.string().optional(),
|
|
2003
|
+
location: zod.z.enum(["settings", "top-tab"]).optional(),
|
|
2004
|
+
order: zod.z.number().optional(),
|
|
2005
|
+
fields: zod.z.array(zod.z.any())
|
|
2006
|
+
});
|
|
2007
|
+
var ContributionTabSchema = zod.z.object({
|
|
2008
|
+
id: zod.z.string(),
|
|
2009
|
+
label: zod.z.string(),
|
|
2010
|
+
icon: zod.z.string(),
|
|
2011
|
+
order: zod.z.number().optional()
|
|
2012
|
+
});
|
|
2013
|
+
var ContributionOutputSchema = zod.z.object({
|
|
2014
|
+
tabs: zod.z.array(ContributionTabSchema).optional(),
|
|
2015
|
+
sections: zod.z.array(ContributionSectionSchema)
|
|
2016
|
+
}).nullable();
|
|
2017
|
+
var DEVICE_SETTINGS_CONTRIBUTION_METHODS = {
|
|
2018
|
+
getDeviceSettingsContribution: {
|
|
2019
|
+
input: zod.z.object({ deviceId: zod.z.number() }),
|
|
2020
|
+
output: ContributionOutputSchema,
|
|
2021
|
+
kind: "query",
|
|
2022
|
+
auth: "protected"
|
|
2023
|
+
},
|
|
2024
|
+
getDeviceLiveContribution: {
|
|
2025
|
+
input: zod.z.object({ deviceId: zod.z.number() }),
|
|
2026
|
+
output: ContributionOutputSchema,
|
|
2027
|
+
kind: "query",
|
|
2028
|
+
auth: "protected"
|
|
2029
|
+
},
|
|
2030
|
+
applyDeviceSettingsPatch: {
|
|
2031
|
+
input: zod.z.object({
|
|
2032
|
+
deviceId: zod.z.number(),
|
|
2033
|
+
patch: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
2034
|
+
}),
|
|
2035
|
+
output: zod.z.object({ success: zod.z.literal(true) }),
|
|
2036
|
+
kind: "mutation",
|
|
2037
|
+
auth: "admin"
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2103
2040
|
/**
|
|
2104
|
-
*
|
|
2041
|
+
* Schema for the `getStatus` method auto-injected when a cap declares
|
|
2042
|
+
* `status`. The runtime shape of the output is the cap's own
|
|
2043
|
+
* `status.schema` — but at codegen time we need a concrete Zod to emit
|
|
2044
|
+
* a typed tRPC route, so we keep the output as `z.unknown().nullable()`
|
|
2045
|
+
* here and tighten it on the client side via the generated
|
|
2046
|
+
* `CapStatusTypeMap` (see `scripts/generate-cap-status-types.ts`).
|
|
2047
|
+
*/
|
|
2048
|
+
var DEVICE_STATUS_METHOD = { getStatus: {
|
|
2049
|
+
input: zod.z.object({ deviceId: zod.z.number() }),
|
|
2050
|
+
output: zod.z.unknown().nullable(),
|
|
2051
|
+
kind: "query",
|
|
2052
|
+
auth: "protected"
|
|
2053
|
+
} };
|
|
2054
|
+
/**
|
|
2055
|
+
* Expand a cap def's methods map with every auto-injected method:
|
|
2056
|
+
* - `exposesDeviceSettings: true` → 3 contribution methods
|
|
2057
|
+
* - `status: {...}` → `getStatus`
|
|
2105
2058
|
*
|
|
2106
|
-
*
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2059
|
+
* Callers walk `expandCapMethods(def)` instead of `def.methods` when
|
|
2060
|
+
* they need the effective runtime method surface (Moleculer actions,
|
|
2061
|
+
* proxy shape, tRPC router entries). The cap def stays the single
|
|
2062
|
+
* source of truth. Providers still declare their concrete `methods`
|
|
2063
|
+
* block; the expansion happens at every consumption point, so there's
|
|
2064
|
+
* no accidental divergence between the declared surface and what's
|
|
2065
|
+
* actually mounted.
|
|
2111
2066
|
*/
|
|
2112
|
-
function
|
|
2113
|
-
|
|
2067
|
+
function expandCapMethods(def) {
|
|
2068
|
+
let out = def.methods;
|
|
2069
|
+
if (def.exposesDeviceSettings) out = {
|
|
2070
|
+
...DEVICE_SETTINGS_CONTRIBUTION_METHODS,
|
|
2071
|
+
...out
|
|
2072
|
+
};
|
|
2073
|
+
if (def.status) out = {
|
|
2074
|
+
...DEVICE_STATUS_METHOD,
|
|
2075
|
+
...out
|
|
2076
|
+
};
|
|
2077
|
+
return out;
|
|
2078
|
+
}
|
|
2079
|
+
function method(input, output, options) {
|
|
2080
|
+
return {
|
|
2081
|
+
input,
|
|
2082
|
+
output,
|
|
2083
|
+
kind: options?.kind ?? "query",
|
|
2084
|
+
auth: options?.auth ?? "protected",
|
|
2085
|
+
...options?.access !== void 0 ? { access: options.access } : {},
|
|
2086
|
+
...options?.caller !== void 0 ? { caller: options.caller } : {},
|
|
2087
|
+
timeoutMs: options?.timeoutMs
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* A wrapper/system-only method: served exclusively by the cap's system-level
|
|
2092
|
+
* provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
|
|
2093
|
+
* driver natives don't stub out a wrapper concern (e.g. a cross-device cache
|
|
2094
|
+
* overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
|
|
2095
|
+
*/
|
|
2096
|
+
function systemMethod(input, output, options) {
|
|
2097
|
+
return {
|
|
2098
|
+
...method(input, output, options),
|
|
2099
|
+
systemOnly: true
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
/** Shorthand to define an event schema */
|
|
2103
|
+
function event(data) {
|
|
2104
|
+
return { data };
|
|
2105
|
+
}
|
|
2106
|
+
/** Type guard: does this cap declare the D14 device-config archetype? */
|
|
2107
|
+
function isDeviceConfigCap(def) {
|
|
2108
|
+
return def?.deviceConfig !== void 0;
|
|
2114
2109
|
}
|
|
2115
2110
|
//#endregion
|
|
2111
|
+
//#region src/capabilities/admin-ui.cap.ts
|
|
2112
|
+
var StaticDirOutputSchema$1 = zod.z.object({ staticDir: zod.z.string() });
|
|
2113
|
+
var VersionOutputSchema$1 = zod.z.object({ version: zod.z.string() });
|
|
2114
|
+
var adminUiCapability = {
|
|
2115
|
+
name: "admin-ui",
|
|
2116
|
+
scope: "system",
|
|
2117
|
+
mode: "singleton",
|
|
2118
|
+
internal: true,
|
|
2119
|
+
methods: {
|
|
2120
|
+
getStaticDir: method(zod.z.void(), StaticDirOutputSchema$1),
|
|
2121
|
+
getVersion: method(zod.z.void(), VersionOutputSchema$1)
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
//#endregion
|
|
2116
2125
|
//#region src/device/device-type.ts
|
|
2117
2126
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
2118
2127
|
DeviceType["Camera"] = "camera";
|
|
@@ -2439,165 +2448,144 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
2439
2448
|
return DeviceRole;
|
|
2440
2449
|
}({});
|
|
2441
2450
|
//#endregion
|
|
2442
|
-
//#region src/capabilities/
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
*
|
|
2446
|
-
* A capability is defined with Zod schemas for methods, events, and settings.
|
|
2447
|
-
* TypeScript types are inferred via z.infer<> — zero duplication.
|
|
2448
|
-
*
|
|
2449
|
-
* Pattern:
|
|
2450
|
-
* 1. Define Zod schemas for data, methods, settings
|
|
2451
|
-
* 2. Export const capabilityDef = { ... } satisfies CapabilityDefinition
|
|
2452
|
-
* 3. Export type IProvider = InferProvider<typeof capabilityDef>
|
|
2453
|
-
* 4. Addon implements IProvider
|
|
2454
|
-
* 5. Registry auto-mounts tRPC router from definition.methods
|
|
2455
|
-
*/
|
|
2451
|
+
//#region src/capabilities/viewer-ui.cap.ts
|
|
2452
|
+
var StaticDirOutputSchema = zod.z.object({ staticDir: zod.z.string() });
|
|
2453
|
+
var VersionOutputSchema = zod.z.object({ version: zod.z.string() });
|
|
2456
2454
|
/**
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2459
|
-
*
|
|
2460
|
-
*
|
|
2455
|
+
* viewer-ui — serves the CamStack VIEWER web build (the Expo/React-Native
|
|
2456
|
+
* universal client's PWA export) from the hub, alongside admin-ui. Mirrors
|
|
2457
|
+
* `admin-ui` exactly: a singleton, server-internal cap exposing the static
|
|
2458
|
+
* dist directory + build version so `main.ts` can mount the SPA under a
|
|
2459
|
+
* dedicated prefix.
|
|
2461
2460
|
*/
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2461
|
+
var viewerUiCapability = {
|
|
2462
|
+
name: "viewer-ui",
|
|
2463
|
+
scope: "system",
|
|
2464
|
+
mode: "singleton",
|
|
2465
|
+
internal: true,
|
|
2466
|
+
methods: {
|
|
2467
|
+
getStaticDir: method(zod.z.void(), StaticDirOutputSchema),
|
|
2468
|
+
getVersion: method(zod.z.void(), VersionOutputSchema)
|
|
2469
|
+
}
|
|
2470
|
+
};
|
|
2471
|
+
//#endregion
|
|
2472
|
+
//#region src/capabilities/device-ops.cap.ts
|
|
2468
2473
|
/**
|
|
2469
|
-
*
|
|
2470
|
-
*
|
|
2471
|
-
* Mirrors the `ConfigUISchemaWithValues` shape (sections[] + optional
|
|
2472
|
-
* tabs[]) without importing from `../interfaces/config-ui.js` — a
|
|
2473
|
-
* concrete-but-lenient Zod object keeps tRPC output inference happy
|
|
2474
|
-
* (using `z.unknown()` here collapses unrelated router branches to
|
|
2475
|
-
* `unknown` when the generator re-inlines the huge AppRouter type).
|
|
2474
|
+
* device-ops — device-scoped cap that unifies the per-IDevice operations
|
|
2475
|
+
* previously routed through the `.device-ops` Moleculer bridge service.
|
|
2476
2476
|
*
|
|
2477
|
-
*
|
|
2478
|
-
*
|
|
2479
|
-
*
|
|
2477
|
+
* Each worker that hosts live `IDevice` instances auto-registers a native
|
|
2478
|
+
* provider for this cap (per device) backed by its local
|
|
2479
|
+
* `DeviceRegistry`. Hub-side callers reach it transparently through
|
|
2480
|
+
* `ctx.fetchDevice(id).deviceOps.*` — the DeviceProxy injects
|
|
2481
|
+
* `deviceId` + `nodeId` and dispatches through the standard cap-router,
|
|
2482
|
+
* so there's no parallel bridge path anymore.
|
|
2483
|
+
*
|
|
2484
|
+
* The surface is intentionally small — every method corresponds to a
|
|
2485
|
+
* single action on the live `IDevice` (or `ICameraDevice` for
|
|
2486
|
+
* `getStreamSources`). Richer orchestration (enable/disable with
|
|
2487
|
+
* integration plumbing, bulk updates) stays in the `device-manager` cap;
|
|
2488
|
+
* `device-ops` is the per-device primitive the device-manager routes to.
|
|
2480
2489
|
*/
|
|
2481
|
-
var
|
|
2482
|
-
id: zod.z.string(),
|
|
2483
|
-
title: zod.z.string(),
|
|
2484
|
-
description: zod.z.string().optional(),
|
|
2485
|
-
style: zod.z.enum(["card", "accordion"]).optional(),
|
|
2486
|
-
defaultCollapsed: zod.z.boolean().optional(),
|
|
2487
|
-
columns: zod.z.union([
|
|
2488
|
-
zod.z.literal(1),
|
|
2489
|
-
zod.z.literal(2),
|
|
2490
|
-
zod.z.literal(3),
|
|
2491
|
-
zod.z.literal(4)
|
|
2492
|
-
]).optional(),
|
|
2493
|
-
tab: zod.z.string().optional(),
|
|
2494
|
-
location: zod.z.enum(["settings", "top-tab"]).optional(),
|
|
2495
|
-
order: zod.z.number().optional(),
|
|
2496
|
-
fields: zod.z.array(zod.z.any())
|
|
2497
|
-
});
|
|
2498
|
-
var ContributionTabSchema = zod.z.object({
|
|
2490
|
+
var StreamSourceEntrySchema = zod.z.object({
|
|
2499
2491
|
id: zod.z.string(),
|
|
2500
2492
|
label: zod.z.string(),
|
|
2501
|
-
|
|
2502
|
-
|
|
2493
|
+
protocol: zod.z.enum([
|
|
2494
|
+
"rtsp",
|
|
2495
|
+
"rtmp",
|
|
2496
|
+
"annexb",
|
|
2497
|
+
"http-mjpeg",
|
|
2498
|
+
"webrtc",
|
|
2499
|
+
"custom"
|
|
2500
|
+
]),
|
|
2501
|
+
url: zod.z.string().optional(),
|
|
2502
|
+
resolution: zod.z.object({
|
|
2503
|
+
width: zod.z.number(),
|
|
2504
|
+
height: zod.z.number()
|
|
2505
|
+
}).optional(),
|
|
2506
|
+
fps: zod.z.number().optional(),
|
|
2507
|
+
bitrate: zod.z.number().optional(),
|
|
2508
|
+
codec: zod.z.string().optional(),
|
|
2509
|
+
profileHint: CamProfileSchema.optional(),
|
|
2510
|
+
sdp: zod.z.string().optional()
|
|
2503
2511
|
});
|
|
2504
|
-
var
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
})
|
|
2508
|
-
var
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2512
|
+
var ConfigEntrySchema = zod.z.object({
|
|
2513
|
+
key: zod.z.string(),
|
|
2514
|
+
value: zod.z.unknown()
|
|
2515
|
+
});
|
|
2516
|
+
var RawStateResultSchema = zod.z.object({
|
|
2517
|
+
/** Originating provider id, e.g. 'homeassistant' | 'reolink' | 'hikvision'. */
|
|
2518
|
+
source: zod.z.string(),
|
|
2519
|
+
/** Opaque, DISPLAY-SAFE upstream blob (no secrets/PII). */
|
|
2520
|
+
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
2521
|
+
});
|
|
2522
|
+
var deviceOpsCapability = {
|
|
2523
|
+
name: "device-ops",
|
|
2524
|
+
scope: "device",
|
|
2525
|
+
deviceNative: true,
|
|
2526
|
+
mode: "singleton",
|
|
2527
|
+
methods: {
|
|
2528
|
+
/**
|
|
2529
|
+
* Return stream sources for camera-like devices. Non-camera devices
|
|
2530
|
+
* return an empty array (the bridge did the same; preserved for compat).
|
|
2531
|
+
*/
|
|
2532
|
+
getStreamSources: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(StreamSourceEntrySchema)),
|
|
2533
|
+
/**
|
|
2534
|
+
* Return the device's config entries (key + current value). Used by
|
|
2535
|
+
* the device-manager aggregator when reading the driver's schema+values.
|
|
2536
|
+
*/
|
|
2537
|
+
getConfigEntries: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(ConfigEntrySchema)),
|
|
2538
|
+
/**
|
|
2539
|
+
* Bulk-apply a config patch via `IDevice.config.setAll`. Covers the
|
|
2540
|
+
* updateConfig / setStreamProfileMap / enable-as-config paths from
|
|
2541
|
+
* the old bridge.
|
|
2542
|
+
*/
|
|
2543
|
+
setConfig: method(zod.z.object({
|
|
2523
2544
|
deviceId: zod.z.number(),
|
|
2524
|
-
|
|
2525
|
-
}),
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2545
|
+
values: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
2546
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
2547
|
+
/**
|
|
2548
|
+
* Invoke a device custom action on a forked/remote device (the
|
|
2549
|
+
* cross-process transport for `IDevice.runDeviceAction`). Mirrors
|
|
2550
|
+
* `setConfig` — the device-manager calls this when the device is not
|
|
2551
|
+
* hub-local.
|
|
2552
|
+
*/
|
|
2553
|
+
runAction: method(zod.z.object({
|
|
2554
|
+
deviceId: zod.z.number(),
|
|
2555
|
+
action: zod.z.string().min(1),
|
|
2556
|
+
input: zod.z.unknown()
|
|
2557
|
+
}), zod.z.unknown(), { kind: "mutation" }),
|
|
2558
|
+
/**
|
|
2559
|
+
* Invoke `IDevice.removeDevice()` so the driver can release resources
|
|
2560
|
+
* (close sockets, stop background tasks, …). The device-manager still
|
|
2561
|
+
* performs its own persistence cleanup before/after this call.
|
|
2562
|
+
*/
|
|
2563
|
+
removeDevice: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
2564
|
+
/**
|
|
2565
|
+
* Build the ConfigUISchema (FormBuilder input shape) from the device's
|
|
2566
|
+
* Zod config schema. Runs on the worker that owns the IDevice so the
|
|
2567
|
+
* Zod types stay local (they're function references, not
|
|
2568
|
+
* serializable). Returns a fully JSON-serializable schema with
|
|
2569
|
+
* sections/fields the admin UI renders directly.
|
|
2570
|
+
*
|
|
2571
|
+
* Needed because the hub-side `device-manager.getSettingsSchema`
|
|
2572
|
+
* couldn't reach forked-worker devices — it had no registry entry
|
|
2573
|
+
* and no cross-process lookup, so the UI silently rendered an empty
|
|
2574
|
+
* settings panel for every worker-owned device.
|
|
2575
|
+
*
|
|
2576
|
+
* Returns `null` when the device isn't found on this worker.
|
|
2577
|
+
*/
|
|
2578
|
+
getSettingsSchema: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.unknown().nullable()),
|
|
2579
|
+
/**
|
|
2580
|
+
* Opt-in: return the device's RAW upstream state (the provider's
|
|
2581
|
+
* cached values) as a display-safe `{ source, data }` blob. Returns
|
|
2582
|
+
* `null` when the device exposes no raw state — the State panel hides
|
|
2583
|
+
* its Raw toggle in that case. One-shot (read the provider's existing
|
|
2584
|
+
* cache; no upstream round-trip).
|
|
2585
|
+
*/
|
|
2586
|
+
getRawState: method(zod.z.object({ deviceId: zod.z.number() }), RawStateResultSchema.nullable(), { auth: "protected" })
|
|
2529
2587
|
}
|
|
2530
2588
|
};
|
|
2531
|
-
/**
|
|
2532
|
-
* Schema for the `getStatus` method auto-injected when a cap declares
|
|
2533
|
-
* `status`. The runtime shape of the output is the cap's own
|
|
2534
|
-
* `status.schema` — but at codegen time we need a concrete Zod to emit
|
|
2535
|
-
* a typed tRPC route, so we keep the output as `z.unknown().nullable()`
|
|
2536
|
-
* here and tighten it on the client side via the generated
|
|
2537
|
-
* `CapStatusTypeMap` (see `scripts/generate-cap-status-types.ts`).
|
|
2538
|
-
*/
|
|
2539
|
-
var DEVICE_STATUS_METHOD = { getStatus: {
|
|
2540
|
-
input: zod.z.object({ deviceId: zod.z.number() }),
|
|
2541
|
-
output: zod.z.unknown().nullable(),
|
|
2542
|
-
kind: "query",
|
|
2543
|
-
auth: "protected"
|
|
2544
|
-
} };
|
|
2545
|
-
/**
|
|
2546
|
-
* Expand a cap def's methods map with every auto-injected method:
|
|
2547
|
-
* - `exposesDeviceSettings: true` → 3 contribution methods
|
|
2548
|
-
* - `status: {...}` → `getStatus`
|
|
2549
|
-
*
|
|
2550
|
-
* Callers walk `expandCapMethods(def)` instead of `def.methods` when
|
|
2551
|
-
* they need the effective runtime method surface (Moleculer actions,
|
|
2552
|
-
* proxy shape, tRPC router entries). The cap def stays the single
|
|
2553
|
-
* source of truth. Providers still declare their concrete `methods`
|
|
2554
|
-
* block; the expansion happens at every consumption point, so there's
|
|
2555
|
-
* no accidental divergence between the declared surface and what's
|
|
2556
|
-
* actually mounted.
|
|
2557
|
-
*/
|
|
2558
|
-
function expandCapMethods(def) {
|
|
2559
|
-
let out = def.methods;
|
|
2560
|
-
if (def.exposesDeviceSettings) out = {
|
|
2561
|
-
...DEVICE_SETTINGS_CONTRIBUTION_METHODS,
|
|
2562
|
-
...out
|
|
2563
|
-
};
|
|
2564
|
-
if (def.status) out = {
|
|
2565
|
-
...DEVICE_STATUS_METHOD,
|
|
2566
|
-
...out
|
|
2567
|
-
};
|
|
2568
|
-
return out;
|
|
2569
|
-
}
|
|
2570
|
-
function method(input, output, options) {
|
|
2571
|
-
return {
|
|
2572
|
-
input,
|
|
2573
|
-
output,
|
|
2574
|
-
kind: options?.kind ?? "query",
|
|
2575
|
-
auth: options?.auth ?? "protected",
|
|
2576
|
-
...options?.access !== void 0 ? { access: options.access } : {},
|
|
2577
|
-
...options?.caller !== void 0 ? { caller: options.caller } : {},
|
|
2578
|
-
timeoutMs: options?.timeoutMs
|
|
2579
|
-
};
|
|
2580
|
-
}
|
|
2581
|
-
/**
|
|
2582
|
-
* A wrapper/system-only method: served exclusively by the cap's system-level
|
|
2583
|
-
* provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
|
|
2584
|
-
* driver natives don't stub out a wrapper concern (e.g. a cross-device cache
|
|
2585
|
-
* overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
|
|
2586
|
-
*/
|
|
2587
|
-
function systemMethod(input, output, options) {
|
|
2588
|
-
return {
|
|
2589
|
-
...method(input, output, options),
|
|
2590
|
-
systemOnly: true
|
|
2591
|
-
};
|
|
2592
|
-
}
|
|
2593
|
-
/** Shorthand to define an event schema */
|
|
2594
|
-
function event(data) {
|
|
2595
|
-
return { data };
|
|
2596
|
-
}
|
|
2597
|
-
/** Type guard: does this cap declare the D14 device-config archetype? */
|
|
2598
|
-
function isDeviceConfigCap(def) {
|
|
2599
|
-
return def?.deviceConfig !== void 0;
|
|
2600
|
-
}
|
|
2601
2589
|
//#endregion
|
|
2602
2590
|
//#region src/device/device-state-handle.ts
|
|
2603
2591
|
var DEVICE_STATE_EVENT_CATEGORY = "device.state-changed";
|
|
@@ -3120,7 +3108,9 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3120
3108
|
getHistory: (input) => dispatch("notification-rules", "notificationRules", "getHistory", "query", input),
|
|
3121
3109
|
listSnoozes: (input) => dispatch("notification-rules", "notificationRules", "listSnoozes", "query", input),
|
|
3122
3110
|
createSnooze: (input) => dispatch("notification-rules", "notificationRules", "createSnooze", "mutation", input),
|
|
3123
|
-
cancelSnooze: (input) => dispatch("notification-rules", "notificationRules", "cancelSnooze", "mutation", input)
|
|
3111
|
+
cancelSnooze: (input) => dispatch("notification-rules", "notificationRules", "cancelSnooze", "mutation", input),
|
|
3112
|
+
getAlarmConfig: (input) => dispatch("notification-rules", "notificationRules", "getAlarmConfig", "query", input),
|
|
3113
|
+
setAlarmConfig: (input) => dispatch("notification-rules", "notificationRules", "setAlarmConfig", "mutation", input)
|
|
3124
3114
|
},
|
|
3125
3115
|
notifier: {
|
|
3126
3116
|
send: (input) => dispatch("notifier", "notifier", "send", "mutation", input),
|
|
@@ -3445,158 +3435,170 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3445
3435
|
};
|
|
3446
3436
|
}
|
|
3447
3437
|
//#endregion
|
|
3448
|
-
//#region src/
|
|
3449
|
-
var StaticDirOutputSchema$1 = zod.z.object({ staticDir: zod.z.string() });
|
|
3450
|
-
var VersionOutputSchema$1 = zod.z.object({ version: zod.z.string() });
|
|
3451
|
-
var adminUiCapability = {
|
|
3452
|
-
name: "admin-ui",
|
|
3453
|
-
scope: "system",
|
|
3454
|
-
mode: "singleton",
|
|
3455
|
-
internal: true,
|
|
3456
|
-
methods: {
|
|
3457
|
-
getStaticDir: method(zod.z.void(), StaticDirOutputSchema$1),
|
|
3458
|
-
getVersion: method(zod.z.void(), VersionOutputSchema$1)
|
|
3459
|
-
}
|
|
3460
|
-
};
|
|
3461
|
-
//#endregion
|
|
3462
|
-
//#region src/capabilities/viewer-ui.cap.ts
|
|
3463
|
-
var StaticDirOutputSchema = zod.z.object({ staticDir: zod.z.string() });
|
|
3464
|
-
var VersionOutputSchema = zod.z.object({ version: zod.z.string() });
|
|
3438
|
+
//#region src/generated/device-scoped-caps.ts
|
|
3465
3439
|
/**
|
|
3466
|
-
*
|
|
3467
|
-
*
|
|
3468
|
-
* `
|
|
3469
|
-
*
|
|
3470
|
-
*
|
|
3440
|
+
* AUTO-GENERATED by scripts/generate-device-scoped-caps.ts — DO NOT EDIT.
|
|
3441
|
+
*
|
|
3442
|
+
* Every `scope: 'device'` capability name, as plain data — so a forked runner
|
|
3443
|
+
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
3444
|
+
* (~144MB RSS per runner, D28).
|
|
3445
|
+
*
|
|
3446
|
+
* Coverage: 80 device-scoped capabilities.
|
|
3471
3447
|
*/
|
|
3472
|
-
var
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3448
|
+
var DEVICE_SCOPED_CAPS = new Set([
|
|
3449
|
+
"accessories",
|
|
3450
|
+
"air-quality-sensor",
|
|
3451
|
+
"alarm-panel",
|
|
3452
|
+
"ambient-light-sensor",
|
|
3453
|
+
"audio-analysis",
|
|
3454
|
+
"audio-metrics",
|
|
3455
|
+
"automation-control",
|
|
3456
|
+
"battery",
|
|
3457
|
+
"binary",
|
|
3458
|
+
"brightness",
|
|
3459
|
+
"button",
|
|
3460
|
+
"camera-credentials",
|
|
3461
|
+
"camera-pipeline-config",
|
|
3462
|
+
"camera-streams",
|
|
3463
|
+
"carbon-monoxide",
|
|
3464
|
+
"climate-control",
|
|
3465
|
+
"color",
|
|
3466
|
+
"connectivity",
|
|
3467
|
+
"consumables",
|
|
3468
|
+
"contact",
|
|
3469
|
+
"control",
|
|
3470
|
+
"cover",
|
|
3471
|
+
"day-night",
|
|
3472
|
+
"detection-pipeline",
|
|
3473
|
+
"device-discovery",
|
|
3474
|
+
"device-ops",
|
|
3475
|
+
"device-status",
|
|
3476
|
+
"doorbell",
|
|
3477
|
+
"enum-sensor",
|
|
3478
|
+
"event-emitter",
|
|
3479
|
+
"events",
|
|
3480
|
+
"fan-control",
|
|
3481
|
+
"feature-probe",
|
|
3482
|
+
"flood",
|
|
3483
|
+
"gas",
|
|
3484
|
+
"humidifier",
|
|
3485
|
+
"humidity-sensor",
|
|
3486
|
+
"image",
|
|
3487
|
+
"image-settings",
|
|
3488
|
+
"intercom",
|
|
3489
|
+
"lawn-mower-control",
|
|
3490
|
+
"lock-control",
|
|
3491
|
+
"media-player",
|
|
3492
|
+
"motion",
|
|
3493
|
+
"motion-detection",
|
|
3494
|
+
"motion-trigger",
|
|
3495
|
+
"motion-zones",
|
|
3496
|
+
"native-object-detection",
|
|
3497
|
+
"notifier",
|
|
3498
|
+
"numeric-sensor",
|
|
3499
|
+
"osd",
|
|
3500
|
+
"pet-feeder",
|
|
3501
|
+
"pipeline-analytics",
|
|
3502
|
+
"power-meter",
|
|
3503
|
+
"presence",
|
|
3504
|
+
"pressure-sensor",
|
|
3505
|
+
"privacy-mask",
|
|
3506
|
+
"ptz",
|
|
3507
|
+
"ptz-autotrack",
|
|
3508
|
+
"reboot",
|
|
3509
|
+
"scene-monitor",
|
|
3510
|
+
"script-runner",
|
|
3511
|
+
"smoke",
|
|
3512
|
+
"snapshot",
|
|
3513
|
+
"stream-catalog",
|
|
3514
|
+
"stream-params",
|
|
3515
|
+
"switch",
|
|
3516
|
+
"tamper",
|
|
3517
|
+
"temperature-sensor",
|
|
3518
|
+
"update",
|
|
3519
|
+
"vacuum-control",
|
|
3520
|
+
"valve",
|
|
3521
|
+
"vibration",
|
|
3522
|
+
"videoclips",
|
|
3523
|
+
"water-heater",
|
|
3524
|
+
"weather",
|
|
3525
|
+
"webrtc-session",
|
|
3526
|
+
"zone-analytics",
|
|
3527
|
+
"zone-rules",
|
|
3528
|
+
"zones"
|
|
3529
|
+
]);
|
|
3530
|
+
/**
|
|
3531
|
+
* True when `capName` is a device capability.
|
|
3532
|
+
*
|
|
3533
|
+
* This is the ONLY boundary on what a notification rule may actuate. A rule can
|
|
3534
|
+
* be authored by a non-admin and the runner executes with the addon's
|
|
3535
|
+
* privileges, so an unbounded action would be an arbitrary RPC channel with a
|
|
3536
|
+
* privilege escalation attached. Device scope excludes the system caps
|
|
3537
|
+
* (`device-manager.removeDevice` and friends) by construction.
|
|
3538
|
+
*/
|
|
3539
|
+
function isDeviceScopedCap(capName) {
|
|
3540
|
+
return DEVICE_SCOPED_CAPS.has(capName);
|
|
3541
|
+
}
|
|
3482
3542
|
//#endregion
|
|
3483
|
-
//#region src/
|
|
3543
|
+
//#region src/utils/json-safe.ts
|
|
3484
3544
|
/**
|
|
3485
|
-
*
|
|
3486
|
-
* previously routed through the `.device-ops` Moleculer bridge service.
|
|
3545
|
+
* Type-safe JSON parsing helpers.
|
|
3487
3546
|
*
|
|
3488
|
-
*
|
|
3489
|
-
*
|
|
3490
|
-
* `
|
|
3491
|
-
* `
|
|
3492
|
-
|
|
3493
|
-
|
|
3547
|
+
* `JSON.parse` is typed as `any` in lib.es5.d.ts, which triggers
|
|
3548
|
+
* `no-unsafe-*` ESLint rules and destroys downstream inference. These
|
|
3549
|
+
* wrappers return `unknown` — callers narrow structurally via type
|
|
3550
|
+
* guards, `typeof` checks, or helpers like `asRecord`/`asString`.
|
|
3551
|
+
*/
|
|
3552
|
+
/**
|
|
3553
|
+
* Parse JSON and return it as `unknown` — the only entry point for untrusted JSON.
|
|
3494
3554
|
*
|
|
3495
|
-
* The
|
|
3496
|
-
*
|
|
3497
|
-
*
|
|
3498
|
-
*
|
|
3499
|
-
* `
|
|
3555
|
+
* The optional generic overload `parseJsonUnknown<T>(text)` returns `T` for
|
|
3556
|
+
* call sites that know the shape at parse time (e.g. MQTT payloads with a
|
|
3557
|
+
* known protocol schema). This is a **type-level bridge only** — no runtime
|
|
3558
|
+
* validation is performed. Callers that need runtime validation should parse
|
|
3559
|
+
* as `unknown` and narrow via Zod or structural guards.
|
|
3500
3560
|
*/
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
]
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
3532
|
-
});
|
|
3533
|
-
var deviceOpsCapability = {
|
|
3534
|
-
name: "device-ops",
|
|
3535
|
-
scope: "device",
|
|
3536
|
-
deviceNative: true,
|
|
3537
|
-
mode: "singleton",
|
|
3538
|
-
methods: {
|
|
3539
|
-
/**
|
|
3540
|
-
* Return stream sources for camera-like devices. Non-camera devices
|
|
3541
|
-
* return an empty array (the bridge did the same; preserved for compat).
|
|
3542
|
-
*/
|
|
3543
|
-
getStreamSources: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(StreamSourceEntrySchema)),
|
|
3544
|
-
/**
|
|
3545
|
-
* Return the device's config entries (key + current value). Used by
|
|
3546
|
-
* the device-manager aggregator when reading the driver's schema+values.
|
|
3547
|
-
*/
|
|
3548
|
-
getConfigEntries: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(ConfigEntrySchema)),
|
|
3549
|
-
/**
|
|
3550
|
-
* Bulk-apply a config patch via `IDevice.config.setAll`. Covers the
|
|
3551
|
-
* updateConfig / setStreamProfileMap / enable-as-config paths from
|
|
3552
|
-
* the old bridge.
|
|
3553
|
-
*/
|
|
3554
|
-
setConfig: method(zod.z.object({
|
|
3555
|
-
deviceId: zod.z.number(),
|
|
3556
|
-
values: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
3557
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
3558
|
-
/**
|
|
3559
|
-
* Invoke a device custom action on a forked/remote device (the
|
|
3560
|
-
* cross-process transport for `IDevice.runDeviceAction`). Mirrors
|
|
3561
|
-
* `setConfig` — the device-manager calls this when the device is not
|
|
3562
|
-
* hub-local.
|
|
3563
|
-
*/
|
|
3564
|
-
runAction: method(zod.z.object({
|
|
3565
|
-
deviceId: zod.z.number(),
|
|
3566
|
-
action: zod.z.string().min(1),
|
|
3567
|
-
input: zod.z.unknown()
|
|
3568
|
-
}), zod.z.unknown(), { kind: "mutation" }),
|
|
3569
|
-
/**
|
|
3570
|
-
* Invoke `IDevice.removeDevice()` so the driver can release resources
|
|
3571
|
-
* (close sockets, stop background tasks, …). The device-manager still
|
|
3572
|
-
* performs its own persistence cleanup before/after this call.
|
|
3573
|
-
*/
|
|
3574
|
-
removeDevice: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
3575
|
-
/**
|
|
3576
|
-
* Build the ConfigUISchema (FormBuilder input shape) from the device's
|
|
3577
|
-
* Zod config schema. Runs on the worker that owns the IDevice so the
|
|
3578
|
-
* Zod types stay local (they're function references, not
|
|
3579
|
-
* serializable). Returns a fully JSON-serializable schema with
|
|
3580
|
-
* sections/fields the admin UI renders directly.
|
|
3581
|
-
*
|
|
3582
|
-
* Needed because the hub-side `device-manager.getSettingsSchema`
|
|
3583
|
-
* couldn't reach forked-worker devices — it had no registry entry
|
|
3584
|
-
* and no cross-process lookup, so the UI silently rendered an empty
|
|
3585
|
-
* settings panel for every worker-owned device.
|
|
3586
|
-
*
|
|
3587
|
-
* Returns `null` when the device isn't found on this worker.
|
|
3588
|
-
*/
|
|
3589
|
-
getSettingsSchema: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.unknown().nullable()),
|
|
3590
|
-
/**
|
|
3591
|
-
* Opt-in: return the device's RAW upstream state (the provider's
|
|
3592
|
-
* cached values) as a display-safe `{ source, data }` blob. Returns
|
|
3593
|
-
* `null` when the device exposes no raw state — the State panel hides
|
|
3594
|
-
* its Raw toggle in that case. One-shot (read the provider's existing
|
|
3595
|
-
* cache; no upstream round-trip).
|
|
3596
|
-
*/
|
|
3597
|
-
getRawState: method(zod.z.object({ deviceId: zod.z.number() }), RawStateResultSchema.nullable(), { auth: "protected" })
|
|
3561
|
+
function parseJsonUnknown(text) {
|
|
3562
|
+
return JSON.parse(text);
|
|
3563
|
+
}
|
|
3564
|
+
/** Narrow an unknown value to a plain `Record<string, unknown>` or return null. */
|
|
3565
|
+
function asJsonObject(value) {
|
|
3566
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
3567
|
+
return { ...value };
|
|
3568
|
+
}
|
|
3569
|
+
/** Narrow an unknown value to a `readonly unknown[]` or return an empty array. */
|
|
3570
|
+
function asJsonArray(value) {
|
|
3571
|
+
return Array.isArray(value) ? value : [];
|
|
3572
|
+
}
|
|
3573
|
+
/** Safe string extraction from an unknown record field. */
|
|
3574
|
+
function asString(value, fallback = "") {
|
|
3575
|
+
return typeof value === "string" ? value : fallback;
|
|
3576
|
+
}
|
|
3577
|
+
/** Safe number extraction from an unknown record field. */
|
|
3578
|
+
function asNumber(value, fallback = 0) {
|
|
3579
|
+
return typeof value === "number" ? value : fallback;
|
|
3580
|
+
}
|
|
3581
|
+
/** Safe boolean extraction from an unknown record field. */
|
|
3582
|
+
function asBoolean(value, fallback = false) {
|
|
3583
|
+
return typeof value === "boolean" ? value : fallback;
|
|
3584
|
+
}
|
|
3585
|
+
/** Parse JSON + narrow to object in one step. */
|
|
3586
|
+
function parseJsonObject(text) {
|
|
3587
|
+
try {
|
|
3588
|
+
return asJsonObject(parseJsonUnknown(text));
|
|
3589
|
+
} catch {
|
|
3590
|
+
return null;
|
|
3598
3591
|
}
|
|
3599
|
-
}
|
|
3592
|
+
}
|
|
3593
|
+
/** Parse JSON + narrow to array in one step. */
|
|
3594
|
+
function parseJsonArray(text) {
|
|
3595
|
+
try {
|
|
3596
|
+
const parsed = parseJsonUnknown(text);
|
|
3597
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
3598
|
+
} catch {
|
|
3599
|
+
return null;
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3600
3602
|
//#endregion
|
|
3601
3603
|
//#region src/utils/sleep.ts
|
|
3602
3604
|
/**
|